1
0
mirror of https://github.com/janet-lang/janet synced 2025-11-22 10:14:49 +00:00

Compare commits

...

69 Commits

Author SHA1 Message Date
Calvin Rose
eecffe01a5 Add some more test cases for hex floats. 2025-02-01 08:09:57 -06:00
Calvin Rose
f63a33884f Add hexfloats - Address #1550 2025-01-30 07:36:18 -06:00
Calvin Rose
fa75a395cb Add support for buffer peg literals - address #1549 2025-01-26 09:48:48 -06:00
Calvin Rose
1f34ec9902 Merge pull request #1547 from pyrmont/bugfix.codeql-upgrade
Update CodeQL actions to latest version
2025-01-25 09:24:40 -08:00
Michael Camilleri
f75c08a78e Add 'tools: linked' to CodeQL workflow 2025-01-24 04:00:52 +09:00
Michael Camilleri
5e93f0e34b Trigger workflow to run again 2025-01-24 03:02:15 +09:00
Calvin Rose
49f151e265 Allocate parser with GC.
This fixes janet_dobytes, which manually allocated a parser on the
stack, and then possibly ran garbage collection before it was
deallocated.
2025-01-22 09:21:56 -06:00
Michael Camilleri
2b73a15ad8 Update CodeQL actions to latest version 2025-01-22 23:47:44 +09:00
Calvin Rose
06d581dde3 Fix #1546 - large ranges.
Raise an error for very large ranges instead of internal assert.
2025-01-20 09:02:22 -06:00
Calvin Rose
2b49903c82 Merge pull request #1544 from cideM/docstring-fixes
Fix docstrings for int/u64, int/s64 and int/to-number
2025-01-14 06:30:08 -08:00
Florian Beeres
a17ae977a5 docstring int/u64 int/s64: supports number as well
Update the docstrings of the u64 and s64 functions to indicate that they
work on numbers as well strings. Previously the docstring only mentioned
string support.
2025-01-11 22:51:21 +01:00
Florian Beeres
8a6b44cb4e docstring int/to-number: supports int64, not int32
Update the cfun_to_number docstring to indicate that it handles values
up to JANET_INTMAX_INT64 (75845c0283),
rather than up to int32, what the current docstring says.
2025-01-11 22:46:44 +01:00
Calvin Rose
60d9f97750 Address issue #1539 - infinite loop in peg split
Other looping rules ensure forward progress by terminating if an
iteration is at the same location as the previous iteration. Do the same
for split.
2025-01-01 11:28:10 -06:00
Calvin Rose
f252933f62 Merge pull request #1541 from rwtolbert/feature/win_arm64_dist
Brief: Add Arm64 .msi support on Windows
2025-01-01 09:19:57 -08:00
Calvin Rose
6dbd7b476c Merge pull request #1538 from sogaiu/tweak-exit-value-doc
Tweak *exit-value* doc - address #1537
2024-12-31 10:28:03 -08:00
Bob Tolbert
a47eb847fb Brief: Add Arm64 .msi support on Windows
Summary: Small update to add Windows on Arm64 support.

Also requires the latest version 3.14 of the WiX toolset.
2024-12-31 11:47:27 -06:00
Calvin Rose
ba5990ef21 Add switch to turn off "reuse" behavior for server sockets. 2024-12-31 09:05:54 -06:00
sogaiu
753911fe2d Tweak *exit-value* doc - address #1537 2024-12-27 16:02:45 +09:00
Calvin Rose
746ced5501 Revert behavior of deep= on mutable keys.
Mutable keys are a minefield for comparisons, as resolving
equality require re-implementing a lot of the internal structures, as
well as dealing with multiple mutable keys that are in the same
equivalency class by deep=.

Simplifying the implementation to not resole mutable keys is much
simpler, faster, and has the benefit that deep= and deep-not= do not
need to allocate.
2024-12-21 09:03:01 -06:00
Calvin Rose
1b49934e4f Allow table/to-struct to take a prototype.
Use this prototype struct in freeze.
2024-12-19 19:41:19 -06:00
Calvin Rose
682f0f584f freeze with mutable keys should be determinsic help address #1535 2024-12-19 19:31:01 -06:00
Calvin Rose
611b2a6c3a Add more test cases for #1535 2024-12-19 18:37:51 -06:00
Calvin Rose
8043caf581 Update CHANGELOG. 2024-12-19 18:31:05 -06:00
Calvin Rose
b2d2690eb9 Merge pull request #1534 from pyrmont/bugfix.windows-longstrings
Support dedenting long-strings with Windows EOLs
2024-12-19 16:24:21 -08:00
Calvin Rose
7f745a34c3 Allow for mutable keys correctly in deep= 2024-12-19 18:20:05 -06:00
Calvin Rose
b16cf17246 Merge pull request #1533 from pyrmont/feature.file-socket
Add `ev/to-file` for synchronous resource operations
2024-12-17 20:50:47 -08:00
Michael Camilleri
67e8518ba6 Support dedenting longstrings with Windows EOLs 2024-12-17 05:14:59 +09:00
Michael Camilleri
e94e8dc484 Remove special casing for MinGW 2024-12-16 08:12:14 +09:00
Michael Camilleri
1a24d4fc86 Raise error if using ev/to-file on MinGW 2024-12-15 21:00:52 +09:00
Michael Camilleri
6ee05785d1 Disable buffering for files created with ev/to-file 2024-12-15 20:37:58 +09:00
Michael Camilleri
268ff666d2 Move <fcntl.h> header to general imports 2024-12-15 20:02:41 +09:00
Michael Camilleri
91bb34c3bf Add missing <io.h> header for Windows 2024-12-15 19:17:48 +09:00
Michael Camilleri
17d5fb3210 Fix ev/to-file on Windows 2024-12-15 18:56:35 +09:00
Michael Camilleri
687b987f7e Add ev/to-file for synchronous resource operations 2024-12-15 17:38:01 +09:00
Calvin Rose
4daecc9a41 Prevent await inside janet_call - address #1531
This was partially implemented before, but not in the case where the
await or other signal itself was created by a C function. We have to
separate code paths for generating signals - one via normal returns in
janet_vm_continue, and the other via longjump. This adds handling for
the longjump case, as well as improved messaging.
2024-12-14 10:34:36 -06:00
Calvin Rose
a85eacadda Merge pull request #1532 from strangepete/fstat-directory-test
file/open: check if directory
2024-12-14 06:00:41 -08:00
peteee
ed63987fd1 use fileno()
Should use fileno() instead of direct
2024-12-14 07:17:28 -05:00
peteee
ff173047f4 file/open: check if directory
Adds fstat() directory test after fopen(), which can return non-NULL when passed a directory name on Linux
2024-12-13 00:20:44 -05:00
Calvin Rose
83e8aab289 Prepare for 1.37.1 release and fix CI. 2024-12-05 20:18:16 -06:00
Calvin Rose
85cb35e68f Prepare for 1.37.0 release. 2024-12-05 17:51:06 -06:00
Calvin Rose
5b79b48ae0 Address #1524 - fix meson cross compilation linking.
In the cross compilation case, we need to resolve our
dependencies on libc twice, once for the build machine and once for the
target machine. This includes pthreads, -libc, and android-spawn.
2024-12-03 21:05:37 -06:00
Calvin Rose
7c44127bcb Merge pull request #1526 from sogaiu/master
Additional tweak to address #1523
2024-12-02 05:46:00 -08:00
sogaiu
9338312103 Additional tweak to address #1523 2024-12-02 11:21:56 +09:00
Calvin Rose
a0eeb630e7 Correct documentation for issue #1523
net/* API documentation was not consistent with the implementation. The
`ev/*` module documentation was, however. On timeout, all networking
function calls raise an error and do not return nil. That was the old
behavior.
2024-12-01 09:04:03 -06:00
Calvin Rose
6535d72bd4 Merge pull request #1522 from sogaiu/remove-pstatus
Remove unused var pstatus
2024-11-25 06:15:43 -08:00
sogaiu
a7d424bc81 Remove unused var pstatus 2024-11-25 12:39:53 +09:00
Calvin Rose
2bceba4a7a Assertf with no arguments does not make sense. 2024-11-24 19:14:18 -06:00
Calvin Rose
e3159bb0f5 Update CHANGELOG.md 2024-11-23 10:29:03 -06:00
Calvin Rose
5d1bd8a932 Add an extra has mix round to string hashes.
This should improve hashing quality of strings.
2024-11-17 11:31:12 -06:00
Calvin Rose
bafa6bfff0 Merge pull request #1519 from ianthehenry/fix-string-equal-with-byteview
fix janet_string_equalconst
2024-11-17 07:33:47 -08:00
Ian Henry
e2eb7ab4b2 fix janet_string_equalconst
Check string length before pointer equality, so that a string is not considered
equal to a prefix slice of itself.
2024-11-16 21:20:26 -08:00
Calvin Rose
9f4497a5ae Merge pull request #1518 from pyrmont/bugfix.s390x-workflow
Update Docker command to use `--platform` flag
2024-11-11 12:24:20 -08:00
Michael Camilleri
70de8bf092 Update Docker command to use --platform flag 2024-11-12 04:02:54 +09:00
Calvin Rose
e52575e23a Merge pull request #1517 from sogaiu/add-assertf
Add assertf and use in boot.janet. Address #1516
2024-10-31 07:27:05 -07:00
sogaiu
10994cbc6a Add some tests for assertf 2024-10-30 23:41:31 +09:00
sogaiu
abad9d7db9 Add assertf and use in boot.janet. Address #1516 2024-10-30 17:43:00 +09:00
Calvin Rose
5e443cd29d Merge pull request #1514 from ArtSin/fix-formatb-int32_t-arg
Cast arguments to `int32_t` before passing to `janet_formatb` with `%d` format specifier
2024-10-25 05:36:08 -07:00
Calvin Rose
7bf3a9d24c Merge pull request #1515 from sogaiu/tweak-install-info-in-readme
Clarify installation info a bit
2024-10-25 05:34:53 -07:00
sogaiu
d80a7094ae Clarify installation info a bit 2024-10-25 20:04:56 +09:00
ArtSin
ad77bc391c Cast arguments to int32_t before passing to janet_formatb with %d format specifier
`s->line` and `s->column` in `delim_error` are `size_t`, which is typically 64-bit, but `va_arg` in `janet_formatbv` reads `int32_t` for `%d`.
2024-10-20 12:03:40 +04:00
Calvin Rose
2b84fb14b4 Fix Issue #1512 2024-10-18 18:17:06 -05:00
Calvin Rose
07155ce657 Don't error on empty struct. 2024-10-18 17:53:21 -05:00
Calvin Rose
046d28662d Merge pull request #1513 from sogaiu/add-nth-and-only-tags-to-changelog
Mention nth and only-tags in changelog
2024-10-18 05:45:36 -07:00
sogaiu
84dd3db620 Mention nth and only-tags in changelog 2024-10-16 14:16:05 +09:00
Calvin Rose
282f2671ea Formatting. 2024-10-11 20:10:46 -05:00
Calvin Rose
3fc2be3e6e Use _Exit since it is standard in c99 2024-10-11 20:10:04 -05:00
Calvin Rose
d10c1fe759 Use msvc compiler intrinsics for atomics.
This will let us use GCC atomics on mingw.
2024-10-11 20:03:06 -05:00
Calvin Rose
d18472b07d More CI testing - add meson min build for windows. 2024-10-10 20:42:12 -05:00
Calvin Rose
43a68dcd2a Include windows.h for atomics always in capi.c 2024-10-10 20:32:28 -05:00
38 changed files with 812 additions and 477 deletions

View File

@@ -19,3 +19,8 @@ tasks:
ninja ninja
ninja test ninja test
sudo ninja install sudo ninja install
- meson_min: |
cd janet
meson setup build_meson_min --buildtype=release -Dsingle_threaded=true -Dnanbox=false -Ddynamic_modules=false -Ddocstrings=false -Dnet=false -Dsourcemaps=false -Dpeg=false -Dassembler=false -Dint_types=false -Dreduced_os=true -Dffi=false
cd build_meson_min
ninja

View File

@@ -27,15 +27,16 @@ jobs:
uses: actions/checkout@v3 uses: actions/checkout@v3
- name: Initialize CodeQL - name: Initialize CodeQL
uses: github/codeql-action/init@v2 uses: github/codeql-action/init@v3
with: with:
languages: ${{ matrix.language }} languages: ${{ matrix.language }}
queries: +security-and-quality queries: +security-and-quality
tools: linked
- name: Autobuild - name: Autobuild
uses: github/codeql-action/autobuild@v2 uses: github/codeql-action/autobuild@v3
- name: Perform CodeQL Analysis - name: Perform CodeQL Analysis
uses: github/codeql-action/analyze@v2 uses: github/codeql-action/analyze@v3
with: with:
category: "/language:${{ matrix.language }}" category: "/language:${{ matrix.language }}"

View File

@@ -38,28 +38,61 @@ jobs:
- name: Test the project - name: Test the project
shell: cmd shell: cmd
run: build_win test run: build_win test
- name: Test installer build
shell: cmd
run: build_win dist
test-windows-min:
name: Build and test on Windows Minimal build
strategy:
matrix:
os: [ windows-2019 ]
runs-on: ${{ matrix.os }}
steps:
- name: Checkout the repository
uses: actions/checkout@master
- name: Setup MSVC
uses: ilammy/msvc-dev-cmd@v1
- name: Setup Python
uses: actions/setup-python@v2
with:
python-version: '3.x'
- name: Install Python Dependencies
run: pip install meson ninja
- name: Build
shell: cmd
run: |
meson setup build_meson_min --buildtype=release -Dsingle_threaded=true -Dnanbox=false -Ddynamic_modules=false -Ddocstrings=false -Dnet=false -Dsourcemaps=false -Dpeg=false -Dassembler=false -Dint_types=false -Dreduced_os=true -Dffi=false
cd build_meson_min
ninja
test-mingw: test-mingw:
name: Build on Windows with Mingw (no test yet) name: Build on Windows with Mingw
runs-on: windows-latest runs-on: windows-latest
defaults: defaults:
run: run:
shell: msys2 {0} shell: msys2 {0}
strategy:
matrix:
msystem: [ UCRT64, CLANG64 ]
steps: steps:
- name: Checkout the repository - name: Checkout the repository
uses: actions/checkout@master uses: actions/checkout@master
- name: Setup Mingw - name: Setup Mingw
uses: msys2/setup-msys2@v2 uses: msys2/setup-msys2@v2
with: with:
msystem: UCRT64 msystem: ${{ matrix.msystem }}
update: true update: true
install: >- install: >-
base-devel base-devel
git git
gcc gcc
- name: Build the project - name: Build
shell: cmd shell: cmd
run: make -j4 CC=gcc JANET_NO_AMALG=1 run: make -j4 CC=gcc
- name: Test
shell: cmd
run: make -j4 CC=gcc test
test-mingw-linux: test-mingw-linux:
name: Build and test with Mingw on Linux + Wine name: Build and test with Mingw on Linux + Wine
@@ -102,4 +135,4 @@ jobs:
- name: Do Qemu build and test - name: Do Qemu build and test
run: | run: |
docker run --rm --privileged multiarch/qemu-user-static --reset -p yes docker run --rm --privileged multiarch/qemu-user-static --reset -p yes
docker run --rm -v .:/janet s390x/ubuntu bash -c "apt-get -y update && apt-get -y install git build-essential && cd /janet && make -j3 && make test" docker run --rm -v .:/janet --platform linux/s390x ubuntu bash -c "apt-get -y update && apt-get -y install git build-essential && cd /janet && make -j3 && make test"

View File

@@ -2,10 +2,24 @@
All notable changes to this project will be documented in this file. All notable changes to this project will be documented in this file.
## ??? - Unreleased ## ??? - Unreleased
- Add `struct/rawget`
- Fix `deep=` and `deep-not=` to better handle degenerate cases with mutable table keys
- Long strings will now dedent on `\r\n` instead of just `\n`.
- Add `ev/to-file` for synchronous resource operations
## 1.37.1 - 2024-12-05
- Fix meson cross compilation
- Update timeout documentation for networking APIs: timeouts raise errors and do not return nil.
- Add `janet_addtimeout_nil(double sec);` to the C API.
- Change string hashing.
- Fix string equality bug.
- Add `assertf`
- Change how JANET_PROFILE is loaded to allow more easily customizing the environment. - Change how JANET_PROFILE is loaded to allow more easily customizing the environment.
- Add `*repl-prompt*` dynamic binding to allow customizing the built in repl. - Add `*repl-prompt*` dynamic binding to allow customizing the built in repl.
- Add multiple path support in the `JANET_PATH` environment variables. This lets - Add multiple path support in the `JANET_PATH` environment variables. This lets
user more easily import modules from many directories. user more easily import modules from many directories.
- Add `nth` and `only-tags` PEG specials to select from sub-captures while
dropping the rest.
## 1.36.0 - 2024-09-07 ## 1.36.0 - 2024-09-07
- Improve error messages in `bundle/add*` functions. - Improve error messages in `bundle/add*` functions.

View File

@@ -207,7 +207,7 @@ Alternatively, install the package directly with `pkgin install janet`.
To build an `.msi` installer executable, in addition to the above steps, you will have to: To build an `.msi` installer executable, in addition to the above steps, you will have to:
5. Install, or otherwise add to your PATH the [WiX 3.11 Toolset](https://github.com/wixtoolset/wix3/releases). 5. Install, or otherwise add to your PATH the [WiX 3.14 Toolset](https://github.com/wixtoolset/wix3/releases).
6. Run `build_win dist`. 6. Run `build_win dist`.
Now you should have an `.msi`. You can run `build_win install` to install the `.msi`, or execute the file itself. Now you should have an `.msi`. You can run `build_win install` to install the `.msi`, or execute the file itself.
@@ -250,8 +250,10 @@ Emacs, and Atom each have syntax packages for the Janet language, though.
## Installation ## Installation
See the [Introduction](https://janet-lang.org/docs/index.html) for more details. If you just want If you just want to try out the language, you don't need to install anything.
to try out the language, you don't need to install anything. You can also move the `janet` executable wherever you want on your system and run it. In this case you can also move the `janet` executable wherever you want on
your system and run it. However, for a fuller setup, please see the
[Introduction](https://janet-lang.org/docs/index.html) for more details.
## Usage ## Usage

View File

@@ -91,7 +91,7 @@ exit /b 0
@rem Clean build artifacts @rem Clean build artifacts
:CLEAN :CLEAN
del *.exe *.lib *.exp del *.exe *.lib *.exp *.msi *.wixpdb
rd /s /q build rd /s /q build
if exist dist ( if exist dist (
rd /s /q dist rd /s /q dist
@@ -138,11 +138,18 @@ if defined APPVEYOR_REPO_TAG_NAME (
set RELEASE_VERSION=%JANET_VERSION% set RELEASE_VERSION=%JANET_VERSION%
) )
if defined CI ( if defined CI (
set WIXBIN="c:\Program Files (x86)\WiX Toolset v3.11\bin\" set WIXBIN="%WIX%bin\"
echo WIXBIN = %WIXBIN%
) else ( ) else (
set WIXBIN= set WIXBIN=
) )
%WIXBIN%candle.exe tools\msi\janet.wxs -arch %BUILDARCH% -out build\
set WIXARCH=%BUILDARCH%
if "%WIXARCH%"=="aarch64" (
set WIXARCH=arm64
)
%WIXBIN%candle.exe tools\msi\janet.wxs -arch %WIXARCH% -out build\
%WIXBIN%light.exe "-sice:ICE38" -b tools\msi -ext WixUIExtension build\janet.wixobj -out janet-%RELEASE_VERSION%-windows-%BUILDARCH%-installer.msi %WIXBIN%light.exe "-sice:ICE38" -b tools\msi -ext WixUIExtension build\janet.wixobj -out janet-%RELEASE_VERSION%-windows-%BUILDARCH%-installer.msi
exit /b 0 exit /b 0

View File

@@ -20,14 +20,23 @@
project('janet', 'c', project('janet', 'c',
default_options : ['c_std=c99', 'build.c_std=c99', 'b_lundef=false', 'default_library=both'], default_options : ['c_std=c99', 'build.c_std=c99', 'b_lundef=false', 'default_library=both'],
version : '1.37.0') version : '1.37.1')
# Global settings # Global settings
janet_path = join_paths(get_option('prefix'), get_option('libdir'), 'janet') janet_path = join_paths(get_option('prefix'), get_option('libdir'), 'janet')
header_path = join_paths(get_option('prefix'), get_option('includedir'), 'janet') header_path = join_paths(get_option('prefix'), get_option('includedir'), 'janet')
# Link math library on all systems # Compilers
cc = meson.get_compiler('c') cc = meson.get_compiler('c')
native_cc = meson.get_compiler('c', native : true)
# Native deps
native_m_dep = native_cc.find_library('m', required : false)
native_dl_dep = native_cc.find_library('dl', required : false)
native_android_spawn_dep = native_cc.find_library('android-spawn', required : false)
native_thread_dep = dependency('threads', native : true)
# Deps
m_dep = cc.find_library('m', required : false) m_dep = cc.find_library('m', required : false)
dl_dep = cc.find_library('dl', required : false) dl_dep = cc.find_library('dl', required : false)
android_spawn_dep = cc.find_library('android-spawn', required : false) android_spawn_dep = cc.find_library('android-spawn', required : false)
@@ -164,11 +173,18 @@ mainclient_src = [
'src/mainclient/shell.c' 'src/mainclient/shell.c'
] ]
janet_dependencies = [m_dep, dl_dep, android_spawn_dep]
janet_native_dependencies = [native_m_dep, native_dl_dep, native_android_spawn_dep]
if not get_option('single_threaded')
janet_dependencies += thread_dep
janet_native_dependencies += native_thread_dep
endif
# Build boot binary # Build boot binary
janet_boot = executable('janet-boot', core_src, boot_src, janet_boot = executable('janet-boot', core_src, boot_src,
include_directories : incdir, include_directories : incdir,
c_args : '-DJANET_BOOTSTRAP', c_args : '-DJANET_BOOTSTRAP',
dependencies : [m_dep, dl_dep, thread_dep, android_spawn_dep], dependencies : janet_native_dependencies,
native : true) native : true)
# Build janet.c # Build janet.c
@@ -181,11 +197,6 @@ janetc = custom_target('janetc',
'JANET_PATH', janet_path 'JANET_PATH', janet_path
]) ])
janet_dependencies = [m_dep, dl_dep, android_spawn_dep]
if not get_option('single_threaded')
janet_dependencies += thread_dep
endif
# Allow building with no shared library # Allow building with no shared library
if cc.has_argument('-fvisibility=hidden') if cc.has_argument('-fvisibility=hidden')
lib_cflags = ['-fvisibility=hidden'] lib_cflags = ['-fvisibility=hidden']
@@ -231,7 +242,7 @@ if meson.is_cross_build()
endif endif
janet_nativeclient = executable('janet-native', janetc, mainclient_src, janet_nativeclient = executable('janet-native', janetc, mainclient_src,
include_directories : incdir, include_directories : incdir,
dependencies : janet_dependencies, dependencies : janet_native_dependencies,
c_args : extra_native_cflags, c_args : extra_native_cflags,
native : true) native : true)
else else

View File

@@ -204,6 +204,16 @@
[fmt & args] [fmt & args]
(error (string/format fmt ;args))) (error (string/format fmt ;args)))
(defmacro assertf
"Convenience macro that combines `assert` and `string/format`."
[x fmt & args]
(def v (gensym))
~(do
(def ,v ,x)
(if ,v
,v
(,errorf ,fmt ,;args))))
(defmacro default (defmacro default
``Define a default value for an optional argument. ``Define a default value for an optional argument.
Expands to `(def sym (if (= nil sym) val sym))`.`` Expands to `(def sym (if (= nil sym) val sym))`.``
@@ -1301,7 +1311,7 @@
(defdyn *redef* "When set, allow dynamically rebinding top level defs. Will slow generated code and is intended to be used for development.") (defdyn *redef* "When set, allow dynamically rebinding top level defs. Will slow generated code and is intended to be used for development.")
(defdyn *debug* "Enables a built in debugger on errors and other useful features for debugging in a repl.") (defdyn *debug* "Enables a built in debugger on errors and other useful features for debugging in a repl.")
(defdyn *exit* "When set, will cause the current context to complete. Can be set to exit from repl (or file), for example.") (defdyn *exit* "When set, will cause the current context to complete. Can be set to exit from repl (or file), for example.")
(defdyn *exit-value* "Set the return value from `run-context` upon an exit. By default, `run-context` will return nil.") (defdyn *exit-value* "Set the return value from `run-context` upon an exit.")
(defdyn *task-id* "When spawning a thread or fiber, the task-id can be assigned for concurrency control.") (defdyn *task-id* "When spawning a thread or fiber, the task-id can be assigned for concurrency control.")
(defdyn *current-file* (defdyn *current-file*
@@ -2209,56 +2219,31 @@
(map-template :some res pred ind inds) (map-template :some res pred ind inds)
res) res)
(defn deep-not=
``Like `not=`, but mutable types (arrays, tables, buffers) are considered
equal if they have identical structure. Much slower than `not=`.``
[x y]
(def tx (type x))
(or
(not= tx (type y))
(case tx
:tuple (or (not= (length x) (length y))
(do
(var ret false)
(forv i 0 (length x)
(def xx (in x i))
(def yy (in y i))
(if (deep-not= xx yy)
(break (set ret true))))
ret))
:array (or (not= (length x) (length y))
(do
(var ret false)
(forv i 0 (length x)
(def xx (in x i))
(def yy (in y i))
(if (deep-not= xx yy)
(break (set ret true))))
ret))
:struct (deep-not= (kvs x) (kvs y))
:table (deep-not= (table/to-struct x) (table/to-struct y))
:buffer (not= (string x) (string y))
(not= x y))))
(defn deep=
``Like `=`, but mutable types (arrays, tables, buffers) are considered
equal if they have identical structure. Much slower than `=`.``
[x y]
(not (deep-not= x y)))
(defn freeze (defn freeze
`Freeze an object (make it immutable) and do a deep copy, making `Freeze an object (make it immutable) and do a deep copy, making
child values also immutable. Closures, fibers, and abstract types child values also immutable. Closures, fibers, and abstract types
will not be recursively frozen, but all other types will.` will not be recursively frozen, but all other types will.`
[x] [x]
(case (type x) (def tx (type x))
:array (tuple/slice (map freeze x)) (cond
:tuple (tuple/slice (map freeze x)) (or (= tx :array) (= tx :tuple))
:table (if-let [p (table/getproto x)] (tuple/slice (map freeze x))
(freeze (merge (table/clone p) x))
(struct ;(map freeze (kvs x)))) (or (= tx :table) (= tx :struct))
:struct (struct ;(map freeze (kvs x))) (let [temp-tab @{}]
:buffer (string x) # Handle multiple unique keys that freeze. Result should
# be independent of iteration order.
(eachp [k v] x
(def kk (freeze k))
(def vv (freeze v))
(def old (get temp-tab kk))
(def new (if (= nil old) vv (max vv old)))
(put temp-tab kk new))
(table/to-struct temp-tab (freeze (getproto x))))
(= tx :buffer)
(string x)
x)) x))
(defn thaw (defn thaw
@@ -2274,6 +2259,41 @@
:string (buffer ds) :string (buffer ds)
ds)) ds))
(defn deep-not=
``Like `not=`, but mutable types (arrays, tables, buffers) are considered
equal if they have identical structure. Much slower than `not=`.``
[x y]
(def tx (type x))
(or
(not= tx (type y))
(cond
(or (= tx :tuple) (= tx :array))
(or (not= (length x) (length y))
(do
(var ret false)
(forv i 0 (length x)
(def xx (in x i))
(def yy (in y i))
(if (deep-not= xx yy)
(break (set ret true))))
ret))
(or (= tx :struct) (= tx :table))
(or (not= (length x) (length y))
(do
(def rawget (if (= tx :struct) struct/rawget table/rawget))
(var ret false)
(eachp [k v] x
(if (deep-not= (rawget y k) v) (break (set ret true))))
ret))
(= tx :buffer) (not= 0 (- (length x) (length y)) (memcmp x y))
(not= x y))))
(defn deep=
``Like `=`, but mutable types (arrays, tables, buffers) are considered
equal if they have identical structure. Much slower than `=`.``
[x y]
(not (deep-not= x y)))
(defn macex (defn macex
``Expand macros completely. ``Expand macros completely.
`on-binding` is an optional callback for whenever a normal symbolic binding `on-binding` is an optional callback for whenever a normal symbolic binding
@@ -2655,7 +2675,6 @@
(do (do
(var pindex 0) (var pindex 0)
(var pstatus nil)
(def len (length buf)) (def len (length buf))
(when (= len 0) (when (= len 0)
(:eof p) (:eof p)
@@ -3934,7 +3953,7 @@
(defn make-sig [] (defn make-sig []
(ffi/signature :default real-ret-type ;computed-type-args)) (ffi/signature :default real-ret-type ;computed-type-args))
(defn make-ptr [] (defn make-ptr []
(assert (ffi/lookup (if lazy (llib) lib) raw-symbol) (string "failed to find ffi symbol " raw-symbol))) (assertf (ffi/lookup (if lazy (llib) lib) raw-symbol) "failed to find ffi symbol %v" raw-symbol))
(if lazy (if lazy
~(defn ,alias ,;meta [,;formal-args] ~(defn ,alias ,;meta [,;formal-args]
(,ffi/call (,(delay (make-ptr))) (,(delay (make-sig))) ,;formal-args)) (,ffi/call (,(delay (make-ptr))) (,(delay (make-sig))) ,;formal-args))
@@ -4111,7 +4130,7 @@
"Get the manifest for a give installed bundle" "Get the manifest for a give installed bundle"
[bundle-name] [bundle-name]
(def name (get-manifest-filename bundle-name)) (def name (get-manifest-filename bundle-name))
(assert (fexists name) (string "no bundle " bundle-name " found")) (assertf (fexists name) "no bundle %v found" bundle-name)
(parse (slurp name))) (parse (slurp name)))
(defn- get-bundle-module (defn- get-bundle-module
@@ -4254,11 +4273,9 @@
(def missing (seq [d :in deps :when (not (bundle/installed? d))] (string d))) (def missing (seq [d :in deps :when (not (bundle/installed? d))] (string d)))
(when (next missing) (errorf "missing dependencies %s" (string/join missing ", ")))) (when (next missing) (errorf "missing dependencies %s" (string/join missing ", "))))
(def bundle-name (get config :name default-bundle-name)) (def bundle-name (get config :name default-bundle-name))
(assert bundle-name (errorf "unable to infer bundle name for %v, use :name argument" path)) (assertf bundle-name "unable to infer bundle name for %v, use :name argument" path)
(assert (not (string/check-set "\\/" bundle-name)) (assertf (not (string/check-set "\\/" bundle-name))
(string "bundle name " "bundle name %v cannot contain path separators" bundle-name)
bundle-name
" cannot contain path separators"))
(assert (next bundle-name) "cannot use empty bundle-name") (assert (next bundle-name) "cannot use empty bundle-name")
(assert (not (fexists (get-manifest-filename bundle-name))) (assert (not (fexists (get-manifest-filename bundle-name)))
"bundle is already installed") "bundle is already installed")
@@ -4310,7 +4327,7 @@
(var i 0) (var i 0)
(def man (bundle/manifest bundle-name)) (def man (bundle/manifest bundle-name))
(def files (get man :files @[])) (def files (get man :files @[]))
(assert (os/mkdir dest-dir) (string "could not create directory " dest-dir " (or it already exists)")) (assertf (os/mkdir dest-dir) "could not create directory %v (or it already exists)" dest-dir)
(def s (sep)) (def s (sep))
(os/mkdir (string dest-dir s "bundle")) (os/mkdir (string dest-dir s "bundle"))
(def install-hook (string dest-dir s "bundle" s "init.janet")) (def install-hook (string dest-dir s "bundle" s "init.janet"))

View File

@@ -5,9 +5,9 @@
#define JANET_VERSION_MAJOR 1 #define JANET_VERSION_MAJOR 1
#define JANET_VERSION_MINOR 37 #define JANET_VERSION_MINOR 37
#define JANET_VERSION_PATCH 0 #define JANET_VERSION_PATCH 1
#define JANET_VERSION_EXTRA "-dev" #define JANET_VERSION_EXTRA ""
#define JANET_VERSION "1.37.0-dev" #define JANET_VERSION "1.37.1"
/* #define JANET_BUILD "local" */ /* #define JANET_BUILD "local" */

View File

@@ -31,11 +31,13 @@
#ifndef JANET_SINGLE_THREADED #ifndef JANET_SINGLE_THREADED
#ifndef JANET_WINDOWS #ifndef JANET_WINDOWS
#include <pthread.h> #include <pthread.h>
#else
#include <windows.h>
#endif #endif
#endif #endif
#ifdef JANET_WINDOWS
#include <windows.h>
#endif
#ifdef JANET_USE_STDATOMIC #ifdef JANET_USE_STDATOMIC
#include <stdatomic.h> #include <stdatomic.h>
/* We don't need stdatomic on most compilers since we use compiler builtins for atomic operations. /* We don't need stdatomic on most compilers since we use compiler builtins for atomic operations.
@@ -60,6 +62,13 @@ JANET_NO_RETURN static void janet_top_level_signal(const char *msg) {
void janet_signalv(JanetSignal sig, Janet message) { void janet_signalv(JanetSignal sig, Janet message) {
if (janet_vm.return_reg != NULL) { if (janet_vm.return_reg != NULL) {
/* Should match logic in janet_call for coercing everything not ok to an error (no awaits, yields, etc.) */
if (janet_vm.coerce_error && sig != JANET_SIGNAL_OK) {
if (sig != JANET_SIGNAL_ERROR) {
message = janet_wrap_string(janet_formatc("%v coerced from %s to error", message, janet_signal_names[sig]));
}
sig = JANET_SIGNAL_ERROR;
}
*janet_vm.return_reg = message; *janet_vm.return_reg = message;
if (NULL != janet_vm.fiber) { if (NULL != janet_vm.fiber) {
janet_vm.fiber->flags |= JANET_FIBER_DID_LONGJUMP; janet_vm.fiber->flags |= JANET_FIBER_DID_LONGJUMP;
@@ -546,8 +555,8 @@ void *janet_optabstract(const Janet *argv, int32_t argc, int32_t n, const JanetA
/* Atomic refcounts */ /* Atomic refcounts */
JanetAtomicInt janet_atomic_inc(JanetAtomicInt volatile *x) { JanetAtomicInt janet_atomic_inc(JanetAtomicInt volatile *x) {
#ifdef JANET_WINDOWS #ifdef _MSC_VER
return InterlockedIncrement(x); return _InterlockedIncrement(x);
#elif defined(JANET_USE_STDATOMIC) #elif defined(JANET_USE_STDATOMIC)
return atomic_fetch_add_explicit(x, 1, memory_order_relaxed) + 1; return atomic_fetch_add_explicit(x, 1, memory_order_relaxed) + 1;
#else #else
@@ -556,8 +565,8 @@ JanetAtomicInt janet_atomic_inc(JanetAtomicInt volatile *x) {
} }
JanetAtomicInt janet_atomic_dec(JanetAtomicInt volatile *x) { JanetAtomicInt janet_atomic_dec(JanetAtomicInt volatile *x) {
#ifdef JANET_WINDOWS #ifdef _MSC_VER
return InterlockedDecrement(x); return _InterlockedDecrement(x);
#elif defined(JANET_USE_STDATOMIC) #elif defined(JANET_USE_STDATOMIC)
return atomic_fetch_add_explicit(x, -1, memory_order_acq_rel) - 1; return atomic_fetch_add_explicit(x, -1, memory_order_acq_rel) - 1;
#else #else
@@ -566,8 +575,8 @@ JanetAtomicInt janet_atomic_dec(JanetAtomicInt volatile *x) {
} }
JanetAtomicInt janet_atomic_load(JanetAtomicInt volatile *x) { JanetAtomicInt janet_atomic_load(JanetAtomicInt volatile *x) {
#ifdef JANET_WINDOWS #ifdef _MSC_VER
return InterlockedOr(x, 0); return _InterlockedOr(x, 0);
#elif defined(JANET_USE_STDATOMIC) #elif defined(JANET_USE_STDATOMIC)
return atomic_load_explicit(x, memory_order_acquire); return atomic_load_explicit(x, memory_order_acquire);
#else #else

View File

@@ -449,8 +449,9 @@ JANET_CORE_FN(janet_core_range,
} }
count = (count > 0) ? count : 0; count = (count > 0) ? count : 0;
int32_t int_count; int32_t int_count;
janet_assert(count >= 0, "bad range code");
if (count > (double) INT32_MAX) { if (count > (double) INT32_MAX) {
int_count = INT32_MAX; janet_panicf("range is too large, %f elements", count);
} else { } else {
int_count = (int32_t) ceil(count); int_count = (int32_t) ceil(count);
} }

View File

@@ -32,9 +32,11 @@
#ifdef JANET_EV #ifdef JANET_EV
#include <math.h> #include <math.h>
#include <fcntl.h>
#ifdef JANET_WINDOWS #ifdef JANET_WINDOWS
#include <winsock2.h> #include <winsock2.h>
#include <windows.h> #include <windows.h>
#include <io.h>
#else #else
#include <pthread.h> #include <pthread.h>
#include <limits.h> #include <limits.h>
@@ -43,7 +45,6 @@
#include <signal.h> #include <signal.h>
#include <sys/ioctl.h> #include <sys/ioctl.h>
#include <sys/types.h> #include <sys/types.h>
#include <fcntl.h>
#include <netinet/in.h> #include <netinet/in.h>
#include <netinet/tcp.h> #include <netinet/tcp.h>
#include <netdb.h> #include <netdb.h>
@@ -625,6 +626,18 @@ void janet_addtimeout(double sec) {
add_timeout(to); add_timeout(to);
} }
/* Set timeout for the current root fiber but resume with nil instead of raising an error */
void janet_addtimeout_nil(double sec) {
JanetFiber *fiber = janet_vm.root_fiber;
JanetTimeout to;
to.when = ts_delta(ts_now(), sec);
to.fiber = fiber;
to.curr_fiber = NULL;
to.sched_id = fiber->sched_id;
to.is_error = 0;
add_timeout(to);
}
void janet_ev_inc_refcount(void) { void janet_ev_inc_refcount(void) {
janet_atomic_inc(&janet_vm.listener_count); janet_atomic_inc(&janet_vm.listener_count);
} }
@@ -3263,6 +3276,64 @@ JANET_CORE_FN(janet_cfun_rwlock_write_release,
return argv[0]; return argv[0];
} }
static JanetFile *get_file_for_stream(JanetStream *stream) {
int32_t flags = 0;
char fmt[4] = {0};
int index = 0;
if (stream->flags & JANET_STREAM_READABLE) {
flags |= JANET_FILE_READ;
janet_sandbox_assert(JANET_SANDBOX_FS_READ);
fmt[index++] = 'r';
}
if (stream->flags & JANET_STREAM_WRITABLE) {
flags |= JANET_FILE_WRITE;
janet_sandbox_assert(JANET_SANDBOX_FS_WRITE);
int currindex = index;
fmt[index++] = (currindex == 0) ? 'w' : '+';
}
if (index == 0) return NULL;
/* duplicate handle when converting stream to file */
#ifdef JANET_WINDOWS
int htype = 0;
if (fmt[0] == 'r' && fmt[1] == '+') {
htype = _O_RDWR;
} else if (fmt[0] == 'r') {
htype = _O_RDONLY;
} else if (fmt[0] == 'w') {
htype = _O_WRONLY;
}
int fd = _open_osfhandle((intptr_t) stream->handle, htype);
if (fd < 0) return NULL;
int fd_dup = _dup(fd);
if (fd_dup < 0) return NULL;
FILE *f = _fdopen(fd_dup, fmt);
if (NULL == f) {
_close(fd_dup);
return NULL;
}
#else
int fd_dup = dup(stream->handle);
if (fd_dup < 0) return NULL;
FILE *f = fdopen(fd_dup, fmt);
if (NULL == f) {
close(fd_dup);
return NULL;
}
#endif
return janet_makejfile(f, flags);
}
JANET_CORE_FN(janet_cfun_to_file,
"(ev/to-file)",
"Create core/file copy of the stream. This value can be used "
"when blocking IO behavior is needed.") {
janet_fixarity(argc, 1);
JanetStream *stream = janet_getabstract(argv, 0, &janet_stream_type);
JanetFile *iof = get_file_for_stream(stream);
if (iof == NULL) janet_panic("cannot make file from stream");
return janet_wrap_abstract(iof);
}
JANET_CORE_FN(janet_cfun_ev_all_tasks, JANET_CORE_FN(janet_cfun_ev_all_tasks,
"(ev/all-tasks)", "(ev/all-tasks)",
"Get an array of all active fibers that are being used by the scheduler.") { "Get an array of all active fibers that are being used by the scheduler.") {
@@ -3307,6 +3378,7 @@ void janet_lib_ev(JanetTable *env) {
JANET_CORE_REG("ev/acquire-wlock", janet_cfun_rwlock_write_lock), JANET_CORE_REG("ev/acquire-wlock", janet_cfun_rwlock_write_lock),
JANET_CORE_REG("ev/release-rlock", janet_cfun_rwlock_read_release), JANET_CORE_REG("ev/release-rlock", janet_cfun_rwlock_read_release),
JANET_CORE_REG("ev/release-wlock", janet_cfun_rwlock_write_release), JANET_CORE_REG("ev/release-wlock", janet_cfun_rwlock_write_release),
JANET_CORE_REG("ev/to-file", janet_cfun_to_file),
JANET_CORE_REG("ev/all-tasks", janet_cfun_ev_all_tasks), JANET_CORE_REG("ev/all-tasks", janet_cfun_ev_all_tasks),
JANET_REG_END JANET_REG_END
}; };

View File

@@ -400,7 +400,7 @@ static JanetFFIStruct *build_struct_type(int32_t argc, const Janet *argv) {
JanetFFIStruct *st = janet_abstract(&janet_struct_type, JanetFFIStruct *st = janet_abstract(&janet_struct_type,
sizeof(JanetFFIStruct) + argc * sizeof(JanetFFIStructMember)); sizeof(JanetFFIStruct) + argc * sizeof(JanetFFIStructMember));
st->field_count = member_count; st->field_count = 0;
st->size = 0; st->size = 0;
st->align = 1; st->align = 1;
if (argc == 0) { if (argc == 0) {
@@ -418,6 +418,7 @@ static JanetFFIStruct *build_struct_type(int32_t argc, const Janet *argv) {
st->fields[i].type = decode_ffi_type(argv[j]); st->fields[i].type = decode_ffi_type(argv[j]);
size_t el_size = type_size(st->fields[i].type); size_t el_size = type_size(st->fields[i].type);
size_t el_align = type_align(st->fields[i].type); size_t el_align = type_align(st->fields[i].type);
if (el_align <= 0) janet_panicf("bad field type %V", argv[j]);
if (all_packed || pack_one) { if (all_packed || pack_one) {
if (st->size % el_align != 0) is_aligned = 0; if (st->size % el_align != 0) is_aligned = 0;
st->fields[i].offset = st->size; st->fields[i].offset = st->size;
@@ -433,6 +434,7 @@ static JanetFFIStruct *build_struct_type(int32_t argc, const Janet *argv) {
st->size += (st->align - 1); st->size += (st->align - 1);
st->size /= st->align; st->size /= st->align;
st->size *= st->align; st->size *= st->align;
st->field_count = member_count;
return st; return st;
} }

View File

@@ -154,8 +154,7 @@ static void watcher_callback_read(JanetFiber *fiber, JanetAsyncEvent event) {
janet_schedule(fiber, janet_wrap_nil()); janet_schedule(fiber, janet_wrap_nil());
janet_async_end(fiber); janet_async_end(fiber);
break; break;
case JANET_ASYNC_EVENT_ERR: case JANET_ASYNC_EVENT_ERR: {
{
janet_schedule(fiber, janet_wrap_nil()); janet_schedule(fiber, janet_wrap_nil());
janet_async_end(fiber); janet_async_end(fiber);
break; break;
@@ -163,8 +162,7 @@ static void watcher_callback_read(JanetFiber *fiber, JanetAsyncEvent event) {
read_more: read_more:
case JANET_ASYNC_EVENT_HUP: case JANET_ASYNC_EVENT_HUP:
case JANET_ASYNC_EVENT_INIT: case JANET_ASYNC_EVENT_INIT:
case JANET_ASYNC_EVENT_READ: case JANET_ASYNC_EVENT_READ: {
{
Janet name = janet_wrap_nil(); Janet name = janet_wrap_nil();
/* Assumption - read will never return partial events * /* Assumption - read will never return partial events *
@@ -273,7 +271,8 @@ static void janet_watcher_unlisten(JanetWatcher *watcher) {
#define WATCHFLAG_RECURSIVE 0x100000u #define WATCHFLAG_RECURSIVE 0x100000u
static const JanetWatchFlagName watcher_flags_windows[] = { static const JanetWatchFlagName watcher_flags_windows[] = {
{"all", {
"all",
FILE_NOTIFY_CHANGE_ATTRIBUTES | FILE_NOTIFY_CHANGE_ATTRIBUTES |
FILE_NOTIFY_CHANGE_CREATION | FILE_NOTIFY_CHANGE_CREATION |
FILE_NOTIFY_CHANGE_DIR_NAME | FILE_NOTIFY_CHANGE_DIR_NAME |
@@ -282,7 +281,8 @@ static const JanetWatchFlagName watcher_flags_windows[] = {
FILE_NOTIFY_CHANGE_LAST_WRITE | FILE_NOTIFY_CHANGE_LAST_WRITE |
FILE_NOTIFY_CHANGE_SECURITY | FILE_NOTIFY_CHANGE_SECURITY |
FILE_NOTIFY_CHANGE_SIZE | FILE_NOTIFY_CHANGE_SIZE |
WATCHFLAG_RECURSIVE}, WATCHFLAG_RECURSIVE
},
{"attributes", FILE_NOTIFY_CHANGE_ATTRIBUTES}, {"attributes", FILE_NOTIFY_CHANGE_ATTRIBUTES},
{"creation", FILE_NOTIFY_CHANGE_CREATION}, {"creation", FILE_NOTIFY_CHANGE_CREATION},
{"dir-name", FILE_NOTIFY_CHANGE_DIR_NAME}, {"dir-name", FILE_NOTIFY_CHANGE_DIR_NAME},
@@ -382,8 +382,7 @@ static void watcher_callback_read(JanetFiber *fiber, JanetAsyncEvent event) {
case JANET_ASYNC_EVENT_FAILED: case JANET_ASYNC_EVENT_FAILED:
janet_stream_close(ow->stream); janet_stream_close(ow->stream);
break; break;
case JANET_ASYNC_EVENT_COMPLETE: case JANET_ASYNC_EVENT_COMPLETE: {
{
if (!watcher->is_watching) { if (!watcher->is_watching) {
janet_stream_close(ow->stream); janet_stream_close(ow->stream);
break; break;

View File

@@ -191,21 +191,21 @@ Janet janet_wrap_u64(uint64_t x) {
JANET_CORE_FN(cfun_it_s64_new, JANET_CORE_FN(cfun_it_s64_new,
"(int/s64 value)", "(int/s64 value)",
"Create a boxed signed 64 bit integer from a string value.") { "Create a boxed signed 64 bit integer from a string value or a number.") {
janet_fixarity(argc, 1); janet_fixarity(argc, 1);
return janet_wrap_s64(janet_unwrap_s64(argv[0])); return janet_wrap_s64(janet_unwrap_s64(argv[0]));
} }
JANET_CORE_FN(cfun_it_u64_new, JANET_CORE_FN(cfun_it_u64_new,
"(int/u64 value)", "(int/u64 value)",
"Create a boxed unsigned 64 bit integer from a string value.") { "Create a boxed unsigned 64 bit integer from a string value or a number.") {
janet_fixarity(argc, 1); janet_fixarity(argc, 1);
return janet_wrap_u64(janet_unwrap_u64(argv[0])); return janet_wrap_u64(janet_unwrap_u64(argv[0]));
} }
JANET_CORE_FN(cfun_to_number, JANET_CORE_FN(cfun_to_number,
"(int/to-number value)", "(int/to-number value)",
"Convert an int/u64 or int/s64 to a number. Fails if the number is out of range for an int32.") { "Convert an int/u64 or int/s64 to a number. Fails if the number is out of range for an int64.") {
janet_fixarity(argc, 1); janet_fixarity(argc, 1);
if (janet_type(argv[0]) == JANET_ABSTRACT) { if (janet_type(argv[0]) == JANET_ABSTRACT) {
void *abst = janet_unwrap_abstract(argv[0]); void *abst = janet_unwrap_abstract(argv[0]);

View File

@@ -31,6 +31,7 @@
#ifndef JANET_WINDOWS #ifndef JANET_WINDOWS
#include <fcntl.h> #include <fcntl.h>
#include <sys/stat.h>
#include <sys/wait.h> #include <sys/wait.h>
#include <unistd.h> #include <unistd.h>
#endif #endif
@@ -164,6 +165,14 @@ JANET_CORE_FN(cfun_io_fopen,
} }
FILE *f = fopen((const char *)fname, (const char *)fmode); FILE *f = fopen((const char *)fname, (const char *)fmode);
if (f != NULL) { if (f != NULL) {
#ifndef JANET_WINDOWS
struct stat st;
fstat(fileno(f), &st);
if (S_ISDIR(st.st_mode)) {
fclose(f);
janet_panicf("cannot open directory: %s", fname);
}
#endif
size_t bufsize = janet_optsize(argv, argc, 2, BUFSIZ); size_t bufsize = janet_optsize(argv, argc, 2, BUFSIZ);
if (bufsize != BUFSIZ) { if (bufsize != BUFSIZ) {
int result = setvbuf(f, NULL, bufsize ? _IOFBF : _IONBF, bufsize); int result = setvbuf(f, NULL, bufsize ? _IOFBF : _IONBF, bufsize);

View File

@@ -578,9 +578,10 @@ JANET_CORE_FN(cfun_net_connect,
net_sched_connect(stream); net_sched_connect(stream);
} }
static const char *serverify_socket(JSock sfd) { static const char *serverify_socket(JSock sfd, int reuse) {
/* Set various socket options */ /* Set various socket options */
int enable = 1; int enable = 1;
if (reuse) {
if (setsockopt(sfd, SOL_SOCKET, SO_REUSEADDR, (char *) &enable, sizeof(int)) < 0) { if (setsockopt(sfd, SOL_SOCKET, SO_REUSEADDR, (char *) &enable, sizeof(int)) < 0) {
return "setsockopt(SO_REUSEADDR) failed"; return "setsockopt(SO_REUSEADDR) failed";
} }
@@ -589,6 +590,7 @@ static const char *serverify_socket(JSock sfd) {
return "setsockopt(SO_REUSEPORT) failed"; return "setsockopt(SO_REUSEPORT) failed";
} }
#endif #endif
}
janet_net_socknoblock(sfd); janet_net_socknoblock(sfd);
return NULL; return NULL;
} }
@@ -642,19 +644,21 @@ JANET_CORE_FN(cfun_net_shutdown,
} }
JANET_CORE_FN(cfun_net_listen, JANET_CORE_FN(cfun_net_listen,
"(net/listen host port &opt type)", "(net/listen host port &opt type no-reuse)",
"Creates a server. Returns a new stream that is neither readable nor " "Creates a server. Returns a new stream that is neither readable nor "
"writeable. Use net/accept or net/accept-loop be to handle connections and start the server. " "writeable. Use net/accept or net/accept-loop be to handle connections and start the server. "
"The type parameter specifies the type of network connection, either " "The type parameter specifies the type of network connection, either "
"a :stream (usually tcp), or :datagram (usually udp). If not specified, the default is " "a :stream (usually tcp), or :datagram (usually udp). If not specified, the default is "
":stream. The host and port arguments are the same as in net/address.") { ":stream. The host and port arguments are the same as in net/address. The last boolean parameter `no-reuse` will "
"disable the use of SO_REUSEADDR and SO_REUSEPORT when creating a server on some operating systems.") {
janet_sandbox_assert(JANET_SANDBOX_NET_LISTEN); janet_sandbox_assert(JANET_SANDBOX_NET_LISTEN);
janet_arity(argc, 2, 3); janet_arity(argc, 2, 4);
/* Get host, port, and handler*/ /* Get host, port, and handler*/
int socktype = janet_get_sockettype(argv, argc, 2); int socktype = janet_get_sockettype(argv, argc, 2);
int is_unix = 0; int is_unix = 0;
struct addrinfo *ai = janet_get_addrinfo(argv, 0, socktype, 1, &is_unix); struct addrinfo *ai = janet_get_addrinfo(argv, 0, socktype, 1, &is_unix);
int reuse = !(argc >= 4 && janet_truthy(argv[3]));
JSock sfd = JSOCKDEFAULT; JSock sfd = JSOCKDEFAULT;
#ifndef JANET_WINDOWS #ifndef JANET_WINDOWS
@@ -664,7 +668,7 @@ JANET_CORE_FN(cfun_net_listen,
janet_free(ai); janet_free(ai);
janet_panicf("could not create socket: %V", janet_ev_lasterr()); janet_panicf("could not create socket: %V", janet_ev_lasterr());
} }
const char *err = serverify_socket(sfd); const char *err = serverify_socket(sfd, reuse);
if (NULL != err || bind(sfd, (struct sockaddr *)ai, sizeof(struct sockaddr_un))) { if (NULL != err || bind(sfd, (struct sockaddr *)ai, sizeof(struct sockaddr_un))) {
JSOCKCLOSE(sfd); JSOCKCLOSE(sfd);
janet_free(ai); janet_free(ai);
@@ -687,7 +691,7 @@ JANET_CORE_FN(cfun_net_listen,
sfd = socket(rp->ai_family, rp->ai_socktype | JSOCKFLAGS, rp->ai_protocol); sfd = socket(rp->ai_family, rp->ai_socktype | JSOCKFLAGS, rp->ai_protocol);
#endif #endif
if (!JSOCKVALID(sfd)) continue; if (!JSOCKVALID(sfd)) continue;
const char *err = serverify_socket(sfd); const char *err = serverify_socket(sfd, reuse);
if (NULL != err) { if (NULL != err) {
JSOCKCLOSE(sfd); JSOCKCLOSE(sfd);
continue; continue;
@@ -829,7 +833,7 @@ JANET_CORE_FN(cfun_stream_accept_loop,
JANET_CORE_FN(cfun_stream_accept, JANET_CORE_FN(cfun_stream_accept,
"(net/accept stream &opt timeout)", "(net/accept stream &opt timeout)",
"Get the next connection on a server stream. This would usually be called in a loop in a dedicated fiber. " "Get the next connection on a server stream. This would usually be called in a loop in a dedicated fiber. "
"Takes an optional timeout in seconds, after which will return nil. " "Takes an optional timeout in seconds, after which will raise an error. "
"Returns a new duplex stream which represents a connection to the client.") { "Returns a new duplex stream which represents a connection to the client.") {
janet_arity(argc, 1, 2); janet_arity(argc, 1, 2);
JanetStream *stream = janet_getabstract(argv, 0, &janet_stream_type); JanetStream *stream = janet_getabstract(argv, 0, &janet_stream_type);
@@ -844,7 +848,7 @@ JANET_CORE_FN(cfun_stream_read,
"Read up to n bytes from a stream, suspending the current fiber until the bytes are available. " "Read up to n bytes from a stream, suspending the current fiber until the bytes are available. "
"`n` can also be the keyword `:all` to read into the buffer until end of stream. " "`n` can also be the keyword `:all` to read into the buffer until end of stream. "
"If less than n bytes are available (and more than 0), will push those bytes and return early. " "If less than n bytes are available (and more than 0), will push those bytes and return early. "
"Takes an optional timeout in seconds, after which will return nil. " "Takes an optional timeout in seconds, after which will raise an error. "
"Returns a buffer with up to n more bytes in it, or raises an error if the read failed.") { "Returns a buffer with up to n more bytes in it, or raises an error if the read failed.") {
janet_arity(argc, 2, 4); janet_arity(argc, 2, 4);
JanetStream *stream = janet_getabstract(argv, 0, &janet_stream_type); JanetStream *stream = janet_getabstract(argv, 0, &janet_stream_type);
@@ -864,7 +868,7 @@ JANET_CORE_FN(cfun_stream_read,
JANET_CORE_FN(cfun_stream_chunk, JANET_CORE_FN(cfun_stream_chunk,
"(net/chunk stream nbytes &opt buf timeout)", "(net/chunk stream nbytes &opt buf timeout)",
"Same a net/read, but will wait for all n bytes to arrive rather than return early. " "Same a net/read, but will wait for all n bytes to arrive rather than return early. "
"Takes an optional timeout in seconds, after which will return nil.") { "Takes an optional timeout in seconds, after which will raise an error.") {
janet_arity(argc, 2, 4); janet_arity(argc, 2, 4);
JanetStream *stream = janet_getabstract(argv, 0, &janet_stream_type); JanetStream *stream = janet_getabstract(argv, 0, &janet_stream_type);
janet_stream_flags(stream, JANET_STREAM_READABLE | JANET_STREAM_SOCKET); janet_stream_flags(stream, JANET_STREAM_READABLE | JANET_STREAM_SOCKET);
@@ -878,7 +882,7 @@ JANET_CORE_FN(cfun_stream_chunk,
JANET_CORE_FN(cfun_stream_recv_from, JANET_CORE_FN(cfun_stream_recv_from,
"(net/recv-from stream nbytes buf &opt timeout)", "(net/recv-from stream nbytes buf &opt timeout)",
"Receives data from a server stream and puts it into a buffer. Returns the socket-address the " "Receives data from a server stream and puts it into a buffer. Returns the socket-address the "
"packet came from. Takes an optional timeout in seconds, after which will return nil.") { "packet came from. Takes an optional timeout in seconds, after which will raise an error.") {
janet_arity(argc, 3, 4); janet_arity(argc, 3, 4);
JanetStream *stream = janet_getabstract(argv, 0, &janet_stream_type); JanetStream *stream = janet_getabstract(argv, 0, &janet_stream_type);
janet_stream_flags(stream, JANET_STREAM_UDPSERVER | JANET_STREAM_SOCKET); janet_stream_flags(stream, JANET_STREAM_UDPSERVER | JANET_STREAM_SOCKET);
@@ -892,7 +896,7 @@ JANET_CORE_FN(cfun_stream_recv_from,
JANET_CORE_FN(cfun_stream_write, JANET_CORE_FN(cfun_stream_write,
"(net/write stream data &opt timeout)", "(net/write stream data &opt timeout)",
"Write data to a stream, suspending the current fiber until the write " "Write data to a stream, suspending the current fiber until the write "
"completes. Takes an optional timeout in seconds, after which will return nil. " "completes. Takes an optional timeout in seconds, after which will raise an error. "
"Returns nil, or raises an error if the write failed.") { "Returns nil, or raises an error if the write failed.") {
janet_arity(argc, 2, 3); janet_arity(argc, 2, 3);
JanetStream *stream = janet_getabstract(argv, 0, &janet_stream_type); JanetStream *stream = janet_getabstract(argv, 0, &janet_stream_type);
@@ -911,7 +915,7 @@ JANET_CORE_FN(cfun_stream_write,
JANET_CORE_FN(cfun_stream_send_to, JANET_CORE_FN(cfun_stream_send_to,
"(net/send-to stream dest data &opt timeout)", "(net/send-to stream dest data &opt timeout)",
"Writes a datagram to a server stream. dest is a the destination address of the packet. " "Writes a datagram to a server stream. dest is a the destination address of the packet. "
"Takes an optional timeout in seconds, after which will return nil. " "Takes an optional timeout in seconds, after which will raise an error. "
"Returns stream.") { "Returns stream.") {
janet_arity(argc, 3, 4); janet_arity(argc, 3, 4);
JanetStream *stream = janet_getabstract(argv, 0, &janet_stream_type); JanetStream *stream = janet_getabstract(argv, 0, &janet_stream_type);

View File

@@ -27,9 +27,10 @@
#include "gc.h" #include "gc.h"
#endif #endif
#include <stdlib.h>
#ifndef JANET_REDUCED_OS #ifndef JANET_REDUCED_OS
#include <stdlib.h>
#include <time.h> #include <time.h>
#include <fcntl.h> #include <fcntl.h>
#include <errno.h> #include <errno.h>
@@ -251,7 +252,7 @@ JANET_CORE_FN(os_exit,
} }
janet_deinit(); janet_deinit();
if (argc >= 2 && janet_truthy(argv[1])) { if (argc >= 2 && janet_truthy(argv[1])) {
_exit(status); _Exit(status);
} else { } else {
exit(status); exit(status);
} }

View File

@@ -231,7 +231,7 @@ static void delim_error(JanetParser *parser, size_t stack_index, char c, const c
janet_buffer_push_u8(buffer, '`'); janet_buffer_push_u8(buffer, '`');
} }
} }
janet_formatb(buffer, " opened at line %d, column %d", s->line, s->column); janet_formatb(buffer, " opened at line %d, column %d", (int32_t) s->line, (int32_t) s->column);
} }
parser->error = (const char *) janet_string(buffer->data, buffer->count); parser->error = (const char *) janet_string(buffer->data, buffer->count);
parser->flag |= JANET_PARSER_GENERATED_ERROR; parser->flag |= JANET_PARSER_GENERATED_ERROR;
@@ -363,8 +363,7 @@ static int stringend(JanetParser *p, JanetParseState *state) {
JanetParseState top = p->states[p->statecount - 1]; JanetParseState top = p->states[p->statecount - 1];
int32_t indent_col = (int32_t) top.column - 1; int32_t indent_col = (int32_t) top.column - 1;
uint8_t *r = bufstart, *end = r + buflen; uint8_t *r = bufstart, *end = r + buflen;
/* Check if there are any characters before the start column - /* Unless there are only spaces before EOLs, disable reindenting */
* if so, do not reindent. */
int reindent = 1; int reindent = 1;
while (reindent && (r < end)) { while (reindent && (r < end)) {
if (*r++ == '\n') { if (*r++ == '\n') {
@@ -374,34 +373,36 @@ static int stringend(JanetParser *p, JanetParseState *state) {
break; break;
} }
} }
if ((r + 1) < end && *r == '\r' && *(r + 1) == '\n') reindent = 1;
} }
} }
/* Now reindent if able to, otherwise just drop leading newline. */ /* Now reindent if able */
if (!reindent) { if (reindent) {
if (buflen > 0 && bufstart[0] == '\n') {
buflen--;
bufstart++;
}
} else {
uint8_t *w = bufstart; uint8_t *w = bufstart;
r = bufstart; r = bufstart;
while (r < end) { while (r < end) {
if (*r == '\n') { if (*r == '\n') {
if (r == bufstart) {
/* Skip leading newline */
r++;
} else {
*w++ = *r++; *w++ = *r++;
}
for (int32_t j = 0; (r < end) && (*r != '\n') && (j < indent_col); j++, r++); for (int32_t j = 0; (r < end) && (*r != '\n') && (j < indent_col); j++, r++);
if ((r + 1) < end && *r == '\r' && *(r + 1) == '\n') *w++ = *r++;
} else { } else {
*w++ = *r++; *w++ = *r++;
} }
} }
buflen = (int32_t)(w - bufstart); buflen = (int32_t)(w - bufstart);
} }
/* Check for trailing newline character so we can remove it */ /* Check for leading EOL so we can remove it */
if (buflen > 0 && bufstart[buflen - 1] == '\n') { if (buflen > 1 && bufstart[0] == '\r' && bufstart[1] == '\n') { /* Windows EOL */
buflen = buflen - 2;
bufstart = bufstart + 2;
} else if (buflen > 0 && bufstart[0] == '\n') { /* Unix EOL */
buflen--;
bufstart++;
}
/* Check for trailing EOL so we can remove it */
if (buflen > 1 && bufstart[buflen - 2] == '\r' && bufstart[buflen - 1] == '\n') { /* Windows EOL */
buflen = buflen - 2;
} else if (buflen > 0 && bufstart[buflen - 1] == '\n') { /* Unix EOL */
buflen--; buflen--;
} }
} }

View File

@@ -1,5 +1,5 @@
/* /*
* Copyright (c) 2024 Calvin Rose * Copyright (c) 2025 Calvin Rose
* *
* Permission is hereby granted, free of charge, to any person obtaining a copy * Permission is hereby granted, free of charge, to any person obtaining a copy
* of this software and associated documentation files (the "Software"), to * of this software and associated documentation files (the "Software"), to
@@ -549,36 +549,39 @@ tail:
const uint32_t *rule_separator = s->bytecode + rule[1]; const uint32_t *rule_separator = s->bytecode + rule[1];
const uint32_t *rule_subpattern = s->bytecode + rule[2]; const uint32_t *rule_subpattern = s->bytecode + rule[2];
const uint8_t *separator_end = NULL; const uint8_t *chunk_start = text;
do { const uint8_t *chunk_end = NULL;
const uint8_t *text_start = text;
while (text <= saved_end) {
/* Find next split (or end of text) */
CapState cs = cap_save(s); CapState cs = cap_save(s);
down1(s); down1(s);
while (text <= s->text_end) { while (text <= saved_end) {
separator_end = peg_rule(s, rule_separator, text); chunk_end = text;
const uint8_t *check = peg_rule(s, rule_separator, text);
cap_load(s, cs); cap_load(s, cs);
if (separator_end) { if (check) {
text = check;
break; break;
} }
text++; text++;
} }
up1(s); up1(s);
if (separator_end) { /* Match between splits */
s->text_end = text; s->text_end = chunk_end;
text = separator_end;
}
down1(s); down1(s);
const uint8_t *subpattern_end = peg_rule(s, rule_subpattern, text_start); const uint8_t *subpattern_end = peg_rule(s, rule_subpattern, chunk_start);
up1(s); up1(s);
s->text_end = saved_end; s->text_end = saved_end;
if (!subpattern_end) return NULL; /* Don't match anything */
if (!subpattern_end) { /* Ensure forward progress */
return NULL; if (text == chunk_start) return NULL;
chunk_start = text;
} }
} while (separator_end);
s->text_end = saved_end;
return s->text_end; return s->text_end;
} }
@@ -1416,6 +1419,11 @@ static uint32_t peg_compile1(Builder *b, Janet peg) {
emit_bytes(b, RULE_LITERAL, len, str); emit_bytes(b, RULE_LITERAL, len, str);
break; break;
} }
case JANET_BUFFER: {
const JanetBuffer *buf = janet_unwrap_buffer(peg);
emit_bytes(b, RULE_LITERAL, buf->count, buf->data);
break;
}
case JANET_TABLE: { case JANET_TABLE: {
/* Build grammar table */ /* Build grammar table */
JanetTable *new_grammar = janet_table_clone(janet_unwrap_table(peg)); JanetTable *new_grammar = janet_table_clone(janet_unwrap_table(peg));

View File

@@ -28,7 +28,7 @@
/* Run a string */ /* Run a string */
int janet_dobytes(JanetTable *env, const uint8_t *bytes, int32_t len, const char *sourcePath, Janet *out) { int janet_dobytes(JanetTable *env, const uint8_t *bytes, int32_t len, const char *sourcePath, Janet *out) {
JanetParser parser; JanetParser *parser;
int errflags = 0, done = 0; int errflags = 0, done = 0;
int32_t index = 0; int32_t index = 0;
Janet ret = janet_wrap_nil(); Janet ret = janet_wrap_nil();
@@ -37,14 +37,16 @@ int janet_dobytes(JanetTable *env, const uint8_t *bytes, int32_t len, const char
if (where) janet_gcroot(janet_wrap_string(where)); if (where) janet_gcroot(janet_wrap_string(where));
if (NULL == sourcePath) sourcePath = "<unknown>"; if (NULL == sourcePath) sourcePath = "<unknown>";
janet_parser_init(&parser); parser = janet_abstract(&janet_parser_type, sizeof(JanetParser));
janet_parser_init(parser);
janet_gcroot(janet_wrap_abstract(parser));
/* While we haven't seen an error */ /* While we haven't seen an error */
while (!done) { while (!done) {
/* Evaluate parsed values */ /* Evaluate parsed values */
while (janet_parser_has_more(&parser)) { while (janet_parser_has_more(parser)) {
Janet form = janet_parser_produce(&parser); Janet form = janet_parser_produce(parser);
JanetCompileResult cres = janet_compile(form, env, where); JanetCompileResult cres = janet_compile(form, env, where);
if (cres.status == JANET_COMPILE_OK) { if (cres.status == JANET_COMPILE_OK) {
JanetFunction *f = janet_thunk(cres.funcdef); JanetFunction *f = janet_thunk(cres.funcdef);
@@ -58,8 +60,8 @@ int janet_dobytes(JanetTable *env, const uint8_t *bytes, int32_t len, const char
} }
} else { } else {
ret = janet_wrap_string(cres.error); ret = janet_wrap_string(cres.error);
int32_t line = (int32_t) parser.line; int32_t line = (int32_t) parser->line;
int32_t col = (int32_t) parser.column; int32_t col = (int32_t) parser->column;
if ((cres.error_mapping.line > 0) && if ((cres.error_mapping.line > 0) &&
(cres.error_mapping.column > 0)) { (cres.error_mapping.column > 0)) {
line = cres.error_mapping.line; line = cres.error_mapping.line;
@@ -81,16 +83,16 @@ int janet_dobytes(JanetTable *env, const uint8_t *bytes, int32_t len, const char
if (done) break; if (done) break;
/* Dispatch based on parse state */ /* Dispatch based on parse state */
switch (janet_parser_status(&parser)) { switch (janet_parser_status(parser)) {
case JANET_PARSE_DEAD: case JANET_PARSE_DEAD:
done = 1; done = 1;
break; break;
case JANET_PARSE_ERROR: { case JANET_PARSE_ERROR: {
const char *e = janet_parser_error(&parser); const char *e = janet_parser_error(parser);
errflags |= 0x04; errflags |= 0x04;
ret = janet_cstringv(e); ret = janet_cstringv(e);
int32_t line = (int32_t) parser.line; int32_t line = (int32_t) parser->line;
int32_t col = (int32_t) parser.column; int32_t col = (int32_t) parser->column;
janet_eprintf("%s:%d:%d: parse error: %s\n", sourcePath, line, col, e); janet_eprintf("%s:%d:%d: parse error: %s\n", sourcePath, line, col, e);
done = 1; done = 1;
break; break;
@@ -98,9 +100,9 @@ int janet_dobytes(JanetTable *env, const uint8_t *bytes, int32_t len, const char
case JANET_PARSE_ROOT: case JANET_PARSE_ROOT:
case JANET_PARSE_PENDING: case JANET_PARSE_PENDING:
if (index >= len) { if (index >= len) {
janet_parser_eof(&parser); janet_parser_eof(parser);
} else { } else {
janet_parser_consume(&parser, bytes[index++]); janet_parser_consume(parser, bytes[index++]);
} }
break; break;
} }
@@ -108,7 +110,7 @@ int janet_dobytes(JanetTable *env, const uint8_t *bytes, int32_t len, const char
} }
/* Clean up and return errors */ /* Clean up and return errors */
janet_parser_deinit(&parser); janet_gcunroot(janet_wrap_abstract(parser));
if (where) janet_gcunroot(janet_wrap_string(where)); if (where) janet_gcunroot(janet_wrap_string(where));
#ifdef JANET_EV #ifdef JANET_EV
/* Enter the event loop if we are not already in it */ /* Enter the event loop if we are not already in it */

View File

@@ -100,6 +100,7 @@ struct JanetVM {
* return point for panics. */ * return point for panics. */
jmp_buf *signal_buf; jmp_buf *signal_buf;
Janet *return_reg; Janet *return_reg;
int coerce_error;
/* The global registry for c functions. Used to store meta-data /* The global registry for c functions. Used to store meta-data
* along with otherwise bare c function pointers. */ * along with otherwise bare c function pointers. */

View File

@@ -71,10 +71,10 @@ int janet_string_compare(const uint8_t *lhs, const uint8_t *rhs) {
int janet_string_equalconst(const uint8_t *lhs, const uint8_t *rhs, int32_t rlen, int32_t rhash) { int janet_string_equalconst(const uint8_t *lhs, const uint8_t *rhs, int32_t rlen, int32_t rhash) {
int32_t lhash = janet_string_hash(lhs); int32_t lhash = janet_string_hash(lhs);
int32_t llen = janet_string_length(lhs); int32_t llen = janet_string_length(lhs);
if (lhs == rhs)
return 1;
if (lhash != rhash || llen != rlen) if (lhash != rhash || llen != rlen)
return 0; return 0;
if (lhs == rhs)
return 1;
return !memcmp(lhs, rhs, rlen); return !memcmp(lhs, rhs, rlen);
} }

View File

@@ -301,6 +301,7 @@ int janet_scan_number_base(
if (base == 0) { if (base == 0) {
base = 10; base = 10;
} }
int exp_base = base;
/* Skip leading zeros */ /* Skip leading zeros */
while (str < end && (*str == '0' || *str == '.')) { while (str < end && (*str == '0' || *str == '.')) {
@@ -322,6 +323,12 @@ int janet_scan_number_base(
} else if (*str == '&') { } else if (*str == '&') {
foundexp = 1; foundexp = 1;
break; break;
} else if (base == 16 && (*str == 'P' || *str == 'p')) { /* IEEE hex float */
foundexp = 1;
exp_base = 10;
base = 2;
ex *= 4; /* We need to correct the current exponent after we change the base */
break;
} else if (base == 10 && (*str == 'E' || *str == 'e')) { } else if (base == 10 && (*str == 'E' || *str == 'e')) {
foundexp = 1; foundexp = 1;
break; break;
@@ -360,9 +367,9 @@ int janet_scan_number_base(
} }
while (str < end) { while (str < end) {
int digit = digit_lookup[*str & 0x7F]; int digit = digit_lookup[*str & 0x7F];
if (*str > 127 || digit >= base) goto error; if (*str > 127 || digit >= exp_base) goto error;
if (ee < (INT32_MAX / 40)) { if (ee < (INT32_MAX / 40)) {
ee = base * ee + digit; ee = exp_base * ee + digit;
} }
str++; str++;
seenadigit = 1; seenadigit = 1;

View File

@@ -294,6 +294,16 @@ JANET_CORE_FN(cfun_struct_to_table,
return janet_wrap_table(tab); return janet_wrap_table(tab);
} }
JANET_CORE_FN(cfun_struct_rawget,
"(struct/rawget st key)",
"Gets a value from a struct `st` without looking at the prototype struct. "
"If `st` does not contain the key directly, the function will return "
"nil without checking the prototype. Returns the value in the struct.") {
janet_fixarity(argc, 2);
JanetStruct st = janet_getstruct(argv, 0);
return janet_struct_rawget(st, argv[1]);
}
/* Load the struct module */ /* Load the struct module */
void janet_lib_struct(JanetTable *env) { void janet_lib_struct(JanetTable *env) {
JanetRegExt struct_cfuns[] = { JanetRegExt struct_cfuns[] = {
@@ -301,6 +311,7 @@ void janet_lib_struct(JanetTable *env) {
JANET_CORE_REG("struct/getproto", cfun_struct_getproto), JANET_CORE_REG("struct/getproto", cfun_struct_getproto),
JANET_CORE_REG("struct/proto-flatten", cfun_struct_flatten), JANET_CORE_REG("struct/proto-flatten", cfun_struct_flatten),
JANET_CORE_REG("struct/to-table", cfun_struct_to_table), JANET_CORE_REG("struct/to-table", cfun_struct_to_table),
JANET_CORE_REG("struct/rawget", cfun_struct_rawget),
JANET_REG_END JANET_REG_END
}; };
janet_core_cfuns_ext(env, NULL, struct_cfuns); janet_core_cfuns_ext(env, NULL, struct_cfuns);

View File

@@ -372,12 +372,14 @@ JANET_CORE_FN(cfun_table_setproto,
} }
JANET_CORE_FN(cfun_table_tostruct, JANET_CORE_FN(cfun_table_tostruct,
"(table/to-struct tab)", "(table/to-struct tab &opt proto)",
"Convert a table to a struct. Returns a new struct. This function " "Convert a table to a struct. Returns a new struct.") {
"does not take into account prototype tables.") { janet_arity(argc, 1, 2);
janet_fixarity(argc, 1);
JanetTable *t = janet_gettable(argv, 0); JanetTable *t = janet_gettable(argv, 0);
return janet_wrap_struct(janet_table_to_struct(t)); JanetStruct proto = janet_optstruct(argv, argc, 1, NULL);
JanetStruct st = janet_table_to_struct(t);
janet_struct_proto(st) = proto;
return janet_wrap_struct(st);
} }
JANET_CORE_FN(cfun_table_rawget, JANET_CORE_FN(cfun_table_rawget,

View File

@@ -117,14 +117,20 @@ const char *const janet_status_names[16] = {
"alive" "alive"
}; };
uint32_t janet_hash_mix(uint32_t input, uint32_t more) {
uint32_t mix1 = (more + 0x9e3779b9 + (input << 6) + (input >> 2));
return input ^ (0x9e3779b9 + (mix1 << 6) + (mix1 >> 2));
}
#ifndef JANET_PRF #ifndef JANET_PRF
int32_t janet_string_calchash(const uint8_t *str, int32_t len) { int32_t janet_string_calchash(const uint8_t *str, int32_t len) {
if (NULL == str) return 5381; if (NULL == str || len == 0) return 5381;
const uint8_t *end = str + len; const uint8_t *end = str + len;
uint32_t hash = 5381; uint32_t hash = 5381;
while (str < end) while (str < end)
hash = (hash << 5) + hash + *str++; hash = (hash << 5) + hash + *str++;
hash = janet_hash_mix(hash, (uint32_t) len);
return (int32_t) hash; return (int32_t) hash;
} }
@@ -240,11 +246,6 @@ int32_t janet_string_calchash(const uint8_t *str, int32_t len) {
#endif #endif
uint32_t janet_hash_mix(uint32_t input, uint32_t more) {
uint32_t mix1 = (more + 0x9e3779b9 + (input << 6) + (input >> 2));
return input ^ (0x9e3779b9 + (mix1 << 6) + (mix1 >> 2));
}
/* Computes hash of an array of values */ /* Computes hash of an array of values */
int32_t janet_array_calchash(const Janet *array, int32_t len) { int32_t janet_array_calchash(const Janet *array, int32_t len) {
const Janet *end = array + len; const Janet *end = array + len;

View File

@@ -1373,7 +1373,10 @@ Janet janet_call(JanetFunction *fun, int32_t argc, const Janet *argv) {
/* Run vm */ /* Run vm */
janet_vm.fiber->flags |= JANET_FIBER_RESUME_NO_USEVAL | JANET_FIBER_RESUME_NO_SKIP; janet_vm.fiber->flags |= JANET_FIBER_RESUME_NO_USEVAL | JANET_FIBER_RESUME_NO_SKIP;
int old_coerce_error = janet_vm.coerce_error;
janet_vm.coerce_error = 1;
JanetSignal signal = run_vm(janet_vm.fiber, janet_wrap_nil()); JanetSignal signal = run_vm(janet_vm.fiber, janet_wrap_nil());
janet_vm.coerce_error = old_coerce_error;
/* Teardown */ /* Teardown */
janet_vm.stackn = oldn; janet_vm.stackn = oldn;
@@ -1384,6 +1387,10 @@ Janet janet_call(JanetFunction *fun, int32_t argc, const Janet *argv) {
} }
if (signal != JANET_SIGNAL_OK) { if (signal != JANET_SIGNAL_OK) {
/* Should match logic in janet_signalv */
if (signal != JANET_SIGNAL_ERROR) {
*janet_vm.return_reg = janet_wrap_string(janet_formatc("%v coerced from %s to error", *janet_vm.return_reg, janet_signal_names[signal]));
}
janet_panicv(*janet_vm.return_reg); janet_panicv(*janet_vm.return_reg);
} }
@@ -1430,8 +1437,10 @@ void janet_try_init(JanetTryState *state) {
state->vm_fiber = janet_vm.fiber; state->vm_fiber = janet_vm.fiber;
state->vm_jmp_buf = janet_vm.signal_buf; state->vm_jmp_buf = janet_vm.signal_buf;
state->vm_return_reg = janet_vm.return_reg; state->vm_return_reg = janet_vm.return_reg;
state->coerce_error = janet_vm.coerce_error;
janet_vm.return_reg = &(state->payload); janet_vm.return_reg = &(state->payload);
janet_vm.signal_buf = &(state->buf); janet_vm.signal_buf = &(state->buf);
janet_vm.coerce_error = 0;
} }
void janet_restore(JanetTryState *state) { void janet_restore(JanetTryState *state) {
@@ -1440,6 +1449,7 @@ void janet_restore(JanetTryState *state) {
janet_vm.fiber = state->vm_fiber; janet_vm.fiber = state->vm_fiber;
janet_vm.signal_buf = state->vm_jmp_buf; janet_vm.signal_buf = state->vm_jmp_buf;
janet_vm.return_reg = state->vm_return_reg; janet_vm.return_reg = state->vm_return_reg;
janet_vm.coerce_error = state->coerce_error;
} }
static JanetSignal janet_continue_no_check(JanetFiber *fiber, Janet in, Janet *out) { static JanetSignal janet_continue_no_check(JanetFiber *fiber, Janet in, Janet *out) {

View File

@@ -1261,6 +1261,7 @@ typedef struct {
/* new state */ /* new state */
jmp_buf buf; jmp_buf buf;
Janet payload; Janet payload;
int coerce_error;
} JanetTryState; } JanetTryState;
/***** END SECTION TYPES *****/ /***** END SECTION TYPES *****/
@@ -1442,6 +1443,7 @@ JANET_NO_RETURN JANET_API void janet_sleep_await(double sec);
/* For use inside listeners - adds a timeout to the current fiber, such that /* For use inside listeners - adds a timeout to the current fiber, such that
* it will be resumed after sec seconds if no other event schedules the current fiber. */ * it will be resumed after sec seconds if no other event schedules the current fiber. */
JANET_API void janet_addtimeout(double sec); JANET_API void janet_addtimeout(double sec);
JANET_API void janet_addtimeout_nil(double sec);
JANET_API void janet_ev_inc_refcount(void); JANET_API void janet_ev_inc_refcount(void);
JANET_API void janet_ev_dec_refcount(void); JANET_API void janet_ev_dec_refcount(void);

View File

@@ -39,7 +39,7 @@
(defmacro assert (defmacro assert
[x &opt e] [x &opt e]
(def xx (gensym)) (def xx (gensym))
(default e ~',x) (default e (string/format "%j" x))
~(do ~(do
(def ,xx ,x) (def ,xx ,x)
(,assert-no-tail ,xx ,e) (,assert-no-tail ,xx ,e)

View File

@@ -896,11 +896,18 @@
(struct/with-proto {:a [1 2 3]} :c 22 :b [1 2 3 4] :d "test" :e "test2")) (struct/with-proto {:a [1 2 3]} :c 22 :b [1 2 3 4] :d "test" :e "test2"))
(table/setproto table-to-freeze @{:a @[1 2 3]}) (table/setproto table-to-freeze @{:a @[1 2 3]})
(assert (deep= {:a [1 2 3] :b [1 2 3 4] :c 22 :d "test" :e "test2"} (assert (deep= struct-to-thaw (freeze table-to-freeze)))
(freeze table-to-freeze)))
(assert (deep= table-to-freeze-with-inline-proto (thaw table-to-freeze))) (assert (deep= table-to-freeze-with-inline-proto (thaw table-to-freeze)))
(assert (deep= table-to-freeze-with-inline-proto (thaw struct-to-thaw))) (assert (deep= table-to-freeze-with-inline-proto (thaw struct-to-thaw)))
# Check that freezing mutable keys is deterministic
# for issue #1535
(def hashes @{})
(repeat 200
(def x (freeze {@"" 1 @"" 2 @"" 3 @"" 4 @"" 5}))
(put hashes (hash x) true))
(assert (= 1 (length hashes)) "freeze mutable keys is deterministic")
# Make sure Carriage Returns don't end up in doc strings # Make sure Carriage Returns don't end up in doc strings
# e528b86 # e528b86
(assert (not (string/find "\r" (assert (not (string/find "\r"
@@ -986,4 +993,27 @@
(assert (deep= (get (dyn 'a) :source-form) source)) (assert (deep= (get (dyn 'a) :source-form) source))
(setdyn *debug* nil) (setdyn *debug* nil)
# issue #1516
(assert-error "assertf 1 argument" (macex '(assertf true)))
(assert (assertf true "fun message") "assertf 2 arguments")
(assert (assertf true "%s message" "mystery") "assertf 3 arguments")
(assert (assertf (not nil) "%s message" "ordinary") "assertf not nil")
(assert-error "assertf error 2" (assertf false "fun message"))
(assert-error "assertf error 3" (assertf false "%s message" "mystery"))
(assert-error "assertf error 4" (assertf nil "%s %s" "alice" "bob"))
# issue #1535
(loop [i :range [1 1000]]
(assert (deep-not= @{:key1 "value1" @"key" "value2"}
@{:key1 "value1" @"key" "value2"}) "deep= mutable keys"))
(assert (deep-not= {"abc" 123} {@"abc" 123}) "deep= mutable keys vs immutable key")
(assert (deep-not= {@"" 1 @"" 2 @"" 3} {@"" 1 @"" 2 @"" 3}) "deep= duplicate mutable keys")
(assert (deep-not= {@"" @"" @"" @"" @"" 3} {@"" @"" @"" @"" @"" 3}) "deep= duplicate mutable keys 2")
(assert (deep-not= {@[] @"" @[] @"" @[] 3} {@[] @"" @[] @"" @[] 3}) "deep= duplicate mutable keys 3")
(assert (deep-not= {@{} @"" @{} @"" @{} 3} {@{} @"" @{} @"" @{} 3}) "deep= duplicate mutable keys 4")
(assert (deep-not= @{:key1 "value1" @"key2" @"value2"}
@{:key1 "value1" @"key2" "value2"}) "deep= mutable keys")
(assert (deep-not= @{:key1 "value1" [@"key2"] @"value2"}
@{:key1 "value1" [@"key2"] @"value2"}) "deep= mutable keys")
(end-suite) (end-suite)

View File

@@ -174,6 +174,7 @@
(assert (deep= (range 0 17 4) @[0 4 8 12 16]) "(range 0 17 4)") (assert (deep= (range 0 17 4) @[0 4 8 12 16]) "(range 0 17 4)")
(assert (deep= (range 16 0 -4) @[16 12 8 4]) "(range 16 0 -4)") (assert (deep= (range 16 0 -4) @[16 12 8 4]) "(range 16 0 -4)")
(assert (deep= (range 17 0 -4) @[17 13 9 5 1]) "(range 17 0 -4)") (assert (deep= (range 17 0 -4) @[17 13 9 5 1]) "(range 17 0 -4)")
(assert-error "large range" (range 0xFFFFFFFFFF))
(assert (= (length (range 10)) 10) "(range 10)") (assert (= (length (range 10)) 10) "(range 10)")
(assert (= (length (range -10)) 0) "(range -10)") (assert (= (length (range -10)) 0) "(range -10)")

View File

@@ -410,6 +410,10 @@
(ev/call handler connection) (ev/call handler connection)
(break)))) (break))))
# Make sure we can't bind again with no-reuse
(assert-error "no-reuse"
(net/listen test-host test-port :stream true))
# Read from socket # Read from socket
(defn expect-read (defn expect-read
@@ -418,9 +422,15 @@
(assert (= result text) (string/format "expected %v, got %v" text result))) (assert (= result text) (string/format "expected %v, got %v" text result)))
# Now do our telnet chat # Now do our telnet chat
(def bob (net/connect test-host test-port)) (def bob (net/connect test-host test-port :stream))
(expect-read bob "Whats your name?\n") (expect-read bob "Whats your name?\n")
(if (= :mingw (os/which))
(net/write bob "bob") (net/write bob "bob")
(do
(def fbob (ev/to-file bob))
(file/write fbob "bob")
(file/flush fbob)
(:close fbob)))
(expect-read bob "Welcome bob\n") (expect-read bob "Welcome bob\n")
(def alice (net/connect test-host test-port)) (def alice (net/connect test-host test-port))
(expect-read alice "Whats your name?\n") (expect-read alice "Whats your name?\n")
@@ -465,4 +475,13 @@
# Close chat server # Close chat server
(:close chat-server) (:close chat-server)
# Issue #1531
(def c (ev/chan 0))
(ev/spawn (while (def x (ev/take c))))
(defn print-to-chan [x] (ev/give c x))
(assert-error "coerce await inside janet_call to error"
(with-dyns [*out* print-to-chan]
(pp :foo)))
(ev/chan-close c)
(end-suite) (end-suite)

View File

@@ -52,5 +52,7 @@
(assert (= 26 (ffi/size [:char :pack :int @[:char 21]])) (assert (= 26 (ffi/size [:char :pack :int @[:char 21]]))
"array struct size")) "array struct size"))
(end-suite) (compwhen has-ffi
(assert-error "bad struct issue #1512" (ffi/struct :void)))
(end-suite)

View File

@@ -207,7 +207,7 @@ neldb\0\0\0\xD8\x05printG\x01\0\xDE\xDE\xDE'\x03\0marshal_tes/\x02
(assert (= 2 (length tclone)) "table/weak-values marsh 2") (assert (= 2 (length tclone)) "table/weak-values marsh 2")
(gccollect) (gccollect)
(assert (= 1 (length t)) "table/weak-value marsh 3") (assert (= 1 (length t)) "table/weak-value marsh 3")
(assert (deep= t tclone) "table/weak-values marsh 4") (assert (deep= (freeze t) (freeze tclone)) "table/weak-values marsh 4")
# tables with prototypes # tables with prototypes
(def t (table/weak-values 1)) (def t (table/weak-values 1))
@@ -219,7 +219,7 @@ neldb\0\0\0\xD8\x05printG\x01\0\xDE\xDE\xDE'\x03\0marshal_tes/\x02
(assert (= 2 (length tclone)) "marsh weak tables with prototypes 2") (assert (= 2 (length tclone)) "marsh weak tables with prototypes 2")
(gccollect) (gccollect)
(assert (= 1 (length t)) "marsh weak tables with prototypes 3") (assert (= 1 (length t)) "marsh weak tables with prototypes 3")
(assert (deep= t tclone) "marsh weak tables with prototypes 4") (assert (deep= (freeze t) (freeze tclone)) "marsh weak tables with prototypes 4")
(assert (deep= (getproto t) (getproto tclone)) "marsh weak tables with prototypes 5") (assert (deep= (getproto t) (getproto tclone)) "marsh weak tables with prototypes 5")
(end-suite) (end-suite)

View File

@@ -57,6 +57,8 @@
(for i (+ index 1) (+ index indent 1) (for i (+ index 1) (+ index indent 1)
(case (get text i) (case (get text i)
nil (break) nil (break)
(chr "\r") (if-not (= (chr "\n") (get text (inc i)))
(set rewrite false))
(chr "\n") (break) (chr "\n") (break)
(chr " ") nil (chr " ") nil
(set rewrite false)))) (set rewrite false))))
@@ -64,12 +66,17 @@
# Only re-indent if no dedented characters. # Only re-indent if no dedented characters.
(def str (def str
(if rewrite (if rewrite
(peg/replace-all ~(* "\n" (between 0 ,indent " ")) "\n" text) (peg/replace-all ~(* '(* (? "\r") "\n") (between 0 ,indent " "))
(fn [mtch eol] eol) text)
text)) text))
(def first-nl (= (chr "\n") (first str))) (def first-eol (cond
(def last-nl (= (chr "\n") (last str))) (string/has-prefix? "\r\n" str) :crlf
(string/slice str (if first-nl 1 0) (if last-nl -2))) (string/has-prefix? "\n" str) :lf))
(def last-eol (cond
(string/has-suffix? "\r\n" str) :crlf
(string/has-suffix? "\n" str) :lf))
(string/slice str (case first-eol :crlf 2 :lf 1 0) (case last-eol :crlf -3 :lf -2)))
(defn reindent-reference (defn reindent-reference
"Same as reindent but use parser functionality. Useful for "Same as reindent but use parser functionality. Useful for
@@ -89,8 +96,10 @@
(let [a (reindent text indent) (let [a (reindent text indent)
b (reindent-reference text indent)] b (reindent-reference text indent)]
(assert (= a b) (assert (= a b)
(string "indent " indent-counter " (indent=" indent ")")))) (string/format "reindent: %q, parse: %q (indent-test #%d with indent of %d)" a b indent-counter indent)
)))
# Unix EOLs
(check-indent "" 0) (check-indent "" 0)
(check-indent "\n" 0) (check-indent "\n" 0)
(check-indent "\n" 1) (check-indent "\n" 1)
@@ -106,6 +115,17 @@
(check-indent "\n Hello, world!\n " 4) (check-indent "\n Hello, world!\n " 4)
(check-indent "\n Hello, world!\n dedented text\n " 4) (check-indent "\n Hello, world!\n dedented text\n " 4)
(check-indent "\n Hello, world!\n indented text\n " 4) (check-indent "\n Hello, world!\n indented text\n " 4)
# Windows EOLs
(check-indent "\r\n" 0)
(check-indent "\r\n" 1)
(check-indent "\r\n\r\n" 0)
(check-indent "\r\n\r\n" 1)
(check-indent "\r\nHello, world!" 0)
(check-indent "\r\nHello, world!" 1)
(check-indent "\r\n Hello, world!\r\n " 4)
(check-indent "\r\n Hello, world!\r\n " 4)
(check-indent "\r\n Hello, world!\r\n dedented text\r\n " 4)
(check-indent "\r\n Hello, world!\r\n indented text\r\n " 4)
# Symbols with @ character # Symbols with @ character
# d68eae9 # d68eae9
@@ -188,5 +208,14 @@
(parser/consume p `")`) (parser/consume p `")`)
(assert (= (parser/produce p) ["hello"])) (assert (= (parser/produce p) ["hello"]))
# Hex floats
(assert (= math/pi +0x1.921fb54442d18p+0001))
(assert (= math/int-max +0x1.ffff_ffff_ffff_ffp+0052))
(assert (= math/int-min -0x1.ffff_ffff_ffff_ffp+0052))
(assert (= 1 0x1P0))
(assert (= 2 0x1P1))
(assert (= -2 -0x1p1))
(assert (= -0.5 -0x1p-1))
(end-suite) (end-suite)

View File

@@ -772,5 +772,22 @@
"5:apple6:banana6:cherry" "5:apple6:banana6:cherry"
@["apple" "banana" "cherry"]) @["apple" "banana" "cherry"])
# Issue #1539 - make sure split with "" doesn't infinite loop/oom
(test "issue 1539"
~(split "" (capture (to -1)))
"hello there friends"
nil)
(test "issue 1539 pt. 2"
~(split "," (capture 0))
"abc123,,,,"
@["" "" "" "" ""])
# Issue #1549 - allow buffers as peg literals
(test "issue 1549"
''@"abc123"
"abc123"
@["abc123"])
(end-suite) (end-suite)

View File

@@ -19,6 +19,11 @@
<?define ProgramFilesFolder="ProgramFilesFolder" ?> <?define ProgramFilesFolder="ProgramFilesFolder" ?>
<?define Win64="no" ?> <?define Win64="no" ?>
<?define Arch="(x86)" ?> <?define Arch="(x86)" ?>
<?elseif $(sys.BUILDARCH)="arm64" ?>
<?define UpgradeCode="0bd4bab6-c838-4c2a-b9e6-56ea8064863c" ?>
<?define ProgramFilesFolder="ProgramFiles64Folder" ?>
<?define Win64="yes" ?>
<?define Arch="(Arm)" ?>
<?else ?> <?else ?>
<?error Unsupported value of sys.BUILDARCH=$(sys.BUILDARCH)?> <?error Unsupported value of sys.BUILDARCH=$(sys.BUILDARCH)?>
<?endif?> <?endif?>