1
0
mirror of https://github.com/janet-lang/janet synced 2025-11-06 18:43:04 +00:00

Compare commits

..

79 Commits

Author SHA1 Message Date
Calvin Rose
73334f3485 Prepare for 1.38.0 release. 2025-03-18 21:14:45 -05:00
Calvin Rose
a5b8da8d67 Merge pull request #1572 from tsujamin/janet-ffi-define-fix
Remove janet_lib_ffi() dependency on JANET_EV
2025-03-18 19:12:44 -07:00
Benjamin Roberts
e8cccfced5 Remove janet_lib_ffi() dependency on JANET_EV 2025-03-18 10:54:18 -04:00
Calvin Rose
88984f7ffb Add bundle/replace
Slight generalization of bundle/reinstall to allow inplace upgrades
of a bundle without reinstalling all dependent bundles as well.
2025-03-17 17:58:40 -05:00
Calvin Rose
182170b3be Make installed manifests nicer to work with. 2025-03-16 20:39:16 -05:00
Calvin Rose
f92412841b Merge pull request #1569 from sogaiu/vm-bug-1566 2025-03-12 21:13:49 -07:00
sogaiu
18c00e89da Fix vm bugs from #1566 2025-03-13 11:43:43 +09:00
Calvin Rose
7c38a55a9a Merge pull request #1568 from rwtolbert/msys2_mingw_build_fix
Update Makefile and meson to build with msys2 gcc and clang
2025-03-10 17:56:00 -07:00
Bob Tolbert
a15916ec9c Update Makefile and meson to build with msys2 gcc and clang 2025-03-10 10:13:36 -05:00
Calvin Rose
3583d4c92f Fix defdyn in flychecking. 2025-03-08 20:03:02 -06:00
Calvin Rose
a456c67a7b Merge pull request #1564 from pyrmont/bugfix.qemu-segfault
Use Alpine Linux as base image for s390x testing
2025-03-05 05:52:20 -08:00
Michael Camilleri
6e226e4073 Use sh rather than bash on Alpine Linux 2025-03-04 15:19:44 +09:00
Michael Camilleri
d23e6614b1 Use Alpine Linux as the base image rather than Ubuntu 2025-03-04 15:17:07 +09:00
Michael Camilleri
ab6afa72fd Use simpler base image 2025-03-04 15:01:25 +09:00
Calvin Rose
9538b8a77c Merge pull request #1562 from pyrmont/bugfix.subprocess-cleanup
Improve subprocess-related docstrings and functions
2025-03-03 18:47:59 -08:00
Calvin Rose
36d3804dd2 Merge pull request #1563 from pyrmont/bugfix.copyright-dates
Add script to update copyright dates
2025-03-03 18:47:38 -08:00
Michael Camilleri
a34b8ea68f Update older dates to 2025 2025-03-04 11:32:15 +09:00
Michael Camilleri
55c10f98bb Check more file extensions 2025-03-04 11:31:56 +09:00
Michael Camilleri
4b5a2a14c0 Update year in copyright disclaimer to 2025 2025-03-04 11:24:09 +09:00
Michael Camilleri
665705d06d Add Janet script to update copyright disclaimer 2025-03-04 11:23:26 +09:00
Michael Camilleri
cc8cd4bace Use uraimo GitHub action for s390x testing 2025-03-04 09:14:05 +09:00
Michael Camilleri
8f8b6ed001 Remove note on cancellation and tidy up wording 2025-03-03 23:28:37 +09:00
Calvin Rose
aa9efee868 Expose bundle operations in the CLI.
Bundle installation is a fairly natural command on the command line.
2025-03-03 08:14:09 -06:00
Calvin Rose
e0a0e2ed42 Prevent multi-scheduling with os/proc-wait. (address #1562) 2025-03-02 10:44:15 -06:00
Michael Camilleri
39f5c539d7 Clarify wording of docstrings further 2025-03-02 13:25:14 +09:00
Michael Camilleri
1b6437a4f8 Remove unnecessary returns 2025-03-02 08:30:51 +09:00
Michael Camilleri
c62c1a58f0 Reword documentation relating to subprocess creation 2025-03-02 08:29:32 +09:00
Calvin Rose
3441bcbd69 Add more cases for checking coerce error.
Channel operations inside a janet_call could wreak more
havoc later, when scheduled fibers are shooting off when they shouldn't
be. The easiest way to prevent this is simply check that we are not
inside janet_call before doing channel operations. We also get nicer
error messages this way.
2025-02-16 10:40:38 -06:00
Calvin Rose
2e6001316a Work on bug #1556 2025-02-16 09:09:33 -06:00
Calvin Rose
53bcc15207 Merge pull request #1557 from sogaiu/update-changelog-2025-02
Update CHANGELOG.md
2025-02-09 23:22:45 -08:00
sogaiu
dd609bb1bb Update CHANGELOG.md 2025-02-08 14:29:22 +09:00
Calvin Rose
5c56c7fa91 Formatting in peg.c 2025-02-07 21:05:28 -06:00
Calvin Rose
9a892363a3 Address issue #1558
Don't attempt SO_REUSEPORT on unix domain sockets since it doesn't make
sense and may cause a failure to bind without extra effort by the
programmer.
2025-02-07 21:05:07 -06:00
Calvin Rose
5f550ea5d4 Merge pull request #1555 from sogaiu/tweak-apply-short-fn-docstrings
Tweak apply and short-fn docstrings
2025-02-06 16:44:45 -08:00
sogaiu
1b6cc023a5 Tweak apply and short-fn docstrings 2025-02-06 22:35:59 +09:00
Calvin Rose
410f8d69bc Merge pull request #1528 from ianthehenry/til-peg-special
add (til) PEG special
2025-02-03 19:07:08 -08:00
Calvin Rose
6da44bdb6a Get rid of early termination rule in all finite loops. 2025-02-03 07:36:00 -06:00
Calvin Rose
d30fd27575 Address #1554 - capture first 0-width match in repeating rules and
optionals.

This fixes some undesirable interplay between lookaheads and looping
(repeating) rules where lookaheads caused the loop to immediately
terminate _and_ discard any captures. This change adds an exception if
there is a 0-width match at the first iteration of the loop, in which
case captures will be kept, although the loop will still be terminated
(any further iteration of the loop would possibly cause an infinite
 loop).

This allows optionals to work as expected when combined with lookahead.
There is also a question of whether or not optional should be
implemented as separate rule rather than as `(between 0 1 subrule)`.
2025-02-02 20:34:36 -06:00
Calvin Rose
1b278fc657 Update reduce2 documentation to fix issue #1545 2025-02-01 17:31:55 -06:00
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
Ian Henry
952906279c add (til) PEG special
(til sep subpattern) is a specialized (sub) that behaves like
(sub (to sep) subpattern), but advances over the input like (thru sep).
2024-12-04 21:17:10 -08:00
104 changed files with 991 additions and 428 deletions

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,6 +38,9 @@ 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: test-windows-min:
name: Build and test on Windows Minimal build name: Build and test on Windows Minimal build
@@ -129,7 +132,7 @@ jobs:
steps: steps:
- name: Checkout the repository - name: Checkout the repository
uses: actions/checkout@master uses: actions/checkout@master
- name: Do Qemu build and test - name: Enable qemu
run: | run: docker run --privileged --rm tonistiigi/binfmt --install s390x
docker run --rm --privileged multiarch/qemu-user-static --reset -p yes - name: Build and run on emulated architecture
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" run: docker run --rm -v .:/janet --platform linux/s390x alpine sh -c "apk update && apk add --no-interactive git build-base && cd /janet && make -j3 && make test"

View File

@@ -1,7 +1,22 @@
# Changelog # Changelog
All notable changes to this project will be documented in this file. All notable changes to this project will be documented in this file.
## 1.37.0 - 2024-12-05 ## 1.38.0 - 2025-03-18
- Add `bundle/replace`
- Add CLI flags for the `bundle/` module to install and manage bundles.
- Improve `?` peg special termination behavior
- Add IEEE hex floats to grammar.
- Add buffer peg literal support
- Improve `split` peg special edge case behavior
- Add Arm64 .msi support
- Add `no-reuse` argument to `net/listen` to disable reusing server sockets
- 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
- Improve `file/open` error message by including path
## 1.37.1 - 2024-12-05
- Fix meson cross compilation - Fix meson cross compilation
- Update timeout documentation for networking APIs: timeouts raise errors and do not return nil. - Update timeout documentation for networking APIs: timeouts raise errors and do not return nil.
- Add `janet_addtimeout_nil(double sec);` to the C API. - Add `janet_addtimeout_nil(double sec);` to the C API.

View File

@@ -1,4 +1,4 @@
Copyright (c) 2023 Calvin Rose and contributors Copyright (c) 2025 Calvin Rose and contributors
Permission is hereby granted, free of charge, to any person obtaining a copy of Permission is hereby granted, free of charge, to any person obtaining a copy of
this software and associated documentation files (the "Software"), to deal in this software and associated documentation files (the "Software"), to deal in

View File

@@ -1,4 +1,4 @@
# 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
@@ -57,6 +57,7 @@ LDFLAGS?=-rdynamic
LIBJANET_LDFLAGS?=$(LD_FLAGS) LIBJANET_LDFLAGS?=$(LD_FLAGS)
RUN:=$(RUN) RUN:=$(RUN)
COMMON_CFLAGS:=-std=c99 -Wall -Wextra -Isrc/include -Isrc/conf -fvisibility=hidden -fPIC COMMON_CFLAGS:=-std=c99 -Wall -Wextra -Isrc/include -Isrc/conf -fvisibility=hidden -fPIC
BOOT_CFLAGS:=-DJANET_BOOTSTRAP -DJANET_BUILD=$(JANET_BUILD) -O0 $(COMMON_CFLAGS) -g BOOT_CFLAGS:=-DJANET_BOOTSTRAP -DJANET_BUILD=$(JANET_BUILD) -O0 $(COMMON_CFLAGS) -g
BUILD_CFLAGS:=$(CFLAGS) $(COMMON_CFLAGS) BUILD_CFLAGS:=$(CFLAGS) $(COMMON_CFLAGS)
@@ -94,12 +95,18 @@ endif
endif endif
# Mingw # Mingw
MINGW_COMPILER=
ifeq ($(findstring MINGW,$(UNAME)), MINGW) ifeq ($(findstring MINGW,$(UNAME)), MINGW)
MINGW_COMPILER=gcc
CLIBS:=-lws2_32 -lpsapi -lwsock32 CLIBS:=-lws2_32 -lpsapi -lwsock32
LDFLAGS:=-Wl,--out-implib,$(JANET_IMPORT_LIB) LDFLAGS:=-Wl,--out-implib,$(JANET_IMPORT_LIB)
LIBJANET_LDFLAGS:=-Wl,--out-implib,$(JANET_LIBRARY_IMPORT_LIB) LIBJANET_LDFLAGS:=-Wl,--out-implib,$(JANET_LIBRARY_IMPORT_LIB)
JANET_TARGET:=$(JANET_TARGET).exe JANET_TARGET:=$(JANET_TARGET).exe
JANET_BOOT:=$(JANET_BOOT).exe JANET_BOOT:=$(JANET_BOOT).exe
COMPILER_VERSION:=$(shell $(CC) --version)
ifeq ($(findstring clang,$(COMPILER_VERSION)), clang)
MINGW_COMPILER=clang
endif
endif endif
@@ -206,9 +213,14 @@ build/%.bin.o: src/%.c $(JANET_HEADERS) $(JANET_LOCAL_HEADERS) Makefile
######################## ########################
ifeq ($(UNAME), Darwin) ifeq ($(UNAME), Darwin)
SONAME=libjanet.1.37.dylib SONAME=libjanet.1.38.dylib
else else
SONAME=libjanet.so.1.37 SONAME=libjanet.so.1.38
endif
ifeq ($(MINGW_COMPILER), clang)
SONAME=
SONAME_SETTER=
endif endif
build/c/shell.c: src/mainclient/shell.c build/c/shell.c: src/mainclient/shell.c

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.

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

@@ -1,4 +1,4 @@
# Copyright (c) 2024 Calvin Rose and contributors # Copyright (c) 2025 Calvin Rose and contributors
# #
# 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
@@ -20,7 +20,7 @@
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.38.0')
# 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')
@@ -39,6 +39,15 @@ native_thread_dep = dependency('threads', native : true)
# Deps # 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)
# for MINGW/MSYS2
native_ws2_dep = native_cc.find_library('ws2_32', required: false)
native_psapi_dep = native_cc.find_library('psapi', required: false)
native_wsock_dep = native_cc.find_library('wsock32', required: false)
ws2_dep = cc.find_library('ws2_32', required: false)
psapi_dep = cc.find_library('psapi', required: false)
wsock_dep = cc.find_library('wsock32', required: false)
android_spawn_dep = cc.find_library('android-spawn', required : false) android_spawn_dep = cc.find_library('android-spawn', required : false)
thread_dep = dependency('threads') thread_dep = dependency('threads')
@@ -173,8 +182,8 @@ mainclient_src = [
'src/mainclient/shell.c' 'src/mainclient/shell.c'
] ]
janet_dependencies = [m_dep, dl_dep, android_spawn_dep] janet_dependencies = [m_dep, dl_dep, android_spawn_dep, ws2_dep, psapi_dep, wsock_dep]
janet_native_dependencies = [native_m_dep, native_dl_dep, native_android_spawn_dep] janet_native_dependencies = [native_m_dep, native_dl_dep, native_android_spawn_dep, native_ws2_dep, native_psapi_dep, native_wsock_dep]
if not get_option('single_threaded') if not get_option('single_threaded')
janet_dependencies += thread_dep janet_dependencies += thread_dep
janet_native_dependencies += native_thread_dep janet_native_dependencies += native_thread_dep

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

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

View File

@@ -1,5 +1,5 @@
# The core janet library # The core janet library
# Copyright 2024 © Calvin Rose # Copyright 2025 © Calvin Rose
### ###
### ###
@@ -996,7 +996,7 @@
(defn reduce2 (defn reduce2
``The 2-argument version of `reduce` that does not take an initialization value. ``The 2-argument version of `reduce` that does not take an initialization value.
Instead, the first element of the array is used for initialization.`` Instead, the first element of the array is used for initialization. If `ind` is empty, will evaluate to nil.``
[f ind] [f ind]
(var k (next ind)) (var k (next ind))
(if (= nil k) (break nil)) (if (= nil k) (break nil))
@@ -1311,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*
@@ -2219,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
@@ -2284,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
@@ -2335,17 +2345,11 @@
(defmacro short-fn (defmacro short-fn
``` ```
Shorthand for `fn`. Arguments are given as `$n`, where `n` is the 0-indexed Shorthand for `fn`. Arguments are given as `$n`, where `n` is the
argument of the function. `$` is also an alias for the first (index 0) argument. 0-indexed argument of the function. `$` is also an alias for the
The `$&` symbol will make the anonymous function variadic if it appears in the first (index 0) argument. The `$&` symbol will make the anonymous
body of the function, and can be combined with positional arguments. function variadic if it appears in the body of the function, and
can be combined with positional arguments.
Example usage:
(short-fn (+ $ $)) # A function that doubles its arguments.
(short-fn (string $0 $1)) # accepting multiple args.
|(+ $ $) # use pipe reader macro for terse function literals.
|(+ $&) # variadic functions
``` ```
[arg &opt name] [arg &opt name]
(var max-param-seen -1) (var max-param-seen -1)
@@ -2854,8 +2858,8 @@
(when (and (string? pattern) (string/has-prefix? ":sys:/" pattern)) (when (and (string? pattern) (string/has-prefix? ":sys:/" pattern))
(set last-index index) (set last-index index)
(array/push copies [(string/replace ":sys:" path pattern) ;(drop 1 entry)]))) (array/push copies [(string/replace ":sys:" path pattern) ;(drop 1 entry)])))
(array/insert mp (+ 1 last-index) ;copies) (array/insert mp (+ 1 last-index) ;copies)
mp) mp)
(module/add-paths ":native:" :native) (module/add-paths ":native:" :native)
(module/add-paths "/init.janet" :source) (module/add-paths "/init.janet" :source)
@@ -3873,8 +3877,8 @@
(compwhen (dyn 'net/listen) (compwhen (dyn 'net/listen)
(defn net/server (defn net/server
"Start a server asynchronously with `net/listen` and `net/accept-loop`. Returns the new server stream." "Start a server asynchronously with `net/listen` and `net/accept-loop`. Returns the new server stream."
[host port &opt handler type] [host port &opt handler type no-reuse]
(def s (net/listen host port type)) (def s (net/listen host port type no-reuse))
(if handler (if handler
(ev/go (fn [] (net/accept-loop s handler)))) (ev/go (fn [] (net/accept-loop s handler))))
s)) s))
@@ -3980,7 +3984,7 @@
(def- safe-forms {'defn true 'varfn true 'defn- true 'defmacro true 'defmacro- true (def- safe-forms {'defn true 'varfn true 'defn- true 'defmacro true 'defmacro- true
'def is-safe-def 'var is-safe-def 'def- is-safe-def 'var- is-safe-def 'def is-safe-def 'var is-safe-def 'def- is-safe-def 'var- is-safe-def
'defglobal is-safe-def 'varglobal is-safe-def}) 'defglobal is-safe-def 'varglobal is-safe-def 'defdyn true})
(def- importers {'import true 'import* true 'dofile true 'require true}) (def- importers {'import true 'import* true 'dofile true 'require true})
(defn- use-2 [evaluator args] (defn- use-2 [evaluator args]
@@ -4096,7 +4100,7 @@
(when (empty? b) (buffer/trim b) (os/chmod to perm) (break)) (when (empty? b) (buffer/trim b) (os/chmod to perm) (break))
(file/write fto b) (file/write fto b)
(buffer/clear b))) (buffer/clear b)))
(errorf "destination file %s cannot be opened for writing" to)) (errorf "destination file %s cannot be opened for writing" to))
(errorf "source file %s cannot be opened for reading" from))) (errorf "source file %s cannot be opened for reading" from)))
(defn- copyrf (defn- copyrf
@@ -4114,7 +4118,11 @@
[manifest] [manifest]
(def bn (get manifest :name)) (def bn (get manifest :name))
(def manifest-name (get-manifest-filename bn)) (def manifest-name (get-manifest-filename bn))
(spit manifest-name (string/format "%j\n" manifest))) (def b @"")
(buffer/format b "%j" manifest) # make sure it is valid jdn
(buffer/clear b)
(buffer/format b "%.99m\n" manifest)
(spit manifest-name b))
(defn bundle/manifest (defn bundle/manifest
"Get the manifest for a give installed bundle" "Get the manifest for a give installed bundle"
@@ -4133,7 +4141,7 @@
(os/cd workdir) (os/cd workdir)
([_] (print "cannot enter source directory " workdir " for bundle " bundle-name))) ([_] (print "cannot enter source directory " workdir " for bundle " bundle-name)))
(defer (os/cd dir) (defer (os/cd dir)
(def new-env (make-env (curenv))) (def new-env (make-env))
(put new-env *module-cache* @{}) (put new-env *module-cache* @{})
(put new-env *module-loading* @{}) (put new-env *module-loading* @{})
(put new-env *module-make-env* (fn make-bundle-env [&] (make-env new-env))) (put new-env *module-make-env* (fn make-bundle-env [&] (make-env new-env)))
@@ -4148,7 +4156,6 @@
[module bundle-name hook & args] [module bundle-name hook & args]
(def hookf (module/value module (symbol hook))) (def hookf (module/value module (symbol hook)))
(unless hookf (break)) (unless hookf (break))
(def manifest (bundle/manifest bundle-name))
(def dir (os/cwd)) (def dir (os/cwd))
(os/cd (get module :workdir ".")) (os/cd (get module :workdir "."))
(defer (os/cd dir) (defer (os/cd dir)
@@ -4267,8 +4274,8 @@
(assertf (not (string/check-set "\\/" bundle-name)) (assertf (not (string/check-set "\\/" bundle-name))
"bundle name %v cannot contain path separators" bundle-name) "bundle name %v cannot contain path separators" bundle-name)
(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))) (assertf (not (fexists (get-manifest-filename bundle-name)))
"bundle is already installed") "bundle %v is already installed" bundle-name)
# Setup installed paths # Setup installed paths
(prime-bundle-paths) (prime-bundle-paths)
(os/mkdir (bundle-dir bundle-name)) (os/mkdir (bundle-dir bundle-name))
@@ -4341,14 +4348,15 @@
(spit install-hook b)) (spit install-hook b))
dest-dir) dest-dir)
(defn bundle/reinstall (defn bundle/replace
"Reinstall an existing bundle from the local source code." "Reinstall an existing bundle from a new directory. Similar to bundle/reinstall,
[bundle-name &keys new-config] but installs the replacement bundle from any directory. This is necesarry to replace a package without
breaking any dependencies."
[bundle-name path &keys new-config]
(def manifest (bundle/manifest bundle-name)) (def manifest (bundle/manifest bundle-name))
(def path (get manifest :local-source))
(def config (get manifest :config @{})) (def config (get manifest :config @{}))
(def s (sep)) (def s (sep))
(assert (= :directory (os/stat path :mode)) "local source not available") (assertf (= :directory (os/stat path :mode)) "local source %v not available" path)
(def backup-dir (string (dyn *syspath*) s bundle-name ".backup")) (def backup-dir (string (dyn *syspath*) s bundle-name ".backup"))
(rmrf backup-dir) (rmrf backup-dir)
(def backup-bundle-source (bundle/pack bundle-name backup-dir true)) (def backup-bundle-source (bundle/pack bundle-name backup-dir true))
@@ -4361,6 +4369,14 @@
(rmrf backup-bundle-source) (rmrf backup-bundle-source)
bundle-name) bundle-name)
(defn bundle/reinstall
"Reinstall an existing bundle from the local source code."
[bundle-name &keys new-config]
(def manifest (bundle/manifest bundle-name))
(def path (get manifest :local-source))
(bundle/replace bundle-name path ;(kvs new-config))
bundle-name)
(defn bundle/add-directory (defn bundle/add-directory
"Add a directory during the install process relative to `(dyn *syspath*)`" "Add a directory during the install process relative to `(dyn *syspath*)`"
[manifest dest &opt chmod-mode] [manifest dest &opt chmod-mode]
@@ -4427,10 +4443,11 @@
`Shorthand for adding scripts during an install. Scripts will be installed to `Shorthand for adding scripts during an install. Scripts will be installed to
(string (dyn *syspath*) "/bin") by default and will be set to be executable.` (string (dyn *syspath*) "/bin") by default and will be set to be executable.`
[manifest src &opt dest chmod-mode] [manifest src &opt dest chmod-mode]
(default dest (last (string/split "/" src))) (def s (sep))
(default dest (last (string/split s src)))
(default chmod-mode 8r755) (default chmod-mode 8r755)
(os/mkdir (string (dyn *syspath*) (sep) "bin")) (os/mkdir (string (dyn *syspath*) s "bin"))
(bundle/add-file manifest src (string "bin" (sep) dest) chmod-mode)) (bundle/add-file manifest src (string "bin" s dest) chmod-mode))
(defn bundle/update-all (defn bundle/update-all
"Reinstall all bundles" "Reinstall all bundles"
@@ -4493,6 +4510,12 @@
"-nocolor" "n" "-nocolor" "n"
"-color" "N" "-color" "N"
"-library" "l" "-library" "l"
"-install" "b"
"-reinstall" "B"
"-uninstall" "u"
"-update-all" "U"
"-list" "L"
"-prune" "P"
"-lint-warn" "w" "-lint-warn" "w"
"-lint-error" "x"}) "-lint-error" "x"})
@@ -4503,7 +4526,7 @@
(setdyn *args* args) (setdyn *args* args)
(var should-repl false) (var should-repl nil)
(var no-file true) (var no-file true)
(var quiet false) (var quiet false)
(var raw-stdin false) (var raw-stdin false)
@@ -4558,6 +4581,12 @@
--library (-l) lib : Use a module before processing more arguments --library (-l) lib : Use a module before processing more arguments
--lint-warn (-w) level : Set the lint warning level - default is "normal" --lint-warn (-w) level : Set the lint warning level - default is "normal"
--lint-error (-x) level : Set the lint error level - default is "none" --lint-error (-x) level : Set the lint error level - default is "none"
--install (-b) dirpath : Install a bundle from a directory
--reinstall (-B) name : Reinstall a bundle by bundle name
--uninstall (-u) name : Uninstall a bundle by bundle name
--update-all (-U) : Reinstall all installed bundles
--prune (-P) : Uninstalled all bundles that are orphaned
--list (-L) : List all installed bundles
-- : Stop handling options -- : Stop handling options
```) ```)
(os/exit 0) (os/exit 0)
@@ -4602,6 +4631,30 @@
((thunk) ;subargs) ((thunk) ;subargs)
(error (get thunk :error))) (error (get thunk :error)))
math/inf) math/inf)
"b"
(compif (dyn 'bundle/install)
(fn [i &] (bundle/install (in args (+ i 1))) (set no-file false) (if (= nil should-repl) (set should-repl false)) 2)
(fn [i &] (eprint "--install not supported with reduced os") 2))
"B"
(compif (dyn 'bundle/reinstall)
(fn [i &] (bundle/reinstall (in args (+ i 1))) (set no-file false) (if (= nil should-repl) (set should-repl false)) 2)
(fn [i &] (eprint "--reinstall not supported with reduced os") 2))
"u"
(compif (dyn 'bundle/uninstall)
(fn [i &] (bundle/uninstall (in args (+ i 1))) (set no-file false) (if (= nil should-repl) (set should-repl false)) 2)
(fn [i &] (eprint "--uninstall not supported with reduced os") 2))
"P"
(compif (dyn 'bundle/prune)
(fn [i &] (bundle/prune) (set no-file false) (if (= nil should-repl) (set should-repl false)) 1)
(fn [i &] (eprint "--prune not supported with reduced os") 1))
"U"
(compif (dyn 'bundle/update-all)
(fn [i &] (bundle/update-all) (set no-file false) (if (= nil should-repl) (set should-repl false)) 1)
(fn [i &] (eprint "--update-all not supported with reduced os") 1))
"L"
(compif (dyn 'bundle/list)
(fn [i &] (each l (bundle/list) (print l)) (set no-file false) (if (= nil should-repl) (set should-repl false)) 1)
(fn [i &] (eprint "--list not supported with reduced os") 1))
"d" (fn [&] (set debug-flag true) 1) "d" (fn [&] (set debug-flag true) 1)
"w" (fn [i &] (set warn-level (get-lint-level i)) 2) "w" (fn [i &] (set warn-level (get-lint-level i)) 2)
"x" (fn [i &] (set error-level (get-lint-level i)) 2) "x" (fn [i &] (set error-level (get-lint-level i)) 2)

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

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

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

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

View File

@@ -4,10 +4,10 @@
#define JANETCONF_H #define JANETCONF_H
#define JANET_VERSION_MAJOR 1 #define JANET_VERSION_MAJOR 1
#define JANET_VERSION_MINOR 37 #define JANET_VERSION_MINOR 38
#define JANET_VERSION_PATCH 0 #define JANET_VERSION_PATCH 0
#define JANET_VERSION_EXTRA "" #define JANET_VERSION_EXTRA ""
#define JANET_VERSION "1.37.0" #define JANET_VERSION "1.38.0"
/* #define JANET_BUILD "local" */ /* #define JANET_BUILD "local" */

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

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

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

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

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

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
@@ -62,6 +62,18 @@ 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) {
#ifdef JANET_EV
if (NULL != janet_vm.root_fiber && sig == JANET_SIGNAL_EVENT) {
janet_vm.root_fiber->sched_id++;
}
#endif
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;

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

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

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

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
@@ -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);
} }
@@ -1001,12 +1002,11 @@ static void make_apply(JanetTable *env) {
janet_quick_asm(env, JANET_FUN_APPLY | JANET_FUNCDEF_FLAG_VARARG, janet_quick_asm(env, JANET_FUN_APPLY | JANET_FUNCDEF_FLAG_VARARG,
"apply", 1, 1, INT32_MAX, 6, apply_asm, sizeof(apply_asm), "apply", 1, 1, INT32_MAX, 6, apply_asm, sizeof(apply_asm),
JDOC("(apply f & args)\n\n" JDOC("(apply f & args)\n\n"
"Applies a function to a variable number of arguments. Each element in args " "Applies a function f to a variable number of arguments. Each "
"is used as an argument to f, except the last element in args, which is expected to " "element in args is used as an argument to f, except the last "
"be an array-like. Each element in this last argument is then also pushed as an argument to " "element in args, which is expected to be an array or a tuple. "
"f. For example:\n\n" "Each element in this last argument is then also pushed as an "
"\t(apply + 1000 (range 10))\n\n" "argument to f."));
"sums the first 10 integers and 1000."));
} }
static const uint32_t error_asm[] = { static const uint32_t error_asm[] = {
@@ -1159,82 +1159,82 @@ JanetTable *janet_core_env(JanetTable *replacements) {
janet_quick_asm(env, JANET_FUN_CMP, janet_quick_asm(env, JANET_FUN_CMP,
"cmp", 2, 2, 2, 2, cmp_asm, sizeof(cmp_asm), "cmp", 2, 2, 2, 2, cmp_asm, sizeof(cmp_asm),
JDOC("(cmp x y)\n\n" JDOC("(cmp x y)\n\n"
"Returns -1 if x is strictly less than y, 1 if y is strictly greater " "Returns -1 if x is strictly less than y, 1 if y is strictly greater "
"than x, and 0 otherwise. To return 0, x and y must be the exact same type.")); "than x, and 0 otherwise. To return 0, x and y must be the exact same type."));
janet_quick_asm(env, JANET_FUN_NEXT, janet_quick_asm(env, JANET_FUN_NEXT,
"next", 2, 1, 2, 2, next_asm, sizeof(next_asm), "next", 2, 1, 2, 2, next_asm, sizeof(next_asm),
JDOC("(next ds &opt key)\n\n" JDOC("(next ds &opt key)\n\n"
"Gets the next key in a data structure. Can be used to iterate through " "Gets the next key in a data structure. Can be used to iterate through "
"the keys of a data structure in an unspecified order. Keys are guaranteed " "the keys of a data structure in an unspecified order. Keys are guaranteed "
"to be seen only once per iteration if the data structure is not mutated " "to be seen only once per iteration if the data structure is not mutated "
"during iteration. If key is nil, next returns the first key. If next " "during iteration. If key is nil, next returns the first key. If next "
"returns nil, there are no more keys to iterate through.")); "returns nil, there are no more keys to iterate through."));
janet_quick_asm(env, JANET_FUN_PROP, janet_quick_asm(env, JANET_FUN_PROP,
"propagate", 2, 2, 2, 2, propagate_asm, sizeof(propagate_asm), "propagate", 2, 2, 2, 2, propagate_asm, sizeof(propagate_asm),
JDOC("(propagate x fiber)\n\n" JDOC("(propagate x fiber)\n\n"
"Propagate a signal from a fiber to the current fiber and " "Propagate a signal from a fiber to the current fiber and "
"set the last value of the current fiber to `x`. The signal " "set the last value of the current fiber to `x`. The signal "
"value is then available as the status of the current fiber. " "value is then available as the status of the current fiber. "
"The resulting stack trace from the current fiber will include " "The resulting stack trace from the current fiber will include "
"frames from fiber. If fiber is in a state that can be resumed, " "frames from fiber. If fiber is in a state that can be resumed, "
"resuming the current fiber will first resume `fiber`. " "resuming the current fiber will first resume `fiber`. "
"This function can be used to re-raise an error without losing " "This function can be used to re-raise an error without losing "
"the original stack trace.")); "the original stack trace."));
janet_quick_asm(env, JANET_FUN_DEBUG, janet_quick_asm(env, JANET_FUN_DEBUG,
"debug", 1, 0, 1, 1, debug_asm, sizeof(debug_asm), "debug", 1, 0, 1, 1, debug_asm, sizeof(debug_asm),
JDOC("(debug &opt x)\n\n" JDOC("(debug &opt x)\n\n"
"Throws a debug signal that can be caught by a parent fiber and used to inspect " "Throws a debug signal that can be caught by a parent fiber and used to inspect "
"the running state of the current fiber. Returns the value passed in by resume.")); "the running state of the current fiber. Returns the value passed in by resume."));
janet_quick_asm(env, JANET_FUN_ERROR, janet_quick_asm(env, JANET_FUN_ERROR,
"error", 1, 1, 1, 1, error_asm, sizeof(error_asm), "error", 1, 1, 1, 1, error_asm, sizeof(error_asm),
JDOC("(error e)\n\n" JDOC("(error e)\n\n"
"Throws an error e that can be caught and handled by a parent fiber.")); "Throws an error e that can be caught and handled by a parent fiber."));
janet_quick_asm(env, JANET_FUN_YIELD, janet_quick_asm(env, JANET_FUN_YIELD,
"yield", 1, 0, 1, 2, yield_asm, sizeof(yield_asm), "yield", 1, 0, 1, 2, yield_asm, sizeof(yield_asm),
JDOC("(yield &opt x)\n\n" JDOC("(yield &opt x)\n\n"
"Yield a value to a parent fiber. When a fiber yields, its execution is paused until " "Yield a value to a parent fiber. When a fiber yields, its execution is paused until "
"another thread resumes it. The fiber will then resume, and the last yield call will " "another thread resumes it. The fiber will then resume, and the last yield call will "
"return the value that was passed to resume.")); "return the value that was passed to resume."));
janet_quick_asm(env, JANET_FUN_CANCEL, janet_quick_asm(env, JANET_FUN_CANCEL,
"cancel", 2, 2, 2, 2, cancel_asm, sizeof(cancel_asm), "cancel", 2, 2, 2, 2, cancel_asm, sizeof(cancel_asm),
JDOC("(cancel fiber err)\n\n" JDOC("(cancel fiber err)\n\n"
"Resume a fiber but have it immediately raise an error. This lets a programmer unwind a pending fiber. " "Resume a fiber but have it immediately raise an error. This lets a programmer unwind a pending fiber. "
"Returns the same result as resume.")); "Returns the same result as resume."));
janet_quick_asm(env, JANET_FUN_RESUME, janet_quick_asm(env, JANET_FUN_RESUME,
"resume", 2, 1, 2, 2, resume_asm, sizeof(resume_asm), "resume", 2, 1, 2, 2, resume_asm, sizeof(resume_asm),
JDOC("(resume fiber &opt x)\n\n" JDOC("(resume fiber &opt x)\n\n"
"Resume a new or suspended fiber and optionally pass in a value to the fiber that " "Resume a new or suspended fiber and optionally pass in a value to the fiber that "
"will be returned to the last yield in the case of a pending fiber, or the argument to " "will be returned to the last yield in the case of a pending fiber, or the argument to "
"the dispatch function in the case of a new fiber. Returns either the return result of " "the dispatch function in the case of a new fiber. Returns either the return result of "
"the fiber's dispatch function, or the value from the next yield call in fiber.")); "the fiber's dispatch function, or the value from the next yield call in fiber."));
janet_quick_asm(env, JANET_FUN_IN, janet_quick_asm(env, JANET_FUN_IN,
"in", 3, 2, 3, 4, in_asm, sizeof(in_asm), "in", 3, 2, 3, 4, in_asm, sizeof(in_asm),
JDOC("(in ds key &opt dflt)\n\n" JDOC("(in ds key &opt dflt)\n\n"
"Get value in ds at key, works on associative data structures. Arrays, tuples, tables, structs, " "Get value in ds at key, works on associative data structures. Arrays, tuples, tables, structs, "
"strings, symbols, and buffers are all associative and can be used. Arrays, tuples, strings, buffers, " "strings, symbols, and buffers are all associative and can be used. Arrays, tuples, strings, buffers, "
"and symbols must use integer keys that are in bounds or an error is raised. Structs and tables can " "and symbols must use integer keys that are in bounds or an error is raised. Structs and tables can "
"take any value as a key except nil and will return nil or dflt if not found.")); "take any value as a key except nil and will return nil or dflt if not found."));
janet_quick_asm(env, JANET_FUN_GET, janet_quick_asm(env, JANET_FUN_GET,
"get", 3, 2, 3, 4, get_asm, sizeof(in_asm), "get", 3, 2, 3, 4, get_asm, sizeof(in_asm),
JDOC("(get ds key &opt dflt)\n\n" JDOC("(get ds key &opt dflt)\n\n"
"Get the value mapped to key in data structure ds, and return dflt or nil if not found. " "Get the value mapped to key in data structure ds, and return dflt or nil if not found. "
"Similar to in, but will not throw an error if the key is invalid for the data structure " "Similar to in, but will not throw an error if the key is invalid for the data structure "
"unless the data structure is an abstract type. In that case, the abstract type getter may throw " "unless the data structure is an abstract type. In that case, the abstract type getter may throw "
"an error.")); "an error."));
janet_quick_asm(env, JANET_FUN_PUT, janet_quick_asm(env, JANET_FUN_PUT,
"put", 3, 3, 3, 3, put_asm, sizeof(put_asm), "put", 3, 3, 3, 3, put_asm, sizeof(put_asm),
JDOC("(put ds key value)\n\n" JDOC("(put ds key value)\n\n"
"Associate a key with a value in any mutable associative data structure. Indexed data structures " "Associate a key with a value in any mutable associative data structure. Indexed data structures "
"(arrays and buffers) only accept non-negative integer keys, and will expand if an out of bounds " "(arrays and buffers) only accept non-negative integer keys, and will expand if an out of bounds "
"value is provided. In an array, extra space will be filled with nils, and in a buffer, extra " "value is provided. In an array, extra space will be filled with nils, and in a buffer, extra "
"space will be filled with 0 bytes. In a table, putting a key that is contained in the table prototype " "space will be filled with 0 bytes. In a table, putting a key that is contained in the table prototype "
"will hide the association defined by the prototype, but will not mutate the prototype table. Putting " "will hide the association defined by the prototype, but will not mutate the prototype table. Putting "
"a value nil into a table will remove the key from the table. Returns the data structure ds.")); "a value nil into a table will remove the key from the table. Returns the data structure ds."));
janet_quick_asm(env, JANET_FUN_LENGTH, janet_quick_asm(env, JANET_FUN_LENGTH,
"length", 1, 1, 1, 1, length_asm, sizeof(length_asm), "length", 1, 1, 1, 1, length_asm, sizeof(length_asm),
JDOC("(length ds)\n\n" JDOC("(length ds)\n\n"
"Returns the length or count of a data structure in constant time as an integer. For " "Returns the length or count of a data structure in constant time as an integer. For "
"structs and tables, returns the number of key-value pairs in the data structure.")); "structs and tables, returns the number of key-value pairs in the data structure."));
janet_quick_asm(env, JANET_FUN_BNOT, janet_quick_asm(env, JANET_FUN_BNOT,
"bnot", 1, 1, 1, 1, bnot_asm, sizeof(bnot_asm), "bnot", 1, 1, 1, 1, bnot_asm, sizeof(bnot_asm),
JDOC("(bnot x)\n\nReturns the bit-wise inverse of integer x.")); JDOC("(bnot x)\n\nReturns the bit-wise inverse of integer x."));
@@ -1243,74 +1243,74 @@ JanetTable *janet_core_env(JanetTable *replacements) {
/* Variadic ops */ /* Variadic ops */
templatize_varop(env, JANET_FUN_ADD, "+", 0, 0, JOP_ADD, templatize_varop(env, JANET_FUN_ADD, "+", 0, 0, JOP_ADD,
JDOC("(+ & xs)\n\n" JDOC("(+ & xs)\n\n"
"Returns the sum of all xs. xs must be integers or real numbers only. If xs is empty, return 0.")); "Returns the sum of all xs. xs must be integers or real numbers only. If xs is empty, return 0."));
templatize_varop(env, JANET_FUN_SUBTRACT, "-", 0, 0, JOP_SUBTRACT, templatize_varop(env, JANET_FUN_SUBTRACT, "-", 0, 0, JOP_SUBTRACT,
JDOC("(- & xs)\n\n" JDOC("(- & xs)\n\n"
"Returns the difference of xs. If xs is empty, returns 0. If xs has one element, returns the " "Returns the difference of xs. If xs is empty, returns 0. If xs has one element, returns the "
"negative value of that element. Otherwise, returns the first element in xs minus the sum of " "negative value of that element. Otherwise, returns the first element in xs minus the sum of "
"the rest of the elements.")); "the rest of the elements."));
templatize_varop(env, JANET_FUN_MULTIPLY, "*", 1, 1, JOP_MULTIPLY, templatize_varop(env, JANET_FUN_MULTIPLY, "*", 1, 1, JOP_MULTIPLY,
JDOC("(* & xs)\n\n" JDOC("(* & xs)\n\n"
"Returns the product of all elements in xs. If xs is empty, returns 1.")); "Returns the product of all elements in xs. If xs is empty, returns 1."));
templatize_varop(env, JANET_FUN_DIVIDE, "/", 1, 1, JOP_DIVIDE, templatize_varop(env, JANET_FUN_DIVIDE, "/", 1, 1, JOP_DIVIDE,
JDOC("(/ & xs)\n\n" JDOC("(/ & xs)\n\n"
"Returns the quotient of xs. If xs is empty, returns 1. If xs has one value x, returns " "Returns the quotient of xs. If xs is empty, returns 1. If xs has one value x, returns "
"the reciprocal of x. Otherwise return the first value of xs repeatedly divided by the remaining " "the reciprocal of x. Otherwise return the first value of xs repeatedly divided by the remaining "
"values.")); "values."));
templatize_varop(env, JANET_FUN_DIVIDE_FLOOR, "div", 1, 1, JOP_DIVIDE_FLOOR, templatize_varop(env, JANET_FUN_DIVIDE_FLOOR, "div", 1, 1, JOP_DIVIDE_FLOOR,
JDOC("(div & xs)\n\n" JDOC("(div & xs)\n\n"
"Returns the floored division of xs. If xs is empty, returns 1. If xs has one value x, returns " "Returns the floored division of xs. If xs is empty, returns 1. If xs has one value x, returns "
"the reciprocal of x. Otherwise return the first value of xs repeatedly divided by the remaining " "the reciprocal of x. Otherwise return the first value of xs repeatedly divided by the remaining "
"values.")); "values."));
templatize_varop(env, JANET_FUN_MODULO, "mod", 0, 1, JOP_MODULO, templatize_varop(env, JANET_FUN_MODULO, "mod", 0, 1, JOP_MODULO,
JDOC("(mod & xs)\n\n" JDOC("(mod & xs)\n\n"
"Returns the result of applying the modulo operator on the first value of xs with each remaining value. " "Returns the result of applying the modulo operator on the first value of xs with each remaining value. "
"`(mod x 0)` is defined to be `x`.")); "`(mod x 0)` is defined to be `x`."));
templatize_varop(env, JANET_FUN_REMAINDER, "%", 0, 1, JOP_REMAINDER, templatize_varop(env, JANET_FUN_REMAINDER, "%", 0, 1, JOP_REMAINDER,
JDOC("(% & xs)\n\n" JDOC("(% & xs)\n\n"
"Returns the remainder of dividing the first value of xs by each remaining value.")); "Returns the remainder of dividing the first value of xs by each remaining value."));
templatize_varop(env, JANET_FUN_BAND, "band", -1, -1, JOP_BAND, templatize_varop(env, JANET_FUN_BAND, "band", -1, -1, JOP_BAND,
JDOC("(band & xs)\n\n" JDOC("(band & xs)\n\n"
"Returns the bit-wise and of all values in xs. Each x in xs must be an integer.")); "Returns the bit-wise and of all values in xs. Each x in xs must be an integer."));
templatize_varop(env, JANET_FUN_BOR, "bor", 0, 0, JOP_BOR, templatize_varop(env, JANET_FUN_BOR, "bor", 0, 0, JOP_BOR,
JDOC("(bor & xs)\n\n" JDOC("(bor & xs)\n\n"
"Returns the bit-wise or of all values in xs. Each x in xs must be an integer.")); "Returns the bit-wise or of all values in xs. Each x in xs must be an integer."));
templatize_varop(env, JANET_FUN_BXOR, "bxor", 0, 0, JOP_BXOR, templatize_varop(env, JANET_FUN_BXOR, "bxor", 0, 0, JOP_BXOR,
JDOC("(bxor & xs)\n\n" JDOC("(bxor & xs)\n\n"
"Returns the bit-wise xor of all values in xs. Each in xs must be an integer.")); "Returns the bit-wise xor of all values in xs. Each in xs must be an integer."));
templatize_varop(env, JANET_FUN_LSHIFT, "blshift", 1, 1, JOP_SHIFT_LEFT, templatize_varop(env, JANET_FUN_LSHIFT, "blshift", 1, 1, JOP_SHIFT_LEFT,
JDOC("(blshift x & shifts)\n\n" JDOC("(blshift x & shifts)\n\n"
"Returns the value of x bit shifted left by the sum of all values in shifts. x " "Returns the value of x bit shifted left by the sum of all values in shifts. x "
"and each element in shift must be an integer.")); "and each element in shift must be an integer."));
templatize_varop(env, JANET_FUN_RSHIFT, "brshift", 1, 1, JOP_SHIFT_RIGHT, templatize_varop(env, JANET_FUN_RSHIFT, "brshift", 1, 1, JOP_SHIFT_RIGHT,
JDOC("(brshift x & shifts)\n\n" JDOC("(brshift x & shifts)\n\n"
"Returns the value of x bit shifted right by the sum of all values in shifts. x " "Returns the value of x bit shifted right by the sum of all values in shifts. x "
"and each element in shift must be an integer.")); "and each element in shift must be an integer."));
templatize_varop(env, JANET_FUN_RSHIFTU, "brushift", 1, 1, JOP_SHIFT_RIGHT_UNSIGNED, templatize_varop(env, JANET_FUN_RSHIFTU, "brushift", 1, 1, JOP_SHIFT_RIGHT_UNSIGNED,
JDOC("(brushift x & shifts)\n\n" JDOC("(brushift x & shifts)\n\n"
"Returns the value of x bit shifted right by the sum of all values in shifts. x " "Returns the value of x bit shifted right by the sum of all values in shifts. x "
"and each element in shift must be an integer. The sign of x is not preserved, so " "and each element in shift must be an integer. The sign of x is not preserved, so "
"for positive shifts the return value will always be positive.")); "for positive shifts the return value will always be positive."));
/* Variadic comparators */ /* Variadic comparators */
templatize_comparator(env, JANET_FUN_GT, ">", 0, JOP_GREATER_THAN, templatize_comparator(env, JANET_FUN_GT, ">", 0, JOP_GREATER_THAN,
JDOC("(> & xs)\n\n" JDOC("(> & xs)\n\n"
"Check if xs is in descending order. Returns a boolean.")); "Check if xs is in descending order. Returns a boolean."));
templatize_comparator(env, JANET_FUN_LT, "<", 0, JOP_LESS_THAN, templatize_comparator(env, JANET_FUN_LT, "<", 0, JOP_LESS_THAN,
JDOC("(< & xs)\n\n" JDOC("(< & xs)\n\n"
"Check if xs is in ascending order. Returns a boolean.")); "Check if xs is in ascending order. Returns a boolean."));
templatize_comparator(env, JANET_FUN_GTE, ">=", 0, JOP_GREATER_THAN_EQUAL, templatize_comparator(env, JANET_FUN_GTE, ">=", 0, JOP_GREATER_THAN_EQUAL,
JDOC("(>= & xs)\n\n" JDOC("(>= & xs)\n\n"
"Check if xs is in non-ascending order. Returns a boolean.")); "Check if xs is in non-ascending order. Returns a boolean."));
templatize_comparator(env, JANET_FUN_LTE, "<=", 0, JOP_LESS_THAN_EQUAL, templatize_comparator(env, JANET_FUN_LTE, "<=", 0, JOP_LESS_THAN_EQUAL,
JDOC("(<= & xs)\n\n" JDOC("(<= & xs)\n\n"
"Check if xs is in non-descending order. Returns a boolean.")); "Check if xs is in non-descending order. Returns a boolean."));
templatize_comparator(env, JANET_FUN_EQ, "=", 0, JOP_EQUALS, templatize_comparator(env, JANET_FUN_EQ, "=", 0, JOP_EQUALS,
JDOC("(= & xs)\n\n" JDOC("(= & xs)\n\n"
"Check if all values in xs are equal. Returns a boolean.")); "Check if all values in xs are equal. Returns a boolean."));
templatize_comparator(env, JANET_FUN_NEQ, "not=", 1, JOP_EQUALS, templatize_comparator(env, JANET_FUN_NEQ, "not=", 1, JOP_EQUALS,
JDOC("(not= & xs)\n\n" JDOC("(not= & xs)\n\n"
"Check if any values in xs are not equal. Returns a boolean.")); "Check if any values in xs are not equal. Returns a boolean."));
/* Platform detection */ /* Platform detection */
janet_def(env, "janet/version", janet_cstringv(JANET_VERSION), janet_def(env, "janet/version", janet_cstringv(JANET_VERSION),
@@ -1319,7 +1319,7 @@ JanetTable *janet_core_env(JanetTable *replacements) {
JDOC("The build identifier of the running janet program.")); JDOC("The build identifier of the running janet program."));
janet_def(env, "janet/config-bits", janet_wrap_integer(JANET_CURRENT_CONFIG_BITS), janet_def(env, "janet/config-bits", janet_wrap_integer(JANET_CURRENT_CONFIG_BITS),
JDOC("The flag set of config options from janetconf.h which is used to check " JDOC("The flag set of config options from janetconf.h which is used to check "
"if native modules are compatible with the host program.")); "if native modules are compatible with the host program."));
/* Allow references to the environment */ /* Allow references to the environment */
janet_def(env, "root-env", janet_wrap_table(env), janet_def(env, "root-env", janet_wrap_table(env),

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

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

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

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
@@ -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>
@@ -1036,6 +1037,9 @@ JANET_CORE_FN(cfun_channel_push,
"Returns the channel if the write succeeded, nil otherwise.") { "Returns the channel if the write succeeded, nil otherwise.") {
janet_fixarity(argc, 2); janet_fixarity(argc, 2);
JanetChannel *channel = janet_getchannel(argv, 0); JanetChannel *channel = janet_getchannel(argv, 0);
if (janet_vm.coerce_error) {
janet_panic("cannot give to channel inside janet_call");
}
if (janet_channel_push(channel, argv[1], 0)) { if (janet_channel_push(channel, argv[1], 0)) {
janet_await(); janet_await();
} }
@@ -1048,6 +1052,9 @@ JANET_CORE_FN(cfun_channel_pop,
janet_fixarity(argc, 1); janet_fixarity(argc, 1);
JanetChannel *channel = janet_getchannel(argv, 0); JanetChannel *channel = janet_getchannel(argv, 0);
Janet item; Janet item;
if (janet_vm.coerce_error) {
janet_panic("cannot take from channel inside janet_call");
}
if (janet_channel_pop(channel, &item, 0)) { if (janet_channel_pop(channel, &item, 0)) {
janet_schedule(janet_vm.root_fiber, item); janet_schedule(janet_vm.root_fiber, item);
} }
@@ -1084,6 +1091,10 @@ JANET_CORE_FN(cfun_channel_choice,
int32_t len; int32_t len;
const Janet *data; const Janet *data;
if (janet_vm.coerce_error) {
janet_panic("cannot select from channel inside janet_call");
}
/* Check channels for immediate reads and writes */ /* Check channels for immediate reads and writes */
for (int32_t i = 0; i < argc; i++) { for (int32_t i = 0; i < argc; i++) {
if (janet_indexed_view(argv[i], &data, &len) && len == 2) { if (janet_indexed_view(argv[i], &data, &len) && len == 2) {
@@ -1788,6 +1799,22 @@ void janet_stream_edge_triggered(JanetStream *stream) {
} }
void janet_stream_level_triggered(JanetStream *stream) { void janet_stream_level_triggered(JanetStream *stream) {
/* On macos, we seem to need to delete any registered events before re-registering without
* EV_CLEAR, otherwise the new event will still have EV_CLEAR set erroneously. This could be a
* kernel bug, but unfortunately the specification is vague here, esp. in regards to where and when
* EV_CLEAR is set automatically. */
struct kevent kevs[2];
int length = 0;
if (stream->flags & (JANET_STREAM_READABLE | JANET_STREAM_ACCEPTABLE)) {
EV_SETx(&kevs[length++], stream->handle, EVFILT_READ, EV_DELETE, 0, 0, stream);
}
if (stream->flags & JANET_STREAM_WRITABLE) {
EV_SETx(&kevs[length++], stream->handle, EVFILT_WRITE, EV_DELETE, 0, 0, stream);
}
int status;
do {
status = kevent(janet_vm.kq, kevs, length, NULL, 0, NULL);
} while (status == -1 && errno == EINTR);
janet_register_stream_impl(stream, 0); janet_register_stream_impl(stream, 0);
} }
@@ -3275,6 +3302,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.") {
@@ -3319,6 +3404,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

@@ -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

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

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

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

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

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

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

View File

@@ -1,5 +1,5 @@
/* /*
* Copyright (c) 2024 Calvin Rose & contributors * Copyright (c) 2025 Calvin Rose & contributors
* *
* 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
@@ -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

@@ -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
@@ -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

@@ -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

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

View File

@@ -1,5 +1,5 @@
/* /*
* Copyright (c) 2024 Calvin Rose and contributors. * Copyright (c) 2025 Calvin Rose and contributors.
* *
* 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
@@ -554,7 +554,10 @@ JANET_CORE_FN(cfun_net_connect,
int err = WSAGetLastError(); int err = WSAGetLastError();
freeaddrinfo(ai); freeaddrinfo(ai);
#else #else
int status = connect(sock, addr, addrlen); int status;
do {
status = connect(sock, addr, addrlen);
} while (status == -1 && errno == EINTR);
int err = errno; int err = errno;
if (is_unix) { if (is_unix) {
janet_free(ai); janet_free(ai);
@@ -578,17 +581,23 @@ 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_addr, int reuse_port) {
/* Set various socket options */ /* Set various socket options */
int enable = 1; int enable = 1;
if (setsockopt(sfd, SOL_SOCKET, SO_REUSEADDR, (char *) &enable, sizeof(int)) < 0) { if (reuse_addr) {
return "setsockopt(SO_REUSEADDR) failed"; if (setsockopt(sfd, SOL_SOCKET, SO_REUSEADDR, (char *) &enable, sizeof(int)) < 0) {
return "setsockopt(SO_REUSEADDR) failed";
}
} }
if (reuse_port) {
#ifdef SO_REUSEPORT #ifdef SO_REUSEPORT
if (setsockopt(sfd, SOL_SOCKET, SO_REUSEPORT, &enable, sizeof(int)) < 0) { if (setsockopt(sfd, SOL_SOCKET, SO_REUSEPORT, &enable, sizeof(int)) < 0) {
return "setsockopt(SO_REUSEPORT) failed"; return "setsockopt(SO_REUSEPORT) failed";
} }
#else
(void) reuse_port;
#endif #endif
}
janet_net_socknoblock(sfd); janet_net_socknoblock(sfd);
return NULL; return NULL;
} }
@@ -642,19 +651,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 +675,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, 0);
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 +698,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, reuse);
if (NULL != err) { if (NULL != err) {
JSOCKCLOSE(sfd); JSOCKCLOSE(sfd);
continue; continue;

View File

@@ -1,5 +1,5 @@
/* /*
* Copyright (c) 2024 Calvin Rose and contributors. * Copyright (c) 2025 Calvin Rose and contributors.
* *
* 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
@@ -541,11 +541,12 @@ static void janet_proc_wait_cb(JanetEVGenericMessage args) {
proc->flags &= ~JANET_PROC_WAITING; proc->flags &= ~JANET_PROC_WAITING;
janet_gcunroot(janet_wrap_abstract(proc)); janet_gcunroot(janet_wrap_abstract(proc));
janet_gcunroot(janet_wrap_fiber(args.fiber)); janet_gcunroot(janet_wrap_fiber(args.fiber));
if ((status != 0) && (proc->flags & JANET_PROC_ERROR_NONZERO)) { uint32_t sched_id = (uint32_t) args.argi;
JanetString s = janet_formatc("command failed with non-zero exit code %d", status); if (janet_fiber_can_resume(args.fiber) && args.fiber->sched_id == sched_id) {
janet_cancel(args.fiber, janet_wrap_string(s)); if ((status != 0) && (proc->flags & JANET_PROC_ERROR_NONZERO)) {
} else { JanetString s = janet_formatc("command failed with non-zero exit code %d", status);
if (janet_fiber_can_resume(args.fiber)) { janet_cancel(args.fiber, janet_wrap_string(s));
} else {
janet_schedule(args.fiber, janet_wrap_integer(status)); janet_schedule(args.fiber, janet_wrap_integer(status));
} }
} }
@@ -603,6 +604,7 @@ os_proc_wait_impl(JanetProc *proc) {
memset(&targs, 0, sizeof(targs)); memset(&targs, 0, sizeof(targs));
targs.argp = proc; targs.argp = proc;
targs.fiber = janet_root_fiber(); targs.fiber = janet_root_fiber();
targs.argi = (uint32_t) targs.fiber->sched_id;
janet_gcroot(janet_wrap_abstract(proc)); janet_gcroot(janet_wrap_abstract(proc));
janet_gcroot(janet_wrap_fiber(targs.fiber)); janet_gcroot(janet_wrap_fiber(targs.fiber));
janet_ev_threaded_call(janet_proc_wait_subr, targs, janet_proc_wait_cb); janet_ev_threaded_call(janet_proc_wait_subr, targs, janet_proc_wait_cb);
@@ -629,16 +631,15 @@ os_proc_wait_impl(JanetProc *proc) {
JANET_CORE_FN(os_proc_wait, JANET_CORE_FN(os_proc_wait,
"(os/proc-wait proc)", "(os/proc-wait proc)",
"Suspend the current fiber until the subprocess completes. Returns the subprocess return code. " "Suspend the current fiber until the subprocess `proc` completes. Once `proc` "
"os/proc-wait cannot be called twice on the same process. If `ev/with-deadline` cancels `os/proc-wait` " "completes, return the exit code of `proc`. If called more than once on the same "
"with an error or os/proc-wait is cancelled with any error caused by anything else, os/proc-wait still " "core/process value, will raise an error. When creating subprocesses using "
"finishes in the background. Only after os/proc-wait finishes, a process is cleaned up by the operating " "`os/spawn`, this function should be called on the returned value to avoid zombie "
"system. Thus, a process becomes a zombie process if os/proc-wait is not called.") { "processes.") {
janet_fixarity(argc, 1); janet_fixarity(argc, 1);
JanetProc *proc = janet_getabstract(argv, 0, &ProcAT); JanetProc *proc = janet_getabstract(argv, 0, &ProcAT);
#ifdef JANET_EV #ifdef JANET_EV
os_proc_wait_impl(proc); os_proc_wait_impl(proc);
return janet_wrap_nil();
#else #else
return os_proc_wait_impl(proc); return os_proc_wait_impl(proc);
#endif #endif
@@ -743,12 +744,13 @@ static int get_signal_kw(const Janet *argv, int32_t n) {
JANET_CORE_FN(os_proc_kill, JANET_CORE_FN(os_proc_kill,
"(os/proc-kill proc &opt wait signal)", "(os/proc-kill proc &opt wait signal)",
"Kill a subprocess by sending SIGKILL to it on posix systems, or by closing the process " "Kill the subprocess `proc` by sending SIGKILL to it on POSIX systems, or by closing "
"handle on windows. If os/proc-wait already finished for proc, os/proc-kill raises an error. After " "the process handle on Windows. If `proc` has already completed, raise an error. If "
"sending signal to proc, if `wait` is truthy, will wait for the process to finish and return the exit " "`wait` is truthy, will wait for `proc` to complete and return the exit code (this "
"code by calling os/proc-wait. Otherwise, returns `proc`. If signal is specified, send it instead. " "will raise an error if `proc` is being waited for). Otherwise, return `proc`. If "
"Signal keywords are named after their C counterparts but in lowercase with the leading `SIG` stripped. " "`signal` is provided, send it instead of SIGKILL. Signal keywords are named after "
"Signals are ignored on windows.") { "their C counterparts but in lowercase with the leading SIG stripped. `signal` is "
"ignored on Windows.") {
janet_arity(argc, 1, 3); janet_arity(argc, 1, 3);
JanetProc *proc = janet_getabstract(argv, 0, &ProcAT); JanetProc *proc = janet_getabstract(argv, 0, &ProcAT);
if (proc->flags & JANET_PROC_WAITED) { if (proc->flags & JANET_PROC_WAITED) {
@@ -776,7 +778,6 @@ JANET_CORE_FN(os_proc_kill,
if (argc > 1 && janet_truthy(argv[1])) { if (argc > 1 && janet_truthy(argv[1])) {
#ifdef JANET_EV #ifdef JANET_EV
os_proc_wait_impl(proc); os_proc_wait_impl(proc);
return janet_wrap_nil();
#else #else
return os_proc_wait_impl(proc); return os_proc_wait_impl(proc);
#endif #endif
@@ -787,9 +788,9 @@ JANET_CORE_FN(os_proc_kill,
JANET_CORE_FN(os_proc_close, JANET_CORE_FN(os_proc_close,
"(os/proc-close proc)", "(os/proc-close proc)",
"Close pipes created by `os/spawn` if they have not been closed. Then, if os/proc-wait was not already " "Close pipes created for subprocess `proc` by `os/spawn` if they have not been "
"called on proc, os/proc-wait is called on it, and it returns the exit code returned by os/proc-wait. " "closed. Then, if `proc` is not being waited for, wait. If this function waits, when "
"Otherwise, returns nil.") { "`proc` completes, return the exit code of `proc`. Otherwise, return nil.") {
janet_fixarity(argc, 1); janet_fixarity(argc, 1);
JanetProc *proc = janet_getabstract(argv, 0, &ProcAT); JanetProc *proc = janet_getabstract(argv, 0, &ProcAT);
#ifdef JANET_EV #ifdef JANET_EV
@@ -807,7 +808,6 @@ JANET_CORE_FN(os_proc_close,
} }
#ifdef JANET_EV #ifdef JANET_EV
os_proc_wait_impl(proc); os_proc_wait_impl(proc);
return janet_wrap_nil();
#else #else
return os_proc_wait_impl(proc); return os_proc_wait_impl(proc);
#endif #endif
@@ -1268,9 +1268,6 @@ static Janet os_execute_impl(int32_t argc, Janet *argv, JanetExecuteMode mode) {
/* exec mode */ /* exec mode */
if (mode == JANET_EXECUTE_EXEC) { if (mode == JANET_EXECUTE_EXEC) {
#ifdef JANET_WINDOWS
janet_panic("not supported on windows");
#else
int status; int status;
if (!use_environ) { if (!use_environ) {
environ = envp; environ = envp;
@@ -1283,7 +1280,6 @@ static Janet os_execute_impl(int32_t argc, Janet *argv, JanetExecuteMode mode) {
} }
} while (status == -1 && errno == EINTR); } while (status == -1 && errno == EINTR);
janet_panicf("%p: %s", cargv[0], janet_strerror(errno ? errno : ENOENT)); janet_panicf("%p: %s", cargv[0], janet_strerror(errno ? errno : ENOENT));
#endif
} }
/* Use posix_spawn to spawn new process */ /* Use posix_spawn to spawn new process */
@@ -1384,45 +1380,56 @@ static Janet os_execute_impl(int32_t argc, Janet *argv, JanetExecuteMode mode) {
JANET_CORE_FN(os_execute, JANET_CORE_FN(os_execute,
"(os/execute args &opt flags env)", "(os/execute args &opt flags env)",
"Execute a program on the system and pass it string arguments. `flags` " "Execute a program on the system and return the exit code. `args` is an array/tuple "
"is a keyword that modifies how the program will execute.\n" "of strings. The first string is the name of the program and the remainder are "
"* :e - enables passing an environment to the program. Without :e, the " "arguments passed to the program. `flags` is a keyword made from the following "
"characters that modifies how the program executes:\n"
"* :e - enables passing an environment to the program. Without 'e', the "
"current environment is inherited.\n" "current environment is inherited.\n"
"* :p - allows searching the current PATH for the binary to execute. " "* :p - allows searching the current PATH for the program to execute. "
"Without this flag, binaries must use absolute paths.\n" "Without this flag, the first element of `args` must be an absolute path.\n"
"* :x - raise error if exit code is non-zero.\n" "* :x - raises error if exit code is non-zero.\n"
"* :d - Don't try and terminate the process on garbage collection (allow spawning zombies).\n" "* :d - prevents the garbage collector terminating the program (if still running) "
"`env` is a table or struct mapping environment variables to values. It can also " "and calling the equivalent of `os/proc-wait` (allows zombie processes).\n"
"contain the keys :in, :out, and :err, which allow redirecting stdio in the subprocess. " "`env` is a table/struct mapping environment variables to values. It can also "
":in, :out, and :err should be core/file values or core/stream values. core/file values and core/stream " "contain the keys :in, :out, and :err, which allow redirecting stdio in the "
"values passed to :in, :out, and :err should be closed manually because os/execute doesn't close them. " "subprocess. :in, :out, and :err should be core/file or core/stream values. "
"Returns the exit code of the program.") { "If core/stream values are used, the caller is responsible for ensuring pipes do not "
"cause the program to block and deadlock.") {
return os_execute_impl(argc, argv, JANET_EXECUTE_EXECUTE); return os_execute_impl(argc, argv, JANET_EXECUTE_EXECUTE);
} }
JANET_CORE_FN(os_spawn, JANET_CORE_FN(os_spawn,
"(os/spawn args &opt flags env)", "(os/spawn args &opt flags env)",
"Execute a program on the system and return a handle to the process. Otherwise, takes the " "Execute a program on the system and return a core/process value representing the "
"same arguments as `os/execute`. Does not wait for the process. For each of the :in, :out, and :err keys " "spawned subprocess. Takes the same arguments as `os/execute` but does not wait for "
"of the `env` argument, one can also pass in the keyword `:pipe` to get streams for standard IO of the " "the subprocess to complete. Unlike `os/execute`, the value `:pipe` can be used for "
"subprocess that can be read from and written to. The returned value `proc` has the fields :in, :out, " ":in, :out and :err keys in `env`. If used, the returned core/process will have a "
":err, and the additional field :pid on unix-like platforms. `(os/proc-wait proc)` must be called to " "writable stream in the :in field and readable streams in the :out and :err fields. "
"rejoin the subprocess. After `(os/proc-wait proc)` finishes, proc gains a new field, :return-code. " "On non-Windows systems, the subprocess PID will be in the :pid field. The caller is "
"If :x flag is passed to os/spawn, non-zero exit code will cause os/proc-wait to raise an error. " "responsible for waiting on the process (e.g. by calling `os/proc-wait` on the "
"If pipe streams created with :pipe keyword are not closed in time, janet can run out of file " "returned core/process value) to avoid creating zombie process. After the subprocess "
"descriptors. They can be closed individually, or `os/proc-close` can close all pipe streams on proc. " "completes, the exit value is in the :return-code field. If `flags` includes 'x', a "
"If pipe streams aren't read before `os/proc-wait` finishes, then pipe buffers become full, and the " "non-zero exit code will cause a waiting fiber to raise an error. The use of "
"process cannot finish because the process cannot print more on pipe buffers which are already full. " "`:pipe` may fail if there are too many active file descriptors. The caller is "
"If the process cannot finish, os/proc-wait cannot finish, either.") { "responsible for closing pipes created by `:pipe` (either individually or using "
"`os/proc-close`). Similar to `os/execute`, the caller is responsible for ensuring "
"pipes do not cause the program to block and deadlock.") {
return os_execute_impl(argc, argv, JANET_EXECUTE_SPAWN); return os_execute_impl(argc, argv, JANET_EXECUTE_SPAWN);
} }
JANET_CORE_FN(os_posix_exec, JANET_CORE_FN(os_posix_exec,
"(os/posix-exec args &opt flags env)", "(os/posix-exec args &opt flags env)",
"Use the execvpe or execve system calls to replace the current process with an interface similar to os/execute. " "Use the execvpe or execve system calls to replace the current process with an interface similar to os/execute. "
"However, instead of creating a subprocess, the current process is replaced. Is not supported on windows, and " "However, instead of creating a subprocess, the current process is replaced. Is not supported on Windows, and "
"does not allow redirection of stdio.") { "does not allow redirection of stdio.") {
#ifdef JANET_WINDOWS
(void) argc;
(void) argv;
janet_panic("not supported on Windows");
#else
return os_execute_impl(argc, argv, JANET_EXECUTE_EXEC); return os_execute_impl(argc, argv, JANET_EXECUTE_EXEC);
#endif
} }
JANET_CORE_FN(os_posix_fork, JANET_CORE_FN(os_posix_fork,
@@ -1433,7 +1440,7 @@ JANET_CORE_FN(os_posix_fork,
janet_fixarity(argc, 0); janet_fixarity(argc, 0);
(void) argv; (void) argv;
#ifdef JANET_WINDOWS #ifdef JANET_WINDOWS
janet_panic("not supported"); janet_panic("not supported on Windows");
#else #else
pid_t result; pid_t result;
do { do {
@@ -1880,7 +1887,6 @@ JANET_CORE_FN(os_mktime,
/* utc time */ /* utc time */
#ifdef JANET_NO_UTC_MKTIME #ifdef JANET_NO_UTC_MKTIME
janet_panic("os/mktime UTC not supported on this platform"); janet_panic("os/mktime UTC not supported on this platform");
return janet_wrap_nil();
#else #else
t = timegm(&t_info); t = timegm(&t_info);
#endif #endif
@@ -1947,8 +1953,7 @@ JANET_CORE_FN(os_link,
#ifdef JANET_WINDOWS #ifdef JANET_WINDOWS
(void) argc; (void) argc;
(void) argv; (void) argv;
janet_panic("os/link not supported on Windows"); janet_panic("not supported on Windows");
return janet_wrap_nil();
#else #else
const char *oldpath = janet_getcstring(argv, 0); const char *oldpath = janet_getcstring(argv, 0);
const char *newpath = janet_getcstring(argv, 1); const char *newpath = janet_getcstring(argv, 1);
@@ -1966,8 +1971,7 @@ JANET_CORE_FN(os_symlink,
#ifdef JANET_WINDOWS #ifdef JANET_WINDOWS
(void) argc; (void) argc;
(void) argv; (void) argv;
janet_panic("os/symlink not supported on Windows"); janet_panic("not supported on Windows");
return janet_wrap_nil();
#else #else
const char *oldpath = janet_getcstring(argv, 0); const char *oldpath = janet_getcstring(argv, 0);
const char *newpath = janet_getcstring(argv, 1); const char *newpath = janet_getcstring(argv, 1);
@@ -2069,8 +2073,7 @@ JANET_CORE_FN(os_readlink,
#ifdef JANET_WINDOWS #ifdef JANET_WINDOWS
(void) argc; (void) argc;
(void) argv; (void) argv;
janet_panic("os/readlink not supported on Windows"); janet_panic("not supported on Windows");
return janet_wrap_nil();
#else #else
static char buffer[PATH_MAX]; static char buffer[PATH_MAX];
const char *path = janet_getcstring(argv, 0); const char *path = janet_getcstring(argv, 0);
@@ -2326,7 +2329,6 @@ static Janet os_stat_or_lstat(int do_lstat, int32_t argc, Janet *argv) {
return sg->fn(&st); return sg->fn(&st);
} }
janet_panicf("unexpected keyword %v", janet_wrap_keyword(key)); janet_panicf("unexpected keyword %v", janet_wrap_keyword(key));
return janet_wrap_nil();
} }
} }

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
@@ -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) { *w++ = *r++;
/* Skip leading newline */
r++;
} else {
*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
@@ -342,7 +342,7 @@ tail:
while (captured < hi) { while (captured < hi) {
CapState cs2 = cap_save(s); CapState cs2 = cap_save(s);
next_text = peg_rule(s, rule_a, text); next_text = peg_rule(s, rule_a, text);
if (!next_text || next_text == text) { if (!next_text || ((next_text == text) && (hi == UINT32_MAX))) {
cap_load(s, cs2); cap_load(s, cs2);
break; break;
} }
@@ -544,41 +544,80 @@ tail:
return window_end; return window_end;
} }
case RULE_TIL: {
const uint32_t *rule_terminus = s->bytecode + rule[1];
const uint32_t *rule_subpattern = s->bytecode + rule[2];
const uint8_t *terminus_start = text;
const uint8_t *terminus_end = NULL;
down1(s);
while (terminus_start <= s->text_end) {
CapState cs2 = cap_save(s);
terminus_end = peg_rule(s, rule_terminus, terminus_start);
cap_load(s, cs2);
if (terminus_end) {
break;
}
terminus_start++;
}
up1(s);
if (!terminus_end) {
return NULL;
}
const uint8_t *saved_end = s->text_end;
s->text_end = terminus_start;
down1(s);
const uint8_t *matched = peg_rule(s, rule_subpattern, text);
up1(s);
s->text_end = saved_end;
if (!matched) {
return NULL;
}
return terminus_end;
}
case RULE_SPLIT: { case RULE_SPLIT: {
const uint8_t *saved_end = s->text_end; const uint8_t *saved_end = s->text_end;
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;
} }
@@ -1227,6 +1266,14 @@ static void spec_sub(Builder *b, int32_t argc, const Janet *argv) {
emit_2(r, RULE_SUB, subrule1, subrule2); emit_2(r, RULE_SUB, subrule1, subrule2);
} }
static void spec_til(Builder *b, int32_t argc, const Janet *argv) {
peg_fixarity(b, argc, 2);
Reserve r = reserve(b, 3);
uint32_t subrule1 = peg_compile1(b, argv[0]);
uint32_t subrule2 = peg_compile1(b, argv[1]);
emit_2(r, RULE_TIL, subrule1, subrule2);
}
static void spec_split(Builder *b, int32_t argc, const Janet *argv) { static void spec_split(Builder *b, int32_t argc, const Janet *argv) {
peg_fixarity(b, argc, 2); peg_fixarity(b, argc, 2);
Reserve r = reserve(b, 3); Reserve r = reserve(b, 3);
@@ -1323,6 +1370,7 @@ static const SpecialPair peg_specials[] = {
{"split", spec_split}, {"split", spec_split},
{"sub", spec_sub}, {"sub", spec_sub},
{"thru", spec_thru}, {"thru", spec_thru},
{"til", spec_til},
{"to", spec_to}, {"to", spec_to},
{"uint", spec_uint_le}, {"uint", spec_uint_le},
{"uint-be", spec_uint_be}, {"uint-be", spec_uint_be},
@@ -1416,6 +1464,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));
@@ -1657,6 +1710,7 @@ static void *peg_unmarshal(JanetMarshalContext *ctx) {
i += 4; i += 4;
break; break;
case RULE_SUB: case RULE_SUB:
case RULE_TIL:
case RULE_SPLIT: case RULE_SPLIT:
/* [rule, rule] */ /* [rule, rule] */
if (rule[1] >= blen) goto bad; if (rule[1] >= blen) goto bad;

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

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

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

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
@@ -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

@@ -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

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

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
@@ -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

@@ -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

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
@@ -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

@@ -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
@@ -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

@@ -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

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

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
@@ -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

@@ -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

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

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
@@ -205,9 +205,9 @@ int janet_make_pipe(JanetHandle handles[2], int mode);
#ifdef JANET_FILEWATCH #ifdef JANET_FILEWATCH
void janet_lib_filewatch(JanetTable *env); void janet_lib_filewatch(JanetTable *env);
#endif #endif
#endif
#ifdef JANET_FFI #ifdef JANET_FFI
void janet_lib_ffi(JanetTable *env); void janet_lib_ffi(JanetTable *env);
#endif #endif
#endif
#endif #endif

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

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

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

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
@@ -798,14 +798,14 @@ static JanetSignal run_vm(JanetFiber *fiber, Janet in) {
vm_pcnext(); vm_pcnext();
VM_OP(JOP_JUMP) VM_OP(JOP_JUMP)
pc += DS;
vm_maybe_auto_suspend(DS <= 0); vm_maybe_auto_suspend(DS <= 0);
pc += DS;
vm_next(); vm_next();
VM_OP(JOP_JUMP_IF) VM_OP(JOP_JUMP_IF)
if (janet_truthy(stack[A])) { if (janet_truthy(stack[A])) {
pc += ES;
vm_maybe_auto_suspend(ES <= 0); vm_maybe_auto_suspend(ES <= 0);
pc += ES;
} else { } else {
pc++; pc++;
} }
@@ -815,15 +815,15 @@ static JanetSignal run_vm(JanetFiber *fiber, Janet in) {
if (janet_truthy(stack[A])) { if (janet_truthy(stack[A])) {
pc++; pc++;
} else { } else {
pc += ES;
vm_maybe_auto_suspend(ES <= 0); vm_maybe_auto_suspend(ES <= 0);
pc += ES;
} }
vm_next(); vm_next();
VM_OP(JOP_JUMP_IF_NIL) VM_OP(JOP_JUMP_IF_NIL)
if (janet_checktype(stack[A], JANET_NIL)) { if (janet_checktype(stack[A], JANET_NIL)) {
pc += ES;
vm_maybe_auto_suspend(ES <= 0); vm_maybe_auto_suspend(ES <= 0);
pc += ES;
} else { } else {
pc++; pc++;
} }
@@ -833,8 +833,8 @@ static JanetSignal run_vm(JanetFiber *fiber, Janet in) {
if (janet_checktype(stack[A], JANET_NIL)) { if (janet_checktype(stack[A], JANET_NIL)) {
pc++; pc++;
} else { } else {
pc += ES;
vm_maybe_auto_suspend(ES <= 0); vm_maybe_auto_suspend(ES <= 0);
pc += ES;
} }
vm_next(); vm_next();
@@ -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,15 @@ 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 */
#ifdef JANET_EV
if (janet_vm.root_fiber != NULL && signal == JANET_SIGNAL_EVENT) {
janet_vm.root_fiber->sched_id++;
}
#endif
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 +1442,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 +1454,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

@@ -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

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
@@ -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 *****/
@@ -2181,6 +2182,7 @@ typedef enum {
RULE_UNREF, /* [rule, tag] */ RULE_UNREF, /* [rule, tag] */
RULE_CAPTURE_NUM, /* [rule, tag] */ RULE_CAPTURE_NUM, /* [rule, tag] */
RULE_SUB, /* [rule, rule] */ RULE_SUB, /* [rule, rule] */
RULE_TIL, /* [rule, rule] */
RULE_SPLIT, /* [rule, rule] */ RULE_SPLIT, /* [rule, rule] */
RULE_NTH, /* [nth, rule, tag] */ RULE_NTH, /* [nth, rule, tag] */
RULE_ONLY_TAGS, /* [rule] */ RULE_ONLY_TAGS, /* [rule] */

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

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

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

@@ -1,4 +1,4 @@
# Copyright (c) 2023 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

View File

@@ -1,4 +1,4 @@
# Copyright (c) 2023 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

View File

@@ -1,4 +1,4 @@
# Copyright (c) 2023 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
@@ -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"
@@ -995,4 +1002,18 @@
(assert-error "assertf error 3" (assertf false "%s message" "mystery")) (assert-error "assertf error 3" (assertf false "%s message" "mystery"))
(assert-error "assertf error 4" (assertf nil "%s %s" "alice" "bob")) (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

@@ -1,4 +1,4 @@
# 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

View File

@@ -1,4 +1,4 @@
# 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

View File

@@ -1,4 +1,4 @@
# Copyright (c) 2023 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

View File

@@ -1,4 +1,4 @@
# Copyright (c) 2023 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

View File

@@ -1,4 +1,4 @@
# Copyright (c) 2023 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

View File

@@ -1,4 +1,4 @@
# Copyright (c) 2023 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
@@ -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

@@ -1,4 +1,4 @@
# Copyright (c) 2023 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

View File

@@ -1,4 +1,4 @@
# Copyright (c) 2023 Calvin Rose & contributors # Copyright (c) 2025 Calvin Rose & contributors
# #
# 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
@@ -199,7 +199,7 @@
(assert s "made server 1") (assert s "made server 1")
(defn test-echo [msg] (defn test-echo [msg]
(with [conn (net/connect test-host test-port)] (with [conn (assert (net/connect test-host test-port))]
(net/write conn msg) (net/write conn msg)
(def res (net/read conn 1024)) (def res (net/read conn 1024))
(assert (= (string res) msg) (string "echo " msg)))) (assert (= (string res) msg) (string "echo " msg))))
@@ -213,6 +213,7 @@
# Test on both server and client # Test on both server and client
# 504411e # 504411e
(var iterations 0)
(defn names-handler (defn names-handler
[stream] [stream]
(defer (:close stream) (defer (:close stream)
@@ -220,21 +221,26 @@
(ev/read stream 1) (ev/read stream 1)
(def [host port] (net/localname stream)) (def [host port] (net/localname stream))
(assert (= host test-host) "localname host server") (assert (= host test-host) "localname host server")
(assert (= port (scan-number test-port)) "localname port server"))) (assert (= port (scan-number test-port)) "localname port server")
(++ iterations)
(ev/write stream " ")))
# Test localname and peername # Test localname and peername
# 077bf5eba # 077bf5eba
(repeat 10 (repeat 10
(with [s (net/server test-host test-port names-handler)] (with [s (net/server test-host test-port names-handler)]
(repeat 10 (repeat 10
(with [conn (net/connect test-host test-port)] (with [conn (assert (net/connect test-host test-port))]
(def [host port] (net/peername conn)) (def [host port] (net/peername conn))
(assert (= host test-host) "peername host client ") (assert (= host test-host) "peername host client ")
(assert (= port (scan-number test-port)) "peername port client") (assert (= port (scan-number test-port)) "peername port client")
# let server close (++ iterations)
(ev/write conn " ")))) (ev/write conn " ")
(ev/read conn 1))))
(gccollect)) (gccollect))
(assert (= iterations 200) "localname and peername not enough checks")
# Create pipe # Create pipe
# 12f09ad2d # 12f09ad2d
(var pipe-counter 0) (var pipe-counter 0)
@@ -410,6 +416,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,11 +428,17 @@
(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 (assert (net/connect test-host test-port :stream)))
(expect-read bob "Whats your name?\n") (expect-read bob "Whats your name?\n")
(net/write bob "bob") (if (= :mingw (os/which))
(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 (assert (net/connect test-host test-port)))
(expect-read alice "Whats your name?\n") (expect-read alice "Whats your name?\n")
(net/write alice "alice") (net/write alice "alice")
(expect-read alice "Welcome alice\n") (expect-read alice "Welcome alice\n")
@@ -436,7 +452,7 @@
(expect-read bob "[alice]:hi\n") (expect-read bob "[alice]:hi\n")
# Ted joins the chat server # Ted joins the chat server
(def ted (net/connect test-host test-port)) (def ted (assert (net/connect test-host test-port)))
(expect-read ted "Whats your name?\n") (expect-read ted "Whats your name?\n")
(net/write ted "ted") (net/write ted "ted")
(expect-read ted "Welcome ted\n") (expect-read ted "Welcome ted\n")
@@ -465,4 +481,73 @@
# Close chat server # Close chat server
(:close chat-server) (:close chat-server)
# Issue #1531
(defn sleep-print [x] (ev/sleep 0) (print x))
(protect (with-dyns [*out* sleep-print] (prin :foo)))
(defn level-trigger-handling [conn &] (:close conn))
(def s (assert (net/server test-host test-port level-trigger-handling)))
(def c (assert (net/connect test-host test-port)))
(:close s)
# Issue #1531 no. 2
(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)
# soreuseport on unix domain sockets
(compwhen (or (= :macos (os/which)) (= :linux (os/which)))
(assert-no-error "unix-domain socket reuseaddr"
(let [s (net/listen :unix "./unix-domain-socket" :stream)]
(:close s))))
# net/accept-loop level triggering
(gccollect)
(def maxconn 50)
(var connect-count 0)
(defn level-trigger-handling
[conn &]
(with [conn conn]
(ev/write conn (ev/read conn 4096))
(++ connect-count)))
(def s (assert (net/server test-host test-port level-trigger-handling)))
(def cons @[])
(repeat maxconn (array/push cons (assert (net/connect test-host test-port))))
(assert (= maxconn (length cons)))
(defn do-connect [i]
(with [c (get cons i)]
(ev/write c "abc123")
(ev/read c 4096)))
(for i 0 maxconn (ev/spawn (do-connect i)))
(ev/sleep 0.1)
(assert (= maxconn connect-count))
(:close s)
# Cancel os/proc-wait with ev/deadline
(let [p (os/spawn [;run janet "-e" "(os/sleep 4)"] :p)]
(var terminated-normally false)
(assert-error "deadline expired"
(ev/with-deadline 0.01
(os/proc-wait p)
(print "uhoh")
(set terminated-normally true)))
(assert (not terminated-normally) "early termination failure")
# Without this kill, janet will wait the full 4 seconds for the subprocess to complete before exiting.
(assert-no-error "kill proc after wait failed" (os/proc-kill p)))
# Cancel os/proc-wait with ev/deadline 2
(let [p (os/spawn [;run janet "-e" "(os/sleep 0.1)"] :p)]
(var terminated-normally false)
(assert-error "deadline expired"
(ev/with-deadline 0.05
(os/proc-wait p)
(print "uhoh")
(set terminated-normally true)))
(assert (not terminated-normally) "early termination failure 2")
(ev/sleep 0.15)
(assert (not terminated-normally) "early termination failure 3"))
(end-suite) (end-suite)

View File

@@ -1,4 +1,4 @@
# Copyright (c) 2023 Calvin Rose & contributors # Copyright (c) 2025 Calvin Rose & contributors
# #
# 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

View File

@@ -1,4 +1,4 @@
# Copyright (c) 2024 Calvin Rose & contributors # Copyright (c) 2025 Calvin Rose & contributors
# #
# 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

View File

@@ -1,4 +1,4 @@
# Copyright (c) 2023 Calvin Rose & contributors # Copyright (c) 2025 Calvin Rose & contributors
# #
# 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

View File

@@ -1,4 +1,4 @@
# Copyright (c) 2023 Calvin Rose & contributors # Copyright (c) 2025 Calvin Rose & contributors
# #
# 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

View File

@@ -1,4 +1,4 @@
# Copyright (c) 2023 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
@@ -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

@@ -1,4 +1,4 @@
# Copyright (c) 2023 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

View File

@@ -1,4 +1,4 @@
# Copyright (c) 2023 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

View File

@@ -1,4 +1,4 @@
# Copyright (c) 2023 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
@@ -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

@@ -1,4 +1,4 @@
# Copyright (c) 2023 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
@@ -713,6 +713,41 @@
"abcdef" "abcdef"
@[]) @[])
(test "til: basic matching"
~(til "d" "abc")
"abcdef"
@[])
(test "til: second pattern can't see past the first occurrence of first pattern"
~(til "d" (* "abc" -1))
"abcdef"
@[])
(test "til: fails if first pattern fails"
~(til "x" "abc")
"abcdef"
nil)
(test "til: fails if second pattern fails"
~(til "abc" "x")
"abcdef"
nil)
(test "til: discards captures from initial pattern"
~(til '"d" '"abc")
"abcdef"
@["abc"])
(test "til: positions inside second match are still relative to the entire input"
~(* "one\ntw" (til 0 (* ($) (line) (column))))
"one\ntwo\nthree\n"
@[6 2 3])
(test "til: advances to the end of the first pattern's first occurrence"
~(* (til "d" "ab") "e")
"abcdef"
@[])
(test "split: basic functionality" (test "split: basic functionality"
~(split "," '1) ~(split "," '1)
"a,b,c" "a,b,c"
@@ -772,5 +807,33 @@
"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"])
# Issue 1554 - 0-width match termination behavior
(test "issue 1554 case 1" '(any (> '1)) "abc" @[])
(test "issue 1554 case 2" '(any (? (> '1))) "abc" @[])
(test "issue 1554 case 3" '(any (> (? '1))) "abc" @[])
(test "issue 1554 case 4" '(* "a" (> '1)) "abc" @["b"])
(test "issue 1554 case 5" '(* "a" (? (> '1))) "abc" @["b"])
(test "issue 1554 case 6" '(* "a" (> (? '1))) "abc" @["b"])
(test "issue 1554 case 7" '(between 0 2 (> '1)) "abc" @["a" "a"])
(test "issue 1554 case 8" '(between 2 3 (? (> '1))) "abc" @["a" "a" "a"])
(test "issue 1554 case 9" '(between 0 0 (> (? '1))) "abc" @[])
(end-suite) (end-suite)

View File

@@ -1,4 +1,4 @@
# Copyright (c) 2023 Calvin Rose & contributors # Copyright (c) 2025 Calvin Rose & contributors
# #
# 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

View File

@@ -1,4 +1,4 @@
# Copyright (c) 2023 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

View File

@@ -1,4 +1,4 @@
# Copyright (c) 2023 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

View File

@@ -1,4 +1,4 @@
# Copyright (c) 2023 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

View File

@@ -1,4 +1,4 @@
# Copyright (c) 2023 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

View File

@@ -1,4 +1,4 @@
# Copyright (c) 2023 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

View File

@@ -1,4 +1,4 @@
# Copyright (c) 2023 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

View File

@@ -1,4 +1,4 @@
# Copyright (c) 2023 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

View File

@@ -1,4 +1,4 @@
# Copyright (c) 2023 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

View File

@@ -1,4 +1,4 @@
# Copyright (c) 2023 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

Some files were not shown because too many files have changed in this diff Show More