From aaf8ac2217de1c18cb4a6652a19d0b1b4ad40a94 Mon Sep 17 00:00:00 2001 From: Joshua Krusell Date: Tue, 23 Sep 2025 11:28:06 +0200 Subject: [PATCH 001/307] Tweak kvs docstring --- src/boot/boot.janet | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/boot/boot.janet b/src/boot/boot.janet index 49c5a942..984a18fd 100644 --- a/src/boot/boot.janet +++ b/src/boot/boot.janet @@ -1803,8 +1803,8 @@ (flatten-into @[] xs)) (defn kvs - ``Takes a table or struct and returns and array of key value pairs - like `@[k v k v ...]`. Returns a new array.`` + ``Takes a table or struct and returns a new array of key value pairs + like `@[k v k v ...]`.`` [dict] (def ret @[]) (loop [k :keys dict] (array/push ret k (in dict k))) From 07bfd34c2f227dddbd3a2c5844668ae08a24fbc6 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?J=2E=20Neusch=C3=A4fer?= Date: Fri, 26 Sep 2025 02:07:09 +0000 Subject: [PATCH 002/307] Recognise aarch64_be as big-endian --- src/include/janet.h | 1 + 1 file changed, 1 insertion(+) diff --git a/src/include/janet.h b/src/include/janet.h index 2084418a..352d296d 100644 --- a/src/include/janet.h +++ b/src/include/janet.h @@ -147,6 +147,7 @@ extern "C" { || defined(__s390x__) /* S390 64-bit */ \ || defined(__s390__) /* S390 32-bit */ \ || defined(__ARMEB__) /* ARM big endian */ \ + || defined(__AARCH64EB__) /* ARM64 big endian */ \ || ((defined(__CC_ARM) || defined(__ARMCC__)) /* ARM RealView compiler */ \ && defined(__BIG_ENDIAN)) #define JANET_BIG_ENDIAN 1 From 6a96b615f0113a5c671fea71c2b66e89df8348dc Mon Sep 17 00:00:00 2001 From: Michael Camilleri Date: Tue, 23 Sep 2025 09:18:51 +0900 Subject: [PATCH 003/307] Tidy up bundle/ docstrings --- src/boot/boot.janet | 61 +++++++++++++++++++++++---------------------- 1 file changed, 31 insertions(+), 30 deletions(-) diff --git a/src/boot/boot.janet b/src/boot/boot.janet index 984a18fd..31680ced 100644 --- a/src/boot/boot.janet +++ b/src/boot/boot.janet @@ -4194,7 +4194,7 @@ (spit manifest-name b)) (defn bundle/manifest - "Get the manifest for a give installed bundle" + "Get the manifest for a given installed bundle." [bundle-name] (def name (get-manifest-filename bundle-name)) (assertf (fexists name) "no bundle %v found" bundle-name) @@ -4256,7 +4256,9 @@ nil) (defn bundle/uninstall - "Remove a bundle from the current syspath" + ``Remove a bundle from the current syspath. There is 1 hook called during + uninstallation (uninstall). A user can register a hook by defining a + function with the same name in the bundle script.`` [bundle-name] (def breakage @{}) (each b (bundle/list) @@ -4292,8 +4294,8 @@ order) (defn bundle/prune - "Remove all orphaned bundles from the syspath. An orphaned bundle is a bundle that is - marked for :auto-remove and is not depended on by any other bundle." + ``Remove all orphaned bundles from the current syspath. An orphaned bundle is a + bundle that is marked for :auto-remove and is not depended on by any other bundle.`` [] (def topo (bundle/topolist)) (def rtopo (reverse topo)) @@ -4322,7 +4324,11 @@ (not (not (os/stat (bundle-dir bundle-name) :mode)))) (defn bundle/install - "Install a bundle from the local filesystem. The name of the bundle will be inferred from the bundle, or passed as a parameter :name in `config`." + ``Install a bundle from the local filesystem. The name of the bundle is + the value mapped to :name in either `config` or the info file. There are + 5 hooks called during installation (dependencies, clean, build, install and + check). A user can register a hook by defining a function with the same name + in the bundle script.`` [path &keys config] (def path (bundle-rpath path)) (def s (sep)) @@ -4366,20 +4372,16 @@ (error (string "missing dependencies " (string/join missing ", ")))) (put man :dependencies deps) (put man :info info)) - (def clean (get config :clean)) - (def check (get config :check)) (def module (get-bundle-module bundle-name)) (def all-hooks (seq [[k v] :pairs module :when (symbol? k) :unless (get v :private)] (keyword k))) (put man :hooks all-hooks) (do-hook module bundle-name :dependencies man) - (when clean - (do-hook module bundle-name :clean man)) + (do-hook module bundle-name :clean man) (do-hook module bundle-name :build man) (do-hook module bundle-name :install man) (if (empty? (get man :files)) (print "no files installed, is this a valid bundle?")) (sync-manifest man) - (when check - (do-hook module bundle-name :check man))) + (do-hook module bundle-name :check man)) (print "installed " bundle-name) (when (get man :has-bin-script) (def binpath (string (dyn *syspath*) s "bin")) @@ -4387,9 +4389,10 @@ bundle-name) (defn- bundle/pack - "Take an installed bundle and create a bundle source directory that can be used to - reinstall the bundle on a compatible system. This is used to create backups for installed - bundles without rebuilding, or make a prebuilt bundle for other systems." + ``Take an installed bundle and create a bundle source directory that can be + used to reinstall the bundle on a compatible system. This is used to create + backups for installed bundles without rebuilding, or make a prebuilt bundle + for other systems.`` [bundle-name dest-dir &opt is-backup] (var i 0) (def man (bundle/manifest bundle-name)) @@ -4419,9 +4422,9 @@ dest-dir) (defn bundle/replace - "Reinstall an existing bundle from a new directory. Similar to bundle/reinstall, - but installs the replacement bundle from any directory. This is necesarry to replace a package without - breaking any dependencies." + ``Reinstall an existing bundle from a new directory. Similar to + bundle/reinstall, but installs the replacement bundle from any directory. + This is necessary to replace a package without breaking any dependencies.`` [bundle-name path &keys new-config] (def manifest (bundle/manifest bundle-name)) (def config (get manifest :config @{})) @@ -4448,7 +4451,7 @@ bundle-name) (defn bundle/add-directory - "Add a directory during the install process relative to `(dyn *syspath*)`" + "Add a directory during an install relative to `(dyn *syspath*)`." [manifest dest &opt chmod-mode] (def files (get-files manifest)) (def s (sep)) @@ -4476,7 +4479,7 @@ ret) (defn bundle/add-file - "Add files during an install relative to `(dyn *syspath*)`" + "Add a file during an install relative to `(dyn *syspath*)`." [manifest src &opt dest chmod-mode] (default dest src) (def files (get-files manifest)) @@ -4493,9 +4496,9 @@ absdest) (defn bundle/add - "Add files and directories during a bundle install relative to `(dyn *syspath*)`. - Added files and directories will be recorded in the bundle manifest such that they are properly tracked - and removed during an upgrade or uninstall." + ``Add a file or directory during an install relative to `(dyn *syspath*)`. + Added files and directories will be recorded in the bundle manifest such + that they are properly tracked and removed during an upgrade or uninstall.`` [manifest src &opt dest chmod-mode] (default dest src) (def s (sep)) @@ -4510,20 +4513,18 @@ (errorf "bad path %s - file is a %s" src mode))) (defn bundle/add-bin - `` - 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. - `` - [manifest src &opt dest chmod-mode] + ``Add a script to the bin subdirectory of the current syspath. Scripts will + be set to be executable.`` + [manifest src &opt filename chmod-mode] (def s (sep)) - (default dest (last (string/split s src))) + (default filename (last (string/split s src))) (default chmod-mode 8r755) (os/mkdir (string (dyn *syspath*) s "bin")) (put manifest :has-bin-script true) - (bundle/add-file manifest src (string "bin" s dest) chmod-mode)) + (bundle/add-file manifest src (string "bin" s filename) chmod-mode)) (defn bundle/update-all - "Reinstall all bundles" + "Reinstall all bundles." [&keys configs] (each bundle (bundle/topolist) (bundle/reinstall bundle ;(kvs configs))))) From 6b8e5249ca85e885ddf9a63a42907178a5f25ba0 Mon Sep 17 00:00:00 2001 From: Michael Camilleri Date: Tue, 23 Sep 2025 09:19:57 +0900 Subject: [PATCH 004/307] Change :dependencies hook to :postdeps --- src/boot/boot.janet | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/src/boot/boot.janet b/src/boot/boot.janet index 31680ced..64373160 100644 --- a/src/boot/boot.janet +++ b/src/boot/boot.janet @@ -4326,7 +4326,7 @@ (defn bundle/install ``Install a bundle from the local filesystem. The name of the bundle is the value mapped to :name in either `config` or the info file. There are - 5 hooks called during installation (dependencies, clean, build, install and + 5 hooks called during installation (postdeps, clean, build, install and check). A user can register a hook by defining a function with the same name in the bundle script.`` [path &keys config] @@ -4375,7 +4375,8 @@ (def module (get-bundle-module bundle-name)) (def all-hooks (seq [[k v] :pairs module :when (symbol? k) :unless (get v :private)] (keyword k))) (put man :hooks all-hooks) - (do-hook module bundle-name :dependencies man) + (do-hook module bundle-name :dependencies man) # deprecated, use :postdeps + (do-hook module bundle-name :postdeps man) (do-hook module bundle-name :clean man) (do-hook module bundle-name :build man) (do-hook module bundle-name :install man) From f39353133528a405a2dd790d285f280ef52f151a Mon Sep 17 00:00:00 2001 From: Michael Camilleri Date: Tue, 23 Sep 2025 09:36:16 +0900 Subject: [PATCH 005/307] Add bundle/add-manpage --- src/boot/boot.janet | 21 ++++++++++++++++++--- 1 file changed, 18 insertions(+), 3 deletions(-) diff --git a/src/boot/boot.janet b/src/boot/boot.janet index 64373160..4ff5a851 100644 --- a/src/boot/boot.janet +++ b/src/boot/boot.janet @@ -4386,7 +4386,10 @@ (print "installed " bundle-name) (when (get man :has-bin-script) (def binpath (string (dyn *syspath*) s "bin")) - (eprintf "executable scripts have been installed to %s" binpath)) + (eprintf "executable files have been installed to %s" binpath)) + (when (get man :has-man) + (def manpath (string (dyn *syspath*) s "man")) + (eprintf "man pages have been installed to %s" manpath)) bundle-name) (defn- bundle/pack @@ -4514,8 +4517,8 @@ (errorf "bad path %s - file is a %s" src mode))) (defn bundle/add-bin - ``Add a script to the bin subdirectory of the current syspath. Scripts will - be set to be executable.`` + ``Add a file to the "bin" subdirectory of the current syspath. By default, + files will be set to be executable.`` [manifest src &opt filename chmod-mode] (def s (sep)) (default filename (last (string/split s src))) @@ -4524,6 +4527,18 @@ (put manifest :has-bin-script true) (bundle/add-file manifest src (string "bin" s filename) chmod-mode)) + (defn bundle/add-manpage + ``Add a file to the man subdirectory of the current syspath. Files are + copied inside a directory `mansec`. By default, `mansec` is "man1".`` + [manifest src &opt mansec] + (def s (sep)) + (default mansec "man1") + (def filename (last (string/split s src))) + (os/mkdir (string (dyn *syspath*) s "man")) + (os/mkdir (string (dyn *syspath*) s "man" s mansec)) + (put manifest :has-man true) + (bundle/add-file manifest src (string "man" s mansec s filename))) + (defn bundle/update-all "Reinstall all bundles." [&keys configs] From 9b99fc44b984211acd8ffb14f3321711a723a771 Mon Sep 17 00:00:00 2001 From: sogaiu <983021772@users.noreply.github.com> Date: Mon, 29 Sep 2025 23:45:53 +0900 Subject: [PATCH 006/307] Revert some bundle/install changes --- src/boot/boot.janet | 8 ++++++-- 1 file changed, 6 insertions(+), 2 deletions(-) diff --git a/src/boot/boot.janet b/src/boot/boot.janet index 4ff5a851..f50ef7de 100644 --- a/src/boot/boot.janet +++ b/src/boot/boot.janet @@ -4373,16 +4373,20 @@ (put man :dependencies deps) (put man :info info)) (def module (get-bundle-module bundle-name)) + (def clean (get config :clean)) + (def check (get config :check)) (def all-hooks (seq [[k v] :pairs module :when (symbol? k) :unless (get v :private)] (keyword k))) (put man :hooks all-hooks) (do-hook module bundle-name :dependencies man) # deprecated, use :postdeps (do-hook module bundle-name :postdeps man) - (do-hook module bundle-name :clean man) + (when clean + (do-hook module bundle-name :clean man)) (do-hook module bundle-name :build man) (do-hook module bundle-name :install man) (if (empty? (get man :files)) (print "no files installed, is this a valid bundle?")) (sync-manifest man) - (do-hook module bundle-name :check man)) + (when check + (do-hook module bundle-name :check man))) (print "installed " bundle-name) (when (get man :has-bin-script) (def binpath (string (dyn *syspath*) s "bin")) From c901edbfb969972802252e4203f18f371f054d95 Mon Sep 17 00:00:00 2001 From: Calvin Rose Date: Sat, 4 Oct 2025 10:42:20 -0500 Subject: [PATCH 007/307] Interpret an empty JANET_PATH as unset This is a common idiom with environment variables, where a variable set to the empty string should behave the same as an unset variable. --- src/boot/boot.janet | 11 ++++++----- 1 file changed, 6 insertions(+), 5 deletions(-) diff --git a/src/boot/boot.janet b/src/boot/boot.janet index f50ef7de..0d96008d 100644 --- a/src/boot/boot.janet +++ b/src/boot/boot.janet @@ -4634,11 +4634,12 @@ (var expect-image false) (when-let [jp (getenv-alias "JANET_PATH")] - (def path-sep (if (index-of (os/which) [:windows :mingw]) ";" ":")) - (def paths (reverse! (string/split path-sep jp))) - (for i 1 (length paths) - (module/add-syspath (get paths i))) - (setdyn *syspath* (first paths))) + (unless (empty? jp) + (def path-sep (if (index-of (os/which) [:windows :mingw]) ";" ":")) + (def paths (reverse! (string/split path-sep jp))) + (for i 1 (length paths) + (module/add-syspath (get paths i))) + (setdyn *syspath* (first paths)))) (if-let [jprofile (getenv-alias "JANET_PROFILE")] (setdyn *profilepath* jprofile)) (set colorize (and (not (getenv-alias "NO_COLOR")) From db7f741dad064fbf392eb116f88590af02dc7139 Mon Sep 17 00:00:00 2001 From: Calvin Rose Date: Sat, 4 Oct 2025 13:12:29 -0500 Subject: [PATCH 008/307] Extend environment variable behavior to NO_COLOR and JANET_PROFILE Env vars set to empty strings should behave the same as unset variables. --- src/boot/boot.janet | 16 +++++++++------- 1 file changed, 9 insertions(+), 7 deletions(-) diff --git a/src/boot/boot.janet b/src/boot/boot.janet index 0d96008d..fa21e55d 100644 --- a/src/boot/boot.janet +++ b/src/boot/boot.janet @@ -4556,7 +4556,10 @@ ### # conditional compilation for reduced os -(def- getenv-alias (if-let [entry (in root-env 'os/getenv)] (entry :value) (fn [&]))) +(def- getenv-raw (if-let [entry (in root-env 'os/getenv)] (entry :value) (fn [&]))) +(defn- getenv-alias [env-var &opt dflt] + (def x (getenv-raw env-var dflt)) + (if (= x "") nil x)) # empty string is coerced to nil (defn- run-main [env subargs arg] @@ -4634,12 +4637,11 @@ (var expect-image false) (when-let [jp (getenv-alias "JANET_PATH")] - (unless (empty? jp) - (def path-sep (if (index-of (os/which) [:windows :mingw]) ";" ":")) - (def paths (reverse! (string/split path-sep jp))) - (for i 1 (length paths) - (module/add-syspath (get paths i))) - (setdyn *syspath* (first paths)))) + (def path-sep (if (index-of (os/which) [:windows :mingw]) ";" ":")) + (def paths (reverse! (string/split path-sep jp))) + (for i 1 (length paths) + (module/add-syspath (get paths i))) + (setdyn *syspath* (first paths))) (if-let [jprofile (getenv-alias "JANET_PROFILE")] (setdyn *profilepath* jprofile)) (set colorize (and (not (getenv-alias "NO_COLOR")) From c057e14b2027ce2e9300d2ba63d516105910299b Mon Sep 17 00:00:00 2001 From: jpaquim Date: Sat, 11 Oct 2025 16:02:02 +0100 Subject: [PATCH 009/307] Fix dependencies and variable names in Makefile --- Makefile | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/Makefile b/Makefile index 6e703324..bf7a2b51 100644 --- a/Makefile +++ b/Makefile @@ -261,6 +261,7 @@ $(JANET_STATIC_LIBRARY): $(JANET_TARGET_OBJECTS) # Testing assumes HOSTCC=CC TEST_SCRIPTS=$(wildcard test/suite*.janet) +EXAMPLE_SCRIPTS=$(wildcard examples/*.janet) repl: $(JANET_TARGET) $(RUN) ./$(JANET_TARGET) @@ -273,15 +274,15 @@ VALGRIND_COMMAND=valgrind --leak-check=full --quiet valgrind: $(JANET_TARGET) $(VALGRIND_COMMAND) ./$(JANET_TARGET) -test: $(JANET_TARGET) $(TEST_PROGRAMS) +test: $(JANET_TARGET) $(TEST_SCRIPTS) $(EXAMPLE_SCRIPTS) for f in test/suite*.janet; do $(RUN) ./$(JANET_TARGET) "$$f" || exit; done for f in examples/*.janet; do $(RUN) ./$(JANET_TARGET) -k "$$f"; done -valtest: $(JANET_TARGET) $(TEST_PROGRAMS) +valtest: $(JANET_TARGET) $(TEST_SCRIPTS) $(EXAMPLE_SCRIPTS) for f in test/suite*.janet; do $(VALGRIND_COMMAND) ./$(JANET_TARGET) "$$f" || exit; done for f in examples/*.janet; do ./$(JANET_TARGET) -k "$$f"; done -callgrind: $(JANET_TARGET) +callgrind: $(JANET_TARGET) $(TEST_SCRIPTS) for f in test/suite*.janet; do valgrind --tool=callgrind ./$(JANET_TARGET) "$$f" || exit; done ######################## From 56027227fb92815f76f242625ed43e140f7a0269 Mon Sep 17 00:00:00 2001 From: jpaquim Date: Sat, 11 Oct 2025 16:11:05 +0100 Subject: [PATCH 010/307] Add missing phony targets and documentation for install-{jpm,spork}-git --- Makefile | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/Makefile b/Makefile index bf7a2b51..c3594507 100644 --- a/Makefile +++ b/Makefile @@ -414,9 +414,6 @@ clean: -rm -rf build vgcore.* callgrind.* -rm -rf test/install/build test/install/modpath -test-install: - echo "JPM has been removed from default install." - help: @echo @echo 'Janet: A Dynamic Language & Bytecode VM' @@ -438,6 +435,9 @@ help: @echo " make format Format Janet's own source files" @echo ' make grammar Generate a TextMate language grammar' @echo + @echo ' make install-jpm-git Install jpm into the current filesystem' + @echo ' make install-spork-git Install spork into the current filesystem' + @echo -.PHONY: clean install repl debug valgrind test \ +.PHONY: clean install install-jpm-git install-spork-git repl debug valgrind test \ valtest dist uninstall docs grammar format help compile-commands From c29195596e1f81244434d1347a73e6b464483f4f Mon Sep 17 00:00:00 2001 From: jpaquim Date: Sat, 11 Oct 2025 16:29:14 +0100 Subject: [PATCH 011/307] Adapt generated file comments to build type --- src/boot/boot.janet | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/src/boot/boot.janet b/src/boot/boot.janet index fa21e55d..5f49a031 100644 --- a/src/boot/boot.janet +++ b/src/boot/boot.janet @@ -4934,7 +4934,9 @@ "src/core/wrap.c"]) # Print janet.c to stdout - (print "/* Amalgamated build - DO NOT EDIT */") + (if (has-value? boot/args "image-only") + (print "/* Image-only build - DO NOT EDIT */") + (print "/* Amalgamated build - DO NOT EDIT */")) (print "/* Generated from janet version " janet/version "-" janet/build " */") (print "#define JANET_BUILD \"" janet/build "\"") (print ```#define JANET_AMALG```) From 511c1f4b0a1b977fc721ece6e0f24a9deac87f34 Mon Sep 17 00:00:00 2001 From: jpaquim Date: Sat, 11 Oct 2025 16:32:24 +0100 Subject: [PATCH 012/307] Refactor with image-only variable --- src/boot/boot.janet | 7 +++---- 1 file changed, 3 insertions(+), 4 deletions(-) diff --git a/src/boot/boot.janet b/src/boot/boot.janet index 5f49a031..9f7fd34b 100644 --- a/src/boot/boot.janet +++ b/src/boot/boot.janet @@ -4934,16 +4934,15 @@ "src/core/wrap.c"]) # Print janet.c to stdout - (if (has-value? boot/args "image-only") - (print "/* Image-only build - DO NOT EDIT */") - (print "/* Amalgamated build - DO NOT EDIT */")) + (def image-only (has-value? boot/args "image-only")) + (print "/* " (if image-only "Image-only" "Amalgamated") " build - DO NOT EDIT */") (print "/* Generated from janet version " janet/version "-" janet/build " */") (print "#define JANET_BUILD \"" janet/build "\"") (print ```#define JANET_AMALG```) (defn do-one-file [fname] - (unless (has-value? boot/args "image-only") + (unless image-only (print "\n/* " fname " */") (print "#line 0 \"" fname "\"\n") (def source (slurp fname)) From a7764664235e739804c4714c55557116bcaf9851 Mon Sep 17 00:00:00 2001 From: jpaquim Date: Sat, 11 Oct 2025 17:42:47 +0100 Subject: [PATCH 013/307] Add $(RUN) for valgrind/callgrind, add dedicated callgrind-test target --- Makefile | 18 ++++++++++++------ 1 file changed, 12 insertions(+), 6 deletions(-) diff --git a/Makefile b/Makefile index c3594507..acacc0c4 100644 --- a/Makefile +++ b/Makefile @@ -269,7 +269,8 @@ repl: $(JANET_TARGET) debug: $(JANET_TARGET) $(DEBUGGER) ./$(JANET_TARGET) -VALGRIND_COMMAND=valgrind --leak-check=full --quiet +VALGRIND_COMMAND=$(RUN) valgrind --leak-check=full --quiet +CALLGRIND_COMMAND=$(RUN) valgrind --tool=callgrind valgrind: $(JANET_TARGET) $(VALGRIND_COMMAND) ./$(JANET_TARGET) @@ -280,10 +281,14 @@ test: $(JANET_TARGET) $(TEST_SCRIPTS) $(EXAMPLE_SCRIPTS) valtest: $(JANET_TARGET) $(TEST_SCRIPTS) $(EXAMPLE_SCRIPTS) for f in test/suite*.janet; do $(VALGRIND_COMMAND) ./$(JANET_TARGET) "$$f" || exit; done - for f in examples/*.janet; do ./$(JANET_TARGET) -k "$$f"; done + for f in examples/*.janet; do $(VALGRIND_COMMAND) ./$(JANET_TARGET) -k "$$f"; done -callgrind: $(JANET_TARGET) $(TEST_SCRIPTS) - for f in test/suite*.janet; do valgrind --tool=callgrind ./$(JANET_TARGET) "$$f" || exit; done +callgrind: $(JANET_TARGET) + $(CALLGRIND_COMMAND) ./$(JANET_TARGET) + +calltest: $(JANET_TARGET) $(TEST_SCRIPTS) $(EXAMPLE_SCRIPTS) + for f in test/suite*.janet; do $(CALLGRIND_COMMAND) ./$(JANET_TARGET) "$$f" || exit; done + for f in examples/*.janet; do $(CALLGRIND_COMMAND) ./$(JANET_TARGET) -k "$$f"; done ######################## ##### Distribution ##### @@ -425,7 +430,8 @@ help: @echo ' make test Test a built Janet' @echo ' make valgrind Assess Janet with Valgrind' @echo ' make callgrind Assess Janet with Valgrind, using Callgrind' - @echo ' make valtest Run the test suite with Valgrind to check for memory leaks' + @echo ' make valtest Run the test suite and examples with Valgrind to check for memory leaks' + @echo ' make calltest Run the test suite and examples with Callgrind' @echo ' make dist Create a distribution tarball' @echo ' make docs Generate documentation' @echo ' make debug Run janet with GDB or LLDB' @@ -440,4 +446,4 @@ help: @echo .PHONY: clean install install-jpm-git install-spork-git repl debug valgrind test \ - valtest dist uninstall docs grammar format help compile-commands + valtest callgrind callgrind-test dist uninstall docs grammar format help compile-commands From 95abff31d97276b9b422bc9bbcd41d24cfdcde62 Mon Sep 17 00:00:00 2001 From: jpaquim Date: Sat, 11 Oct 2025 17:48:23 +0100 Subject: [PATCH 014/307] Fix file mention in CONTRIBUTING.md --- CONTRIBUTING.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md index 23470084..2be36b1d 100644 --- a/CONTRIBUTING.md +++ b/CONTRIBUTING.md @@ -61,7 +61,7 @@ ensure a consistent code style for C. ## Janet style -All janet code in the project should be formatted similar to the code in core.janet. +All janet code in the project should be formatted similar to the code in src/boot/boot.janet. The auto formatting from janet.vim will work well. ## Typo Fixing and One-Line changes From 1018cb9ccab5d9be75bc19c59faf81d01e6ae7bc Mon Sep 17 00:00:00 2001 From: sogaiu <983021772@users.noreply.github.com> Date: Fri, 17 Oct 2025 22:36:45 +0900 Subject: [PATCH 015/307] Support :has-exe as well This is a companion change to spork's issue As implemented, :has-been-script didn't quite mean what was intended. :has-exe should be an improvement. To prevent breakage, both :has-bin-script and :has-exe should be supported for a while in both spork and janet. Eventually :has-bin-script can be retired. --- src/boot/boot.janet | 7 +++++-- 1 file changed, 5 insertions(+), 2 deletions(-) diff --git a/src/boot/boot.janet b/src/boot/boot.janet index 9f7fd34b..c929cb0e 100644 --- a/src/boot/boot.janet +++ b/src/boot/boot.janet @@ -4388,7 +4388,9 @@ (when check (do-hook module bundle-name :check man))) (print "installed " bundle-name) - (when (get man :has-bin-script) + (when (or (get man :has-exe) + # remove eventually + (get man :has-bin-script)) (def binpath (string (dyn *syspath*) s "bin")) (eprintf "executable files have been installed to %s" binpath)) (when (get man :has-man) @@ -4528,7 +4530,8 @@ (default filename (last (string/split s src))) (default chmod-mode 8r755) (os/mkdir (string (dyn *syspath*) s "bin")) - (put manifest :has-bin-script true) + (put manifest :has-exe true) + (put manifest :has-bin-script true) # remove eventually (bundle/add-file manifest src (string "bin" s filename) chmod-mode)) (defn bundle/add-manpage From f1ec0cc48b43b972710274be437f3d2ad71e40fc Mon Sep 17 00:00:00 2001 From: Ian Henry Date: Fri, 24 Oct 2025 21:25:44 -0700 Subject: [PATCH 016/307] fix try macro hygiene Allow re-using the same symbol for the fiber and error. This allows you to write code like (try (print "hi") ([_ _] (print "oh no"))), fixing a regression introduced in #1605. --- src/boot/boot.janet | 9 ++++++--- 1 file changed, 6 insertions(+), 3 deletions(-) diff --git a/src/boot/boot.janet b/src/boot/boot.janet index c929cb0e..cc8f3fa0 100644 --- a/src/boot/boot.janet +++ b/src/boot/boot.janet @@ -348,12 +348,15 @@ [body catch] (assert (and (not (empty? catch)) (indexed? (catch 0))) "the first element of `catch` must be a tuple or array") (let [[err fib] (catch 0) - r (or err (gensym)) - f (or fib (gensym))] + r (gensym) + f (gensym)] ~(let [,f (,fiber/new (fn :try [] ,body) :ie) ,r (,resume ,f)] (if (,= (,fiber/status ,f) :error) - (do ,;(tuple/slice catch 1)) + (do + ,(if err ~(def ,err ,r)) + ,(if fib ~(def ,fib ,f)) + ,;(tuple/slice catch 1)) ,r)))) (defmacro with-syms From 0c34033b72d506789e8d197b861538ba677f9ef5 Mon Sep 17 00:00:00 2001 From: Ian Henry Date: Fri, 24 Oct 2025 21:54:05 -0700 Subject: [PATCH 017/307] add some tests for the (try) macro --- test/suite-boot.janet | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/test/suite-boot.janet b/test/suite-boot.janet index 7532a972..e94ea16a 100644 --- a/test/suite-boot.janet +++ b/test/suite-boot.janet @@ -1023,4 +1023,11 @@ (assert (deep-not= @{:key1 "value1" [@"key2"] @"value2"} @{:key1 "value1" [@"key2"] @"value2"}) "deep= mutable keys") +# different try overloads +(assert (= (try (error :error) ([] :caught)) :caught)) +(assert (= (try (error :error) ([e] e)) :error)) +(assert (= (try (error :error) ([e fib] [e (fiber? fib)])) [:error true])) +# regression test for #1659 +(assert (= (try (error :error) ([_ _] :caught)) :caught)) + (end-suite) From 6bd02bb5b6889f093d13b8ffcfbdd84e229fe414 Mon Sep 17 00:00:00 2001 From: Michael Camilleri Date: Sun, 26 Oct 2025 06:53:27 +0900 Subject: [PATCH 018/307] Confirm necessary files during bundle installation --- examples/sample-bad-bundle1/info.jdn | 3 ++ .../badmod.janet | 0 .../bundle.janet | 0 examples/sample-bad-bundle2/info.jdn | 3 ++ src/boot/boot.janet | 36 ++++++++++--------- test/helper.janet | 5 +++ test/suite-bundle.janet | 11 +++++- 7 files changed, 40 insertions(+), 18 deletions(-) create mode 100644 examples/sample-bad-bundle1/info.jdn rename examples/{sample-bad-bundle => sample-bad-bundle2}/badmod.janet (100%) rename examples/{sample-bad-bundle => sample-bad-bundle2}/bundle.janet (100%) create mode 100644 examples/sample-bad-bundle2/info.jdn diff --git a/examples/sample-bad-bundle1/info.jdn b/examples/sample-bad-bundle1/info.jdn new file mode 100644 index 00000000..ed04ca73 --- /dev/null +++ b/examples/sample-bad-bundle1/info.jdn @@ -0,0 +1,3 @@ +@{ + :name "sample-bad-bundle1" +} diff --git a/examples/sample-bad-bundle/badmod.janet b/examples/sample-bad-bundle2/badmod.janet similarity index 100% rename from examples/sample-bad-bundle/badmod.janet rename to examples/sample-bad-bundle2/badmod.janet diff --git a/examples/sample-bad-bundle/bundle.janet b/examples/sample-bad-bundle2/bundle.janet similarity index 100% rename from examples/sample-bad-bundle/bundle.janet rename to examples/sample-bad-bundle2/bundle.janet diff --git a/examples/sample-bad-bundle2/info.jdn b/examples/sample-bad-bundle2/info.jdn new file mode 100644 index 00000000..96936f9f --- /dev/null +++ b/examples/sample-bad-bundle2/info.jdn @@ -0,0 +1,3 @@ +@{ + :name "sample-bad-bundle2" +} diff --git a/src/boot/boot.janet b/src/boot/boot.janet index cc8f3fa0..7bf1262c 100644 --- a/src/boot/boot.janet +++ b/src/boot/boot.janet @@ -4338,8 +4338,8 @@ # Detect bundle name (def infofile-src1 (string path s "bundle" s "info.jdn")) (def infofile-src2 (string path s "info.jdn")) - (def infofile-src (cond (fexists infofile-src1) infofile-src1 - (fexists infofile-src2) infofile-src2)) + (def infofile-src (if (fexists infofile-src1) infofile-src1 infofile-src2)) + (assert (fexists infofile-src) "bundle must contain info.jdn or bundle/info.jdn") (def info (-?> infofile-src slurp parse)) (def bundle-name (get config :name (get info :name))) (assertf bundle-name "unable to infer bundle name for %v, use :name argument" path) @@ -4348,16 +4348,20 @@ (assert (next bundle-name) "cannot use empty bundle-name") (assertf (not (fexists (get-manifest-filename bundle-name))) "bundle %v is already installed" bundle-name) + # Check initfile + (def initfile-src1 (string path s "bundle" s "init.janet")) + (def initfile-src2 (string path s "bundle.janet")) + (def initfile-src (if (fexists initfile-src1) initfile-src1 initfile-src2)) + (assert (fexists initfile-src) "bundle must contain bundle.janet or bundle/init.janet") # Setup installed paths (prime-bundle-paths) (os/mkdir (bundle-dir bundle-name)) - # Copy infofile - (def infofile-dest (bundle-file bundle-name "info.jdn")) - (when infofile-src (copyfile infofile-src infofile-dest)) + # Copy aliased infofile + (when (fexists infofile-src2) + (copyfile infofile-src2 (bundle-file bundle-name "info.jdn"))) # Copy aliased initfile - (def initfile-alias (string path s "bundle.janet")) - (def initfile-dest (bundle-file bundle-name "init.janet")) - (when (fexists initfile-alias) (copyfile initfile-alias initfile-dest)) + (when (fexists initfile-src2) + (copyfile initfile-src2 (bundle-file bundle-name "init.janet"))) # Copy some files into the new location unconditionally (def implicit-sources (string path s "bundle")) (when (= :directory (os/stat implicit-sources :mode)) @@ -4366,15 +4370,13 @@ (merge-into man config) (sync-manifest man) (edefer (do (print "installation error, uninstalling") (bundle/uninstall bundle-name)) - (when (os/stat infofile-dest :mode) - (def info (-> infofile-dest slurp parse)) - (def deps (seq [d :in (get info :dependencies @[])] - (string (if (dictionary? d) (get d :name) d)))) - (def missing (filter (complement bundle/installed?) deps)) - (when (next missing) - (error (string "missing dependencies " (string/join missing ", ")))) - (put man :dependencies deps) - (put man :info info)) + (def deps (seq [d :in (get info :dependencies @[])] + (string (if (dictionary? d) (get d :name) d)))) + (def missing (filter (complement bundle/installed?) deps)) + (when (next missing) + (error (string "missing dependencies " (string/join missing ", ")))) + (put man :dependencies deps) + (put man :info info) (def module (get-bundle-module bundle-name)) (def clean (get config :clean)) (def check (get config :check)) diff --git a/test/helper.janet b/test/helper.janet index 4a6c8596..c7b1e242 100644 --- a/test/helper.janet +++ b/test/helper.janet @@ -50,6 +50,11 @@ (def errsym (keyword (gensym))) ~(assert (= ,errsym (try (do ,;forms) ([_] ,errsym))) ,msg)) +(defmacro assert-error-value + [msg errval & forms] + (def e (gensym)) + ~(assert (= ,errval (try (do ,;forms) ([,e] ,e))) ,msg)) + (defn check-compile-error [form] (def result (compile form)) diff --git a/test/suite-bundle.janet b/test/suite-bundle.janet index 0d228cc9..d24fd226 100644 --- a/test/suite-bundle.janet +++ b/test/suite-bundle.janet @@ -117,8 +117,17 @@ (assert (= 0 (length (bundle/list))) "bundles are listed correctly 7") (assert (= 0 (length (bundle/topolist))) "bundles are listed correctly 8") +# Try installing a bundle that is missing initfile +(assert-error-value "bad test" + "bundle must contain bundle.janet or bundle/init.janet" + (bundle/install "./examples/sample-bad-bundle1")) +(assert (= 0 (length (bundle/list))) "check failure 0") +(assert (= 0 (length (bundle/topolist))) "check failure 1") + # Try installing a bundle that fails check -(assert-error "bad test" (bundle/install "./examples/sample-bad-bundle" :check true)) +(assert-error-value "bad test" + "Check failed!" + (bundle/install "./examples/sample-bad-bundle2" :check true)) (assert (= 0 (length (bundle/list))) "check failure 0") (assert (= 0 (length (bundle/topolist))) "check failure 1") From 8241d9cbb4ed9fff970e60c27603dde694f94949 Mon Sep 17 00:00:00 2001 From: Michael Camilleri Date: Mon, 27 Oct 2025 09:32:38 +0900 Subject: [PATCH 019/307] Revert support for bundles without an info file --- src/boot/boot.janet | 44 +++++++++++++++++++++++------------------ test/suite-bundle.janet | 6 +++--- 2 files changed, 28 insertions(+), 22 deletions(-) diff --git a/src/boot/boot.janet b/src/boot/boot.janet index 7bf1262c..d33a9066 100644 --- a/src/boot/boot.janet +++ b/src/boot/boot.janet @@ -4222,7 +4222,9 @@ (put new-env *syspath* fixed-syspath) (with-env new-env (put new-env :bundle-dir (bundle-dir bundle-name)) # get the syspath right - (require (string "@syspath/bundle/" bundle-name))))) + (try + (require (string "@syspath/bundle/" bundle-name)) + ([_] (error "bundle must contain bundle.janet or bundle/init.janet")))))) (defn- do-hook [module bundle-name hook & args] @@ -4338,30 +4340,33 @@ # Detect bundle name (def infofile-src1 (string path s "bundle" s "info.jdn")) (def infofile-src2 (string path s "info.jdn")) - (def infofile-src (if (fexists infofile-src1) infofile-src1 infofile-src2)) - (assert (fexists infofile-src) "bundle must contain info.jdn or bundle/info.jdn") + (def infofile-src (cond + (fexists infofile-src1) infofile-src1 + (fexists infofile-src2) infofile-src2)) (def info (-?> infofile-src slurp parse)) (def bundle-name (get config :name (get info :name))) - (assertf bundle-name "unable to infer bundle name for %v, use :name argument" path) + (assertf bundle-name + "unable to infer bundle name for %v, use :name argument or add :name to info file" path) (assertf (not (string/check-set "\\/" bundle-name)) "bundle name %v cannot contain path separators" bundle-name) (assert (next bundle-name) "cannot use empty bundle-name") (assertf (not (fexists (get-manifest-filename bundle-name))) "bundle %v is already installed" bundle-name) - # Check initfile - (def initfile-src1 (string path s "bundle" s "init.janet")) - (def initfile-src2 (string path s "bundle.janet")) - (def initfile-src (if (fexists initfile-src1) initfile-src1 initfile-src2)) - (assert (fexists initfile-src) "bundle must contain bundle.janet or bundle/init.janet") + # Check bscript + (def bscript-src1 (string path s "bundle" s "init.janet")) + (def bscript-src2 (string path s "bundle.janet")) + (def bscript-src (cond + (fexists bscript-src1) bscript-src1 + (fexists bscript-src2) bscript-src2)) # Setup installed paths (prime-bundle-paths) (os/mkdir (bundle-dir bundle-name)) # Copy aliased infofile (when (fexists infofile-src2) (copyfile infofile-src2 (bundle-file bundle-name "info.jdn"))) - # Copy aliased initfile - (when (fexists initfile-src2) - (copyfile initfile-src2 (bundle-file bundle-name "init.janet"))) + # Copy aliased bscript + (when (fexists bscript-src2) + (copyfile bscript-src2 (bundle-file bundle-name "init.janet"))) # Copy some files into the new location unconditionally (def implicit-sources (string path s "bundle")) (when (= :directory (os/stat implicit-sources :mode)) @@ -4370,13 +4375,14 @@ (merge-into man config) (sync-manifest man) (edefer (do (print "installation error, uninstalling") (bundle/uninstall bundle-name)) - (def deps (seq [d :in (get info :dependencies @[])] - (string (if (dictionary? d) (get d :name) d)))) - (def missing (filter (complement bundle/installed?) deps)) - (when (next missing) - (error (string "missing dependencies " (string/join missing ", ")))) - (put man :dependencies deps) - (put man :info info) + (when info + (def deps (seq [d :in (get info :dependencies @[])] + (string (if (dictionary? d) (get d :name) d)))) + (def missing (filter (complement bundle/installed?) deps)) + (when (next missing) + (error (string "missing dependencies " (string/join missing ", ")))) + (put man :dependencies deps) + (put man :info info)) (def module (get-bundle-module bundle-name)) (def clean (get config :clean)) (def check (get config :check)) diff --git a/test/suite-bundle.janet b/test/suite-bundle.janet index d24fd226..7da438cd 100644 --- a/test/suite-bundle.janet +++ b/test/suite-bundle.janet @@ -117,15 +117,15 @@ (assert (= 0 (length (bundle/list))) "bundles are listed correctly 7") (assert (= 0 (length (bundle/topolist))) "bundles are listed correctly 8") -# Try installing a bundle that is missing initfile -(assert-error-value "bad test" +# Try installing a bundle that is missing bundle script +(assert-error-value "bundle missing bundle script" "bundle must contain bundle.janet or bundle/init.janet" (bundle/install "./examples/sample-bad-bundle1")) (assert (= 0 (length (bundle/list))) "check failure 0") (assert (= 0 (length (bundle/topolist))) "check failure 1") # Try installing a bundle that fails check -(assert-error-value "bad test" +(assert-error-value "bundle check hook fails" "Check failed!" (bundle/install "./examples/sample-bad-bundle2" :check true)) (assert (= 0 (length (bundle/list))) "check failure 0") From 8ace58049879d5133a2bdd0f3dd333ad9acb05ca Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Nguy=E1=BB=85n=20Gia=20Phong?= Date: Wed, 5 Nov 2025 16:53:19 +0900 Subject: [PATCH 020/307] Include context in dobytes' error string --- src/core/run.c | 21 +++++++++++++-------- 1 file changed, 13 insertions(+), 8 deletions(-) diff --git a/src/core/run.c b/src/core/run.c index b6a78847..dca0f066 100644 --- a/src/core/run.c +++ b/src/core/run.c @@ -60,7 +60,6 @@ int janet_dobytes(JanetTable *env, const uint8_t *bytes, int32_t len, const char done = 1; } } else { - ret = janet_wrap_string(cres.error); int32_t line = (int32_t) parser->line; int32_t col = (int32_t) parser->column; if ((cres.error_mapping.line > 0) && @@ -68,13 +67,17 @@ int janet_dobytes(JanetTable *env, const uint8_t *bytes, int32_t len, const char line = cres.error_mapping.line; col = cres.error_mapping.column; } + JanetString ctx = janet_formatc("%s:%d:%d: compile error", + sourcePath, line, col); + JanetString errstr = janet_formatc("%s: %s", + (const char *)ctx, + (const char *)cres.error); + ret = janet_wrap_string(errstr); if (cres.macrofiber) { - janet_eprintf("%s:%d:%d: compile error", sourcePath, - line, col); + janet_eprintf("%s", (const char *)ctx); janet_stacktrace_ext(cres.macrofiber, ret, ""); } else { - janet_eprintf("%s:%d:%d: compile error: %s\n", sourcePath, - line, col, (const char *)cres.error); + janet_eprintf("%s\n", (const char *)errstr); } errflags |= JANET_DO_ERROR_COMPILE; done = 1; @@ -89,12 +92,14 @@ int janet_dobytes(JanetTable *env, const uint8_t *bytes, int32_t len, const char done = 1; break; case JANET_PARSE_ERROR: { - const char *e = janet_parser_error(parser); errflags |= JANET_DO_ERROR_PARSE; - ret = janet_cstringv(e); int32_t line = (int32_t) parser->line; int32_t col = (int32_t) parser->column; - janet_eprintf("%s:%d:%d: parse error: %s\n", sourcePath, line, col, e); + JanetString errstr = janet_formatc("%s:%d:%d: parse error: %s", + sourcePath, line, col, + janet_parser_error(parser)); + ret = janet_wrap_string(errstr); + janet_eprintf("%s\n", (const char *)errstr); done = 1; break; } From 1c372fbf3257a6cc2bb8697ed53fefd00dca365c Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Nguy=E1=BB=85n=20Gia=20Phong?= Date: Wed, 5 Nov 2025 16:42:12 +0900 Subject: [PATCH 021/307] Keep dobytes' error string The reason for failure would be more useful than the most recently evaluated value. --- src/core/run.c | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/src/core/run.c b/src/core/run.c index b6a78847..da7e5260 100644 --- a/src/core/run.c +++ b/src/core/run.c @@ -122,7 +122,8 @@ int janet_dobytes(JanetTable *env, const uint8_t *bytes, int32_t len, const char janet_loop(); if (fiber) { janet_gcunroot(janet_wrap_fiber(fiber)); - ret = fiber->last_value; + if (!errflags) + ret = fiber->last_value; } } #endif From 0f9ecc2da56b2b16792aefd5b58063f8f6179099 Mon Sep 17 00:00:00 2001 From: Calvin Rose Date: Sat, 15 Nov 2025 07:31:23 -0600 Subject: [PATCH 022/307] Prepare for 1.40.0 release --- CHANGELOG.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index e0537afe..5302d132 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,7 +1,7 @@ # Changelog All notable changes to this project will be documented in this file. -## Unreleased - ??? +## 1.40.0 - 2025-11-15 - Add `os/posix-chroot` - Fix `ev/deadline` with interrupt race condition bug on Windows. - Improve `flycheck` by allowing functions and macros to define their own flycheck behavior via the metadata `:flycheck`. From 94f2494f8deccf7f71e621c9df47e3b97e0ac3b4 Mon Sep 17 00:00:00 2001 From: Calvin Rose Date: Sat, 15 Nov 2025 08:14:28 -0600 Subject: [PATCH 023/307] MSVC warning --- src/core/ev.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/core/ev.c b/src/core/ev.c index 5e2b1044..f57f7c2e 100644 --- a/src/core/ev.c +++ b/src/core/ev.c @@ -699,7 +699,7 @@ static DWORD WINAPI janet_timeout_body(LPVOID ptr) { DWORD duration = (DWORD)round(tto.sec * 1000); DWORD res = WAIT_TIMEOUT; JanetTimestamp wait_end = ts_now(); - for (size_t i = 1; res == WAIT_TIMEOUT && (wait_end - wait_begin) < duration; i++) { + for (DWORD i = 1; res == WAIT_TIMEOUT && (wait_end - wait_begin) < duration; i++) { res = WaitForSingleObject(tto.cancel_event, (duration + i)); wait_end = ts_now(); } From 0c8622c803320f0aa511b422786cf7aa81c10989 Mon Sep 17 00:00:00 2001 From: Calvin Rose Date: Sat, 15 Nov 2025 08:15:26 -0600 Subject: [PATCH 024/307] posix_chroot warning --- src/core/os.c | 1 + 1 file changed, 1 insertion(+) diff --git a/src/core/os.c b/src/core/os.c index 885b7b33..d73ee0c1 100644 --- a/src/core/os.c +++ b/src/core/os.c @@ -1549,6 +1549,7 @@ JANET_CORE_FN(os_posix_chroot, janet_sandbox_assert(JANET_SANDBOX_CHROOT); janet_fixarity(argc, 1); #ifdef JANET_WINDOWS + (void) argv; janet_panic("not supported on Windows"); #else const char *root = janet_getcstring(argv, 0); From 732fe0ad0353bc3aee2e7fd26238e46da446aef5 Mon Sep 17 00:00:00 2001 From: Calvin Rose Date: Sat, 15 Nov 2025 08:33:41 -0600 Subject: [PATCH 025/307] Try new openbsd. --- .builds/openbsd.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.builds/openbsd.yml b/.builds/openbsd.yml index 47989c22..f1aba068 100644 --- a/.builds/openbsd.yml +++ b/.builds/openbsd.yml @@ -1,4 +1,4 @@ -image: openbsd/7.6 +image: openbsd/7.7 sources: - https://git.sr.ht/~bakpakin/janet packages: From 7e0c692d4e69383c26088f70a50bf793ed0d735b Mon Sep 17 00:00:00 2001 From: Calvin Rose Date: Sat, 15 Nov 2025 08:39:47 -0600 Subject: [PATCH 026/307] Remove test-install target from sr.ht --- .builds/openbsd.yml | 1 - 1 file changed, 1 deletion(-) diff --git a/.builds/openbsd.yml b/.builds/openbsd.yml index f1aba068..178fe946 100644 --- a/.builds/openbsd.yml +++ b/.builds/openbsd.yml @@ -10,7 +10,6 @@ tasks: gmake gmake test doas gmake install - gmake test-install doas gmake uninstall - meson_min: | cd janet From 8c2a517cd75e1cc568566507061eec06f690f5f2 Mon Sep 17 00:00:00 2001 From: Calvin Rose Date: Sat, 15 Nov 2025 09:43:15 -0600 Subject: [PATCH 027/307] Formatting and fix chroot build. --- src/core/corelib.c | 158 ++++++++++++++++++++++----------------------- src/core/os.c | 2 +- 2 files changed, 80 insertions(+), 80 deletions(-) diff --git a/src/core/corelib.c b/src/core/corelib.c index 4f2b5237..f103c445 100644 --- a/src/core/corelib.c +++ b/src/core/corelib.c @@ -997,11 +997,11 @@ static void make_apply(JanetTable *env) { janet_quick_asm(env, JANET_FUN_APPLY | JANET_FUNCDEF_FLAG_VARARG, "apply", 1, 1, INT32_MAX, 6, apply_asm, sizeof(apply_asm), JDOC("(apply f & args)\n\n" - "Applies a function f to a variable number of arguments. Each " - "element in args is used as an argument to f, except the last " - "element in args, which is expected to be an array or a tuple. " - "Each element in this last argument is then also pushed as an " - "argument to f.")); + "Applies a function f to a variable number of arguments. Each " + "element in args is used as an argument to f, except the last " + "element in args, which is expected to be an array or a tuple. " + "Each element in this last argument is then also pushed as an " + "argument to f.")); } static const uint32_t error_asm[] = { @@ -1154,82 +1154,82 @@ JanetTable *janet_core_env(JanetTable *replacements) { janet_quick_asm(env, JANET_FUN_CMP, "cmp", 2, 2, 2, 2, cmp_asm, sizeof(cmp_asm), JDOC("(cmp x y)\n\n" - "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.")); + "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.")); janet_quick_asm(env, JANET_FUN_NEXT, "next", 2, 1, 2, 2, next_asm, sizeof(next_asm), JDOC("(next ds &opt key)\n\n" - "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 " - "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 " - "returns nil, there are no more keys 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 " + "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 " + "returns nil, there are no more keys to iterate through.")); janet_quick_asm(env, JANET_FUN_PROP, "propagate", 2, 2, 2, 2, propagate_asm, sizeof(propagate_asm), JDOC("(propagate x fiber)\n\n" - "Propagate a signal from a fiber to the current fiber and " - "set the last value of the current fiber to `x`. The signal " - "value is then available as the status of the current fiber. " - "The resulting stack trace from the current fiber will include " - "frames from fiber. If fiber is in a state that can be resumed, " - "resuming the current fiber will first resume `fiber`. " - "This function can be used to re-raise an error without losing " - "the original stack trace.")); + "Propagate a signal from a fiber to the current fiber and " + "set the last value of the current fiber to `x`. The signal " + "value is then available as the status of the current fiber. " + "The resulting stack trace from the current fiber will include " + "frames from fiber. If fiber is in a state that can be resumed, " + "resuming the current fiber will first resume `fiber`. " + "This function can be used to re-raise an error without losing " + "the original stack trace.")); janet_quick_asm(env, JANET_FUN_DEBUG, "debug", 1, 0, 1, 1, debug_asm, sizeof(debug_asm), JDOC("(debug &opt x)\n\n" - "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.")); + "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.")); janet_quick_asm(env, JANET_FUN_ERROR, "error", 1, 1, 1, 1, error_asm, sizeof(error_asm), 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, "yield", 1, 0, 1, 2, yield_asm, sizeof(yield_asm), JDOC("(yield &opt x)\n\n" - "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 " - "return the value that was passed to resume.")); + "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 " + "return the value that was passed to resume.")); janet_quick_asm(env, JANET_FUN_CANCEL, "cancel", 2, 2, 2, 2, cancel_asm, sizeof(cancel_asm), JDOC("(cancel fiber err)\n\n" - "Resume a fiber but have it immediately raise an error. This lets a programmer unwind a pending fiber. " - "Returns the same result as resume.")); + "Resume a fiber but have it immediately raise an error. This lets a programmer unwind a pending fiber. " + "Returns the same result as resume.")); janet_quick_asm(env, JANET_FUN_RESUME, "resume", 2, 1, 2, 2, resume_asm, sizeof(resume_asm), JDOC("(resume fiber &opt x)\n\n" - "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 " - "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.")); + "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 " + "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.")); janet_quick_asm(env, JANET_FUN_IN, "in", 3, 2, 3, 4, in_asm, sizeof(in_asm), JDOC("(in ds key &opt dflt)\n\n" - "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, " - "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.")); + "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, " + "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.")); janet_quick_asm(env, JANET_FUN_GET, "get", 3, 2, 3, 4, get_asm, sizeof(in_asm), 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. " - "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 " - "an error.")); + "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 " + "unless the data structure is an abstract type. In that case, the abstract type getter may throw " + "an error.")); janet_quick_asm(env, JANET_FUN_PUT, "put", 3, 3, 3, 3, put_asm, sizeof(put_asm), JDOC("(put ds key value)\n\n" - "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 " - "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 " - "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.")); + "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 " + "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 " + "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.")); janet_quick_asm(env, JANET_FUN_LENGTH, "length", 1, 1, 1, 1, length_asm, sizeof(length_asm), JDOC("(length ds)\n\n" - "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.")); + "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.")); janet_quick_asm(env, JANET_FUN_BNOT, "bnot", 1, 1, 1, 1, bnot_asm, sizeof(bnot_asm), JDOC("(bnot x)\n\nReturns the bit-wise inverse of integer x.")); @@ -1238,74 +1238,74 @@ JanetTable *janet_core_env(JanetTable *replacements) { /* Variadic ops */ templatize_varop(env, JANET_FUN_ADD, "+", 0, 0, JOP_ADD, 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, JDOC("(- & xs)\n\n" - "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 " - "the rest of the elements.")); + "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 " + "the rest of the elements.")); templatize_varop(env, JANET_FUN_MULTIPLY, "*", 1, 1, JOP_MULTIPLY, 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, JDOC("(/ & xs)\n\n" - "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 " - "values.")); + "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 " + "values.")); templatize_varop(env, JANET_FUN_DIVIDE_FLOOR, "div", 1, 1, JOP_DIVIDE_FLOOR, JDOC("(div & xs)\n\n" - "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 " - "values.")); + "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 " + "values.")); templatize_varop(env, JANET_FUN_MODULO, "mod", 0, 1, JOP_MODULO, JDOC("(mod & xs)\n\n" - "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`.")); + "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`.")); templatize_varop(env, JANET_FUN_REMAINDER, "%", 0, 1, JOP_REMAINDER, 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, 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, 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, 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, JDOC("(blshift x & shifts)\n\n" - "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.")); + "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.")); templatize_varop(env, JANET_FUN_RSHIFT, "brshift", 1, 1, JOP_SHIFT_RIGHT, JDOC("(brshift x & shifts)\n\n" - "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.")); + "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.")); templatize_varop(env, JANET_FUN_RSHIFTU, "brushift", 1, 1, JOP_SHIFT_RIGHT_UNSIGNED, JDOC("(brushift x & shifts)\n\n" - "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 " - "for positive shifts the return value will always be positive.")); + "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 " + "for positive shifts the return value will always be positive.")); /* Variadic comparators */ templatize_comparator(env, JANET_FUN_GT, ">", 0, JOP_GREATER_THAN, 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, 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, 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, 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, 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, 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 */ janet_def(env, "janet/version", janet_cstringv(JANET_VERSION), @@ -1314,7 +1314,7 @@ JanetTable *janet_core_env(JanetTable *replacements) { JDOC("The build identifier of the running janet program.")); 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 " - "if native modules are compatible with the host program.")); + "if native modules are compatible with the host program.")); /* Allow references to the environment */ janet_def(env, "root-env", janet_wrap_table(env), diff --git a/src/core/os.c b/src/core/os.c index d73ee0c1..5d86c291 100644 --- a/src/core/os.c +++ b/src/core/os.c @@ -2872,7 +2872,6 @@ void janet_lib_os(JanetTable *env) { JANET_CORE_REG("os/touch", os_touch), JANET_CORE_REG("os/realpath", os_realpath), JANET_CORE_REG("os/cd", os_cd), - JANET_CORE_REG("os/posix-chroot", os_posix_chroot), #ifndef JANET_NO_UMASK JANET_CORE_REG("os/umask", os_umask), #endif @@ -2897,6 +2896,7 @@ void janet_lib_os(JanetTable *env) { JANET_CORE_REG("os/shell", os_shell), JANET_CORE_REG("os/posix-fork", os_posix_fork), JANET_CORE_REG("os/posix-exec", os_posix_exec), + JANET_CORE_REG("os/posix-chroot", os_posix_chroot), /* no need to sandbox process management if you can't create processes * (allows for limited functionality if use exposes C-functions to create specific processes) */ JANET_CORE_REG("os/proc-wait", os_proc_wait), From f7d0d065c8518c25749c755b815843fbf87f65c2 Mon Sep 17 00:00:00 2001 From: Calvin Rose Date: Sat, 15 Nov 2025 09:46:35 -0600 Subject: [PATCH 028/307] Add sr.ht build for meson reduced builds. This should help better catch configs that break our website emscripten build. --- .builds/openbsd.yml | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/.builds/openbsd.yml b/.builds/openbsd.yml index 178fe946..894a558a 100644 --- a/.builds/openbsd.yml +++ b/.builds/openbsd.yml @@ -16,6 +16,11 @@ tasks: meson setup build_meson_min --buildtype=release -Dsingle_threaded=true -Dnanbox=false -Ddynamic_modules=false -Ddocstrings=false -Dnet=false -Dsourcemaps=false -Dpeg=false -Dassembler=false -Dint_types=false -Dreduced_os=true -Dffi=false cd build_meson_min ninja +- meson_reduced: | + cd janet + meson setup build_meson_reduced --buildtype=release -Dreduced_os=true + cd build_meson_reduced + ninja - meson_prf: | cd janet meson setup build_meson_prf --buildtype=release -Dprf=true From a886a93d2fd190033c33c2801883560a1b3e4585 Mon Sep 17 00:00:00 2001 From: sogaiu <983021772@users.noreply.github.com> Date: Sun, 16 Nov 2025 15:14:11 +0900 Subject: [PATCH 029/307] Fix comment about syspath --- build_win.bat | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/build_win.bat b/build_win.bat index 342ee681..6883b1bc 100644 --- a/build_win.bat +++ b/build_win.bat @@ -49,7 +49,7 @@ for %%f in (src\boot\*.c) do ( ) %JANET_LINK% /out:build\janet_boot.exe build\boot\*.obj @if errorlevel 1 goto :BUILDFAIL -@rem note that there is no default sysroot being baked in +@rem note that there is no default syspath being baked in build\janet_boot . > build\c\janet.c @if errorlevel 1 goto :BUILDFAIL From b389f010053f4f8db181b9359eb22349583dd643 Mon Sep 17 00:00:00 2001 From: Calvin Rose Date: Sun, 16 Nov 2025 08:14:19 -0600 Subject: [PATCH 030/307] Fix undefined behavior (integer overflow) in hash function. If an intermediate hash value was 0xFFFFFFFF and we tried to add 1 as a signed addition, this technically triggers undefined behavior. --- src/core/value.c | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/src/core/value.c b/src/core/value.c index b278a1bc..291baeb2 100644 --- a/src/core/value.c +++ b/src/core/value.c @@ -322,7 +322,8 @@ int32_t janet_hash(Janet x) { break; case JANET_TUPLE: hash = janet_tuple_hash(janet_unwrap_tuple(x)); - hash += (janet_tuple_flag(janet_unwrap_tuple(x)) & JANET_TUPLE_FLAG_BRACKETCTOR) ? 1 : 0; + uint32_t inc = (janet_tuple_flag(janet_unwrap_tuple(x)) & JANET_TUPLE_FLAG_BRACKETCTOR) ? 1 : 0; + hash = (int32_t)((uint32_t)hash + inc); /* avoid overflow undefined behavior */ break; case JANET_STRUCT: hash = janet_struct_hash(janet_unwrap_struct(x)); From c44592c84d1fff56c98373d9bd834ebec601653b Mon Sep 17 00:00:00 2001 From: Calvin Rose Date: Sun, 16 Nov 2025 15:53:52 -0600 Subject: [PATCH 031/307] Prepare for 1.40.1 patch release. --- Makefile | 9 +++++---- meson.build | 2 +- src/conf/janetconf.h | 4 ++-- 3 files changed, 8 insertions(+), 7 deletions(-) diff --git a/Makefile b/Makefile index acacc0c4..90bbca1e 100644 --- a/Makefile +++ b/Makefile @@ -347,6 +347,7 @@ build/janet.pc: $(JANET_TARGET) echo 'Libs.private: $(CLIBS)' >> $@ install: $(JANET_TARGET) $(JANET_LIBRARY) $(JANET_STATIC_LIBRARY) build/janet.pc build/janet.h + $(eval JANET_VERSION := $(shell $(JANET_TARGET) -e '(print janet/version)')) mkdir -p '$(DESTDIR)$(BINDIR)' cp $(JANET_TARGET) '$(DESTDIR)$(BINDIR)/janet' strip $(STRIPFLAGS) '$(DESTDIR)$(BINDIR)/janet' @@ -356,13 +357,13 @@ install: $(JANET_TARGET) $(JANET_LIBRARY) $(JANET_STATIC_LIBRARY) build/janet.pc mkdir -p '$(DESTDIR)$(JANET_PATH)' mkdir -p '$(DESTDIR)$(LIBDIR)' if test $(UNAME) = Darwin ; then \ - cp $(JANET_LIBRARY) '$(DESTDIR)$(LIBDIR)/libjanet.$(shell $(JANET_TARGET) -e '(print janet/version)').dylib' ; \ + cp $(JANET_LIBRARY) '$(DESTDIR)$(LIBDIR)/libjanet.$(JANET_VERSION).dylib' ; \ ln -sf $(SONAME) '$(DESTDIR)$(LIBDIR)/libjanet.dylib' ; \ - ln -sf libjanet.$(shell $(JANET_TARGET) -e '(print janet/version)').dylib $(DESTDIR)$(LIBDIR)/$(SONAME) ; \ + ln -sf libjanet.$(JANET_VERSION).dylib $(DESTDIR)$(LIBDIR)/$(SONAME) ; \ else \ - cp $(JANET_LIBRARY) '$(DESTDIR)$(LIBDIR)/libjanet.so.$(shell $(JANET_TARGET) -e '(print janet/version)')' ; \ + cp $(JANET_LIBRARY) '$(DESTDIR)$(LIBDIR)/libjanet.so.$(JANET_VERSION)' ; \ ln -sf $(SONAME) '$(DESTDIR)$(LIBDIR)/libjanet.so' ; \ - ln -sf libjanet.so.$(shell $(JANET_TARGET) -e '(print janet/version)') $(DESTDIR)$(LIBDIR)/$(SONAME) ; \ + ln -sf libjanet.so.$(JANET_VERSION) $(DESTDIR)$(LIBDIR)/$(SONAME) ; \ fi cp $(JANET_STATIC_LIBRARY) '$(DESTDIR)$(LIBDIR)/libjanet.a' mkdir -p '$(DESTDIR)$(JANET_MANPATH)' diff --git a/meson.build b/meson.build index 6eed4efa..e81251a4 100644 --- a/meson.build +++ b/meson.build @@ -20,7 +20,7 @@ project('janet', 'c', default_options : ['c_std=c99', 'build.c_std=c99', 'b_lundef=false', 'default_library=both'], - version : '1.40.0') + version : '1.40.1') # Global settings janet_path = join_paths(get_option('prefix'), get_option('libdir'), 'janet') diff --git a/src/conf/janetconf.h b/src/conf/janetconf.h index 66c7284f..0b7202bd 100644 --- a/src/conf/janetconf.h +++ b/src/conf/janetconf.h @@ -5,9 +5,9 @@ #define JANET_VERSION_MAJOR 1 #define JANET_VERSION_MINOR 40 -#define JANET_VERSION_PATCH 0 +#define JANET_VERSION_PATCH 1 #define JANET_VERSION_EXTRA "" -#define JANET_VERSION "1.40.0" +#define JANET_VERSION "1.40.1" /* #define JANET_BUILD "local" */ From 1449ad8b31947999b2b6887fe633883106e5e65c Mon Sep 17 00:00:00 2001 From: Calvin Rose Date: Sun, 16 Nov 2025 17:39:19 -0600 Subject: [PATCH 032/307] Update changelog. --- CHANGELOG.md | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index 5302d132..c46147a6 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,6 +1,10 @@ # Changelog All notable changes to this project will be documented in this file. +## 1.40.1 - 2025-11-16 +- Fix `JANET_REDUCED_OS` build regression caused by `os/posix-chroot`. +- Code formatting + ## 1.40.0 - 2025-11-15 - Add `os/posix-chroot` - Fix `ev/deadline` with interrupt race condition bug on Windows. From 4fa1a6947c4fe5e6b94055d2c204f9c825dbe594 Mon Sep 17 00:00:00 2001 From: sogaiu <983021772@users.noreply.github.com> Date: Mon, 17 Nov 2025 18:51:55 +0900 Subject: [PATCH 033/307] Update file/write docstring --- src/core/io.c | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/src/core/io.c b/src/core/io.c index 4d45caba..ee5f9c84 100644 --- a/src/core/io.c +++ b/src/core/io.c @@ -249,9 +249,9 @@ JANET_CORE_FN(cfun_io_fread, /* Write bytes to a file */ JANET_CORE_FN(cfun_io_fwrite, - "(file/write f bytes)", - "Writes to a file. 'bytes' must be string, buffer, or symbol. Returns the " - "file.") { + "(file/write f & bytes)", + "Writes to a file `f`. Each value of `bytes` must be a " + "string, buffer, symbol, or keyword. Returns the file.") { janet_arity(argc, 1, -1); JanetFile *iof = janet_getabstract(argv, 0, &janet_file_type); if (iof->flags & JANET_FILE_CLOSED) From ee46315e1d1ddb545d698d1cec627e0aaf3e7fc1 Mon Sep 17 00:00:00 2001 From: Michael Camilleri Date: Fri, 21 Nov 2025 22:45:36 +0900 Subject: [PATCH 034/307] Improve messages on errors when loading the bundle script --- src/boot/boot.janet | 8 +++++++- 1 file changed, 7 insertions(+), 1 deletion(-) diff --git a/src/boot/boot.janet b/src/boot/boot.janet index d33a9066..af60ed75 100644 --- a/src/boot/boot.janet +++ b/src/boot/boot.janet @@ -4224,7 +4224,13 @@ (put new-env :bundle-dir (bundle-dir bundle-name)) # get the syspath right (try (require (string "@syspath/bundle/" bundle-name)) - ([_] (error "bundle must contain bundle.janet or bundle/init.janet")))))) + ([e f] + (def pfx "could not find module @syspath/bundle/") + (def msg (if (and (string? e) + (string/has-prefix? pfx e)) + "bundle must contain bundle.janet or bundle/init.janet" + e)) + (propagate msg f)))))) (defn- do-hook [module bundle-name hook & args] From bd1641b9362516568ec590c2eb24c49f1bfbfdc8 Mon Sep 17 00:00:00 2001 From: sogaiu <983021772@users.noreply.github.com> Date: Sat, 29 Nov 2025 23:38:07 +0900 Subject: [PATCH 035/307] Remove unused def --- src/boot/boot.janet | 1 - 1 file changed, 1 deletion(-) diff --git a/src/boot/boot.janet b/src/boot/boot.janet index af60ed75..448297af 100644 --- a/src/boot/boot.janet +++ b/src/boot/boot.janet @@ -4055,7 +4055,6 @@ Other arguments to `flycheck` are the same as `dofile`. Returns nil. ``` [path &keys kwargs] - (def mc @{}) (def new-env (make-env (get kwargs :env))) (put new-env *flychecking* true) (put new-env *module-cache* @{}) From b92992c86287e107d3f0a5c87dc5de9ab26ecec5 Mon Sep 17 00:00:00 2001 From: Calvin Rose Date: Sun, 30 Nov 2025 07:58:18 -0600 Subject: [PATCH 036/307] Make additions to core library * Add `thaw-keep-keys` variant of `thaw`. * Add `walk2` to traverse key-value pairs and fibers. Also add corresponding `postwalk2` and `prewalk2` * Built-in `repl` function now respects custom repl prompt. --- src/boot/boot.janet | 88 ++++++++++++++++++++++++++++++++++++++------- 1 file changed, 76 insertions(+), 12 deletions(-) diff --git a/src/boot/boot.janet b/src/boot/boot.janet index 448297af..9f1c95f0 100644 --- a/src/boot/boot.janet +++ b/src/boot/boot.janet @@ -1457,6 +1457,12 @@ (put ret (f k) (f (in form k)))) ret) +(defn- walk-dict-values [f form] + (def ret @{}) + (loop [k :keys form] + (put ret k (f (in form k)))) + ret) + (defn walk ``Iterate over the values in ast and apply `f` to them. Collect the results in a data structure. If ast is not a @@ -1474,12 +1480,52 @@ ``Do a post-order traversal of a data structure and call `(f x)` on every visitation.`` [f form] - (f (walk (fn [x] (postwalk f x)) form))) + (f (walk (fn :visit [x] (postwalk f x)) form))) (defn prewalk "Similar to `postwalk`, but do pre-order traversal." [f form] - (walk (fn [x] (prewalk f x)) (f form))) + (walk (fn :visit [x] (prewalk f x)) (f form))) + +(defn- walk-dict-pairs [f form] + (def ret @{}) + (eachp p1 form + (def p2 (f p1)) + (when p2 + (def [k v] p2) + (put ret k v))) + ret) + +(defn walk2 + ``Iterate over the values in ast and apply `f` + to them. Collect the results in a data structure. Differs from `walk` in 2 ways: + + * Will iterate fibers and gather results into an array. + * Tables and structs are traversed by visiting each key-value as a tuple instead + of iterating them in an interleaved fashion. + + If ast is not a + table, struct, array, tuple, or fiber, + returns form.`` + [f form] + (case (type form) + :table (walk-dict-pairs f form) + :struct (table/to-struct (walk-dict-pairs f form)) + :array (walk-ind f form) + :tuple (keep-syntax! form (walk-ind f form)) + :fiber (walk-ind f form) + form)) + +(defn postwalk2 + ``Do a post-order traversal of a data structure and call `(f x)` + on every visitation.`` + [f form] + (f (walk2 (fn :visit [x] (postwalk2 f x)) form))) + +(defn prewalk2 + "Similar to `postwalk`, but do pre-order traversal." + [f form] + (walk2 (fn :visit [x] (prewalk2 f x)) (f form))) (defmacro as-> ``Thread forms together, replacing `as` in `forms` with the value @@ -2294,9 +2340,11 @@ x)) (defn thaw - `Thaw an object (make it mutable) and do a deep copy, making - child value also mutable. Closures, fibers, and abstract - types will not be recursively thawed, but all other types will` + ``` + Thaw an object (make it mutable) and do a deep copy, making + child values also mutable. Closures, fibers, and abstract + types will not be recursively thawed, but all other types will. + ``` [ds] (case (type ds) :array (walk-ind thaw ds) @@ -2306,6 +2354,19 @@ :string (buffer ds) ds)) +(defn thaw-keep-keys + ``` + Similar to `thaw`, but do not modify table or struct keys. + ``` + [ds] + (case (type ds) + :array (walk-ind thaw-keep-keys ds) + :tuple (walk-ind thaw-keep-keys ds) + :table (walk-dict-values thaw-keep-keys (table/proto-flatten ds)) + :struct (walk-dict-values thaw-keep-keys (struct/proto-flatten ds)) + :string (buffer 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=`.`` @@ -3799,13 +3860,16 @@ (default env (make-env)) (default chunks (fn :chunks [buf p] - (getline - (string - "repl:" - ((:where p) 0) - ":" - (:state p :delimiters) "> ") - buf env))) + (def custom-prompt (get env *repl-prompt*)) + (def repl-prompt + (if custom-prompt + (custom-prompt p) + (string + "repl:" + ((:where p) 0) + ":" + (:state p :delimiters) "> "))) + (getline repl-prompt buf env))) (run-context {:env env :chunks chunks :on-status (or onsignal (debugger-on-status env 1 true)) From 8efeeaec9579a670f88151a537b7f674edb1686d Mon Sep 17 00:00:00 2001 From: Calvin Rose Date: Sun, 30 Nov 2025 08:09:06 -0600 Subject: [PATCH 037/307] Allow matching tuples and arrays exactly via a dollar suffix. --- src/boot/boot.janet | 16 ++++++++++------ 1 file changed, 10 insertions(+), 6 deletions(-) diff --git a/src/boot/boot.janet b/src/boot/boot.janet index 9f1c95f0..f46e576d 100644 --- a/src/boot/boot.janet +++ b/src/boot/boot.janet @@ -2068,6 +2068,11 @@ (put b2g (pattern (inc i)) @[[slice s i]]) (break)) + (when (= sub-pattern '$) + (when (not= (length pattern) (inc i)) + (error "expected $ to be last symbol in pattern")) + (break)) + (visit-pattern-1 b2g s i sub-pattern))) # match global unification @@ -2087,11 +2092,10 @@ (def isarr (or (= t :array) (and (= t :tuple) (= (tuple/type pattern) :brackets)))) (when isarr (array/push anda (get-length-sym s)) - (def pattern-len - (if-let [rest-idx (find-index (fn [x] (= x '&)) pattern)] - rest-idx - (length pattern))) - (array/push anda [<= pattern-len (get-length-sym s)])) + (def amp-index (find-index (fn [x] (= x '&)) pattern)) + (def dollar-index (find-index (fn [x] (= x '$)) pattern)) + (def pattern-len (or dollar-index amp-index (length pattern))) + (array/push anda [(if dollar-index = <=) pattern-len (get-length-sym s)])) (cond # match data structure template @@ -2103,7 +2107,7 @@ isarr (eachp [i sub-pattern] pattern # stop recursing to sub-patterns if the rest sigil is found - (when (= sub-pattern '&) + (when (or (= sub-pattern '$) (= sub-pattern '&)) (break)) (visit-pattern-2 anda gun preds s i sub-pattern)) From d9105299f10f33ed332c8d950a43f7051434ec22 Mon Sep 17 00:00:00 2001 From: Calvin Rose Date: Tue, 25 Nov 2025 19:36:56 -0600 Subject: [PATCH 038/307] Allow matching tuples and arrays exactly via a dollar suffix. --- CHANGELOG.md | 4 ++++ src/boot/boot.janet | 44 ++-------------------------------------- test/suite-corelib.janet | 6 ++++++ 3 files changed, 12 insertions(+), 42 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index c46147a6..9ceb7c8b 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,6 +1,10 @@ # Changelog All notable changes to this project will be documented in this file. +## Unreleased - ??? +- Add `thaw-keep-keys` as a variant of thaw +- The `repl` function now respects the `*repl-prompt*` dynamic binding. + ## 1.40.1 - 2025-11-16 - Fix `JANET_REDUCED_OS` build regression caused by `os/posix-chroot`. - Code formatting diff --git a/src/boot/boot.janet b/src/boot/boot.janet index f46e576d..9b46b4d7 100644 --- a/src/boot/boot.janet +++ b/src/boot/boot.janet @@ -1480,52 +1480,12 @@ ``Do a post-order traversal of a data structure and call `(f x)` on every visitation.`` [f form] - (f (walk (fn :visit [x] (postwalk f x)) form))) + (f (walk (fn [x] (postwalk f x)) form))) (defn prewalk "Similar to `postwalk`, but do pre-order traversal." [f form] - (walk (fn :visit [x] (prewalk f x)) (f form))) - -(defn- walk-dict-pairs [f form] - (def ret @{}) - (eachp p1 form - (def p2 (f p1)) - (when p2 - (def [k v] p2) - (put ret k v))) - ret) - -(defn walk2 - ``Iterate over the values in ast and apply `f` - to them. Collect the results in a data structure. Differs from `walk` in 2 ways: - - * Will iterate fibers and gather results into an array. - * Tables and structs are traversed by visiting each key-value as a tuple instead - of iterating them in an interleaved fashion. - - If ast is not a - table, struct, array, tuple, or fiber, - returns form.`` - [f form] - (case (type form) - :table (walk-dict-pairs f form) - :struct (table/to-struct (walk-dict-pairs f form)) - :array (walk-ind f form) - :tuple (keep-syntax! form (walk-ind f form)) - :fiber (walk-ind f form) - form)) - -(defn postwalk2 - ``Do a post-order traversal of a data structure and call `(f x)` - on every visitation.`` - [f form] - (f (walk2 (fn :visit [x] (postwalk2 f x)) form))) - -(defn prewalk2 - "Similar to `postwalk`, but do pre-order traversal." - [f form] - (walk2 (fn :visit [x] (prewalk2 f x)) (f form))) + (walk (fn [x] (prewalk f x)) (f form))) (defmacro as-> ``Thread forms together, replacing `as` in `forms` with the value diff --git a/test/suite-corelib.janet b/test/suite-corelib.janet index d2a5d4d5..3008ef3d 100644 --- a/test/suite-corelib.janet +++ b/test/suite-corelib.janet @@ -185,5 +185,11 @@ (assert-no-error "iterate over coro 2" (keys (generate [x :range [0 10]] x))) (assert-no-error "iterate over coro 3" (pairs (generate [x :range [0 10]] x))) +# thaw +(def ds1 [1 2 3 {:a 2} {:b 3} 4 5 6]) +(def ds2 [1 2 3 {:a 2 {:c :d} {:e :f}} {:b 3} 4 5 6]) +(assert (deep= (thaw ds1) (thaw-keep-keys ds1)) "thaw vs. thaw-keep-keys 1") +(assert (deep-not= (thaw ds2) (thaw-keep-keys ds2)) "thaw vs. thaw-keep-keys 2") + (end-suite) From be2a1ddf9641322832d01234d17e0d8e6eba1f6e Mon Sep 17 00:00:00 2001 From: Calvin Rose Date: Sun, 30 Nov 2025 08:47:01 -0600 Subject: [PATCH 039/307] Add test case for dollar suffix match. --- CHANGELOG.md | 1 + test/suite-corelib.janet | 4 ++++ 2 files changed, 5 insertions(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index 9ceb7c8b..b08f27f4 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -4,6 +4,7 @@ All notable changes to this project will be documented in this file. ## Unreleased - ??? - Add `thaw-keep-keys` as a variant of thaw - The `repl` function now respects the `*repl-prompt*` dynamic binding. +- Allow matching exact lengths of datastructures with the `match` macro using a dollar suffix. ## 1.40.1 - 2025-11-16 - Fix `JANET_REDUCED_OS` build regression caused by `os/posix-chroot`. diff --git a/test/suite-corelib.janet b/test/suite-corelib.janet index 3008ef3d..b1fddd56 100644 --- a/test/suite-corelib.janet +++ b/test/suite-corelib.janet @@ -191,5 +191,9 @@ (assert (deep= (thaw ds1) (thaw-keep-keys ds1)) "thaw vs. thaw-keep-keys 1") (assert (deep-not= (thaw ds2) (thaw-keep-keys ds2)) "thaw vs. thaw-keep-keys 2") +# match +(assert (= :yes (match [1 2 3] [x y z w] :no1 [x y $] :no2 [x y z] :yes)) "match dollar suffix 1") +(assert (= :yes (match [1 2 3] [x y z w] :no1 [x y z $] :yes [x y z] :no2)) "match dollar suffix 2") + (end-suite) From 238d6c2e132fe78bd1fb6f1cade75aebfeff7afb Mon Sep 17 00:00:00 2001 From: Calvin Rose Date: Sun, 30 Nov 2025 09:24:30 -0600 Subject: [PATCH 040/307] Remove type constraint on doc for `+`. This constraint is no longer true in all cases. --- src/core/corelib.c | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/core/corelib.c b/src/core/corelib.c index f103c445..bbfeec55 100644 --- a/src/core/corelib.c +++ b/src/core/corelib.c @@ -1238,7 +1238,7 @@ JanetTable *janet_core_env(JanetTable *replacements) { /* Variadic ops */ templatize_varop(env, JANET_FUN_ADD, "+", 0, 0, JOP_ADD, 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. If xs is empty, return 0.")); templatize_varop(env, JANET_FUN_SUBTRACT, "-", 0, 0, JOP_SUBTRACT, JDOC("(- & xs)\n\n" "Returns the difference of xs. If xs is empty, returns 0. If xs has one element, returns the " @@ -1272,7 +1272,7 @@ JanetTable *janet_core_env(JanetTable *replacements) { "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, 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 x in xs must be an integer.")); templatize_varop(env, JANET_FUN_LSHIFT, "blshift", 1, 1, JOP_SHIFT_LEFT, JDOC("(blshift x & shifts)\n\n" "Returns the value of x bit shifted left by the sum of all values in shifts. x " From f79e4d6249ecc25f460204f2bc509dd1ad9713f5 Mon Sep 17 00:00:00 2001 From: Calvin Rose Date: Sun, 30 Nov 2025 14:05:47 -0600 Subject: [PATCH 041/307] Address #1674 - Add `cms` combinator to peg. It behaves like `cmt`, but flattens the result and can collect multiple captures if an indexed data structure is returned. --- CHANGELOG.md | 3 ++- src/core/peg.c | 30 +++++++++++++++++++++++++----- src/include/janet.h | 1 + test/suite-peg.janet | 11 ++++++++++- 4 files changed, 38 insertions(+), 7 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index b08f27f4..6a527f32 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -2,8 +2,9 @@ All notable changes to this project will be documented in this file. ## Unreleased - ??? +- Add `cms` combinator to PEGs. - Add `thaw-keep-keys` as a variant of thaw -- The `repl` function now respects the `*repl-prompt*` dynamic binding. +- The `repl` function now respects the `*repl-prompt*` dynamic binding by default. - Allow matching exact lengths of datastructures with the `match` macro using a dollar suffix. ## 1.40.1 - 2025-11-16 diff --git a/src/core/peg.c b/src/core/peg.c index 454cc802..1b015329 100644 --- a/src/core/peg.c +++ b/src/core/peg.c @@ -622,6 +622,7 @@ tail: } case RULE_REPLACE: + case RULE_MATCHSPLICE: case RULE_MATCHTIME: { uint32_t tag = rule[3]; int oldmode = s->mode; @@ -662,8 +663,17 @@ tail: break; } cap_load_keept(s, cs); - if (rule[0] == RULE_MATCHTIME && !janet_truthy(cap)) return NULL; - pushcap(s, cap, tag); + if (rule[0] != RULE_REPLACE && !janet_truthy(cap)) return NULL; /* matchtime or matchtime flatten */ + const Janet *elements = NULL; + int32_t len = 0; + if ((rule[0] == RULE_MATCHSPLICE) && janet_indexed_view(cap, &elements, &len)) { + /* unpack and flatten capture */ + for (int32_t i = 0; i < len; i++) { + pushcap(s, elements[i], tag); + } + } else { + pushcap(s, cap, tag); + } return result; } @@ -1244,7 +1254,7 @@ static void spec_replace(Builder *b, int32_t argc, const Janet *argv) { emit_3(r, RULE_REPLACE, subrule, constant, tag); } -static void spec_matchtime(Builder *b, int32_t argc, const Janet *argv) { +static void spec_matchtime_impl(Builder *b, int32_t argc, const Janet *argv, uint32_t op) { peg_arity(b, argc, 2, 3); Reserve r = reserve(b, 4); uint32_t subrule = peg_compile1(b, argv[0]); @@ -1255,7 +1265,15 @@ static void spec_matchtime(Builder *b, int32_t argc, const Janet *argv) { } uint32_t tag = (argc == 3) ? emit_tag(b, argv[2]) : 0; uint32_t cindex = emit_constant(b, fun); - emit_3(r, RULE_MATCHTIME, subrule, cindex, tag); + emit_3(r, op, subrule, cindex, tag); +} + +static void spec_matchtime(Builder *b, int32_t argc, const Janet *argv) { + spec_matchtime_impl(b, argc, argv, RULE_MATCHTIME); +} + +static void spec_matchtime_splice(Builder *b, int32_t argc, const Janet *argv) { + spec_matchtime_impl(b, argc, argv, RULE_MATCHSPLICE); } static void spec_sub(Builder *b, int32_t argc, const Janet *argv) { @@ -1341,6 +1359,7 @@ static const SpecialPair peg_specials[] = { {"between", spec_between}, {"capture", spec_capture}, {"choice", spec_choice}, + {"cms", spec_matchtime_splice}, {"cmt", spec_matchtime}, {"column", spec_column}, {"constant", spec_constant}, @@ -1597,7 +1616,7 @@ static void *peg_unmarshal(JanetMarshalContext *ctx) { /* After here, no panics except for the bad: label. */ /* Keep track at each index if an instruction was - * reference (0x01) or is in a main bytecode position + * referenced (0x01) or is in a main bytecode position * (0x02). This lets us do a linear scan and not * need to a depth first traversal. It is stricter * than a dfs by not allowing certain kinds of unused @@ -1703,6 +1722,7 @@ static void *peg_unmarshal(JanetMarshalContext *ctx) { break; case RULE_REPLACE: case RULE_MATCHTIME: + case RULE_MATCHSPLICE: /* [rule, constant, tag] */ if (rule[1] >= blen) goto bad; if (rule[2] >= clen) goto bad; diff --git a/src/include/janet.h b/src/include/janet.h index 352d296d..37c660ab 100644 --- a/src/include/janet.h +++ b/src/include/janet.h @@ -2203,6 +2203,7 @@ typedef enum { RULE_SPLIT, /* [rule, rule] */ RULE_NTH, /* [nth, rule, tag] */ RULE_ONLY_TAGS, /* [rule] */ + RULE_MATCHSPLICE, /* [rule, constant, tag] */ } JanetPegOpcod; typedef struct { diff --git a/test/suite-peg.janet b/test/suite-peg.janet index cf7e86d6..fbd98994 100644 --- a/test/suite-peg.janet +++ b/test/suite-peg.janet @@ -835,5 +835,14 @@ (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) +# Capture Match Flatten +(test "capture match splice 1" + ~(cms (* 1 '1 1) ,|[$ $ $]) + "abc" + @["b" "b" "b"]) +(test "capture match no splice 1" + ~(cmt (* 1 '1 1) ,|[$ $ $]) + "abc" + @[["b" "b" "b"]]) +(end-suite) From b9bc89a38ca44a806b60bbdb8f73d2227c268d85 Mon Sep 17 00:00:00 2001 From: Calvin Rose Date: Sun, 30 Nov 2025 14:22:17 -0600 Subject: [PATCH 042/307] Fix very old typo in janet.h --- src/include/janet.h | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/include/janet.h b/src/include/janet.h index 37c660ab..b99d547a 100644 --- a/src/include/janet.h +++ b/src/include/janet.h @@ -2204,7 +2204,7 @@ typedef enum { RULE_NTH, /* [nth, rule, tag] */ RULE_ONLY_TAGS, /* [rule] */ RULE_MATCHSPLICE, /* [rule, constant, tag] */ -} JanetPegOpcod; +} JanetPegOpcode; typedef struct { uint32_t *bytecode; From a85689312a7f9db727457fdb7d0b7d5a7eb6dfb8 Mon Sep 17 00:00:00 2001 From: Benjamin Roberts Date: Mon, 1 Dec 2025 15:37:09 -0500 Subject: [PATCH 043/307] Disable MSVC runtime stack checks in janet_ffi_win64 The MSVC /RTCs argument instrument alloca() to prepend stack allocations with metadata used to check for memory corruption during the function prologue. This causes stack-based FFI arguments in janet_ffi_win64 to become mis-aligned, and also for the alloca()-prepended header to be corrupted leading to a fatal exception. --- src/core/ffi.c | 13 +++++++++++++ 1 file changed, 13 insertions(+) diff --git a/src/core/ffi.c b/src/core/ffi.c index 81483b6a..7e791ab8 100644 --- a/src/core/ffi.c +++ b/src/core/ffi.c @@ -1344,6 +1344,15 @@ typedef double (win64_variant_f_ffif)(double, double, uint64_t, double); typedef double (win64_variant_f_fffi)(double, double, double, uint64_t); typedef double (win64_variant_f_ffff)(double, double, double, double); +/* MSVC stack frame runtime error checking (/RTCs) prepends alloca() allocations with an _RTC_ALLOCA_NODE + * header; misalligning stack-based FFI arguments and causing the memmove() (by stack_shift) to corrupt + * the _RTC_ALLOCA_NODE header. + * + * We turn off the RTC-instrumented alloca() and adding of _RTC_CheckStackVars to function prologue just + * for janet_ffi_win64() */ +#ifdef __MSVC_RUNTIME_CHECKS +#pragma runtime_checks( "s", off ) +#endif static Janet janet_ffi_win64(JanetFFISignature *signature, void *function_pointer, const Janet *argv) { union { uint64_t integer; @@ -1493,6 +1502,10 @@ static Janet janet_ffi_win64(JanetFFISignature *signature, void *function_pointe return janet_ffi_read_one(ret_mem, signature->ret.type, JANET_FFI_MAX_RECUR); } +#ifdef __MSVC_RUNTIME_CHECKS +// Restore stack frame runtime error checking (/RTCs) if it was enabled. +#pragma runtime_checks ( "s", restore ) +#endif #endif From 6e883b89720a359e01f8a45a9ee84c2560c1618c Mon Sep 17 00:00:00 2001 From: Tw Date: Wed, 3 Dec 2025 19:11:10 +0800 Subject: [PATCH 044/307] shell: Prevent buggy moving zero column behavior According to https://unix.stackexchange.com/a/559331, moving zero column is implemented inconsistently between different terminal emulators. In order to fix this inconsistency, we avoid using this escape sequence entirely. Signed-off-by: Tw Change-Id: I73d4252f8472c769f0cf98b6bbdf2b3d6a6a6964 --- src/mainclient/shell.c | 8 +++++--- 1 file changed, 5 insertions(+), 3 deletions(-) diff --git a/src/mainclient/shell.c b/src/mainclient/shell.c index 3d95ee2d..2b36375b 100644 --- a/src/mainclient/shell.c +++ b/src/mainclient/shell.c @@ -379,10 +379,12 @@ static void refresh(void) { janet_buffer_push_cstring(&b, gbl_prompt); janet_buffer_push_bytes(&b, (uint8_t *) _buf, _len); /* Erase to right */ - janet_buffer_push_cstring(&b, "\x1b[0K"); + janet_buffer_push_cstring(&b, "\x1b[0K\r"); /* Move cursor to original position. */ - snprintf(seq, 64, "\r\x1b[%dC", (int)(_pos + gbl_plen)); - janet_buffer_push_cstring(&b, seq); + if (_pos + gbl_plen) { + snprintf(seq, 64, "\x1b[%dC", (int)(_pos + gbl_plen)); + janet_buffer_push_cstring(&b, seq); + } if (write_console((char *) b.data, b.count) == -1) { exit(1); } From d6803f7a17310341af039d0335f4a752f28b1ea0 Mon Sep 17 00:00:00 2001 From: Calvin Rose Date: Thu, 4 Dec 2025 08:48:12 -0600 Subject: [PATCH 045/307] Formatting. --- src/core/ffi.c | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/core/ffi.c b/src/core/ffi.c index 72a649dc..bc424400 100644 --- a/src/core/ffi.c +++ b/src/core/ffi.c @@ -1345,9 +1345,9 @@ typedef double (win64_variant_f_fffi)(double, double, double, uint64_t); typedef double (win64_variant_f_ffff)(double, double, double, double); /* MSVC stack frame runtime error checking (/RTCs) prepends alloca() allocations with an _RTC_ALLOCA_NODE - * header; misalligning stack-based FFI arguments and causing the memmove() (by stack_shift) to corrupt + * header; misalligning stack-based FFI arguments and causing the memmove() (by stack_shift) to corrupt * the _RTC_ALLOCA_NODE header. - * + * * We turn off the RTC-instrumented alloca() and adding of _RTC_CheckStackVars to function prologue just * for janet_ffi_win64() */ #ifdef __MSVC_RUNTIME_CHECKS From 2f69678f2ffc99304140335f2568c08d15847754 Mon Sep 17 00:00:00 2001 From: Noam Preil Date: Wed, 10 Dec 2025 12:18:17 +0000 Subject: [PATCH 046/307] 9front port --- mkfile | 58 ++++++++++++++++++++++++++++++++++++++++++ src/boot/number_test.c | 2 ++ src/boot/system_test.c | 2 ++ src/core/capi.c | 8 ++++++ src/core/io.c | 4 ++- src/core/math.c | 38 +++++++++++++++------------ src/core/os.c | 4 +++ src/core/parse.c | 9 ------- src/core/pp.c | 2 ++ src/core/util.h | 4 +++ src/include/janet.h | 21 +++++++++++++++ src/mainclient/shell.c | 2 ++ 12 files changed, 128 insertions(+), 26 deletions(-) create mode 100644 mkfile diff --git a/mkfile b/mkfile new file mode 100644 index 00000000..0996aaf6 --- /dev/null +++ b/mkfile @@ -0,0 +1,58 @@ +$target + +build/c/shell.c: src/mainclient/shell.c + cp $prereq $target + + +build/janet.$O: build/c/janet.c src/conf/janetconf.h src/include/janet.h + $CC $CFLAGS -D^$JANET_CONFIG -o $target $prereq(1) + +build/shell.$O: src/mainclient/shell.c src/conf/janetconf.h src/include/janet.h + $CC $CFLAGS -D^$JANET_CONFIG -o $target $prereq(1) + + #include +#ifndef JANET_PLAN9 #include +#endif #include #endif @@ -113,7 +115,7 @@ static void *makef(FILE *f, int32_t flags) { JanetFile *iof = (JanetFile *) janet_abstract(&janet_file_type, sizeof(JanetFile)); iof->file = f; iof->flags = flags; -#ifndef JANET_WINDOWS +#if !(defined(JANET_WINDOWS) || defined(JANET_PLAN9)) /* While we would like fopen to set cloexec by default (like O_CLOEXEC) with the e flag, that is * not standard. */ if (!(flags & JANET_FILE_NOT_CLOSEABLE)) diff --git a/src/core/math.c b/src/core/math.c index 400ed7e4..4aa22a9f 100644 --- a/src/core/math.c +++ b/src/core/math.c @@ -271,28 +271,30 @@ JANET_DEFINE_MATHOP(cosh, "Returns the hyperbolic cosine of x.") JANET_DEFINE_MATHOP(acosh, "Returns the hyperbolic arccosine of x.") JANET_DEFINE_MATHOP(sin, "Returns the sine of x.") JANET_DEFINE_MATHOP(sinh, "Returns the hyperbolic sine of x.") -JANET_DEFINE_MATHOP(asinh, "Returns the hyperbolic arcsine of x.") JANET_DEFINE_MATHOP(tan, "Returns the tangent of x.") JANET_DEFINE_MATHOP(tanh, "Returns the hyperbolic tangent of x.") -JANET_DEFINE_MATHOP(atanh, "Returns the hyperbolic arctangent of x.") JANET_DEFINE_MATHOP(exp, "Returns e to the power of x.") JANET_DEFINE_MATHOP(exp2, "Returns 2 to the power of x.") +JANET_DEFINE_MATHOP(log1p, "Returns (log base e of x) + 1 more accurately than (+ (math/log x) 1)") +#ifndef JANET_PLAN9 JANET_DEFINE_MATHOP(expm1, "Returns e to the power of x minus 1.") +JANET_DEFINE_MATHOP(cbrt, "Returns the cube root of x.") +JANET_DEFINE_MATHOP(erf, "Returns the error function of x.") +JANET_DEFINE_MATHOP(erfc, "Returns the complementary error function of x.") +JANET_DEFINE_NAMED_MATHOP("log-gamma", lgamma, "Returns log-gamma(x).") +JANET_DEFINE_NAMED_MATHOP("gamma", tgamma, "Returns gamma(x).") +JANET_DEFINE_MATHOP(atanh, "Returns the hyperbolic arctangent of x.") +JANET_DEFINE_MATHOP(asinh, "Returns the hyperbolic arcsine of x.") +#endif JANET_DEFINE_MATHOP(log, "Returns the natural logarithm of x.") JANET_DEFINE_MATHOP(log10, "Returns the log base 10 of x.") JANET_DEFINE_MATHOP(log2, "Returns the log base 2 of x.") JANET_DEFINE_MATHOP(sqrt, "Returns the square root of x.") -JANET_DEFINE_MATHOP(cbrt, "Returns the cube root of x.") JANET_DEFINE_MATHOP(ceil, "Returns the smallest integer value number that is not less than x.") JANET_DEFINE_MATHOP(floor, "Returns the largest integer value number that is not greater than x.") JANET_DEFINE_MATHOP(trunc, "Returns the integer between x and 0 nearest to x.") JANET_DEFINE_MATHOP(round, "Returns the integer nearest to x.") -JANET_DEFINE_MATHOP(log1p, "Returns (log base e of x) + 1 more accurately than (+ (math/log x) 1)") -JANET_DEFINE_MATHOP(erf, "Returns the error function of x.") -JANET_DEFINE_MATHOP(erfc, "Returns the complementary error function of x.") -JANET_DEFINE_NAMED_MATHOP("log-gamma", lgamma, "Returns log-gamma(x).") JANET_DEFINE_NAMED_MATHOP("abs", fabs, "Return the absolute value of x.") -JANET_DEFINE_NAMED_MATHOP("gamma", tgamma, "Returns gamma(x).") #define JANET_DEFINE_MATH2OP(name, fop, signature, doc)\ JANET_CORE_FN(janet_##name, signature, doc) {\ @@ -305,7 +307,9 @@ JANET_CORE_FN(janet_##name, signature, doc) {\ JANET_DEFINE_MATH2OP(atan2, atan2, "(math/atan2 y x)", "Returns the arctangent of y/x. Works even when x is 0.") JANET_DEFINE_MATH2OP(pow, pow, "(math/pow a x)", "Returns a to the power of x.") JANET_DEFINE_MATH2OP(hypot, hypot, "(math/hypot a b)", "Returns c from the equation c^2 = a^2 + b^2.") +#ifndef JANET_PLAN9 JANET_DEFINE_MATH2OP(nextafter, nextafter, "(math/next x y)", "Returns the next representable floating point value after x in the direction of y.") +#endif JANET_CORE_FN(janet_not, "(not x)", "Returns the boolean inverse of x.") { janet_fixarity(argc, 1); @@ -386,7 +390,17 @@ void janet_lib_math(JanetTable *env) { JANET_CORE_REG("math/log10", janet_log10), JANET_CORE_REG("math/log2", janet_log2), JANET_CORE_REG("math/sqrt", janet_sqrt), +#ifndef JANET_PLAN9 JANET_CORE_REG("math/cbrt", janet_cbrt), + JANET_CORE_REG("math/gamma", janet_tgamma), + JANET_CORE_REG("math/log-gamma", janet_lgamma), + JANET_CORE_REG("math/erfc", janet_erfc), + JANET_CORE_REG("math/erf", janet_erf), + JANET_CORE_REG("math/expm1", janet_expm1), + JANET_CORE_REG("math/atanh", janet_atanh), + JANET_CORE_REG("math/asinh", janet_asinh), + JANET_CORE_REG("math/next", janet_nextafter), +#endif JANET_CORE_REG("math/floor", janet_floor), JANET_CORE_REG("math/ceil", janet_ceil), JANET_CORE_REG("math/pow", janet_pow), @@ -394,8 +408,6 @@ void janet_lib_math(JanetTable *env) { JANET_CORE_REG("math/sinh", janet_sinh), JANET_CORE_REG("math/cosh", janet_cosh), JANET_CORE_REG("math/tanh", janet_tanh), - JANET_CORE_REG("math/atanh", janet_atanh), - JANET_CORE_REG("math/asinh", janet_asinh), JANET_CORE_REG("math/acosh", janet_acosh), JANET_CORE_REG("math/atan2", janet_atan2), JANET_CORE_REG("math/rng", cfun_rng_make), @@ -405,14 +417,8 @@ void janet_lib_math(JanetTable *env) { JANET_CORE_REG("math/hypot", janet_hypot), JANET_CORE_REG("math/exp2", janet_exp2), JANET_CORE_REG("math/log1p", janet_log1p), - JANET_CORE_REG("math/gamma", janet_tgamma), - JANET_CORE_REG("math/log-gamma", janet_lgamma), - JANET_CORE_REG("math/erfc", janet_erfc), - JANET_CORE_REG("math/erf", janet_erf), - JANET_CORE_REG("math/expm1", janet_expm1), JANET_CORE_REG("math/trunc", janet_trunc), JANET_CORE_REG("math/round", janet_round), - JANET_CORE_REG("math/next", janet_nextafter), JANET_CORE_REG("math/gcd", janet_cfun_gcd), JANET_CORE_REG("math/lcm", janet_cfun_lcm), JANET_CORE_REG("math/frexp", janet_cfun_frexp), diff --git a/src/core/os.c b/src/core/os.c index 5d86c291..66140c41 100644 --- a/src/core/os.c +++ b/src/core/os.c @@ -277,7 +277,11 @@ JANET_CORE_FN(os_exit, } janet_deinit(); if (argc >= 2 && janet_truthy(argv[1])) { +#ifdef JANET_PLAN9 + exit(status); +#else _Exit(status); +#endif } else { exit(status); } diff --git a/src/core/parse.c b/src/core/parse.c index 5b5ffff8..a9695220 100644 --- a/src/core/parse.c +++ b/src/core/parse.c @@ -105,15 +105,6 @@ static int to_hex(uint8_t c) { } } -typedef int (*Consumer)(JanetParser *p, JanetParseState *state, uint8_t c); -struct JanetParseState { - int32_t counter; - int32_t argn; - int flags; - size_t line; - size_t column; - Consumer consumer; -}; /* Define a stack on the main parser struct */ #define DEF_PARSER_STACK(NAME, T, STACK, STACKCOUNT, STACKCAP) \ diff --git a/src/core/pp.c b/src/core/pp.c index 3b000f41..888d3e61 100644 --- a/src/core/pp.c +++ b/src/core/pp.c @@ -794,7 +794,9 @@ static const char *get_fmt_mapping(char c) { if (format_mappings[i].c == c) return format_mappings[i].mapping; } +#ifndef JANET_PLAN9 janet_assert(0, "bad format mapping"); +#endif } static const char *scanformat( diff --git a/src/core/util.h b/src/core/util.h index 4e16f5e0..be1e2a05 100644 --- a/src/core/util.h +++ b/src/core/util.h @@ -167,7 +167,11 @@ typedef void *Clib; #endif char *get_processed_name(const char *name); +#ifdef JANET_PLAN9 +#define RETRY_EINTR(RC, CALL) (RC) = CALL; +#else #define RETRY_EINTR(RC, CALL) do { (RC) = CALL; } while((RC) < 0 && errno == EINTR) +#endif /* Initialize builtin libraries */ void janet_lib_io(JanetTable *env); diff --git a/src/include/janet.h b/src/include/janet.h index b99d547a..4b47a7ae 100644 --- a/src/include/janet.h +++ b/src/include/janet.h @@ -113,6 +113,8 @@ extern "C" { #endif /* Check 64-bit vs 32-bit */ +#ifndef JANET_64 +#ifndef JANET_32 #if ((defined(__x86_64__) || defined(_M_X64)) \ && (defined(JANET_POSIX) || defined(JANET_WINDOWS))) \ || (defined(_WIN64)) /* Windows 64 bit */ \ @@ -128,6 +130,8 @@ extern "C" { #else #define JANET_32 1 #endif +#endif +#endif /* Check big endian */ #if defined(__LITTLE_ENDIAN__) || \ @@ -301,6 +305,10 @@ extern "C" { #define JANET_STACK_MAX 0x7fffffff #endif +#ifdef JANET_PLAN9 +#undef NAN +#endif + /* Use nanboxed values - uses 8 bytes per value instead of 12 or 16. * To turn of nanboxing, for debugging purposes or for certain * architectures (Nanboxing only tested on x86 and x64), comment out @@ -667,6 +675,8 @@ JANET_API void janet_stream_level_triggered(JanetStream *stream); * signals. Define them here */ #ifdef JANET_WINDOWS typedef long JanetAtomicInt; +#elif defined(JANET_PLAN9) +typedef long JanetAtomicInt; #else typedef int32_t JanetAtomicInt; #endif @@ -1138,6 +1148,17 @@ struct JanetFunction { typedef struct JanetParseState JanetParseState; typedef struct JanetParser JanetParser; +typedef int (*Consumer)(JanetParser *p, JanetParseState *state, uint8_t c); + +struct JanetParseState { + int32_t counter; + int32_t argn; + int flags; + size_t line; + size_t column; + Consumer consumer; +}; + enum JanetParserStatus { JANET_PARSE_ROOT, JANET_PARSE_ERROR, diff --git a/src/mainclient/shell.c b/src/mainclient/shell.c index 2b36375b..93546db2 100644 --- a/src/mainclient/shell.c +++ b/src/mainclient/shell.c @@ -79,9 +79,11 @@ static void simpleline(JanetBuffer *buffer) { int c; for (;;) { c = fgetc(in); +#ifndef JANET_PLAN9 if (c < 0 && !feof(in) && errno == EINTR) { continue; } +#endif if (feof(in) || c < 0) { break; } From 98bbe9f474047ef9414e346e35e4fa02540b440d Mon Sep 17 00:00:00 2001 From: Noam Preil Date: Wed, 10 Dec 2025 23:35:24 +0000 Subject: [PATCH 047/307] expose os/isatty, fix floating point math --- src/boot/system_test.c | 6 ++++-- src/core/os.c | 24 ++++++++++++++++++++++-- src/include/janet.h | 4 ---- src/mainclient/shell.c | 10 ++++++++++ 4 files changed, 36 insertions(+), 8 deletions(-) diff --git a/src/boot/system_test.c b/src/boot/system_test.c index ba13571e..0b4c65b5 100644 --- a/src/boot/system_test.c +++ b/src/boot/system_test.c @@ -51,9 +51,11 @@ int system_test() { assert(janet_equals(janet_wrap_number(1.4), janet_wrap_number(1.4))); assert(janet_equals(janet_wrap_number(3.14159265), janet_wrap_number(3.14159265))); #ifdef NAN -#ifndef JANET_PLAN9 - assert(janet_checktype(janet_wrap_number(NAN), JANET_NUMBER)); +#ifdef JANET_PLAN9 + // Plan 9 traps NaNs by default; disable that. + setfcr(0); #endif + assert(janet_checktype(janet_wrap_number(NAN), JANET_NUMBER)); #else assert(janet_checktype(janet_wrap_number(0.0 / 0.0), JANET_NUMBER)); #endif diff --git a/src/core/os.c b/src/core/os.c index 66140c41..e183bf43 100644 --- a/src/core/os.c +++ b/src/core/os.c @@ -29,6 +29,11 @@ #include +#ifdef JANET_PLAN9 +#include +#endif + + #ifndef JANET_REDUCED_OS #include @@ -288,7 +293,20 @@ JANET_CORE_FN(os_exit, return janet_wrap_nil(); } -#ifndef JANET_REDUCED_OS +#ifdef JANET_PLAN9 + +JANET_CORE_FN(os_isatty, + "(os/isatty &opt file)", + "Returns true if `file` is a terminal. If `file` is not specified, " + "it will default to standard output.") { + janet_arity(argc, 0, 1); + FILE *f = (argc == 1) ? janet_getfile(argv, 0, NULL) : stdout; + int fd = fileno(f); + if (fd == -1) janet_panic(janet_strerror(errno)); + return janet_wrap_boolean(isatty(fd)); +} + +#elif !defined(JANET_REDUCED_OS) JANET_CORE_FN(os_cpu_count, "(os/cpu-count &opt dflt)", @@ -2847,7 +2865,9 @@ void janet_lib_os(JanetTable *env) { JANET_CORE_REG("os/which", os_which), JANET_CORE_REG("os/arch", os_arch), JANET_CORE_REG("os/compiler", os_compiler), -#ifndef JANET_REDUCED_OS +#ifdef JANET_PLAN9 + JANET_CORE_REG("os/isatty", os_isatty), +#elif !defined(JANET_REDUCED_OS) /* misc (un-sandboxed) */ JANET_CORE_REG("os/cpu-count", os_cpu_count), diff --git a/src/include/janet.h b/src/include/janet.h index 4b47a7ae..0401bf0e 100644 --- a/src/include/janet.h +++ b/src/include/janet.h @@ -305,10 +305,6 @@ extern "C" { #define JANET_STACK_MAX 0x7fffffff #endif -#ifdef JANET_PLAN9 -#undef NAN -#endif - /* Use nanboxed values - uses 8 bytes per value instead of 12 or 16. * To turn of nanboxing, for debugging purposes or for certain * architectures (Nanboxing only tested on x86 and x64), comment out diff --git a/src/mainclient/shell.c b/src/mainclient/shell.c index 93546db2..beb938dd 100644 --- a/src/mainclient/shell.c +++ b/src/mainclient/shell.c @@ -309,7 +309,9 @@ static int curpos(void) { char buf[32]; int cols, rows; unsigned int i = 0; +#ifndef JANET_PLAN9 if (write_console("\x1b[6n", 4) != 4) return -1; +#endif while (i < sizeof(buf) - 1) { if (read_console(buf + i, 1) != 1) break; if (buf[i] == 'R') break; @@ -327,6 +329,10 @@ static int getcols(void) { CONSOLE_SCREEN_BUFFER_INFO csbi; GetConsoleScreenBufferInfo(GetStdHandle(STD_OUTPUT_HANDLE), &csbi); return (int)(csbi.srWindow.Right - csbi.srWindow.Left + 1); +#elif defined(JANET_PLAN9) + //FIXME(noam): this can probably read /dev/window to get window size, and + //divide by the active font width, since plan9 requires fixed-width glyphs. + return 80; #else struct winsize ws; if (ioctl(1, TIOCGWINSZ, &ws) == -1 || ws.ws_col == 0) { @@ -1133,6 +1139,10 @@ int main(int argc, char **argv) { JanetArray *args; JanetTable *env; +#ifdef JANET_PLAN9 + setfcr(0); +#endif + #ifdef _WIN32 setup_console_output(); #endif From b723c00b98214a090e099ef37b69d9640ab08774 Mon Sep 17 00:00:00 2001 From: Noam Preil Date: Thu, 11 Dec 2025 07:54:39 +0000 Subject: [PATCH 048/307] mkfile: fix cleanfiles list, drop debugging target --- mkfile | 7 ++----- 1 file changed, 2 insertions(+), 5 deletions(-) diff --git a/mkfile b/mkfile index 0996aaf6..95887baf 100644 --- a/mkfile +++ b/mkfile @@ -4,13 +4,10 @@ TARG=janet HFILES=src/include/janet.h src/conf/janetconf.h JANET_PATH=/sys/lib/janet BIN=/$objtype/bin/ -JANET_CONFIG=JANET_SINGLE_THREADED JANET_NO_DYNAMIC_MODULES JANET_NO_THREADS JANET_OS_NAME=9front JANET_ARCH_NAME=$objtype JANET_BUILD="9front" JANET_API='' JANET_NO_RETURN='' JANET_NO_EV JANET_NO_REALPATH JANET_NO_UTC_MKTIME JANET_SIMPLE_GETLINE JANET_NO_FFI JANET_REDUCED_OS JANET_64 JANET_NO_ASSEMBLER +JANET_CONFIG=JANET_SINGLE_THREADED JANET_NO_DYNAMIC_MODULES JANET_NO_THREADS JANET_OS_NAME=9front JANET_ARCH_NAME=$objtype JANET_BUILD="9front" JANET_API='' JANET_NO_RETURN='' JANET_NO_REALPATH JANET_NO_UTC_MKTIME JANET_SIMPLE_GETLINE JANET_NO_FFI JANET_REDUCED_OS JANET_64 JANET_NO_ASSEMBLER CFLAGS=-FTVBNcwp -D _POSIX_SOURCE -DJANET_PLAN9 -D_BSD_EXTENSION -D_LIMITS_EXTENSION -Isrc/include -Isrc/conf -I/sys/include/npe -Dtypestr=janettypestr -DJANET_API `{echo '-D'^$JANET_CONFIG} BOOT_CFLAGS=$CFLAGS -DJANET_BOOTSTRAP -CLEANFILES=`{ls build/c/* build/boot/* build/core/*}y - -list: - echo $CFLAGS +CLEANFILES=`{ls build/c/* build/boot/* build/core/*} JANET_CORE_HEADERS=`{ls src/core/*.h} JANET_CORE_SOURCES=`{ls src/core/*.c} From d8a074731e535d17c7096dd644f4678932519a07 Mon Sep 17 00:00:00 2001 From: Tw Date: Thu, 11 Dec 2025 18:48:07 +0800 Subject: [PATCH 049/307] Apply :flycheck on def as well Signed-off-by: Tw Change-Id: I7bd10cbaa838b44794f8a881b0deedd06a6a6964 --- src/boot/boot.janet | 13 +++++++++++-- 1 file changed, 11 insertions(+), 2 deletions(-) diff --git a/src/boot/boot.janet b/src/boot/boot.janet index 9b46b4d7..13fa1185 100644 --- a/src/boot/boot.janet +++ b/src/boot/boot.janet @@ -4110,8 +4110,17 @@ true)) (defn- is-safe-def [thunk source env where] - (if (no-side-effects (last source)) - (thunk))) + (if-let [ve (get env (source 1)) + fc (get ve :flycheck)] + (cond + # Sometimes safe form + (function? fc) + (fc thunk source env where) + # Always safe form + fc + (thunk)) + (if (no-side-effects (last source)) + (thunk)))) (defn- flycheck-importer [thunk source env where] From 7777a017e275a9a8037889b1d8c08fb0b5f1608d Mon Sep 17 00:00:00 2001 From: sogaiu <983021772@users.noreply.github.com> Date: Fri, 12 Dec 2025 11:13:27 +0900 Subject: [PATCH 050/307] Update to macos-14 in ci --- .github/workflows/test.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/test.yml b/.github/workflows/test.yml index 2ace8070..ed02f369 100644 --- a/.github/workflows/test.yml +++ b/.github/workflows/test.yml @@ -12,7 +12,7 @@ jobs: runs-on: ${{ matrix.os }} strategy: matrix: - os: [ ubuntu-latest, macos-latest, macos-13 ] + os: [ ubuntu-latest, macos-latest, macos-14 ] steps: - name: Checkout the repository uses: actions/checkout@master From d888b1c5306da3c38f342fc91738ac666270ac2e Mon Sep 17 00:00:00 2001 From: Noam Preil Date: Fri, 12 Dec 2025 02:51:22 +0000 Subject: [PATCH 051/307] plan9: silence first mk, redisable EV for now --- mkfile | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/mkfile b/mkfile index 95887baf..3ef9a2bb 100644 --- a/mkfile +++ b/mkfile @@ -4,10 +4,10 @@ TARG=janet HFILES=src/include/janet.h src/conf/janetconf.h JANET_PATH=/sys/lib/janet BIN=/$objtype/bin/ -JANET_CONFIG=JANET_SINGLE_THREADED JANET_NO_DYNAMIC_MODULES JANET_NO_THREADS JANET_OS_NAME=9front JANET_ARCH_NAME=$objtype JANET_BUILD="9front" JANET_API='' JANET_NO_RETURN='' JANET_NO_REALPATH JANET_NO_UTC_MKTIME JANET_SIMPLE_GETLINE JANET_NO_FFI JANET_REDUCED_OS JANET_64 JANET_NO_ASSEMBLER +JANET_CONFIG=JANET_SINGLE_THREADED JANET_NO_DYNAMIC_MODULES JANET_NO_THREADS JANET_OS_NAME=9front JANET_ARCH_NAME=$objtype JANET_BUILD="9front" JANET_API='' JANET_NO_RETURN='' JANET_NO_REALPATH JANET_NO_UTC_MKTIME JANET_SIMPLE_GETLINE JANET_NO_FFI JANET_REDUCED_OS JANET_64 JANET_NO_ASSEMBLER JANET_NO_NET JANET_NO_EV CFLAGS=-FTVBNcwp -D _POSIX_SOURCE -DJANET_PLAN9 -D_BSD_EXTENSION -D_LIMITS_EXTENSION -Isrc/include -Isrc/conf -I/sys/include/npe -Dtypestr=janettypestr -DJANET_API `{echo '-D'^$JANET_CONFIG} BOOT_CFLAGS=$CFLAGS -DJANET_BOOTSTRAP -CLEANFILES=`{ls build/c/* build/boot/* build/core/*} +CLEANFILES=`{ls build/c/* build/boot/* build/core/* >[2]/dev/null} JANET_CORE_HEADERS=`{ls src/core/*.h} JANET_CORE_SOURCES=`{ls src/core/*.c} From 04c96296b50397665c221a8bf859352652e90098 Mon Sep 17 00:00:00 2001 From: Noam Preil Date: Fri, 12 Dec 2025 02:54:07 +0000 Subject: [PATCH 052/307] drop fixme, can worry about it later --- src/mainclient/shell.c | 4 ---- 1 file changed, 4 deletions(-) diff --git a/src/mainclient/shell.c b/src/mainclient/shell.c index beb938dd..ccf072da 100644 --- a/src/mainclient/shell.c +++ b/src/mainclient/shell.c @@ -329,10 +329,6 @@ static int getcols(void) { CONSOLE_SCREEN_BUFFER_INFO csbi; GetConsoleScreenBufferInfo(GetStdHandle(STD_OUTPUT_HANDLE), &csbi); return (int)(csbi.srWindow.Right - csbi.srWindow.Left + 1); -#elif defined(JANET_PLAN9) - //FIXME(noam): this can probably read /dev/window to get window size, and - //divide by the active font width, since plan9 requires fixed-width glyphs. - return 80; #else struct winsize ws; if (ioctl(1, TIOCGWINSZ, &ws) == -1 || ws.ws_col == 0) { From 8284b1f5f2ab66f5f936b998af86c2122101f9b6 Mon Sep 17 00:00:00 2001 From: Noam Preil Date: Fri, 12 Dec 2025 03:00:44 +0000 Subject: [PATCH 053/307] mkfile: standalone build in place, fix clean target --- mkfile | 31 +++++++++++-------------------- 1 file changed, 11 insertions(+), 20 deletions(-) diff --git a/mkfile b/mkfile index 3ef9a2bb..f7a1e767 100644 --- a/mkfile +++ b/mkfile @@ -7,7 +7,6 @@ BIN=/$objtype/bin/ JANET_CONFIG=JANET_SINGLE_THREADED JANET_NO_DYNAMIC_MODULES JANET_NO_THREADS JANET_OS_NAME=9front JANET_ARCH_NAME=$objtype JANET_BUILD="9front" JANET_API='' JANET_NO_RETURN='' JANET_NO_REALPATH JANET_NO_UTC_MKTIME JANET_SIMPLE_GETLINE JANET_NO_FFI JANET_REDUCED_OS JANET_64 JANET_NO_ASSEMBLER JANET_NO_NET JANET_NO_EV CFLAGS=-FTVBNcwp -D _POSIX_SOURCE -DJANET_PLAN9 -D_BSD_EXTENSION -D_LIMITS_EXTENSION -Isrc/include -Isrc/conf -I/sys/include/npe -Dtypestr=janettypestr -DJANET_API `{echo '-D'^$JANET_CONFIG} BOOT_CFLAGS=$CFLAGS -DJANET_BOOTSTRAP -CLEANFILES=`{ls build/c/* build/boot/* build/core/* >[2]/dev/null} JANET_CORE_HEADERS=`{ls src/core/*.h} JANET_CORE_SOURCES=`{ls src/core/*.c} @@ -19,32 +18,21 @@ JANET_BOOT_SOURCES=src/boot/array_test.c \ src/boot/system_test.c \ src/boot/table_test.c JANET_BOOT_HEADERS=src/boot/tests.h -JANET_BOOT_OBJECTS=`{echo $JANET_CORE_SOURCES $JANET_BOOT_SOURCES | sed -e 's/\.c/.boot.o/g' -e 's$src/$build/$g'} +JANET_BOOT_OBJECTS=`{echo $JANET_CORE_SOURCES $JANET_BOOT_SOURCES | sed -e 's/\.c/.boot.'$O'/g'} -OFILES=build/janet.$O build/shell.$O +OFILES=janet.$O src/mainclient/shell.$O -build/%.boot.o: src/%.c $JANET_HEADERS $JANET_CORE_HEADERS $JANET_BOOT_HEADERS +src/%.boot.$O: src/%.c $JANET_HEADERS $JANET_CORE_HEADERS $JANET_BOOT_HEADERS $CC $BOOT_CFLAGS -o $target $prereq(1) -build/core/%.$O: build/core +src/mainclient/shell.$O: src/mainclient/shell.c + $CC $BOOT_CFLAGS -o $target $prereq(1) -build/core: - mkdir -p build/core - -build/boot/%.$O: build/boot - -build/boot: - mkdir -p build/boot - -build/boot/$O.janet: $JANET_BOOT_OBJECTS +$O.janetboot: $JANET_BOOT_OBJECTS $LD $LDFLAGS -o $target $prereq -build/c/janet.c: build/boot/$O.janet src/boot/boot.janet - build/boot/$O.janet . JANET_PATH $JANET_PATH >$target - -build/c/shell.c: src/mainclient/shell.c - cp $prereq $target - +janet.c: $O.janetboot src/boot/boot.janet + $prereq(1) . JANET_PATH $JANET_PATH >$target build/janet.$O: build/c/janet.c src/conf/janetconf.h src/include/janet.h $CC $CFLAGS -D^$JANET_CONFIG -o $target $prereq(1) @@ -53,3 +41,6 @@ build/shell.$O: src/mainclient/shell.c src/conf/janetconf.h src/include/janet.h $CC $CFLAGS -D^$JANET_CONFIG -o $target $prereq(1) Date: Fri, 12 Dec 2025 04:08:00 +0000 Subject: [PATCH 054/307] 9front: clean up mkfile --- mkfile | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/mkfile b/mkfile index f7a1e767..41e7a9c2 100644 --- a/mkfile +++ b/mkfile @@ -4,7 +4,8 @@ TARG=janet HFILES=src/include/janet.h src/conf/janetconf.h JANET_PATH=/sys/lib/janet BIN=/$objtype/bin/ -JANET_CONFIG=JANET_SINGLE_THREADED JANET_NO_DYNAMIC_MODULES JANET_NO_THREADS JANET_OS_NAME=9front JANET_ARCH_NAME=$objtype JANET_BUILD="9front" JANET_API='' JANET_NO_RETURN='' JANET_NO_REALPATH JANET_NO_UTC_MKTIME JANET_SIMPLE_GETLINE JANET_NO_FFI JANET_REDUCED_OS JANET_64 JANET_NO_ASSEMBLER JANET_NO_NET JANET_NO_EV +DISABLED=PROCESSES EV NET ASSEMBLER FFI UTC_MKTIME REALPATH DYNAMIC_MODULES THREADS SYMLINKS LOCALES UMASK +JANET_CONFIG=JANET_SINGLE_THREADED JANET_OS_NAME=9front JANET_ARCH_NAME=$objtype JANET_BUILD="9front" JANET_API='' JANET_NO_RETURN='' JANET_SIMPLE_GETLINE JANET_64 `{echo JANET_NO_^$DISABLED} CFLAGS=-FTVBNcwp -D _POSIX_SOURCE -DJANET_PLAN9 -D_BSD_EXTENSION -D_LIMITS_EXTENSION -Isrc/include -Isrc/conf -I/sys/include/npe -Dtypestr=janettypestr -DJANET_API `{echo '-D'^$JANET_CONFIG} BOOT_CFLAGS=$CFLAGS -DJANET_BOOTSTRAP @@ -22,6 +23,8 @@ JANET_BOOT_OBJECTS=`{echo $JANET_CORE_SOURCES $JANET_BOOT_SOURCES | sed -e 's/\. OFILES=janet.$O src/mainclient/shell.$O +default:V:all + src/%.boot.$O: src/%.c $JANET_HEADERS $JANET_CORE_HEADERS $JANET_BOOT_HEADERS $CC $BOOT_CFLAGS -o $target $prereq(1) From 53fd2185fcdd1626473695b801e96075000c1d3f Mon Sep 17 00:00:00 2001 From: Noam Preil Date: Fri, 12 Dec 2025 04:25:41 +0000 Subject: [PATCH 055/307] fix mkfile, rename port to plan9 --- mkfile | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/mkfile b/mkfile index 41e7a9c2..8f33ee09 100644 --- a/mkfile +++ b/mkfile @@ -5,7 +5,7 @@ HFILES=src/include/janet.h src/conf/janetconf.h JANET_PATH=/sys/lib/janet BIN=/$objtype/bin/ DISABLED=PROCESSES EV NET ASSEMBLER FFI UTC_MKTIME REALPATH DYNAMIC_MODULES THREADS SYMLINKS LOCALES UMASK -JANET_CONFIG=JANET_SINGLE_THREADED JANET_OS_NAME=9front JANET_ARCH_NAME=$objtype JANET_BUILD="9front" JANET_API='' JANET_NO_RETURN='' JANET_SIMPLE_GETLINE JANET_64 `{echo JANET_NO_^$DISABLED} +JANET_CONFIG=JANET_SINGLE_THREADED JANET_OS_NAME=plan9 JANET_ARCH_NAME=$objtype JANET_API='' JANET_NO_RETURN='' JANET_SIMPLE_GETLINE JANET_64 `{echo JANET_NO_^$DISABLED} JANET_REDUCED_OS CFLAGS=-FTVBNcwp -D _POSIX_SOURCE -DJANET_PLAN9 -D_BSD_EXTENSION -D_LIMITS_EXTENSION -Isrc/include -Isrc/conf -I/sys/include/npe -Dtypestr=janettypestr -DJANET_API `{echo '-D'^$JANET_CONFIG} BOOT_CFLAGS=$CFLAGS -DJANET_BOOTSTRAP From d41eeaf7fadd33f194e6292ffc8d3e5865e09cf9 Mon Sep 17 00:00:00 2001 From: Noam Preil Date: Fri, 12 Dec 2025 04:14:46 +0000 Subject: [PATCH 056/307] 9front: detect compiler as kencc --- src/core/os.c | 3 +++ 1 file changed, 3 insertions(+) diff --git a/src/core/os.c b/src/core/os.c index e183bf43..78b39e4d 100644 --- a/src/core/os.c +++ b/src/core/os.c @@ -249,6 +249,7 @@ JANET_CORE_FN(os_compiler, "* :gcc\n\n" "* :clang\n\n" "* :msvc\n\n" + "* :kencc\n\n" "* :unknown\n\n") { janet_fixarity(argc, 0); (void) argv; @@ -258,6 +259,8 @@ JANET_CORE_FN(os_compiler, return janet_ckeywordv("clang"); #elif defined(__GNUC__) return janet_ckeywordv("gcc"); +#elif defined(JANET_PLAN9) + return janet_ckeywordv("kencc"); #else return janet_ckeywordv("unknown"); #endif From 7c311101951c1419547369c2f7ab45c645be9d8b Mon Sep 17 00:00:00 2001 From: Noam Preil Date: Fri, 12 Dec 2025 04:32:19 +0000 Subject: [PATCH 057/307] 9front: allow file/open of directories --- src/core/io.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/core/io.c b/src/core/io.c index c05e29a7..ed554316 100644 --- a/src/core/io.c +++ b/src/core/io.c @@ -167,7 +167,7 @@ JANET_CORE_FN(cfun_io_fopen, } FILE *f = fopen((const char *)fname, (const char *)fmode); if (f != NULL) { -#ifndef JANET_WINDOWS +#if !(defined(JANET_WINDOWS) || defined(JANET_PLAN9)) struct stat st; fstat(fileno(f), &st); if (S_ISDIR(st.st_mode)) { From dde1a67b7795b5789d1393d865762f1f3ab4f491 Mon Sep 17 00:00:00 2001 From: Noam Preil Date: Fri, 12 Dec 2025 07:41:05 +0000 Subject: [PATCH 058/307] plan9: 32-bit support --- mkfile | 3 ++- src/include/janet.h | 5 +---- 2 files changed, 3 insertions(+), 5 deletions(-) diff --git a/mkfile b/mkfile index 8f33ee09..c6f4937d 100644 --- a/mkfile +++ b/mkfile @@ -5,7 +5,8 @@ HFILES=src/include/janet.h src/conf/janetconf.h JANET_PATH=/sys/lib/janet BIN=/$objtype/bin/ DISABLED=PROCESSES EV NET ASSEMBLER FFI UTC_MKTIME REALPATH DYNAMIC_MODULES THREADS SYMLINKS LOCALES UMASK -JANET_CONFIG=JANET_SINGLE_THREADED JANET_OS_NAME=plan9 JANET_ARCH_NAME=$objtype JANET_API='' JANET_NO_RETURN='' JANET_SIMPLE_GETLINE JANET_64 `{echo JANET_NO_^$DISABLED} JANET_REDUCED_OS +JANET_CONFIG=JANET_SINGLE_THREADED JANET_OS_NAME=plan9 JANET_ARCH_NAME=$objtype JANET_API='' JANET_NO_RETURN='' JANET_SIMPLE_GETLINE `{echo JANET_NO_^$DISABLED} JANET_REDUCED_OS PLAN9_`{echo -n $objtype} + CFLAGS=-FTVBNcwp -D _POSIX_SOURCE -DJANET_PLAN9 -D_BSD_EXTENSION -D_LIMITS_EXTENSION -Isrc/include -Isrc/conf -I/sys/include/npe -Dtypestr=janettypestr -DJANET_API `{echo '-D'^$JANET_CONFIG} BOOT_CFLAGS=$CFLAGS -DJANET_BOOTSTRAP diff --git a/src/include/janet.h b/src/include/janet.h index 0401bf0e..9879db64 100644 --- a/src/include/janet.h +++ b/src/include/janet.h @@ -113,8 +113,6 @@ extern "C" { #endif /* Check 64-bit vs 32-bit */ -#ifndef JANET_64 -#ifndef JANET_32 #if ((defined(__x86_64__) || defined(_M_X64)) \ && (defined(JANET_POSIX) || defined(JANET_WINDOWS))) \ || (defined(_WIN64)) /* Windows 64 bit */ \ @@ -123,6 +121,7 @@ extern "C" { || (defined(__sparc__) && defined(__arch64__) || defined (__sparcv9)) /* BE */ \ || defined(__s390x__) /* S390 64-bit (BE) */ \ || (defined(__ppc64__) || defined(__PPC64__)) \ + || defined(PLAN9_arm64) || defined(PLAN9_amd64) \ || defined(__aarch64__) /* ARM 64-bit */ \ || (defined(__riscv) && (__riscv_xlen == 64)) /* RISC-V 64-bit */ \ || defined(__loongarch64) /* LoongArch64 64-bit */ @@ -130,8 +129,6 @@ extern "C" { #else #define JANET_32 1 #endif -#endif -#endif /* Check big endian */ #if defined(__LITTLE_ENDIAN__) || \ From dc52242d36dc78948c39264ed70de6f9b08d909f Mon Sep 17 00:00:00 2001 From: Noam Preil Date: Fri, 12 Dec 2025 07:44:25 +0000 Subject: [PATCH 059/307] add plan9 support to changelog --- CHANGELOG.md | 1 + 1 file changed, 1 insertion(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index 6a527f32..7c621948 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -6,6 +6,7 @@ All notable changes to this project will be documented in this file. - Add `thaw-keep-keys` as a variant of thaw - The `repl` function now respects the `*repl-prompt*` dynamic binding by default. - Allow matching exact lengths of datastructures with the `match` macro using a dollar suffix. +- Add initial support for Plan 9. Some modules (e.g. ev) are not yet enabled. ## 1.40.1 - 2025-11-16 - Fix `JANET_REDUCED_OS` build regression caused by `os/posix-chroot`. From 352935596abfef6f54f12ca32d3d75074864d25c Mon Sep 17 00:00:00 2001 From: Noam Preil Date: Fri, 12 Dec 2025 04:17:20 +0000 Subject: [PATCH 060/307] 9front: support (os/cpu-count) --- src/core/os.c | 2 ++ 1 file changed, 2 insertions(+) diff --git a/src/core/os.c b/src/core/os.c index 78b39e4d..99bf5f94 100644 --- a/src/core/os.c +++ b/src/core/os.c @@ -354,6 +354,8 @@ JANET_CORE_FN(os_cpu_count, return dflt; } return janet_wrap_integer(result); +#elif defined(JANET_PLAN9) + return janet_wrap_integer(atoi(getenv("NPROC"))); #else return dflt; #endif From 7d06faca665cf973d072970cc801890883af0174 Mon Sep 17 00:00:00 2001 From: Noam Preil Date: Thu, 11 Dec 2025 21:35:38 -0600 Subject: [PATCH 061/307] 9front: expanded OS support --- mkfile | 3 +-- src/core/os.c | 62 ++++++++++++++++++++++++++++++------------------- src/core/util.c | 6 +++++ 3 files changed, 45 insertions(+), 26 deletions(-) diff --git a/mkfile b/mkfile index c6f4937d..ba6969f6 100644 --- a/mkfile +++ b/mkfile @@ -5,8 +5,7 @@ HFILES=src/include/janet.h src/conf/janetconf.h JANET_PATH=/sys/lib/janet BIN=/$objtype/bin/ DISABLED=PROCESSES EV NET ASSEMBLER FFI UTC_MKTIME REALPATH DYNAMIC_MODULES THREADS SYMLINKS LOCALES UMASK -JANET_CONFIG=JANET_SINGLE_THREADED JANET_OS_NAME=plan9 JANET_ARCH_NAME=$objtype JANET_API='' JANET_NO_RETURN='' JANET_SIMPLE_GETLINE `{echo JANET_NO_^$DISABLED} JANET_REDUCED_OS PLAN9_`{echo -n $objtype} - +JANET_CONFIG=JANET_SINGLE_THREADED JANET_OS_NAME=plan9 JANET_ARCH_NAME=$objtype JANET_API='' JANET_NO_RETURN='' JANET_SIMPLE_GETLINE `{echo JANET_NO_^$DISABLED} PLAN9_`{echo -n $objtype} CFLAGS=-FTVBNcwp -D _POSIX_SOURCE -DJANET_PLAN9 -D_BSD_EXTENSION -D_LIMITS_EXTENSION -Isrc/include -Isrc/conf -I/sys/include/npe -Dtypestr=janettypestr -DJANET_API `{echo '-D'^$JANET_CONFIG} BOOT_CFLAGS=$CFLAGS -DJANET_BOOTSTRAP diff --git a/src/core/os.c b/src/core/os.c index 99bf5f94..bdf6e7bf 100644 --- a/src/core/os.c +++ b/src/core/os.c @@ -62,12 +62,14 @@ #include #define JANET_SPAWN_CHDIR #else +#ifndef JANET_PLAN9 #include +#include +#endif #include #include #include #include -#include #ifdef JANET_APPLE #include #define environ (*_NSGetEnviron()) @@ -296,20 +298,7 @@ JANET_CORE_FN(os_exit, return janet_wrap_nil(); } -#ifdef JANET_PLAN9 - -JANET_CORE_FN(os_isatty, - "(os/isatty &opt file)", - "Returns true if `file` is a terminal. If `file` is not specified, " - "it will default to standard output.") { - janet_arity(argc, 0, 1); - FILE *f = (argc == 1) ? janet_getfile(argv, 0, NULL) : stdout; - int fd = fileno(f); - if (fd == -1) janet_panic(janet_strerror(errno)); - return janet_wrap_boolean(isatty(fd)); -} - -#elif !defined(JANET_REDUCED_OS) +#ifndef JANET_REDUCED_OS JANET_CORE_FN(os_cpu_count, "(os/cpu-count &opt dflt)", @@ -1626,6 +1615,7 @@ JANET_CORE_FN(os_shell, #endif /* JANET_NO_PROCESSES */ +#ifndef JANET_PLAN9 JANET_CORE_FN(os_environ, "(os/environ)", "Get a copy of the OS environment table.") { @@ -1657,6 +1647,7 @@ JANET_CORE_FN(os_environ, janet_unlock_environ(); return janet_wrap_table(t); } +#endif JANET_CORE_FN(os_getenv, "(os/getenv variable &opt dflt)", @@ -1681,6 +1672,9 @@ JANET_CORE_FN(os_setenv, #ifdef JANET_WINDOWS #define SETENV(K,V) _putenv_s(K, V) #define UNSETENV(K) _putenv_s(K, "") +#elif defined(JANET_PLAN9) +#define SETENV(K,V) putenv(K, V) +#define UNSETENV(K) unsetenv(K) #else #define SETENV(K,V) setenv(K, V, 1) #define UNSETENV(K) unsetenv(K) @@ -1853,6 +1847,8 @@ static struct tm *time_to_tm(const Janet *argv, int32_t argc, int32_t n, struct _tzset(); localtime_s(t_infos, &t); t_info = t_infos; +#elif defined(JANET_PLAN9) + t_info = localtime(&t); #else tzset(); t_info = localtime_r(&t, t_infos); @@ -1862,6 +1858,8 @@ static struct tm *time_to_tm(const Janet *argv, int32_t argc, int32_t n, struct #ifdef JANET_WINDOWS gmtime_s(t_infos, &t); t_info = t_infos; +#elif defined(JANET_PLAN9) + t_info = gmtime(&t); #else t_info = gmtime_r(&t, t_infos); #endif @@ -2036,6 +2034,7 @@ JANET_CORE_FN(os_mktime, #define j_symlink symlink #endif +#ifndef JANET_NO_LOCALES JANET_CORE_FN(os_setlocale, "(os/setlocale &opt locale category)", "Set the system locale, which affects how dates and numbers are formatted. " @@ -2072,19 +2071,20 @@ JANET_CORE_FN(os_setlocale, if (old == NULL) return janet_wrap_nil(); return janet_cstringv(old); } +#endif JANET_CORE_FN(os_link, "(os/link oldpath newpath &opt symlink)", "Create a link at newpath that points to oldpath and returns nil. " "Iff symlink is truthy, creates a symlink. " "Iff symlink is falsey or not provided, " - "creates a hard link. Does not work on Windows.") { + "creates a hard link. Does not work on Windows or Plan 9.") { janet_sandbox_assert(JANET_SANDBOX_FS_WRITE); janet_arity(argc, 2, 3); -#ifdef JANET_WINDOWS +#if defined(JANET_WINDOWS) || defined(JANET_PLAN9) (void) argc; (void) argv; - janet_panic("not supported on Windows"); + janet_panic("not supported on Windows or Plan 9"); #else const char *oldpath = janet_getcstring(argv, 0); const char *newpath = janet_getcstring(argv, 1); @@ -2099,10 +2099,10 @@ JANET_CORE_FN(os_symlink, "Create a symlink from oldpath to newpath, returning nil. Same as `(os/link oldpath newpath true)`.") { janet_sandbox_assert(JANET_SANDBOX_FS_WRITE); janet_fixarity(argc, 2); -#ifdef JANET_WINDOWS +#if defined(JANET_WINDOWS) || defined(JANET_PLAN9) (void) argc; (void) argv; - janet_panic("not supported on Windows"); + janet_panic("not supported on Windows or Plan 9"); #else const char *oldpath = janet_getcstring(argv, 0); const char *newpath = janet_getcstring(argv, 1); @@ -2140,6 +2140,8 @@ JANET_CORE_FN(os_rmdir, const char *path = janet_getcstring(argv, 0); #ifdef JANET_WINDOWS int res = _rmdir(path); +#elif defined(JANET_PLAN9) + int res = remove(path); #else int res = rmdir(path); #endif @@ -2266,11 +2268,13 @@ static const uint8_t *janet_decode_mode(mode_t m) { const char *str = "other"; if (S_ISREG(m)) str = "file"; else if (S_ISDIR(m)) str = "directory"; +#ifndef JANET_PLAN9 else if (S_ISFIFO(m)) str = "fifo"; else if (S_ISBLK(m)) str = "block"; else if (S_ISSOCK(m)) str = "socket"; else if (S_ISLNK(m)) str = "link"; else if (S_ISCHR(m)) str = "character"; +#endif return janet_ckeyword(str); } @@ -2435,6 +2439,9 @@ static Janet os_stat_or_lstat(int do_lstat, int32_t argc, Janet *argv) { #ifdef JANET_WINDOWS (void) do_lstat; int res = _stat(path, &st); +#elif defined(JANET_PLAN9) + (void)do_lstat; + int res = stat(path, &st); #else int res; if (do_lstat) { @@ -2495,9 +2502,13 @@ JANET_CORE_FN(os_chmod, "Change file permissions, where `mode` is a permission string as returned by " "`os/perm-string`, or an integer as returned by `os/perm-int`. " "When `mode` is an integer, it is interpreted as a Unix permission value, best specified in octal, like " - "8r666 or 8r400. Windows will not differentiate between user, group, and other permissions, and thus will combine all of these permissions. Returns nil.") { + "8r666 or 8r400. Windows will not differentiate between user, group, and other permissions, and thus will combine all of these permissions. Returns nil." + "Unsupported on plan9.") { janet_sandbox_assert(JANET_SANDBOX_FS_WRITE); janet_fixarity(argc, 2); +#ifdef JANET_PLAN9 + janet_panic("not supported on Plan 9"); +#else const char *path = janet_getcstring(argv, 0); #ifdef JANET_WINDOWS int res = _chmod(path, os_getmode(argv, 1)); @@ -2506,6 +2517,7 @@ JANET_CORE_FN(os_chmod, #endif if (-1 == res) janet_panicf("%s: %s", janet_strerror(errno), path); return janet_wrap_nil(); +#endif } #ifndef JANET_NO_UMASK @@ -2870,9 +2882,7 @@ void janet_lib_os(JanetTable *env) { JANET_CORE_REG("os/which", os_which), JANET_CORE_REG("os/arch", os_arch), JANET_CORE_REG("os/compiler", os_compiler), -#ifdef JANET_PLAN9 - JANET_CORE_REG("os/isatty", os_isatty), -#elif !defined(JANET_REDUCED_OS) +#ifndef JANET_REDUCED_OS /* misc (un-sandboxed) */ JANET_CORE_REG("os/cpu-count", os_cpu_count), @@ -2886,10 +2896,14 @@ void janet_lib_os(JanetTable *env) { JANET_CORE_REG("os/strftime", os_strftime), JANET_CORE_REG("os/sleep", os_sleep), JANET_CORE_REG("os/isatty", os_isatty), +#ifndef JANET_NO_LOCALES JANET_CORE_REG("os/setlocale", os_setlocale), +#endif /* env functions */ +#ifndef JANET_PLAN9 JANET_CORE_REG("os/environ", os_environ), +#endif JANET_CORE_REG("os/getenv", os_getenv), JANET_CORE_REG("os/setenv", os_setenv), diff --git a/src/core/util.c b/src/core/util.c index f239d38e..26ff23d0 100644 --- a/src/core/util.c +++ b/src/core/util.c @@ -848,11 +848,15 @@ int janet_checksize(Janet x) { return 0; double dval = janet_unwrap_number(x); if (dval != (double)((size_t) dval)) return 0; +#ifdef JANET_PLAN9 + return dval <= SIZE_MAX; +#else if (SIZE_MAX > JANET_INTMAX_INT64) { return dval <= JANET_INTMAX_INT64; } else { return dval <= SIZE_MAX; } +#endif } JanetTable *janet_get_core_table(const char *name) { @@ -959,8 +963,10 @@ int janet_gettime(struct timespec *spec, enum JanetTimeSource source) { cid = CLOCK_REALTIME; } else if (source == JANET_TIME_MONOTONIC) { cid = CLOCK_MONOTONIC; +#ifndef JANET_PLAN9 } else if (source == JANET_TIME_CPUTIME) { cid = CLOCK_PROCESS_CPUTIME_ID; +#endif } return clock_gettime(cid, spec); } From 8887075a8c7e005410161076abb959561d8b7abc Mon Sep 17 00:00:00 2001 From: Noam Preil Date: Fri, 12 Dec 2025 09:21:19 +0000 Subject: [PATCH 062/307] plan9: working process support --- mkfile | 2 +- src/core/os.c | 28 +++++++++++++++++----------- 2 files changed, 18 insertions(+), 12 deletions(-) mode change 100644 => 100755 mkfile diff --git a/mkfile b/mkfile old mode 100644 new mode 100755 index ba6969f6..77c0bf75 --- a/mkfile +++ b/mkfile @@ -4,7 +4,7 @@ TARG=janet HFILES=src/include/janet.h src/conf/janetconf.h JANET_PATH=/sys/lib/janet BIN=/$objtype/bin/ -DISABLED=PROCESSES EV NET ASSEMBLER FFI UTC_MKTIME REALPATH DYNAMIC_MODULES THREADS SYMLINKS LOCALES UMASK +DISABLED=EV NET ASSEMBLER FFI UTC_MKTIME REALPATH DYNAMIC_MODULES THREADS SYMLINKS LOCALES UMASK SPAWN JANET_CONFIG=JANET_SINGLE_THREADED JANET_OS_NAME=plan9 JANET_ARCH_NAME=$objtype JANET_API='' JANET_NO_RETURN='' JANET_SIMPLE_GETLINE `{echo JANET_NO_^$DISABLED} PLAN9_`{echo -n $objtype} CFLAGS=-FTVBNcwp -D _POSIX_SOURCE -DJANET_PLAN9 -D_BSD_EXTENSION -D_LIMITS_EXTENSION -Isrc/include -Isrc/conf -I/sys/include/npe -Dtypestr=janettypestr -DJANET_API `{echo '-D'^$JANET_CONFIG} BOOT_CFLAGS=$CFLAGS -DJANET_BOOTSTRAP diff --git a/src/core/os.c b/src/core/os.c index bdf6e7bf..fb997a02 100644 --- a/src/core/os.c +++ b/src/core/os.c @@ -75,7 +75,7 @@ #define environ (*_NSGetEnviron()) #include int chroot(const char *dirname); -#else +#elif !defined(JANET_PLAN9) extern char **environ; #endif #ifdef JANET_THREADS @@ -305,21 +305,17 @@ JANET_CORE_FN(os_cpu_count, "Get an approximate number of CPUs available on for this process to use. If " "unable to get an approximation, will return a default value dflt.") { janet_arity(argc, 0, 1); - Janet dflt = argc > 0 ? argv[0] : janet_wrap_nil(); #ifdef JANET_WINDOWS - (void) dflt; SYSTEM_INFO info; GetSystemInfo(&info); return janet_wrap_integer(info.dwNumberOfProcessors); #elif defined(JANET_LINUX) - (void) dflt; cpu_set_t cs; CPU_ZERO(&cs); sched_getaffinity(0, sizeof(cs), &cs); int count = CPU_COUNT(&cs); return janet_wrap_integer(count); #elif defined(JANET_BSD) && defined(HW_NCPUONLINE) - (void) dflt; const int name[2] = {CTL_HW, HW_NCPUONLINE}; int result = 0; size_t len = sizeof(int); @@ -328,7 +324,6 @@ JANET_CORE_FN(os_cpu_count, } return janet_wrap_integer(result); #elif defined(JANET_BSD) && defined(HW_NCPU) - (void) dflt; const int name[2] = {CTL_HW, HW_NCPU}; int result = 0; size_t len = sizeof(int); @@ -337,7 +332,6 @@ JANET_CORE_FN(os_cpu_count, } return janet_wrap_integer(result); #elif defined(JANET_ILLUMOS) - (void) dflt; long result = sysconf(_SC_NPROCESSORS_CONF); if (result < 0) { return dflt; @@ -346,7 +340,7 @@ JANET_CORE_FN(os_cpu_count, #elif defined(JANET_PLAN9) return janet_wrap_integer(atoi(getenv("NPROC"))); #else - return dflt; + return argc > 0 ? argv[0] : janet_wrap_nil(); #endif } @@ -1355,6 +1349,9 @@ static Janet os_execute_impl(int32_t argc, Janet *argv, JanetExecuteMode mode) { /* exec mode */ if (mode == JANET_EXECUTE_EXEC) { int status; +#ifdef JANET_PLAN9 + status = exec(cargv[0], cargv); +#else if (!use_environ) { environ = envp; } @@ -1365,9 +1362,11 @@ static Janet os_execute_impl(int32_t argc, Janet *argv, JanetExecuteMode mode) { status = execv(cargv[0], cargv); } } while (status == -1 && errno == EINTR); +#endif janet_panicf("%p: %s", cargv[0], janet_strerror(errno ? errno : ENOENT)); } +#ifndef JANET_NO_SPAWN /* Use posix_spawn to spawn new process */ /* Posix spawn setup */ @@ -1436,6 +1435,8 @@ static Janet os_execute_impl(int32_t argc, Janet *argv, JanetExecuteMode mode) { } #endif +#endif +#ifndef JANET_NO_SPAWN JanetProc *proc = janet_abstract(&ProcAT, sizeof(JanetProc)); proc->return_code = -1; #ifdef JANET_WINDOWS @@ -1473,6 +1474,7 @@ static Janet os_execute_impl(int32_t argc, Janet *argv, JanetExecuteMode mode) { return os_proc_wait_impl(proc); #endif } +#endif } JANET_CORE_FN(os_execute, @@ -1533,7 +1535,7 @@ JANET_CORE_FN(os_posix_exec, JANET_CORE_FN(os_posix_fork, "(os/posix-fork)", "Make a `fork` system call and create a new process. Return nil if in the new process, otherwise a core/process object (as returned by os/spawn). " - "Not supported on all systems (POSIX only).") { + "Not supported on all systems (POSIX and Plan 9 only).") { janet_sandbox_assert(JANET_SANDBOX_SUBPROCESS); janet_fixarity(argc, 0); (void) argv; @@ -1541,9 +1543,13 @@ JANET_CORE_FN(os_posix_fork, janet_panic("not supported on Windows"); #else pid_t result; +#ifdef JANET_PLAN9 + result = fork(); +#else do { result = fork(); } while (result == -1 && errno == EINTR); +#endif if (result == -1) { janet_panic(janet_strerror(errno)); } @@ -1564,9 +1570,9 @@ JANET_CORE_FN(os_posix_chroot, "Not supported on all systems (POSIX only).") { janet_sandbox_assert(JANET_SANDBOX_CHROOT); janet_fixarity(argc, 1); -#ifdef JANET_WINDOWS +#if defined(JANET_WINDOWS) || defined(JANET_PLAN9) (void) argv; - janet_panic("not supported on Windows"); + janet_panic("not supported on Windows or Plan 9"); #else const char *root = janet_getcstring(argv, 0); int result; From 4a9c9ebefccd0492e5ff0487d3a42c952e9fefea Mon Sep 17 00:00:00 2001 From: Noam Preil Date: Fri, 12 Dec 2025 09:29:23 +0000 Subject: [PATCH 063/307] plan9: restore sys/wait usage (patched in NPE) --- src/core/io.c | 2 -- src/core/os.c | 2 +- 2 files changed, 1 insertion(+), 3 deletions(-) diff --git a/src/core/io.c b/src/core/io.c index ed554316..26d47652 100644 --- a/src/core/io.c +++ b/src/core/io.c @@ -32,9 +32,7 @@ #ifndef JANET_WINDOWS #include #include -#ifndef JANET_PLAN9 #include -#endif #include #endif diff --git a/src/core/os.c b/src/core/os.c index fb997a02..30242a3a 100644 --- a/src/core/os.c +++ b/src/core/os.c @@ -64,12 +64,12 @@ #else #ifndef JANET_PLAN9 #include -#include #endif #include #include #include #include +#include #ifdef JANET_APPLE #include #define environ (*_NSGetEnviron()) From 2de6d26a46ce7d7707144937a41472622b7eb3ce Mon Sep 17 00:00:00 2001 From: Noam Preil Date: Fri, 12 Dec 2025 09:35:37 +0000 Subject: [PATCH 064/307] plan9: restore CLOCK_PROCESS_CPUTIME_ID support --- src/core/util.c | 2 -- 1 file changed, 2 deletions(-) diff --git a/src/core/util.c b/src/core/util.c index 26ff23d0..d57dfeb3 100644 --- a/src/core/util.c +++ b/src/core/util.c @@ -963,10 +963,8 @@ int janet_gettime(struct timespec *spec, enum JanetTimeSource source) { cid = CLOCK_REALTIME; } else if (source == JANET_TIME_MONOTONIC) { cid = CLOCK_MONOTONIC; -#ifndef JANET_PLAN9 } else if (source == JANET_TIME_CPUTIME) { cid = CLOCK_PROCESS_CPUTIME_ID; -#endif } return clock_gettime(cid, spec); } From 7e2a57687ac1611a83eb2031ed13a3c9fefaad5c Mon Sep 17 00:00:00 2001 From: Noam Preil Date: Fri, 12 Dec 2025 09:42:55 +0000 Subject: [PATCH 065/307] clean test --- src/boot/number_test.c | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/src/boot/number_test.c b/src/boot/number_test.c index 4599790a..a9c9f262 100644 --- a/src/boot/number_test.c +++ b/src/boot/number_test.c @@ -44,8 +44,9 @@ static void test_valid_str(const char *str) { } int number_test() { - -#ifndef JANET_PLAN9 +#ifdef JANET_PLAN9 + return 0; +#endif test_valid_str("1.0"); test_valid_str("1"); test_valid_str("2.1"); @@ -63,7 +64,6 @@ int number_test() { test_valid_str("123123123123123123132123"); test_valid_str("0000000011111111111111111111111111"); test_valid_str(".112312333333323123123123123123123"); -#endif return 0; } From 0d7afc9419ad054c19bc17cc164fa2e4fed93daf Mon Sep 17 00:00:00 2001 From: Noam Preil Date: Fri, 12 Dec 2025 09:45:39 +0000 Subject: [PATCH 066/307] clean up headers --- src/core/os.c | 5 ----- 1 file changed, 5 deletions(-) diff --git a/src/core/os.c b/src/core/os.c index 30242a3a..319b995d 100644 --- a/src/core/os.c +++ b/src/core/os.c @@ -29,11 +29,6 @@ #include -#ifdef JANET_PLAN9 -#include -#endif - - #ifndef JANET_REDUCED_OS #include From 1971d7df0a583c1223fa675c5ebc605b81d5de2d Mon Sep 17 00:00:00 2001 From: Noam Preil Date: Fri, 12 Dec 2025 09:52:30 +0000 Subject: [PATCH 067/307] plan9: use _Exit --- src/core/os.c | 4 ---- 1 file changed, 4 deletions(-) diff --git a/src/core/os.c b/src/core/os.c index 319b995d..487fe150 100644 --- a/src/core/os.c +++ b/src/core/os.c @@ -282,11 +282,7 @@ JANET_CORE_FN(os_exit, } janet_deinit(); if (argc >= 2 && janet_truthy(argv[1])) { -#ifdef JANET_PLAN9 - exit(status); -#else _Exit(status); -#endif } else { exit(status); } From 715dd413cc8caa121d4a5f2a2e8b45460b513649 Mon Sep 17 00:00:00 2001 From: Noam Preil Date: Fri, 12 Dec 2025 10:32:43 +0000 Subject: [PATCH 068/307] plan9: support %r format to read errstr --- src/core/pp.c | 11 +++++++++-- 1 file changed, 9 insertions(+), 2 deletions(-) diff --git a/src/core/pp.c b/src/core/pp.c index 888d3e61..80f88dc6 100644 --- a/src/core/pp.c +++ b/src/core/pp.c @@ -794,9 +794,7 @@ static const char *get_fmt_mapping(char c) { if (format_mappings[i].c == c) return format_mappings[i].mapping; } -#ifndef JANET_PLAN9 janet_assert(0, "bad format mapping"); -#endif } static const char *scanformat( @@ -1025,10 +1023,19 @@ void janet_buffer_format( char form[MAX_FORMAT], item[MAX_ITEM]; char width[3], precision[3]; int nb = 0; /* number of bytes in added item */ +#ifdef JANET_PLAN9 + if(*strfrmt == 'r'){ + rerrstr(item, MAX_ITEM); + nb = strlen(item); + } else +#endif if (++arg >= argc) janet_panic("not enough values for format"); strfrmt = scanformat(strfrmt, form, width, precision); switch (*strfrmt++) { +#ifdef JANET_PLAN9 + case 'r': break; +#endif case 'c': { nb = snprintf(item, MAX_ITEM, form, (int) janet_getinteger(argv, arg)); From 7f9d92a73bfd655f9b5a9e9a56f865b8bf23a92f Mon Sep 17 00:00:00 2001 From: Calvin Rose Date: Sat, 13 Dec 2025 06:00:16 -0600 Subject: [PATCH 069/307] Formatting --- src/boot/number_test.c | 2 +- src/boot/system_test.c | 4 ++-- src/core/capi.c | 8 ++++---- src/core/ev.c | 2 +- src/core/os.c | 19 ++++++++++--------- src/core/pp.c | 15 ++++++++------- src/core/util.c | 2 +- src/mainclient/shell.c | 2 +- 8 files changed, 28 insertions(+), 26 deletions(-) diff --git a/src/boot/number_test.c b/src/boot/number_test.c index a9c9f262..4d783008 100644 --- a/src/boot/number_test.c +++ b/src/boot/number_test.c @@ -45,7 +45,7 @@ static void test_valid_str(const char *str) { int number_test() { #ifdef JANET_PLAN9 - return 0; + return 0; #endif test_valid_str("1.0"); test_valid_str("1"); diff --git a/src/boot/system_test.c b/src/boot/system_test.c index 0b4c65b5..397d9ff8 100644 --- a/src/boot/system_test.c +++ b/src/boot/system_test.c @@ -52,8 +52,8 @@ int system_test() { assert(janet_equals(janet_wrap_number(3.14159265), janet_wrap_number(3.14159265))); #ifdef NAN #ifdef JANET_PLAN9 - // Plan 9 traps NaNs by default; disable that. - setfcr(0); + // Plan 9 traps NaNs by default; disable that. + setfcr(0); #endif assert(janet_checktype(janet_wrap_number(NAN), JANET_NUMBER)); #else diff --git a/src/core/capi.c b/src/core/capi.c index e15b1a30..8fc55c9e 100644 --- a/src/core/capi.c +++ b/src/core/capi.c @@ -565,7 +565,7 @@ JanetAtomicInt janet_atomic_inc(JanetAtomicInt volatile *x) { #elif defined(JANET_USE_STDATOMIC) return atomic_fetch_add_explicit(x, 1, memory_order_relaxed) + 1; #elif defined(JANET_PLAN9) - return aincl((void*)x, 1); + return aincl((void*)x, 1); #else return __atomic_add_fetch(x, 1, __ATOMIC_RELAXED); #endif @@ -577,7 +577,7 @@ JanetAtomicInt janet_atomic_dec(JanetAtomicInt volatile *x) { #elif defined(JANET_USE_STDATOMIC) return atomic_fetch_add_explicit(x, -1, memory_order_acq_rel) - 1; #elif defined(JANET_PLAN9) - return aincl((void*)x, -1); + return aincl((void*)x, -1); #else return __atomic_add_fetch(x, -1, __ATOMIC_ACQ_REL); #endif @@ -587,7 +587,7 @@ JanetAtomicInt janet_atomic_load(JanetAtomicInt volatile *x) { #ifdef _MSC_VER return _InterlockedOr(x, 0); #elif defined(JANET_PLAN9) - return agetl((void*)x); + return agetl((void*)x); #elif defined(JANET_USE_STDATOMIC) return atomic_load_explicit(x, memory_order_acquire); #else @@ -599,7 +599,7 @@ JanetAtomicInt janet_atomic_load_relaxed(JanetAtomicInt volatile *x) { #ifdef _MSC_VER return _InterlockedOr(x, 0); #elif defined(JANET_PLAN9) - return agetl((void*)x); + return agetl((void*)x); #elif defined(JANET_USE_STDATOMIC) return atomic_load_explicit(x, memory_order_relaxed); #else diff --git a/src/core/ev.c b/src/core/ev.c index f57f7c2e..2adf80ed 100644 --- a/src/core/ev.c +++ b/src/core/ev.c @@ -1082,7 +1082,7 @@ static int janet_channel_pop_with_lock(JanetChannel *channel, Janet *item, int i int is_threaded = janet_chan_is_threaded(channel); if (janet_q_pop(&channel->items, item, sizeof(Janet))) { /* Queue empty */ - if (is_choice == 2) return 0; // Skip pending read + if (is_choice == 2) return 0; /* Skip pending read */ JanetChannelPending pending; pending.thread = &janet_vm; pending.fiber = janet_vm.root_fiber, diff --git a/src/core/os.c b/src/core/os.c index 487fe150..7b989c35 100644 --- a/src/core/os.c +++ b/src/core/os.c @@ -257,7 +257,7 @@ JANET_CORE_FN(os_compiler, #elif defined(__GNUC__) return janet_ckeywordv("gcc"); #elif defined(JANET_PLAN9) - return janet_ckeywordv("kencc"); + return janet_ckeywordv("kencc"); #else return janet_ckeywordv("unknown"); #endif @@ -296,6 +296,7 @@ JANET_CORE_FN(os_cpu_count, "Get an approximate number of CPUs available on for this process to use. If " "unable to get an approximation, will return a default value dflt.") { janet_arity(argc, 0, 1); + (void) argv; /* Prevent unused argument warning */ #ifdef JANET_WINDOWS SYSTEM_INFO info; GetSystemInfo(&info); @@ -329,7 +330,7 @@ JANET_CORE_FN(os_cpu_count, } return janet_wrap_integer(result); #elif defined(JANET_PLAN9) - return janet_wrap_integer(atoi(getenv("NPROC"))); + return janet_wrap_integer(atoi(getenv("NPROC"))); #else return argc > 0 ? argv[0] : janet_wrap_nil(); #endif @@ -1341,7 +1342,7 @@ static Janet os_execute_impl(int32_t argc, Janet *argv, JanetExecuteMode mode) { if (mode == JANET_EXECUTE_EXEC) { int status; #ifdef JANET_PLAN9 - status = exec(cargv[0], cargv); + status = exec(cargv[0], cargv); #else if (!use_environ) { environ = envp; @@ -1535,7 +1536,7 @@ JANET_CORE_FN(os_posix_fork, #else pid_t result; #ifdef JANET_PLAN9 - result = fork(); + result = fork(); #else do { result = fork(); @@ -1845,7 +1846,7 @@ static struct tm *time_to_tm(const Janet *argv, int32_t argc, int32_t n, struct localtime_s(t_infos, &t); t_info = t_infos; #elif defined(JANET_PLAN9) - t_info = localtime(&t); + t_info = localtime(&t); #else tzset(); t_info = localtime_r(&t, t_infos); @@ -1856,7 +1857,7 @@ static struct tm *time_to_tm(const Janet *argv, int32_t argc, int32_t n, struct gmtime_s(t_infos, &t); t_info = t_infos; #elif defined(JANET_PLAN9) - t_info = gmtime(&t); + t_info = gmtime(&t); #else t_info = gmtime_r(&t, t_infos); #endif @@ -2138,7 +2139,7 @@ JANET_CORE_FN(os_rmdir, #ifdef JANET_WINDOWS int res = _rmdir(path); #elif defined(JANET_PLAN9) - int res = remove(path); + int res = remove(path); #else int res = rmdir(path); #endif @@ -2437,8 +2438,8 @@ static Janet os_stat_or_lstat(int do_lstat, int32_t argc, Janet *argv) { (void) do_lstat; int res = _stat(path, &st); #elif defined(JANET_PLAN9) - (void)do_lstat; - int res = stat(path, &st); + (void)do_lstat; + int res = stat(path, &st); #else int res; if (do_lstat) { diff --git a/src/core/pp.c b/src/core/pp.c index 80f88dc6..2e66e5aa 100644 --- a/src/core/pp.c +++ b/src/core/pp.c @@ -1024,17 +1024,18 @@ void janet_buffer_format( char width[3], precision[3]; int nb = 0; /* number of bytes in added item */ #ifdef JANET_PLAN9 - if(*strfrmt == 'r'){ - rerrstr(item, MAX_ITEM); - nb = strlen(item); - } else + if (*strfrmt == 'r') { + rerrstr(item, MAX_ITEM); + nb = strlen(item); + } else #endif - if (++arg >= argc) - janet_panic("not enough values for format"); + if (++arg >= argc) + janet_panic("not enough values for format"); strfrmt = scanformat(strfrmt, form, width, precision); switch (*strfrmt++) { #ifdef JANET_PLAN9 - case 'r': break; + case 'r': + break; #endif case 'c': { nb = snprintf(item, MAX_ITEM, form, (int) diff --git a/src/core/util.c b/src/core/util.c index d57dfeb3..b945b1fb 100644 --- a/src/core/util.c +++ b/src/core/util.c @@ -849,7 +849,7 @@ int janet_checksize(Janet x) { double dval = janet_unwrap_number(x); if (dval != (double)((size_t) dval)) return 0; #ifdef JANET_PLAN9 - return dval <= SIZE_MAX; + return dval <= SIZE_MAX; #else if (SIZE_MAX > JANET_INTMAX_INT64) { return dval <= JANET_INTMAX_INT64; diff --git a/src/mainclient/shell.c b/src/mainclient/shell.c index ccf072da..faa27133 100644 --- a/src/mainclient/shell.c +++ b/src/mainclient/shell.c @@ -1136,7 +1136,7 @@ int main(int argc, char **argv) { JanetTable *env; #ifdef JANET_PLAN9 - setfcr(0); + setfcr(0); #endif #ifdef _WIN32 From 2544c4ae1a64abc1f9520408268bf7dbd94ace9c Mon Sep 17 00:00:00 2001 From: Calvin Rose Date: Sat, 13 Dec 2025 06:20:50 -0600 Subject: [PATCH 070/307] Add os/cpu-count get defaults. --- CHANGELOG.md | 1 + src/core/os.c | 6 +++--- 2 files changed, 4 insertions(+), 3 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 7c621948..950a3aee 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -7,6 +7,7 @@ All notable changes to this project will be documented in this file. - The `repl` function now respects the `*repl-prompt*` dynamic binding by default. - Allow matching exact lengths of datastructures with the `match` macro using a dollar suffix. - Add initial support for Plan 9. Some modules (e.g. ev) are not yet enabled. +- Allow specifying `:flycheck` for individual defines to annotate that they are safe to evaluate for flychecking. ## 1.40.1 - 2025-11-16 - Fix `JANET_REDUCED_OS` build regression caused by `os/posix-chroot`. diff --git a/src/core/os.c b/src/core/os.c index 7b989c35..ad42a09d 100644 --- a/src/core/os.c +++ b/src/core/os.c @@ -312,7 +312,7 @@ JANET_CORE_FN(os_cpu_count, int result = 0; size_t len = sizeof(int); if (-1 == sysctl(name, 2, &result, &len, NULL, 0)) { - return dflt; + return argc > 0 ? argv[0] : janet_wrap_nil(); } return janet_wrap_integer(result); #elif defined(JANET_BSD) && defined(HW_NCPU) @@ -320,13 +320,13 @@ JANET_CORE_FN(os_cpu_count, int result = 0; size_t len = sizeof(int); if (-1 == sysctl(name, 2, &result, &len, NULL, 0)) { - return dflt; + return argc > 0 ? argv[0] : janet_wrap_nil(); } return janet_wrap_integer(result); #elif defined(JANET_ILLUMOS) long result = sysconf(_SC_NPROCESSORS_CONF); if (result < 0) { - return dflt; + return argc > 0 ? argv[0] : janet_wrap_nil(); } return janet_wrap_integer(result); #elif defined(JANET_PLAN9) From 978c4e8b69a1cde2c449e477de35a497dd0cc506 Mon Sep 17 00:00:00 2001 From: llmII Date: Sat, 13 Dec 2025 16:56:56 -0600 Subject: [PATCH 071/307] Fix unix sockets issue on FreeBSD Sometimes a unix socket has a 0 status return which indicates the connection immediately succeeded, at which point entering the event loop waiting on the connection to complete actually breaks things. It seems on FreeBSD with events being edge triggered, we're awaiting a connection to signal it's writeable (to complete the connection) but that never occurs (the event already took place before we registered for the event). Going by status alone to determine if we should enter into the event loop to await the complete connection seems sensible here. --- src/core/net.c | 10 +++++++++- 1 file changed, 9 insertions(+), 1 deletion(-) diff --git a/src/core/net.c b/src/core/net.c index d6c2240c..f40352e4 100644 --- a/src/core/net.c +++ b/src/core/net.c @@ -572,7 +572,15 @@ JANET_CORE_FN(cfun_net_connect, } #endif - if (status) { + if (status == 0) { + /* Connect completed synchronously (common for unix domain sockets). + * Return the stream directly without scheduling an async wait, + * as edge-triggered kqueue may not signal EVFILT_WRITE if the socket + * is already connected when registered. */ + return janet_wrap_abstract(stream); + } + + if (status == -1) { #ifdef JANET_WINDOWS if (err != WSAEWOULDBLOCK) { #else From 9bc47e766ef27003eb4b1d2d6d2f974bcceed00c Mon Sep 17 00:00:00 2001 From: sogaiu <983021772@users.noreply.github.com> Date: Sun, 14 Dec 2025 13:41:00 +0900 Subject: [PATCH 072/307] Fix math/int-{max,min} docstrings --- src/core/math.c | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/core/math.c b/src/core/math.c index 4aa22a9f..b809dc11 100644 --- a/src/core/math.c +++ b/src/core/math.c @@ -441,9 +441,9 @@ void janet_lib_math(JanetTable *env) { JANET_CORE_DEF(env, "math/int32-max", janet_wrap_number(INT32_MAX), "The maximum contiguous integer representable by a 32 bit signed integer"); JANET_CORE_DEF(env, "math/int-min", janet_wrap_number(JANET_INTMIN_DOUBLE), - "The minimum contiguous integer representable by a double (2^53)"); + "The minimum contiguous integer representable by a double (-(2^53))"); JANET_CORE_DEF(env, "math/int-max", janet_wrap_number(JANET_INTMAX_DOUBLE), - "The maximum contiguous integer representable by a double (-(2^53))"); + "The maximum contiguous integer representable by a double (2^53)"); #ifdef NAN JANET_CORE_DEF(env, "math/nan", janet_wrap_number(NAN), "Not a number (IEEE-754 NaN)"); #else From bb3cf7d83b8deadb495903d98b9b58aeb1466328 Mon Sep 17 00:00:00 2001 From: Calvin Rose Date: Sun, 14 Dec 2025 11:46:57 -0600 Subject: [PATCH 073/307] Add missing uinteger janet_opt* funcitons. --- CHANGELOG.md | 1 + src/core/capi.c | 12 ++++++++++++ src/include/janet.h | 2 ++ 3 files changed, 15 insertions(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index 950a3aee..67bab0ec 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -2,6 +2,7 @@ All notable changes to this project will be documented in this file. ## Unreleased - ??? +- Add `janet_optuinteger` and `janet_optuinteger64` to the C API. - Add `cms` combinator to PEGs. - Add `thaw-keep-keys` as a variant of thaw - The `repl` function now respects the `*repl-prompt*` dynamic binding by default. diff --git a/src/core/capi.c b/src/core/capi.c index 8fc55c9e..5babc312 100644 --- a/src/core/capi.c +++ b/src/core/capi.c @@ -557,6 +557,18 @@ void *janet_optabstract(const Janet *argv, int32_t argc, int32_t n, const JanetA return janet_getabstract(argv, n, at); } +uint32_t janet_optuinteger(const Janet *argv, int32_t argc, int32_t n, uint32_t dflt) { + if (argc <= n) return dflt; + if (janet_checktype(argv[n], JANET_NIL)) return dflt; + return janet_getuinteger(argv, n); +} + +uint64_t janet_optuinteger64(const Janet *argv, int32_t argc, int32_t n, uint64_t dflt) { + if (argc <= n) return dflt; + if (janet_checktype(argv[n], JANET_NIL)) return dflt; + return janet_getuinteger64(argv, n); +} + /* Atomic refcounts */ JanetAtomicInt janet_atomic_inc(JanetAtomicInt volatile *x) { diff --git a/src/include/janet.h b/src/include/janet.h index 9879db64..c44c3d3a 100644 --- a/src/include/janet.h +++ b/src/include/janet.h @@ -2117,6 +2117,8 @@ JANET_API int32_t janet_optinteger(const Janet *argv, int32_t argc, int32_t n, i JANET_API int64_t janet_optinteger64(const Janet *argv, int32_t argc, int32_t n, int64_t dflt); JANET_API size_t janet_optsize(const Janet *argv, int32_t argc, int32_t n, size_t dflt); JANET_API JanetAbstract janet_optabstract(const Janet *argv, int32_t argc, int32_t n, const JanetAbstractType *at, JanetAbstract dflt); +JANET_API uint32_t janet_optuinteger(const Janet *argv, int32_t argc, int32_t n, uint32_t dflt); +JANET_API uint64_t janet_optuinteger64(const Janet *argv, int32_t argc, int32_t n, uint64_t dflt); /* Mutable optional types specify a size default, and construct a new value if none is provided */ JANET_API JanetBuffer *janet_optbuffer(const Janet *argv, int32_t argc, int32_t n, int32_t dflt_len); From 9b1194b08a73c41f267595a9e4db5a0862d067d9 Mon Sep 17 00:00:00 2001 From: Michael Camilleri Date: Mon, 15 Dec 2025 10:00:03 +0900 Subject: [PATCH 074/307] Use double-NULL byte for empty process environments on Windows --- src/core/os.c | 2 ++ test/suite-os.janet | 4 ++++ 2 files changed, 6 insertions(+) diff --git a/src/core/os.c b/src/core/os.c index ad42a09d..d621e6ab 100644 --- a/src/core/os.c +++ b/src/core/os.c @@ -362,6 +362,8 @@ static EnvBlock os_execute_env(int32_t argc, const Janet *argv) { janet_buffer_push_bytes(temp, vals, janet_string_length(vals)); janet_buffer_push_u8(temp, '\0'); } + /* Windows environment blocks must be double-NULL terminated */ + if (temp->count == 0) janet_buffer_push_u8(temp, '\0'); janet_buffer_push_u8(temp, '\0'); char *ret = janet_smalloc(temp->count); memcpy(ret, temp->data, temp->count); diff --git a/test/suite-os.janet b/test/suite-os.janet index 40e62bfd..2151d880 100644 --- a/test/suite-os.janet +++ b/test/suite-os.janet @@ -143,6 +143,10 @@ (merge (os/environ) {"HELLO" "WORLD"}))) "os/execute with env") +# os/execute with empty environment +(assert (= 0 (os/execute [;run janet "-e" "(+ 1 2 3)"] :pe {})) + "os/execute with empty env") + # os/execute regressions # 427f7c362 (for i 0 10 From 1e73fa7ec7bef048bb8da838190df36ad2c4cfbf Mon Sep 17 00:00:00 2001 From: Michael Camilleri Date: Mon, 15 Dec 2025 11:27:46 +0900 Subject: [PATCH 075/307] Add special case for expected error on MinGW --- test/suite-os.janet | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/test/suite-os.janet b/test/suite-os.janet index 2151d880..afb00d28 100644 --- a/test/suite-os.janet +++ b/test/suite-os.janet @@ -144,8 +144,10 @@ "os/execute with env") # os/execute with empty environment -(assert (= 0 (os/execute [;run janet "-e" "(+ 1 2 3)"] :pe {})) - "os/execute with empty env") +# pr #1686 +(assert (= (if (= :mingw (os/which)) -1073741515 0) # MinGW can't find DLLs + (os/execute [;run janet "-e" "(+ 1 2 3)"] :pe {})) + "os/execute with minimal env") # os/execute regressions # 427f7c362 From 7810724ed2f4141f9b6fd5eeac17691324ee6abd Mon Sep 17 00:00:00 2001 From: Michael Camilleri Date: Mon, 15 Dec 2025 12:04:14 +0900 Subject: [PATCH 076/307] Exclude MinGW on WINE from special case --- test/suite-os.janet | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/test/suite-os.janet b/test/suite-os.janet index afb00d28..c16e7714 100644 --- a/test/suite-os.janet +++ b/test/suite-os.janet @@ -145,7 +145,10 @@ # os/execute with empty environment # pr #1686 -(assert (= (if (= :mingw (os/which)) -1073741515 0) # MinGW can't find DLLs +# native MinGW can't find system DLLs without PATH and so fails +(assert (= (if (and (= :mingw (os/which)) + (nil? (os/stat "C:\\windows\\system32\\wineboot.exe"))) + -1073741515 0) (os/execute [;run janet "-e" "(+ 1 2 3)"] :pe {})) "os/execute with minimal env") From d5a56caa33a4cd62ba17d8e7627a03993016aea1 Mon Sep 17 00:00:00 2001 From: Calvin Rose Date: Mon, 22 Dec 2025 07:38:57 -0600 Subject: [PATCH 077/307] Add fix for issue #1687 - destrucutre + splice An optimization to ellude creation of intermediate tuples was erroneously flagging a splice as invalid, even though it was valid. Instead, if we see splice on the rhs, bail out of the optimization. --- src/core/filewatch.c | 4 ++-- src/core/specials.c | 19 ++++++++++++++++++- test/suite-corelib.janet | 5 +++++ 3 files changed, 25 insertions(+), 3 deletions(-) diff --git a/src/core/filewatch.c b/src/core/filewatch.c index 104ac832..422b08b1 100644 --- a/src/core/filewatch.c +++ b/src/core/filewatch.c @@ -573,7 +573,7 @@ static const JanetAbstractType janet_filewatch_at = { }; JANET_CORE_FN(cfun_filewatch_make, - "(filewatch/new channel &opt default-flags)", + "(filewatch/new channel & default-flags)", "Create a new filewatcher that will give events to a channel channel. See `filewatch/add` for available flags.\n\n" "When an event is triggered by the filewatcher, a struct containing information will be given to channel as with `ev/give`. " "The contents of the channel depend on the OS, but will contain some common keys:\n\n" @@ -597,7 +597,7 @@ JANET_CORE_FN(cfun_filewatch_make, } JANET_CORE_FN(cfun_filewatch_add, - "(filewatch/add watcher path &opt flags)", + "(filewatch/add watcher path flag & more-flags)", "Add a path to the watcher. Available flags depend on the current OS, and are as follows:\n\n" "Windows/MINGW (flags correspond to `FILE_NOTIFY_CHANGE_*` flags in win32 documentation):\n\n" "* `:all` - trigger an event for all of the below triggers.\n\n" diff --git a/src/core/specials.c b/src/core/specials.c index 1a17257e..eb68016a 100644 --- a/src/core/specials.c +++ b/src/core/specials.c @@ -362,6 +362,23 @@ SlotHeadPair *dohead_destructure(JanetCompiler *c, SlotHeadPair *into, JanetFopt janet_indexed_view(lhs, &view_lhs.items, &view_lhs.len); janet_indexed_view(rhs, &view_rhs.items, &view_rhs.len); int found_amp = 0; + int found_splice = 0; + /* Check for (def [x y z] [(splice [1 2 3]) 4 5 6]), bail out of optimization */ + for (int32_t i = 0; i < view_rhs.len; i++) { + if (!janet_checktype(view_rhs.items[i], JANET_TUPLE)) { + continue; + } + JanetTuple tup = janet_unwrap_tuple(view_rhs.items[i]); + if (janet_tuple_length(tup) == 0) { + continue; + } + if (janet_symeq(tup[0], "splice")) { + found_splice = 1; + /* Good error will be generated later. */ + break; + } + } + /* Check for (def [x & more] [1 2 3]), bail out of optimization */ for (int32_t i = 0; i < view_lhs.len; i++) { if (janet_symeq(view_lhs.items[i], "&")) { found_amp = 1; @@ -369,7 +386,7 @@ SlotHeadPair *dohead_destructure(JanetCompiler *c, SlotHeadPair *into, JanetFopt break; } } - if (!found_amp) { + if (!found_amp && !found_splice) { for (int32_t i = 0; i < view_lhs.len; i++) { Janet sub_rhs = view_rhs.len <= i ? janet_wrap_nil() : view_rhs.items[i]; into = dohead_destructure(c, into, subopts, view_lhs.items[i], sub_rhs); diff --git a/test/suite-corelib.janet b/test/suite-corelib.janet index b1fddd56..c8f9a1bf 100644 --- a/test/suite-corelib.janet +++ b/test/suite-corelib.janet @@ -195,5 +195,10 @@ (assert (= :yes (match [1 2 3] [x y z w] :no1 [x y $] :no2 [x y z] :yes)) "match dollar suffix 1") (assert (= :yes (match [1 2 3] [x y z w] :no1 [x y z $] :yes [x y z] :no2)) "match dollar suffix 2") +# Issue #1687 +(assert-no-error "def destructure splice works 1" (do (def [a] [;[1]]) a)) +(assert-no-error "def destructure splice works 2" (do (def (n) [(splice [])]) n)) +(assert-no-error "var destructure splice works" (do (var [a] [;[1]]) a)) + (end-suite) From 6e4b0b92592860493c89243c2f0ff2c97a8784dc Mon Sep 17 00:00:00 2001 From: Michael Camilleri Date: Fri, 2 Jan 2026 14:14:53 +0900 Subject: [PATCH 078/307] Update year in copyright disclaimer to 2026 --- LICENSE | 2 +- Makefile | 2 +- meson.build | 2 +- src/boot/array_test.c | 2 +- src/boot/boot.c | 2 +- src/boot/buffer_test.c | 2 +- src/boot/number_test.c | 2 +- src/boot/system_test.c | 2 +- src/boot/table_test.c | 2 +- src/core/abstract.c | 2 +- src/core/array.c | 2 +- src/core/asm.c | 2 +- src/core/buffer.c | 2 +- src/core/bytecode.c | 2 +- src/core/capi.c | 2 +- src/core/cfuns.c | 2 +- src/core/compile.c | 2 +- src/core/compile.h | 2 +- src/core/corelib.c | 2 +- src/core/debug.c | 2 +- src/core/emit.c | 2 +- src/core/emit.h | 2 +- src/core/ev.c | 2 +- src/core/features.h | 2 +- src/core/ffi.c | 2 +- src/core/fiber.c | 2 +- src/core/fiber.h | 2 +- src/core/filewatch.c | 2 +- src/core/gc.c | 2 +- src/core/gc.h | 2 +- src/core/inttypes.c | 2 +- src/core/io.c | 2 +- src/core/marsh.c | 2 +- src/core/math.c | 2 +- src/core/net.c | 2 +- src/core/os.c | 2 +- src/core/parse.c | 2 +- src/core/peg.c | 2 +- src/core/pp.c | 2 +- src/core/regalloc.c | 2 +- src/core/regalloc.h | 2 +- src/core/run.c | 2 +- src/core/specials.c | 2 +- src/core/state.c | 2 +- src/core/state.h | 2 +- src/core/string.c | 2 +- src/core/strtod.c | 2 +- src/core/struct.c | 2 +- src/core/symcache.c | 2 +- src/core/symcache.h | 2 +- src/core/table.c | 2 +- src/core/tuple.c | 2 +- src/core/util.c | 2 +- src/core/util.h | 2 +- src/core/value.c | 2 +- src/core/vector.c | 2 +- src/core/vector.h | 2 +- src/core/vm.c | 2 +- src/core/wrap.c | 2 +- src/include/janet.h | 2 +- src/mainclient/shell.c | 2 +- test/amalg/main.c | 2 +- test/suite-array.janet | 2 +- test/suite-asm.janet | 2 +- test/suite-boot.janet | 2 +- test/suite-buffer.janet | 2 +- test/suite-bundle.janet | 2 +- test/suite-capi.janet | 2 +- test/suite-cfuns.janet | 2 +- test/suite-compile.janet | 2 +- test/suite-corelib.janet | 2 +- test/suite-debug.janet | 2 +- test/suite-ev.janet | 2 +- test/suite-ev2.janet | 2 +- test/suite-ffi.janet | 2 +- test/suite-filewatch.janet | 2 +- test/suite-inttypes.janet | 2 +- test/suite-io.janet | 2 +- test/suite-marsh.janet | 2 +- test/suite-math.janet | 2 +- test/suite-os.janet | 2 +- test/suite-parse.janet | 2 +- test/suite-peg.janet | 2 +- test/suite-pp.janet | 2 +- test/suite-specials.janet | 2 +- test/suite-string.janet | 2 +- test/suite-strtod.janet | 2 +- test/suite-struct.janet | 2 +- test/suite-symcache.janet | 2 +- test/suite-table.janet | 2 +- test/suite-tuple.janet | 2 +- test/suite-unknown.janet | 2 +- test/suite-value.janet | 2 +- test/suite-vm.janet | 2 +- tools/symcharsgen.c | 2 +- 95 files changed, 95 insertions(+), 95 deletions(-) diff --git a/LICENSE b/LICENSE index ceaa6a0e..3a254f29 100644 --- a/LICENSE +++ b/LICENSE @@ -1,4 +1,4 @@ -Copyright (c) 2025 Calvin Rose and contributors +Copyright (c) 2026 Calvin Rose and contributors 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 diff --git a/Makefile b/Makefile index 90bbca1e..81b862e6 100644 --- a/Makefile +++ b/Makefile @@ -1,4 +1,4 @@ -# Copyright (c) 2025 Calvin Rose +# Copyright (c) 2026 Calvin Rose # # Permission is hereby granted, free of charge, to any person obtaining a copy # of this software and associated documentation files (the "Software"), to diff --git a/meson.build b/meson.build index e81251a4..3b6b7de3 100644 --- a/meson.build +++ b/meson.build @@ -1,4 +1,4 @@ -# Copyright (c) 2025 Calvin Rose and contributors +# Copyright (c) 2026 Calvin Rose and contributors # # Permission is hereby granted, free of charge, to any person obtaining a copy # of this software and associated documentation files (the "Software"), to diff --git a/src/boot/array_test.c b/src/boot/array_test.c index e1b545cc..db68f180 100644 --- a/src/boot/array_test.c +++ b/src/boot/array_test.c @@ -1,5 +1,5 @@ /* -* Copyright (c) 2025 Calvin Rose +* Copyright (c) 2026 Calvin Rose * * Permission is hereby granted, free of charge, to any person obtaining a copy * of this software and associated documentation files (the "Software"), to diff --git a/src/boot/boot.c b/src/boot/boot.c index df1b180f..3a3c502b 100644 --- a/src/boot/boot.c +++ b/src/boot/boot.c @@ -1,5 +1,5 @@ /* -* Copyright (c) 2025 Calvin Rose +* Copyright (c) 2026 Calvin Rose * * Permission is hereby granted, free of charge, to any person obtaining a copy * of this software and associated documentation files (the "Software"), to diff --git a/src/boot/buffer_test.c b/src/boot/buffer_test.c index 0698e3c9..03800f45 100644 --- a/src/boot/buffer_test.c +++ b/src/boot/buffer_test.c @@ -1,5 +1,5 @@ /* -* Copyright (c) 2025 Calvin Rose +* Copyright (c) 2026 Calvin Rose * * Permission is hereby granted, free of charge, to any person obtaining a copy * of this software and associated documentation files (the "Software"), to diff --git a/src/boot/number_test.c b/src/boot/number_test.c index 4d783008..dbcb07b9 100644 --- a/src/boot/number_test.c +++ b/src/boot/number_test.c @@ -1,5 +1,5 @@ /* -* Copyright (c) 2025 Calvin Rose +* Copyright (c) 2026 Calvin Rose * * Permission is hereby granted, free of charge, to any person obtaining a copy * of this software and associated documentation files (the "Software"), to diff --git a/src/boot/system_test.c b/src/boot/system_test.c index 397d9ff8..d0fe41ee 100644 --- a/src/boot/system_test.c +++ b/src/boot/system_test.c @@ -1,5 +1,5 @@ /* -* Copyright (c) 2025 Calvin Rose +* Copyright (c) 2026 Calvin Rose * * Permission is hereby granted, free of charge, to any person obtaining a copy * of this software and associated documentation files (the "Software"), to diff --git a/src/boot/table_test.c b/src/boot/table_test.c index 1b07a577..761702f6 100644 --- a/src/boot/table_test.c +++ b/src/boot/table_test.c @@ -1,5 +1,5 @@ /* -* Copyright (c) 2025 Calvin Rose +* Copyright (c) 2026 Calvin Rose * * Permission is hereby granted, free of charge, to any person obtaining a copy * of this software and associated documentation files (the "Software"), to diff --git a/src/core/abstract.c b/src/core/abstract.c index 44f98b60..94d69b98 100644 --- a/src/core/abstract.c +++ b/src/core/abstract.c @@ -1,5 +1,5 @@ /* -* Copyright (c) 2025 Calvin Rose +* Copyright (c) 2026 Calvin Rose * * Permission is hereby granted, free of charge, to any person obtaining a copy * of this software and associated documentation files (the "Software"), to diff --git a/src/core/array.c b/src/core/array.c index bda15159..43bec654 100644 --- a/src/core/array.c +++ b/src/core/array.c @@ -1,5 +1,5 @@ /* -* Copyright (c) 2025 Calvin Rose +* Copyright (c) 2026 Calvin Rose * * Permission is hereby granted, free of charge, to any person obtaining a copy * of this software and associated documentation files (the "Software"), to diff --git a/src/core/asm.c b/src/core/asm.c index 4f54623a..287838e3 100644 --- a/src/core/asm.c +++ b/src/core/asm.c @@ -1,5 +1,5 @@ /* -* Copyright (c) 2025 Calvin Rose +* Copyright (c) 2026 Calvin Rose * * Permission is hereby granted, free of charge, to any person obtaining a copy * of this software and associated documentation files (the "Software"), to diff --git a/src/core/buffer.c b/src/core/buffer.c index 06e3f9f9..c5745b2f 100644 --- a/src/core/buffer.c +++ b/src/core/buffer.c @@ -1,5 +1,5 @@ /* -* Copyright (c) 2025 Calvin Rose +* Copyright (c) 2026 Calvin Rose * * Permission is hereby granted, free of charge, to any person obtaining a copy * of this software and associated documentation files (the "Software"), to diff --git a/src/core/bytecode.c b/src/core/bytecode.c index 7f1acdac..ddc198e6 100644 --- a/src/core/bytecode.c +++ b/src/core/bytecode.c @@ -1,5 +1,5 @@ /* -* Copyright (c) 2025 Calvin Rose +* Copyright (c) 2026 Calvin Rose * * Permission is hereby granted, free of charge, to any person obtaining a copy * of this software and associated documentation files (the "Software"), to diff --git a/src/core/capi.c b/src/core/capi.c index 5babc312..526e58a8 100644 --- a/src/core/capi.c +++ b/src/core/capi.c @@ -1,5 +1,5 @@ /* -* Copyright (c) 2025 Calvin Rose +* Copyright (c) 2026 Calvin Rose * * Permission is hereby granted, free of charge, to any person obtaining a copy * of this software and associated documentation files (the "Software"), to diff --git a/src/core/cfuns.c b/src/core/cfuns.c index 960bd502..d8f917c6 100644 --- a/src/core/cfuns.c +++ b/src/core/cfuns.c @@ -1,5 +1,5 @@ /* -* Copyright (c) 2025 Calvin Rose +* Copyright (c) 2026 Calvin Rose * * Permission is hereby granted, free of charge, to any person obtaining a copy * of this software and associated documentation files (the "Software"), to diff --git a/src/core/compile.c b/src/core/compile.c index 8ab96c40..6e92b47d 100644 --- a/src/core/compile.c +++ b/src/core/compile.c @@ -1,5 +1,5 @@ /* -* Copyright (c) 2025 Calvin Rose +* Copyright (c) 2026 Calvin Rose * * Permission is hereby granted, free of charge, to any person obtaining a copy * of this software and associated documentation files (the "Software"), to diff --git a/src/core/compile.h b/src/core/compile.h index 765efe24..5a68df33 100644 --- a/src/core/compile.h +++ b/src/core/compile.h @@ -1,5 +1,5 @@ /* -* Copyright (c) 2025 Calvin Rose +* Copyright (c) 2026 Calvin Rose * * Permission is hereby granted, free of charge, to any person obtaining a copy * of this software and associated documentation files (the "Software"), to diff --git a/src/core/corelib.c b/src/core/corelib.c index bbfeec55..75094612 100644 --- a/src/core/corelib.c +++ b/src/core/corelib.c @@ -1,5 +1,5 @@ /* -* Copyright (c) 2025 Calvin Rose +* Copyright (c) 2026 Calvin Rose * * Permission is hereby granted, free of charge, to any person obtaining a copy * of this software and associated documentation files (the "Software"), to diff --git a/src/core/debug.c b/src/core/debug.c index 6e03f78d..dc36961e 100644 --- a/src/core/debug.c +++ b/src/core/debug.c @@ -1,5 +1,5 @@ /* -* Copyright (c) 2025 Calvin Rose +* Copyright (c) 2026 Calvin Rose * * Permission is hereby granted, free of charge, to any person obtaining a copy * of this software and associated documentation files (the "Software"), to diff --git a/src/core/emit.c b/src/core/emit.c index 47127800..1a4c74bb 100644 --- a/src/core/emit.c +++ b/src/core/emit.c @@ -1,5 +1,5 @@ /* -* Copyright (c) 2025 Calvin Rose +* Copyright (c) 2026 Calvin Rose * * Permission is hereby granted, free of charge, to any person obtaining a copy * of this software and associated documentation files (the "Software"), to diff --git a/src/core/emit.h b/src/core/emit.h index 64a6a268..4e343198 100644 --- a/src/core/emit.h +++ b/src/core/emit.h @@ -1,5 +1,5 @@ /* -* Copyright (c) 2025 Calvin Rose +* Copyright (c) 2026 Calvin Rose * * Permission is hereby granted, free of charge, to any person obtaining a copy * of this software and associated documentation files (the "Software"), to diff --git a/src/core/ev.c b/src/core/ev.c index 2adf80ed..7ad1f6f1 100644 --- a/src/core/ev.c +++ b/src/core/ev.c @@ -1,5 +1,5 @@ /* -* Copyright (c) 2025 Calvin Rose +* Copyright (c) 2026 Calvin Rose * * Permission is hereby granted, free of charge, to any person obtaining a copy * of this software and associated documentation files (the "Software"), to diff --git a/src/core/features.h b/src/core/features.h index d799880a..61032004 100644 --- a/src/core/features.h +++ b/src/core/features.h @@ -1,5 +1,5 @@ /* -* Copyright (c) 2025 Calvin Rose +* Copyright (c) 2026 Calvin Rose * * Permission is hereby granted, free of charge, to any person obtaining a copy * of this software and associated documentation files (the "Software"), to diff --git a/src/core/ffi.c b/src/core/ffi.c index bc424400..01024d53 100644 --- a/src/core/ffi.c +++ b/src/core/ffi.c @@ -1,5 +1,5 @@ /* -* Copyright (c) 2025 Calvin Rose +* Copyright (c) 2026 Calvin Rose * * Permission is hereby granted, free of charge, to any person obtaining a copy * of this software and associated documentation files (the "Software"), to diff --git a/src/core/fiber.c b/src/core/fiber.c index 72d8f2de..b711874f 100644 --- a/src/core/fiber.c +++ b/src/core/fiber.c @@ -1,5 +1,5 @@ /* -* Copyright (c) 2025 Calvin Rose +* Copyright (c) 2026 Calvin Rose * * Permission is hereby granted, free of charge, to any person obtaining a copy * of this software and associated documentation files (the "Software"), to diff --git a/src/core/fiber.h b/src/core/fiber.h index 8626e89c..064e09c0 100644 --- a/src/core/fiber.h +++ b/src/core/fiber.h @@ -1,5 +1,5 @@ /* -* Copyright (c) 2025 Calvin Rose +* Copyright (c) 2026 Calvin Rose * * Permission is hereby granted, free of charge, to any person obtaining a copy * of this software and associated documentation files (the "Software"), to diff --git a/src/core/filewatch.c b/src/core/filewatch.c index 422b08b1..91d24801 100644 --- a/src/core/filewatch.c +++ b/src/core/filewatch.c @@ -1,5 +1,5 @@ /* -* Copyright (c) 2025 Calvin Rose +* Copyright (c) 2026 Calvin Rose * * Permission is hereby granted, free of charge, to any person obtaining a copy * of this software and associated documentation files (the "Software"), to diff --git a/src/core/gc.c b/src/core/gc.c index aad993ad..532a9cc6 100644 --- a/src/core/gc.c +++ b/src/core/gc.c @@ -1,5 +1,5 @@ /* -* Copyright (c) 2025 Calvin Rose +* Copyright (c) 2026 Calvin Rose * * Permission is hereby granted, free of charge, to any person obtaining a copy * of this software and associated documentation files (the "Software"), to diff --git a/src/core/gc.h b/src/core/gc.h index 37318eee..89d00302 100644 --- a/src/core/gc.h +++ b/src/core/gc.h @@ -1,5 +1,5 @@ /* -* Copyright (c) 2025 Calvin Rose +* Copyright (c) 2026 Calvin Rose * * Permission is hereby granted, free of charge, to any person obtaining a copy * of this software and associated documentation files (the "Software"), to diff --git a/src/core/inttypes.c b/src/core/inttypes.c index db0d2b28..fbf4fedd 100644 --- a/src/core/inttypes.c +++ b/src/core/inttypes.c @@ -1,5 +1,5 @@ /* -* Copyright (c) 2025 Calvin Rose & contributors +* Copyright (c) 2026 Calvin Rose & contributors * * Permission is hereby granted, free of charge, to any person obtaining a copy * of this software and associated documentation files (the "Software"), to diff --git a/src/core/io.c b/src/core/io.c index 26d47652..b7ff524f 100644 --- a/src/core/io.c +++ b/src/core/io.c @@ -1,5 +1,5 @@ /* -* Copyright (c) 2025 Calvin Rose +* Copyright (c) 2026 Calvin Rose * * Permission is hereby granted, free of charge, to any person obtaining a copy * of this software and associated documentation files (the "Software"), to diff --git a/src/core/marsh.c b/src/core/marsh.c index 13b3344b..f66cb6a4 100644 --- a/src/core/marsh.c +++ b/src/core/marsh.c @@ -1,5 +1,5 @@ /* -* Copyright (c) 2025 Calvin Rose +* Copyright (c) 2026 Calvin Rose * * Permission is hereby granted, free of charge, to any person obtaining a copy * of this software and associated documentation files (the "Software"), to diff --git a/src/core/math.c b/src/core/math.c index b809dc11..4d0d6a53 100644 --- a/src/core/math.c +++ b/src/core/math.c @@ -1,5 +1,5 @@ /* -* Copyright (c) 2025 Calvin Rose +* Copyright (c) 2026 Calvin Rose * * Permission is hereby granted, free of charge, to any person obtaining a copy * of this software and associated documentation files (the "Software"), to diff --git a/src/core/net.c b/src/core/net.c index f40352e4..55023d91 100644 --- a/src/core/net.c +++ b/src/core/net.c @@ -1,5 +1,5 @@ /* -* Copyright (c) 2025 Calvin Rose and contributors. +* Copyright (c) 2026 Calvin Rose and contributors. * * Permission is hereby granted, free of charge, to any person obtaining a copy * of this software and associated documentation files (the "Software"), to diff --git a/src/core/os.c b/src/core/os.c index d621e6ab..a4aa60d1 100644 --- a/src/core/os.c +++ b/src/core/os.c @@ -1,5 +1,5 @@ /* -* Copyright (c) 2025 Calvin Rose and contributors. +* Copyright (c) 2026 Calvin Rose and contributors. * * Permission is hereby granted, free of charge, to any person obtaining a copy * of this software and associated documentation files (the "Software"), to diff --git a/src/core/parse.c b/src/core/parse.c index a9695220..ff6a5978 100644 --- a/src/core/parse.c +++ b/src/core/parse.c @@ -1,5 +1,5 @@ /* -* Copyright (c) 2025 Calvin Rose +* Copyright (c) 2026 Calvin Rose * * Permission is hereby granted, free of charge, to any person obtaining a copy * of this software and associated documentation files (the "Software"), to diff --git a/src/core/peg.c b/src/core/peg.c index 1b015329..d9624aed 100644 --- a/src/core/peg.c +++ b/src/core/peg.c @@ -1,5 +1,5 @@ /* -* Copyright (c) 2025 Calvin Rose +* Copyright (c) 2026 Calvin Rose * * Permission is hereby granted, free of charge, to any person obtaining a copy * of this software and associated documentation files (the "Software"), to diff --git a/src/core/pp.c b/src/core/pp.c index 2e66e5aa..8c8b3884 100644 --- a/src/core/pp.c +++ b/src/core/pp.c @@ -1,5 +1,5 @@ /* -* Copyright (c) 2025 Calvin Rose +* Copyright (c) 2026 Calvin Rose * * Permission is hereby granted, free of charge, to any person obtaining a copy * of this software and associated documentation files (the "Software"), to diff --git a/src/core/regalloc.c b/src/core/regalloc.c index ef8768eb..2b08b2a4 100644 --- a/src/core/regalloc.c +++ b/src/core/regalloc.c @@ -1,5 +1,5 @@ /* -* Copyright (c) 2025 Calvin Rose +* Copyright (c) 2026 Calvin Rose * * Permission is hereby granted, free of charge, to any person obtaining a copy * of this software and associated documentation files (the "Software"), to diff --git a/src/core/regalloc.h b/src/core/regalloc.h index ca6c833e..7291d9fc 100644 --- a/src/core/regalloc.h +++ b/src/core/regalloc.h @@ -1,5 +1,5 @@ /* -* Copyright (c) 2025 Calvin Rose +* Copyright (c) 2026 Calvin Rose * * Permission is hereby granted, free of charge, to any person obtaining a copy * of this software and associated documentation files (the "Software"), to diff --git a/src/core/run.c b/src/core/run.c index b7302bc2..2846f818 100644 --- a/src/core/run.c +++ b/src/core/run.c @@ -1,5 +1,5 @@ /* -* Copyright (c) 2025 Calvin Rose +* Copyright (c) 2026 Calvin Rose * * Permission is hereby granted, free of charge, to any person obtaining a copy * of this software and associated documentation files (the "Software"), to diff --git a/src/core/specials.c b/src/core/specials.c index eb68016a..35b06966 100644 --- a/src/core/specials.c +++ b/src/core/specials.c @@ -1,5 +1,5 @@ /* -* Copyright (c) 2025 Calvin Rose +* Copyright (c) 2026 Calvin Rose * * Permission is hereby granted, free of charge, to any person obtaining a copy * of this software and associated documentation files (the "Software"), to diff --git a/src/core/state.c b/src/core/state.c index 3d84dcd1..64dcc701 100644 --- a/src/core/state.c +++ b/src/core/state.c @@ -1,5 +1,5 @@ /* -* Copyright (c) 2025 Calvin Rose +* Copyright (c) 2026 Calvin Rose * * Permission is hereby granted, free of charge, to any person obtaining a copy * of this software and associated documentation files (the "Software"), to diff --git a/src/core/state.h b/src/core/state.h index 86cc8a72..a606eb76 100644 --- a/src/core/state.h +++ b/src/core/state.h @@ -1,5 +1,5 @@ /* -* Copyright (c) 2025 Calvin Rose +* Copyright (c) 2026 Calvin Rose * * Permission is hereby granted, free of charge, to any person obtaining a copy * of this software and associated documentation files (the "Software"), to diff --git a/src/core/string.c b/src/core/string.c index 24181cbc..54c25562 100644 --- a/src/core/string.c +++ b/src/core/string.c @@ -1,5 +1,5 @@ /* -* Copyright (c) 2025 Calvin Rose +* Copyright (c) 2026 Calvin Rose * * Permission is hereby granted, free of charge, to any person obtaining a copy * of this software and associated documentation files (the "Software"), to diff --git a/src/core/strtod.c b/src/core/strtod.c index 3873a6fd..5c59fa10 100644 --- a/src/core/strtod.c +++ b/src/core/strtod.c @@ -1,5 +1,5 @@ /* -* Copyright (c) 2025 Calvin Rose +* Copyright (c) 2026 Calvin Rose * * Permission is hereby granted, free of charge, to any person obtaining a copy * of this software and associated documentation files (the "Software"), to diff --git a/src/core/struct.c b/src/core/struct.c index 1b1cad7b..6ca09471 100644 --- a/src/core/struct.c +++ b/src/core/struct.c @@ -1,5 +1,5 @@ /* -* Copyright (c) 2025 Calvin Rose +* Copyright (c) 2026 Calvin Rose * * Permission is hereby granted, free of charge, to any person obtaining a copy * of this software and associated documentation files (the "Software"), to diff --git a/src/core/symcache.c b/src/core/symcache.c index 68ddf8bf..c6615be4 100644 --- a/src/core/symcache.c +++ b/src/core/symcache.c @@ -1,5 +1,5 @@ /* -* Copyright (c) 2025 Calvin Rose +* Copyright (c) 2026 Calvin Rose * * Permission is hereby granted, free of charge, to any person obtaining a copy * of this software and associated documentation files (the "Software"), to diff --git a/src/core/symcache.h b/src/core/symcache.h index 5e3fbbe0..a3273be5 100644 --- a/src/core/symcache.h +++ b/src/core/symcache.h @@ -1,5 +1,5 @@ /* -* Copyright (c) 2025 Calvin Rose +* Copyright (c) 2026 Calvin Rose * * Permission is hereby granted, free of charge, to any person obtaining a copy * of this software and associated documentation files (the "Software"), to diff --git a/src/core/table.c b/src/core/table.c index 910ceba2..ffaf7591 100644 --- a/src/core/table.c +++ b/src/core/table.c @@ -1,5 +1,5 @@ /* -* Copyright (c) 2025 Calvin Rose +* Copyright (c) 2026 Calvin Rose * * Permission is hereby granted, free of charge, to any person obtaining a copy * of this software and associated documentation files (the "Software"), to diff --git a/src/core/tuple.c b/src/core/tuple.c index 7c004751..5e09e41a 100644 --- a/src/core/tuple.c +++ b/src/core/tuple.c @@ -1,5 +1,5 @@ /* -* Copyright (c) 2025 Calvin Rose +* Copyright (c) 2026 Calvin Rose * * Permission is hereby granted, free of charge, to any person obtaining a copy * of this software and associated documentation files (the "Software"), to diff --git a/src/core/util.c b/src/core/util.c index b945b1fb..b45df256 100644 --- a/src/core/util.c +++ b/src/core/util.c @@ -1,5 +1,5 @@ /* -* Copyright (c) 2025 Calvin Rose +* Copyright (c) 2026 Calvin Rose * * Permission is hereby granted, free of charge, to any person obtaining a copy * of this software and associated documentation files (the "Software"), to diff --git a/src/core/util.h b/src/core/util.h index be1e2a05..7b550f50 100644 --- a/src/core/util.h +++ b/src/core/util.h @@ -1,5 +1,5 @@ /* -* Copyright (c) 2025 Calvin Rose +* Copyright (c) 2026 Calvin Rose * * Permission is hereby granted, free of charge, to any person obtaining a copy * of this software and associated documentation files (the "Software"), to diff --git a/src/core/value.c b/src/core/value.c index 291baeb2..7f71986b 100644 --- a/src/core/value.c +++ b/src/core/value.c @@ -1,5 +1,5 @@ /* -* Copyright (c) 2025 Calvin Rose +* Copyright (c) 2026 Calvin Rose * * Permission is hereby granted, free of charge, to any person obtaining a copy * of this software and associated documentation files (the "Software"), to diff --git a/src/core/vector.c b/src/core/vector.c index 64f1e780..47182355 100644 --- a/src/core/vector.c +++ b/src/core/vector.c @@ -1,5 +1,5 @@ /* -* Copyright (c) 2025 Calvin Rose +* Copyright (c) 2026 Calvin Rose * * Permission is hereby granted, free of charge, to any person obtaining a copy * of this software and associated documentation files (the "Software"), to diff --git a/src/core/vector.h b/src/core/vector.h index 5aa24c13..4341ee63 100644 --- a/src/core/vector.h +++ b/src/core/vector.h @@ -1,5 +1,5 @@ /* -* Copyright (c) 2025 Calvin Rose +* Copyright (c) 2026 Calvin Rose * * Permission is hereby granted, free of charge, to any person obtaining a copy * of this software and associated documentation files (the "Software"), to diff --git a/src/core/vm.c b/src/core/vm.c index a0dae1c8..0d19aea7 100644 --- a/src/core/vm.c +++ b/src/core/vm.c @@ -1,5 +1,5 @@ /* -* Copyright (c) 2025 Calvin Rose +* Copyright (c) 2026 Calvin Rose * * Permission is hereby granted, free of charge, to any person obtaining a copy * of this software and associated documentation files (the "Software"), to diff --git a/src/core/wrap.c b/src/core/wrap.c index c4fc1d16..2881326d 100644 --- a/src/core/wrap.c +++ b/src/core/wrap.c @@ -1,5 +1,5 @@ /* -* Copyright (c) 2025 Calvin Rose +* Copyright (c) 2026 Calvin Rose * * Permission is hereby granted, free of charge, to any person obtaining a copy * of this software and associated documentation files (the "Software"), to diff --git a/src/include/janet.h b/src/include/janet.h index c44c3d3a..495a78c2 100644 --- a/src/include/janet.h +++ b/src/include/janet.h @@ -1,5 +1,5 @@ /* -* Copyright (c) 2025 Calvin Rose +* Copyright (c) 2026 Calvin Rose * * Permission is hereby granted, free of charge, to any person obtaining a copy * of this software and associated documentation files (the "Software"), to diff --git a/src/mainclient/shell.c b/src/mainclient/shell.c index faa27133..a665acca 100644 --- a/src/mainclient/shell.c +++ b/src/mainclient/shell.c @@ -1,5 +1,5 @@ /* -* Copyright (c) 2025 Calvin Rose +* Copyright (c) 2026 Calvin Rose * * Permission is hereby granted, free of charge, to any person obtaining a copy * of this software and associated documentation files (the "Software"), to diff --git a/test/amalg/main.c b/test/amalg/main.c index 3bcc688b..9b5a503e 100644 --- a/test/amalg/main.c +++ b/test/amalg/main.c @@ -1,5 +1,5 @@ /* -* Copyright (c) 2025 Calvin Rose +* Copyright (c) 2026 Calvin Rose * * Permission is hereby granted, free of charge, to any person obtaining a copy * of this software and associated documentation files (the "Software"), to diff --git a/test/suite-array.janet b/test/suite-array.janet index 13e2407d..b6c7954a 100644 --- a/test/suite-array.janet +++ b/test/suite-array.janet @@ -1,4 +1,4 @@ -# Copyright (c) 2025 Calvin Rose +# Copyright (c) 2026 Calvin Rose # # Permission is hereby granted, free of charge, to any person obtaining a copy # of this software and associated documentation files (the "Software"), to diff --git a/test/suite-asm.janet b/test/suite-asm.janet index 075fe4e1..07bc597b 100644 --- a/test/suite-asm.janet +++ b/test/suite-asm.janet @@ -1,4 +1,4 @@ -# Copyright (c) 2025 Calvin Rose +# Copyright (c) 2026 Calvin Rose # # Permission is hereby granted, free of charge, to any person obtaining a copy # of this software and associated documentation files (the "Software"), to diff --git a/test/suite-boot.janet b/test/suite-boot.janet index e94ea16a..703ea9e4 100644 --- a/test/suite-boot.janet +++ b/test/suite-boot.janet @@ -1,4 +1,4 @@ -# Copyright (c) 2025 Calvin Rose +# Copyright (c) 2026 Calvin Rose # # Permission is hereby granted, free of charge, to any person obtaining a copy # of this software and associated documentation files (the "Software"), to diff --git a/test/suite-buffer.janet b/test/suite-buffer.janet index 45d077b9..b1362537 100644 --- a/test/suite-buffer.janet +++ b/test/suite-buffer.janet @@ -1,4 +1,4 @@ -# Copyright (c) 2025 Calvin Rose +# Copyright (c) 2026 Calvin Rose # # Permission is hereby granted, free of charge, to any person obtaining a copy # of this software and associated documentation files (the "Software"), to diff --git a/test/suite-bundle.janet b/test/suite-bundle.janet index 7da438cd..8ec37c92 100644 --- a/test/suite-bundle.janet +++ b/test/suite-bundle.janet @@ -1,4 +1,4 @@ -# Copyright (c) 2025 Calvin Rose +# Copyright (c) 2026 Calvin Rose # # Permission is hereby granted, free of charge, to any person obtaining a copy # of this software and associated documentation files (the "Software"), to diff --git a/test/suite-capi.janet b/test/suite-capi.janet index dfa86fc0..7076df66 100644 --- a/test/suite-capi.janet +++ b/test/suite-capi.janet @@ -1,4 +1,4 @@ -# Copyright (c) 2025 Calvin Rose +# Copyright (c) 2026 Calvin Rose # # Permission is hereby granted, free of charge, to any person obtaining a copy # of this software and associated documentation files (the "Software"), to diff --git a/test/suite-cfuns.janet b/test/suite-cfuns.janet index 3e36c3f7..80a378f7 100644 --- a/test/suite-cfuns.janet +++ b/test/suite-cfuns.janet @@ -1,4 +1,4 @@ -# Copyright (c) 2025 Calvin Rose +# Copyright (c) 2026 Calvin Rose # # Permission is hereby granted, free of charge, to any person obtaining a copy # of this software and associated documentation files (the "Software"), to diff --git a/test/suite-compile.janet b/test/suite-compile.janet index 204a91f8..82b3f2ad 100644 --- a/test/suite-compile.janet +++ b/test/suite-compile.janet @@ -1,4 +1,4 @@ -# Copyright (c) 2025 Calvin Rose +# Copyright (c) 2026 Calvin Rose # # Permission is hereby granted, free of charge, to any person obtaining a copy # of this software and associated documentation files (the "Software"), to diff --git a/test/suite-corelib.janet b/test/suite-corelib.janet index c8f9a1bf..8081c965 100644 --- a/test/suite-corelib.janet +++ b/test/suite-corelib.janet @@ -1,4 +1,4 @@ -# Copyright (c) 2025 Calvin Rose +# Copyright (c) 2026 Calvin Rose # # Permission is hereby granted, free of charge, to any person obtaining a copy # of this software and associated documentation files (the "Software"), to diff --git a/test/suite-debug.janet b/test/suite-debug.janet index e0d8793d..95514710 100644 --- a/test/suite-debug.janet +++ b/test/suite-debug.janet @@ -1,4 +1,4 @@ -# Copyright (c) 2025 Calvin Rose +# Copyright (c) 2026 Calvin Rose # # Permission is hereby granted, free of charge, to any person obtaining a copy # of this software and associated documentation files (the "Software"), to diff --git a/test/suite-ev.janet b/test/suite-ev.janet index ead27c8e..c0ec5d78 100644 --- a/test/suite-ev.janet +++ b/test/suite-ev.janet @@ -1,4 +1,4 @@ -# Copyright (c) 2025 Calvin Rose & contributors +# Copyright (c) 2026 Calvin Rose & contributors # # Permission is hereby granted, free of charge, to any person obtaining a copy # of this software and associated documentation files (the "Software"), to diff --git a/test/suite-ev2.janet b/test/suite-ev2.janet index 1d774b85..8752c3d7 100644 --- a/test/suite-ev2.janet +++ b/test/suite-ev2.janet @@ -1,4 +1,4 @@ -# Copyright (c) 2025 Calvin Rose & contributors +# Copyright (c) 2026 Calvin Rose & contributors # # Permission is hereby granted, free of charge, to any person obtaining a copy # of this software and associated documentation files (the "Software"), to diff --git a/test/suite-ffi.janet b/test/suite-ffi.janet index c6237736..45bcd0ae 100644 --- a/test/suite-ffi.janet +++ b/test/suite-ffi.janet @@ -1,4 +1,4 @@ -# Copyright (c) 2025 Calvin Rose & contributors +# Copyright (c) 2026 Calvin Rose & contributors # # Permission is hereby granted, free of charge, to any person obtaining a copy # of this software and associated documentation files (the "Software"), to diff --git a/test/suite-filewatch.janet b/test/suite-filewatch.janet index 9c3f7404..1c77fa65 100644 --- a/test/suite-filewatch.janet +++ b/test/suite-filewatch.janet @@ -1,4 +1,4 @@ -# Copyright (c) 2025 Calvin Rose & contributors +# Copyright (c) 2026 Calvin Rose & contributors # # Permission is hereby granted, free of charge, to any person obtaining a copy # of this software and associated documentation files (the "Software"), to diff --git a/test/suite-inttypes.janet b/test/suite-inttypes.janet index 91b38590..6b67e25d 100644 --- a/test/suite-inttypes.janet +++ b/test/suite-inttypes.janet @@ -1,4 +1,4 @@ -# Copyright (c) 2025 Calvin Rose & contributors +# Copyright (c) 2026 Calvin Rose & contributors # # Permission is hereby granted, free of charge, to any person obtaining a copy # of this software and associated documentation files (the "Software"), to diff --git a/test/suite-io.janet b/test/suite-io.janet index 726b7aa4..6a0513c2 100644 --- a/test/suite-io.janet +++ b/test/suite-io.janet @@ -1,4 +1,4 @@ -# Copyright (c) 2025 Calvin Rose & contributors +# Copyright (c) 2026 Calvin Rose & contributors # # Permission is hereby granted, free of charge, to any person obtaining a copy # of this software and associated documentation files (the "Software"), to diff --git a/test/suite-marsh.janet b/test/suite-marsh.janet index 9defbc55..3fc2c75a 100644 --- a/test/suite-marsh.janet +++ b/test/suite-marsh.janet @@ -1,4 +1,4 @@ -# Copyright (c) 2025 Calvin Rose +# Copyright (c) 2026 Calvin Rose # # Permission is hereby granted, free of charge, to any person obtaining a copy # of this software and associated documentation files (the "Software"), to diff --git a/test/suite-math.janet b/test/suite-math.janet index 58b4ff18..557ad84d 100644 --- a/test/suite-math.janet +++ b/test/suite-math.janet @@ -1,4 +1,4 @@ -# Copyright (c) 2025 Calvin Rose +# Copyright (c) 2026 Calvin Rose # # Permission is hereby granted, free of charge, to any person obtaining a copy # of this software and associated documentation files (the "Software"), to diff --git a/test/suite-os.janet b/test/suite-os.janet index c16e7714..edef8ef8 100644 --- a/test/suite-os.janet +++ b/test/suite-os.janet @@ -1,4 +1,4 @@ -# Copyright (c) 2025 Calvin Rose +# Copyright (c) 2026 Calvin Rose # # Permission is hereby granted, free of charge, to any person obtaining a copy # of this software and associated documentation files (the "Software"), to diff --git a/test/suite-parse.janet b/test/suite-parse.janet index 1cb3c71a..045d2443 100644 --- a/test/suite-parse.janet +++ b/test/suite-parse.janet @@ -1,4 +1,4 @@ -# Copyright (c) 2025 Calvin Rose +# Copyright (c) 2026 Calvin Rose # # Permission is hereby granted, free of charge, to any person obtaining a copy # of this software and associated documentation files (the "Software"), to diff --git a/test/suite-peg.janet b/test/suite-peg.janet index fbd98994..5b6d4f61 100644 --- a/test/suite-peg.janet +++ b/test/suite-peg.janet @@ -1,4 +1,4 @@ -# Copyright (c) 2025 Calvin Rose +# Copyright (c) 2026 Calvin Rose # # Permission is hereby granted, free of charge, to any person obtaining a copy # of this software and associated documentation files (the "Software"), to diff --git a/test/suite-pp.janet b/test/suite-pp.janet index 4030d64f..be17d195 100644 --- a/test/suite-pp.janet +++ b/test/suite-pp.janet @@ -1,4 +1,4 @@ -# Copyright (c) 2025 Calvin Rose & contributors +# Copyright (c) 2026 Calvin Rose & contributors # # Permission is hereby granted, free of charge, to any person obtaining a copy # of this software and associated documentation files (the "Software"), to diff --git a/test/suite-specials.janet b/test/suite-specials.janet index edabf8c3..8b5ef2cf 100644 --- a/test/suite-specials.janet +++ b/test/suite-specials.janet @@ -1,4 +1,4 @@ -# Copyright (c) 2025 Calvin Rose +# Copyright (c) 2026 Calvin Rose # # Permission is hereby granted, free of charge, to any person obtaining a copy # of this software and associated documentation files (the "Software"), to diff --git a/test/suite-string.janet b/test/suite-string.janet index a92b0eb6..a7cd7e4c 100644 --- a/test/suite-string.janet +++ b/test/suite-string.janet @@ -1,4 +1,4 @@ -# Copyright (c) 2025 Calvin Rose +# Copyright (c) 2026 Calvin Rose # # Permission is hereby granted, free of charge, to any person obtaining a copy # of this software and associated documentation files (the "Software"), to diff --git a/test/suite-strtod.janet b/test/suite-strtod.janet index d14985b9..99685ef7 100644 --- a/test/suite-strtod.janet +++ b/test/suite-strtod.janet @@ -1,4 +1,4 @@ -# Copyright (c) 2025 Calvin Rose +# Copyright (c) 2026 Calvin Rose # # Permission is hereby granted, free of charge, to any person obtaining a copy # of this software and associated documentation files (the "Software"), to diff --git a/test/suite-struct.janet b/test/suite-struct.janet index 3178a9e2..48fe6800 100644 --- a/test/suite-struct.janet +++ b/test/suite-struct.janet @@ -1,4 +1,4 @@ -# Copyright (c) 2025 Calvin Rose +# Copyright (c) 2026 Calvin Rose # # Permission is hereby granted, free of charge, to any person obtaining a copy # of this software and associated documentation files (the "Software"), to diff --git a/test/suite-symcache.janet b/test/suite-symcache.janet index 29b6c541..7f0c98f6 100644 --- a/test/suite-symcache.janet +++ b/test/suite-symcache.janet @@ -1,4 +1,4 @@ -# Copyright (c) 2025 Calvin Rose +# Copyright (c) 2026 Calvin Rose # # Permission is hereby granted, free of charge, to any person obtaining a copy # of this software and associated documentation files (the "Software"), to diff --git a/test/suite-table.janet b/test/suite-table.janet index 4f954778..f261c678 100644 --- a/test/suite-table.janet +++ b/test/suite-table.janet @@ -1,4 +1,4 @@ -# Copyright (c) 2025 Calvin Rose +# Copyright (c) 2026 Calvin Rose # # Permission is hereby granted, free of charge, to any person obtaining a copy # of this software and associated documentation files (the "Software"), to diff --git a/test/suite-tuple.janet b/test/suite-tuple.janet index f2010080..a83e0f81 100644 --- a/test/suite-tuple.janet +++ b/test/suite-tuple.janet @@ -1,4 +1,4 @@ -# Copyright (c) 2025 Calvin Rose +# Copyright (c) 2026 Calvin Rose # # Permission is hereby granted, free of charge, to any person obtaining a copy # of this software and associated documentation files (the "Software"), to diff --git a/test/suite-unknown.janet b/test/suite-unknown.janet index f35d3ab4..4997749f 100644 --- a/test/suite-unknown.janet +++ b/test/suite-unknown.janet @@ -1,4 +1,4 @@ -# Copyright (c) 2025 Calvin Rose +# Copyright (c) 2026 Calvin Rose # # Permission is hereby granted, free of charge, to any person obtaining a copy # of this software and associated documentation files (the "Software"), to diff --git a/test/suite-value.janet b/test/suite-value.janet index 16a6d455..25070583 100644 --- a/test/suite-value.janet +++ b/test/suite-value.janet @@ -1,4 +1,4 @@ -# Copyright (c) 2025 Calvin Rose +# Copyright (c) 2026 Calvin Rose # # Permission is hereby granted, free of charge, to any person obtaining a copy # of this software and associated documentation files (the "Software"), to diff --git a/test/suite-vm.janet b/test/suite-vm.janet index e1019379..0eb6385b 100644 --- a/test/suite-vm.janet +++ b/test/suite-vm.janet @@ -1,4 +1,4 @@ -# Copyright (c) 2025 Calvin Rose +# Copyright (c) 2026 Calvin Rose # # Permission is hereby granted, free of charge, to any person obtaining a copy # of this software and associated documentation files (the "Software"), to diff --git a/tools/symcharsgen.c b/tools/symcharsgen.c index 8193cd5c..3908ce1b 100644 --- a/tools/symcharsgen.c +++ b/tools/symcharsgen.c @@ -1,5 +1,5 @@ /* -* Copyright (c) 2025 Calvin Rose +* Copyright (c) 2026 Calvin Rose * * Permission is hereby granted, free of charge, to any person obtaining a copy * of this software and associated documentation files (the "Software"), to From 80da028aeb7fc9f9676ed7d0c4032c0c041c287c Mon Sep 17 00:00:00 2001 From: Calvin Rose Date: Sun, 4 Jan 2026 08:18:23 -0600 Subject: [PATCH 079/307] Add linting to address #1688 Warn when defining macros on inner scopes, and also point out unused metadata as well. --- src/core/specials.c | 13 +++++++++++++ 1 file changed, 13 insertions(+) diff --git a/src/core/specials.c b/src/core/specials.c index 35b06966..94a6e36c 100644 --- a/src/core/specials.c +++ b/src/core/specials.c @@ -464,12 +464,24 @@ static int varleaf( } } +static void check_metadata_lint(JanetCompiler *c, JanetTable *attr_table) { + if (!(c->scope->flags & JANET_SCOPE_TOP) && attr_table && attr_table->count) { + /* A macro is a normal lint, other metadata is a strict lint */ + if (janet_truthy(janet_table_get(attr_table, janet_ckeywordv("macro")))) { + janetc_lintf(c, JANET_C_LINT_NORMAL, "macro tag is ignored in inner scopes"); + } else { + janetc_lintf(c, JANET_C_LINT_STRICT, "unused metadata %j in inner scope", janet_wrap_table(attr_table)); + } + } +} + static JanetSlot janetc_var(JanetFopts opts, int32_t argn, const Janet *argv) { JanetCompiler *c = opts.compiler; JanetTable *attr_table = handleattr(c, "var", argn, argv); if (c->result.status == JANET_COMPILE_ERROR) { return janetc_cslot(janet_wrap_nil()); } + check_metadata_lint(c, attr_table); SlotHeadPair *into = NULL; into = dohead_destructure(c, into, opts, argv[0], argv[argn - 1]); if (c->result.status == JANET_COMPILE_ERROR) { @@ -530,6 +542,7 @@ static JanetSlot janetc_def(JanetFopts opts, int32_t argn, const Janet *argv) { if (c->result.status == JANET_COMPILE_ERROR) { return janetc_cslot(janet_wrap_nil()); } + check_metadata_lint(c, attr_table); opts.flags &= ~JANET_FOPTS_HINT; SlotHeadPair *into = NULL; into = dohead_destructure(c, into, opts, argv[0], argv[argn - 1]); From 7b0b9f57e8e91467cb6787e7b9debba7da10d520 Mon Sep 17 00:00:00 2001 From: Calvin Rose Date: Fri, 9 Jan 2026 08:04:07 -0600 Subject: [PATCH 080/307] Make os/realpath error on windows if path does not exist. --- src/core/os.c | 8 ++++++++ 1 file changed, 8 insertions(+) diff --git a/src/core/os.c b/src/core/os.c index a4aa60d1..a241cf14 100644 --- a/src/core/os.c +++ b/src/core/os.c @@ -2616,7 +2616,15 @@ JANET_CORE_FN(os_realpath, #endif if (NULL == dest) janet_panicf("%s: %s", janet_strerror(errno), src); Janet ret = janet_cstringv(dest); +#ifdef JANET_WINDOWS + DWORD attrib = GetFileAttributes(dest); + free(dest); /* if janet_malloc is redefined, still use free to correspond with _fullpath */ + if (attrib == INVALID_FILE_ATTRIBUTES) { + janet_panicf("path does not exist: %v", ret); + } +#else janet_free(dest); +#endif return ret; #endif } From 84ced0881051123e4cd2aea1de948d75749b6351 Mon Sep 17 00:00:00 2001 From: Calvin Rose Date: Fri, 9 Jan 2026 08:15:18 -0600 Subject: [PATCH 081/307] Add test case. --- test/suite-os.janet | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/test/suite-os.janet b/test/suite-os.janet index edef8ef8..6b615d0f 100644 --- a/test/suite-os.janet +++ b/test/suite-os.janet @@ -174,4 +174,8 @@ :px {:out dn :err dn}))) +# Issue 16922 +(assert-error "os/realpath errors when path does not exist" + (os/realpath "abc123def456")) + (end-suite) From ddc7cc5ae443b7c4abad2f42046299acb57066f7 Mon Sep 17 00:00:00 2001 From: Calvin Rose Date: Fri, 9 Jan 2026 17:06:14 -0600 Subject: [PATCH 082/307] Warn on unused bindings (strict lint). This does create a lot of warnings, especially in the test suite, but should improve code and point out real issues. To disable individual messages, either disable linting, add the metadata :unused to a binding, or add the prefix "_" to a symbol. --- CHANGELOG.md | 1 + examples/chatserver.janet | 2 +- examples/debugger.janet | 4 ++-- examples/rtest.janet | 2 +- examples/threaded-channels.janet | 2 +- src/boot/boot.janet | 8 ++++++-- src/core/compile.c | 26 ++++++++++++++++++++++++++ src/core/compile.h | 2 ++ src/core/specials.c | 18 +++++++++++------- test/helper.janet | 1 - test/suite-asm.janet | 2 ++ test/suite-boot.janet | 2 ++ test/suite-compile.janet | 2 +- test/suite-corelib.janet | 2 ++ test/suite-ev.janet | 2 ++ test/suite-inttypes.janet | 3 +++ test/suite-marsh.janet | 2 ++ test/suite-math.janet | 6 +++--- test/suite-parse.janet | 6 +++--- test/suite-specials.janet | 3 +++ test/suite-unknown.janet | 3 +++ test/suite-vm.janet | 8 ++++---- 22 files changed, 81 insertions(+), 26 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 67bab0ec..4dcbd0ba 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -2,6 +2,7 @@ All notable changes to this project will be documented in this file. ## Unreleased - ??? +- Add linting for unused bindings. - Add `janet_optuinteger` and `janet_optuinteger64` to the C API. - Add `cms` combinator to PEGs. - Add `thaw-keep-keys` as a variant of thaw diff --git a/examples/chatserver.janet b/examples/chatserver.janet index 534444f5..1fab3710 100644 --- a/examples/chatserver.janet +++ b/examples/chatserver.janet @@ -26,7 +26,7 @@ (broadcast name (string msg))) (print name " disconnected"))))) -(defn main [& args] +(defn main [&] (printf "STARTING SERVER...") (flush) (def my-server (net/listen "127.0.0.1" "8000")) diff --git a/examples/debugger.janet b/examples/debugger.janet index 588e55f3..6d48802c 100644 --- a/examples/debugger.janet +++ b/examples/debugger.janet @@ -132,7 +132,7 @@ "Go to the next breakpoint." [&opt n] (var res nil) - (for i 0 (or n 1) + (repeat (or n 1) (set res (resume (.fiber)))) res) @@ -146,6 +146,6 @@ "Execute the next n instructions." [&opt n] (var res nil) - (for i 0 (or n 1) + (repeat (or n 1) (set res (debug/step (.fiber)))) res) diff --git a/examples/rtest.janet b/examples/rtest.janet index 9d494674..774d4e46 100644 --- a/examples/rtest.janet +++ b/examples/rtest.janet @@ -2,7 +2,7 @@ (def counts (seq [_ :range [0 100]] 0)) -(for i 0 1000000 +(repeat 1000000 (let [x (math/random) intrange (math/floor (* 100 x)) oldcount (counts intrange)] diff --git a/examples/threaded-channels.janet b/examples/threaded-channels.janet index c4e1bc1d..de32a2cf 100644 --- a/examples/threaded-channels.janet +++ b/examples/threaded-channels.janet @@ -7,7 +7,7 @@ (ev/give chan (math/random)) (ev/give chan (math/random)) (ev/sleep 0.5) - (for i 0 10 + (repeat 10 (print "giving to channel...") (ev/give chan (math/random)) (ev/sleep 1)) diff --git a/src/boot/boot.janet b/src/boot/boot.janet index 13fa1185..a7caa046 100644 --- a/src/boot/boot.janet +++ b/src/boot/boot.janet @@ -2113,11 +2113,15 @@ (array/concat anda unify) # Final binding (def defs (seq [[k v] :in (sort (pairs b2g))] ['def k (first v)])) + (def unused-defs (seq [[k v] :in (sort (pairs b2g))] ['def k :unused (first v)])) # Predicates (unless (empty? preds) - (def pred-join ~(do ,;defs (and ,;preds))) + (def pred-join ~(do ,;unused-defs (and ,;preds))) (array/push anda pred-join)) - (emit-branch (tuple/slice anda) ['do ;defs expression])) + # Use `unused-defs` instead of `defs` when we have predicates to avoid unused binding lint + # e.g. (match x (n (even? n)) :yes :no) should not warn on unused binding `n`. + # This is unfortunately not perfect since one programmer written binding is expanded for use in multiple places. + (emit-branch (tuple/slice anda) ['do ;(if (next preds) unused-defs defs) expression])) # Expand branches (def stack @[else]) diff --git a/src/core/compile.c b/src/core/compile.c index 6e92b47d..75e777d7 100644 --- a/src/core/compile.c +++ b/src/core/compile.c @@ -98,6 +98,22 @@ void janetc_nameslot(JanetCompiler *c, const uint8_t *sym, JanetSlot s) { sp.sym2 = sym; sp.slot = s; sp.keep = 0; + sp.referenced = sym[0] == '_'; /* Fake ref if symbol is _ to avoid lints */ + sp.slot.flags |= JANET_SLOT_NAMED; + sp.birth_pc = cnt ? cnt - 1 : 0; + sp.death_pc = UINT32_MAX; + janet_v_push(c->scope->syms, sp); +} + +/* Same as janetc_nameslot, but don't have a lint for unused bindings. */ +void janetc_nameslot_no_unused(JanetCompiler *c, const uint8_t *sym, JanetSlot s) { + SymPair sp; + int32_t cnt = janet_v_count(c->buffer); + sp.sym = sym; + sp.sym2 = sym; + sp.slot = s; + sp.keep = 0; + sp.referenced = 1; sp.slot.flags |= JANET_SLOT_NAMED; sp.birth_pc = cnt ? cnt - 1 : 0; sp.death_pc = UINT32_MAX; @@ -170,6 +186,10 @@ void janetc_popscope(JanetCompiler *c) { /* Keep upvalue slots and symbols for debugging. */ for (int32_t i = 0; i < janet_v_count(oldscope->syms); i++) { SymPair pair = oldscope->syms[i]; + /* Check for unused symbols */ + if (pair.referenced == 0 && pair.sym) { + janetc_lintf(c, JANET_C_LINT_STRICT, "binding %q is unused", janet_wrap_symbol(pair.sym)); + } /* The variable should not be lexically accessible */ pair.sym = NULL; if (pair.death_pc == UINT32_MAX) { @@ -262,6 +282,7 @@ JanetSlot janetc_resolve( pair = scope->syms + i; if (pair->sym == sym) { ret = pair->slot; + pair->referenced = 1; goto found; } } @@ -346,6 +367,7 @@ found: /* non-local scope needs to expose its environment */ JanetScope *original_scope = scope; pair->keep = 1; + pair->referenced = 1; while (scope && !(scope->flags & JANET_SCOPE_FUNCTION)) scope = scope->parent; janet_assert(scope, "invalid scopes"); @@ -974,6 +996,10 @@ JanetFuncDef *janetc_pop_funcdef(JanetCompiler *c) { SymPair pair = scope->syms[i]; if (pair.sym2) { JanetSymbolMap jsm; + /* Check for unused symbols */ + if (pair.referenced == 0 && pair.sym) { + janetc_lintf(c, JANET_C_LINT_STRICT, "binding %q is unused", janet_wrap_symbol(pair.sym)); + } if (pair.death_pc == UINT32_MAX) { jsm.death_pc = def->bytecode_length; } else { diff --git a/src/core/compile.h b/src/core/compile.h index 5a68df33..e7739767 100644 --- a/src/core/compile.h +++ b/src/core/compile.h @@ -114,6 +114,7 @@ typedef struct SymPair { const uint8_t *sym; const uint8_t *sym2; int keep; + int referenced; /* Has this value been used */ uint32_t birth_pc; uint32_t death_pc; } SymPair; @@ -222,6 +223,7 @@ const JanetSpecial *janetc_special(const uint8_t *name); void janetc_freeslot(JanetCompiler *c, JanetSlot s); void janetc_nameslot(JanetCompiler *c, const uint8_t *sym, JanetSlot s); +void janetc_nameslot_no_unused(JanetCompiler *c, const uint8_t *sym, JanetSlot s); JanetSlot janetc_farslot(JanetCompiler *c); /* Throw away some code after checking that it is well formed. */ diff --git a/src/core/specials.c b/src/core/specials.c index 94a6e36c..2e25f677 100644 --- a/src/core/specials.c +++ b/src/core/specials.c @@ -404,7 +404,7 @@ SlotHeadPair *dohead_destructure(JanetCompiler *c, SlotHeadPair *into, JanetFopt } /* Def or var a symbol in a local scope */ -static int namelocal(JanetCompiler *c, const uint8_t *head, int32_t flags, JanetSlot ret) { +static int namelocal(JanetCompiler *c, const uint8_t *head, int32_t flags, JanetSlot ret, int no_unused) { int isUnnamedRegister = !(ret.flags & JANET_SLOT_NAMED) && ret.index > 0 && ret.envindex >= 0; @@ -425,7 +425,11 @@ static int namelocal(JanetCompiler *c, const uint8_t *head, int32_t flags, Janet ret = localslot; } ret.flags |= flags; - janetc_nameslot(c, head, ret); + if ((c->scope->flags & JANET_SCOPE_TOP) || no_unused) { + janetc_nameslot_no_unused(c, head, ret); + } else { + janetc_nameslot(c, head, ret); + } return !isUnnamedRegister; } @@ -460,7 +464,8 @@ static int varleaf( janetc_emit_ssu(c, JOP_PUT_INDEX, refslot, s, 0, 0); return 1; } else { - return namelocal(c, sym, JANET_SLOT_MUTABLE, s); + int no_unused = reftab && reftab->count && janet_truthy(janet_table_get(reftab, janet_ckeywordv("unused"))); + return namelocal(c, sym, JANET_SLOT_MUTABLE, s, no_unused); } } @@ -469,8 +474,6 @@ static void check_metadata_lint(JanetCompiler *c, JanetTable *attr_table) { /* A macro is a normal lint, other metadata is a strict lint */ if (janet_truthy(janet_table_get(attr_table, janet_ckeywordv("macro")))) { janetc_lintf(c, JANET_C_LINT_NORMAL, "macro tag is ignored in inner scopes"); - } else { - janetc_lintf(c, JANET_C_LINT_STRICT, "unused metadata %j in inner scope", janet_wrap_table(attr_table)); } } } @@ -533,7 +536,8 @@ static int defleaf( /* Add env entry to env */ janet_table_put(c->env, janet_wrap_symbol(sym), janet_wrap_table(entry)); } - return namelocal(c, sym, 0, s); + int no_unused = tab && tab->count && janet_truthy(janet_table_get(tab, janet_ckeywordv("unused"))); + return namelocal(c, sym, 0, s, no_unused); } static JanetSlot janetc_def(JanetFopts opts, int32_t argn, const Janet *argv) { @@ -1114,7 +1118,7 @@ static JanetSlot janetc_fn(JanetFopts opts, int32_t argn, const Janet *argv) { JanetSlot slot = janetc_farslot(c); slot.flags = JANET_SLOT_NAMED | JANET_FUNCTION; janetc_emit_s(c, JOP_LOAD_SELF, slot, 1); - janetc_nameslot(c, sym, slot); + janetc_nameslot_no_unused(c, sym, slot); } } diff --git a/test/helper.janet b/test/helper.janet index c7b1e242..f717fe5a 100644 --- a/test/helper.janet +++ b/test/helper.janet @@ -19,7 +19,6 @@ (break x)) (default e "assert error") (when x (++ num-tests-passed)) - (def str (string e)) (def stack (debug/stack (fiber/current))) (def frame (last stack)) (def line-info (string/format "%s:%d" diff --git a/test/suite-asm.janet b/test/suite-asm.janet index 07bc597b..cd3c2dd9 100644 --- a/test/suite-asm.janet +++ b/test/suite-asm.janet @@ -21,6 +21,8 @@ (import ./helper :prefix "" :exit true) (start-suite) +(setdyn *lint-warn* :none) + # Assembly test # Fibonacci sequence, implemented with naive recursion. # a679f60 diff --git a/test/suite-boot.janet b/test/suite-boot.janet index 703ea9e4..c24d4c50 100644 --- a/test/suite-boot.janet +++ b/test/suite-boot.janet @@ -21,6 +21,8 @@ (import ./helper :prefix "" :exit true) (start-suite) +(setdyn *lint-warn* :none) + # Let # 807f981 (assert (= (let [a 1 b 2] (+ a b)) 3) "simple let") diff --git a/test/suite-compile.janet b/test/suite-compile.janet index 82b3f2ad..96326ad4 100644 --- a/test/suite-compile.janet +++ b/test/suite-compile.janet @@ -30,7 +30,7 @@ (defn myfun [x] (var a 10) (set a (do - (def y x) + (def _y x) (if x 8 9)))) (assert (= (myfun true) 8) "check do form regression") diff --git a/test/suite-corelib.janet b/test/suite-corelib.janet index 8081c965..02cdaaba 100644 --- a/test/suite-corelib.janet +++ b/test/suite-corelib.janet @@ -192,8 +192,10 @@ (assert (deep-not= (thaw ds2) (thaw-keep-keys ds2)) "thaw vs. thaw-keep-keys 2") # match +(setdyn *lint-warn* :none) (assert (= :yes (match [1 2 3] [x y z w] :no1 [x y $] :no2 [x y z] :yes)) "match dollar suffix 1") (assert (= :yes (match [1 2 3] [x y z w] :no1 [x y z $] :yes [x y z] :no2)) "match dollar suffix 2") +(setdyn *lint-warn* nil) # Issue #1687 (assert-no-error "def destructure splice works 1" (do (def [a] [;[1]]) a)) diff --git a/test/suite-ev.janet b/test/suite-ev.janet index c0ec5d78..77c8ad39 100644 --- a/test/suite-ev.janet +++ b/test/suite-ev.janet @@ -21,6 +21,8 @@ (import ./helper :prefix "" :exit true) (start-suite) +(setdyn *lint-warn* :none) + (def test-port (os/getenv "JANET_TEST_PORT" "8761")) (def test-host (os/getenv "JANET_TEST_HOST" "127.0.0.1")) diff --git a/test/suite-inttypes.janet b/test/suite-inttypes.janet index 6b67e25d..529be503 100644 --- a/test/suite-inttypes.janet +++ b/test/suite-inttypes.janet @@ -21,6 +21,9 @@ (import ./helper :prefix "" :exit true) (start-suite) +# Disable linting warnings +(setdyn *lint-warn* :none) + # some tests for bigint # 319575c (def i64 int/s64) diff --git a/test/suite-marsh.janet b/test/suite-marsh.janet index 3fc2c75a..c0cd6b0d 100644 --- a/test/suite-marsh.janet +++ b/test/suite-marsh.janet @@ -81,10 +81,12 @@ "marshal nested fibers") # issue #53 - f4908ebc4 +(setdyn *lint-warn* :none) (def issue-53-x (fiber/new (fn [] (var y (fiber/new (fn [] (print "1") (yield) (print "2"))))))) +(setdyn *lint-warn* nil) (check-image issue-53-x "issue 53 regression") diff --git a/test/suite-math.janet b/test/suite-math.janet index 557ad84d..338bc7cf 100644 --- a/test/suite-math.janet +++ b/test/suite-math.janet @@ -31,13 +31,13 @@ [rng] (assert (all identity (seq [i :range [0 1000]] (<= (math/rng-int rng i) i))) "math/rng-int test") - (assert (all identity (seq [i :range [0 1000]] + (assert (all identity (seq [_ :range [0 1000]] (def x (math/rng-uniform rng)) (and (>= x 0) (< x 1)))) "math/rng-uniform test")) (def seedrng (math/rng 123)) -(for i 0 75 +(for _ 0 75 (test-rng (math/rng (:int seedrng)))) # 70328437f @@ -49,7 +49,7 @@ # 027b2a8 (defn assert-many [f n e] (var good true) - (loop [i :range [0 n]] + (loop [_ :range [0 n]] (if (not (f)) (set good false))) (assert good e)) diff --git a/test/suite-parse.janet b/test/suite-parse.janet index 045d2443..7b410029 100644 --- a/test/suite-parse.janet +++ b/test/suite-parse.janet @@ -67,7 +67,7 @@ (def str (if rewrite (peg/replace-all ~(* '(* (? "\r") "\n") (between 0 ,indent " ")) - (fn [mtch eol] eol) text) + (fn [_mtch eol] eol) text) text)) (def first-eol (cond @@ -177,12 +177,12 @@ (def p1 (parser/new)) (parser/state p1) (parser/consume p1 step1) -(loop [v :iterate (parser/produce p1)]) +(loop [_ :iterate (parser/produce p1)]) (parser/state p1) (def p2 (parser/clone p1)) (parser/state p2) (parser/consume p2 step2) -(loop [v :iterate (parser/produce p2)]) +(loop [_ :iterate (parser/produce p2)]) (parser/state p2) # parser delimiter errors diff --git a/test/suite-specials.janet b/test/suite-specials.janet index 8b5ef2cf..4532f885 100644 --- a/test/suite-specials.janet +++ b/test/suite-specials.janet @@ -21,6 +21,9 @@ (import ./helper :prefix "" :exit true) (start-suite) +# Disable linting warnings +(setdyn *lint-warn* :none) + # Regression Test #137 # affcb5b45 (def [a b c] (range 10)) diff --git a/test/suite-unknown.janet b/test/suite-unknown.janet index 4997749f..926356de 100644 --- a/test/suite-unknown.janet +++ b/test/suite-unknown.janet @@ -21,6 +21,9 @@ (import ./helper :prefix "" :exit true) (start-suite) +# Disable linting warnings +(setdyn *lint-warn* :none) + # Set global variables to prevent some possible compiler optimizations # that defeat point of the test # 2771171 diff --git a/test/suite-vm.janet b/test/suite-vm.janet index 0eb6385b..56bddcc0 100644 --- a/test/suite-vm.janet +++ b/test/suite-vm.janet @@ -64,11 +64,11 @@ # b9c0fc820 (assert (= 1 ({:ok 1} :ok)) "calling struct") (assert (= 2 (@{:ok 2} :ok)) "calling table") -(assert (= :bad (try ((identity @{:ok 2}) :ok :no) ([err] :bad))) +(assert (= :bad (try ((identity @{:ok 2}) :ok :no) ([_err] :bad))) "calling table too many arguments") -(assert (= :bad (try ((identity :ok) @{:ok 2} :no) ([err] :bad))) +(assert (= :bad (try ((identity :ok) @{:ok 2} :no) ([_err] :bad))) "calling keyword too many arguments") -(assert (= :oops (try ((+ 2 -1) 1) ([err] :oops))) +(assert (= :oops (try ((+ 2 -1) 1) ([_err] :oops))) "calling number fails") # Method test @@ -119,7 +119,7 @@ (with-dyns [] (ev/sleep 0) (error "oops"))) - ([err] :caught)))) + ([_err] :caught)))) "regression #638")) # From cb355815eec74ae7e7977ff23f2a4ac7c0b0e333 Mon Sep 17 00:00:00 2001 From: Calvin Rose Date: Sat, 10 Jan 2026 08:37:09 -0600 Subject: [PATCH 083/307] Turn on strict linting in test suite. --- src/core/compile.c | 2 +- test/helper.janet | 5 ++++- test/suite-compile.janet | 2 ++ test/suite-ffi.janet | 2 +- test/suite-filewatch.janet | 4 ++-- 5 files changed, 10 insertions(+), 5 deletions(-) diff --git a/src/core/compile.c b/src/core/compile.c index 75e777d7..40d2c3e4 100644 --- a/src/core/compile.c +++ b/src/core/compile.c @@ -538,7 +538,7 @@ void janetc_throwaway(JanetFopts opts, Janet x) { int32_t mapbufstart = janet_v_count(c->mapbuffer); janetc_scope(&unusedScope, c, JANET_SCOPE_UNUSED, "unusued"); janetc_value(opts, x); - janetc_lintf(c, JANET_C_LINT_STRICT, "dead code, consider removing %.2q", x); + janetc_lintf(c, JANET_C_LINT_STRICT, "dead code, consider removing %.4q", x); janetc_popscope(c); if (c->buffer) { janet_v__cnt(c->buffer) = bufstart; diff --git a/test/helper.janet b/test/helper.janet index f717fe5a..c0b89b78 100644 --- a/test/helper.janet +++ b/test/helper.janet @@ -1,5 +1,8 @@ # Helper code for running tests +# Turn on strict linting by default in test suite. +(put root-env *lint-warn* :strict) + (var num-tests-passed 0) (var num-tests-run 0) (var suite-name 0) @@ -7,7 +10,7 @@ (var skip-count 0) (var skip-n 0) -(def is-verbose (os/getenv "VERBOSE")) +(var is-verbose (os/getenv "VERBOSE")) (defn- assert-no-tail "Override's the default assert with some nice error handling." diff --git a/test/suite-compile.janet b/test/suite-compile.janet index 96326ad4..b3c280b0 100644 --- a/test/suite-compile.janet +++ b/test/suite-compile.janet @@ -46,8 +46,10 @@ # Edge case should cause old compilers to fail due to # if statement optimization # 17283241 +(setdyn *lint-warn* :relaxed) (var var-a 1) (var var-b (if false 2 (string "hello"))) +(setdyn *lint-warn* nil) (assert (= var-b "hello") "regression 1") diff --git a/test/suite-ffi.janet b/test/suite-ffi.janet index 45bcd0ae..73e8e07a 100644 --- a/test/suite-ffi.janet +++ b/test/suite-ffi.janet @@ -21,7 +21,7 @@ (import ./helper :prefix "" :exit true) (start-suite) -(def has-ffi (dyn 'ffi/native)) +(var has-ffi (dyn 'ffi/native)) (def has-full-ffi (and has-ffi (when-let [entry (dyn 'ffi/calling-conventions)] diff --git a/test/suite-filewatch.janet b/test/suite-filewatch.janet index 1c77fa65..fb5e3894 100644 --- a/test/suite-filewatch.janet +++ b/test/suite-filewatch.janet @@ -24,8 +24,8 @@ (assert true) (def chan (ev/chan 1000)) -(def is-win (or (= :mingw (os/which)) (= :windows (os/which)))) -(def is-linux (= :linux (os/which))) +(var is-win (or (= :mingw (os/which)) (= :windows (os/which)))) +(var is-linux (= :linux (os/which))) # If not supported, exit early (def [supported msg] (protect (filewatch/new chan))) From 70fb13eb481c6aa8cd4200b91ebde579acffdb5f Mon Sep 17 00:00:00 2001 From: Calvin Rose Date: Sat, 10 Jan 2026 12:35:41 -0600 Subject: [PATCH 084/307] Fix -w flag with repl. --- src/boot/boot.janet | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/boot/boot.janet b/src/boot/boot.janet index a7caa046..207c62fa 100644 --- a/src/boot/boot.janet +++ b/src/boot/boot.janet @@ -4882,7 +4882,7 @@ (setdyn *err-color* (if colorize true)) (setdyn *doc-color* (if colorize true)) (setdyn *lint-error* error-level) - (setdyn *lint-warn* error-level) + (setdyn *lint-warn* warn-level) (when-let [profile.janet (dyn *profilepath*)] (dofile profile.janet :exit true :env env) (put env *current-file* nil)) From 10954fe0d74a7b916e5c8d6b2108c1d87787baa8 Mon Sep 17 00:00:00 2001 From: Calvin Rose Date: Sat, 10 Jan 2026 13:15:46 -0600 Subject: [PATCH 085/307] Reworking coloring logic for repl and CLI interface. --- src/boot/boot.janet | 20 +++++++++++++------- test/suite-compile.janet | 1 - 2 files changed, 13 insertions(+), 8 deletions(-) diff --git a/src/boot/boot.janet b/src/boot/boot.janet index 207c62fa..6c602dbf 100644 --- a/src/boot/boot.janet +++ b/src/boot/boot.janet @@ -3093,7 +3093,7 @@ (os/exit 1)) (put env :exit true) (def buf @"") - (with-dyns [*err* buf *err-color* false] + (with-dyns [*err* buf] (bad-parse x y)) (set exit-error (string/slice buf 0 -2))) (defn bc [&opt x y z a b] @@ -3102,7 +3102,7 @@ (os/exit 1)) (put env :exit true) (def buf @"") - (with-dyns [*err* buf *err-color* false] + (with-dyns [*err* buf] (bad-compile x nil z a b)) (set exit-error (string/slice buf 0 -2)) (set exit-fiber y)) @@ -4837,6 +4837,9 @@ (put env *args* subargs) (put env *lint-error* error-level) (put env *lint-warn* warn-level) + (put env *pretty-format* (if colorize "%.20Q" "%.20q")) + (put env *err-color* (if colorize true)) + (put env *doc-color* (if colorize true)) (when debug-flag (put env *debug* true) (put env *redef* true)) @@ -4846,6 +4849,9 @@ (put env *args* subargs) (put env *lint-error* error-level) (put env *lint-warn* warn-level) + (put env *pretty-format* (if colorize "%.20Q" "%.20q")) + (put env *err-color* (if colorize true)) + (put env *doc-color* (if colorize true)) (when debug-flag (put env *debug* true) (put env *redef* true)) @@ -4878,11 +4884,11 @@ (def getter (if raw-stdin getstdin getline)) (defn getchunk [buf p] (getter (getprompt p) buf env)) - (setdyn *pretty-format* (if colorize "%.20Q" "%.20q")) - (setdyn *err-color* (if colorize true)) - (setdyn *doc-color* (if colorize true)) - (setdyn *lint-error* error-level) - (setdyn *lint-warn* warn-level) + (put env *pretty-format* (if colorize "%.20Q" "%.20q")) + (put env *err-color* (if colorize true)) + (put env *doc-color* (if colorize true)) + (put env *lint-error* error-level) + (put env *lint-warn* warn-level) (when-let [profile.janet (dyn *profilepath*)] (dofile profile.janet :exit true :env env) (put env *current-file* nil)) diff --git a/test/suite-compile.janet b/test/suite-compile.janet index b3c280b0..5f3330fc 100644 --- a/test/suite-compile.janet +++ b/test/suite-compile.janet @@ -76,4 +76,3 @@ 10) (end-suite) - From 15cea60589d40cbfafb434ee8fc0a139bf64334c Mon Sep 17 00:00:00 2001 From: Calvin Rose Date: Sat, 10 Jan 2026 13:20:02 -0600 Subject: [PATCH 086/307] Revert "Reworking coloring logic for repl and CLI interface." This reverts commit 10954fe0d74a7b916e5c8d6b2108c1d87787baa8. --- src/boot/boot.janet | 20 +++++++------------- test/suite-compile.janet | 1 + 2 files changed, 8 insertions(+), 13 deletions(-) diff --git a/src/boot/boot.janet b/src/boot/boot.janet index 6c602dbf..207c62fa 100644 --- a/src/boot/boot.janet +++ b/src/boot/boot.janet @@ -3093,7 +3093,7 @@ (os/exit 1)) (put env :exit true) (def buf @"") - (with-dyns [*err* buf] + (with-dyns [*err* buf *err-color* false] (bad-parse x y)) (set exit-error (string/slice buf 0 -2))) (defn bc [&opt x y z a b] @@ -3102,7 +3102,7 @@ (os/exit 1)) (put env :exit true) (def buf @"") - (with-dyns [*err* buf] + (with-dyns [*err* buf *err-color* false] (bad-compile x nil z a b)) (set exit-error (string/slice buf 0 -2)) (set exit-fiber y)) @@ -4837,9 +4837,6 @@ (put env *args* subargs) (put env *lint-error* error-level) (put env *lint-warn* warn-level) - (put env *pretty-format* (if colorize "%.20Q" "%.20q")) - (put env *err-color* (if colorize true)) - (put env *doc-color* (if colorize true)) (when debug-flag (put env *debug* true) (put env *redef* true)) @@ -4849,9 +4846,6 @@ (put env *args* subargs) (put env *lint-error* error-level) (put env *lint-warn* warn-level) - (put env *pretty-format* (if colorize "%.20Q" "%.20q")) - (put env *err-color* (if colorize true)) - (put env *doc-color* (if colorize true)) (when debug-flag (put env *debug* true) (put env *redef* true)) @@ -4884,11 +4878,11 @@ (def getter (if raw-stdin getstdin getline)) (defn getchunk [buf p] (getter (getprompt p) buf env)) - (put env *pretty-format* (if colorize "%.20Q" "%.20q")) - (put env *err-color* (if colorize true)) - (put env *doc-color* (if colorize true)) - (put env *lint-error* error-level) - (put env *lint-warn* warn-level) + (setdyn *pretty-format* (if colorize "%.20Q" "%.20q")) + (setdyn *err-color* (if colorize true)) + (setdyn *doc-color* (if colorize true)) + (setdyn *lint-error* error-level) + (setdyn *lint-warn* warn-level) (when-let [profile.janet (dyn *profilepath*)] (dofile profile.janet :exit true :env env) (put env *current-file* nil)) diff --git a/test/suite-compile.janet b/test/suite-compile.janet index 5f3330fc..b3c280b0 100644 --- a/test/suite-compile.janet +++ b/test/suite-compile.janet @@ -76,3 +76,4 @@ 10) (end-suite) + From 7a8d8444fef5498a399d0242d1c36b5f85dd72b2 Mon Sep 17 00:00:00 2001 From: Calvin Rose Date: Sat, 10 Jan 2026 13:37:42 -0600 Subject: [PATCH 087/307] Simplify dynamic binding setup in cli-main. Instead of manually putting bindings into environment tables, just use `setdyn` to set the bindings based on the command line flags. This results in easier to understand behavior and prevents "swallowing" of options. For example, linting and debug flags should be set in the root-env by default so that they are used in all loaded modules. --- src/boot/boot.janet | 49 ++++++++++++++++----------------------------- 1 file changed, 17 insertions(+), 32 deletions(-) diff --git a/src/boot/boot.janet b/src/boot/boot.janet index 207c62fa..7c05f42a 100644 --- a/src/boot/boot.janet +++ b/src/boot/boot.janet @@ -3093,7 +3093,7 @@ (os/exit 1)) (put env :exit true) (def buf @"") - (with-dyns [*err* buf *err-color* false] + (with-dyns [*err* buf] (bad-parse x y)) (set exit-error (string/slice buf 0 -2))) (defn bc [&opt x y z a b] @@ -3102,7 +3102,7 @@ (os/exit 1)) (put env :exit true) (def buf @"") - (with-dyns [*err* buf *err-color* false] + (with-dyns [*err* buf] (bad-compile x nil z a b)) (set exit-error (string/slice buf 0 -2)) (set exit-fiber y)) @@ -4676,6 +4676,12 @@ "-lint-warn" "w" "-lint-error" "x"}) +(defn- apply-color + [colorize] + (setdyn *pretty-format* (if colorize "%.20Q" "%.20q")) + (setdyn *err-color* (if colorize true)) + (setdyn *doc-color* (if colorize true))) + (defn cli-main `Entrance for the Janet CLI tool. Call this function with the command line arguments as an array or tuple of strings to invoke the CLI interface.` @@ -4689,11 +4695,7 @@ (var raw-stdin false) (var handleopts true) (var exit-on-error true) - (var colorize true) - (var debug-flag false) (var compile-only false) - (var warn-level nil) - (var error-level nil) (var expect-image false) (when-let [jp (getenv-alias "JANET_PATH")] @@ -4703,9 +4705,10 @@ (module/add-syspath (get paths i))) (setdyn *syspath* (first paths))) (if-let [jprofile (getenv-alias "JANET_PROFILE")] (setdyn *profilepath* jprofile)) - (set colorize (and - (not (getenv-alias "NO_COLOR")) - (os/isatty stdout))) + (when (and + (not (getenv-alias "NO_COLOR")) + (os/isatty stdout)) + (apply-color true)) (defn- get-lint-level [i] @@ -4755,8 +4758,8 @@ "q" (fn [&] (set quiet true) 1) "i" (fn [&] (set expect-image true) 1) "k" (fn [&] (set compile-only true) (set exit-on-error false) 1) - "n" (fn [&] (set colorize false) 1) - "N" (fn [&] (set colorize true) 1) + "n" (fn [&] (apply-color false) 1) + "N" (fn [&] (apply-color true) 1) "m" (fn [i &] (setdyn *syspath* (in args (+ i 1))) 2) "c" (fn c-switch [i &] (def path (in args (+ i 1))) @@ -4812,9 +4815,9 @@ (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) - "w" (fn [i &] (set warn-level (get-lint-level i)) 2) - "x" (fn [i &] (set error-level (get-lint-level i)) 2) + "d" (fn [&] (setdyn *debug* true) (setdyn *redef* true) 1) + "w" (fn [i &] (setdyn *lint-warn* (get-lint-level i)) 2) + "x" (fn [i &] (setdyn *lint-error* (get-lint-level i)) 2) "R" (fn [&] (setdyn *profilepath* nil) 1)}) (defn- dohandler [n i &] @@ -4835,20 +4838,10 @@ (do (def env (load-image (slurp arg))) (put env *args* subargs) - (put env *lint-error* error-level) - (put env *lint-warn* warn-level) - (when debug-flag - (put env *debug* true) - (put env *redef* true)) (run-main env subargs arg)) (do (def env (make-env)) (put env *args* subargs) - (put env *lint-error* error-level) - (put env *lint-warn* warn-level) - (when debug-flag - (put env *debug* true) - (put env *redef* true)) (if compile-only (flycheck arg :exit exit-on-error :env env) (do @@ -4872,17 +4865,9 @@ (file/write stdout prompt) (file/flush stdout) (file/read stdin :line buf)) - (when debug-flag - (put env *debug* true) - (put env *redef* true)) (def getter (if raw-stdin getstdin getline)) (defn getchunk [buf p] (getter (getprompt p) buf env)) - (setdyn *pretty-format* (if colorize "%.20Q" "%.20q")) - (setdyn *err-color* (if colorize true)) - (setdyn *doc-color* (if colorize true)) - (setdyn *lint-error* error-level) - (setdyn *lint-warn* warn-level) (when-let [profile.janet (dyn *profilepath*)] (dofile profile.janet :exit true :env env) (put env *current-file* nil)) From 9bc308532f0a3fa698dbcb0d9ff815d64ad4ae4f Mon Sep 17 00:00:00 2001 From: Calvin Rose Date: Sat, 10 Jan 2026 13:42:45 -0600 Subject: [PATCH 088/307] Make varfn correctly handle verbose metadata. The macro would previously choke on `(varfn abc {:abc 123} ...)` due to mishandling of structs and tables as metadata. This caused issues when running `janet -d` with spork. --- CHANGELOG.md | 2 ++ src/boot/boot.janet | 18 ++++++++++-------- 2 files changed, 12 insertions(+), 8 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 4dcbd0ba..32d3fcfb 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -2,6 +2,8 @@ All notable changes to this project will be documented in this file. ## Unreleased - ??? +- Use color in script output if color is being used in REPL output. +- Fix `varfn` macros handling of extra metadata. - Add linting for unused bindings. - Add `janet_optuinteger` and `janet_optuinteger64` to the C API. - Add `cms` combinator to PEGs. diff --git a/src/boot/boot.janet b/src/boot/boot.janet index 7c05f42a..e2befd3d 100644 --- a/src/boot/boot.janet +++ b/src/boot/boot.janet @@ -2403,6 +2403,7 @@ (cond (keyword? m) (put metadata m true) (string? m) (put metadata :doc m) + (dictionary? m) (merge-into metadata m) (error (string "invalid metadata " m)))) (with-syms [entry old-entry f] ~(let [,old-entry (,dyn ',name)] @@ -4682,6 +4683,11 @@ (setdyn *err-color* (if colorize true)) (setdyn *doc-color* (if colorize true))) +(defn- getstdin [prompt buf _] + (file/write stdout prompt) + (file/flush stdout) + (file/read stdin :line buf)) + (defn cli-main `Entrance for the Janet CLI tool. Call this function with the command line arguments as an array or tuple of strings to invoke the CLI interface.` @@ -4705,10 +4711,10 @@ (module/add-syspath (get paths i))) (setdyn *syspath* (first paths))) (if-let [jprofile (getenv-alias "JANET_PROFILE")] (setdyn *profilepath* jprofile)) - (when (and - (not (getenv-alias "NO_COLOR")) - (os/isatty stdout)) - (apply-color true)) + (apply-color + (and + (not (getenv-alias "NO_COLOR")) + (os/isatty stdout))) (defn- get-lint-level [i] @@ -4861,10 +4867,6 @@ (when-let [custom-prompt (get env *repl-prompt*)] (break (custom-prompt p))) (def [line] (parser/where p)) (string "repl:" line ":" (parser/state p :delimiters) "> ")) - (defn getstdin [prompt buf _] - (file/write stdout prompt) - (file/flush stdout) - (file/read stdin :line buf)) (def getter (if raw-stdin getstdin getline)) (defn getchunk [buf p] (getter (getprompt p) buf env)) From f80690e4c9f80fb4fe10196114e83523ddb194d4 Mon Sep 17 00:00:00 2001 From: Calvin Rose Date: Sat, 10 Jan 2026 15:40:04 -0600 Subject: [PATCH 089/307] Version bump for wip 1.41.x --- Makefile | 4 ++-- meson.build | 2 +- src/conf/janetconf.h | 8 ++++---- 3 files changed, 7 insertions(+), 7 deletions(-) diff --git a/Makefile b/Makefile index 81b862e6..b60c2c90 100644 --- a/Makefile +++ b/Makefile @@ -220,9 +220,9 @@ build/%.bin.o: src/%.c $(JANET_HEADERS) $(JANET_LOCAL_HEADERS) Makefile ######################## ifeq ($(UNAME), Darwin) -SONAME=libjanet.1.40.dylib +SONAME=libjanet.1.41.dylib else -SONAME=libjanet.so.1.40 +SONAME=libjanet.so.1.41 endif ifeq ($(MINGW_COMPILER), clang) diff --git a/meson.build b/meson.build index 3b6b7de3..bdd827f5 100644 --- a/meson.build +++ b/meson.build @@ -20,7 +20,7 @@ project('janet', 'c', default_options : ['c_std=c99', 'build.c_std=c99', 'b_lundef=false', 'default_library=both'], - version : '1.40.1') + version : '1.41.0') # Global settings janet_path = join_paths(get_option('prefix'), get_option('libdir'), 'janet') diff --git a/src/conf/janetconf.h b/src/conf/janetconf.h index 0b7202bd..1bc830d9 100644 --- a/src/conf/janetconf.h +++ b/src/conf/janetconf.h @@ -4,10 +4,10 @@ #define JANETCONF_H #define JANET_VERSION_MAJOR 1 -#define JANET_VERSION_MINOR 40 -#define JANET_VERSION_PATCH 1 -#define JANET_VERSION_EXTRA "" -#define JANET_VERSION "1.40.1" +#define JANET_VERSION_MINOR 41 +#define JANET_VERSION_PATCH 0 +#define JANET_VERSION_EXTRA "-dev" +#define JANET_VERSION "1.41.0-dev" /* #define JANET_BUILD "local" */ From 59660172321c12da935ffc98ccebec0eb29bb0ee Mon Sep 17 00:00:00 2001 From: Peter Rippe Date: Tue, 30 Dec 2025 17:04:19 -0500 Subject: [PATCH 090/307] use u_char for IP_MULTICAST_TTL sockopt on BSD --- src/core/net.c | 14 ++++++++++++++ 1 file changed, 14 insertions(+) diff --git a/src/core/net.c b/src/core/net.c index 55023d91..c3181519 100644 --- a/src/core/net.c +++ b/src/core/net.c @@ -1073,6 +1073,9 @@ JANET_CORE_FN(cfun_net_setsockopt, } union { +#ifdef JANET_BSD + u_char v_uchar; +#endif int v_int; struct ip_mreq v_mreq; #ifndef JANET_NO_IPV6 @@ -1087,8 +1090,19 @@ JANET_CORE_FN(cfun_net_setsockopt, val.v_int = janet_getboolean(argv, 2); optlen = sizeof(val.v_int); } else if (st->type == JANET_NUMBER) { +#ifdef JANET_BSD + int v_int = janet_getinteger(argv, 2); + if (st->optname == IP_MULTICAST_TTL) { + val.v_uchar = v_int; + optlen = sizeof(val.v_uchar); + } else { + val.v_int = v_int; + optlen = sizeof(val.v_int); + } +#else val.v_int = janet_getinteger(argv, 2); optlen = sizeof(val.v_int); +#endif } else if (st->optname == IP_ADD_MEMBERSHIP || st->optname == IP_DROP_MEMBERSHIP) { const char *addr = janet_getcstring(argv, 2); memset(&val.v_mreq, 0, sizeof val.v_mreq); From 216c9799f54bd3e4beae1dd4e08e8b4e78299577 Mon Sep 17 00:00:00 2001 From: Calvin Rose Date: Mon, 12 Jan 2026 07:41:14 -0600 Subject: [PATCH 091/307] Some more work on socket testing and allow setting ipv4 vs. ipv6 on raw sockets. --- meson.build | 1 + src/core/net.c | 44 +++++++++++++++++++++++++++++++++++++------- test/helper.janet | 6 +++--- test/suite-net.janet | 35 +++++++++++++++++++++++++++++++++++ 4 files changed, 76 insertions(+), 10 deletions(-) create mode 100644 test/suite-net.janet diff --git a/meson.build b/meson.build index bdd827f5..742d6f09 100644 --- a/meson.build +++ b/meson.build @@ -288,6 +288,7 @@ test_files = [ 'test/suite-io.janet', 'test/suite-marsh.janet', 'test/suite-math.janet', + 'test/suite-net.janet', 'test/suite-os.janet', 'test/suite-parse.janet', 'test/suite-peg.janet', diff --git a/src/core/net.c b/src/core/net.c index c3181519..a9bcb0f9 100644 --- a/src/core/net.c +++ b/src/core/net.c @@ -120,6 +120,25 @@ static void janet_net_socknoblock(JSock s) { #endif } +/* Allow specifying IPV6 vs. IPV4 (or unix domain socket) */ +static int net_get_address_family(Janet x) { + if (janet_checktype(x, JANET_NIL)) { + return AF_UNSPEC; + } + if (janet_keyeq(x, "ipv4")) { + return AF_INET; + } + if (janet_keyeq(x, "ipv6")) { + return AF_INET6; + } +#ifndef JANET_WINDOWS + if (janet_keyeq(x, "unix")) { + return AF_UNIX; + } +#endif + return AF_UNSPEC; +} + /* State machine for async connect */ void net_callback_connect(JanetFiber *fiber, JanetAsyncEvent event) { @@ -596,10 +615,11 @@ JANET_CORE_FN(cfun_net_connect, } JANET_CORE_FN(cfun_net_socket, - "(net/socket &opt type)", + "(net/socket &opt type address-family)", "Creates a new unbound socket. Type is an optional keyword, " - "either a :stream (usually tcp), or :datagram (usually udp). The default is :stream.") { - janet_arity(argc, 0, 1); + "either a :stream (usually tcp), or :datagram (usually udp). The default is :stream. " + "`address-family` should be one of :ipv4 or :ipv6.") { + janet_arity(argc, 0, 2); int socktype = janet_get_sockettype(argv, argc, 0); @@ -610,7 +630,14 @@ JANET_CORE_FN(cfun_net_socket, memset(&hints, 0, sizeof(hints)); hints.ai_family = AF_UNSPEC; hints.ai_socktype = socktype; +#ifdef AI_NUMERICSERV + hints.ai_flags = AI_NUMERICSERV; /* Explicitly prevent name resolution */ +#else hints.ai_flags = 0; +#endif + if (argc >= 2) { + hints.ai_family = net_get_address_family(argv[1]); + } int status = getaddrinfo(NULL, "0", &hints, &ai); if (status) { janet_panicf("could not get address info: %s", gai_strerror(status)); @@ -1038,6 +1065,8 @@ static const struct sockopt_type sockopt_type_list[] = { #ifndef JANET_NO_IPV6 { "ipv6-join-group", IPPROTO_IPV6, IPV6_JOIN_GROUP, JANET_POINTER }, { "ipv6-leave-group", IPPROTO_IPV6, IPV6_LEAVE_GROUP, JANET_POINTER }, + { "ipv6-multicast-hops", IPPROTO_IPV6, IPV6_MULTICAST_HOPS, JANET_NUMBER }, + { "ipv6-unicast-hops", IPPROTO_IPV6, IPV6_UNICAST_HOPS, JANET_NUMBER }, #endif { NULL, 0, 0, JANET_POINTER } }; @@ -1054,7 +1083,10 @@ JANET_CORE_FN(cfun_net_setsockopt, "- :ip-add-membership string\n" "- :ip-drop-membership string\n" "- :ipv6-join-group string\n" - "- :ipv6-leave-group string\n") { + "- :ipv6-leave-group string\n" + "- :ipv6-multicast-hops number\n" + "- :ipv6-unicast-hops number\n" + ) { janet_arity(argc, 3, 3); JanetStream *stream = janet_getabstract(argv, 0, &janet_stream_type); janet_stream_flags(stream, JANET_STREAM_SOCKET); @@ -1073,9 +1105,7 @@ JANET_CORE_FN(cfun_net_setsockopt, } union { -#ifdef JANET_BSD - u_char v_uchar; -#endif + unsigned char v_uchar; int v_int; struct ip_mreq v_mreq; #ifndef JANET_NO_IPV6 diff --git a/test/helper.janet b/test/helper.janet index c0b89b78..293b4ccc 100644 --- a/test/helper.janet +++ b/test/helper.janet @@ -1,7 +1,7 @@ # Helper code for running tests # Turn on strict linting by default in test suite. -(put root-env *lint-warn* :strict) +#(put root-env *lint-warn* :strict) (var num-tests-passed 0) (var num-tests-run 0) @@ -67,8 +67,8 @@ (def e (gensym)) (def f (gensym)) (if is-verbose - ~(try (do ,;forms (,assert true ,msg)) ([,e ,f] (,assert false ,msg) (,debug/stacktrace ,f ,e "\e[31m✘\e[0m "))) - ~(try (do ,;forms (,assert true ,msg)) ([_] (,assert false ,msg))))) + ~(try (do ,;forms (as-macro ,assert true ,msg)) ([,e ,f] (as-macro ,assert false ,msg) (,debug/stacktrace ,f ,e "\e[31m✘\e[0m "))) + ~(try (do ,;forms (as-macro ,assert true ,msg)) ([_] (as-macro ,assert false ,msg))))) (defn start-suite [&opt x] (default x (dyn :current-file)) diff --git a/test/suite-net.janet b/test/suite-net.janet new file mode 100644 index 00000000..1cf59fab --- /dev/null +++ b/test/suite-net.janet @@ -0,0 +1,35 @@ +# Copyright (c) 2026 Calvin Rose & contributors +# +# 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 the Software without restriction, including without limitation the +# rights to use, copy, modify, merge, publish, distribute, sublicense, and/or +# sell copies of the Software, and to permit persons to whom the Software is +# furnished to do so, subject to the following conditions: +# +# The above copyright notice and this permission notice shall be included in +# all copies or substantial portions of the Software. +# +# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +# IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +# FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +# AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +# LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING +# FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS +# IN THE SOFTWARE. + +# Expand on ev testing with some extra network protocol testing. + +(import ./helper :prefix "" :exit true) +(start-suite) + +# Smoke +(assert true) + +# Raw socket testing +(def s (net/socket :datagram :ipv4)) +(assert-no-error "multicast ipv4" (net/setsockopt s :ip-multicast-ttl 255)) +(def s6 (net/socket :datagram :ipv6)) +(assert-no-error "multicast ipv6" (net/setsockopt s6 :ipv6-multicast-hops 255)) + +(end-suite) From d35f1894466ee8fc6b3770825d2b4499f736fc0d Mon Sep 17 00:00:00 2001 From: Calvin Rose Date: Mon, 12 Jan 2026 07:57:41 -0600 Subject: [PATCH 092/307] Turn strict linting back on. --- test/helper.janet | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/test/helper.janet b/test/helper.janet index 293b4ccc..aadf1084 100644 --- a/test/helper.janet +++ b/test/helper.janet @@ -1,7 +1,7 @@ # Helper code for running tests # Turn on strict linting by default in test suite. -#(put root-env *lint-warn* :strict) +(put root-env *lint-warn* :strict) (var num-tests-passed 0) (var num-tests-run 0) From 0a5ff208a80934657847f190e75dcf886433778e Mon Sep 17 00:00:00 2001 From: Calvin Rose Date: Mon, 12 Jan 2026 08:00:11 -0600 Subject: [PATCH 093/307] Disable ipv6 for now in main test. --- test/suite-net.janet | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/test/suite-net.janet b/test/suite-net.janet index 1cf59fab..86433168 100644 --- a/test/suite-net.janet +++ b/test/suite-net.janet @@ -29,7 +29,7 @@ # Raw socket testing (def s (net/socket :datagram :ipv4)) (assert-no-error "multicast ipv4" (net/setsockopt s :ip-multicast-ttl 255)) -(def s6 (net/socket :datagram :ipv6)) -(assert-no-error "multicast ipv6" (net/setsockopt s6 :ipv6-multicast-hops 255)) +#(def s6 (net/socket :datagram :ipv6)) +#(assert-no-error "multicast ipv6" (net/setsockopt s6 :ipv6-multicast-hops 255)) (end-suite) From 19b5502f508d352ff20141c54647baf250b2407c Mon Sep 17 00:00:00 2001 From: Calvin Rose Date: Wed, 14 Jan 2026 17:33:10 -0600 Subject: [PATCH 094/307] Simplify `ev/gather` and add `ev/go-gather`. This allows using `ev/gather` semantics on a dynamic number of tasks and not relying on the macro magic. --- src/boot/boot.janet | 33 +++++++++++++++++++++------------ 1 file changed, 21 insertions(+), 12 deletions(-) diff --git a/src/boot/boot.janet b/src/boot/boot.janet index e2befd3d..aaadb66c 100644 --- a/src/boot/boot.janet +++ b/src/boot/boot.janet @@ -3938,23 +3938,32 @@ (cancel-all chan fibers "sibling canceled") (propagate (fiber/last-value fiber) fiber)))))) + (defn ev/go-gather + ``` + Run a dyanmic number of fibers in parallel and resume the current fiber after they complete. Takes + an array of functions or fibers, `thunks`, that will be run via `ev/go` in another task. + Returns the gathered results in an array. + ``` + [thunks] + (def fset @{}) + (def chan (ev/chan)) + (def results @[]) + (each thunk thunks + (def ftemp (ev/go thunk nil chan)) + (array/push results ftemp) + (put fset ftemp ftemp)) + (wait-for-fibers chan fset) + (for i 0 (length results) # avoid extra copy from map + (set (results i) (fiber/last-value (in results i)))) + results) + (defmacro ev/gather `` - Run a number of fibers in parallel on the event loop, and join when they complete. + Run a number of fibers in parallel and resume the current fiber after they complete. Returns the gathered results in an array. `` [& bodies] - (with-syms [chan res fset ftemp] - ~(do - (def ,fset @{}) - (def ,chan (,ev/chan)) - (def ,res @[]) - ,;(seq [[i body] :pairs bodies] - ~(do - (def ,ftemp (,ev/go (fn :ev/gather [] (put ,res ,i ,body)) nil ,chan)) - (,put ,fset ,ftemp ,ftemp))) - (,wait-for-fibers ,chan ,fset) - ,res)))) + ~(,ev/go-gather ,(seq [body :in bodies] ~(fn :ev/gather [] ,body))))) (compwhen (dyn 'net/listen) (defn net/server From 8fe284b5eb13479edcf083da6304f88447a339c2 Mon Sep 17 00:00:00 2001 From: Calvin Rose Date: Thu, 15 Jan 2026 13:04:11 -0600 Subject: [PATCH 095/307] Fix pathologically slow insertion pattern. The correct resizing behavior for arrays and buffers was done for `janet_putindex` but not for `janet_put`. This change copies the correct behavior to `janet_put`. --- CHANGELOG.md | 1 + src/core/value.c | 6 ++++-- 2 files changed, 5 insertions(+), 2 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 32d3fcfb..5c90c8a0 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -2,6 +2,7 @@ All notable changes to this project will be documented in this file. ## Unreleased - ??? +- Add `ev/go-gather` for a dynamic `ev/gather`. - Use color in script output if color is being used in REPL output. - Fix `varfn` macros handling of extra metadata. - Add linting for unused bindings. diff --git a/src/core/value.c b/src/core/value.c index 7f71986b..5c86479e 100644 --- a/src/core/value.c +++ b/src/core/value.c @@ -768,7 +768,8 @@ void janet_put(Janet ds, Janet key, Janet value) { JanetArray *array = janet_unwrap_array(ds); int32_t index = getter_checkint(type, key, INT32_MAX - 1); if (index >= array->count) { - janet_array_setcount(array, index + 1); + janet_array_ensure(array, index + 1, 2); + array->count = index + 1; } array->data[index] = value; break; @@ -779,7 +780,8 @@ void janet_put(Janet ds, Janet key, Janet value) { if (!janet_checkint(value)) janet_panicf("can only put integers in buffers, got %v", value); if (index >= buffer->count) { - janet_buffer_setcount(buffer, index + 1); + janet_buffer_ensure(buffer, index + 1, 2); + buffer->count = index + 1; } buffer->data[index] = (uint8_t)(janet_unwrap_integer(value) & 0xFF); break; From 5819408715cbd589c8d783de4c366a047d7c63c6 Mon Sep 17 00:00:00 2001 From: Nora Gruner <4781070+nfgrusk@users.noreply.github.com> Date: Sat, 17 Jan 2026 00:26:16 +0100 Subject: [PATCH 096/307] Add omitted word "is" in docstrings. --- src/boot/boot.janet | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/src/boot/boot.janet b/src/boot/boot.janet index aaadb66c..f0c02dfd 100644 --- a/src/boot/boot.janet +++ b/src/boot/boot.janet @@ -105,9 +105,9 @@ (defn keyword? "Check if x is a keyword." [x] (= (type x) :keyword)) (defn buffer? "Check if x is a buffer." [x] (= (type x) :buffer)) (defn function? "Check if x is a function (not a cfunction)." [x] (= (type x) :function)) -(defn cfunction? "Check if x a cfunction." [x] (= (type x) :cfunction)) -(defn table? "Check if x a table." [x] (= (type x) :table)) -(defn struct? "Check if x a struct." [x] (= (type x) :struct)) +(defn cfunction? "Check if x is a cfunction." [x] (= (type x) :cfunction)) +(defn table? "Check if x is a table." [x] (= (type x) :table)) +(defn struct? "Check if x is a struct." [x] (= (type x) :struct)) (defn array? "Check if x is an array." [x] (= (type x) :array)) (defn tuple? "Check if x is a tuple." [x] (= (type x) :tuple)) (defn boolean? "Check if x is a boolean." [x] (= (type x) :boolean)) From eb825772bbd6c27873e33ad754fcfeb995b5f3ec Mon Sep 17 00:00:00 2001 From: Calvin Rose Date: Sat, 17 Jan 2026 07:29:03 -0600 Subject: [PATCH 097/307] Add more information to process creation failure on windows. --- mkfile => plan9.mk | 0 src/core/os.c | 22 +++++++++++++++++++++- 2 files changed, 21 insertions(+), 1 deletion(-) rename mkfile => plan9.mk (100%) diff --git a/mkfile b/plan9.mk similarity index 100% rename from mkfile rename to plan9.mk diff --git a/src/core/os.c b/src/core/os.c index a241cf14..63910e47 100644 --- a/src/core/os.c +++ b/src/core/os.c @@ -1297,6 +1297,7 @@ static Janet os_execute_impl(int32_t argc, Janet *argv, JanetExecuteMode mode) { } int cp_failed = 0; + DWORD cp_error_code = 0; if (!CreateProcess(janet_flag_at(flags, 1) ? NULL : path, (char *) buf->data, /* Single CLI argument */ &saAttr, /* no proc inheritance */ @@ -1308,6 +1309,7 @@ static Janet os_execute_impl(int32_t argc, Janet *argv, JanetExecuteMode mode) { &startupInfo, &processInfo)) { cp_failed = 1; + cp_error_code = GetLastError(); } if (pipe_in != JANET_HANDLE_NONE) CloseHandle(pipe_in); @@ -1317,7 +1319,25 @@ static Janet os_execute_impl(int32_t argc, Janet *argv, JanetExecuteMode mode) { os_execute_cleanup(envp, NULL); if (cp_failed) { - janet_panic("failed to create process"); + char msgbuf[256]; + msgbuf[0] = '\0'; + FormatMessage(FORMAT_MESSAGE_FROM_SYSTEM | FORMAT_MESSAGE_IGNORE_INSERTS, + NULL, + cp_error_code, + MAKELANGID(LANG_NEUTRAL, SUBLANG_DEFAULT), + msgbuf, + sizeof(msgbuf), + NULL); + if (!*msgbuf) sprintf(msgbuf, "%d", code); + char *c = msgbuf; + while (*c) { + if (*c == '\n' || *c == '\r') { + *c = '\0'; + break; + } + c++; + } + janet_panicf("failed to create process: %s", janet_cstringv(msgbuf)); } pHandle = processInfo.hProcess; From d5a014baff405d288cc2faa0bd20e9217eaa4352 Mon Sep 17 00:00:00 2001 From: Calvin Rose Date: Sat, 17 Jan 2026 07:43:43 -0600 Subject: [PATCH 098/307] Fix reference. --- src/core/os.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/core/os.c b/src/core/os.c index 63910e47..e2c8731c 100644 --- a/src/core/os.c +++ b/src/core/os.c @@ -1328,7 +1328,7 @@ static Janet os_execute_impl(int32_t argc, Janet *argv, JanetExecuteMode mode) { msgbuf, sizeof(msgbuf), NULL); - if (!*msgbuf) sprintf(msgbuf, "%d", code); + if (!*msgbuf) sprintf(msgbuf, "%d", cp_error_code); char *c = msgbuf; while (*c) { if (*c == '\n' || *c == '\r') { From 4575cefb7e561c9752163acadf7ea8c66bf44975 Mon Sep 17 00:00:00 2001 From: Calvin Rose Date: Sat, 17 Jan 2026 10:11:56 -0600 Subject: [PATCH 099/307] Add shorthand for os test. --- src/core/os.c | 13 ++++++++----- test/suite-os.janet | 4 ++++ 2 files changed, 12 insertions(+), 5 deletions(-) diff --git a/src/core/os.c b/src/core/os.c index e2c8731c..3bd647d5 100644 --- a/src/core/os.c +++ b/src/core/os.c @@ -142,8 +142,8 @@ static void janet_unlock_environ(void) { #define janet_stringify(x) janet_stringify1(x) JANET_CORE_FN(os_which, - "(os/which)", - "Check the current operating system. Returns one of:\n\n" + "(os/which &opt test)", + "Check the current operating system. If `test` is nil or unset, Returns one of:\n\n" "* :windows\n\n" "* :mingw\n\n" "* :cygwin\n\n" @@ -156,9 +156,12 @@ JANET_CORE_FN(os_which, "* :dragonfly\n\n" "* :bsd\n\n" "* :posix - A POSIX compatible system (default)\n\n" - "May also return a custom keyword specified at build time.") { - janet_fixarity(argc, 0); - (void) argv; + "May also return a custom keyword specified at build time. Is `test` is truthy, will check if the current operating system equals `test` and return true if they are the same, false otherwise.") { + janet_arity(argc, 0, 1); + if (argc == 1 && janet_truthy(argv[0])) { + janet_getkeyword(argv, 0); /* Constrain to keywords */ + return janet_wrap_boolean(janet_equals(argv[0], os_which(0, NULL))); + } #if defined(JANET_OS_NAME) return janet_ckeywordv(janet_stringify(JANET_OS_NAME)); #elif defined(JANET_MINGW) diff --git a/test/suite-os.janet b/test/suite-os.janet index 6b615d0f..fdfa80bd 100644 --- a/test/suite-os.janet +++ b/test/suite-os.janet @@ -178,4 +178,8 @@ (assert-error "os/realpath errors when path does not exist" (os/realpath "abc123def456")) +# os/which changes +(assert (os/which (os/which)) "os/which 1 arg") +(assert (not (os/which :gobbledegook)) "os/which 2") + (end-suite) From 67932bbaed3cb0caa001ec4f76bb0d1ea68b861c Mon Sep 17 00:00:00 2001 From: Calvin Rose Date: Sat, 17 Jan 2026 17:04:56 -0600 Subject: [PATCH 100/307] On windows, make sure to use FILE_ATTRIBUTE_NORMAL by default. --- src/core/os.c | 20 ++++++++++++-------- 1 file changed, 12 insertions(+), 8 deletions(-) diff --git a/src/core/os.c b/src/core/os.c index 3bd647d5..b7f37674 100644 --- a/src/core/os.c +++ b/src/core/os.c @@ -2716,7 +2716,8 @@ JANET_CORE_FN(os_open, DWORD desiredAccess = 0; DWORD shareMode = 0; DWORD creationDisp = 0; - DWORD flagsAndAttributes = FILE_FLAG_OVERLAPPED; + DWORD fileFlags = FILE_FLAG_OVERLAPPED; + DWORD fileAttributes = 0; /* We map unix-like open flags to the creationDisp parameter */ int creatUnix = 0; #define OCREAT 1 @@ -2758,22 +2759,22 @@ JANET_CORE_FN(os_open, shareMode |= FILE_SHARE_WRITE; break; case 'H': - flagsAndAttributes |= FILE_ATTRIBUTE_HIDDEN; + fileAttributes |= FILE_ATTRIBUTE_HIDDEN; break; case 'O': - flagsAndAttributes |= FILE_ATTRIBUTE_READONLY; + fileAttributes |= FILE_ATTRIBUTE_READONLY; break; case 'F': - flagsAndAttributes |= FILE_ATTRIBUTE_OFFLINE; + fileAttributes |= FILE_ATTRIBUTE_OFFLINE; break; case 'T': - flagsAndAttributes |= FILE_ATTRIBUTE_TEMPORARY; + fileAttributes |= FILE_ATTRIBUTE_TEMPORARY; break; case 'd': - flagsAndAttributes |= FILE_FLAG_DELETE_ON_CLOSE; + fileFlags |= FILE_FLAG_DELETE_ON_CLOSE; break; case 'b': - flagsAndAttributes |= FILE_FLAG_NO_BUFFERING; + fileFlags |= FILE_FLAG_NO_BUFFERING; break; /* we could potentially add more here - * https://docs.microsoft.com/en-us/windows/win32/api/fileapi/nf-fileapi-createfilea @@ -2799,7 +2800,10 @@ JANET_CORE_FN(os_open, creationDisp = TRUNCATE_EXISTING; break; } - fd = CreateFileA(path, desiredAccess, shareMode, NULL, creationDisp, flagsAndAttributes, NULL); + if (fileAttributes == 0) { + fileAttributes = FILE_ATTRIBUTE_NORMAL; + } + fd = CreateFileA(path, desiredAccess, shareMode, NULL, creationDisp, fileFlags | fileAttributes, NULL); if (fd == INVALID_HANDLE_VALUE) janet_panicv(janet_ev_lasterr()); #else int open_flags = O_NONBLOCK; From 4aa5615a37dc08e824e56354bd8759f4d8a8779f Mon Sep 17 00:00:00 2001 From: Calvin Rose Date: Sat, 17 Jan 2026 18:19:17 -0600 Subject: [PATCH 101/307] On windows, streams created with os/open improved. We now set bInheritHandles to true when creating them, which makes it possible to pass them to subprocesses. Before, it would fail silently in strange ways often simply losing data. Also added flags to disable OVERLAPPED_IO on windows and O_NONBLOCK on posix. --- src/core/os.c | 26 +++++++++++++++++++++++--- 1 file changed, 23 insertions(+), 3 deletions(-) diff --git a/src/core/os.c b/src/core/os.c index b7f37674..551a92f8 100644 --- a/src/core/os.c +++ b/src/core/os.c @@ -2691,10 +2691,11 @@ JANET_CORE_FN(os_open, " * :c - create a new file (O\\_CREATE)\n" " * :e - fail if the file exists (O\\_EXCL)\n" " * :t - shorten an existing file to length 0 (O\\_TRUNC)\n\n" + " * :a - append to a file (O\\_APPEND on posix, FILE_APPEND_DATA on windows)\n" "Posix-only flags:\n\n" - " * :a - append to a file (O\\_APPEND)\n" " * :x - O\\_SYNC\n" " * :C - O\\_NOCTTY\n\n" + " * :N - Turn off O\\_NONBLOCK and disable ev reading/writing\n\n" "Windows-only flags:\n\n" " * :R - share reads (FILE\\_SHARE\\_READ)\n" " * :W - share writes (FILE\\_SHARE\\_WRITE)\n" @@ -2704,12 +2705,14 @@ JANET_CORE_FN(os_open, " * :F - FILE\\_ATTRIBUTE\\_OFFLINE\n" " * :T - FILE\\_ATTRIBUTE\\_TEMPORARY\n" " * :d - FILE\\_FLAG\\_DELETE\\_ON\\_CLOSE\n" + " * :V - Turn off FILE\\_FLAG\\_OVERLAPPED and disable ev reading/writing\n" " * :b - FILE\\_FLAG\\_NO\\_BUFFERING\n") { janet_arity(argc, 1, 3); const char *path = janet_getcstring(argv, 0); const uint8_t *opt_flags = janet_optkeyword(argv, argc, 1, (const uint8_t *) "r"); jmode_t mode = os_optmode(argc, argv, 2, 0666); uint32_t stream_flags = 0; + int disable_stream_mode = 0; JanetHandle fd; #ifdef JANET_WINDOWS (void) mode; @@ -2737,6 +2740,11 @@ JANET_CORE_FN(os_open, stream_flags |= JANET_STREAM_WRITABLE; janet_sandbox_assert(JANET_SANDBOX_FS_WRITE); break; + case 'a': + desiredAccess |= FILE_APPEND_DATA; + stream_flags |= JANET_STREAM_WRITABLE; + janet_sandbox_assert(JANET_SANDBOX_FS_WRITE); + break; case 'c': creatUnix |= OCREAT; janet_sandbox_assert(JANET_SANDBOX_FS_WRITE); @@ -2776,6 +2784,10 @@ JANET_CORE_FN(os_open, case 'b': fileFlags |= FILE_FLAG_NO_BUFFERING; break; + case 'V': + fileFlags &= ~FILE_FLAG_OVERLAPPED; + disable_stream_mode = 1; + break; /* we could potentially add more here - * https://docs.microsoft.com/en-us/windows/win32/api/fileapi/nf-fileapi-createfilea */ @@ -2803,7 +2815,11 @@ JANET_CORE_FN(os_open, if (fileAttributes == 0) { fileAttributes = FILE_ATTRIBUTE_NORMAL; } - fd = CreateFileA(path, desiredAccess, shareMode, NULL, creationDisp, fileFlags | fileAttributes, NULL); + SECURITY_ATTRIBUTES saAttr; + memset(&saAttr, 0, sizeof(saAttr)); + saAttr.nLength = sizeof(saAttr); + saAttr.bInheritHandle = TRUE; /* Needed to do interesting things with file */ + fd = CreateFileA(path, desiredAccess, shareMode, &saAttr, creationDisp, fileFlags | fileAttributes, NULL); if (fd == INVALID_HANDLE_VALUE) janet_panicv(janet_ev_lasterr()); #else int open_flags = O_NONBLOCK; @@ -2847,6 +2863,10 @@ JANET_CORE_FN(os_open, case 'a': open_flags |= O_APPEND; break; + case 'N': + open_flags &= ~O_NONBLOCK; + disable_stream_mode = 1; + break; } } /* If both read and write, fix up to O_RDWR */ @@ -2863,7 +2883,7 @@ JANET_CORE_FN(os_open, } while (fd == -1 && errno == EINTR); if (fd == -1) janet_panicv(janet_ev_lasterr()); #endif - return janet_wrap_abstract(janet_stream(fd, stream_flags, NULL)); + return janet_wrap_abstract(janet_stream(fd, disable_stream_mode ? 0 : stream_flags, NULL)); } JANET_CORE_FN(os_pipe, From c1dff351d9979de5dbb91b16d685d8ed5d55fd06 Mon Sep 17 00:00:00 2001 From: Calvin Rose Date: Sat, 17 Jan 2026 19:59:28 -0600 Subject: [PATCH 102/307] Improve stacktraces for macros that use defer. Previously, they would show up as `defer` in stack traces. --- src/boot/boot.janet | 21 +++++++++++++-------- 1 file changed, 13 insertions(+), 8 deletions(-) diff --git a/src/boot/boot.janet b/src/boot/boot.janet index f0c02dfd..c9302fe1 100644 --- a/src/boot/boot.janet +++ b/src/boot/boot.janet @@ -370,19 +370,24 @@ (++ i)) ~(let (,;accum) ,;body)) -(defmacro defer - ``Run `form` unconditionally after `body`, even if the body throws an error. - Will also run `form` if a user signal 0-4 is received.`` - [form & body] +(defn- defer-impl + "Defer but allow custom name for stack traces" + [name form body] (with-syms [f r] ~(do - (def ,f (,fiber/new (fn :defer [] ,;body) :ti)) + (def ,f (,fiber/new (fn ,name [] ,;body) :ti)) (def ,r (,resume ,f)) ,form (if (= (,fiber/status ,f) :dead) ,r (,propagate ,r ,f))))) +(defmacro defer + ``Run `form` unconditionally after `body`, even if the body throws an error. + Will also run `form` if a user signal 0-4 is received.`` + [form & body] + (defer-impl :defer form body)) + (defmacro edefer ``Run `form` after `body` in the case that body terminates abnormally (an error or user signal 0-4). Otherwise, return last form in `body`.`` @@ -436,14 +441,14 @@ [[binding ctor dtor] & body] ~(do (def ,binding ,ctor) - ,(apply defer [(or dtor :close) binding] body))) + ,(defer-impl :with [(or dtor :close) binding] body))) (defmacro when-with ``Similar to with, but if binding is false or nil, returns nil without evaluating the body. Otherwise, the same as `with`.`` [[binding ctor dtor] & body] ~(if-let [,binding ,ctor] - ,(apply defer [(or dtor :close) binding] body))) + ,(defer-impl :when-with [(or dtor :close) binding] body))) (defmacro if-with ``Similar to `with`, but if binding is false or nil, evaluates @@ -451,7 +456,7 @@ `ctor` is bound to binding.`` [[binding ctor dtor] truthy &opt falsey] ~(if-let [,binding ,ctor] - ,(apply defer [(or dtor :close) binding] [truthy]) + ,(defer-impl :if-with [(or dtor :close) binding] [truthy]) ,falsey)) (defn- for-var-template From 6549903c513c3b0f2f5520738e09d2c02fde1572 Mon Sep 17 00:00:00 2001 From: Calvin Rose Date: Sat, 17 Jan 2026 20:07:12 -0600 Subject: [PATCH 103/307] Add option to explicity inherit handles rather than implicitly. --- src/core/os.c | 9 ++++++++- test/suite-os.janet | 2 +- 2 files changed, 9 insertions(+), 2 deletions(-) diff --git a/src/core/os.c b/src/core/os.c index 551a92f8..27e3f04b 100644 --- a/src/core/os.c +++ b/src/core/os.c @@ -2706,6 +2706,7 @@ JANET_CORE_FN(os_open, " * :T - FILE\\_ATTRIBUTE\\_TEMPORARY\n" " * :d - FILE\\_FLAG\\_DELETE\\_ON\\_CLOSE\n" " * :V - Turn off FILE\\_FLAG\\_OVERLAPPED and disable ev reading/writing\n" + " * :I - set bInheritHandle on the created file so it can be passed to other processes.\n" " * :b - FILE\\_FLAG\\_NO\\_BUFFERING\n") { janet_arity(argc, 1, 3); const char *path = janet_getcstring(argv, 0); @@ -2716,6 +2717,7 @@ JANET_CORE_FN(os_open, JanetHandle fd; #ifdef JANET_WINDOWS (void) mode; + int inherrited_handle = 0; DWORD desiredAccess = 0; DWORD shareMode = 0; DWORD creationDisp = 0; @@ -2784,6 +2786,9 @@ JANET_CORE_FN(os_open, case 'b': fileFlags |= FILE_FLAG_NO_BUFFERING; break; + case 'I': + inherrited_handle = 1; + break; case 'V': fileFlags &= ~FILE_FLAG_OVERLAPPED; disable_stream_mode = 1; @@ -2818,7 +2823,9 @@ JANET_CORE_FN(os_open, SECURITY_ATTRIBUTES saAttr; memset(&saAttr, 0, sizeof(saAttr)); saAttr.nLength = sizeof(saAttr); - saAttr.bInheritHandle = TRUE; /* Needed to do interesting things with file */ + if (inherrited_handle) { + saAttr.bInheritHandle = TRUE; /* Needed to do interesting things with file */ + } fd = CreateFileA(path, desiredAccess, shareMode, &saAttr, creationDisp, fileFlags | fileAttributes, NULL); if (fd == INVALID_HANDLE_VALUE) janet_panicv(janet_ev_lasterr()); #else diff --git a/test/suite-os.janet b/test/suite-os.janet index fdfa80bd..3cd82cd7 100644 --- a/test/suite-os.janet +++ b/test/suite-os.janet @@ -166,7 +166,7 @@ (def path (if (or (= os :mingw) (= os :windows)) "NUL" "/dev/null")) - (os/open path :w)) + (os/open path :wW)) (with [dn (devnull)] (os/execute [;run janet "-e" From 06f099d7f9377f6ec6f04a8fe3db11548fac2ec4 Mon Sep 17 00:00:00 2001 From: Calvin Rose Date: Sat, 17 Jan 2026 20:25:06 -0600 Subject: [PATCH 104/307] Make sure to run test with old flags as well. --- test/suite-os.janet | 17 ++++++++++++++++- 1 file changed, 16 insertions(+), 1 deletion(-) diff --git a/test/suite-os.janet b/test/suite-os.janet index 3cd82cd7..951f4018 100644 --- a/test/suite-os.janet +++ b/test/suite-os.janet @@ -166,7 +166,22 @@ (def path (if (or (= os :mingw) (= os :windows)) "NUL" "/dev/null")) - (os/open path :wW)) + (os/open path :w)) + (with [dn (devnull)] + (os/execute [;run janet + "-e" + "(print :foo) (eprint :bar)"] + :px + {:out dn :err dn}))) + +# os/execute IO redirection with more windows flags +(assert-no-error "IO redirection more windows flags" + (defn devnull [] + (def os (os/which)) + (def path (if (or (= os :mingw) (= os :windows)) + "NUL" + "/dev/null")) + (os/open path :wWI)) (with [dn (devnull)] (os/execute [;run janet "-e" From 268864b072ff46a9abe5ebc9c5f6096339a2c943 Mon Sep 17 00:00:00 2001 From: Calvin Rose Date: Sat, 17 Jan 2026 22:15:03 -0600 Subject: [PATCH 105/307] Don't use new flag on mingw test. --- test/suite-os.janet | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/test/suite-os.janet b/test/suite-os.janet index 951f4018..5bfde34f 100644 --- a/test/suite-os.janet +++ b/test/suite-os.janet @@ -181,7 +181,7 @@ (def path (if (or (= os :mingw) (= os :windows)) "NUL" "/dev/null")) - (os/open path :wWI)) + (os/open path (if (= os :windows) :wWI :wW))) (with [dn (devnull)] (os/execute [;run janet "-e" From 78a785175a7b6d2513d819c50d08838a0d8c453e Mon Sep 17 00:00:00 2001 From: Calvin Rose Date: Sat, 17 Jan 2026 22:37:35 -0600 Subject: [PATCH 106/307] Allow redirecting :err to :out with os/execute, no just os/spawn. --- src/core/os.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/core/os.c b/src/core/os.c index 27e3f04b..d7510029 100644 --- a/src/core/os.c +++ b/src/core/os.c @@ -1214,7 +1214,7 @@ static Janet os_execute_impl(int32_t argc, Janet *argv, JanetExecuteMode mode) { if (is_spawn && janet_keyeq(maybe_stderr, "pipe")) { new_err = make_pipes(&pipe_err, 0, &pipe_errflag); pipe_owner_flags |= JANET_PROC_OWNS_STDERR; - } else if (is_spawn && janet_keyeq(maybe_stderr, "out")) { + } else if (janet_keyeq(maybe_stderr, "out")) { stderr_is_stdout = 1; } else if (!janet_checktype(maybe_stderr, JANET_NIL)) { new_err = janet_getjstream(&maybe_stderr, 0, &orig_err); From 979233dee5b73ea6a784af962c0aa4d7582d9d80 Mon Sep 17 00:00:00 2001 From: Calvin Rose Date: Sun, 18 Jan 2026 11:08:50 -0600 Subject: [PATCH 107/307] Update changelog and spelling --- CHANGELOG.md | 1 + src/core/os.c | 6 +++--- 2 files changed, 4 insertions(+), 3 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 5c90c8a0..9990a7c5 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -2,6 +2,7 @@ All notable changes to this project will be documented in this file. ## Unreleased - ??? +- Add `:I`, `:V`, and `:N` flags to `os/open` for more control when creating streams. - Add `ev/go-gather` for a dynamic `ev/gather`. - Use color in script output if color is being used in REPL output. - Fix `varfn` macros handling of extra metadata. diff --git a/src/core/os.c b/src/core/os.c index d7510029..7bce62b1 100644 --- a/src/core/os.c +++ b/src/core/os.c @@ -2717,7 +2717,7 @@ JANET_CORE_FN(os_open, JanetHandle fd; #ifdef JANET_WINDOWS (void) mode; - int inherrited_handle = 0; + int inherited_handle = 0; DWORD desiredAccess = 0; DWORD shareMode = 0; DWORD creationDisp = 0; @@ -2787,7 +2787,7 @@ JANET_CORE_FN(os_open, fileFlags |= FILE_FLAG_NO_BUFFERING; break; case 'I': - inherrited_handle = 1; + inherited_handle = 1; break; case 'V': fileFlags &= ~FILE_FLAG_OVERLAPPED; @@ -2823,7 +2823,7 @@ JANET_CORE_FN(os_open, SECURITY_ATTRIBUTES saAttr; memset(&saAttr, 0, sizeof(saAttr)); saAttr.nLength = sizeof(saAttr); - if (inherrited_handle) { + if (inherited_handle) { saAttr.bInheritHandle = TRUE; /* Needed to do interesting things with file */ } fd = CreateFileA(path, desiredAccess, shareMode, &saAttr, creationDisp, fileFlags | fileAttributes, NULL); From f021bb28394c5ce939aab1c3471d6fe0c3f7bbc3 Mon Sep 17 00:00:00 2001 From: Calvin Rose Date: Sun, 18 Jan 2026 13:05:55 -0600 Subject: [PATCH 108/307] janet-format boot.janet --- src/boot/boot.janet | 16 ++++++++-------- 1 file changed, 8 insertions(+), 8 deletions(-) diff --git a/src/boot/boot.janet b/src/boot/boot.janet index c9302fe1..61c1a31e 100644 --- a/src/boot/boot.janet +++ b/src/boot/boot.janet @@ -4280,12 +4280,12 @@ (try (require (string "@syspath/bundle/" bundle-name)) ([e f] - (def pfx "could not find module @syspath/bundle/") - (def msg (if (and (string? e) - (string/has-prefix? pfx e)) - "bundle must contain bundle.janet or bundle/init.janet" - e)) - (propagate msg f)))))) + (def pfx "could not find module @syspath/bundle/") + (def msg (if (and (string? e) + (string/has-prefix? pfx e)) + "bundle must contain bundle.janet or bundle/init.janet" + e)) + (propagate msg f)))))) (defn- do-hook [module bundle-name hook & args] @@ -4417,8 +4417,8 @@ (def bscript-src1 (string path s "bundle" s "init.janet")) (def bscript-src2 (string path s "bundle.janet")) (def bscript-src (cond - (fexists bscript-src1) bscript-src1 - (fexists bscript-src2) bscript-src2)) + (fexists bscript-src1) bscript-src1 + (fexists bscript-src2) bscript-src2)) # Setup installed paths (prime-bundle-paths) (os/mkdir (bundle-dir bundle-name)) From a2db57b9dc9ab64120aef6663de25bf74d65b4e4 Mon Sep 17 00:00:00 2001 From: "amano.kenji" Date: Mon, 19 Jan 2026 09:50:18 +0000 Subject: [PATCH 109/307] Document empty? more clearly. --- src/boot/boot.janet | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/boot/boot.janet b/src/boot/boot.janet index 46fcabae..24861c86 100644 --- a/src/boot/boot.janet +++ b/src/boot/boot.janet @@ -114,7 +114,7 @@ (defn true? "Check if x is true." [x] (= x true)) (defn false? "Check if x is false." [x] (= x false)) (defn nil? "Check if x is nil." [x] (= x nil)) -(defn empty? "Check if xs is empty." [xs] (= nil (next xs nil))) +(defn empty? "Check if an iterable, `iter`, is empty." [iter] (= nil (next iter nil))) # For macros, we define an imcomplete odd? function that will be overriden. (defn odd? [x] (= 1 (mod x 2))) From 0c402cf3d60b8cdd40b173cdeecdef24cecbc243 Mon Sep 17 00:00:00 2001 From: Calvin Rose Date: Tue, 20 Jan 2026 13:41:51 -0600 Subject: [PATCH 110/307] Add persistent repl history. --- janet.1 | 5 +++ src/core/io.c | 12 ++++++ src/include/janet.h | 4 +- src/mainclient/shell.c | 93 ++++++++++++++++++++++++++++++++++++++---- 4 files changed, 105 insertions(+), 9 deletions(-) diff --git a/janet.1 b/janet.1 index b2afd7c4..6e03808c 100644 --- a/janet.1 +++ b/janet.1 @@ -272,6 +272,11 @@ This variable does nothing in the default configuration of Janet, as PRF is disa cannot be defined for this variable to have an effect. .RE +.B JANET_HISTFILE +.RS +A file location to use for the default shell's REPL history. This relative path is where commands are persisted between sessions. +.RE + .B NO_COLOR .RS Turn off color by default in the repl and in the error handler of scripts. This can be changed at runtime diff --git a/src/core/io.c b/src/core/io.c index b7ff524f..52eb9f73 100644 --- a/src/core/io.c +++ b/src/core/io.c @@ -718,6 +718,18 @@ JANET_CORE_FN(cfun_io_eflush, return janet_wrap_nil(); } +void janet_xprintf(FILE *file, const char *format, ...) { + va_list args; + va_start(args, format); + JanetBuffer buffer; + int32_t len = 0; + while (format[len]) len++; + janet_buffer_init(&buffer, len); + janet_formatbv(&buffer, format, args); + fwrite(buffer.data, buffer.count, 1, file); + janet_buffer_deinit(&buffer); +} + void janet_dynprintf(const char *name, FILE *dflt_file, const char *format, ...) { va_list args; va_start(args, format); diff --git a/src/include/janet.h b/src/include/janet.h index 495a78c2..d6d7fe1f 100644 --- a/src/include/janet.h +++ b/src/include/janet.h @@ -1439,6 +1439,7 @@ JANET_API void janet_loop(void); * } else { * janet_schedule(interrupted_fiber, janet_wrap_nil()); * } + * janet_interpreter_interrupt_handled(NULL); * } * } * @@ -1889,7 +1890,7 @@ JANET_API void janet_vm_free(JanetVM *vm); JANET_API void janet_vm_save(JanetVM *into); JANET_API void janet_vm_load(JanetVM *from); JANET_API void janet_interpreter_interrupt(JanetVM *vm); -JANET_API void janet_interpreter_interrupt_handled(JanetVM *vm); +JANET_API void janet_interpreter_interrupt_handled(JanetVM *vm); /* Call this after running interrupt handler */ JANET_API JanetSignal janet_continue(JanetFiber *fiber, Janet in, Janet *out); JANET_API JanetSignal janet_continue_signal(JanetFiber *fiber, Janet in, Janet *out, JanetSignal sig); JANET_API JanetSignal janet_pcall(JanetFunction *fun, int32_t argn, const Janet *argv, Janet *out, JanetFiber **f); @@ -2051,6 +2052,7 @@ JANET_NO_RETURN JANET_API void janet_panicv(Janet message); JANET_NO_RETURN JANET_API void janet_panic(const char *message); JANET_NO_RETURN JANET_API void janet_panics(JanetString message); JANET_NO_RETURN JANET_API void janet_panicf(const char *format, ...); +JANET_API void janet_xprintf(FILE *file, const char *format, ...); JANET_API void janet_dynprintf(const char *name, FILE *dflt_file, const char *format, ...); #define janet_printf(...) janet_dynprintf("out", stdout, __VA_ARGS__) #define janet_eprintf(...) janet_dynprintf("err", stderr, __VA_ARGS__) diff --git a/src/mainclient/shell.c b/src/mainclient/shell.c index a665acca..4828c444 100644 --- a/src/mainclient/shell.c +++ b/src/mainclient/shell.c @@ -96,9 +96,9 @@ static void simpleline(JanetBuffer *buffer) { #ifndef JANET_SIMPLE_GETLINE /* static state */ -#define JANET_LINE_MAX 1024 +#define JANET_LINE_MAX 4096 #define JANET_MATCH_MAX 256 -#define JANET_HISTORY_MAX 100 +#define JANET_HISTORY_MAX 1000 static JANET_THREAD_LOCAL int gbl_israwmode = 0; static JANET_THREAD_LOCAL const char *gbl_prompt = "> "; static JANET_THREAD_LOCAL int gbl_plen = 2; @@ -112,6 +112,8 @@ static JANET_THREAD_LOCAL int gbl_historyi = 0; static JANET_THREAD_LOCAL JanetByteView gbl_matches[JANET_MATCH_MAX]; static JANET_THREAD_LOCAL int gbl_match_count = 0; static JANET_THREAD_LOCAL int gbl_lines_below = 0; +static JANET_THREAD_LOCAL int gbl_history_loaded = 0; +static JANET_THREAD_LOCAL char *gbl_history_file = NULL; #endif /* Fallback */ @@ -430,6 +432,74 @@ static int insert(char c, int draw) { return 0; } +static void calc_history_file(void) { + char *hist = getenv("JANET_HISTFILE"); + if (hist) { + gbl_history_file = sdup(hist); + } else { + const char *home = (const char *) getenv("HOME"); + if (NULL == home) home = ""; + const char *filename = ".janet_history.jdn"; + size_t homelen = strlen(home); + size_t filelen = strlen(filename); + size_t buflen = homelen + filelen + 1; + char *buffer = janet_malloc(buflen + 1); + if (homelen) memcpy(buffer, home, homelen); + buffer[homelen] = '/'; + memcpy(buffer + homelen + 1, filename, filelen); + buffer[buflen] = '\0'; + gbl_history_file = buffer; + } + /* fprintf(stderr, "history_file: %s\n", gbl_history_file); */ +} + +static void loadhistory(void) { + if (gbl_history_loaded) return; + calc_history_file(); + gbl_history_loaded = 1; + FILE *history_file = fopen(gbl_history_file, "rb"); + if (NULL == history_file) return; + JanetParser p; + janet_parser_init(&p); + int c = 0; + while ((c = fgetc(history_file))) { + if (c == EOF) { + janet_parser_eof(&p); + } else { + janet_parser_consume(&p, c); + } + + while (janet_parser_has_more(&p) && gbl_history_count < JANET_HISTORY_MAX) { + if (janet_parser_status(&p) == JANET_PARSE_ERROR) { + janet_eprintf("bad history file: %s\n", janet_parser_error(&p)); + goto parsing_done; + } + Janet x = janet_parser_produce(&p); + const char *cstr = (const char *) janet_to_string(x); + if (cstr[0]) { /* Drop empty strings */ + gbl_history[gbl_history_count++] = sdup(cstr); + } + } + + if (c == EOF) break; + } +parsing_done: + janet_parser_deinit(&p); + gbl_historyi = 0; + fclose(history_file); +} + +static void savehistory(void) { + if (gbl_history_count <= 1 || !gbl_history_file) return; + FILE *history_file = fopen(gbl_history_file, "wb"); + for (int i = 0; i < gbl_history_count; i++) { + if (gbl_history[i][0]) { /* Drop empty strings */ + janet_xprintf(history_file, "%j\n", janet_cstringv(gbl_history[i])); + } + } + fclose(history_file); +} + static void historymove(int delta) { if (gbl_history_count > 1) { janet_free(gbl_history[gbl_historyi]); @@ -896,6 +966,7 @@ static int line() { case 3: /* ctrl-c */ clearlines(); norawmode(); + savehistory(); #ifdef _WIN32 ExitProcess(1); #else @@ -1089,17 +1160,18 @@ void janet_line_init() { } void janet_line_deinit() { - int i; norawmode(); - for (i = 0; i < gbl_history_count; i++) + for (int i = 0; i < gbl_history_count; i++) janet_free(gbl_history[i]); gbl_historyi = 0; + if (gbl_history_file) janet_free(gbl_history_file); } void janet_line_get(const char *p, JanetBuffer *buffer) { gbl_prompt = p; buffer->count = 0; gbl_historyi = 0; + loadhistory(); if (check_simpleline(buffer)) return; FILE *out = janet_dynfile("err", stderr); if (line()) { @@ -1130,6 +1202,13 @@ static void clear_at_exit(void) { * Entry */ +static void cleanup_at_exit(void) { + /* Deinitialize vm */ + savehistory(); + janet_deinit(); + janet_line_deinit(); +} + int main(int argc, char **argv) { int i, status; JanetArray *args; @@ -1146,6 +1225,7 @@ int main(int argc, char **argv) { #if !defined(JANET_SIMPLE_GETLINE) atexit(clear_at_exit); #endif + atexit(cleanup_at_exit); #if defined(JANET_PRF) uint8_t hash_key[JANET_HASH_KEY_SIZE + 1]; @@ -1193,9 +1273,6 @@ int main(int argc, char **argv) { /* Run the fiber in an event loop */ status = janet_loop_fiber(fiber); - /* Deinitialize vm */ - janet_deinit(); - janet_line_deinit(); - + /* Deinit moved to atexit */ return status; } From 53b8bf2684b3f4fb19bdcc8e4559811998f19fee Mon Sep 17 00:00:00 2001 From: Calvin Rose Date: Thu, 22 Jan 2026 17:58:18 -0600 Subject: [PATCH 111/307] Disable persistent REPL history without JANET_HISTFILE. --- janet.1 | 5 +++-- src/mainclient/shell.c | 14 +------------- 2 files changed, 4 insertions(+), 15 deletions(-) diff --git a/janet.1 b/janet.1 index 6e03808c..ae64a316 100644 --- a/janet.1 +++ b/janet.1 @@ -156,7 +156,7 @@ Shows the version text and exits immediately. .TP .BR \-s -Read raw input from stdin and forgo prompt history and other readline-like features. +Read raw input from stdin and forgo fancy input, which includes prompt history and other readline-like features. .TP .BR \-e\ code @@ -274,7 +274,8 @@ cannot be defined for this variable to have an effect. .B JANET_HISTFILE .RS -A file location to use for the default shell's REPL history. This relative path is where commands are persisted between sessions. +A file location to use for the default shell's REPL history when using fancy input. This relative path is where commands are persisted between sessions. +If unset, no repl history well be used. Does not work with the -s flag where fancy input is disabled. .RE .B NO_COLOR diff --git a/src/mainclient/shell.c b/src/mainclient/shell.c index 4828c444..548728ba 100644 --- a/src/mainclient/shell.c +++ b/src/mainclient/shell.c @@ -437,20 +437,8 @@ static void calc_history_file(void) { if (hist) { gbl_history_file = sdup(hist); } else { - const char *home = (const char *) getenv("HOME"); - if (NULL == home) home = ""; - const char *filename = ".janet_history.jdn"; - size_t homelen = strlen(home); - size_t filelen = strlen(filename); - size_t buflen = homelen + filelen + 1; - char *buffer = janet_malloc(buflen + 1); - if (homelen) memcpy(buffer, home, homelen); - buffer[homelen] = '/'; - memcpy(buffer + homelen + 1, filename, filelen); - buffer[buflen] = '\0'; - gbl_history_file = buffer; + gbl_history_file = NULL; } - /* fprintf(stderr, "history_file: %s\n", gbl_history_file); */ } static void loadhistory(void) { From 6111291edeaf259a06ad39441ae25fe38db16c5a Mon Sep 17 00:00:00 2001 From: Calvin Rose Date: Thu, 22 Jan 2026 20:08:29 -0600 Subject: [PATCH 112/307] Revert a number of minor, unneeded changes for persistent REPL history. --- src/core/io.c | 23 +++++++++-------------- src/include/janet.h | 1 - src/mainclient/shell.c | 27 +++++++++++++-------------- 3 files changed, 22 insertions(+), 29 deletions(-) diff --git a/src/core/io.c b/src/core/io.c index 52eb9f73..97668887 100644 --- a/src/core/io.c +++ b/src/core/io.c @@ -718,23 +718,18 @@ JANET_CORE_FN(cfun_io_eflush, return janet_wrap_nil(); } -void janet_xprintf(FILE *file, const char *format, ...) { - va_list args; - va_start(args, format); - JanetBuffer buffer; - int32_t len = 0; - while (format[len]) len++; - janet_buffer_init(&buffer, len); - janet_formatbv(&buffer, format, args); - fwrite(buffer.data, buffer.count, 1, file); - janet_buffer_deinit(&buffer); -} - void janet_dynprintf(const char *name, FILE *dflt_file, const char *format, ...) { va_list args; va_start(args, format); - Janet x = janet_dyn(name); - JanetType xtype = janet_type(x); + JanetType xtype; + Janet x; + if (name[0] == '\0') { /* Allow empty string to just use dflt_file directly */ + x = janet_wrap_nil(); + xtype = JANET_NIL; + } else { + x = janet_dyn(name); + xtype = janet_type(x); + } switch (xtype) { default: /* Other values simply do nothing */ diff --git a/src/include/janet.h b/src/include/janet.h index d6d7fe1f..248998d9 100644 --- a/src/include/janet.h +++ b/src/include/janet.h @@ -2052,7 +2052,6 @@ JANET_NO_RETURN JANET_API void janet_panicv(Janet message); JANET_NO_RETURN JANET_API void janet_panic(const char *message); JANET_NO_RETURN JANET_API void janet_panics(JanetString message); JANET_NO_RETURN JANET_API void janet_panicf(const char *format, ...); -JANET_API void janet_xprintf(FILE *file, const char *format, ...); JANET_API void janet_dynprintf(const char *name, FILE *dflt_file, const char *format, ...); #define janet_printf(...) janet_dynprintf("out", stdout, __VA_ARGS__) #define janet_eprintf(...) janet_dynprintf("err", stderr, __VA_ARGS__) diff --git a/src/mainclient/shell.c b/src/mainclient/shell.c index 548728ba..b9073378 100644 --- a/src/mainclient/shell.c +++ b/src/mainclient/shell.c @@ -96,9 +96,9 @@ static void simpleline(JanetBuffer *buffer) { #ifndef JANET_SIMPLE_GETLINE /* static state */ -#define JANET_LINE_MAX 4096 +#define JANET_LINE_MAX 1024 #define JANET_MATCH_MAX 256 -#define JANET_HISTORY_MAX 1000 +#define JANET_HISTORY_MAX 100 static JANET_THREAD_LOCAL int gbl_israwmode = 0; static JANET_THREAD_LOCAL const char *gbl_prompt = "> "; static JANET_THREAD_LOCAL int gbl_plen = 2; @@ -434,7 +434,7 @@ static int insert(char c, int draw) { static void calc_history_file(void) { char *hist = getenv("JANET_HISTFILE"); - if (hist) { + if (hist != NULL) { gbl_history_file = sdup(hist); } else { gbl_history_file = NULL; @@ -478,7 +478,7 @@ parsing_done: } static void savehistory(void) { - if (gbl_history_count <= 1 || !gbl_history_file) return; + if (gbl_history_count < 1 || (gbl_history_file == NULL)) return; FILE *history_file = fopen(gbl_history_file, "wb"); for (int i = 0; i < gbl_history_count; i++) { if (gbl_history[i][0]) { /* Drop empty strings */ @@ -1152,7 +1152,10 @@ void janet_line_deinit() { for (int i = 0; i < gbl_history_count; i++) janet_free(gbl_history[i]); gbl_historyi = 0; - if (gbl_history_file) janet_free(gbl_history_file); + if (gbl_history_file) { + janet_free(gbl_history_file); + gbl_history_file = NULL; + } } void janet_line_get(const char *p, JanetBuffer *buffer) { @@ -1190,13 +1193,6 @@ static void clear_at_exit(void) { * Entry */ -static void cleanup_at_exit(void) { - /* Deinitialize vm */ - savehistory(); - janet_deinit(); - janet_line_deinit(); -} - int main(int argc, char **argv) { int i, status; JanetArray *args; @@ -1213,7 +1209,6 @@ int main(int argc, char **argv) { #if !defined(JANET_SIMPLE_GETLINE) atexit(clear_at_exit); #endif - atexit(cleanup_at_exit); #if defined(JANET_PRF) uint8_t hash_key[JANET_HASH_KEY_SIZE + 1]; @@ -1261,6 +1256,10 @@ int main(int argc, char **argv) { /* Run the fiber in an event loop */ status = janet_loop_fiber(fiber); - /* Deinit moved to atexit */ + /* Deinitialize vm */ + savehistory(); + janet_deinit(); + janet_line_deinit(); + return status; } From 883dde4fa5267e828044da5791beb4cd28557d2f Mon Sep 17 00:00:00 2001 From: Calvin Rose Date: Thu, 22 Jan 2026 20:14:46 -0600 Subject: [PATCH 113/307] Remove xprintf reference. --- src/mainclient/shell.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/mainclient/shell.c b/src/mainclient/shell.c index b9073378..010ea2e5 100644 --- a/src/mainclient/shell.c +++ b/src/mainclient/shell.c @@ -482,7 +482,7 @@ static void savehistory(void) { FILE *history_file = fopen(gbl_history_file, "wb"); for (int i = 0; i < gbl_history_count; i++) { if (gbl_history[i][0]) { /* Drop empty strings */ - janet_xprintf(history_file, "%j\n", janet_cstringv(gbl_history[i])); + janet_dynprintf("", history_file, "%j\n", janet_cstringv(gbl_history[i])); } } fclose(history_file); From 4dd08a4cdef5b1c42d9a2c19fc24412e97ef51d5 Mon Sep 17 00:00:00 2001 From: Calvin Rose Date: Fri, 23 Jan 2026 07:33:05 -0600 Subject: [PATCH 114/307] Address #1702 with extra bounds check. The buffer overflow happened because we were creating many upvalue slots in the compiler without using them, along with some faulty logic that used the wrong length to check for the bitmap's bounds. --- src/core/compile.c | 3 ++- src/core/io.c | 2 +- src/mainclient/shell.c | 2 +- test/suite-corelib.janet | 7 ++++++- 4 files changed, 10 insertions(+), 4 deletions(-) diff --git a/src/core/compile.c b/src/core/compile.c index 40d2c3e4..9f819a83 100644 --- a/src/core/compile.c +++ b/src/core/compile.c @@ -961,8 +961,9 @@ JanetFuncDef *janetc_pop_funcdef(JanetCompiler *c) { JANET_OUT_OF_MEMORY; } memcpy(chunks, scope->ua.chunks, sizeof(uint32_t) * numchunks); + /* fprintf(stderr, "slot chunks: %d, scope->ua.count: %d, numchunks: %d\n", slotchunks, scope->ua.count, numchunks); */ /* Register allocator preallocates some registers [240-255, high 16 bits of chunk index 7], we can ignore those. */ - if (scope->ua.count > 7) chunks[7] &= 0xFFFFU; + if (scope->ua.count > 7 && slotchunks > 7) chunks[7] &= 0xFFFFU; def->closure_bitset = chunks; } diff --git a/src/core/io.c b/src/core/io.c index 97668887..43996526 100644 --- a/src/core/io.c +++ b/src/core/io.c @@ -723,7 +723,7 @@ void janet_dynprintf(const char *name, FILE *dflt_file, const char *format, ...) va_start(args, format); JanetType xtype; Janet x; - if (name[0] == '\0') { /* Allow empty string to just use dflt_file directly */ + if (!name || name[0] == '\0') { /* Allow NULL or empty string to just use dflt_file directly */ x = janet_wrap_nil(); xtype = JANET_NIL; } else { diff --git a/src/mainclient/shell.c b/src/mainclient/shell.c index 010ea2e5..a6fa79ec 100644 --- a/src/mainclient/shell.c +++ b/src/mainclient/shell.c @@ -482,7 +482,7 @@ static void savehistory(void) { FILE *history_file = fopen(gbl_history_file, "wb"); for (int i = 0; i < gbl_history_count; i++) { if (gbl_history[i][0]) { /* Drop empty strings */ - janet_dynprintf("", history_file, "%j\n", janet_cstringv(gbl_history[i])); + janet_dynprintf(NULL, history_file, "%j\n", janet_cstringv(gbl_history[i])); } } fclose(history_file); diff --git a/test/suite-corelib.janet b/test/suite-corelib.janet index 02cdaaba..a3651cd2 100644 --- a/test/suite-corelib.janet +++ b/test/suite-corelib.janet @@ -202,5 +202,10 @@ (assert-no-error "def destructure splice works 2" (do (def (n) [(splice [])]) n)) (assert-no-error "var destructure splice works" (do (var [a] [;[1]]) a)) -(end-suite) +# Issue #1702 - fuzz case with upvalues +(each item [1 2 3] + # Generate a lot of upvalues (more than 224) + (def ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;out-buf @"") + (with-dyns [:out out-buf] 1)) +(end-suite) From 2fabc80151a2b8834ee59cda8a70453f848b40e5 Mon Sep 17 00:00:00 2001 From: Calvin Rose Date: Fri, 23 Jan 2026 07:46:31 -0600 Subject: [PATCH 115/307] Address #1699 and improve test case for #1702 --- src/core/specials.c | 6 +++--- test/suite-corelib.janet | 19 +++++++++++++++---- 2 files changed, 18 insertions(+), 7 deletions(-) diff --git a/src/core/specials.c b/src/core/specials.c index 2e25f677..446f5de1 100644 --- a/src/core/specials.c +++ b/src/core/specials.c @@ -308,13 +308,13 @@ static JanetSlot janetc_varset(JanetFopts opts, int32_t argn, const Janet *argv) static JanetTable *handleattr(JanetCompiler *c, const char *kind, int32_t argn, const Janet *argv) { int32_t i; JanetTable *tab = janet_table(2); - const char *binding_name = janet_type(argv[0]) == JANET_SYMBOL - ? ((const char *)janet_unwrap_symbol(argv[0])) - : ""; if (argn < 2) { janetc_error(c, janet_formatc("expected at least 2 arguments to %s", kind)); return NULL; } + const char *binding_name = janet_type(argv[0]) == JANET_SYMBOL + ? ((const char *)janet_unwrap_symbol(argv[0])) + : ""; for (i = 1; i < argn - 1; i++) { Janet attr = argv[i]; switch (janet_type(attr)) { diff --git a/test/suite-corelib.janet b/test/suite-corelib.janet index a3651cd2..9114a4cc 100644 --- a/test/suite-corelib.janet +++ b/test/suite-corelib.janet @@ -203,9 +203,20 @@ (assert-no-error "var destructure splice works" (do (var [a] [;[1]]) a)) # Issue #1702 - fuzz case with upvalues -(each item [1 2 3] - # Generate a lot of upvalues (more than 224) - (def ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;out-buf @"") - (with-dyns [:out out-buf] 1)) +(def result + (compile + '(each item [1 2 3] + # Generate a lot of upvalues (more than 224) + (def ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;out-buf @"") + (with-dyns [:out out-buf] 1)))) +(assert result "bad upvalues fuzz case") + +# Issue #1699 - fuzz case with bad def +(def result + (compile '(defn sum3 + "Solve the 3SUM problem in O(n^2) time." + [s] + (def)tab @{}))) +(assert (get result :error) "bad sum3 fuzz issue valgrind") (end-suite) From c43e06672cd9dacf2122c99f362120a17c34b391 Mon Sep 17 00:00:00 2001 From: Calvin Rose Date: Fri, 23 Jan 2026 08:01:22 -0600 Subject: [PATCH 116/307] Address #1700 and move test cases to suite-compile.janet --- src/core/specials.c | 6 ++++-- test/suite-compile.janet | 27 +++++++++++++++++++++++++++ test/suite-corelib.janet | 17 ----------------- 3 files changed, 31 insertions(+), 19 deletions(-) diff --git a/src/core/specials.c b/src/core/specials.c index 446f5de1..0b915d78 100644 --- a/src/core/specials.c +++ b/src/core/specials.c @@ -686,8 +686,10 @@ static JanetSlot janetc_if(JanetFopts opts, int32_t argn, const Janet *argv) { /* Write jumps - only add jump lengths if jump actually emitted */ labeld = janet_v_count(c->buffer); - c->buffer[labeljr] |= (labelr - labeljr) << 16; - if (!tail) c->buffer[labeljd] |= (labeld - labeljd) << 8; + if (labeljr < labeld) { + c->buffer[labeljr] |= (labelr - labeljr) << 16; + if (!tail) c->buffer[labeljd] |= (labeld - labeljd) << 8; + } if (tail) target.flags |= JANET_SLOT_RETURNED; return target; diff --git a/test/suite-compile.janet b/test/suite-compile.janet index b3c280b0..d4e0dc67 100644 --- a/test/suite-compile.janet +++ b/test/suite-compile.janet @@ -75,5 +75,32 @@ (foo 0) 10) +# Issue #1699 - fuzz case with bad def +(def result + (compile '(defn sum3 + "Solve the 3SUM problem in O(n^2) time." + [s] + (def)tab @{}))) +(assert (get result :error) "bad sum3 fuzz issue valgrind") + +# Issue #1700 +(def result + (compile + '(defn fuzz-case-1 + [start end &] + (if end + (if e start (lazy-range (+ 1 start) end))) + 1))) +(assert (get result :error) "fuzz case issue #1700") + +# Issue #1702 - fuzz case with upvalues +(def result + (compile + '(each item [1 2 3] + # Generate a lot of upvalues (more than 224) + (def ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;out-buf @"") + (with-dyns [:out out-buf] 1)))) +(assert result "bad upvalues fuzz case") + (end-suite) diff --git a/test/suite-corelib.janet b/test/suite-corelib.janet index 9114a4cc..99ee7eeb 100644 --- a/test/suite-corelib.janet +++ b/test/suite-corelib.janet @@ -202,21 +202,4 @@ (assert-no-error "def destructure splice works 2" (do (def (n) [(splice [])]) n)) (assert-no-error "var destructure splice works" (do (var [a] [;[1]]) a)) -# Issue #1702 - fuzz case with upvalues -(def result - (compile - '(each item [1 2 3] - # Generate a lot of upvalues (more than 224) - (def ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;out-buf @"") - (with-dyns [:out out-buf] 1)))) -(assert result "bad upvalues fuzz case") - -# Issue #1699 - fuzz case with bad def -(def result - (compile '(defn sum3 - "Solve the 3SUM problem in O(n^2) time." - [s] - (def)tab @{}))) -(assert (get result :error) "bad sum3 fuzz issue valgrind") - (end-suite) From 0f285855f0e34f9183956be5f16e045f54626bff Mon Sep 17 00:00:00 2001 From: Calvin Rose Date: Fri, 23 Jan 2026 08:13:15 -0600 Subject: [PATCH 117/307] Address #170 - strchr was not properly checking for 0 byte. --- src/core/os.c | 6 +++--- test/suite-os.janet | 5 ++++- 2 files changed, 7 insertions(+), 4 deletions(-) diff --git a/src/core/os.c b/src/core/os.c index 7bce62b1..c075d29a 100644 --- a/src/core/os.c +++ b/src/core/os.c @@ -1922,7 +1922,7 @@ JANET_CORE_FN(os_date, return janet_wrap_struct(janet_struct_end(st)); } -#define SIZETIMEFMT 250 +#define SIZETIMEFMT 250 JANET_CORE_FN(os_strftime, "(os/strftime fmt &opt time local)", @@ -1937,7 +1937,7 @@ JANET_CORE_FN(os_strftime, const char *p = fmt; while (*p) { if (*p++ == '%') { - if (!strchr(valid, *p)) { + if (!*p || !strchr(valid, *p)) { janet_panicf("invalid conversion specifier '%%%c'", *p); } p++; @@ -1946,7 +1946,7 @@ JANET_CORE_FN(os_strftime, struct tm t_infos; struct tm *t_info = time_to_tm(argv, argc, 1, &t_infos); char buf[SIZETIMEFMT]; - (void)strftime(buf, SIZETIMEFMT, fmt, t_info); + (void)strftime(buf, sizeof(buf), fmt, t_info); return janet_cstringv(buf); } diff --git a/test/suite-os.janet b/test/suite-os.janet index 5bfde34f..880de4ef 100644 --- a/test/suite-os.janet +++ b/test/suite-os.janet @@ -63,7 +63,10 @@ "strftime january 2014") (assert (= (try (os/strftime "%%%d%t") ([err] err)) "invalid conversion specifier '%t'") - "invalid conversion specifier") + "invalid conversion specifier 1") +(assert (= (try (os/strftime "%H:%M:%") ([err] err)) + "invalid conversion specifier") + "invalid conversion specifier 2") # 07db4c530 (os/setenv "TESTENV1" "v1") From ee8d816738c19928c82a01a35ac2c1c307be7a8a Mon Sep 17 00:00:00 2001 From: Calvin Rose Date: Fri, 23 Jan 2026 08:30:15 -0600 Subject: [PATCH 118/307] Fix test case. --- src/core/os.c | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/src/core/os.c b/src/core/os.c index c075d29a..62a15b2c 100644 --- a/src/core/os.c +++ b/src/core/os.c @@ -1937,7 +1937,10 @@ JANET_CORE_FN(os_strftime, const char *p = fmt; while (*p) { if (*p++ == '%') { - if (!*p || !strchr(valid, *p)) { + if (!*p) { + janet_panic("invalid conversion specifier"); + } + if (!strchr(valid, *p)) { janet_panicf("invalid conversion specifier '%%%c'", *p); } p++; From 41354ada9621db31cd510a7791f19251d669f391 Mon Sep 17 00:00:00 2001 From: "amano.kenji" Date: Sat, 24 Jan 2026 11:36:49 +0000 Subject: [PATCH 119/307] Make peg-grammar available in all threads for peg/compile --- src/core/peg.c | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/src/core/peg.c b/src/core/peg.c index d9624aed..e0c5c76f 100644 --- a/src/core/peg.c +++ b/src/core/peg.c @@ -1832,6 +1832,8 @@ static JanetPeg *compile_peg(Janet x) { Janet default_grammarv = janet_dyn("peg-grammar"); if (janet_checktype(default_grammarv, JANET_TABLE)) { builder.default_grammar = janet_unwrap_table(default_grammarv); + } else { + builder.default_grammar = janet_get_core_table("default-peg-grammar"); } } builder.tags = janet_table(0); @@ -1854,8 +1856,8 @@ static JanetPeg *compile_peg(Janet x) { JANET_CORE_FN(cfun_peg_compile, "(peg/compile peg)", "Compiles a peg source data structure into a . This will speed up matching " - "if the same peg will be used multiple times. Will also use `(dyn :peg-grammar)` to supplement " - "the grammar of the peg for otherwise undefined peg keywords.") { + "if the same peg will be used multiple times. `(dyn :peg-grammar)` replaces " + "`default-peg-grammar` for the grammar of the peg.") { janet_fixarity(argc, 1); JanetPeg *peg = compile_peg(argv[0]); return janet_wrap_abstract(peg); From 87b1bf1a66f09475a809033f778bbf65a4c1eaae Mon Sep 17 00:00:00 2001 From: "amano.kenji" Date: Sat, 24 Jan 2026 12:05:32 +0000 Subject: [PATCH 120/307] Document how to set local timezone for os/date and os/strftime --- src/core/os.c | 10 ++++++---- 1 file changed, 6 insertions(+), 4 deletions(-) diff --git a/src/core/os.c b/src/core/os.c index 62a15b2c..9149ac18 100644 --- a/src/core/os.c +++ b/src/core/os.c @@ -1893,9 +1893,8 @@ static struct tm *time_to_tm(const Janet *argv, int32_t argc, int32_t n, struct JANET_CORE_FN(os_date, "(os/date &opt time local)", "Returns the given time as a date struct, or the current time if `time` is not given. " - "Returns a struct with following key values. Note that all numbers are 0-indexed. " "Date is given in UTC unless `local` is truthy, in which case the date is formatted for " - "the local timezone.\n\n" + "the local timezone. Returns a struct with following key values. Note that all numbers are 0-indexed.\n\n" "* :seconds - number of seconds [0-61]\n\n" "* :minutes - number of minutes [0-59]\n\n" "* :hours - number of hours [0-23]\n\n" @@ -1904,7 +1903,9 @@ JANET_CORE_FN(os_date, "* :year - years since year 0 (e.g. 2019)\n\n" "* :week-day - day of the week [0-6]\n\n" "* :year-day - day of the year [0-365]\n\n" - "* :dst - if Day Light Savings is in effect") { + "* :dst - if Day Light Savings is in effect\n\n" + "You can set local timezone by setting TZ environment variable. " + "See tzset() or _tzset() for further details.") { janet_arity(argc, 0, 2); (void) argv; struct tm t_infos; @@ -1929,7 +1930,8 @@ JANET_CORE_FN(os_strftime, "Format the given time as a string, or the current time if `time` is not given. " "The time is formatted according to the same rules as the ISO C89 function strftime(). " "The time is formatted in UTC unless `local` is truthy, in which case the date is formatted for " - "the local timezone.") { + "the local timezone. You can set local timezone by setting TZ environment variable. " + "See tzset() or _tzset() for further details.") { janet_arity(argc, 1, 3); const char *fmt = janet_getcstring(argv, 0); /* ANSI X3.159-1989, section 4.12.3.5 "The strftime function" */ From 339b0751c87433d21cbda0716e29c5ffb02b8996 Mon Sep 17 00:00:00 2001 From: Calvin Rose Date: Sat, 24 Jan 2026 12:17:02 -0600 Subject: [PATCH 121/307] Address #1705 - ev/close affecting ev/select. --- src/core/ev.c | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/src/core/ev.c b/src/core/ev.c index 7ad1f6f1..406872c8 100644 --- a/src/core/ev.c +++ b/src/core/ev.c @@ -1269,11 +1269,13 @@ JANET_CORE_FN(cfun_channel_choice, if (janet_indexed_view(argv[i], &data, &len) && len == 2) { /* Write */ JanetChannel *chan = janet_getchannel(data, 0); + janet_chan_lock(chan); janet_channel_push_with_lock(chan, data[1], 1); } else { /* Read */ Janet item; JanetChannel *chan = janet_getchannel(argv, i); + janet_chan_lock(chan); janet_channel_pop_with_lock(chan, &item, 1); } } @@ -1376,7 +1378,7 @@ JANET_CORE_FN(cfun_channel_close, janet_ev_post_event(vm, janet_thread_chan_cb, msg); } } else { - if (janet_fiber_can_resume(writer.fiber)) { + if (janet_fiber_can_resume(writer.fiber) && writer.sched_id == writer.fiber->sched_id) { if (writer.mode == JANET_CP_MODE_CHOICE_WRITE) { janet_schedule(writer.fiber, make_close_result(channel)); } else { @@ -1399,7 +1401,7 @@ JANET_CORE_FN(cfun_channel_close, janet_ev_post_event(vm, janet_thread_chan_cb, msg); } } else { - if (janet_fiber_can_resume(reader.fiber)) { + if (janet_fiber_can_resume(reader.fiber) && reader.sched_id == reader.fiber->sched_id) { if (reader.mode == JANET_CP_MODE_CHOICE_READ) { janet_schedule(reader.fiber, make_close_result(channel)); } else { From 3ecc9bc54326aeccbd61916090d6355092542c66 Mon Sep 17 00:00:00 2001 From: Calvin Rose Date: Sat, 24 Jan 2026 12:17:24 -0600 Subject: [PATCH 122/307] Small tweaks to boot.janet to improve stack traces. --- src/boot/boot.janet | 9 +++++---- 1 file changed, 5 insertions(+), 4 deletions(-) diff --git a/src/boot/boot.janet b/src/boot/boot.janet index 9ca3b37b..d0a3b005 100644 --- a/src/boot/boot.janet +++ b/src/boot/boot.janet @@ -2845,7 +2845,8 @@ (defmacro comptime "Evals x at compile time and returns the result. Similar to a top level unquote." [x] - (eval x)) + (def y (eval x)) + y) (defmacro compif "Check the condition `cnd` at compile time -- if truthy, compile `tru`, else compile `fals`." @@ -3168,12 +3169,12 @@ (def mc (dyn *module-cache* module/cache)) (def ml (dyn *module-loading* module/loading)) (def mls (dyn *module-loaders* module/loaders)) - (if-let [check (if-not (kargs :fresh) (in mc fullpath))] + (if-let [check (if-not (get kargs :fresh) (in mc fullpath))] check - (if (ml fullpath) + (if (get ml fullpath) (error (string "circular dependency " fullpath " detected")) (do - (def loader (if (keyword? mod-kind) (mls mod-kind) mod-kind)) + (def loader (if (keyword? mod-kind) (get mls mod-kind) mod-kind)) (unless loader (error (string "module type " mod-kind " unknown"))) (def env (loader fullpath args)) (put mc fullpath env) From 33f5a0b319ae454cf65b009e3a60e16a91e633e6 Mon Sep 17 00:00:00 2001 From: Calvin Rose Date: Sat, 24 Jan 2026 12:23:01 -0600 Subject: [PATCH 123/307] Add test case to help address #1705 --- test/suite-ev2.janet | 23 +++++++++++++++++++++++ 1 file changed, 23 insertions(+) diff --git a/test/suite-ev2.janet b/test/suite-ev2.janet index 8752c3d7..bcb6dd7f 100644 --- a/test/suite-ev2.janet +++ b/test/suite-ev2.janet @@ -55,4 +55,27 @@ (ev/sleep 0.2) (assert (deep= '(:error "deadline expired" nil) (ev/take super)) "deadline expirataion") +# Issue #1705 - ev select +(def supervisor (ev/chan 10)) + +(def ch (ev/chan)) +(def ch2 (ev/chan)) + +(ev/go |(do + (ev/select ch ch2) + (:close ch) + "close ch...") + nil supervisor) + +(ev/go |(do + (ev/sleep 0.05) + (:close ch2) + "close ch2...") + nil supervisor) + +(assert (let [[status] (ev/take supervisor)] (= status :ok)) "status 1 ev/select") +(assert (let [[status] (ev/take supervisor)] (= status :ok)) "status 2 ev/select") +(ev/sleep 0.1) # can we do better? +(assert (= 0 (ev/count supervisor)) "empty supervisor") + (end-suite) From ae0afe6198a6b9dc3787d8a6ecc9ab05ecb2b6d6 Mon Sep 17 00:00:00 2001 From: Calvin Rose Date: Tue, 27 Jan 2026 13:28:52 -0600 Subject: [PATCH 124/307] Add janet_decref_abstract_maybe_free --- Makefile | 1 - src/core/abstract.c | 13 +++++++++++++ src/core/filewatch.c | 8 ++++---- src/core/gc.c | 16 ++-------------- src/include/janet.h | 3 +++ 5 files changed, 22 insertions(+), 19 deletions(-) diff --git a/Makefile b/Makefile index b60c2c90..8e2523b3 100644 --- a/Makefile +++ b/Makefile @@ -58,7 +58,6 @@ LDFLAGS?=-rdynamic LIBJANET_LDFLAGS?=$(LDFLAGS) RUN:=$(RUN) - 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 BUILD_CFLAGS:=$(CFLAGS) $(COMMON_CFLAGS) diff --git a/src/core/abstract.c b/src/core/abstract.c index 94d69b98..cbda2296 100644 --- a/src/core/abstract.c +++ b/src/core/abstract.c @@ -201,4 +201,17 @@ int32_t janet_abstract_decref(void *abst) { return janet_atomic_dec(&janet_abstract_head(abst)->gc.data.refcount); } +int32_t janet_abstract_decref_maybe_free(void *abst) { + int32_t result = janet_abstract_decref(abst); + if (0 == result) { + JanetAbstractHead *head = janet_abstract_head(abst); + if (head->type->gc) { + janet_assert(!head->type->gc(head->data, head->size), "finalizer failed"); + } + /* Free memory */ + janet_free(head); + } + return result; +} + #endif diff --git a/src/core/filewatch.c b/src/core/filewatch.c index 91d24801..8023271c 100644 --- a/src/core/filewatch.c +++ b/src/core/filewatch.c @@ -521,23 +521,23 @@ static void janet_watcher_add(JanetWatcher *watcher, const char *path, uint32_t (void) watcher; (void) flags; (void) path; - janet_panic("nyi"); + janet_panic("filewatch not supported on this platform"); } static void janet_watcher_remove(JanetWatcher *watcher, const char *path) { (void) watcher; (void) path; - janet_panic("nyi"); + janet_panic("filewatch not supported on this platform"); } static void janet_watcher_listen(JanetWatcher *watcher) { (void) watcher; - janet_panic("nyi"); + janet_panic("filewatch not supported on this platform"); } static void janet_watcher_unlisten(JanetWatcher *watcher) { (void) watcher; - janet_panic("nyi"); + janet_panic("filewatch not supported on this platform"); } #endif diff --git a/src/core/gc.c b/src/core/gc.c index 532a9cc6..82d68780 100644 --- a/src/core/gc.c +++ b/src/core/gc.c @@ -504,14 +504,7 @@ void janet_sweep() { if (head->type->gcperthread) { janet_assert(!head->type->gcperthread(head->data, head->size), "per-thread finalizer failed"); } - if (0 == janet_abstract_decref(abst)) { - /* Run finalizer */ - if (head->type->gc) { - janet_assert(!head->type->gc(head->data, head->size), "finalizer failed"); - } - /* Free memory */ - janet_free(janet_abstract_head(abst)); - } + janet_abstract_decref_maybe_free(abst); /* Mark as tombstone in place */ items[i].key = janet_wrap_nil(); @@ -682,12 +675,7 @@ void janet_clear_memory(void) { if (head->type->gcperthread) { janet_assert(!head->type->gcperthread(head->data, head->size), "per-thread finalizer failed"); } - if (0 == janet_abstract_decref(abst)) { - if (head->type->gc) { - janet_assert(!head->type->gc(head->data, head->size), "finalizer failed"); - } - janet_free(janet_abstract_head(abst)); - } + janet_abstract_decref_maybe_free(abst); } } #endif diff --git a/src/include/janet.h b/src/include/janet.h index 248998d9..10d38dd0 100644 --- a/src/include/janet.h +++ b/src/include/janet.h @@ -1482,6 +1482,9 @@ JANET_API void *janet_abstract_threaded(const JanetAbstractType *atype, size_t s JANET_API int32_t janet_abstract_incref(void *abst); JANET_API int32_t janet_abstract_decref(void *abst); +/* If this returns 0, *abst will be deinitialized and freed */ +JANET_API int32_t janet_abstract_decref_maybe_free(void *abst); + /* Expose channel utilities */ JANET_API JanetChannel *janet_channel_make(uint32_t limit); JANET_API JanetChannel *janet_channel_make_threaded(uint32_t limit); From eda2e11d3124d64292a11765398e083bdc7b0dae Mon Sep 17 00:00:00 2001 From: Calvin Rose Date: Wed, 28 Jan 2026 10:51:18 -0600 Subject: [PATCH 125/307] MSVC and windows clib does not like fopen(NULL, ...) We were exploiting this behavior on posix when adding shell history for the built-in readline functionality. --- src/mainclient/shell.c | 1 + 1 file changed, 1 insertion(+) diff --git a/src/mainclient/shell.c b/src/mainclient/shell.c index a6fa79ec..497fb33a 100644 --- a/src/mainclient/shell.c +++ b/src/mainclient/shell.c @@ -445,6 +445,7 @@ static void loadhistory(void) { if (gbl_history_loaded) return; calc_history_file(); gbl_history_loaded = 1; + if (NULL == gbl_history_file) return; FILE *history_file = fopen(gbl_history_file, "rb"); if (NULL == history_file) return; JanetParser p; From 797643716b56a0b8608f18ac5ed2eb8ca77662a8 Mon Sep 17 00:00:00 2001 From: Calvin Rose Date: Wed, 28 Jan 2026 09:38:34 -0600 Subject: [PATCH 126/307] Address #1707 - do not schedule non-task fibers. --- src/core/ev.c | 3 +++ 1 file changed, 3 insertions(+) diff --git a/src/core/ev.c b/src/core/ev.c index 406872c8..70408f40 100644 --- a/src/core/ev.c +++ b/src/core/ev.c @@ -539,6 +539,9 @@ void janet_schedule_soon(JanetFiber *fiber, Janet value, JanetSignal sig) { } void janet_cancel(JanetFiber *fiber, Janet value) { + if (!(fiber->gc.flags & JANET_FIBER_FLAG_ROOT)) { + janet_panic("cannot cancel non-task fiber"); + } janet_schedule_signal(fiber, value, JANET_SIGNAL_ERROR); } From 7366fbed1fa52add394a7c35720bb43043780d8e Mon Sep 17 00:00:00 2001 From: Calvin Rose Date: Wed, 28 Jan 2026 10:07:23 -0600 Subject: [PATCH 127/307] Disallow scheduling already modified fibers. While not strictly needed for correctness, this discourages misuse of fibers and mixing both coroutine, error handling, and scheduling purposes. --- src/core/ev.c | 3 +++ test/suite-ev2.janet | 6 ++++++ 2 files changed, 9 insertions(+) diff --git a/src/core/ev.c b/src/core/ev.c index 70408f40..e3be60c2 100644 --- a/src/core/ev.c +++ b/src/core/ev.c @@ -3038,6 +3038,9 @@ JANET_CORE_FN(cfun_ev_go, fiber->env->proto = janet_vm.fiber->env; } else { fiber = janet_getfiber(argv, 0); + if (janet_fiber_status(fiber) != JANET_STATUS_NEW) { + janet_panic("can only schedule new fibers where (= (fiber/status f) :new)"); + } } fiber->supervisor_channel = supervisor; janet_schedule(fiber, value); diff --git a/test/suite-ev2.janet b/test/suite-ev2.janet index bcb6dd7f..c94a2d86 100644 --- a/test/suite-ev2.janet +++ b/test/suite-ev2.janet @@ -78,4 +78,10 @@ (ev/sleep 0.1) # can we do better? (assert (= 0 (ev/count supervisor)) "empty supervisor") +# Issue #1707 +(def f (coro (repeat 10 (yield 1)))) +(resume f) +(assert-error "cannot schedule non-new fiber" + (ev/go f)) + (end-suite) From 24ca10828832c858e367faffa1f7b1d4704fc336 Mon Sep 17 00:00:00 2001 From: Calvin Rose Date: Wed, 28 Jan 2026 10:54:47 -0600 Subject: [PATCH 128/307] Add some extra checks for marshalling code. --- src/core/corelib.c | 16 ++++++++++------ 1 file changed, 10 insertions(+), 6 deletions(-) diff --git a/src/core/corelib.c b/src/core/corelib.c index 75094612..e2450269 100644 --- a/src/core/corelib.c +++ b/src/core/corelib.c @@ -1354,12 +1354,16 @@ JanetTable *janet_core_env(JanetTable *replacements) { lidv = midv = janet_wrap_nil(); janet_resolve(env, janet_csymbol("load-image-dict"), &lidv); janet_resolve(env, janet_csymbol("make-image-dict"), &midv); - JanetTable *lid = janet_unwrap_table(lidv); - JanetTable *mid = janet_unwrap_table(midv); - for (int32_t i = 0; i < lid->capacity; i++) { - const JanetKV *kv = lid->data + i; - if (!janet_checktype(kv->key, JANET_NIL)) { - janet_table_put(mid, kv->value, kv->key); + + /* Check that we actually got tables - if we are using a smaller corelib, may not exist */ + if (janet_checktype(lidv, JANET_TABLE) && janet_checktype(midv, JANET_TABLE)) { + JanetTable *lid = janet_unwrap_table(lidv); + JanetTable *mid = janet_unwrap_table(midv); + for (int32_t i = 0; i < lid->capacity; i++) { + const JanetKV *kv = lid->data + i; + if (!janet_checktype(kv->key, JANET_NIL)) { + janet_table_put(mid, kv->value, kv->key); + } } } From d67b462023d8c32bf7ba50b144d4d63394e5f041 Mon Sep 17 00:00:00 2001 From: sogaiu <983021772@users.noreply.github.com> Date: Thu, 29 Jan 2026 11:39:05 +0900 Subject: [PATCH 129/307] Tweak some fiber-related docstrings --- src/boot/boot.janet | 12 ++++++++---- src/core/ev.c | 19 +++++++++++-------- src/core/fiber.c | 5 +++-- 3 files changed, 22 insertions(+), 14 deletions(-) diff --git a/src/boot/boot.janet b/src/boot/boot.janet index d0a3b005..1dc5df5c 100644 --- a/src/boot/boot.janet +++ b/src/boot/boot.janet @@ -3865,13 +3865,16 @@ (defn ev/call ``` Call a function asynchronously. - Returns a fiber that is scheduled to run the function. + Returns a task fiber that is scheduled to run the function. ``` [f & args] (ev/go (fn :call [&] (f ;args)))) (defmacro ev/spawn - "Run some code in a new fiber. This is shorthand for `(ev/go (fn [] ;body))`." + `` + Run some code in a new task fiber. This is shorthand for + `(ev/go (fn [] ;body))`." + `` [& body] ~(,ev/go (fn :spawn [&] ,;body))) @@ -3965,8 +3968,9 @@ (defmacro ev/gather `` - Run a number of fibers in parallel and resume the current fiber after they complete. - Returns the gathered results in an array. + Run a number of fibers in parallel (created from `bodies`) and resume the + current fiber after they complete. Returns the gathered results in an + array. `` [& bodies] ~(,ev/go-gather ,(seq [body :in bodies] ~(fn :ev/gather [] ,body))))) diff --git a/src/core/ev.c b/src/core/ev.c index e3be60c2..9d2e8fd3 100644 --- a/src/core/ev.c +++ b/src/core/ev.c @@ -3007,12 +3007,14 @@ error: JANET_CORE_FN(cfun_ev_go, "(ev/go fiber-or-fun &opt value supervisor)", - "Put a fiber on the event loop to be resumed later. If a function is used, it is wrapped " - "with `fiber/new` first. " - "Optionally pass a value to resume with, otherwise resumes with nil. Returns the fiber. " - "An optional `core/channel` can be provided as a supervisor. When various " - "events occur in the newly scheduled fiber, an event will be pushed to the supervisor. " - "If not provided, the new fiber will inherit the current supervisor.") { + "Put a fiber on the event loop to be resumed later. If a " + "function is used, it is wrapped with `fiber/new` first. " + "Returns a task fiber. Optionally pass a value to resume " + "with, otherwise resumes with nil. An optional `core/channel` " + "can be provided as a supervisor. When various events occur " + "in the newly scheduled fiber, an event will be pushed to the " + "supervisor. If not provided, the new fiber will inherit the " + "current supervisor.") { janet_arity(argc, 1, 3); Janet value = argc >= 2 ? argv[1] : janet_wrap_nil(); void *supervisor = janet_optabstract(argv, argc, 2, &janet_channel_type, janet_vm.root_fiber->supervisor_channel); @@ -3324,7 +3326,8 @@ JANET_CORE_FN(cfun_ev_deadline, JANET_CORE_FN(cfun_ev_cancel, "(ev/cancel fiber err)", - "Cancel a suspended fiber in the event loop. Differs from cancel in that it returns the canceled fiber immediately.") { + "Cancel a suspended task fiber in the event loop. Differs from " + "`cancel` in that it returns the canceled fiber immediately.") { janet_fixarity(argc, 2); JanetFiber *fiber = janet_getfiber(argv, 0); Janet err = argv[1]; @@ -3557,7 +3560,7 @@ JANET_CORE_FN(janet_cfun_to_file, JANET_CORE_FN(janet_cfun_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 task fibers that are being used by the scheduler.") { janet_fixarity(argc, 0); (void) argv; JanetArray *array = janet_array(janet_vm.active_tasks.count); diff --git a/src/core/fiber.c b/src/core/fiber.c index b711874f..95f78493 100644 --- a/src/core/fiber.c +++ b/src/core/fiber.c @@ -610,8 +610,9 @@ JANET_CORE_FN(cfun_fiber_current, JANET_CORE_FN(cfun_fiber_root, "(fiber/root)", - "Returns the current root fiber. The root fiber is the oldest ancestor " - "that does not have a parent.") { + "Returns the current root fiber. The root fiber is the oldest " + "ancestor that does not have a parent. Note that a root fiber " + "is also a task fiber.") { (void) argv; janet_fixarity(argc, 0); return janet_wrap_fiber(janet_vm.root_fiber); From 0c1585fdfee7bbc6a9fba9520611cc91ced4b170 Mon Sep 17 00:00:00 2001 From: Calvin Rose Date: Thu, 29 Jan 2026 06:52:43 -0600 Subject: [PATCH 130/307] Preserve tuple type to fix #1709 Macro expansion as done by `macex1` was incorrectly losing the semantic tuple information inside the `set` form. Since macro expansion is usually done by the compiler, and `macex1` is mainly used for debugging and deep transformations inside other macros, this only surfaced with certain usage of short-fn macro. --- src/boot/boot.janet | 5 ++++- test/suite-corelib.janet | 5 +++++ 2 files changed, 9 insertions(+), 1 deletion(-) diff --git a/src/boot/boot.janet b/src/boot/boot.janet index 1dc5df5c..c36e2aad 100644 --- a/src/boot/boot.janet +++ b/src/boot/boot.janet @@ -2173,7 +2173,10 @@ (defn expand-bindings [x] (case (type x) :array (map expand-bindings x) - :tuple (tuple/slice (map expand-bindings x)) + :tuple (let [ctor (if (= :brackets (tuple/type x)) + tuple/brackets + tuple)] + (ctor ;(map expand-bindings x))) :table (dotable x expand-bindings) :struct (table/to-struct (dotable x expand-bindings)) (recur x))) diff --git a/test/suite-corelib.janet b/test/suite-corelib.janet index 99ee7eeb..2454cb85 100644 --- a/test/suite-corelib.janet +++ b/test/suite-corelib.janet @@ -202,4 +202,9 @@ (assert-no-error "def destructure splice works 2" (do (def (n) [(splice [])]) n)) (assert-no-error "var destructure splice works" (do (var [a] [;[1]]) a)) +# Issue #1709 +(assert (= (macex1 '|(set (my-table [2 1]) 'foo)) + '(fn :short-fn [] (set (my-table [2 1]) (quote foo)))) + "Macro expand inside set preserves tuple type correctly") + (end-suite) From e1897e18655f3e88b527bf3aefb95d3a66c2e7ae Mon Sep 17 00:00:00 2001 From: Calvin Rose Date: Thu, 29 Jan 2026 07:50:23 -0600 Subject: [PATCH 131/307] More documentation. --- src/boot/boot.janet | 11 ++--- src/include/janet.h | 112 +++++++++++++++++++++++++++++++++++++++----- 2 files changed, 104 insertions(+), 19 deletions(-) diff --git a/src/boot/boot.janet b/src/boot/boot.janet index c36e2aad..248793f6 100644 --- a/src/boot/boot.janet +++ b/src/boot/boot.janet @@ -2173,10 +2173,7 @@ (defn expand-bindings [x] (case (type x) :array (map expand-bindings x) - :tuple (let [ctor (if (= :brackets (tuple/type x)) - tuple/brackets - tuple)] - (ctor ;(map expand-bindings x))) + :tuple (keep-syntax! x (map expand-bindings x)) :table (dotable x expand-bindings) :struct (table/to-struct (dotable x expand-bindings)) (recur x))) @@ -2184,7 +2181,7 @@ (defn expanddef [t] (def last (in t (- (length t) 1))) (def bound (in t 1)) - (tuple/slice + (keep-syntax! t (array/concat @[(in t 0) (expand-bindings bound)] (tuple/slice t 2 -2) @@ -2199,10 +2196,10 @@ (if (symbol? t1) (do (def args (map recur (tuple/slice t 3))) - (tuple 'fn t1 (in t 2) ;args)) + (keep-syntax t (tuple 'fn t1 (in t 2) ;args))) (do (def args (map recur (tuple/slice t 2))) - (tuple 'fn t1 ;args)))) + (keep-syntax t (tuple 'fn t1 ;args))))) (defn expandqq [t] (defn qq [x] diff --git a/src/include/janet.h b/src/include/janet.h index 10d38dd0..add1df78 100644 --- a/src/include/janet.h +++ b/src/include/janet.h @@ -1138,6 +1138,7 @@ struct JanetFunction { JanetFuncEnv *envs[]; }; +/* Use to read Janet data structures into memory from source code */ typedef struct JanetParseState JanetParseState; typedef struct JanetParser JanetParser; @@ -1187,7 +1188,10 @@ typedef struct { const JanetAbstractType *at; } JanetMarshalContext; -/* Defines an abstract type */ +/* Defines an abstract type. Use a const pointer to one of these structures + * when creating abstract types. The memory for this pointer should not be free + * until after janet_deinit is called. Usually, this means declaring JanetAbstractType's + * as const data at file scope, and creating instances with janet_abstract(&MyType, sizeof(MyTypeStruct)); */ struct JanetAbstractType { const char *name; int (*gc)(void *data, size_t len); @@ -1479,10 +1483,16 @@ JANET_API void janet_ev_dec_refcount(void); JANET_API void *janet_abstract_begin_threaded(const JanetAbstractType *atype, size_t size); JANET_API void *janet_abstract_end_threaded(void *x); JANET_API void *janet_abstract_threaded(const JanetAbstractType *atype, size_t size); + +/* Allow reference counting on threaded abstract types. This is useful when external code , either + * in the current OS thread or in a different OS thread, takes a pointer to this abstract type. The programmer + * should tncrement the reference count when taking the pointer, and then decrement and possibly cleanup and free + * if the reference count is 0. */ JANET_API int32_t janet_abstract_incref(void *abst); JANET_API int32_t janet_abstract_decref(void *abst); -/* If this returns 0, *abst will be deinitialized and freed */ +/* If this returns 0, *abst will be deinitialized and freed. Useful shorthand if there is no other cleanup for + * this abstract type before calling `janet_free` on it's backing memory. */ JANET_API int32_t janet_abstract_decref_maybe_free(void *abst); /* Expose channel utilities */ @@ -1493,7 +1503,7 @@ JANET_API JanetChannel *janet_optchannel(const Janet *argv, int32_t argc, int32_ JANET_API int janet_channel_give(JanetChannel *channel, Janet x); JANET_API int janet_channel_take(JanetChannel *channel, Janet *out); -/* Expose some OS sync primitives */ +/* Expose some OS sync primitives - mutexes and reader-writer locks */ JANET_API size_t janet_os_mutex_size(void); JANET_API size_t janet_os_rwlock_size(void); JANET_API void janet_os_mutex_init(JanetOSMutex *mutex); @@ -1561,7 +1571,8 @@ JANET_API void janet_ev_post_event(JanetVM *vm, JanetCallback cb, JanetEVGeneric /* Callback used by janet_ev_threaded_await */ JANET_API void janet_ev_default_threaded_callback(JanetEVGenericMessage return_value); -/* Read async from a stream */ +/* Read async from a stream. These function yield to the event-loop with janet_await(), and so do not return. + * When the fiber is resumed, the fiber will simply continue to the next Janet abstract machine instruction. */ JANET_NO_RETURN JANET_API void janet_ev_read(JanetStream *stream, JanetBuffer *buf, int32_t nbytes); JANET_NO_RETURN JANET_API void janet_ev_readchunk(JanetStream *stream, JanetBuffer *buf, int32_t nbytes); #ifdef JANET_NET @@ -1570,7 +1581,8 @@ JANET_NO_RETURN JANET_API void janet_ev_recvchunk(JanetStream *stream, JanetBuff JANET_NO_RETURN JANET_API void janet_ev_recvfrom(JanetStream *stream, JanetBuffer *buf, int32_t nbytes, int flags); #endif -/* Write async to a stream */ +/* Write async to a stream. These function yield to the event-loop with janet_await(), and so do not return. + * When the fiber is resumed, the fiber will simply continue to the next Janet abstract machine instruction. */ JANET_NO_RETURN JANET_API void janet_ev_write_buffer(JanetStream *stream, JanetBuffer *buf); JANET_NO_RETURN JANET_API void janet_ev_write_string(JanetStream *stream, JanetString str); #ifdef JANET_NET @@ -1582,17 +1594,63 @@ JANET_NO_RETURN JANET_API void janet_ev_sendto_string(JanetStream *stream, Janet #endif -/* Parsing */ +/* Parsing. + * + * E.g. + * + * JanetParser parser; + * janet_parser_init(&parser); + * for (int i = 0; i < source_code_length + 1; i++) { + * if (i >= source_code_length) { + * janet_parser_eof(&parser); + * } else { + * janet_parser_consume(&parser, source_code[i]); + * } + * while (janet_parser_has_more(&parser)) { + * Janet x = janet_parser_produce(&parser); + * janet_printf("got value: %v\n", x); + * } + * switch (janet_parser_status(&parser)) { + * case JANET_PARSE_PENDING: break; + * case JANET_PARSE_ERROR: janet_eprintf("error: %s\n", janet_parser_error(&parser)); break; + * case JANET_PARSE_ROOT: break; + * case JANET_PARSE_DEAD: break; + * } + * } + * janet_parser_deinit(&parser); + * + * */ extern JANET_API const JanetAbstractType janet_parser_type; + +/* Construct/destruct a parser. Parsers can be allocated on the stack or the heap. */ JANET_API void janet_parser_init(JanetParser *parser); JANET_API void janet_parser_deinit(JanetParser *parser); + +/* Feed bytes into the parser. Check the parser state after every byte to handle errors. */ JANET_API void janet_parser_consume(JanetParser *parser, uint8_t c); + +/* Check the current status of the parser */ JANET_API enum JanetParserStatus janet_parser_status(JanetParser *parser); + +/* Produce a value from the parser. Call this when janet_parser_has_more(&parser) is non-zero. */ JANET_API Janet janet_parser_produce(JanetParser *parser); + +/* Produce a value from the parser, wrapped in a tuple. The tuple is used to carry the source mapping information of the + * top level form, such as a line number or symbol. */ JANET_API Janet janet_parser_produce_wrapped(JanetParser *parser); + +/* When there is an error while parsing (janet_parser_status(&parser) == JANET_PARSE_ERROR), get a nice error string. + * Calling this will also flush the parser. */ JANET_API const char *janet_parser_error(JanetParser *parser); + +/* If there is a parsing error, flush the parser to set the state back to empty. + * This allows for better error recover and less confusing error messages on bad syntax deep inside nested data structures. */ JANET_API void janet_parser_flush(JanetParser *parser); + +/* Indicate that there is no more source code */ JANET_API void janet_parser_eof(JanetParser *parser); + +/* If non-zero, the parser has values ready to be produced. */ JANET_API int janet_parser_has_more(JanetParser *parser); /* Assembly */ @@ -1636,7 +1694,10 @@ JANET_API JanetCompileResult janet_compile_lint( JANET_API JanetTable *janet_core_env(JanetTable *replacements); JANET_API JanetTable *janet_core_lookup_table(JanetTable *replacements); -/* Execute strings */ +/* Execute strings. + * + * These functions wrap parsing, compilation, and evalutation into convenient functions. + * */ #define JANET_DO_ERROR_RUNTIME 0x01 #define JANET_DO_ERROR_COMPILE 0x02 #define JANET_DO_ERROR_PARSE 0x04 @@ -1830,21 +1891,41 @@ JANET_API JanetTable *janet_env_lookup(JanetTable *env); JANET_API void janet_env_lookup_into(JanetTable *renv, JanetTable *env, const char *prefix, int recurse); /* GC */ -JANET_API void janet_mark(Janet x); -JANET_API void janet_sweep(void); + +/* The main interface to garbage collection. Call this to do a full mark and sweep cleanup. */ JANET_API void janet_collect(void); -JANET_API void janet_clear_memory(void); + +/* Add "roots" to the garbage collector to prevent the runtime from freeing objects. + * This is only needed if code outside of Janet keeps references to Janet values */ JANET_API void janet_gcroot(Janet root); JANET_API int janet_gcunroot(Janet root); -JANET_API int janet_gcunrootall(Janet root); + +/* Allow disabling garbage collection temporarily or for certain sections of code. + * this is a very cheap operation. */ JANET_API int janet_gclock(void); JANET_API void janet_gcunlock(int handle); + +/* The mark and sweep components of the mark and sweep collector. Prefer using janet_collect directly. */ +JANET_API void janet_mark(Janet x); +JANET_API void janet_sweep(void); + +/* Clear all gced memory and call all destructors. Used as part of the standard cleanup routune, most programmers will not need this. */ +JANET_API void janet_clear_memory(void); + +/* Remove all GC roots. Used as part of the standard cleanup routine, most programmers will not need this. */ +JANET_API int janet_gcunrootall(Janet root); + +/* Hint to the collector that memory of size s was just allocated to help it better understand when to free memory. */ JANET_API void janet_gcpressure(size_t s); /* Functions */ JANET_API JanetFuncDef *janet_funcdef_alloc(void); JANET_API JanetFunction *janet_thunk(JanetFuncDef *def); + +/* Get a function that when called with no args, will return x. */ JANET_API JanetFunction *janet_thunk_delay(Janet x); + +/* Do some simple verfification on constructed bytecode to disallow any trivial incorrect bytecode. */ JANET_API int janet_verify(JanetFuncDef *def); /* Pretty printing */ @@ -1966,7 +2047,14 @@ JANET_API JanetBinding janet_resolve_ext(JanetTable *env, JanetSymbol sym); /* Get values from the core environment. */ JANET_API Janet janet_resolve_core(const char *name); -/* New C API */ +/* New C API + * + * The "New" C API is intended to make constructing good documentation and source maps + * much more straightforward. This not only ensures doc strings for functions in native + * modules, it also add source code mapping for C functions so that programmers can see which + * file and line a native function that calls janet_panic came from. + * + * */ /* Shorthand for janet C function declarations */ #define JANET_CFUN(name) Janet name (int32_t argc, Janet *argv) From ea271b6d6cea858119d3923d2473ad3f6a005584 Mon Sep 17 00:00:00 2001 From: Calvin Rose Date: Fri, 30 Jan 2026 19:55:39 -0600 Subject: [PATCH 132/307] Update README with more clarification on versions to use for building. --- README.md | 56 ++++++++++++++++++++++++++++++++++++++++++++++++------- 1 file changed, 49 insertions(+), 7 deletions(-) diff --git a/README.md b/README.md index 8324e1dd..3cf0c302 100644 --- a/README.md +++ b/README.md @@ -148,8 +148,40 @@ You can get the source on [GitHub](https://github.com/janet-lang/janet) or [SourceHut](https://git.sr.ht/~bakpakin/janet). While the GitHub repo is the official repo, the SourceHut mirror is actively maintained. +## Spork and JPM + +Spork and JPM are two companion projects to Janet. They are optional, especially in an embedding use case. + +Spork is a collection of common utility modules, and several packaged scripts +like `janet-format` for code formatting, `janet-netrepl` for a socket-based +REPL, and `janet-pm` for a comprehensive Janet project manager tool. The +modules in `spork` are less stable than the interfaces in core Janet, although +we try to prevent breaking changes to existing modules, with a preference to +add new modules and functions. Spork requires a C compiler to build and install +various extenstion components such as miniz and JSON utilities. Many spork +sub-modules, for example spork/path, are independent and can be manually +vendored in programmer projects without fully installing spork. + +When install Spork, scripts will be installed to $JANET_PATH/bin/ on POSIX systems by default. +This likely needs to be added to the path to use these scripts. + +JPM is the older, more opinionated, project manager tool, which has it's pros +and cons. It does not require a C compiler to build and install, but is less +flexible and is not receiving many changes and improvements going forward. It +may also be harder to configure correctly on new systems. In that sense, it may +be more stable. + +JPM will install to /usr/local/bin/ on posix systems by default, which may or +may not be on your PATH. + ## Building +When building from source, for stability, please use the latest tagged release. For +example, run `git checkout $(git describe --tags --abbrev=0)` after cloning but +before building. For the latest development, build directly on the master +branch. The master branch is not-necessarily stable as most Janet development +happens directly on the master branch. + ### macOS and Unix-like The Makefile is non-portable and requires GNU-flavored make. @@ -160,15 +192,18 @@ make make test make repl make install -make install-jpm-git +make install-spork-git # optional +make install-jpm-git # optional ``` Find out more about the available make targets by running `make help`. ### Alpine Linux -To build a statically-linked build of Janet, Alpine Linux + MUSL is a good combination. Janet can also -be built inside a docker container or similar in this manner. +To build a statically-linked build of Janet, Alpine Linux + MUSL is a good +combination. Janet can also be built inside a docker container or similar in +this manner. This is a great way to try Janet without committing to a full +install or needing to customize the default install. ```sh docker run -it --rm alpine /bin/ash @@ -178,8 +213,13 @@ $ cd janet $ make -j10 $ make test $ make install +$ make install-spork-git # optional +$ make install-jpm-git # optional ``` +Note that for a true statically-linked binary with MUSL, one needs to add `-static` to the Makefile flags. This +will also disable runtime loading of native modules (plugins) as well as the FFI. + ### 32-bit Haiku 32-bit Haiku build instructions are the same as the UNIX-like build instructions, @@ -191,7 +231,8 @@ make CC=gcc-x86 make test make repl make install -make install-jpm-git +make install-spork-git # optional +make install-jpm-git # optional ``` ### FreeBSD @@ -205,7 +246,8 @@ gmake gmake test gmake repl gmake install -gmake install-jpm-git +gmake install-spork-git # optional +gmake install-jpm-git # optional ``` ### NetBSD @@ -320,8 +362,8 @@ If installed, you can also run `man janet` to get usage information. ## Embedding Janet can be embedded in a host program very easily. The normal build -will create a file `build/janet.c`, which is a single C file -that contains all the source to Janet. This file, along with +will create a file `build/c/janet.c`, a C source code file that +that contains the amalgamated source to Janet. This file, along with `src/include/janet.h` and `src/conf/janetconf.h`, can be dragged into any C project and compiled into it. Janet should be compiled with `-std=c99` on most compilers, and will need to be linked to the math library, `-lm`, and From 8e2ec997f074ef1e1a327ed58638e2ba163f4238 Mon Sep 17 00:00:00 2001 From: Calvin Rose Date: Sat, 31 Jan 2026 10:33:57 -0600 Subject: [PATCH 133/307] Update copyright. --- src/boot/boot.janet | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/boot/boot.janet b/src/boot/boot.janet index 248793f6..ee0fc6b4 100644 --- a/src/boot/boot.janet +++ b/src/boot/boot.janet @@ -1,5 +1,5 @@ # The core janet library -# Copyright 2025 © Calvin Rose +# Copyright 2026 © Calvin Rose ### ### @@ -3728,7 +3728,7 @@ (def digits (inc (math/floor (math/log10 end)))) (def fmt-str (string "%" digits "d: %s")) (for i beg end - (eprin " ") # breakpoint someday? + (eprin " ") (eprin (if (= i cur) "> " " ")) (eprintf fmt-str i (get lines i)))) (let [[sl _] (sourcemap pc)] From 8f8382eead8f6d01a6656ee6e78f8f4c9dbb3541 Mon Sep 17 00:00:00 2001 From: Calvin Rose Date: Sat, 31 Jan 2026 13:16:02 -0600 Subject: [PATCH 134/307] Partially address issue #1654 - better linting for named arguments. While there are still cases that we could improve here, this change will help in most cases with minimal overhead. --- src/core/asm.c | 19 +++++++++++ src/core/bytecode.c | 1 + src/core/compile.c | 72 +++++++++++++++++++++++++++++++--------- src/core/marsh.c | 5 +++ src/core/specials.c | 4 +++ src/include/janet.h | 2 ++ test/suite-compile.janet | 33 ++++++++++++++++++ 7 files changed, 121 insertions(+), 15 deletions(-) diff --git a/src/core/asm.c b/src/core/asm.c index 287838e3..4afc7a28 100644 --- a/src/core/asm.c +++ b/src/core/asm.c @@ -567,6 +567,13 @@ static JanetAssembleResult janet_asm1(JanetAssembler *parent, Janet source, int x = janet_get1(s, janet_ckeywordv("structarg")); if (janet_truthy(x)) def->flags |= JANET_FUNCDEF_FLAG_STRUCTARG; + /* Check namedarg */ + x = janet_get1(s, janet_ckeywordv("namedargs")); + if (janet_checkint(x)) { + def->flags |= JANET_FUNCDEF_FLAG_NAMEDARGS; + def->named_args_count = janet_unwrap_integer(x); + } + /* Check source */ x = janet_get1(s, janet_ckeywordv("source")); if (janet_checktype(x, JANET_STRING)) def->source = janet_unwrap_string(x); @@ -982,6 +989,14 @@ static Janet janet_disasm_structarg(JanetFuncDef *def) { return janet_wrap_boolean(def->flags & JANET_FUNCDEF_FLAG_STRUCTARG); } +static Janet janet_disasm_namedargs(JanetFuncDef *def) { + if (def->flags & JANET_FUNCDEF_FLAG_NAMEDARGS) { + return janet_wrap_integer(def->named_args_count); + } else { + return janet_wrap_nil(); + } +} + static Janet janet_disasm_constants(JanetFuncDef *def) { JanetArray *constants = janet_array(def->constants_length); for (int32_t i = 0; i < def->constants_length; i++) { @@ -1032,6 +1047,7 @@ Janet janet_disasm(JanetFuncDef *def) { janet_table_put(ret, janet_ckeywordv("source"), janet_disasm_source(def)); janet_table_put(ret, janet_ckeywordv("vararg"), janet_disasm_vararg(def)); janet_table_put(ret, janet_ckeywordv("structarg"), janet_disasm_structarg(def)); + janet_table_put(ret, janet_ckeywordv("namedargs"), janet_disasm_namedargs(def)); janet_table_put(ret, janet_ckeywordv("name"), janet_disasm_name(def)); janet_table_put(ret, janet_ckeywordv("slotcount"), janet_disasm_slotcount(def)); janet_table_put(ret, janet_ckeywordv("symbolmap"), janet_disasm_symbolslots(def)); @@ -1067,6 +1083,8 @@ JANET_CORE_FN(cfun_disasm, "* :min-arity - minimum number of arguments function can be called with.\n" "* :max-arity - maximum number of arguments function can be called with.\n" "* :vararg - true if function can take a variable number of arguments.\n" + "* :structarg - true if function can take a variable number of arguments using the &keys option.\n" + "* :namedargs - if function can take a variable number of arguments using the &named option, this will be the number of named arguments.\n" "* :bytecode - array of parsed bytecode instructions. Each instruction is a tuple.\n" "* :source - name of source file that this function was compiled from.\n" "* :name - name of function.\n" @@ -1088,6 +1106,7 @@ JANET_CORE_FN(cfun_disasm, if (!janet_cstrcmp(kw, "name")) return janet_disasm_name(f->def); if (!janet_cstrcmp(kw, "vararg")) return janet_disasm_vararg(f->def); if (!janet_cstrcmp(kw, "structarg")) return janet_disasm_structarg(f->def); + if (!janet_cstrcmp(kw, "namedargs")) return janet_disasm_namedargs(f->def); if (!janet_cstrcmp(kw, "slotcount")) return janet_disasm_slotcount(f->def); if (!janet_cstrcmp(kw, "constants")) return janet_disasm_constants(f->def); if (!janet_cstrcmp(kw, "sourcemap")) return janet_disasm_sourcemap(f->def); diff --git a/src/core/bytecode.c b/src/core/bytecode.c index ddc198e6..478f269a 100644 --- a/src/core/bytecode.c +++ b/src/core/bytecode.c @@ -522,6 +522,7 @@ JanetFuncDef *janet_funcdef_alloc(void) { def->bytecode_length = 0; def->environments_length = 0; def->symbolmap_length = 0; + def->named_args_count = 0; return def; } diff --git a/src/core/compile.c b/src/core/compile.c index 9f819a83..e7e9578e 100644 --- a/src/core/compile.c +++ b/src/core/compile.c @@ -536,7 +536,7 @@ void janetc_throwaway(JanetFopts opts, Janet x) { JanetScope unusedScope; int32_t bufstart = janet_v_count(c->buffer); int32_t mapbufstart = janet_v_count(c->mapbuffer); - janetc_scope(&unusedScope, c, JANET_SCOPE_UNUSED, "unusued"); + janetc_scope(&unusedScope, c, JANET_SCOPE_UNUSED, "unused"); janetc_value(opts, x); janetc_lintf(c, JANET_C_LINT_STRICT, "dead code, consider removing %.4q", x); janetc_popscope(c); @@ -548,7 +548,7 @@ void janetc_throwaway(JanetFopts opts, Janet x) { } /* Compile a call or tailcall instruction */ -static JanetSlot janetc_call(JanetFopts opts, JanetSlot *slots, JanetSlot fun) { +static JanetSlot janetc_call(JanetFopts opts, JanetSlot *slots, JanetSlot fun, const Janet *form) { JanetSlot retslot; JanetCompiler *c = opts.compiler; int specialized = 0; @@ -574,6 +574,8 @@ static JanetSlot janetc_call(JanetFopts opts, JanetSlot *slots, JanetSlot fun) { JanetFunction *f = janet_unwrap_function(fun.constant); int32_t min = f->def->min_arity; int32_t max = f->def->max_arity; + int structarg = f->def->flags & JANET_FUNCDEF_FLAG_STRUCTARG; + int namedarg = f->def->flags & JANET_FUNCDEF_FLAG_NAMEDARGS; if (min_arity < 0) { /* Call has splices */ min_arity = -1 - min_arity; @@ -597,6 +599,42 @@ static JanetSlot janetc_call(JanetFopts opts, JanetSlot *slots, JanetSlot fun) { fun.constant, min, min == 1 ? "" : "s", min_arity); janetc_error(c, es); } + if (structarg && ((min_arity - min) & 1)) { + /* If we have an odd number of variadic arguments to a `&keys` function, that is almost certainly wrong. */ + janetc_lintf(c, JANET_C_LINT_NORMAL, + "odd number of variadic arguments to `&keys` function %v", fun.constant); + } + if (namedarg && f->def->named_args_count > 0) { + /* For each argument passed in, check if it is one of the used named arguments + * by checking the list defined in the function def. If not, raise a normal compiler + * lint. We can also do a strict lint for _missing_ named arguments, although in many + * cases those are assumed to have some kind of default, or we have dynamic keys. */ + int32_t first_arg_key_index = min + 1; + for (int32_t i = first_arg_key_index; i < janet_tuple_length(form); i += 2) { + Janet argkey = form[i]; + /* Assumption: The first N constants of a function are its named argument keys. This + * may change if the compiler changes, but is true for all Janet generated functions. */ + int found = 0; + if (janet_checktype(argkey, JANET_KEYWORD)) { + for (int32_t j = 0; j < f->def->named_args_count && j < f->def->constants_length; j++) { + if (janet_equals(argkey, f->def->constants[j])) { + found = 1; + break; + } + } + } else if (janet_checktype(argkey, JANET_TUPLE)) { + /* Possible lint : too dynamic, be dumber + * (defn f [&named x] [x]) + * (f (if (coin-flip) :x :w) 10) + * A tuple could be a function call the evaluates to a valid key */ + found = 1; + } + if (!found) { + janetc_lintf(c, JANET_C_LINT_NORMAL, + "unused named argument %v to function %v", argkey, fun.constant); + } + } + } } } break; @@ -831,14 +869,16 @@ JanetSlot janetc_value(JanetFopts opts, Janet x) { } else if (janet_tuple_flag(tup) & JANET_TUPLE_FLAG_BRACKETCTOR) { /* [] tuples are not function call */ ret = janetc_tuple(opts, x); } else { + /* Function calls */ JanetSlot head = janetc_value(subopts, tup[0]); subopts.flags = JANET_FUNCTION | JANET_CFUNCTION; - ret = janetc_call(opts, janetc_toslots(c, tup + 1, janet_tuple_length(tup) - 1), head); + ret = janetc_call(opts, janetc_toslots(c, tup + 1, janet_tuple_length(tup) - 1), head, tup); janetc_freeslot(c, head); } ret.flags &= ~JANET_SLOT_SPLICED; } break; + /* Data Constructors */ case JANET_SYMBOL: ret = janetc_resolve(c, janet_unwrap_symbol(x)); break; @@ -878,19 +918,21 @@ void janet_def_addflags(JanetFuncDef *def) { int32_t set_flags = 0; int32_t unset_flags = 0; /* pos checks */ - if (def->name) set_flags |= JANET_FUNCDEF_FLAG_HASNAME; - if (def->source) set_flags |= JANET_FUNCDEF_FLAG_HASSOURCE; - if (def->defs) set_flags |= JANET_FUNCDEF_FLAG_HASDEFS; - if (def->environments) set_flags |= JANET_FUNCDEF_FLAG_HASENVS; - if (def->sourcemap) set_flags |= JANET_FUNCDEF_FLAG_HASSOURCEMAP; - if (def->closure_bitset) set_flags |= JANET_FUNCDEF_FLAG_HASCLOBITSET; + if (def->name) set_flags |= JANET_FUNCDEF_FLAG_HASNAME; + if (def->source) set_flags |= JANET_FUNCDEF_FLAG_HASSOURCE; + if (def->defs) set_flags |= JANET_FUNCDEF_FLAG_HASDEFS; + if (def->environments) set_flags |= JANET_FUNCDEF_FLAG_HASENVS; + if (def->sourcemap) set_flags |= JANET_FUNCDEF_FLAG_HASSOURCEMAP; + if (def->closure_bitset) set_flags |= JANET_FUNCDEF_FLAG_HASCLOBITSET; + if (def->named_args_count) set_flags |= JANET_FUNCDEF_FLAG_NAMEDARGS; /* negative checks */ - if (!def->name) unset_flags |= JANET_FUNCDEF_FLAG_HASNAME; - if (!def->source) unset_flags |= JANET_FUNCDEF_FLAG_HASSOURCE; - if (!def->defs) unset_flags |= JANET_FUNCDEF_FLAG_HASDEFS; - if (!def->environments) unset_flags |= JANET_FUNCDEF_FLAG_HASENVS; - if (!def->sourcemap) unset_flags |= JANET_FUNCDEF_FLAG_HASSOURCEMAP; - if (!def->closure_bitset) unset_flags |= JANET_FUNCDEF_FLAG_HASCLOBITSET; + if (!def->name) unset_flags |= JANET_FUNCDEF_FLAG_HASNAME; + if (!def->source) unset_flags |= JANET_FUNCDEF_FLAG_HASSOURCE; + if (!def->defs) unset_flags |= JANET_FUNCDEF_FLAG_HASDEFS; + if (!def->environments) unset_flags |= JANET_FUNCDEF_FLAG_HASENVS; + if (!def->sourcemap) unset_flags |= JANET_FUNCDEF_FLAG_HASSOURCEMAP; + if (!def->closure_bitset) unset_flags |= JANET_FUNCDEF_FLAG_HASCLOBITSET; + if (!def->named_args_count) unset_flags |= JANET_FUNCDEF_FLAG_NAMEDARGS; /* Update flags */ def->flags |= set_flags; def->flags &= ~unset_flags; diff --git a/src/core/marsh.c b/src/core/marsh.c index f66cb6a4..065b1192 100644 --- a/src/core/marsh.c +++ b/src/core/marsh.c @@ -276,6 +276,8 @@ static void marshal_one_def(MarshalState *st, JanetFuncDef *def, int flags) { pushint(st, def->max_arity); pushint(st, def->constants_length); pushint(st, def->bytecode_length); + if (def->flags & JANET_FUNCDEF_FLAG_NAMEDARGS) + pushint(st, def->named_args_count); if (def->flags & JANET_FUNCDEF_FLAG_HASENVS) pushint(st, def->environments_length); if (def->flags & JANET_FUNCDEF_FLAG_HASDEFS) @@ -914,6 +916,7 @@ static const uint8_t *unmarshal_one_def( def->sourcemap = NULL; def->symbolmap = NULL; def->symbolmap_length = 0; + def->named_args_count = 0; janet_v_push(st->lookup_defs, def); /* Set default lengths to zero */ @@ -933,6 +936,8 @@ static const uint8_t *unmarshal_one_def( /* Read some lengths */ constants_length = readnat(st, &data); bytecode_length = readnat(st, &data); + if (def->flags & JANET_FUNCDEF_FLAG_NAMEDARGS) + def->named_args_count = readnat(st, &data); if (def->flags & JANET_FUNCDEF_FLAG_HASENVS) environments_length = readnat(st, &data); if (def->flags & JANET_FUNCDEF_FLAG_HASDEFS) diff --git a/src/core/specials.c b/src/core/specials.c index 0b915d78..3c981180 100644 --- a/src/core/specials.c +++ b/src/core/specials.c @@ -1141,8 +1141,12 @@ static JanetSlot janetc_fn(JanetFopts opts, int32_t argn, const Janet *argv) { def->arity = arity; def->min_arity = min_arity; def->max_arity = max_arity; + if (named_table != NULL) { + def->named_args_count = named_table->count; + } if (vararg) def->flags |= JANET_FUNCDEF_FLAG_VARARG; if (structarg) def->flags |= JANET_FUNCDEF_FLAG_STRUCTARG; + if (namedargs) def->flags |= JANET_FUNCDEF_FLAG_NAMEDARGS; if (hasname) def->name = janet_unwrap_symbol(head); /* Also correctly unwraps keyword */ janet_def_addflags(def); diff --git a/src/include/janet.h b/src/include/janet.h index add1df78..8514ed1d 100644 --- a/src/include/janet.h +++ b/src/include/janet.h @@ -1074,6 +1074,7 @@ struct JanetAbstractHead { #define JANET_FUNCDEF_FLAG_HASSOURCEMAP 0x800000 #define JANET_FUNCDEF_FLAG_STRUCTARG 0x1000000 #define JANET_FUNCDEF_FLAG_HASCLOBITSET 0x2000000 +#define JANET_FUNCDEF_FLAG_NAMEDARGS 0x4000000 #define JANET_FUNCDEF_FLAG_TAG 0xFFFF /* Source mapping structure for a bytecode instruction */ @@ -1115,6 +1116,7 @@ struct JanetFuncDef { int32_t environments_length; int32_t defs_length; int32_t symbolmap_length; + int32_t named_args_count; }; /* A function environment */ diff --git a/test/suite-compile.janet b/test/suite-compile.janet index d4e0dc67..9343b940 100644 --- a/test/suite-compile.janet +++ b/test/suite-compile.janet @@ -102,5 +102,38 @@ (with-dyns [:out out-buf] 1)))) (assert result "bad upvalues fuzz case") +# Named argument linting +# Enhancement for #1654 + +(defn fnamed [&named x y z] [x y z]) +(defn fkeys [&keys ks] ks) +(defn fnamed2 [_a _b _c &named x y z] [x y z]) +(defn fkeys2 [_a _b _c &keys ks] ks) + +(defn check-good-compile + [code msg] + (def lints @[]) + (def result (compile code (curenv) "suite-compile.janet" lints)) + (assert (and (function? result) (empty? lints)) msg)) + +(defn check-lint-compile + [code msg] + (def lints @[]) + (def result (compile code (curenv) "suite-compile.janet" lints)) + (assert (and (function? result) (next lints)) msg)) + +(check-good-compile '(fnamed) "named no args") +(check-good-compile '(fnamed :x 1 :y 2 :z 3) "named full args") +(check-lint-compile '(fnamed :x) "named odd args") +(check-lint-compile '(fnamed :w 0) "named wrong key args") +(check-good-compile '(fkeys :a 1) "keys even args") +(check-lint-compile '(fkeys :a 1 :b) "keys odd args") +(check-good-compile '(fnamed2 nil nil nil) "named 2 no args") +(check-good-compile '(fnamed2 nil nil nil :x 1 :y 2 :z 3) "named 2 full args") +(check-lint-compile '(fnamed2 nil nil nil :x) "named 2 odd args") +(check-lint-compile '(fnamed2 nil nil nil :w 0) "named 2 wrong key args") +(check-good-compile '(fkeys2 nil nil nil :a 1) "keys 2 even args") +(check-lint-compile '(fkeys2 nil nil nil :a 1 :b) "keys 2 odd args") + (end-suite) From 8481da18d0f546ec78bfb4e802937f7792b11ba3 Mon Sep 17 00:00:00 2001 From: Calvin Rose Date: Sat, 31 Jan 2026 13:20:45 -0600 Subject: [PATCH 135/307] Add another bounds check for updating labels. Related to #1700 --- src/core/specials.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/core/specials.c b/src/core/specials.c index 3c981180..0f6ae408 100644 --- a/src/core/specials.c +++ b/src/core/specials.c @@ -688,7 +688,7 @@ static JanetSlot janetc_if(JanetFopts opts, int32_t argn, const Janet *argv) { labeld = janet_v_count(c->buffer); if (labeljr < labeld) { c->buffer[labeljr] |= (labelr - labeljr) << 16; - if (!tail) c->buffer[labeljd] |= (labeld - labeljd) << 8; + if (!tail && labeljd < labeld) c->buffer[labeljd] |= (labeld - labeljd) << 8; } if (tail) target.flags |= JANET_SLOT_RETURNED; From 497e363401f50381cdb843b89d8e9310838bccea Mon Sep 17 00:00:00 2001 From: Calvin Rose Date: Sat, 31 Jan 2026 19:34:26 -0600 Subject: [PATCH 136/307] Make sure named argumnets are compiled before destructured params. This handles an edge-case when combing destructured and named parameters that produced spurious and confusing compiler lints. --- src/boot/boot.janet | 4 ++-- src/core/specials.c | 16 ++++++++-------- test/suite-compile.janet | 3 +++ 3 files changed, 13 insertions(+), 10 deletions(-) diff --git a/src/boot/boot.janet b/src/boot/boot.janet index ee0fc6b4..d2a33e24 100644 --- a/src/boot/boot.janet +++ b/src/boot/boot.janet @@ -3968,8 +3968,8 @@ (defmacro ev/gather `` - Run a number of fibers in parallel (created from `bodies`) and resume the - current fiber after they complete. Returns the gathered results in an + Create and run a number of fibers in parallel (created from `bodies`) and resume the + current fiber after they complete. Shorthand for `ev/go-gather`. Returns the gathered results in an array. `` [& bodies] diff --git a/src/core/specials.c b/src/core/specials.c index 0f6ae408..ffa77821 100644 --- a/src/core/specials.c +++ b/src/core/specials.c @@ -1078,6 +1078,14 @@ static JanetSlot janetc_fn(JanetFopts opts, int32_t argn, const Janet *argv) { } } + /* Compile named arguments */ + if (namedargs) { + Janet param = janet_wrap_table(named_table); + destructure(c, param, named_slot, defleaf, NULL); + janetc_freeslot(c, named_slot); + janet_v_free(named_params); + } + /* Compile destructed params */ int32_t j = 0; for (i = 0; i < paramcount; i++) { @@ -1091,14 +1099,6 @@ static JanetSlot janetc_fn(JanetFopts opts, int32_t argn, const Janet *argv) { } janet_v_free(destructed_params); - /* Compile named arguments */ - if (namedargs) { - Janet param = janet_wrap_table(named_table); - destructure(c, param, named_slot, defleaf, NULL); - janetc_freeslot(c, named_slot); - janet_v_free(named_params); - } - max_arity = (vararg || allow_extra) ? INT32_MAX : arity; if (!seenopt) min_arity = arity; diff --git a/test/suite-compile.janet b/test/suite-compile.janet index 9343b940..bb34ac62 100644 --- a/test/suite-compile.janet +++ b/test/suite-compile.janet @@ -109,6 +109,7 @@ (defn fkeys [&keys ks] ks) (defn fnamed2 [_a _b _c &named x y z] [x y z]) (defn fkeys2 [_a _b _c &keys ks] ks) +(defn fnamed3 [{:x x} &named a b c] [x a b c]) (defn check-good-compile [code msg] @@ -134,6 +135,8 @@ (check-lint-compile '(fnamed2 nil nil nil :w 0) "named 2 wrong key args") (check-good-compile '(fkeys2 nil nil nil :a 1) "keys 2 even args") (check-lint-compile '(fkeys2 nil nil nil :a 1 :b) "keys 2 odd args") +(check-good-compile '(fnamed3 {:x 1} :a 1 :b 2 :c 3) "named 3 good") +(check-lint-compile '(fnamed3 {:x 1} :a 1 :b 2 :d 3) "named 3 lint") (end-suite) From d47f82713b1d7fbc483fb89fa4f24575aa8bc0df Mon Sep 17 00:00:00 2001 From: Calvin Rose Date: Sat, 31 Jan 2026 23:57:08 -0600 Subject: [PATCH 137/307] More tweaks for named arity linting. Handle more edge cases where &named is combined with &opt --- src/core/compile.c | 13 +++++++++---- test/suite-compile.janet | 12 ++++++++++++ 2 files changed, 21 insertions(+), 4 deletions(-) diff --git a/src/core/compile.c b/src/core/compile.c index e7e9578e..152b95cb 100644 --- a/src/core/compile.c +++ b/src/core/compile.c @@ -599,17 +599,22 @@ static JanetSlot janetc_call(JanetFopts opts, JanetSlot *slots, JanetSlot fun, c fun.constant, min, min == 1 ? "" : "s", min_arity); janetc_error(c, es); } - if (structarg && ((min_arity - min) & 1)) { + if (structarg && (min_arity > f->def->arity) && ((min_arity - f->def->arity) & 1)) { /* If we have an odd number of variadic arguments to a `&keys` function, that is almost certainly wrong. */ - janetc_lintf(c, JANET_C_LINT_NORMAL, - "odd number of variadic arguments to `&keys` function %v", fun.constant); + if (namedarg) { + janetc_lintf(c, JANET_C_LINT_NORMAL, + "odd number of named arguments to `&named` function %v", fun.constant); + } else { + janetc_lintf(c, JANET_C_LINT_NORMAL, + "odd number of named arguments to `&keys` function %v", fun.constant); + } } if (namedarg && f->def->named_args_count > 0) { /* For each argument passed in, check if it is one of the used named arguments * by checking the list defined in the function def. If not, raise a normal compiler * lint. We can also do a strict lint for _missing_ named arguments, although in many * cases those are assumed to have some kind of default, or we have dynamic keys. */ - int32_t first_arg_key_index = min + 1; + int32_t first_arg_key_index = f->def->arity + 1; for (int32_t i = first_arg_key_index; i < janet_tuple_length(form); i += 2) { Janet argkey = form[i]; /* Assumption: The first N constants of a function are its named argument keys. This diff --git a/test/suite-compile.janet b/test/suite-compile.janet index bb34ac62..ec543a1c 100644 --- a/test/suite-compile.janet +++ b/test/suite-compile.janet @@ -110,6 +110,9 @@ (defn fnamed2 [_a _b _c &named x y z] [x y z]) (defn fkeys2 [_a _b _c &keys ks] ks) (defn fnamed3 [{:x x} &named a b c] [x a b c]) +(defn fnamed4 [_y &opt _z &named a b c] [a b c]) +(defn fnamed5 [&opt _z &named a b c] [a b c]) +(defn g [x &opt y &named z] [x y z]) (defn check-good-compile [code msg] @@ -137,6 +140,15 @@ (check-lint-compile '(fkeys2 nil nil nil :a 1 :b) "keys 2 odd args") (check-good-compile '(fnamed3 {:x 1} :a 1 :b 2 :c 3) "named 3 good") (check-lint-compile '(fnamed3 {:x 1} :a 1 :b 2 :d 3) "named 3 lint") +(check-good-compile '(fnamed4 10 20 :a 1 :b 2 :c 3) "named 4 good") +(check-lint-compile '(fnamed4 10 20 :a 1 :b 2 :d 3) "named 4 lint") +(check-good-compile '(fnamed5 10 :a 1 :b 2 :c 3) "named 5 good") +(check-lint-compile '(fnamed5 10 :a 1 :b 2 :d 3) "named 5 lint") +(check-good-compile '(g 1) "g good 1") +(check-good-compile '(g 1 2) "g good 2") +(check-good-compile '(g 1 2 :z 10) "g good 3") +(check-lint-compile '(g 1 2 :z) "g lint 1") +(check-lint-compile '(g 1 2 :z 4 5) "g lint 2") (end-suite) From ecb72c9c9a80436bee180ef45f072ea0e5717830 Mon Sep 17 00:00:00 2001 From: Calvin Rose Date: Sun, 1 Feb 2026 08:08:50 -0600 Subject: [PATCH 138/307] Revert changes to peg grammar. --- src/core/peg.c | 2 -- 1 file changed, 2 deletions(-) diff --git a/src/core/peg.c b/src/core/peg.c index e0c5c76f..560458a7 100644 --- a/src/core/peg.c +++ b/src/core/peg.c @@ -1832,8 +1832,6 @@ static JanetPeg *compile_peg(Janet x) { Janet default_grammarv = janet_dyn("peg-grammar"); if (janet_checktype(default_grammarv, JANET_TABLE)) { builder.default_grammar = janet_unwrap_table(default_grammarv); - } else { - builder.default_grammar = janet_get_core_table("default-peg-grammar"); } } builder.tags = janet_table(0); From 643c0b497642a633369bc1af16da2c2c851dd9dd Mon Sep 17 00:00:00 2001 From: Calvin Rose Date: Sun, 1 Feb 2026 10:54:31 -0600 Subject: [PATCH 139/307] Update CHANGELOG.md --- CHANGELOG.md | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index 9990a7c5..64a29858 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -2,11 +2,15 @@ All notable changes to this project will be documented in this file. ## Unreleased - ??? +- Add support for persistent REPL history with the environment variable `JANET_HISTFILE` +- Fix windows processes launching bug with empty environment table that caused process-launch failures. - Add `:I`, `:V`, and `:N` flags to `os/open` for more control when creating streams. - Add `ev/go-gather` for a dynamic `ev/gather`. - Use color in script output if color is being used in REPL output. - Fix `varfn` macros handling of extra metadata. +- Disallow certain degenerate uses of fibers with the ev/ module. - Add linting for unused bindings. +- Add linting for extra or wrong parameters to &named functions. - Add `janet_optuinteger` and `janet_optuinteger64` to the C API. - Add `cms` combinator to PEGs. - Add `thaw-keep-keys` as a variant of thaw From 5042ad6d4bacfda15e11f3a20b149a0f6fc00dcc Mon Sep 17 00:00:00 2001 From: Calvin Rose Date: Sun, 1 Feb 2026 14:46:20 -0600 Subject: [PATCH 140/307] More tweaks to changelog. --- CHANGELOG.md | 1 + 1 file changed, 1 insertion(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index 64a29858..2678f4ea 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -3,6 +3,7 @@ All notable changes to this project will be documented in this file. ## Unreleased - ??? - Add support for persistent REPL history with the environment variable `JANET_HISTFILE` +- Fix a number of fuzzer-found compiler bugs - Fix windows processes launching bug with empty environment table that caused process-launch failures. - Add `:I`, `:V`, and `:N` flags to `os/open` for more control when creating streams. - Add `ev/go-gather` for a dynamic `ev/gather`. From a37752708e58463635b42505fb6bb03459f54423 Mon Sep 17 00:00:00 2001 From: Calvin Rose Date: Mon, 2 Feb 2026 18:13:46 -0600 Subject: [PATCH 141/307] Add :compile, :asm, and :threads to sandbox flags. These can be used to limit programs in a very expansive way. --- src/core/asm.c | 2 ++ src/core/compile.c | 1 + src/core/corelib.c | 8 +++++++- src/core/ev.c | 1 + src/include/janet.h | 3 +++ 5 files changed, 14 insertions(+), 1 deletion(-) diff --git a/src/core/asm.c b/src/core/asm.c index 4afc7a28..171754c0 100644 --- a/src/core/asm.c +++ b/src/core/asm.c @@ -1064,6 +1064,7 @@ JANET_CORE_FN(cfun_asm, "The syntax for the assembly can be found on the Janet website, and should correspond\n" "to the return value of disasm. Will throw an\n" "error on invalid assembly.") { + janet_sandbox_assert(JANET_SANDBOX_ASM); janet_fixarity(argc, 1); JanetAssembleResult res; res = janet_asm(argv[0], 0); @@ -1094,6 +1095,7 @@ JANET_CORE_FN(cfun_disasm, "* :sourcemap - a mapping of each bytecode instruction to a line and column in the source file.\n" "* :environments - an internal mapping of which enclosing functions are referenced for bindings.\n" "* :defs - other function definitions that this function may instantiate.\n") { + janet_sandbox_assert(JANET_SANDBOX_ASM); janet_arity(argc, 1, 2); JanetFunction *f = janet_getfunction(argv, 0); if (argc == 2) { diff --git a/src/core/compile.c b/src/core/compile.c index 152b95cb..45a252a7 100644 --- a/src/core/compile.c +++ b/src/core/compile.c @@ -1156,6 +1156,7 @@ JANET_CORE_FN(cfun_compile, "struct with keys :line, :column, and :error if compilation fails. " "If a `lints` array is given, linting messages will be appended to the array. " "Each message will be a tuple of the form `(level line col message)`.") { + janet_sandbox_assert(JANET_SANDBOX_COMPILE); janet_arity(argc, 1, 4); JanetTable *env = (argc > 1 && !janet_checktype(argv[1], JANET_NIL)) ? janet_gettable(argv, 1) : janet_vm.fiber->env; diff --git a/src/core/corelib.c b/src/core/corelib.c index e2450269..c67d3b9e 100644 --- a/src/core/corelib.c +++ b/src/core/corelib.c @@ -746,7 +746,9 @@ typedef struct SandboxOption { static const SandboxOption sandbox_options[] = { {"all", JANET_SANDBOX_ALL}, + {"asm", JANET_SANDBOX_ASM}, {"chroot", JANET_SANDBOX_CHROOT}, + {"compile", JANET_SANDBOX_COMPILE}, {"env", JANET_SANDBOX_ENV}, {"ffi", JANET_SANDBOX_FFI}, {"ffi-define", JANET_SANDBOX_FFI_DEFINE}, @@ -764,6 +766,7 @@ static const SandboxOption sandbox_options[] = { {"sandbox", JANET_SANDBOX_SANDBOX}, {"signal", JANET_SANDBOX_SIGNAL}, {"subprocess", JANET_SANDBOX_SUBPROCESS}, + {"threads", JANET_SANDBOX_THREADS}, {NULL, 0} }; @@ -772,7 +775,9 @@ JANET_CORE_FN(janet_core_sandbox, "Disable feature sets to prevent the interpreter from using certain system resources. " "Once a feature is disabled, there is no way to re-enable it. Capabilities can be:\n\n" "* :all - disallow all (except IO to stdout, stderr, and stdin)\n" + "* :asm - disallow calling `asm` and `disasm` functions.\n" "* :chroot - disallow calling `os/posix-chroot`\n" + "* :compile - disallow calling `compile`. This will disable a lot of functionality, such as `eval`.\n" "* :env - disallow reading and write env variables\n" "* :ffi - disallow FFI (recommended if disabling anything else)\n" "* :ffi-define - disallow loading new FFI modules and binding new functions\n" @@ -789,7 +794,8 @@ JANET_CORE_FN(janet_core_sandbox, "* :net-listen - disallow accepting inbound network connections\n" "* :sandbox - disallow calling this function\n" "* :signal - disallow adding or removing signal handlers\n" - "* :subprocess - disallow running subprocesses") { + "* :subprocess - disallow running subprocesses\n" + "* :threads - disallow spawning threads with `ev/thread`. Certain helper threads may still be spawned.") { uint32_t flags = 0; for (int32_t i = 0; i < argc; i++) { JanetKeyword kw = janet_getkeyword(argv, i); diff --git a/src/core/ev.c b/src/core/ev.c index 9d2e8fd3..250ab728 100644 --- a/src/core/ev.c +++ b/src/core/ev.c @@ -3178,6 +3178,7 @@ JANET_CORE_FN(cfun_ev_thread, "* `:t` - set the task-id of the new thread to value. The task-id is passed in messages to the supervisor channel.\n" "* `:a` - don't copy abstract registry to new thread (performance optimization)\n" "* `:c` - don't copy cfunction registry to new thread (performance optimization)") { + janet_sandbox_assert(JANET_SANDBOX_THREADS); janet_arity(argc, 1, 4); Janet value = argc >= 2 ? argv[1] : janet_wrap_nil(); if (!janet_checktype(argv[0], JANET_FUNCTION)) janet_getfiber(argv, 0); diff --git a/src/include/janet.h b/src/include/janet.h index 8514ed1d..9f47d8ef 100644 --- a/src/include/janet.h +++ b/src/include/janet.h @@ -2005,6 +2005,9 @@ JANET_API void janet_stacktrace_ext(JanetFiber *fiber, Janet err, const char *pr #define JANET_SANDBOX_FFI (JANET_SANDBOX_FFI_DEFINE | JANET_SANDBOX_FFI_USE | JANET_SANDBOX_FFI_JIT) #define JANET_SANDBOX_FS (JANET_SANDBOX_FS_WRITE | JANET_SANDBOX_FS_READ | JANET_SANDBOX_FS_TEMP) #define JANET_SANDBOX_NET (JANET_SANDBOX_NET_CONNECT | JANET_SANDBOX_NET_LISTEN) +#define JANET_SANDBOX_COMPILE 32768 +#define JANET_SANDBOX_ASM 65536 +#define JANET_SANDBOX_THREADS 131072 #define JANET_SANDBOX_ALL (UINT32_MAX) JANET_API void janet_sandbox(uint32_t flags); JANET_API void janet_sandbox_assert(uint32_t forbidden_flags); From 4f9a2af35769999fd0f5e41b00bbc7d6631b2f94 Mon Sep 17 00:00:00 2001 From: Calvin Rose Date: Mon, 2 Feb 2026 18:28:08 -0600 Subject: [PATCH 142/307] Add unmarshal sandbox. --- src/core/corelib.c | 4 +++- src/core/marsh.c | 1 + src/include/janet.h | 1 + 3 files changed, 5 insertions(+), 1 deletion(-) diff --git a/src/core/corelib.c b/src/core/corelib.c index c67d3b9e..420c47c7 100644 --- a/src/core/corelib.c +++ b/src/core/corelib.c @@ -767,6 +767,7 @@ static const SandboxOption sandbox_options[] = { {"signal", JANET_SANDBOX_SIGNAL}, {"subprocess", JANET_SANDBOX_SUBPROCESS}, {"threads", JANET_SANDBOX_THREADS}, + {"unmarshal", JANET_SANDBOX_UNMARSHAL}, {NULL, 0} }; @@ -795,7 +796,8 @@ JANET_CORE_FN(janet_core_sandbox, "* :sandbox - disallow calling this function\n" "* :signal - disallow adding or removing signal handlers\n" "* :subprocess - disallow running subprocesses\n" - "* :threads - disallow spawning threads with `ev/thread`. Certain helper threads may still be spawned.") { + "* :threads - disallow spawning threads with `ev/thread`. Certain helper threads may still be spawned.\n" + "* :unmarshal - disallow calling the unmarshal function.\n") { uint32_t flags = 0; for (int32_t i = 0; i < argc; i++) { JanetKeyword kw = janet_getkeyword(argv, i); diff --git a/src/core/marsh.c b/src/core/marsh.c index 065b1192..a30b09f2 100644 --- a/src/core/marsh.c +++ b/src/core/marsh.c @@ -1698,6 +1698,7 @@ JANET_CORE_FN(cfun_unmarshal, "Unmarshal a value from a buffer. An optional lookup table " "can be provided to allow for aliases to be resolved. Returns the value " "unmarshalled from the buffer.") { + janet_sandbox_assert(JANET_SANDBOX_UNMARSHAL); janet_arity(argc, 1, 2); JanetByteView view = janet_getbytes(argv, 0); JanetTable *reg = NULL; diff --git a/src/include/janet.h b/src/include/janet.h index 9f47d8ef..2bada6e6 100644 --- a/src/include/janet.h +++ b/src/include/janet.h @@ -2008,6 +2008,7 @@ JANET_API void janet_stacktrace_ext(JanetFiber *fiber, Janet err, const char *pr #define JANET_SANDBOX_COMPILE 32768 #define JANET_SANDBOX_ASM 65536 #define JANET_SANDBOX_THREADS 131072 +#define JANET_SANDBOX_UNMARSHAL 262144 #define JANET_SANDBOX_ALL (UINT32_MAX) JANET_API void janet_sandbox(uint32_t flags); JANET_API void janet_sandbox_assert(uint32_t forbidden_flags); From 517e40a17b8d42cfc1cae29f2d0b9015da84c9a9 Mon Sep 17 00:00:00 2001 From: Calvin Rose Date: Mon, 2 Feb 2026 19:39:45 -0600 Subject: [PATCH 143/307] Speed up symbol resolution. In my testing of `janet -l spork -e '(os/exit)'`, this speeds up compilation by ~5%. Allocating and interning symbols from C is often slow, so avoid it. Add utility function for looking up keywords in tables more quickly. More measurement on compiling spork for compilation speed, win over original code closer to between 1-2% improvement --- src/core/compile.c | 2 +- src/core/os.c | 4 +-- src/core/specials.c | 16 ++++++------ src/core/table.c | 11 +++++++++ src/core/util.c | 60 ++++++++++++++++++++++++++++++++++++++++----- src/core/util.h | 30 +++++++++++++++++++++++ 6 files changed, 105 insertions(+), 18 deletions(-) diff --git a/src/core/compile.c b/src/core/compile.c index 45a252a7..2c68555a 100644 --- a/src/core/compile.c +++ b/src/core/compile.c @@ -295,7 +295,7 @@ JanetSlot janetc_resolve( { JanetBinding binding = janet_resolve_ext(c->env, sym); if (binding.type == JANET_BINDING_NONE) { - Janet handler = janet_table_get(c->env, janet_ckeywordv("missing-symbol")); + Janet handler = janet_table_get_keyword(c->env, "missing-symbol"); switch (janet_type(handler)) { case JANET_NIL: break; diff --git a/src/core/os.c b/src/core/os.c index 9149ac18..8e514113 100644 --- a/src/core/os.c +++ b/src/core/os.c @@ -1959,7 +1959,7 @@ static int entry_getdst(Janet env_entry) { Janet v; if (janet_checktype(env_entry, JANET_TABLE)) { JanetTable *entry = janet_unwrap_table(env_entry); - v = janet_table_get(entry, janet_ckeywordv("dst")); + v = janet_table_get_keyword(entry, "dst"); } else if (janet_checktype(env_entry, JANET_STRUCT)) { const JanetKV *entry = janet_unwrap_struct(env_entry); v = janet_struct_get(entry, janet_ckeywordv("dst")); @@ -1983,7 +1983,7 @@ static timeint_t entry_getint(Janet env_entry, char *field) { Janet i; if (janet_checktype(env_entry, JANET_TABLE)) { JanetTable *entry = janet_unwrap_table(env_entry); - i = janet_table_get(entry, janet_ckeywordv(field)); + i = janet_table_get_keyword(entry, field); } else if (janet_checktype(env_entry, JANET_STRUCT)) { const JanetKV *entry = janet_unwrap_struct(env_entry); i = janet_struct_get(entry, janet_ckeywordv(field)); diff --git a/src/core/specials.c b/src/core/specials.c index ffa77821..7f4646a2 100644 --- a/src/core/specials.c +++ b/src/core/specials.c @@ -307,11 +307,11 @@ static JanetSlot janetc_varset(JanetFopts opts, int32_t argn, const Janet *argv) /* Add attributes to a global def or var table */ static JanetTable *handleattr(JanetCompiler *c, const char *kind, int32_t argn, const Janet *argv) { int32_t i; - JanetTable *tab = janet_table(2); if (argn < 2) { janetc_error(c, janet_formatc("expected at least 2 arguments to %s", kind)); return NULL; } + JanetTable *tab = janet_table(2); const char *binding_name = janet_type(argv[0]) == JANET_SYMBOL ? ((const char *)janet_unwrap_symbol(argv[0])) : ""; @@ -443,8 +443,7 @@ static int varleaf( JanetSlot refslot; JanetTable *entry = janet_table_clone(reftab); - Janet redef_kw = janet_ckeywordv("redef"); - int is_redef = janet_truthy(janet_table_get(c->env, redef_kw)); + int is_redef = janet_truthy(janet_table_get_keyword(c->env, "redef")); JanetArray *ref; JanetBinding old_binding; @@ -464,7 +463,7 @@ static int varleaf( janetc_emit_ssu(c, JOP_PUT_INDEX, refslot, s, 0, 0); return 1; } else { - int no_unused = reftab && reftab->count && janet_truthy(janet_table_get(reftab, janet_ckeywordv("unused"))); + int no_unused = reftab && reftab->count && janet_truthy(janet_table_get_keyword(reftab, "unused")); return namelocal(c, sym, JANET_SLOT_MUTABLE, s, no_unused); } } @@ -472,7 +471,7 @@ static int varleaf( static void check_metadata_lint(JanetCompiler *c, JanetTable *attr_table) { if (!(c->scope->flags & JANET_SCOPE_TOP) && attr_table && attr_table->count) { /* A macro is a normal lint, other metadata is a strict lint */ - if (janet_truthy(janet_table_get(attr_table, janet_ckeywordv("macro")))) { + if (janet_truthy(janet_table_get_keyword(attr_table, "macro"))) { janetc_lintf(c, JANET_C_LINT_NORMAL, "macro tag is ignored in inner scopes"); } } @@ -511,9 +510,8 @@ static int defleaf( janet_table_put(entry, janet_ckeywordv("source-map"), janet_wrap_tuple(janetc_make_sourcemap(c))); - Janet redef_kw = janet_ckeywordv("redef"); - int is_redef = janet_truthy(janet_table_get(c->env, redef_kw)); - if (is_redef) janet_table_put(entry, redef_kw, janet_wrap_true()); + int is_redef = janet_truthy(janet_table_get_keyword(c->env, "redef")); + if (is_redef) janet_table_put(entry, janet_ckeywordv("redef"), janet_wrap_true()); if (is_redef) { JanetBinding binding = janet_resolve_ext(c->env, sym); @@ -536,7 +534,7 @@ static int defleaf( /* Add env entry to env */ janet_table_put(c->env, janet_wrap_symbol(sym), janet_wrap_table(entry)); } - int no_unused = tab && tab->count && janet_truthy(janet_table_get(tab, janet_ckeywordv("unused"))); + int no_unused = tab && tab->count && janet_truthy(janet_table_get_keyword(tab, "unused")); return namelocal(c, sym, 0, s, no_unused); } diff --git a/src/core/table.c b/src/core/table.c index ffaf7591..e25e699f 100644 --- a/src/core/table.c +++ b/src/core/table.c @@ -155,6 +155,17 @@ Janet janet_table_get(JanetTable *t, Janet key) { return janet_wrap_nil(); } +/* Used internally for compiler stuff */ +Janet janet_table_get_keyword(JanetTable *t, const char *keyword) { + int32_t keyword_len = strlen(keyword); + for (int i = JANET_MAX_PROTO_DEPTH; t && i; t = t->proto, --i) { + JanetKV *bucket = (JanetKV *) janet_dict_find_keyword(t->data, t->capacity, (const uint8_t *) keyword, keyword_len); + if (NULL != bucket && !janet_checktype(bucket->key, JANET_NIL)) + return bucket->value; + } + return janet_wrap_nil(); +} + /* Get a value out of the table, and record which prototype it was from. */ Janet janet_table_get_ex(JanetTable *t, Janet key, JanetTable **which) { for (int i = JANET_MAX_PROTO_DEPTH; t && i; t = t->proto, --i) { diff --git a/src/core/util.c b/src/core/util.c index b45df256..723b64f2 100644 --- a/src/core/util.c +++ b/src/core/util.c @@ -321,6 +321,54 @@ const JanetKV *janet_dict_find(const JanetKV *buckets, int32_t cap, Janet key) { return first_bucket; } +/* Helper to find a keyword, symbol, or string in a Janet struct or table without allocating + * memory or needing to find interned symbols */ +const JanetKV *janet_dict_find_keyword( + const JanetKV *buckets, int32_t cap, + const uint8_t *cstr, int32_t cstr_len) { + int32_t hash = janet_string_calchash(cstr, cstr_len); + int32_t index = janet_maphash(cap, hash); + int32_t i; + const JanetKV *first_bucket = NULL; + /* Higher half */ + for (i = index; i < cap; i++) { + const JanetKV *kv = buckets + i; + if (janet_checktype(kv->key, JANET_NIL)) { + if (janet_checktype(kv->value, JANET_NIL)) { + return kv; + } else if (NULL == first_bucket) { + first_bucket = kv; + } + } else if (janet_checktype(kv->key, JANET_KEYWORD)) { + /* Works for symbol and keyword, too */ + JanetString str = janet_unwrap_string(kv->key); + int32_t len = janet_string_length(str); + if (hash == janet_string_hash(str) && len == cstr_len && !memcmp(str, cstr, len)) { + return buckets + i; + } + } + } + /* Lower half */ + for (i = 0; i < index; i++) { + const JanetKV *kv = buckets + i; + if (janet_checktype(kv->key, JANET_NIL)) { + if (janet_checktype(kv->value, JANET_NIL)) { + return kv; + } else if (NULL == first_bucket) { + first_bucket = kv; + } + } else if (janet_checktype(kv->key, JANET_KEYWORD)) { + /* Works for symbol and keyword, too */ + JanetString str = janet_unwrap_string(kv->key); + int32_t len = janet_string_length(str); + if (hash == janet_string_hash(str) && len == cstr_len && !memcmp(str, cstr, len)) { + return buckets + i; + } + } + } + return first_bucket; +} + /* Get a value from a janet struct or table. */ Janet janet_dictionary_get(const JanetKV *data, int32_t cap, Janet key) { const JanetKV *kv = janet_dict_find(data, cap, key); @@ -628,8 +676,11 @@ JanetBinding janet_binding_from_entry(Janet entry) { return binding; entry_table = janet_unwrap_table(entry); - /* deprecation check */ - Janet deprecate = janet_table_get(entry_table, janet_ckeywordv("deprecated")); + Janet deprecate = janet_table_get_keyword(entry_table, "deprecated"); + int macro = janet_truthy(janet_table_get_keyword(entry_table, "macro")); + Janet value = janet_table_get_keyword(entry_table, "value"); + Janet ref = janet_table_get_keyword(entry_table, "ref"); + if (janet_checktype(deprecate, JANET_KEYWORD)) { JanetKeyword depkw = janet_unwrap_keyword(deprecate); if (!janet_cstrcmp(depkw, "relaxed")) { @@ -643,11 +694,8 @@ JanetBinding janet_binding_from_entry(Janet entry) { binding.deprecation = JANET_BINDING_DEP_NORMAL; } - int macro = janet_truthy(janet_table_get(entry_table, janet_ckeywordv("macro"))); - Janet value = janet_table_get(entry_table, janet_ckeywordv("value")); - Janet ref = janet_table_get(entry_table, janet_ckeywordv("ref")); int ref_is_valid = janet_checktype(ref, JANET_ARRAY); - int redef = ref_is_valid && janet_truthy(janet_table_get(entry_table, janet_ckeywordv("redef"))); + int redef = ref_is_valid && janet_truthy(janet_table_get_keyword(entry_table, "redef")); if (macro) { binding.value = redef ? ref : value; diff --git a/src/core/util.h b/src/core/util.h index 7b550f50..874ef7b0 100644 --- a/src/core/util.h +++ b/src/core/util.h @@ -66,42 +66,72 @@ /* Utils */ uint32_t janet_hash_mix(uint32_t input, uint32_t more); + #define janet_maphash(cap, hash) ((uint32_t)(hash) & (cap - 1)) + int janet_valid_utf8(const uint8_t *str, int32_t len); + int janet_is_symbol_char(uint8_t c); + extern const char janet_base64[65]; + int32_t janet_array_calchash(const Janet *array, int32_t len); + int32_t janet_kv_calchash(const JanetKV *kvs, int32_t len); + int32_t janet_string_calchash(const uint8_t *str, int32_t len); + int32_t janet_tablen(int32_t n); + void safe_memcpy(void *dest, const void *src, size_t len); + void janet_buffer_push_types(JanetBuffer *buffer, int types); + const JanetKV *janet_dict_find(const JanetKV *buckets, int32_t cap, Janet key); + void janet_memempty(JanetKV *mem, int32_t count); + void *janet_memalloc_empty(int32_t count); + JanetTable *janet_get_core_table(const char *name); + void janet_def_addflags(JanetFuncDef *def); + void janet_buffer_dtostr(JanetBuffer *buffer, double x); + const char *janet_strerror(int e); + const void *janet_strbinsearch( const void *tab, size_t tabcount, size_t itemsize, const uint8_t *key); + void janet_buffer_format( JanetBuffer *b, const char *strfrmt, int32_t argstart, int32_t argc, Janet *argv); + Janet janet_next_impl(Janet ds, Janet key, int is_interpreter); + JanetBinding janet_binding_from_entry(Janet entry); + JanetByteView janet_text_substitution( Janet *subst, const uint8_t *bytes, uint32_t len, JanetArray *extra_args); +const JanetKV *janet_dict_find_keyword( + const JanetKV *buckets, + int32_t cap, + const uint8_t *cstr, + int32_t cstr_len); + +Janet janet_table_get_keyword(JanetTable *t, const char *keyword); + /* Registry functions */ void janet_registry_put( JanetCFunction key, From 416bba9bd9b969ed10517afba969bea0f9cc635b Mon Sep 17 00:00:00 2001 From: 0xbec163 <77907411+0xbec163@users.noreply.github.com> Date: Wed, 4 Feb 2026 18:44:46 -0600 Subject: [PATCH 144/307] Add ??/debug PEG rule (#1710) * Add debug (short-form ??) PEG rule * Display capture stack to debug PEG rule * Revise spec_debug to emit just the rule * Update to proper indentation Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com> * Add a marshal/unmarshal test for ??/debug --------- Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com> --- src/core/peg.c | 25 +++++++++++++++++++++++ src/include/janet.h | 1 + test/suite-peg.janet | 47 ++++++++++++++++++++++++++++++++++++++++++++ 3 files changed, 73 insertions(+) diff --git a/src/core/peg.c b/src/core/peg.c index 560458a7..1912dcea 100644 --- a/src/core/peg.c +++ b/src/core/peg.c @@ -194,6 +194,17 @@ tail: return memcmp(text, rule + 2, len) ? NULL : text + len; } + case RULE_DEBUG: { + char buffer[32] = {0}; + size_t len = (size_t)(s->outer_text_end - text); + memcpy(buffer, text, (len > 31 ? 31 : len)); + janet_printf("\n?? at [%s]\nstack [%d]:\n", buffer, s->captures->count); + for (int32_t i = 0; i < s->captures->count; i++) { + janet_printf(" [%d]: %M\n", i, s->captures->data[i]); + } + return text; + } + case RULE_NCHAR: { uint32_t n = rule[1]; return (text + n > s->text_end) ? NULL : text + n; @@ -1245,6 +1256,14 @@ static void spec_constant(Builder *b, int32_t argc, const Janet *argv) { emit_2(r, RULE_CONSTANT, emit_constant(b, argv[0]), tag); } +static void spec_debug(Builder *b, int32_t argc, const Janet *argv) { + peg_arity(b, argc, 0, 0); + Reserve r = reserve(b, 1); + uint32_t empty = 0; + (void) argv; + emit_rule(r, RULE_DEBUG, 0, &empty); +} + static void spec_replace(Builder *b, int32_t argc, const Janet *argv) { peg_arity(b, argc, 2, 3); Reserve r = reserve(b, 4); @@ -1349,6 +1368,7 @@ static const SpecialPair peg_specials[] = { {"<-", spec_capture}, {">", spec_look}, {"?", spec_opt}, + {"??", spec_debug}, {"accumulate", spec_accumulate}, {"any", spec_any}, {"argument", spec_argument}, @@ -1363,6 +1383,7 @@ static const SpecialPair peg_specials[] = { {"cmt", spec_matchtime}, {"column", spec_column}, {"constant", spec_constant}, + {"debug", spec_debug}, {"drop", spec_drop}, {"error", spec_error}, {"group", spec_group}, @@ -1639,6 +1660,10 @@ static void *peg_unmarshal(JanetMarshalContext *ctx) { case RULE_LITERAL: i += 2 + ((rule[1] + 3) >> 2); break; + case RULE_DEBUG: + /* [0 words] */ + i += 1; + break; case RULE_NCHAR: case RULE_NOTNCHAR: case RULE_RANGE: diff --git a/src/include/janet.h b/src/include/janet.h index 2bada6e6..e1a5c79a 100644 --- a/src/include/janet.h +++ b/src/include/janet.h @@ -2318,6 +2318,7 @@ typedef enum { RULE_NTH, /* [nth, rule, tag] */ RULE_ONLY_TAGS, /* [rule] */ RULE_MATCHSPLICE, /* [rule, constant, tag] */ + RULE_DEBUG, /* [] */ } JanetPegOpcode; typedef struct { diff --git a/test/suite-peg.janet b/test/suite-peg.janet index 5b6d4f61..ae3d0dbf 100644 --- a/test/suite-peg.janet +++ b/test/suite-peg.janet @@ -845,4 +845,51 @@ "abc" @[["b" "b" "b"]]) +# Debug and ?? tests. +(defn test-stdout [name peg input expected-matches expected-stdout] + (def actual @"") + (with-dyns [:out actual] + (test name peg input expected-matches)) + (assert (deep= (string actual) expected-stdout))) + +(test-stdout "?? long form" + '(* (debug) "abc") + "abc" + @[] + "\n?? at [abc]\nstack [0]:\n") + +(test-stdout "?? short form" + '(* (??) "abc") + "abc" + @[] + "\n?? at [abc]\nstack [0]:\n") + +(test-stdout "?? end of text" + '(* "abc" (??)) + "abc" + @[] + "\n?? at []\nstack [0]:\n") + +(test-stdout "?? between rules" + '(* "a" (??) "bc") + "abc" + @[] + "\n?? at [bc]\nstack [0]:\n") + +(test-stdout + "?? stack display, string" + '(* (<- "a") (??) "bc") + "abc" + @["a"] + (string/format "\n?? at [bc]\nstack [1]:\n [0]: %M\n" "a")) + +(test-stdout + "?? stack display, multiple types" + '(* (<- "a") (number :d) (constant true) (constant {}) (constant @[]) (??) "bc") + "a1bc" + @["a" 1 true {} @[]] + (string/format "\n?? at [bc]\nstack [5]:\n [0]: %M\n [1]: %M\n [2]: %M\n [3]: %M\n [4]: %M\n" "a" 1 true {} @[])) + +(marshpeg '(* (??) "abc")) + (end-suite) From 855d1f2940128c08843295539fa94c81378429ec Mon Sep 17 00:00:00 2001 From: Calvin Rose Date: Wed, 4 Feb 2026 13:50:23 -0600 Subject: [PATCH 145/307] Update changelog. --- CHANGELOG.md | 1 + 1 file changed, 1 insertion(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index 2678f4ea..0654de7c 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -2,6 +2,7 @@ All notable changes to this project will be documented in this file. ## Unreleased - ??? +- Add :threads, :unmarshal, :compiler, and :asm sandbox flags. - Add support for persistent REPL history with the environment variable `JANET_HISTFILE` - Fix a number of fuzzer-found compiler bugs - Fix windows processes launching bug with empty environment table that caused process-launch failures. From b2bf70eacebbf4ce11fae0bb1fa680c22be2c69e Mon Sep 17 00:00:00 2001 From: Calvin Rose Date: Wed, 4 Feb 2026 18:52:20 -0600 Subject: [PATCH 146/307] Move peg debug output to stderr in line with other internal debug tools. Also allow disabling color in the debug output. --- src/core/capi.c | 2 +- src/core/peg.c | 10 ++++++++-- test/suite-peg.janet | 34 ++++++++++++++++++++-------------- 3 files changed, 29 insertions(+), 17 deletions(-) diff --git a/src/core/capi.c b/src/core/capi.c index 526e58a8..7981a6eb 100644 --- a/src/core/capi.c +++ b/src/core/capi.c @@ -460,7 +460,7 @@ Janet janet_dyn(const char *name) { return janet_table_get(janet_vm.top_dyns, janet_ckeywordv(name)); } if (janet_vm.fiber->env) { - return janet_table_get(janet_vm.fiber->env, janet_ckeywordv(name)); + return janet_table_get_keyword(janet_vm.fiber->env, name); } else { return janet_wrap_nil(); } diff --git a/src/core/peg.c b/src/core/peg.c index 1912dcea..bddb87a4 100644 --- a/src/core/peg.c +++ b/src/core/peg.c @@ -198,10 +198,16 @@ tail: char buffer[32] = {0}; size_t len = (size_t)(s->outer_text_end - text); memcpy(buffer, text, (len > 31 ? 31 : len)); - janet_printf("\n?? at [%s]\nstack [%d]:\n", buffer, s->captures->count); + janet_eprintf("\n?? at [%s]\nstack [%d]:\n", buffer, s->captures->count); + int has_color = janet_truthy(janet_dyn("err-color")); for (int32_t i = 0; i < s->captures->count; i++) { - janet_printf(" [%d]: %M\n", i, s->captures->data[i]); + if (has_color) { + janet_eprintf(" [%d]: %M\n", i, s->captures->data[i]); + } else { + janet_eprintf(" [%d]: %m\n", i, s->captures->data[i]); + } } + janet_eprintf("\n"); return text; } diff --git a/test/suite-peg.janet b/test/suite-peg.janet index ae3d0dbf..9684d63b 100644 --- a/test/suite-peg.janet +++ b/test/suite-peg.janet @@ -266,6 +266,12 @@ (marshpeg '(sub "abcdf" "abc")) (marshpeg '(* (sub 1 1))) (marshpeg '(split "," (+ "a" "b" "c"))) +(marshpeg "") +(marshpeg 1) +(marshpeg 0) +(marshpeg -1) +(marshpeg '(drop 1)) +(marshpeg '(accumulate 1)) # Peg swallowing errors # 159651117 @@ -846,49 +852,49 @@ @[["b" "b" "b"]]) # Debug and ?? tests. -(defn test-stdout [name peg input expected-matches expected-stdout] +(defn test-stderr [name peg input expected-matches expected-stdout] (def actual @"") - (with-dyns [:out actual] + (with-dyns [:err actual] (test name peg input expected-matches)) (assert (deep= (string actual) expected-stdout))) -(test-stdout "?? long form" +(test-stderr "?? long form" '(* (debug) "abc") "abc" @[] - "\n?? at [abc]\nstack [0]:\n") + "\n?? at [abc]\nstack [0]:\n\n") -(test-stdout "?? short form" +(test-stderr "?? short form" '(* (??) "abc") "abc" @[] - "\n?? at [abc]\nstack [0]:\n") + "\n?? at [abc]\nstack [0]:\n\n") -(test-stdout "?? end of text" +(test-stderr "?? end of text" '(* "abc" (??)) "abc" @[] - "\n?? at []\nstack [0]:\n") + "\n?? at []\nstack [0]:\n\n") -(test-stdout "?? between rules" +(test-stderr "?? between rules" '(* "a" (??) "bc") "abc" @[] - "\n?? at [bc]\nstack [0]:\n") + "\n?? at [bc]\nstack [0]:\n\n") -(test-stdout +(test-stderr "?? stack display, string" '(* (<- "a") (??) "bc") "abc" @["a"] - (string/format "\n?? at [bc]\nstack [1]:\n [0]: %M\n" "a")) + (string/format "\n?? at [bc]\nstack [1]:\n [0]: %M\n\n" "a")) -(test-stdout +(test-stderr "?? stack display, multiple types" '(* (<- "a") (number :d) (constant true) (constant {}) (constant @[]) (??) "bc") "a1bc" @["a" 1 true {} @[]] - (string/format "\n?? at [bc]\nstack [5]:\n [0]: %M\n [1]: %M\n [2]: %M\n [3]: %M\n [4]: %M\n" "a" 1 true {} @[])) + (string/format "\n?? at [bc]\nstack [5]:\n [0]: %M\n [1]: %M\n [2]: %M\n [3]: %M\n [4]: %M\n\n" "a" 1 true {} @[])) (marshpeg '(* (??) "abc")) From f33697d6a04c88f3c294aedbe33da9c0500748bc Mon Sep 17 00:00:00 2001 From: Calvin Rose Date: Wed, 4 Feb 2026 21:17:08 -0600 Subject: [PATCH 147/307] Show accumulation buffer and tagged capture in peg debug rule. --- src/core/buffer.c | 1 + src/core/peg.c | 32 +++++++++++++++++----- test/suite-peg.janet | 64 +++++++++++++++++++++++++++++++++++++------- 3 files changed, 81 insertions(+), 16 deletions(-) diff --git a/src/core/buffer.c b/src/core/buffer.c index c5745b2f..20015f81 100644 --- a/src/core/buffer.c +++ b/src/core/buffer.c @@ -74,6 +74,7 @@ JanetBuffer *janet_pointer_buffer_unsafe(void *memory, int32_t capacity, int32_t void janet_buffer_deinit(JanetBuffer *buffer) { if (!(buffer->gc.flags & JANET_BUFFER_FLAG_NO_REALLOC)) { janet_free(buffer->data); + buffer->data = NULL; } } diff --git a/src/core/peg.c b/src/core/peg.c index bddb87a4..72fd6f98 100644 --- a/src/core/peg.c +++ b/src/core/peg.c @@ -198,16 +198,34 @@ tail: char buffer[32] = {0}; size_t len = (size_t)(s->outer_text_end - text); memcpy(buffer, text, (len > 31 ? 31 : len)); - janet_eprintf("\n?? at [%s]\nstack [%d]:\n", buffer, s->captures->count); + janet_eprintf("?? at [%s] (index %d)\n", buffer, (int32_t)(text - s->text_start)); int has_color = janet_truthy(janet_dyn("err-color")); - for (int32_t i = 0; i < s->captures->count; i++) { - if (has_color) { - janet_eprintf(" [%d]: %M\n", i, s->captures->data[i]); - } else { - janet_eprintf(" [%d]: %m\n", i, s->captures->data[i]); + /* Accumulate buffer */ + if (s->scratch->count) { + janet_eprintf("accumulate buffer: %v\n", janet_wrap_buffer(s->scratch)); + } + /* Normal captures */ + if (s->captures->count) { + janet_eprintf("stack [%d]:\n", s->captures->count); + for (int32_t i = 0; i < s->captures->count; i++) { + if (has_color) { + janet_eprintf(" [%d]: %M\n", i, s->captures->data[i]); + } else { + janet_eprintf(" [%d]: %m\n", i, s->captures->data[i]); + } + } + } + /* Tagged captures */ + if (s->tagged_captures->count) { + janet_eprintf("tag stack [%d]:\n", s->tagged_captures->count); + for (int32_t i = 0; i < s->tagged_captures->count; i++) { + if (has_color) { + janet_eprintf(" [%d] tag=%d: %M\n", i, s->tags->data[i], s->tagged_captures->data[i]); + } else { + janet_eprintf(" [%d] tag=%d: %m\n", i, s->tags->data[i], s->tagged_captures->data[i]); + } } } - janet_eprintf("\n"); return text; } diff --git a/test/suite-peg.janet b/test/suite-peg.janet index 9684d63b..04fc0b54 100644 --- a/test/suite-peg.janet +++ b/test/suite-peg.janet @@ -852,50 +852,96 @@ @[["b" "b" "b"]]) # Debug and ?? tests. -(defn test-stderr [name peg input expected-matches expected-stdout] +(defn test-stderr [name peg input expected-matches expected-stderr] (def actual @"") - (with-dyns [:err actual] + (with-dyns [:err actual *err-color* true] (test name peg input expected-matches)) - (assert (deep= (string actual) expected-stdout))) + (assert (deep= (string actual) expected-stderr))) + +(defn test-stderr-no-color [name peg input expected-matches expected-stderr] + (def actual @"") + (with-dyns [:err actual *err-color* false] + (test name peg input expected-matches)) + (assert (deep= (string actual) expected-stderr))) (test-stderr "?? long form" '(* (debug) "abc") "abc" @[] - "\n?? at [abc]\nstack [0]:\n\n") + "?? at [abc] (index 0)\n") (test-stderr "?? short form" '(* (??) "abc") "abc" @[] - "\n?? at [abc]\nstack [0]:\n\n") + "?? at [abc] (index 0)\n") (test-stderr "?? end of text" '(* "abc" (??)) "abc" @[] - "\n?? at []\nstack [0]:\n\n") + "?? at [] (index 3)\n") (test-stderr "?? between rules" '(* "a" (??) "bc") "abc" @[] - "\n?? at [bc]\nstack [0]:\n\n") + "?? at [bc] (index 1)\n") (test-stderr "?? stack display, string" '(* (<- "a") (??) "bc") "abc" @["a"] - (string/format "\n?? at [bc]\nstack [1]:\n [0]: %M\n\n" "a")) + (string/format "?? at [bc] (index 1)\nstack [1]:\n [0]: %M\n" "a")) (test-stderr "?? stack display, multiple types" '(* (<- "a") (number :d) (constant true) (constant {}) (constant @[]) (??) "bc") "a1bc" @["a" 1 true {} @[]] - (string/format "\n?? at [bc]\nstack [5]:\n [0]: %M\n [1]: %M\n [2]: %M\n [3]: %M\n [4]: %M\n\n" "a" 1 true {} @[])) + (string/format "?? at [bc] (index 2)\nstack [5]:\n [0]: %M\n [1]: %M\n [2]: %M\n [3]: %M\n [4]: %M\n" "a" 1 true {} @[])) (marshpeg '(* (??) "abc")) +(marshpeg '(* (some (debug)) (??) "abc")) + +(test-stderr + "?? displays when capture fails" + '(* '1 (??) "x") + "abc" + nil + (string/format "?? at [bc] (index 1)\nstack [1]:\n [0]: %M\n" "a")) + +(test-stderr-no-color + "?? displays accumuate and tagged captures" + '(* '1 '2 (% (* '1 (??) (<- 2 :tag) '3 (backref :tag) (??)))) + "aksjndkajsnd" + @["a" "ks" "jndkajnd"] + (string/replace-all # In case on windows someone messes with line endings. + "\r" "" + ``` + ?? at [ndkajsnd] (index 4) + accumulate buffer: @"j" + stack [2]: + [0]: "a" + [1]: "ks" + tag stack [3]: + [0] tag=0: "a" + [1] tag=0: "ks" + [2] tag=0: "j" + ?? at [snd] (index 9) + accumulate buffer: @"jndkajnd" + stack [2]: + [0]: "a" + [1]: "ks" + tag stack [6]: + [0] tag=0: "a" + [1] tag=0: "ks" + [2] tag=0: "j" + [3] tag=1: "nd" + [4] tag=0: "kaj" + [5] tag=0: "nd" + + ```)) (end-suite) From 0194115412f63b677d05cc7384e3d2fb3646833d Mon Sep 17 00:00:00 2001 From: Calvin Rose Date: Thu, 5 Feb 2026 00:33:52 -0600 Subject: [PATCH 148/307] Auto-format janet. --- test/suite-peg.janet | 275 ++++++++++++++++++++++--------------------- 1 file changed, 138 insertions(+), 137 deletions(-) diff --git a/test/suite-peg.janet b/test/suite-peg.janet index 04fc0b54..f46ed9e5 100644 --- a/test/suite-peg.janet +++ b/test/suite-peg.janet @@ -101,9 +101,9 @@ # 798c88b4c (def csv '{:field (+ - (* `"` (% (any (+ (<- (if-not `"` 1)) - (* (constant `"`) `""`)))) `"`) - (<- (any (if-not (set ",\n") 1)))) + (* `"` (% (any (+ (<- (if-not `"` 1)) + (* (constant `"`) `""`)))) `"`) + (<- (any (if-not (set ",\n") 1)))) :main (* :field (any (* "," :field)) (+ "\n" -1))}) (defn check-csv @@ -351,16 +351,16 @@ # Using a large test grammar # cf05ff610 (def- specials {'fn true - 'var true - 'do true - 'while true - 'def true - 'splice true - 'set true - 'unquote true - 'quasiquote true - 'quote true - 'if true}) + 'var true + 'do true + 'while true + 'def true + 'splice true + 'set true + 'unquote true + 'quasiquote true + 'quote true + 'if true}) (defn- check-number [text] (and (scan-number text) text)) @@ -405,7 +405,7 @@ :struct (* '"{" :root2 (+ '"}" (error ""))) :parray (* '"@" :ptuple) :barray (* '"@" :btuple) - :dict (* '"@" :struct) + :dict (* '"@" :struct) :main (+ :root (error ""))}) (def p (peg/compile grammar)) @@ -569,18 +569,18 @@ (assert (= (string (f peg subst text)) expected) name)) (peg-test "peg/replace has access to captures" - peg/replace - ~(sequence "." (capture (set "ab"))) - (fn [str char] (string/format "%s -> %s, " str (string/ascii-upper char))) - ".a.b.c" - ".a -> A, .b.c") + peg/replace + ~(sequence "." (capture (set "ab"))) + (fn [str char] (string/format "%s -> %s, " str (string/ascii-upper char))) + ".a.b.c" + ".a -> A, .b.c") (peg-test "peg/replace-all has access to captures" - peg/replace-all - ~(sequence "." (capture (set "ab"))) - (fn [str char] (string/format "%s -> %s, " str (string/ascii-upper char))) - ".a.b.c" - ".a -> A, .b -> B, .c") + peg/replace-all + ~(sequence "." (capture (set "ab"))) + (fn [str char] (string/format "%s -> %s, " str (string/ascii-upper char))) + ".a.b.c" + ".a -> A, .b -> B, .c") # Peg bug # eab5f67c5 @@ -654,20 +654,20 @@ # issue #1026 - 9341081a4 (assert (deep= - (peg/match '(not (* (constant 7) "a")) "hello") - @[]) "peg not") + (peg/match '(not (* (constant 7) "a")) "hello") + @[]) "peg not") (assert (deep= - (peg/match '(if-not (* (constant 7) "a") "hello") "hello") - @[]) "peg if-not") + (peg/match '(if-not (* (constant 7) "a") "hello") "hello") + @[]) "peg if-not") (assert (deep= - (peg/match '(if-not (drop (* (constant 7) "a")) "hello") "hello") - @[]) "peg if-not drop") + (peg/match '(if-not (drop (* (constant 7) "a")) "hello") "hello") + @[]) "peg if-not drop") (assert (deep= - (peg/match '(if (not (* (constant 7) "a")) "hello") "hello") - @[]) "peg if not") + (peg/match '(if (not (* (constant 7) "a")) "hello") "hello") + @[]) "peg if not") (defn test [name peg input expected] (assert-no-error "compile peg" (peg/compile peg)) @@ -675,143 +675,143 @@ (assert (deep= (peg/match peg input) expected) name)) (test "sub: matches the same input twice" - ~(sub "abcd" "abc") - "abcdef" - @[]) + ~(sub "abcd" "abc") + "abcdef" + @[]) (test "sub: second pattern cannot match more than the first pattern" - ~(sub "abcd" "abcde") - "abcdef" - nil) + ~(sub "abcd" "abcde") + "abcdef" + nil) (test "sub: fails if first pattern fails" - ~(sub "x" "abc") - "abcdef" - nil) + ~(sub "x" "abc") + "abcdef" + nil) (test "sub: fails if second pattern fails" - ~(sub "abc" "x") - "abcdef" - nil) + ~(sub "abc" "x") + "abcdef" + nil) (test "sub: keeps captures from both patterns" - ~(sub '"abcd" '"abc") - "abcdef" - @["abcd" "abc"]) + ~(sub '"abcd" '"abc") + "abcdef" + @["abcd" "abc"]) (test "sub: second pattern can reference captures from first" - ~(* (constant 5 :tag) (sub (capture "abc" :tag) (backref :tag))) - "abcdef" - @[5 "abc" "abc"]) + ~(* (constant 5 :tag) (sub (capture "abc" :tag) (backref :tag))) + "abcdef" + @[5 "abc" "abc"]) (test "sub: second pattern can't see past what the first pattern matches" - ~(sub "abc" (* "abc" -1)) - "abcdef" - @[]) + ~(sub "abc" (* "abc" -1)) + "abcdef" + @[]) (test "sub: positions inside second match are still relative to the entire input" - ~(* "one\ntw" (sub "o" (* ($) (line) (column)))) - "one\ntwo\nthree\n" - @[6 2 3]) + ~(* "one\ntw" (sub "o" (* ($) (line) (column)))) + "one\ntwo\nthree\n" + @[6 2 3]) (test "sub: advances to the end of the first pattern's match" - ~(* (sub "abc" "ab") "d") - "abcdef" - @[]) + ~(* (sub "abc" "ab") "d") + "abcdef" + @[]) (test "til: basic matching" - ~(til "d" "abc") - "abcdef" - @[]) + ~(til "d" "abc") + "abcdef" + @[]) (test "til: second pattern can't see past the first occurrence of first pattern" - ~(til "d" (* "abc" -1)) - "abcdef" - @[]) + ~(til "d" (* "abc" -1)) + "abcdef" + @[]) (test "til: fails if first pattern fails" - ~(til "x" "abc") - "abcdef" - nil) + ~(til "x" "abc") + "abcdef" + nil) (test "til: fails if second pattern fails" - ~(til "abc" "x") - "abcdef" - nil) + ~(til "abc" "x") + "abcdef" + nil) (test "til: discards captures from initial pattern" - ~(til '"d" '"abc") - "abcdef" - @["abc"]) + ~(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]) + ~(* "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" - @[]) + ~(* (til "d" "ab") "e") + "abcdef" + @[]) (test "split: basic functionality" - ~(split "," '1) - "a,b,c" - @["a" "b" "c"]) + ~(split "," '1) + "a,b,c" + @["a" "b" "c"]) (test "split: drops captures from separator pattern" - ~(split '"," '1) - "a,b,c" - @["a" "b" "c"]) + ~(split '"," '1) + "a,b,c" + @["a" "b" "c"]) (test "split: can match empty subpatterns" - ~(split "," ':w*) - ",a,,bar,,,c,," - @["" "a" "" "bar" "" "" "c" "" ""]) + ~(split "," ':w*) + ",a,,bar,,,c,," + @["" "a" "" "bar" "" "" "c" "" ""]) (test "split: subpattern is limited to only text before the separator" - ~(split "," '(to -1)) - "a,,bar,c" - @["a" "" "bar" "c"]) + ~(split "," '(to -1)) + "a,,bar,c" + @["a" "" "bar" "c"]) (test "split: fails if any subpattern fails" - ~(split "," '"a") - "a,a,b" - nil) + ~(split "," '"a") + "a,a,b" + nil) (test "split: separator does not have to match anything" - ~(split "x" '(to -1)) - "a,a,b" - @["a,a,b"]) + ~(split "x" '(to -1)) + "a,a,b" + @["a,a,b"]) (test "split: always consumes entire input" - ~(split 1 '"") - "abc" - @["" "" "" ""]) + ~(split 1 '"") + "abc" + @["" "" "" ""]) (test "split: separator can be an arbitrary PEG" - ~(split :s+ '(to -1)) - "a b c" - @["a" "b" "c"]) + ~(split :s+ '(to -1)) + "a b c" + @["a" "b" "c"]) (test "split: does not advance past the end of the input" - ~(* (split "," ':w+) 0) - "a,b,c" - @["a" "b" "c"]) + ~(* (split "," ':w+) 0) + "a,b,c" + @["a" "b" "c"]) (test "nth 1" - ~{:prefix (number :d+ nil :n) - :word '(lenprefix (-> :n) :w) - :main (some (nth 1 (* :prefix ":" :word)))} - "5:apple6:banana6:cherry" - @["apple" "banana" "cherry"]) + ~{:prefix (number :d+ nil :n) + :word '(lenprefix (-> :n) :w) + :main (some (nth 1 (* :prefix ":" :word)))} + "5:apple6:banana6:cherry" + @["apple" "banana" "cherry"]) (test "only-tags 1" - ~{:prefix (number :d+ nil :n) - :word (capture (lenprefix (-> :n) :w) :W) - :main (some (* (only-tags (* :prefix ":" :word)) (-> :W)))} - "5:apple6:banana6:cherry" - @["apple" "banana" "cherry"]) + ~{:prefix (number :d+ nil :n) + :word (capture (lenprefix (-> :n) :w) :W) + :main (some (* (only-tags (* :prefix ":" :word)) (-> :W)))} + "5:apple6:banana6:cherry" + @["apple" "banana" "cherry"]) # Issue #1539 - make sure split with "" doesn't infinite loop/oom (test "issue 1539" @@ -820,9 +820,9 @@ nil) (test "issue 1539 pt. 2" - ~(split "," (capture 0)) - "abc123,,,," - @["" "" "" "" ""]) + ~(split "," (capture 0)) + "abc123,,,," + @["" "" "" "" ""]) # Issue #1549 - allow buffers as peg literals (test "issue 1549" @@ -865,28 +865,28 @@ (assert (deep= (string actual) expected-stderr))) (test-stderr "?? long form" - '(* (debug) "abc") - "abc" - @[] - "?? at [abc] (index 0)\n") + '(* (debug) "abc") + "abc" + @[] + "?? at [abc] (index 0)\n") (test-stderr "?? short form" - '(* (??) "abc") - "abc" - @[] - "?? at [abc] (index 0)\n") + '(* (??) "abc") + "abc" + @[] + "?? at [abc] (index 0)\n") (test-stderr "?? end of text" - '(* "abc" (??)) - "abc" - @[] - "?? at [] (index 3)\n") + '(* "abc" (??)) + "abc" + @[] + "?? at [] (index 3)\n") (test-stderr "?? between rules" - '(* "a" (??) "bc") - "abc" - @[] - "?? at [bc] (index 1)\n") + '(* "a" (??) "bc") + "abc" + @[] + "?? at [bc] (index 1)\n") (test-stderr "?? stack display, string" @@ -917,7 +917,8 @@ '(* '1 '2 (% (* '1 (??) (<- 2 :tag) '3 (backref :tag) (??)))) "aksjndkajsnd" @["a" "ks" "jndkajnd"] - (string/replace-all # In case on windows someone messes with line endings. + (string/replace-all + # In case on windows someone messes with line endings. "\r" "" ``` ?? at [ndkajsnd] (index 4) From 42c0096ce75f8cd7733661dd99e365a852f26fa4 Mon Sep 17 00:00:00 2001 From: Calvin Rose Date: Thu, 5 Feb 2026 19:45:30 -0600 Subject: [PATCH 149/307] Allow simpler loading of modules. E.g. (import project/file.janet :as file) (import ./project/file.janet :as file) (import /home/me/project/file.janet :as file) Will now do what you expect. When loading with a file extension, this will first check directly if the relative (or absolute) path exists. Also expands `module/find` to be able to show all matches for a given module name without needing to try and catch import errors. Lastly, one can also override the module/find functionality by passing `:loader` directly. E.g. (import ./macoslib.dylib :loader :native :as my-thing) Which will allow easily bypassing all module/find logic; "./macoslib.dylib" will be passed directly to the :native loader `(get module/loaders :native)`. The module system was previously left open to customization but with defaults to encourage a more typical style. However, these qol improvements are more than worth it and can in some cases let people stop fighting the module system. --- src/boot/boot.janet | 14 ++++++++++---- 1 file changed, 10 insertions(+), 4 deletions(-) diff --git a/src/boot/boot.janet b/src/boot/boot.janet index d2a33e24..5f7afcff 100644 --- a/src/boot/boot.janet +++ b/src/boot/boot.janet @@ -2924,6 +2924,7 @@ (array/insert mp sys-index [(string ":sys:/:all:" ext) loader check-is-dep]) (def curall-index (find-prefix ":cur:/:all:")) (array/insert mp curall-index [(string ":cur:/:all:" ext) loader check-relative]) + (array/insert mp 0 [":all:" loader (fn :check-ext [x] (string/has-suffix? ext x))]) mp) # Don't expose this externally yet - could break if custom module/paths is setup. @@ -2976,20 +2977,22 @@ or :image if the module is found, otherwise a tuple with nil followed by an error message. ``` - [path] + [path &opt find-all] (var ret nil) (def mp (dyn *module-paths* module/paths)) + (def all-matches (if find-all @[])) (each [p mod-kind checker] mp (when (mod-filter checker path) (if (function? p) (when-let [res (p path)] (set ret [res mod-kind]) - (break)) + (if find-all (array/push all-matches ret) (break))) (do (def fullpath (string (module/expand-path path p))) (when (fexists fullpath) (set ret [fullpath mod-kind]) - (break)))))) + (if find-all (array/push all-matches ret) (break))))))) + (if find-all (break all-matches)) (if ret ret (let [expander (fn :expander [[t _ chk]] (when (string? t) @@ -3164,7 +3167,10 @@ (defn- require-1 [path args kargs] - (def [fullpath mod-kind] (module/find path)) + (def [fullpath mod-kind] + (if-let [loader (get kargs :loader)] + [path loader] + (module/find path))) (unless fullpath (error mod-kind)) (def mc (dyn *module-cache* module/cache)) (def ml (dyn *module-loading* module/loading)) From 196f27af3d8026ad803966449b8c8ef92047e0b3 Mon Sep 17 00:00:00 2001 From: Calvin Rose Date: Thu, 5 Feb 2026 20:01:00 -0600 Subject: [PATCH 150/307] Update CHANGELOG.md --- CHANGELOG.md | 3 +++ src/boot/boot.janet | 8 ++++---- 2 files changed, 7 insertions(+), 4 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 0654de7c..4007ba3d 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -2,6 +2,9 @@ All notable changes to this project will be documented in this file. ## Unreleased - ??? +- Allow overriding the loader when doing imports with the `:loader` argument. +- Allow importing modules with a path extension to do what one would expect. +- Add `find-all` argument to `module/find` - Add :threads, :unmarshal, :compiler, and :asm sandbox flags. - Add support for persistent REPL history with the environment variable `JANET_HISTFILE` - Fix a number of fuzzer-found compiler bugs diff --git a/src/boot/boot.janet b/src/boot/boot.janet index 5f7afcff..1923eb46 100644 --- a/src/boot/boot.janet +++ b/src/boot/boot.janet @@ -2182,10 +2182,10 @@ (def last (in t (- (length t) 1))) (def bound (in t 1)) (keep-syntax! t - (array/concat - @[(in t 0) (expand-bindings bound)] - (tuple/slice t 2 -2) - @[(recur last)]))) + (array/concat + @[(in t 0) (expand-bindings bound)] + (tuple/slice t 2 -2) + @[(recur last)]))) (defn expandall [t] (def args (map recur (tuple/slice t 1))) From 2e8dd90a51d2adbef2b7046763ba7470a20f03dd Mon Sep 17 00:00:00 2001 From: Calvin Rose Date: Thu, 5 Feb 2026 23:38:55 -0600 Subject: [PATCH 151/307] Line endings in tests. --- test/suite-peg.janet | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/test/suite-peg.janet b/test/suite-peg.janet index f46ed9e5..3ae0974a 100644 --- a/test/suite-peg.janet +++ b/test/suite-peg.janet @@ -856,13 +856,13 @@ (def actual @"") (with-dyns [:err actual *err-color* true] (test name peg input expected-matches)) - (assert (deep= (string actual) expected-stderr))) + (assert (deep= (string/replace-all "\r" "" actual) expected-stderr))) (defn test-stderr-no-color [name peg input expected-matches expected-stderr] (def actual @"") (with-dyns [:err actual *err-color* false] (test name peg input expected-matches)) - (assert (deep= (string actual) expected-stderr))) + (assert (deep= (string/replace-all "\r" "" actual) expected-stderr))) (test-stderr "?? long form" '(* (debug) "abc") From 16b449a1374386993f1ce637e9b4bae7ccf7bb5a Mon Sep 17 00:00:00 2001 From: Calvin Rose Date: Thu, 5 Feb 2026 23:52:28 -0600 Subject: [PATCH 152/307] Print "actual" output on verbose mode for suite-peg --- src/core/peg.c | 4 ++-- test/suite-peg.janet | 52 +++++++++++++++++++++++++------------------- 2 files changed, 32 insertions(+), 24 deletions(-) diff --git a/src/core/peg.c b/src/core/peg.c index 72fd6f98..5e488886 100644 --- a/src/core/peg.c +++ b/src/core/peg.c @@ -220,9 +220,9 @@ tail: janet_eprintf("tag stack [%d]:\n", s->tagged_captures->count); for (int32_t i = 0; i < s->tagged_captures->count; i++) { if (has_color) { - janet_eprintf(" [%d] tag=%d: %M\n", i, s->tags->data[i], s->tagged_captures->data[i]); + janet_eprintf(" [%d] tag=%d: %M\n", i, (int32_t) s->tags->data[i], s->tagged_captures->data[i]); } else { - janet_eprintf(" [%d] tag=%d: %m\n", i, s->tags->data[i], s->tagged_captures->data[i]); + janet_eprintf(" [%d] tag=%d: %m\n", i, (int32_t) s->tags->data[i], s->tagged_captures->data[i]); } } } diff --git a/test/suite-peg.janet b/test/suite-peg.janet index 3ae0974a..828acfbf 100644 --- a/test/suite-peg.janet +++ b/test/suite-peg.janet @@ -856,37 +856,45 @@ (def actual @"") (with-dyns [:err actual *err-color* true] (test name peg input expected-matches)) - (assert (deep= (string/replace-all "\r" "" actual) expected-stderr))) + (when (os/getenv "VERBOSE") + (eprint actual)) + (assert (deep= (string actual) expected-stderr))) (defn test-stderr-no-color [name peg input expected-matches expected-stderr] (def actual @"") (with-dyns [:err actual *err-color* false] (test name peg input expected-matches)) - (assert (deep= (string/replace-all "\r" "" actual) expected-stderr))) + (when (os/getenv "VERBOSE") + (eprint actual)) + (assert (deep= (string actual) expected-stderr))) -(test-stderr "?? long form" - '(* (debug) "abc") - "abc" - @[] - "?? at [abc] (index 0)\n") +(test-stderr + "?? long form" + '(* (debug) "abc") + "abc" + @[] + "?? at [abc] (index 0)\n") -(test-stderr "?? short form" - '(* (??) "abc") - "abc" - @[] - "?? at [abc] (index 0)\n") +(test-stderr + "?? short form" + '(* (??) "abc") + "abc" + @[] + "?? at [abc] (index 0)\n") -(test-stderr "?? end of text" - '(* "abc" (??)) - "abc" - @[] - "?? at [] (index 3)\n") +(test-stderr + "?? end of text" + '(* "abc" (??)) + "abc" + @[] + "?? at [] (index 3)\n") -(test-stderr "?? between rules" - '(* "a" (??) "bc") - "abc" - @[] - "?? at [bc] (index 1)\n") +(test-stderr + "?? between rules" + '(* "a" (??) "bc") + "abc" + @[] + "?? at [bc] (index 1)\n") (test-stderr "?? stack display, string" From ca5c617fba091e33d3fa0a4d4f24bc28c194a034 Mon Sep 17 00:00:00 2001 From: Calvin Rose Date: Fri, 6 Feb 2026 00:04:18 -0600 Subject: [PATCH 153/307] More tweaks to peg suite for arm32 failures. --- src/core/pp.c | 2 +- test/suite-peg.janet | 6 ++++-- 2 files changed, 5 insertions(+), 3 deletions(-) diff --git a/src/core/pp.c b/src/core/pp.c index 8c8b3884..80d745a1 100644 --- a/src/core/pp.c +++ b/src/core/pp.c @@ -897,7 +897,7 @@ void janet_formatbv(JanetBuffer *b, const char *format, va_list args) { case 's': case 'S': { const char *str = va_arg(args, const char *); - int32_t len = c[-1] == 's' + int32_t len = (c[-1] == 's') ? (int32_t) strlen(str) : janet_string_length((JanetString) str); if (form[2] == '\0') diff --git a/test/suite-peg.janet b/test/suite-peg.janet index 828acfbf..a3edaca4 100644 --- a/test/suite-peg.janet +++ b/test/suite-peg.janet @@ -857,7 +857,8 @@ (with-dyns [:err actual *err-color* true] (test name peg input expected-matches)) (when (os/getenv "VERBOSE") - (eprint actual)) + (eprint (describe actual)) + (eprint (describe expected-stderr))) (assert (deep= (string actual) expected-stderr))) (defn test-stderr-no-color [name peg input expected-matches expected-stderr] @@ -865,7 +866,8 @@ (with-dyns [:err actual *err-color* false] (test name peg input expected-matches)) (when (os/getenv "VERBOSE") - (eprint actual)) + (eprint (describe actual)) + (eprint (describe expected-stderr))) (assert (deep= (string actual) expected-stderr))) (test-stderr From d2685594f9911a462c6368f045562b0f262a02bf Mon Sep 17 00:00:00 2001 From: Calvin Rose Date: Fri, 6 Feb 2026 00:17:11 -0600 Subject: [PATCH 154/307] VERBOSE=1 caused tests to fail as we did more asserts inside the assert. Thenc checked stderr for messages. Since the helper makes assert write to stderr, this caused extra cruft in our test output. --- src/core/peg.c | 2 +- test/suite-peg.janet | 12 ++++-------- 2 files changed, 5 insertions(+), 9 deletions(-) diff --git a/src/core/peg.c b/src/core/peg.c index 5e488886..d98584ae 100644 --- a/src/core/peg.c +++ b/src/core/peg.c @@ -198,7 +198,7 @@ tail: char buffer[32] = {0}; size_t len = (size_t)(s->outer_text_end - text); memcpy(buffer, text, (len > 31 ? 31 : len)); - janet_eprintf("?? at [%s] (index %d)\n", buffer, (int32_t)(text - s->text_start)); + janet_eprintf("?? at [%s] (index %D)\n", buffer, (int64_t)(text - s->text_start)); int has_color = janet_truthy(janet_dyn("err-color")); /* Accumulate buffer */ if (s->scratch->count) { diff --git a/test/suite-peg.janet b/test/suite-peg.janet index a3edaca4..678150e4 100644 --- a/test/suite-peg.janet +++ b/test/suite-peg.janet @@ -853,21 +853,17 @@ # Debug and ?? tests. (defn test-stderr [name peg input expected-matches expected-stderr] + (test name peg input expected-matches) (def actual @"") (with-dyns [:err actual *err-color* true] - (test name peg input expected-matches)) - (when (os/getenv "VERBOSE") - (eprint (describe actual)) - (eprint (describe expected-stderr))) + (peg/match peg input)) (assert (deep= (string actual) expected-stderr))) (defn test-stderr-no-color [name peg input expected-matches expected-stderr] + (test name peg input expected-matches) (def actual @"") (with-dyns [:err actual *err-color* false] - (test name peg input expected-matches)) - (when (os/getenv "VERBOSE") - (eprint (describe actual)) - (eprint (describe expected-stderr))) + (peg/match peg input)) (assert (deep= (string actual) expected-stderr))) (test-stderr From c7c3821aa63d30efaebd7c9a154730ba185b0de4 Mon Sep 17 00:00:00 2001 From: Calvin Rose Date: Fri, 6 Feb 2026 00:23:45 -0600 Subject: [PATCH 155/307] Remove extra output from peg test. --- src/core/peg.c | 2 +- test/suite-peg.janet | 6 ++++-- 2 files changed, 5 insertions(+), 3 deletions(-) diff --git a/src/core/peg.c b/src/core/peg.c index d98584ae..5e488886 100644 --- a/src/core/peg.c +++ b/src/core/peg.c @@ -198,7 +198,7 @@ tail: char buffer[32] = {0}; size_t len = (size_t)(s->outer_text_end - text); memcpy(buffer, text, (len > 31 ? 31 : len)); - janet_eprintf("?? at [%s] (index %D)\n", buffer, (int64_t)(text - s->text_start)); + janet_eprintf("?? at [%s] (index %d)\n", buffer, (int32_t)(text - s->text_start)); int has_color = janet_truthy(janet_dyn("err-color")); /* Accumulate buffer */ if (s->scratch->count) { diff --git a/test/suite-peg.janet b/test/suite-peg.janet index 678150e4..fb194498 100644 --- a/test/suite-peg.janet +++ b/test/suite-peg.janet @@ -853,14 +853,16 @@ # Debug and ?? tests. (defn test-stderr [name peg input expected-matches expected-stderr] - (test name peg input expected-matches) + (with-dyns [:err @""] + (test name peg input expected-matches)) (def actual @"") (with-dyns [:err actual *err-color* true] (peg/match peg input)) (assert (deep= (string actual) expected-stderr))) (defn test-stderr-no-color [name peg input expected-matches expected-stderr] - (test name peg input expected-matches) + (with-dyns [:err @""] + (test name peg input expected-matches)) (def actual @"") (with-dyns [:err actual *err-color* false] (peg/match peg input)) From 2a54154976979e9c91bd20a10c2311ae4ce78869 Mon Sep 17 00:00:00 2001 From: Calvin Rose Date: Fri, 13 Feb 2026 19:36:09 -0600 Subject: [PATCH 156/307] Don't use preload on absolute paths. When importing full paths, the old preload code was preventing (import :fresh true) from working as expected. --- .gitignore | 1 + src/boot/boot.janet | 10 +++++++--- 2 files changed, 8 insertions(+), 3 deletions(-) diff --git a/.gitignore b/.gitignore index 1e5ec418..2ca3693f 100644 --- a/.gitignore +++ b/.gitignore @@ -12,6 +12,7 @@ janet /src/include/generated/*.h janet-*.tar.gz dist +/tmp # jpm lockfile lockfile.janet diff --git a/src/boot/boot.janet b/src/boot/boot.janet index 1923eb46..3e103009 100644 --- a/src/boot/boot.janet +++ b/src/boot/boot.janet @@ -2874,7 +2874,8 @@ (defn- check-dyn-relative [x] (if (string/has-prefix? "@" x) x)) (defn- check-relative [x] (if (string/has-prefix? "." x) x)) -(defn- check-not-relative [x] (if-not (string/has-prefix? "." x) x)) +# Don't try to preload absolute or relative paths +(defn- check-preloadable [x] (if-not (or (string/has-prefix? "/" x) (string/find "." x) (string/find "@" x)) x)) (defn- check-is-dep [x] (unless (or (string/has-prefix? "/" x) (string/has-prefix? "@" x) (string/has-prefix? "." x)) x)) (defn- check-project-relative [x] (if (string/has-prefix? "/" x) x)) @@ -2949,7 +2950,10 @@ (module/add-paths "/init.janet" :source) (module/add-paths ".janet" :source) (module/add-paths ".jimage" :image) -(array/insert module/paths 0 [(fn is-cached [path] (if (in (dyn *module-cache* module/cache) path) path)) :preload check-not-relative]) +(array/insert module/paths 0 + [(fn is-cached [path] (if (in (dyn *module-cache* module/cache) path) path)) + :preload + check-preloadable]) # Version of fexists that works even with a reduced OS (defn- fexists @@ -3219,7 +3223,7 @@ (def prefix (or (and as (string as "/")) prefix - (string (last (string/split "/" path)) "/"))) + (string (first (string/split "." (last (string/split "/" path)))) "/"))) (merge-module env newenv prefix ep only)) (defmacro import From d81512723b2344e9aab4c4d1fca7503ea463f6d6 Mon Sep 17 00:00:00 2001 From: Calvin Rose Date: Sat, 14 Feb 2026 08:57:27 -0600 Subject: [PATCH 157/307] When pretty printing, don't sort keys for huge tables. This was exposed when printing `(invert (range 200000))`, which isn't so large that we shouldn't be able to sort it, but was taking far too long to compute. --- src/core/pp.c | 94 ++++++++++++++++++++++++++++++------------------ src/core/value.c | 5 ++- 2 files changed, 61 insertions(+), 38 deletions(-) diff --git a/src/core/pp.c b/src/core/pp.c index 80d745a1..6a839362 100644 --- a/src/core/pp.c +++ b/src/core/pp.c @@ -487,6 +487,7 @@ static const char *janet_pretty_colors[] = { #define JANET_PRETTY_DICT_ONELINE 4 #define JANET_PRETTY_IND_ONELINE 10 #define JANET_PRETTY_DICT_LIMIT 30 +#define JANET_PRETTY_DICT_KEYSORT_LIMIT 2000 #define JANET_PRETTY_ARRAY_LIMIT 160 /* Helper for pretty printing */ @@ -625,55 +626,78 @@ static void janet_pretty_one(struct pretty *S, Janet x, int is_dict_value) { if (S->depth == 0) { janet_buffer_push_cstring(S->buffer, "..."); } else { - int32_t i = 0, len = 0, cap = 0; + int32_t len = 0, cap = 0; const JanetKV *kvs = NULL; janet_dictionary_view(x, &kvs, &len, &cap); if (!istable && !(S->flags & JANET_PRETTY_ONELINE) && len >= JANET_PRETTY_DICT_ONELINE) janet_buffer_push_u8(S->buffer, ' '); if (is_dict_value && len >= JANET_PRETTY_DICT_ONELINE) print_newline(S, 0); int32_t ks_start = S->keysort_start; - - /* Ensure buffer is large enough to sort keys. */ int truncated = 0; - int64_t mincap = (int64_t) len + (int64_t) ks_start; - if (mincap > INT32_MAX) { - truncated = 1; - len = 0; - mincap = ks_start; - } - if (S->keysort_capacity < mincap) { - if (mincap >= INT32_MAX / 2) { - S->keysort_capacity = INT32_MAX; - } else { - S->keysort_capacity = (int32_t)(mincap * 2); + /* Shortcut for huge dictionaries, don't bother sorting keys */ + if (len > JANET_PRETTY_DICT_KEYSORT_LIMIT) { + if (!(S->flags & JANET_PRETTY_NOTRUNC) && (len > JANET_PRETTY_DICT_LIMIT)) { + len = JANET_PRETTY_DICT_LIMIT; + truncated = 1; } - S->keysort_buffer = janet_srealloc(S->keysort_buffer, sizeof(int32_t) * S->keysort_capacity); - if (NULL == S->keysort_buffer) { - JANET_OUT_OF_MEMORY; + int32_t j = 0; + for (int32_t i = 0; i < len; i++) { + while (janet_checktype(kvs[j].key, JANET_NIL)) j++; + if (i) print_newline(S, len < JANET_PRETTY_DICT_ONELINE); + janet_pretty_one(S, kvs[j].key, 0); + janet_buffer_push_u8(S->buffer, ' '); + janet_pretty_one(S, kvs[j].value, 1); + j++; } - } + if (truncated) { + print_newline(S, 0); + janet_buffer_push_cstring(S->buffer, "..."); + } + } else { + /* Sorted keys dictionaries */ - janet_sorted_keys(kvs, cap, S->keysort_buffer == NULL ? NULL : S->keysort_buffer + ks_start); - S->keysort_start += len; - if (!(S->flags & JANET_PRETTY_NOTRUNC) && (len > JANET_PRETTY_DICT_LIMIT)) { - len = JANET_PRETTY_DICT_LIMIT; - truncated = 1; - } + /* Ensure buffer is large enough to sort keys. */ + int64_t mincap = (int64_t) len + (int64_t) ks_start; + if (mincap > INT32_MAX) { + truncated = 1; + len = 0; + mincap = ks_start; + } - for (i = 0; i < len; i++) { - if (i) print_newline(S, len < JANET_PRETTY_DICT_ONELINE); - int32_t j = S->keysort_buffer[i + ks_start]; - janet_pretty_one(S, kvs[j].key, 0); - janet_buffer_push_u8(S->buffer, ' '); - janet_pretty_one(S, kvs[j].value, 1); - } + if (S->keysort_capacity < mincap) { + if (mincap >= INT32_MAX / 2) { + S->keysort_capacity = INT32_MAX; + } else { + S->keysort_capacity = (int32_t)(mincap * 2); + } + S->keysort_buffer = janet_srealloc(S->keysort_buffer, sizeof(int32_t) * S->keysort_capacity); + if (NULL == S->keysort_buffer) { + JANET_OUT_OF_MEMORY; + } + } - if (truncated) { - print_newline(S, 0); - janet_buffer_push_cstring(S->buffer, "..."); - } + janet_sorted_keys(kvs, cap, S->keysort_buffer == NULL ? NULL : S->keysort_buffer + ks_start); + S->keysort_start += len; + if (!(S->flags & JANET_PRETTY_NOTRUNC) && (len > JANET_PRETTY_DICT_LIMIT)) { + len = JANET_PRETTY_DICT_LIMIT; + truncated = 1; + } + for (int32_t i = 0; i < len; i++) { + if (i) print_newline(S, len < JANET_PRETTY_DICT_ONELINE); + int32_t j = S->keysort_buffer[i + ks_start]; + janet_pretty_one(S, kvs[j].key, 0); + janet_buffer_push_u8(S->buffer, ' '); + janet_pretty_one(S, kvs[j].value, 1); + } + + if (truncated) { + print_newline(S, 0); + janet_buffer_push_cstring(S->buffer, "..."); + } + + } S->keysort_start = ks_start; } S->indent -= 2; diff --git a/src/core/value.c b/src/core/value.c index 5c86479e..ae407f7c 100644 --- a/src/core/value.c +++ b/src/core/value.c @@ -335,10 +335,9 @@ int32_t janet_hash(Janet x) { } as; as.d = janet_unwrap_number(x); as.d += 0.0; /* normalize negative 0 */ - uint32_t lo = (uint32_t)(as.u & 0xFFFFFFFF); + as.u = murmur64(as.u); uint32_t hi = (uint32_t)(as.u >> 32); - uint32_t hilo = (hi ^ lo) * 2654435769u; - hash = (int32_t)((hilo << 16) | (hilo >> 16)); + hash = (int32_t)hi; break; } case JANET_ABSTRACT: { From abd7bb111004e84feca5fefe5d65a52300aa6ffa Mon Sep 17 00:00:00 2001 From: sogaiu <33044872+sogaiu@users.noreply.github.com> Date: Sun, 15 Feb 2026 00:04:33 +0900 Subject: [PATCH 158/307] Use snprintf instead of sprintf (#1711) Co-authored-by: sogaiu <983021772@users.noreply.github.com> --- src/boot/system_test.c | 2 +- src/core/ev.c | 2 +- src/core/os.c | 2 +- 3 files changed, 3 insertions(+), 3 deletions(-) diff --git a/src/boot/system_test.c b/src/boot/system_test.c index d0fe41ee..13e2edef 100644 --- a/src/boot/system_test.c +++ b/src/boot/system_test.c @@ -37,7 +37,7 @@ int system_test() { /* Check the version defines are self consistent */ char version_combined[256]; - sprintf(version_combined, "%d.%d.%d%s", JANET_VERSION_MAJOR, JANET_VERSION_MINOR, JANET_VERSION_PATCH, JANET_VERSION_EXTRA); + snprintf(version_combined, sizeof(version_combined), "%d.%d.%d%s", JANET_VERSION_MAJOR, JANET_VERSION_MINOR, JANET_VERSION_PATCH, JANET_VERSION_EXTRA); assert(!strcmp(JANET_VERSION, version_combined)); /* Reflexive testing and nanbox testing */ diff --git a/src/core/ev.c b/src/core/ev.c index 250ab728..9b03413e 100644 --- a/src/core/ev.c +++ b/src/core/ev.c @@ -2416,7 +2416,7 @@ Janet janet_ev_lasterr(void) { msgbuf, sizeof(msgbuf), NULL); - if (!*msgbuf) sprintf(msgbuf, "%d", code); + if (!*msgbuf) snprintf(msgbuf, sizeof(msgbuf), "%d", code); char *c = msgbuf; while (*c) { if (*c == '\n' || *c == '\r') { diff --git a/src/core/os.c b/src/core/os.c index 8e514113..bc57b64a 100644 --- a/src/core/os.c +++ b/src/core/os.c @@ -1331,7 +1331,7 @@ static Janet os_execute_impl(int32_t argc, Janet *argv, JanetExecuteMode mode) { msgbuf, sizeof(msgbuf), NULL); - if (!*msgbuf) sprintf(msgbuf, "%d", cp_error_code); + if (!*msgbuf) snprintf(msgbuf, sizeof(msgbuf), "%d", cp_error_code); char *c = msgbuf; while (*c) { if (*c == '\n' || *c == '\r') { From 8f7c32e5cb3dddfb579334c3e8e05b3e44328e91 Mon Sep 17 00:00:00 2001 From: Calvin Rose Date: Sat, 14 Feb 2026 18:31:57 -0600 Subject: [PATCH 159/307] Update for msvc build. --- src/core/table.c | 2 +- tools/msi/LICENSE.rtf | Bin 1272 -> 1274 bytes 2 files changed, 1 insertion(+), 1 deletion(-) diff --git a/src/core/table.c b/src/core/table.c index e25e699f..ec418d6a 100644 --- a/src/core/table.c +++ b/src/core/table.c @@ -157,7 +157,7 @@ Janet janet_table_get(JanetTable *t, Janet key) { /* Used internally for compiler stuff */ Janet janet_table_get_keyword(JanetTable *t, const char *keyword) { - int32_t keyword_len = strlen(keyword); + int32_t keyword_len = (int32_t) strlen(keyword); for (int i = JANET_MAX_PROTO_DEPTH; t && i; t = t->proto, --i) { JanetKV *bucket = (JanetKV *) janet_dict_find_keyword(t->data, t->capacity, (const uint8_t *) keyword, keyword_len); if (NULL != bucket && !janet_checktype(bucket->key, JANET_NIL)) diff --git a/tools/msi/LICENSE.rtf b/tools/msi/LICENSE.rtf index 24a86b777612c75a412ff438b1e6181db91c4537..8a6be5e57be8c4b9aa6258fe4b75f6f9b3c7c66d 100644 GIT binary patch delta 15 Wcmeyt`HOSH5k|9(M~zvSc)0*Jas^fZ delta 12 Tcmeyx`Ga%95k}*UM~ztkCWZw% From 0aa7dfeb9a19d1892473c07d8099b1790a84e622 Mon Sep 17 00:00:00 2001 From: Calvin Rose Date: Sat, 14 Feb 2026 21:11:28 -0600 Subject: [PATCH 160/307] Work on windows for WSAConnect not working. For remote connections, if you did not manually wait for the connection to settle, the programmer would see unspecified network errors. This is is because the underlying TCP Connection had not been established yet. The correct way to deal with this is to use ConnectEx if available instead of WSAConnect. --- src/core/net.c | 10 ++++++---- tools/msi/janet.wxs | 6 +++++- 2 files changed, 11 insertions(+), 5 deletions(-) diff --git a/src/core/net.c b/src/core/net.c index a9bcb0f9..fde7f04c 100644 --- a/src/core/net.c +++ b/src/core/net.c @@ -570,15 +570,16 @@ JANET_CORE_FN(cfun_net_connect, if (socktype == SOCK_DGRAM) udp_flag = JANET_STREAM_UDPSERVER; JanetStream *stream = make_stream(sock, JANET_STREAM_READABLE | JANET_STREAM_WRITABLE | udp_flag); - /* Set up the socket for non-blocking IO before connecting */ - janet_net_socknoblock(sock); - /* Connect to socket */ #ifdef JANET_WINDOWS int status = WSAConnect(sock, addr, addrlen, NULL, NULL, NULL, NULL); int err = WSAGetLastError(); freeaddrinfo(ai); + /* Set up the socket for non-blocking IO after connecting on windows by default */ + janet_net_socknoblock(sock); #else + /* Set up the socket for non-blocking IO before connecting */ + janet_net_socknoblock(sock); int status; do { status = connect(sock, addr, addrlen); @@ -599,10 +600,11 @@ JANET_CORE_FN(cfun_net_connect, return janet_wrap_abstract(stream); } - if (status == -1) { #ifdef JANET_WINDOWS + if (status == SOCKET_ERROR) { if (err != WSAEWOULDBLOCK) { #else + if (status == -1) { if (err != EINPROGRESS) { #endif JSOCKCLOSE(sock); diff --git a/tools/msi/janet.wxs b/tools/msi/janet.wxs index d14cfa5a..5269806f 100644 --- a/tools/msi/janet.wxs +++ b/tools/msi/janet.wxs @@ -87,7 +87,9 @@ - + + + @@ -169,6 +171,7 @@ ALLUSERS=1 + @@ -178,6 +181,7 @@ NOT ALLUSERS=1 + From 10bb17199c28a86928ea5c2ba45cc3ecb04d0723 Mon Sep 17 00:00:00 2001 From: Calvin Rose Date: Sun, 15 Feb 2026 08:45:56 -0600 Subject: [PATCH 161/307] Prepare for 1.41.0 release --- CHANGELOG.md | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 4007ba3d..9aef8a65 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,7 +1,8 @@ # Changelog All notable changes to this project will be documented in this file. -## Unreleased - ??? +## 1.41.0 - 2026-02-15 +- Revert to blocking behaior of `net/connect` on windows to fix spurious errors. - Allow overriding the loader when doing imports with the `:loader` argument. - Allow importing modules with a path extension to do what one would expect. - Add `find-all` argument to `module/find` From 2aaa7dfa102f17de21a9fe44bbc7f1e15f43694b Mon Sep 17 00:00:00 2001 From: Calvin Rose Date: Sat, 14 Feb 2026 14:37:23 -0600 Subject: [PATCH 162/307] Sort keys when compiling struct and table literals. Order of evaluation becomes more clear in some cases. --- src/core/compile.c | 19 +++++++++++++++---- src/mainclient/shell.c | 3 +++ 2 files changed, 18 insertions(+), 4 deletions(-) diff --git a/src/core/compile.c b/src/core/compile.c index 2c68555a..cddf14de 100644 --- a/src/core/compile.c +++ b/src/core/compile.c @@ -459,11 +459,22 @@ JanetSlot *janetc_toslotskv(JanetCompiler *c, Janet ds) { const JanetKV *kvs = NULL; int32_t cap = 0, len = 0; janet_dictionary_view(ds, &kvs, &len, &cap); - for (int32_t i = 0; i < cap; i++) { - if (janet_checktype(kvs[i].key, JANET_NIL)) continue; - janet_v_push(ret, janetc_value(subopts, kvs[i].key)); - janet_v_push(ret, janetc_value(subopts, kvs[i].value)); + /* Sort keys for stability of order? */ + int32_t *index_buf; + int32_t index_buf_stack[32]; + int32_t *index_buf_heap = NULL; + if (len < 32) { + index_buf = index_buf_stack; + } else { + index_buf_heap = janet_smalloc(sizeof(int32_t) * len); + index_buf = index_buf_heap; } + if (len) janet_sorted_keys(kvs, cap, index_buf); + for (int32_t i = 0; i < len; i++) { + janet_v_push(ret, janetc_value(subopts, kvs[index_buf[i]].key)); + janet_v_push(ret, janetc_value(subopts, kvs[index_buf[i]].value)); + } + if (index_buf_heap) janet_sfree(index_buf_heap); return ret; } diff --git a/src/mainclient/shell.c b/src/mainclient/shell.c index 497fb33a..4dadc041 100644 --- a/src/mainclient/shell.c +++ b/src/mainclient/shell.c @@ -1258,7 +1258,10 @@ int main(int argc, char **argv) { status = janet_loop_fiber(fiber); /* Deinitialize vm */ + +#if !defined(JANET_SIMPLE_GETLINE) savehistory(); +#endif janet_deinit(); janet_line_deinit(); From 5c0bb4b385d7daac3d872ce4c03d6bb13687ceb9 Mon Sep 17 00:00:00 2001 From: Calvin Rose Date: Sun, 15 Feb 2026 09:57:10 -0600 Subject: [PATCH 163/307] Cosmo libc builds were not working. --- .github/cosmo/setup | 8 ++++---- .github/workflows/test.yml | 19 +++++++++++++++++++ 2 files changed, 23 insertions(+), 4 deletions(-) diff --git a/.github/cosmo/setup b/.github/cosmo/setup index 9de92d41..f515e105 100644 --- a/.github/cosmo/setup +++ b/.github/cosmo/setup @@ -1,19 +1,19 @@ #!/bin/sh set -e -sudo apt update +sudo apt-get update sudo apt-get install -y ca-certificates libssl-dev\ - qemu qemu-utils qemu-user-static\ + qemu-utils qemu-user-static\ texinfo groff\ cmake ninja-build bison zip\ pkg-config build-essential autoconf re2c # download cosmocc cd /sc -wget https://github.com/jart/cosmopolitan/releases/download/3.3.3/cosmocc-3.3.3.zip +wget https://github.com/jart/cosmopolitan/releases/download/4.0.2/cosmocc-4.0.2.zip mkdir -p cosmocc cd cosmocc -unzip ../cosmocc-3.3.3.zip +unzip ../cosmocc-4.0.2.zip # register cd /sc/cosmocc diff --git a/.github/workflows/test.yml b/.github/workflows/test.yml index ed02f369..823f807c 100644 --- a/.github/workflows/test.yml +++ b/.github/workflows/test.yml @@ -136,3 +136,22 @@ jobs: run: docker run --privileged --rm tonistiigi/binfmt --install s390x - name: Build and run on emulated architecture 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" + + test-cosmo: + name: Test build for Cosmo + runs-on: ubuntu-latest + steps: + - name: Checkout the repository + uses: actions/checkout@master + - name: create build folder + run: | + sudo mkdir -p /sc + sudo chmod -R 0777 /sc + - name: setup Cosmopolitan Libc + run: bash ./.github/cosmo/setup + - name: Set the version + run: echo "version=${GITHUB_REF/refs\/tags\//}" >> $GITHUB_ENV + - name: Set the platform + run: echo "platform=cosmo" >> $GITHUB_ENV + - name: build Janet APE binary + run: bash ./.github/cosmo/build From 4b078e7a453af1f67ad097a08007ba7db5e68dc5 Mon Sep 17 00:00:00 2001 From: Calvin Rose Date: Sun, 15 Feb 2026 10:17:16 -0600 Subject: [PATCH 164/307] Use correct format specifier on windows if missing message. --- src/core/os.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/core/os.c b/src/core/os.c index bc57b64a..07a379a2 100644 --- a/src/core/os.c +++ b/src/core/os.c @@ -1331,7 +1331,7 @@ static Janet os_execute_impl(int32_t argc, Janet *argv, JanetExecuteMode mode) { msgbuf, sizeof(msgbuf), NULL); - if (!*msgbuf) snprintf(msgbuf, sizeof(msgbuf), "%d", cp_error_code); + if (!*msgbuf) snprintf(msgbuf, sizeof(msgbuf), "%" PRIu32, cp_error_code); char *c = msgbuf; while (*c) { if (*c == '\n' || *c == '\r') { From a2a7e9f01e3a116cfb049a08e1c66b49ca1641cb Mon Sep 17 00:00:00 2001 From: Calvin Rose Date: Sun, 15 Feb 2026 10:24:56 -0600 Subject: [PATCH 165/307] Add explicit include of inttypes.h --- src/core/os.c | 1 + 1 file changed, 1 insertion(+) diff --git a/src/core/os.c b/src/core/os.c index 07a379a2..76b58aff 100644 --- a/src/core/os.c +++ b/src/core/os.c @@ -40,6 +40,7 @@ #include #include #include +#include #ifdef JANET_BSD #include From 8b6d56edae8c1eb5ae19b024087a065b6918b9ef Mon Sep 17 00:00:00 2001 From: Calvin Rose Date: Sun, 15 Feb 2026 10:36:46 -0600 Subject: [PATCH 166/307] Patch release to 1.41.1 --- CHANGELOG.md | 2 +- meson.build | 2 +- src/conf/janetconf.h | 6 +++--- 3 files changed, 5 insertions(+), 5 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 9aef8a65..96f31273 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,7 +1,7 @@ # Changelog All notable changes to this project will be documented in this file. -## 1.41.0 - 2026-02-15 +## 1.41.1 - 2026-02-15 - Revert to blocking behaior of `net/connect` on windows to fix spurious errors. - Allow overriding the loader when doing imports with the `:loader` argument. - Allow importing modules with a path extension to do what one would expect. diff --git a/meson.build b/meson.build index 742d6f09..8d6c8f9d 100644 --- a/meson.build +++ b/meson.build @@ -20,7 +20,7 @@ project('janet', 'c', default_options : ['c_std=c99', 'build.c_std=c99', 'b_lundef=false', 'default_library=both'], - version : '1.41.0') + version : '1.41.1') # Global settings janet_path = join_paths(get_option('prefix'), get_option('libdir'), 'janet') diff --git a/src/conf/janetconf.h b/src/conf/janetconf.h index 1bc830d9..60934f9e 100644 --- a/src/conf/janetconf.h +++ b/src/conf/janetconf.h @@ -5,9 +5,9 @@ #define JANET_VERSION_MAJOR 1 #define JANET_VERSION_MINOR 41 -#define JANET_VERSION_PATCH 0 -#define JANET_VERSION_EXTRA "-dev" -#define JANET_VERSION "1.41.0-dev" +#define JANET_VERSION_PATCH 1 +#define JANET_VERSION_EXTRA "" +#define JANET_VERSION "1.41.1" /* #define JANET_BUILD "local" */ From b0db2b22d6672fc0060ad26d3ad13e53f0b4551d Mon Sep 17 00:00:00 2001 From: Calvin Rose Date: Sun, 15 Feb 2026 11:18:40 -0600 Subject: [PATCH 167/307] Remove macos-13 --- .github/workflows/release.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml index 689dd540..e40422f7 100644 --- a/.github/workflows/release.yml +++ b/.github/workflows/release.yml @@ -17,7 +17,7 @@ jobs: runs-on: ${{ matrix.os }} strategy: matrix: - os: [ ubuntu-latest, macos-13 ] + os: [ ubuntu-latest ] steps: - name: Checkout the repository uses: actions/checkout@master From 2df16e5a484005e6b93279b4c82346ee5a11ed70 Mon Sep 17 00:00:00 2001 From: Calvin Rose Date: Sun, 15 Feb 2026 18:46:21 -0600 Subject: [PATCH 168/307] Allow garbage collection to be called inside the module entry. This usually shouldn't be needed, but in the case that it is, or if garbage collection is triggered manually, we can prevent use-after-free. --- src/core/corelib.c | 3 +++ 1 file changed, 3 insertions(+) diff --git a/src/core/corelib.c b/src/core/corelib.c index 420c47c7..5083b948 100644 --- a/src/core/corelib.c +++ b/src/core/corelib.c @@ -27,6 +27,7 @@ #include "compile.h" #include "state.h" #include "util.h" +#include "fiber.h" #endif /* Generated bytes */ @@ -306,6 +307,8 @@ JANET_CORE_FN(janet_core_native, if (!init) { janet_panicf("could not load native %S: %S", path, error); } + /* GC root incase garbage collection called inside module entry */ + janet_fiber_push(janet_vm.fiber, janet_wrap_table(env)); init(env); janet_table_put(env, janet_ckeywordv("native"), argv[0]); return janet_wrap_table(env); From 529d8c9e4a20726b9158d277c100f738dd6b7873 Mon Sep 17 00:00:00 2001 From: Calvin Rose Date: Sun, 15 Feb 2026 21:10:26 -0600 Subject: [PATCH 169/307] Improve ability to load modules by full path. Be explicit when we are including this functionality. Add a function module/add-file-extension that can do this programatically. --- src/boot/boot.janet | 21 ++++++++++++++++++++- 1 file changed, 20 insertions(+), 1 deletion(-) diff --git a/src/boot/boot.janet b/src/boot/boot.janet index 3e103009..7296e9cc 100644 --- a/src/boot/boot.janet +++ b/src/boot/boot.janet @@ -2910,7 +2910,9 @@ the generated paths behave like other module types, including relative imports and syspath imports. `ext` is the file extension to associate with this module type, including the dot. `loader` is the - keyword name of a loader in `module/loaders`. Returns the modified `module/paths`. + keyword name of a loader in `module/loaders`. The parameter `match-exact-path` + will allow users to import files with this extension directly with a relative + or absolute path. Returns the modified `module/paths`. ``` [ext loader] (def mp (dyn *module-paths* module/paths)) @@ -2925,6 +2927,17 @@ (array/insert mp sys-index [(string ":sys:/:all:" ext) loader check-is-dep]) (def curall-index (find-prefix ":cur:/:all:")) (array/insert mp curall-index [(string ":cur:/:all:" ext) loader check-relative]) + mp) + +(defn module/add-file-extension + ``` + Add paths to `module/paths` for a given file extension such that + the programmer can import a module by relative or absolute path. + Returns the modified `module/paths`. + ``` + [ext loader] + (assert (string/has-prefix? "." ext) "file extension must have . prefix") + (def mp (dyn *module-paths* module/paths)) (array/insert mp 0 [":all:" loader (fn :check-ext [x] (string/has-suffix? ext x))]) mp) @@ -2950,6 +2963,12 @@ (module/add-paths "/init.janet" :source) (module/add-paths ".janet" :source) (module/add-paths ".jimage" :image) +(module/add-file-extension ".janet" :source) +(module/add-file-extension ".jimage" :source) +# These obviously won't work on all platforms, but if a user explicitly +# tries to import them, we may as well try. +(module/add-file-extension ".so" :native) +(module/add-file-extension ".dll" :native) (array/insert module/paths 0 [(fn is-cached [path] (if (in (dyn *module-cache* module/cache) path) path)) :preload From 1557f9da7817bea8021b57bf53c7539aac73e6e7 Mon Sep 17 00:00:00 2001 From: Calvin Rose Date: Sun, 15 Feb 2026 21:54:42 -0600 Subject: [PATCH 170/307] Don't reference argv after fiber may have been resized. --- src/boot/boot.janet | 3 ++- src/core/corelib.c | 3 ++- 2 files changed, 4 insertions(+), 2 deletions(-) diff --git a/src/boot/boot.janet b/src/boot/boot.janet index 7296e9cc..e28ec5ce 100644 --- a/src/boot/boot.janet +++ b/src/boot/boot.janet @@ -2932,7 +2932,8 @@ (defn module/add-file-extension ``` Add paths to `module/paths` for a given file extension such that - the programmer can import a module by relative or absolute path. + the programmer can import a module by relative or absolute path from + the current working directory. Returns the modified `module/paths`. ``` [ext loader] diff --git a/src/core/corelib.c b/src/core/corelib.c index 5083b948..e420b736 100644 --- a/src/core/corelib.c +++ b/src/core/corelib.c @@ -295,6 +295,7 @@ JANET_CORE_FN(janet_core_native, "from the native module.") { JanetModule init; janet_arity(argc, 1, 2); + Janet argv0 = argv[0]; const uint8_t *path = janet_getstring(argv, 0); const uint8_t *error = NULL; JanetTable *env; @@ -310,7 +311,7 @@ JANET_CORE_FN(janet_core_native, /* GC root incase garbage collection called inside module entry */ janet_fiber_push(janet_vm.fiber, janet_wrap_table(env)); init(env); - janet_table_put(env, janet_ckeywordv("native"), argv[0]); + janet_table_put(env, janet_ckeywordv("native"), argv0); return janet_wrap_table(env); } From 739d9d9fe3e5778ca3ca907a0fd78579b297412a Mon Sep 17 00:00:00 2001 From: Calvin Rose Date: Sun, 15 Feb 2026 22:20:54 -0600 Subject: [PATCH 171/307] Expose module/add-syspath and update CHANGELOG.md --- CHANGELOG.md | 5 +++++ src/boot/boot.janet | 2 +- 2 files changed, 6 insertions(+), 1 deletion(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 96f31273..d91e38ea 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,6 +1,11 @@ # Changelog All notable changes to this project will be documented in this file. +## Unreleased - ??? +- Add `module/add-file-extension` +- Add `module/add-syspath` +- Allow use of the interpreter and garbage collection inside module entry for native modules. + ## 1.41.1 - 2026-02-15 - Revert to blocking behaior of `net/connect` on windows to fix spurious errors. - Allow overriding the loader when doing imports with the `:loader` argument. diff --git a/src/boot/boot.janet b/src/boot/boot.janet index e28ec5ce..994fc1c0 100644 --- a/src/boot/boot.janet +++ b/src/boot/boot.janet @@ -2943,7 +2943,7 @@ mp) # Don't expose this externally yet - could break if custom module/paths is setup. -(defn- module/add-syspath +(defn module/add-syspath ``` Add a custom syspath to `module/paths` by duplicating all entries that being with `:sys:` and adding duplicates with a specific path prefix instead. From 0299620a2d7b8e9d833a2c7ac5dc4135c9302f69 Mon Sep 17 00:00:00 2001 From: Calvin Rose Date: Sun, 15 Feb 2026 23:17:53 -0600 Subject: [PATCH 172/307] Code defensively with regard to stack resizes from custom get and put for abstract types. Abstract types whose get/put/length/etc. implementations allocated fiber slots could break the VM by invalidating the stack pointer in the interpreter. This is admittedly a bit unusual but is something most users would probably expect to work. Debugging this would be a real pain. --- src/core/value.c | 4 +-- src/core/vm.c | 73 +++++++++++++++++++++++++++++++++++++----------- 2 files changed, 59 insertions(+), 18 deletions(-) diff --git a/src/core/value.c b/src/core/value.c index ae407f7c..2a0c4fe0 100644 --- a/src/core/value.c +++ b/src/core/value.c @@ -495,7 +495,7 @@ Janet janet_in(Janet ds, Janet key) { if (!(type->get)(janet_unwrap_abstract(ds), key, &value)) janet_panicf("key %v not found in %v ", key, ds); } else { - janet_panicf("no getter for %v ", ds); + janet_panicf("no getter for %v", ds); } break; } @@ -622,7 +622,7 @@ Janet janet_getindex(Janet ds, int32_t index) { if (!(type->get)(janet_unwrap_abstract(ds), janet_wrap_integer(index), &value)) value = janet_wrap_nil(); } else { - janet_panicf("no getter for %v ", ds); + janet_panicf("no getter for %v", ds); } break; } diff --git a/src/core/vm.c b/src/core/vm.c index 0d19aea7..02ee3d10 100644 --- a/src/core/vm.c +++ b/src/core/vm.c @@ -129,7 +129,9 @@ if (!janet_checktype(op1, JANET_NUMBER)) {\ vm_commit();\ Janet _argv[2] = { op1, janet_wrap_number(CS) };\ - stack[A] = janet_mcall(#op, 2, _argv);\ + Janet a = janet_mcall(#op, 2, _argv);\ + stack = fiber->data + fiber->frame;\ + stack[A] = a;\ vm_checkgc_pcnext();\ } else {\ double x1 = janet_unwrap_number(op1);\ @@ -143,7 +145,9 @@ if (!janet_checktype(op1, JANET_NUMBER)) {\ vm_commit();\ Janet _argv[2] = { op1, janet_wrap_number(CS) };\ - stack[A] = janet_mcall(#op, 2, _argv);\ + Janet a = janet_mcall(#op, 2, _argv);\ + stack = fiber->data + fiber->frame;\ + stack[A] = a;\ vm_checkgc_pcnext();\ } else {\ double y1 = janet_unwrap_number(op1);\ @@ -166,7 +170,9 @@ vm_pcnext();\ } else {\ vm_commit();\ - stack[A] = janet_binop_call(#op, "r" #op, op1, op2);\ + Janet a = janet_binop_call(#op, "r" #op, op1, op2);\ + stack = fiber->data + fiber->frame;\ + stack[A] = a;\ vm_checkgc_pcnext();\ }\ } @@ -186,7 +192,9 @@ vm_pcnext();\ } else {\ vm_commit();\ - stack[A] = janet_binop_call(#op, "r" #op, op1, op2);\ + Janet a = janet_binop_call(#op, "r" #op, op1, op2);\ + stack = fiber->data + fiber->frame;\ + stack[A] = a;\ vm_checkgc_pcnext();\ }\ } @@ -203,7 +211,9 @@ vm_pcnext();\ } else {\ vm_commit();\ - stack[A] = janet_wrap_boolean(janet_compare(op1, op2) op 0);\ + Janet a = janet_wrap_boolean(janet_compare(op1, op2) op 0);\ + stack = fiber->data + fiber->frame;\ + stack[A] = a;\ vm_checkgc_pcnext();\ }\ } @@ -217,7 +227,9 @@ vm_pcnext();\ } else {\ vm_commit();\ - stack[A] = janet_wrap_boolean(janet_compare(op1, janet_wrap_integer(CS)) op 0);\ + Janet a = janet_wrap_boolean(janet_compare(op1, janet_wrap_integer(CS)) op 0);\ + stack = fiber->data + fiber->frame;\ + stack[A] = a;\ vm_checkgc_pcnext();\ }\ } @@ -710,7 +722,9 @@ static JanetSignal run_vm(JanetFiber *fiber, Janet in) { vm_pcnext(); } else { vm_commit(); - stack[A] = janet_binop_call("div", "rdiv", op1, op2); + Janet a = janet_binop_call("div", "rdiv", op1, op2); + stack = fiber->data + fiber->frame; + stack[A] = a; vm_checkgc_pcnext(); } } @@ -730,7 +744,9 @@ static JanetSignal run_vm(JanetFiber *fiber, Janet in) { vm_pcnext(); } else { vm_commit(); - stack[A] = janet_binop_call("mod", "rmod", op1, op2); + Janet a = janet_binop_call("mod", "rmod", op1, op2); + stack = fiber->data + fiber->frame; + stack[A] = a; vm_checkgc_pcnext(); } } @@ -745,7 +761,9 @@ static JanetSignal run_vm(JanetFiber *fiber, Janet in) { vm_pcnext(); } else { vm_commit(); - stack[A] = janet_binop_call("%", "r%", op1, op2); + Janet a = janet_binop_call("%", "r%", op1, op2); + stack = fiber->data + fiber->frame; + stack[A] = a; vm_checkgc_pcnext(); } } @@ -766,7 +784,9 @@ static JanetSignal run_vm(JanetFiber *fiber, Janet in) { vm_pcnext(); } else { vm_commit(); - stack[A] = janet_unary_call("~", op); + Janet a = janet_unary_call("~", op); + stack = fiber->data + fiber->frame; + stack[A] = a; vm_checkgc_pcnext(); } } @@ -872,8 +892,11 @@ static JanetSignal run_vm(JanetFiber *fiber, Janet in) { stack[A] = janet_wrap_boolean(!janet_checktype(stack[B], JANET_NUMBER) || (janet_unwrap_number(stack[B]) != (double) CS)); vm_pcnext(); - VM_OP(JOP_COMPARE) - stack[A] = janet_wrap_integer(janet_compare(stack[B], stack[C])); + VM_OP(JOP_COMPARE) { + Janet a = janet_wrap_integer(janet_compare(stack[B], stack[C])); + stack = fiber->data + fiber->frame; + stack[A] = a; + } vm_pcnext(); VM_OP(JOP_NEXT) @@ -1157,6 +1180,7 @@ static JanetSignal run_vm(JanetFiber *fiber, Janet in) { vm_commit(); fiber->flags |= JANET_FIBER_RESUME_NO_USEVAL; janet_put(stack[A], stack[B], stack[C]); + stack = fiber->data + fiber->frame; fiber->flags &= ~JANET_FIBER_RESUME_NO_USEVAL; vm_checkgc_pcnext(); @@ -1164,27 +1188,44 @@ static JanetSignal run_vm(JanetFiber *fiber, Janet in) { vm_commit(); fiber->flags |= JANET_FIBER_RESUME_NO_USEVAL; janet_putindex(stack[A], C, stack[B]); + stack = fiber->data + fiber->frame; fiber->flags &= ~JANET_FIBER_RESUME_NO_USEVAL; vm_checkgc_pcnext(); VM_OP(JOP_IN) vm_commit(); - stack[A] = janet_in(stack[B], stack[C]); + { + Janet a = janet_in(stack[B], stack[C]); + stack = fiber->data + fiber->frame; + stack[A] = a; + } vm_pcnext(); VM_OP(JOP_GET) vm_commit(); - stack[A] = janet_get(stack[B], stack[C]); + { + Janet a = janet_get(stack[B], stack[C]); + stack = fiber->data + fiber->frame; + stack[A] = a; + } vm_pcnext(); VM_OP(JOP_GET_INDEX) vm_commit(); - stack[A] = janet_getindex(stack[B], C); + { + Janet a = janet_getindex(stack[B], C); + stack = fiber->data + fiber->frame; + stack[A] = a; + } vm_pcnext(); VM_OP(JOP_LENGTH) vm_commit(); - stack[A] = janet_lengthv(stack[E]); + { + Janet a = janet_lengthv(stack[E]); + stack = fiber->data + fiber->frame; + stack[A] = a; + } vm_pcnext(); VM_OP(JOP_MAKE_ARRAY) { From b6676f350c7e30779a3ad9420748c4b1d5ce2557 Mon Sep 17 00:00:00 2001 From: sogaiu <33044872+sogaiu@users.noreply.github.com> Date: Tue, 17 Feb 2026 00:12:02 +0900 Subject: [PATCH 173/307] Use snprintf instead of sprintf - sequel (#1713) Co-authored-by: sogaiu <983021772@users.noreply.github.com> --- src/core/ev.c | 9 +++++---- src/core/os.c | 2 +- 2 files changed, 6 insertions(+), 5 deletions(-) diff --git a/src/core/ev.c b/src/core/ev.c index 9b03413e..b6d85376 100644 --- a/src/core/ev.c +++ b/src/core/ev.c @@ -2948,10 +2948,11 @@ int janet_make_pipe(JanetHandle handles[2], int mode) { if (!CreatePipe(handles, handles + 1, &saAttr, 0)) return -1; return 0; } - sprintf(PipeNameBuffer, - "\\\\.\\Pipe\\JanetPipeFile.%08x.%08x", - (unsigned int) GetCurrentProcessId(), - (unsigned int) InterlockedIncrement(&PipeSerialNumber)); + snprintf(PipeNameBuffer, + sizeof(PipeNameBuffer), + "\\\\.\\Pipe\\JanetPipeFile.%08x.%08x", + (unsigned int) GetCurrentProcessId(), + (unsigned int) InterlockedIncrement(&PipeSerialNumber)); /* server handle goes to subprocess */ shandle = CreateNamedPipeA( diff --git a/src/core/os.c b/src/core/os.c index 76b58aff..21510575 100644 --- a/src/core/os.c +++ b/src/core/os.c @@ -2579,7 +2579,7 @@ JANET_CORE_FN(os_dir, char pattern[MAX_PATH + 1]; if (strlen(dir) > (sizeof(pattern) - 3)) janet_panicf("path too long: %s", dir); - sprintf(pattern, "%s/*", dir); + snprintf(pattern, sizeof(pattern), "%s/*", dir); intptr_t res = _findfirst(pattern, &afile); if (-1 == res) janet_panicv(janet_cstringv(janet_strerror(errno))); do { From 78ef9d173332e01a83608ff73ca09a88d8815a42 Mon Sep 17 00:00:00 2001 From: Evan Shaw Date: Wed, 18 Feb 2026 02:50:50 +1300 Subject: [PATCH 174/307] Initialize memory allocated by put (#1715) --- src/core/value.c | 8 ++++++++ 1 file changed, 8 insertions(+) diff --git a/src/core/value.c b/src/core/value.c index 2a0c4fe0..bd673362 100644 --- a/src/core/value.c +++ b/src/core/value.c @@ -724,6 +724,9 @@ void janet_putindex(Janet ds, int32_t index, Janet value) { JanetArray *array = janet_unwrap_array(ds); if (index >= array->count) { janet_array_ensure(array, index + 1, 2); + for (int32_t i = array->count; i < index + 1; i++) { + array->data[i] = janet_wrap_nil(); + } array->count = index + 1; } array->data[index] = value; @@ -735,6 +738,7 @@ void janet_putindex(Janet ds, int32_t index, Janet value) { janet_panicf("can only put integers in buffers, got %v", value); if (index >= buffer->count) { janet_buffer_ensure(buffer, index + 1, 2); + memset(buffer->data + buffer->count, 0, index + 1 - buffer->count); buffer->count = index + 1; } buffer->data[index] = (uint8_t)(janet_unwrap_integer(value) & 0xFF); @@ -768,6 +772,9 @@ void janet_put(Janet ds, Janet key, Janet value) { int32_t index = getter_checkint(type, key, INT32_MAX - 1); if (index >= array->count) { janet_array_ensure(array, index + 1, 2); + for (int32_t i = array->count; i < index + 1; i++) { + array->data[i] = janet_wrap_nil(); + } array->count = index + 1; } array->data[index] = value; @@ -780,6 +787,7 @@ void janet_put(Janet ds, Janet key, Janet value) { janet_panicf("can only put integers in buffers, got %v", value); if (index >= buffer->count) { janet_buffer_ensure(buffer, index + 1, 2); + memset(buffer->data + buffer->count, 0, index + 1 - buffer->count); buffer->count = index + 1; } buffer->data[index] = (uint8_t)(janet_unwrap_integer(value) & 0xFF); From fa0c039cd366605852386a7fa59b84ff6a9eeb19 Mon Sep 17 00:00:00 2001 From: Calvin Rose Date: Tue, 17 Feb 2026 07:57:38 -0600 Subject: [PATCH 175/307] Add regression test for issue #1714 --- test/suite-array.janet | 4 ++++ test/suite-buffer.janet | 4 ++++ 2 files changed, 8 insertions(+) diff --git a/test/suite-array.janet b/test/suite-array.janet index b6c7954a..e3e8a0b3 100644 --- a/test/suite-array.janet +++ b/test/suite-array.janet @@ -86,5 +86,9 @@ (assert-error "array/join error 4" (array/join @[] "abc123")) (assert-error "array/join error 5" (array/join @[] "abc123")) +# Regression 1714 +(repeat 10 + (assert (deep= (put @[] 100 10) (put (seq [_ :range [0 101]] nil) 100 10)) "regression 1714")) + (end-suite) diff --git a/test/suite-buffer.janet b/test/suite-buffer.janet index b1362537..a0262bf9 100644 --- a/test/suite-buffer.janet +++ b/test/suite-buffer.janet @@ -179,5 +179,9 @@ (assert (= (string buf) "xxxxxx") "buffer/format-at negative index") (assert-error "expected index at to be in range [0, 0), got 1" (buffer/format-at @"" 1 "abc")) +# Regression 1714 +(repeat 10 + (assert (deep= (put @"" 100 10) (put (buffer (string/repeat "\0" 101)) 100 10)) "regression 1714")) + (end-suite) From b2c9fc123c24be0c30ee0b1d4378a3f97d27e389 Mon Sep 17 00:00:00 2001 From: Calvin Rose Date: Tue, 17 Feb 2026 09:07:01 -0600 Subject: [PATCH 176/307] Generate JOP_PUT_INDEX in the compiler when possible. --- src/core/cfuns.c | 29 ++++++++++++++++++++++------- test/suite-array.janet | 3 ++- test/suite-buffer.janet | 3 ++- 3 files changed, 26 insertions(+), 9 deletions(-) diff --git a/src/core/cfuns.c b/src/core/cfuns.c index d8f917c6..2c09ead4 100644 --- a/src/core/cfuns.c +++ b/src/core/cfuns.c @@ -201,14 +201,29 @@ static JanetSlot do_cmp(JanetFopts opts, JanetSlot *args) { return opreduce(opts, args, JOP_COMPARE, 0, janet_wrap_nil(), janet_wrap_nil()); } static JanetSlot do_put(JanetFopts opts, JanetSlot *args) { - if (opts.flags & JANET_FOPTS_DROP) { - janetc_emit_sss(opts.compiler, JOP_PUT, args[0], args[1], args[2], 0); - return janetc_cslot(janet_wrap_nil()); + int8_t inline_index = 0; + if (can_slot_be_imm(args[1], &inline_index)) { + /* Use JOP_PUT_INDEX */ + if (opts.flags & JANET_FOPTS_DROP) { + janetc_emit_ssi(opts.compiler, JOP_PUT_INDEX, args[0], args[2], inline_index, 0); + return janetc_cslot(janet_wrap_nil()); + } else { + JanetSlot t = janetc_gettarget(opts); + janetc_copy(opts.compiler, t, args[0]); + janetc_emit_ssi(opts.compiler, JOP_PUT_INDEX, t, args[2], inline_index, 0); + return t; + } } else { - JanetSlot t = janetc_gettarget(opts); - janetc_copy(opts.compiler, t, args[0]); - janetc_emit_sss(opts.compiler, JOP_PUT, t, args[1], args[2], 0); - return t; + /* Use JOP_PUT */ + if (opts.flags & JANET_FOPTS_DROP) { + janetc_emit_sss(opts.compiler, JOP_PUT, args[0], args[1], args[2], 0); + return janetc_cslot(janet_wrap_nil()); + } else { + JanetSlot t = janetc_gettarget(opts); + janetc_copy(opts.compiler, t, args[0]); + janetc_emit_sss(opts.compiler, JOP_PUT, t, args[1], args[2], 0); + return t; + } } } static JanetSlot do_length(JanetFopts opts, JanetSlot *args) { diff --git a/test/suite-array.janet b/test/suite-array.janet index e3e8a0b3..929135ab 100644 --- a/test/suite-array.janet +++ b/test/suite-array.janet @@ -88,7 +88,8 @@ # Regression 1714 (repeat 10 - (assert (deep= (put @[] 100 10) (put (seq [_ :range [0 101]] nil) 100 10)) "regression 1714")) + (assert (deep= (put @[] 100 10) (put (seq [_ :range [0 101]] nil) 100 10)) "regression 1714") + (assert (deep= (put @[] 200 10) (put (seq [_ :range [0 201]] nil) 200 10)) "regression 1714")) (end-suite) diff --git a/test/suite-buffer.janet b/test/suite-buffer.janet index a0262bf9..f0d2407e 100644 --- a/test/suite-buffer.janet +++ b/test/suite-buffer.janet @@ -181,7 +181,8 @@ # Regression 1714 (repeat 10 - (assert (deep= (put @"" 100 10) (put (buffer (string/repeat "\0" 101)) 100 10)) "regression 1714")) + (assert (deep= (put @"" 100 10) (put (buffer (string/repeat "\0" 101)) 100 10)) "regression 1714") + (assert (deep= (put @"" 200 10) (put (buffer (string/repeat "\0" 201)) 200 10)) "regression 1714")) (end-suite) From d3d7c675a8dd90912aa0fa8bc05845210a8a1144 Mon Sep 17 00:00:00 2001 From: Calvin Rose Date: Tue, 17 Feb 2026 18:56:21 -0600 Subject: [PATCH 177/307] Update CHANGELOG.md --- CHANGELOG.md | 1 + 1 file changed, 1 insertion(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index d91e38ea..fb11a555 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -2,6 +2,7 @@ All notable changes to this project will be documented in this file. ## Unreleased - ??? +- Fix regressions in `put` for arrays and buffers. - Add `module/add-file-extension` - Add `module/add-syspath` - Allow use of the interpreter and garbage collection inside module entry for native modules. From 68850a0a05eeb6fd9a66c2fc0849684c7b66d1c1 Mon Sep 17 00:00:00 2001 From: Calvin Rose Date: Wed, 18 Feb 2026 08:19:13 -0600 Subject: [PATCH 178/307] Update for 1.41.2 patch. --- CHANGELOG.md | 2 +- meson.build | 2 +- src/conf/janetconf.h | 4 ++-- src/core/specials.c | 2 +- 4 files changed, 5 insertions(+), 5 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index fb11a555..782e6c20 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,7 +1,7 @@ # Changelog All notable changes to this project will be documented in this file. -## Unreleased - ??? +## 1.41.2 - ??? - Fix regressions in `put` for arrays and buffers. - Add `module/add-file-extension` - Add `module/add-syspath` diff --git a/meson.build b/meson.build index 8d6c8f9d..45f31297 100644 --- a/meson.build +++ b/meson.build @@ -20,7 +20,7 @@ project('janet', 'c', default_options : ['c_std=c99', 'build.c_std=c99', 'b_lundef=false', 'default_library=both'], - version : '1.41.1') + version : '1.41.2') # Global settings janet_path = join_paths(get_option('prefix'), get_option('libdir'), 'janet') diff --git a/src/conf/janetconf.h b/src/conf/janetconf.h index 60934f9e..86042119 100644 --- a/src/conf/janetconf.h +++ b/src/conf/janetconf.h @@ -5,9 +5,9 @@ #define JANET_VERSION_MAJOR 1 #define JANET_VERSION_MINOR 41 -#define JANET_VERSION_PATCH 1 +#define JANET_VERSION_PATCH 2 #define JANET_VERSION_EXTRA "" -#define JANET_VERSION "1.41.1" +#define JANET_VERSION "1.41.2" /* #define JANET_BUILD "local" */ diff --git a/src/core/specials.c b/src/core/specials.c index 7f4646a2..770de49a 100644 --- a/src/core/specials.c +++ b/src/core/specials.c @@ -909,7 +909,7 @@ static JanetSlot janetc_while(JanetFopts opts, int32_t argn, const Janet *argv) janetc_regalloc_freetemp(&c->scope->ra, tempself, JANETC_REGTEMP_0); /* Compile function */ JanetFuncDef *def = janetc_pop_funcdef(c); - def->name = janet_cstring("_while"); + def->name = janet_cstring("while"); janet_def_addflags(def); int32_t defindex = janetc_addfuncdef(c, def); /* And then load the closure and call it. */ From 91cc499e77ed6b90da6b71264abeac1420e3267d Mon Sep 17 00:00:00 2001 From: Calvin Rose Date: Wed, 18 Feb 2026 08:19:55 -0600 Subject: [PATCH 179/307] 1.42.2 patch. --- CHANGELOG.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 782e6c20..e2ed30fa 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,7 +1,7 @@ # Changelog All notable changes to this project will be documented in this file. -## 1.41.2 - ??? +## 1.41.2 - 2026-02-18 - Fix regressions in `put` for arrays and buffers. - Add `module/add-file-extension` - Add `module/add-syspath` From 0fea20c82182fe661f75b00a8889d801fe2d79b6 Mon Sep 17 00:00:00 2001 From: Calvin Rose Date: Wed, 18 Feb 2026 08:27:10 -0600 Subject: [PATCH 180/307] Prepare for v1.41.2 and indicate vm changes for stack correction. --- CHANGELOG.md | 1 + 1 file changed, 1 insertion(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index e2ed30fa..65d3018a 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -5,6 +5,7 @@ All notable changes to this project will be documented in this file. - Fix regressions in `put` for arrays and buffers. - Add `module/add-file-extension` - Add `module/add-syspath` +- Fix issue with possible stack corrpution with abstract types that modify the current fiber. - Allow use of the interpreter and garbage collection inside module entry for native modules. ## 1.41.1 - 2026-02-15 From d636502c32555e9b07ffb8abaa2ab3a389a0ef6d Mon Sep 17 00:00:00 2001 From: Calvin Rose Date: Wed, 18 Feb 2026 08:41:31 -0600 Subject: [PATCH 181/307] Fix vestigial doc string. --- src/boot/boot.janet | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) diff --git a/src/boot/boot.janet b/src/boot/boot.janet index 994fc1c0..4d3ce6bb 100644 --- a/src/boot/boot.janet +++ b/src/boot/boot.janet @@ -2910,9 +2910,7 @@ the generated paths behave like other module types, including relative imports and syspath imports. `ext` is the file extension to associate with this module type, including the dot. `loader` is the - keyword name of a loader in `module/loaders`. The parameter `match-exact-path` - will allow users to import files with this extension directly with a relative - or absolute path. Returns the modified `module/paths`. + keyword name of a loader in `module/loaders`. Returns the modified `module/paths`. ``` [ext loader] (def mp (dyn *module-paths* module/paths)) From fc725e251189bdeabf5e5ae75d6cd937e1125688 Mon Sep 17 00:00:00 2001 From: Calvin Rose Date: Wed, 18 Feb 2026 08:46:17 -0600 Subject: [PATCH 182/307] Prepare for next patch release. --- CHANGELOG.md | 3 +++ meson.build | 2 +- src/conf/janetconf.h | 6 +++--- 3 files changed, 7 insertions(+), 4 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 65d3018a..aafe0b9c 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,6 +1,9 @@ # Changelog All notable changes to this project will be documented in this file. +## Unreleased - ??? +- Documentation fixes + ## 1.41.2 - 2026-02-18 - Fix regressions in `put` for arrays and buffers. - Add `module/add-file-extension` diff --git a/meson.build b/meson.build index 45f31297..bc773371 100644 --- a/meson.build +++ b/meson.build @@ -20,7 +20,7 @@ project('janet', 'c', default_options : ['c_std=c99', 'build.c_std=c99', 'b_lundef=false', 'default_library=both'], - version : '1.41.2') + version : '1.41.3') # Global settings janet_path = join_paths(get_option('prefix'), get_option('libdir'), 'janet') diff --git a/src/conf/janetconf.h b/src/conf/janetconf.h index 86042119..92eaaa29 100644 --- a/src/conf/janetconf.h +++ b/src/conf/janetconf.h @@ -5,9 +5,9 @@ #define JANET_VERSION_MAJOR 1 #define JANET_VERSION_MINOR 41 -#define JANET_VERSION_PATCH 2 -#define JANET_VERSION_EXTRA "" -#define JANET_VERSION "1.41.2" +#define JANET_VERSION_PATCH 3 +#define JANET_VERSION_EXTRA "-dev" +#define JANET_VERSION "1.41.3-dev" /* #define JANET_BUILD "local" */ From ea997d585bed1c07a4de4d22b13078e84a991f55 Mon Sep 17 00:00:00 2001 From: R Fisher Date: Fri, 20 Feb 2026 13:30:30 +0000 Subject: [PATCH 183/307] fix multicast on illumos (#1717) illumos, like BSD, expects IP_MULTICAST_TTL to be an unsigned char --- src/core/net.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/core/net.c b/src/core/net.c index fde7f04c..379baf97 100644 --- a/src/core/net.c +++ b/src/core/net.c @@ -1122,7 +1122,7 @@ JANET_CORE_FN(cfun_net_setsockopt, val.v_int = janet_getboolean(argv, 2); optlen = sizeof(val.v_int); } else if (st->type == JANET_NUMBER) { -#ifdef JANET_BSD +#if defined(JANET_BSD) || defined(JANET_ILLUMOS) int v_int = janet_getinteger(argv, 2); if (st->optname == IP_MULTICAST_TTL) { val.v_uchar = v_int; From 56b59985537a53059d6935593306c37d2f5f63ca Mon Sep 17 00:00:00 2001 From: Calvin Rose Date: Fri, 20 Feb 2026 07:35:18 -0600 Subject: [PATCH 184/307] Suspicious locking behavior with select. This looks like it could cause deadlocks with threaded channels (normal channels are unaffected, locking/unlocking is a no-op). --- src/core/ev.c | 2 ++ 1 file changed, 2 insertions(+) diff --git a/src/core/ev.c b/src/core/ev.c index b6d85376..98452e28 100644 --- a/src/core/ev.c +++ b/src/core/ev.c @@ -1249,6 +1249,7 @@ JANET_CORE_FN(cfun_channel_choice, chan_unlock_args(argv, i); return make_write_result(chan); } + janet_chan_unlock(chan); } else { /* Read */ JanetChannel *chan = janet_getchannel(argv, i); @@ -1264,6 +1265,7 @@ JANET_CORE_FN(cfun_channel_choice, chan_unlock_args(argv, i); return make_read_result(chan, item); } + janet_chan_unlock(chan); } } From 08e40304879da10d035be2db1ab0da94a3f0dcd9 Mon Sep 17 00:00:00 2001 From: Calvin Rose Date: Fri, 20 Feb 2026 08:00:36 -0600 Subject: [PATCH 185/307] Add builders for issue #1716 --- .github/workflows/release.yml | 2 +- .github/workflows/test.yml | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml index e40422f7..c5389e91 100644 --- a/.github/workflows/release.yml +++ b/.github/workflows/release.yml @@ -46,7 +46,7 @@ jobs: runs-on: ${{ matrix.os }} strategy: matrix: - os: [ macos-latest ] + os: [ macos-latest, macos-15-intel ] steps: - name: Checkout the repository uses: actions/checkout@master diff --git a/.github/workflows/test.yml b/.github/workflows/test.yml index 823f807c..b2669786 100644 --- a/.github/workflows/test.yml +++ b/.github/workflows/test.yml @@ -12,7 +12,7 @@ jobs: runs-on: ${{ matrix.os }} strategy: matrix: - os: [ ubuntu-latest, macos-latest, macos-14 ] + os: [ ubuntu-latest, macos-latest, macos-14, macos-15-intel ] steps: - name: Checkout the repository uses: actions/checkout@master From e61194a8d9da80eb0fc28b677e0053c41a544234 Mon Sep 17 00:00:00 2001 From: Calvin Rose Date: Fri, 20 Feb 2026 08:17:25 -0600 Subject: [PATCH 186/307] Remove older extra channel unlocks. --- src/core/ev.c | 18 ------------------ 1 file changed, 18 deletions(-) diff --git a/src/core/ev.c b/src/core/ev.c index 98452e28..79b9c02c 100644 --- a/src/core/ev.c +++ b/src/core/ev.c @@ -1199,20 +1199,6 @@ JANET_CORE_FN(cfun_channel_pop, janet_await(); } -static void chan_unlock_args(const Janet *argv, int32_t n) { - for (int32_t i = 0; i < n; i++) { - int32_t len; - const Janet *data; - JanetChannel *chan; - if (janet_indexed_view(argv[i], &data, &len) && len == 2) { - chan = janet_getchannel(data, 0); - } else { - chan = janet_getchannel(argv, i); - } - janet_chan_unlock(chan); - } -} - JANET_CORE_FN(cfun_channel_choice, "(ev/select & clauses)", "Block until the first of several channel operations occur. Returns a " @@ -1241,12 +1227,10 @@ JANET_CORE_FN(cfun_channel_choice, janet_chan_lock(chan); if (chan->closed) { janet_chan_unlock(chan); - chan_unlock_args(argv, i); return make_close_result(chan); } if (janet_q_count(&chan->items) < chan->limit) { janet_channel_push_with_lock(chan, data[1], 1); - chan_unlock_args(argv, i); return make_write_result(chan); } janet_chan_unlock(chan); @@ -1256,13 +1240,11 @@ JANET_CORE_FN(cfun_channel_choice, janet_chan_lock(chan); if (chan->closed) { janet_chan_unlock(chan); - chan_unlock_args(argv, i); return make_close_result(chan); } if (chan->items.head != chan->items.tail) { Janet item; janet_channel_pop_with_lock(chan, &item, 1); - chan_unlock_args(argv, i); return make_read_result(chan, item); } janet_chan_unlock(chan); From ca9ffaa5bb7ae19e158c555eb7a2dfc6c8bd66b2 Mon Sep 17 00:00:00 2001 From: Calvin Rose Date: Fri, 20 Feb 2026 14:45:32 -0600 Subject: [PATCH 187/307] Avoid memory leak when canceling fibers with threaded channels. Objects in channels are sent as messages that need to be freed by the consumer. However, in certain cases, no consumer is available and the messages were being discarded without properly being freed. This should also fix `-fsanitize=address` on GCC and CLANG with the default test suite. --- src/core/ev.c | 26 ++++++++++++++++++-------- 1 file changed, 18 insertions(+), 8 deletions(-) diff --git a/src/core/ev.c b/src/core/ev.c index 79b9c02c..19d4b39a 100644 --- a/src/core/ev.c +++ b/src/core/ev.c @@ -524,9 +524,9 @@ static void janet_schedule_general(JanetFiber *fiber, Janet value, JanetSignal s fiber->gc.flags |= JANET_FIBER_FLAG_ROOT; if (sig == JANET_SIGNAL_ERROR) fiber->gc.flags |= JANET_FIBER_EV_FLAG_CANCELED; if (soon) { - janet_q_push_head(&janet_vm.spawn, &t, sizeof(t)); + janet_assert(!janet_q_push_head(&janet_vm.spawn, &t, sizeof(t)), "schedule queue overflow"); } else { - janet_q_push(&janet_vm.spawn, &t, sizeof(t)); + janet_assert(!janet_q_push(&janet_vm.spawn, &t, sizeof(t)), "schedule queue overflow"); } } @@ -959,11 +959,12 @@ static void janet_thread_chan_cb(JanetEVGenericMessage msg) { janet_schedule(fiber, janet_wrap_nil()); } } else if (mode != JANET_CP_MODE_CLOSE) { - /* Fiber has already been cancelled or resumed. */ + /* Fiber has already been canceled or resumed. */ /* Resend event to another waiting thread, depending on mode */ int is_read = (mode == JANET_CP_MODE_CHOICE_READ) || (mode == JANET_CP_MODE_READ); if (is_read) { JanetChannelPending reader; + int sent = 0; while (!janet_q_pop(&channel->read_pending, &reader, sizeof(reader))) { JanetVM *vm = reader.thread; if (!vm) continue; @@ -974,8 +975,12 @@ static void janet_thread_chan_cb(JanetEVGenericMessage msg) { msg.argp = channel; msg.argj = x; janet_ev_post_event(vm, janet_thread_chan_cb, msg); + sent = 1; break; } + if (!sent) { + janet_chan_unpack(channel, &x, 1); + } } else { JanetChannelPending writer; while (!janet_q_pop(&channel->write_pending, &writer, sizeof(writer))) { @@ -1001,14 +1006,14 @@ static void janet_thread_chan_cb(JanetEVGenericMessage msg) { static int janet_channel_push_with_lock(JanetChannel *channel, Janet x, int mode) { JanetChannelPending reader; int is_empty; - if (janet_chan_pack(channel, &x)) { - janet_chan_unlock(channel); - janet_panicf("failed to pack value for channel: %v", x); - } if (channel->closed) { janet_chan_unlock(channel); janet_panic("cannot write to closed channel"); } + if (janet_chan_pack(channel, &x)) { + janet_chan_unlock(channel); + janet_panicf("failed to pack value for channel: %v", x); + } int is_threaded = janet_chan_is_threaded(channel); if (is_threaded) { /* don't dereference fiber from another thread */ @@ -1021,6 +1026,7 @@ static int janet_channel_push_with_lock(JanetChannel *channel, Janet x, int mode if (is_empty) { /* No pending reader */ if (janet_q_push(&channel->items, &x, sizeof(Janet))) { + janet_chan_unpack(channel, &x, 1); janet_chan_unlock(channel); janet_panicf("channel overflow: %v", x); } else if (janet_q_count(&channel->items) > channel->limit) { @@ -1054,6 +1060,9 @@ static int janet_channel_push_with_lock(JanetChannel *channel, Janet x, int mode msg.argj = x; if (vm) { janet_ev_post_event(vm, janet_thread_chan_cb, msg); + } else { + /* If no vm to send to, we must clean up (unpack) the packed payload to avoid leak */ + janet_chan_unpack(channel, &x, 1); } } else { if (reader.mode == JANET_CP_MODE_CHOICE_READ) { @@ -1458,11 +1467,12 @@ static void *janet_chanat_unmarshal(JanetMarshalContext *ctx) { int32_t limit = janet_unmarshal_int(ctx); int32_t count = janet_unmarshal_int(ctx); if (count < 0) janet_panic("invalid negative channel count"); + if (count > limit) janet_panic("invalid channel count"); janet_chan_init(abst, limit, 0); abst->closed = !!is_closed; for (int32_t i = 0; i < count; i++) { Janet item = janet_unmarshal_janet(ctx); - janet_q_push(&abst->items, &item, sizeof(item)); + janet_assert(!janet_q_push(&abst->items, &item, sizeof(item)), "bad unmarshal channel"); } return abst; } From 403b2c704a51049f4d5802c7f6b934d52de10182 Mon Sep 17 00:00:00 2001 From: Calvin Rose Date: Fri, 20 Feb 2026 18:06:18 -0600 Subject: [PATCH 188/307] Add sanitizer test to github actions and sr.ht builds. This will run both clang and gcc sanitizers as part of ordinary testing. --- .builds/freebsd.yml | 6 ++++++ .github/workflows/test.yml | 14 ++++++++++++++ 2 files changed, 20 insertions(+) diff --git a/.builds/freebsd.yml b/.builds/freebsd.yml index a52fe10e..70febddf 100644 --- a/.builds/freebsd.yml +++ b/.builds/freebsd.yml @@ -10,3 +10,9 @@ tasks: gmake test sudo gmake install sudo gmake uninstall +- build-sanitizers: | + cd janet + CFLAGS="-g -O2 -fsanitize=address,undefined" gmake + gmake test + sudo gmake install + sudo gmake uninstall diff --git a/.github/workflows/test.yml b/.github/workflows/test.yml index b2669786..7a7dbde8 100644 --- a/.github/workflows/test.yml +++ b/.github/workflows/test.yml @@ -21,6 +21,20 @@ jobs: - name: Test the project run: make test + test-posix-sanitizers: + name: Build and test on POSIX systems with sanitizers turned on + runs-on: ${{ matrix.os }} + strategy: + matrix: + os: [ ubuntu-latest ] + steps: + - name: Checkout the repository + uses: actions/checkout@master + - name: Compile the project + run: make clean && CFLAGS="-g -O2 -fsanitize=address,undefined" make + - name: Test the project + run: make test + test-windows: name: Build and test on Windows strategy: From 2602bec0176504e05c956a8ac7675067acd8c231 Mon Sep 17 00:00:00 2001 From: Calvin Rose Date: Sat, 21 Feb 2026 08:17:12 -0600 Subject: [PATCH 189/307] Check stderr for redirection before turning on/off color. --- src/boot/boot.janet | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/boot/boot.janet b/src/boot/boot.janet index 4d3ce6bb..370a774f 100644 --- a/src/boot/boot.janet +++ b/src/boot/boot.janet @@ -3302,7 +3302,6 @@ [&opt env local] (env-walk keyword? env local)) - (defdyn *doc-width* "Width in columns to print documentation printed with `doc-format`.") @@ -4761,7 +4760,8 @@ (apply-color (and (not (getenv-alias "NO_COLOR")) - (os/isatty stdout))) + (os/isatty stdout) + (os/isatty stderr))) (defn- get-lint-level [i] From 019829fdf9ffc151d54ce34cfc88476f029588ff Mon Sep 17 00:00:00 2001 From: sogaiu <33044872+sogaiu@users.noreply.github.com> Date: Thu, 26 Feb 2026 08:51:54 +0900 Subject: [PATCH 190/307] Tweak a docstring and a comment (#1718) Co-authored-by: sogaiu <983021772@users.noreply.github.com> --- src/core/fiber.c | 4 ++-- src/core/util.c | 2 +- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/src/core/fiber.c b/src/core/fiber.c index 95f78493..1d12a59b 100644 --- a/src/core/fiber.c +++ b/src/core/fiber.c @@ -592,8 +592,8 @@ JANET_CORE_FN(cfun_fiber_status, "* :user(0-7) - the fiber is suspended by a user signal\n" "* :interrupted - the fiber was interrupted\n" "* :suspended - the fiber is waiting to be resumed by the scheduler\n" - "* :alive - the fiber is currently running and cannot be resumed\n" - "* :new - the fiber has just been created and not yet run") { + "* :new - the fiber has just been created and not yet run\n" + "* :alive - the fiber is currently running and cannot be resumed") { janet_fixarity(argc, 1); JanetFiber *fiber = janet_getfiber(argv, 0); uint32_t s = janet_fiber_status(fiber); diff --git a/src/core/util.c b/src/core/util.c index 723b64f2..c3d3e2d7 100644 --- a/src/core/util.c +++ b/src/core/util.c @@ -268,7 +268,7 @@ int32_t janet_kv_calchash(const JanetKV *kvs, int32_t len) { return (int32_t) hash; } -/* Calculate next power of 2. May overflow. If n is 0, +/* Calculate next power of 2. May overflow. If n < 0, * will return 0. */ int32_t janet_tablen(int32_t n) { if (n < 0) return 0; From 54fbd7607fd3a325999776c17359546c48c2a8c7 Mon Sep 17 00:00:00 2001 From: Brett <332554+brettatoms@users.noreply.github.com> Date: Sat, 28 Feb 2026 00:13:46 +0000 Subject: [PATCH 191/307] Fix GC collecting active fiber during nested janet_continue (#1720) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit janet_collect() marks janet_vm.root_fiber but not janet_vm.fiber. When janet_pcall (or janet_continue) is called from a C function, the inner fiber becomes janet_vm.fiber while root_fiber still points to the outer fiber. If GC triggers inside the inner fiber's execution, the inner fiber is not in any GC root set and can be collected — including its stack memory — while actively running. This also affects deeply nested cases: F1 -> C func -> janet_pcall -> F2 -> C func -> janet_pcall -> F3, where F2 is saved only in a C stack local (tstate.vm_fiber), invisible to GC. Fix: in janet_continue_no_check, root the fiber with janet_gcroot when this is a nested call (root_fiber already set). Each nesting level roots its own fiber, handling arbitrary depth. Top-level calls (event loop, REPL) skip the root/unroot entirely since root_fiber is NULL. Add test/test-gc-pcall.c: standalone C test covering both single and deep nesting cases. Co-authored-by: Brett Adams --- src/core/vm.c | 12 ++++ test/test-gc-pcall.c | 143 +++++++++++++++++++++++++++++++++++++++++++ 2 files changed, 155 insertions(+) create mode 100644 test/test-gc-pcall.c diff --git a/src/core/vm.c b/src/core/vm.c index 02ee3d10..162defd7 100644 --- a/src/core/vm.c +++ b/src/core/vm.c @@ -1559,6 +1559,15 @@ static JanetSignal janet_continue_no_check(JanetFiber *fiber, Janet in, Janet *o } } + /* If this is a nested continue (root_fiber already set), root the fiber + * so it survives GC. janet_collect only marks root_fiber, so without + * this a nested fiber (e.g., from janet_pcall in a C function) would be + * invisible to GC and could be collected while actively running. */ + int fiber_rooted = (janet_vm.root_fiber != NULL); + if (fiber_rooted) { + janet_gcroot(janet_wrap_fiber(fiber)); + } + /* Save global state */ JanetTryState tstate; JanetSignal sig = janet_try(&tstate); @@ -1574,6 +1583,9 @@ static JanetSignal janet_continue_no_check(JanetFiber *fiber, Janet in, Janet *o if (janet_vm.root_fiber == fiber) janet_vm.root_fiber = NULL; janet_fiber_set_status(fiber, sig); janet_restore(&tstate); + if (fiber_rooted) { + janet_gcunroot(janet_wrap_fiber(fiber)); + } fiber->last_value = tstate.payload; *out = tstate.payload; diff --git a/test/test-gc-pcall.c b/test/test-gc-pcall.c new file mode 100644 index 00000000..fc37b8ba --- /dev/null +++ b/test/test-gc-pcall.c @@ -0,0 +1,143 @@ +/* + * Test that GC does not collect fibers during janet_pcall. + * + * Bug: janet_collect() marks janet_vm.root_fiber but not janet_vm.fiber. + * When janet_pcall is called from a C function, the inner fiber becomes + * janet_vm.fiber while root_fiber still points to the outer fiber. If GC + * triggers inside the inner fiber's execution, the inner fiber is not in + * any GC root set and can be collected — including its stack memory — + * while it is actively running. + * + * Two tests: + * 1. Single nesting: F1 -> C func -> janet_pcall -> F2 + * F2 is not marked (it's janet_vm.fiber but not root_fiber) + * 2. Deep nesting: F1 -> C func -> janet_pcall -> F2 -> C func -> janet_pcall -> F3 + * F2 is not marked (saved only in a C stack local tstate.vm_fiber) + * + * Build (after building janet): + * cc -o build/test-gc-pcall test/test-gc-pcall.c \ + * -Isrc/include -Isrc/conf build/libjanet.a -lm -lpthread -ldl + * + * Run: + * ./build/test-gc-pcall + */ + +#include "janet.h" +#include + +/* C function that calls a Janet callback via janet_pcall. */ +static Janet cfun_call_via_pcall(int32_t argc, Janet *argv) { + janet_fixarity(argc, 1); + JanetFunction *fn = janet_getfunction(argv, 0); + + Janet result; + JanetFiber *fiber = NULL; + JanetSignal sig = janet_pcall(fn, 0, NULL, &result, &fiber); + + if (sig != JANET_SIGNAL_OK) { + janet_panicv(result); + } + + return result; +} + +static int run_test(JanetTable *env, const char *name, const char *source) { + printf(" %s... ", name); + fflush(stdout); + Janet result; + int status = janet_dostring(env, source, name, &result); + if (status != 0) { + printf("FAIL (crashed or errored)\n"); + return 1; + } + printf("PASS\n"); + return 0; +} + +/* Test 1: Single nesting. + * F1 -> cfun_call_via_pcall -> janet_pcall -> F2 + * F2 is janet_vm.fiber but not root_fiber, so GC can collect it. + * + * All allocations are done in Janet code so GC checks trigger in the + * VM loop (janet_gcalloc does NOT call janet_collect — only the VM's + * vm_checkgc_next does). */ +static const char test_single[] = + "(gcsetinterval 1024)\n" + "(def cb\n" + " (do\n" + " (def captured @{:key \"value\" :nested @[1 2 3 4 5]})\n" + " (fn []\n" + " (var result nil)\n" + " (for i 0 500\n" + " (def t @{:i i :s (string \"iter-\" i) :arr @[i (+ i 1) (+ i 2)]})\n" + " (set result (get captured :key)))\n" + " result)))\n" + "(for round 0 200\n" + " (def result (call-via-pcall cb))\n" + " (assert (= result \"value\")\n" + " (string \"round \" round \": expected 'value', got \" (describe result))))\n"; + +/* Test 2: Deep nesting. + * F1 -> cfun_call_via_pcall -> janet_pcall -> F2 -> cfun_call_via_pcall -> janet_pcall -> F3 + * F2 is saved only in C stack local tstate.vm_fiber, invisible to GC. + * F2's stack data can be freed if F2 is collected during F3's execution. + * + * The inner callback allocates in Janet code (not C) to ensure the + * VM loop triggers GC checks during F3's execution. */ +static const char test_deep[] = + "(gcsetinterval 1024)\n" + "(def inner-cb\n" + " (do\n" + " (def captured @{:key \"deep\" :nested @[10 20 30]})\n" + " (fn []\n" + " (var result nil)\n" + " (for i 0 500\n" + " (def t @{:i i :s (string \"iter-\" i) :arr @[i (+ i 1) (+ i 2)]})\n" + " (set result (get captured :key)))\n" + " result)))\n" + "\n" + "(def outer-cb\n" + " (do\n" + " (def state @{:count 0 :data @[\"a\" \"b\" \"c\" \"d\" \"e\"]})\n" + " (fn []\n" + " # This runs on F2. Calling call-via-pcall here creates F3.\n" + " # F2 becomes unreachable: it's not root_fiber (that's F1)\n" + " # and it's no longer janet_vm.fiber (that's now F3).\n" + " (def inner-result (call-via-pcall inner-cb))\n" + " # If F2 was collected during F3's execution, accessing\n" + " # state here reads freed memory.\n" + " (put state :count (+ (state :count) 1))\n" + " (string inner-result \"-\" (state :count)))))\n" + "\n" + "(for round 0 200\n" + " (def result (call-via-pcall outer-cb))\n" + " (def expected (string \"deep-\" (+ round 1)))\n" + " (assert (= result expected)\n" + " (string \"round \" round \": expected '\" expected \"', got '\" (describe result) \"'\")))\n"; + +int main(int argc, char **argv) { + (void)argc; + (void)argv; + int failures = 0; + + janet_init(); + + JanetTable *env = janet_core_env(NULL); + + janet_def(env, "call-via-pcall", + janet_wrap_cfunction(cfun_call_via_pcall), + "Call a function via janet_pcall from C."); + + printf("Testing janet_pcall GC safety:\n"); + failures += run_test(env, "single-nesting", test_single); + failures += run_test(env, "deep-nesting", test_deep); + + janet_deinit(); + + if (failures > 0) { + printf("\n%d test(s) FAILED\n", failures); + return 1; + } + printf("\nAll tests passed.\n"); + return 0; +} From 5fec2aa9dfc109e8b95ebfbb61d59018d5739592 Mon Sep 17 00:00:00 2001 From: Calvin Rose Date: Fri, 27 Feb 2026 18:18:10 -0600 Subject: [PATCH 192/307] Move some files around code more defensively for mitigation. --- src/core/vm.c | 2 +- test/{ => c}/test-gc-pcall.c | 0 2 files changed, 1 insertion(+), 1 deletion(-) rename test/{ => c}/test-gc-pcall.c (100%) diff --git a/src/core/vm.c b/src/core/vm.c index 162defd7..1ae82b48 100644 --- a/src/core/vm.c +++ b/src/core/vm.c @@ -1127,11 +1127,11 @@ static JanetSignal run_vm(JanetFiber *fiber, Janet in) { } fiber->child = child; JanetSignal sig = janet_continue_no_check(child, stack[C], &retreg); + stack = fiber->data + fiber->frame; if (sig != JANET_SIGNAL_OK && !(child->flags & (1 << sig))) { vm_return(sig, retreg); } fiber->child = NULL; - stack = fiber->data + fiber->frame; stack[A] = retreg; vm_checkgc_pcnext(); } diff --git a/test/test-gc-pcall.c b/test/c/test-gc-pcall.c similarity index 100% rename from test/test-gc-pcall.c rename to test/c/test-gc-pcall.c From 32d75c9e498e729e5c02433b8ec7084ccf2bbb95 Mon Sep 17 00:00:00 2001 From: Calvin Rose Date: Sun, 1 Mar 2026 10:27:32 -0600 Subject: [PATCH 193/307] Dup io file descriptors when marshalling closable files. For unclosable files, no need to dup, but for closable files we can get a resource leak. Valgrind and similar tools won't catch this but IO will unexpectedly start going to the wrong descriptor if a file was transferred to a new thread, closed, and then a new file was created. --- src/boot/boot.janet | 5 ++--- src/core/io.c | 32 +++++++++++++++++++++++++------- src/include/janet.h | 1 + test/suite-ev2.janet | 17 +++++++++++++++++ 4 files changed, 45 insertions(+), 10 deletions(-) diff --git a/src/boot/boot.janet b/src/boot/boot.janet index 370a774f..8d624e52 100644 --- a/src/boot/boot.janet +++ b/src/boot/boot.janet @@ -4014,7 +4014,7 @@ "handler not supported for :datagram servers") (def s (net/listen host port type no-reuse)) (if handler - (ev/go (fn [] (net/accept-loop s handler)))) + (ev/go (fn :net/server-handler [] (net/accept-loop s handler)))) s)) ### @@ -4670,8 +4670,7 @@ (defn- run-main [env subargs arg] - (when-let [entry (in env 'main) - main (or (get entry :value) (in (get entry :ref) 0))] + (when-let [main (module/value env 'main)] (def guard (if (get env :debug) :ydt :y)) (defn wrap-main [&] (main ;subargs)) diff --git a/src/core/io.c b/src/core/io.c index 43996526..274f9eeb 100644 --- a/src/core/io.c +++ b/src/core/io.c @@ -109,10 +109,11 @@ static int32_t checkflags(const uint8_t *str) { return flags; } -static void *makef(FILE *f, int32_t flags) { +static void *makef(FILE *f, int32_t flags, size_t bufsize) { JanetFile *iof = (JanetFile *) janet_abstract(&janet_file_type, sizeof(JanetFile)); iof->file = f; iof->flags = flags; + iof->vbufsize = bufsize; #if !(defined(JANET_WINDOWS) || defined(JANET_PLAN9)) /* While we would like fopen to set cloexec by default (like O_CLOEXEC) with the e flag, that is * not standard. */ @@ -164,6 +165,7 @@ JANET_CORE_FN(cfun_io_fopen, flags = JANET_FILE_READ; } FILE *f = fopen((const char *)fname, (const char *)fmode); + size_t bufsize = BUFSIZ; if (f != NULL) { #if !(defined(JANET_WINDOWS) || defined(JANET_PLAN9)) struct stat st; @@ -173,7 +175,7 @@ JANET_CORE_FN(cfun_io_fopen, janet_panicf("cannot open directory: %s", fname); } #endif - size_t bufsize = janet_optsize(argv, argc, 2, BUFSIZ); + bufsize = janet_optsize(argv, argc, 2, BUFSIZ); if (bufsize != BUFSIZ) { int result = setvbuf(f, NULL, bufsize ? _IOFBF : _IONBF, bufsize); if (result) { @@ -181,7 +183,7 @@ JANET_CORE_FN(cfun_io_fopen, } } } - return f ? janet_makefile(f, flags) + return f ? janet_wrap_abstract(makef(f, flags, bufsize)) : (flags & JANET_FILE_NONIL) ? (janet_panicf("failed to open file %s: %s", fname, janet_strerror(errno)), janet_wrap_nil()) : janet_wrap_nil(); } @@ -410,12 +412,23 @@ static void io_file_marshal(void *p, JanetMarshalContext *ctx) { JanetFile *iof = (JanetFile *)p; if (ctx->flags & JANET_MARSHAL_UNSAFE) { janet_marshal_abstract(ctx, p); + int fno = -1; #ifdef JANET_WINDOWS - janet_marshal_int(ctx, _fileno(iof->file)); + if (iof->flags & JANET_FILE_NOT_CLOSEABLE) { + fno = _fileno(iof->file); + } else { + fno = _dup(_fileno(iof->file)); + } #else - janet_marshal_int(ctx, fileno(iof->file)); + if (iof->flags & JANET_FILE_NOT_CLOSEABLE) { + fno = fileno(iof->file); + } else { + fno = dup(fileno(iof->file)); + } #endif + janet_marshal_int(ctx, fno); janet_marshal_int(ctx, iof->flags); + janet_marshal_size(ctx, iof->vbufsize); } else { janet_panic("cannot marshal file in safe mode"); } @@ -444,6 +457,11 @@ static void *io_file_unmarshal(JanetMarshalContext *ctx) { } else { iof->flags = flags; } + iof->vbufsize = janet_unmarshal_size(ctx); + if (iof->vbufsize != BUFSIZ) { + int result = setvbuf(iof->file, NULL, iof->vbufsize ? _IOFBF : _IONBF, iof->vbufsize); + janet_assert(!result, "unmarshal setvbuf"); + } return iof; } else { janet_panic("cannot unmarshal file in safe mode"); @@ -785,11 +803,11 @@ FILE *janet_getfile(const Janet *argv, int32_t n, int32_t *flags) { } JanetFile *janet_makejfile(FILE *f, int32_t flags) { - return makef(f, flags); + return makef(f, flags, BUFSIZ); } Janet janet_makefile(FILE *f, int32_t flags) { - return janet_wrap_abstract(makef(f, flags)); + return janet_wrap_abstract(makef(f, flags, BUFSIZ)); } JanetAbstract janet_checkfile(Janet j) { diff --git a/src/include/janet.h b/src/include/janet.h index e1a5c79a..ac2e3019 100644 --- a/src/include/janet.h +++ b/src/include/janet.h @@ -1281,6 +1281,7 @@ typedef struct JanetFile JanetFile; struct JanetFile { FILE *file; int32_t flags; + size_t vbufsize; }; /* For janet_try and janet_restore */ diff --git a/test/suite-ev2.janet b/test/suite-ev2.janet index c94a2d86..caa323a4 100644 --- a/test/suite-ev2.janet +++ b/test/suite-ev2.janet @@ -84,4 +84,21 @@ (assert-error "cannot schedule non-new fiber" (ev/go f)) +# IO file copying +(os/mkdir "tmp") +(def f-original (file/open "tmp/out.txt" :wb)) +(xprin f-original "hello\n") +(file/flush f-original) +(ev/do-thread + # Closes a COPY of the original file, otherwise we get a user-after-close file descriptor + (:close f-original)) +(def g-original (file/open "tmp/out2.txt" :wb)) +(xprin g-original "world1\n") +(xprin f-original "world2\n") +(:close f-original) +(xprin g-original "abc\n") +(:close g-original) +(assert (deep= @"hello\nworld2\n" (slurp "tmp/out.txt")) "file threading 1") +(assert (deep= @"world1\nabc\n" (slurp "tmp/out2.txt")) "file threading 2") + (end-suite) From e8f9c129352f793bb09bd57ba220f39d0a99d71a Mon Sep 17 00:00:00 2001 From: Calvin Rose Date: Sun, 1 Mar 2026 10:52:52 -0600 Subject: [PATCH 194/307] Fix regression where private main was not run. --- src/boot/boot.janet | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/boot/boot.janet b/src/boot/boot.janet index 8d624e52..72970bfc 100644 --- a/src/boot/boot.janet +++ b/src/boot/boot.janet @@ -4670,7 +4670,7 @@ (defn- run-main [env subargs arg] - (when-let [main (module/value env 'main)] + (when-let [main (module/value env 'main true)] (def guard (if (get env :debug) :ydt :y)) (defn wrap-main [&] (main ;subargs)) From 95f2e233c5944d55a4fccd725d423e5f149687f5 Mon Sep 17 00:00:00 2001 From: Calvin Rose Date: Sun, 1 Mar 2026 12:20:15 -0600 Subject: [PATCH 195/307] Try io.h on windows --- src/core/io.c | 1 + 1 file changed, 1 insertion(+) diff --git a/src/core/io.c b/src/core/io.c index 274f9eeb..cb731188 100644 --- a/src/core/io.c +++ b/src/core/io.c @@ -43,6 +43,7 @@ static void *io_file_unmarshal(JanetMarshalContext *ctx); static Janet io_file_next(void *p, Janet key); #ifdef JANET_WINDOWS +#include #define ftell _ftelli64 #define fseek _fseeki64 #endif From 39f8cf207c6ca26925a2fc2c2f3e80728c3e9087 Mon Sep 17 00:00:00 2001 From: Calvin Rose Date: Sun, 1 Mar 2026 12:23:37 -0600 Subject: [PATCH 196/307] Cast for warning on mingw DWORD printing. --- src/core/os.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/core/os.c b/src/core/os.c index 21510575..bc7b7bd6 100644 --- a/src/core/os.c +++ b/src/core/os.c @@ -1332,7 +1332,7 @@ static Janet os_execute_impl(int32_t argc, Janet *argv, JanetExecuteMode mode) { msgbuf, sizeof(msgbuf), NULL); - if (!*msgbuf) snprintf(msgbuf, sizeof(msgbuf), "%" PRIu32, cp_error_code); + if (!*msgbuf) snprintf(msgbuf, sizeof(msgbuf), "%" PRIu32, (uint32_t) cp_error_code); char *c = msgbuf; while (*c) { if (*c == '\n' || *c == '\r') { From 7fc12ff167c16f6929de6214e2412eb5fe1e6cc2 Mon Sep 17 00:00:00 2001 From: Calvin Rose Date: Sun, 1 Mar 2026 12:23:37 -0600 Subject: [PATCH 197/307] Use ConnectEx for non-blocking connect on windows when available. Still fallback to blocking connect with WSAConnect when ConnectEx is not available or applicable, but ConnectEx is preferred and recommended by Microsoft. Also make some changes to our use of OVERLAPPED in various places in the ev code, replacing all uses with JanetOverlapped. This also let's us avoid reusing internal fields for OVERLAPPED which may or may not be used in various places. --- .github/workflows/test.yml | 21 +++++++++ build_win.bat | 12 ++++- src/core/ev.c | 36 +++++++-------- src/core/filewatch.c | 4 +- src/core/net.c | 93 ++++++++++++++++++++++++++++++++------ src/core/state.h | 2 + src/core/util.h | 15 ++++++ test/suite-os.janet | 9 ++-- 8 files changed, 152 insertions(+), 40 deletions(-) diff --git a/.github/workflows/test.yml b/.github/workflows/test.yml index 7a7dbde8..8e2be6ad 100644 --- a/.github/workflows/test.yml +++ b/.github/workflows/test.yml @@ -56,6 +56,27 @@ jobs: shell: cmd run: build_win dist + test-windows-sanitizers: + name: Build and test on Windows with sanitizers + strategy: + matrix: + os: [ windows-latest ] + runs-on: ${{ matrix.os }} + steps: + - name: Checkout the repository + uses: actions/checkout@master + - name: Setup MSVC + uses: ilammy/msvc-dev-cmd@v1 + - name: Build the project + shell: cmd + run: set SANITIZE=1 & build_win + - name: Test the project + shell: cmd + run: set VERBOSE=1 & build_win test + - name: Test installer build + shell: cmd + run: build_win dist + test-windows-min: name: Build and test on Windows Minimal build strategy: diff --git a/build_win.bat b/build_win.bat index 6883b1bc..e0cfa6eb 100644 --- a/build_win.bat +++ b/build_win.bat @@ -23,7 +23,17 @@ @rem set JANET_COMPILE=cl /nologo /Isrc\include /Isrc\conf /c /O2 /W3 /D_CRT_SECURE_NO_WARNINGS /MD /fsanitize=address /Zi @rem set JANET_LINK=link /nologo clang_rt.asan_dynamic-x86_64.lib clang_rt.asan_dynamic_runtime_thunk-x86_64.lib -@set JANET_COMPILE=cl /nologo /Isrc\include /Isrc\conf /c /O2 /W3 /D_CRT_SECURE_NO_WARNINGS /MD +if DEFINED CLANG ( + @set COMPILER=clang-cl.exe +) else ( + @set COMPILER=cl.exe +) +if DEFINED SANITIZE ( + @set "SANITIZERS=/fsanitize=address" +) else ( + @set "SANITIZERS=" +) +@set JANET_COMPILE=%COMPILER% /nologo /Isrc\include /Isrc\conf /c /O2 /W3 /D_CRT_SECURE_NO_WARNINGS /MD %SANITIZERS% @set JANET_LINK=link /nologo @set JANET_LINK_STATIC=lib /nologo diff --git a/src/core/ev.c b/src/core/ev.c index 19d4b39a..6c71a4f2 100644 --- a/src/core/ev.c +++ b/src/core/ev.c @@ -1712,20 +1712,20 @@ void janet_loop1_impl(int has_timeout, JanetTimestamp to) { janet_free(response); } else { /* Normal event */ + JanetOverlapped *jo = (JanetOverlapped *) overlapped; JanetStream *stream = (JanetStream *) completionKey; JanetFiber *fiber = NULL; - if (stream->read_fiber && stream->read_fiber->ev_state == overlapped) { + if (stream->read_fiber && stream->read_fiber->ev_state == jo) { fiber = stream->read_fiber; - } else if (stream->write_fiber && stream->write_fiber->ev_state == overlapped) { + } else if (stream->write_fiber && stream->write_fiber->ev_state == jo) { fiber = stream->write_fiber; } if (fiber != NULL) { fiber->flags &= ~JANET_FIBER_EV_FLAG_IN_FLIGHT; - /* System is done with this, we can reused this data */ - overlapped->InternalHigh = (ULONG_PTR) num_bytes_transferred; + jo->bytes_transfered = (ULONG_PTR) num_bytes_transferred; fiber->ev_callback(fiber, result ? JANET_ASYNC_EVENT_COMPLETE : JANET_ASYNC_EVENT_FAILED); } else { - janet_free((void *) overlapped); + janet_free((void *) jo); janet_ev_dec_refcount(); } janet_stream_checktoclose(stream); @@ -2437,7 +2437,7 @@ typedef enum { typedef struct { #ifdef JANET_WINDOWS - OVERLAPPED overlapped; + JanetOverlapped overlapped; DWORD flags; #ifdef JANET_NET WSABUF wbuf; @@ -2472,7 +2472,7 @@ void ev_callback_read(JanetFiber *fiber, JanetAsyncEvent event) { case JANET_ASYNC_EVENT_FAILED: case JANET_ASYNC_EVENT_COMPLETE: { /* Called when read finished */ - uint32_t ev_bytes = (uint32_t) state->overlapped.InternalHigh; + uint32_t ev_bytes = (uint32_t) state->overlapped.bytes_transfered; state->bytes_read += ev_bytes; if (state->bytes_read == 0 && (state->mode != JANET_ASYNC_READMODE_RECVFROM)) { janet_schedule(fiber, janet_wrap_nil()); @@ -2504,7 +2504,7 @@ void ev_callback_read(JanetFiber *fiber, JanetAsyncEvent event) { /* fallthrough */ case JANET_ASYNC_EVENT_INIT: { int32_t chunk_size = state->bytes_left > JANET_EV_CHUNKSIZE ? JANET_EV_CHUNKSIZE : state->bytes_left; - memset(&(state->overlapped), 0, sizeof(OVERLAPPED)); + memset(&(state->overlapped), 0, sizeof(JanetOverlapped)); int status; #ifdef JANET_NET if (state->mode == JANET_ASYNC_READMODE_RECVFROM) { @@ -2512,7 +2512,7 @@ void ev_callback_read(JanetFiber *fiber, JanetAsyncEvent event) { state->wbuf.buf = (char *) state->chunk_buf; state->fromlen = sizeof(state->from); status = WSARecvFrom((SOCKET) stream->handle, &state->wbuf, 1, - NULL, &state->flags, &state->from, &state->fromlen, &state->overlapped, NULL); + NULL, &state->flags, &state->from, &state->fromlen, &state->overlapped.as.wsaoverlapped, NULL); if (status && (WSA_IO_PENDING != WSAGetLastError())) { janet_cancel(fiber, janet_ev_lasterr()); janet_async_end(fiber); @@ -2523,9 +2523,9 @@ void ev_callback_read(JanetFiber *fiber, JanetAsyncEvent event) { { /* Some handles (not all) read from the offset in lpOverlapped * if its not set before calling `ReadFile` these streams will always read from offset 0 */ - state->overlapped.Offset = (DWORD) state->bytes_read; + state->overlapped.as.overlapped.Offset = (DWORD) state->bytes_read; - status = ReadFile(stream->handle, state->chunk_buf, chunk_size, NULL, &state->overlapped); + status = ReadFile(stream->handle, state->chunk_buf, chunk_size, NULL, &state->overlapped.as.overlapped); if (!status && (ERROR_IO_PENDING != GetLastError())) { if (GetLastError() == ERROR_BROKEN_PIPE) { if (state->bytes_read) { @@ -2681,7 +2681,7 @@ typedef enum { typedef struct { #ifdef JANET_WINDOWS - OVERLAPPED overlapped; + JanetOverlapped overlapped; DWORD flags; #ifdef JANET_NET WSABUF wbuf; @@ -2722,7 +2722,7 @@ void ev_callback_write(JanetFiber *fiber, JanetAsyncEvent event) { case JANET_ASYNC_EVENT_FAILED: case JANET_ASYNC_EVENT_COMPLETE: { /* Called when write finished */ - uint32_t ev_bytes = (uint32_t) state->overlapped.InternalHigh; + uint32_t ev_bytes = (uint32_t) state->overlapped.bytes_transfered; if (ev_bytes == 0 && (state->mode != JANET_ASYNC_WRITEMODE_SENDTO)) { janet_cancel(fiber, janet_cstringv("disconnect")); janet_async_end(fiber); @@ -2751,7 +2751,7 @@ void ev_callback_write(JanetFiber *fiber, JanetAsyncEvent event) { bytes = state->src.str; len = janet_string_length(bytes); } - memset(&(state->overlapped), 0, sizeof(WSAOVERLAPPED)); + memset(&(state->overlapped), 0, sizeof(JanetOverlapped)); int status; #ifdef JANET_NET @@ -2761,7 +2761,7 @@ void ev_callback_write(JanetFiber *fiber, JanetAsyncEvent event) { state->wbuf.len = len; const struct sockaddr *to = state->dest_abst; int tolen = (int) janet_abstract_size((void *) to); - status = WSASendTo(sock, &state->wbuf, 1, NULL, state->flags, to, tolen, &state->overlapped, NULL); + status = WSASendTo(sock, &state->wbuf, 1, NULL, state->flags, to, tolen, &state->overlapped.as.wsaoverlapped, NULL); if (status) { if (WSA_IO_PENDING == WSAGetLastError()) { janet_async_in_flight(fiber); @@ -2784,9 +2784,9 @@ void ev_callback_write(JanetFiber *fiber, JanetAsyncEvent event) { * for more details see the lpOverlapped parameter in * https://docs.microsoft.com/en-us/windows/win32/api/fileapi/nf-fileapi-writefile */ - state->overlapped.Offset = (DWORD) 0xFFFFFFFF; - state->overlapped.OffsetHigh = (DWORD) 0xFFFFFFFF; - status = WriteFile(stream->handle, bytes, len, NULL, &state->overlapped); + state->overlapped.as.overlapped.Offset = (DWORD) 0xFFFFFFFF; + state->overlapped.as.overlapped.OffsetHigh = (DWORD) 0xFFFFFFFF; + status = WriteFile(stream->handle, bytes, len, NULL, &state->overlapped.as.overlapped); if (!status) { if (ERROR_IO_PENDING == GetLastError()) { janet_async_in_flight(fiber); diff --git a/src/core/filewatch.c b/src/core/filewatch.c index 8023271c..a5f3785c 100644 --- a/src/core/filewatch.c +++ b/src/core/filewatch.c @@ -326,7 +326,7 @@ static void janet_watcher_init(JanetWatcher *watcher, JanetChannel *channel, uin #define FILE_INFO_PADDING (4096 * 4) typedef struct { - OVERLAPPED overlapped; + JanetOverlapped overlapped; JanetStream *stream; JanetWatcher *watcher; JanetFiber *fiber; @@ -456,7 +456,7 @@ static void janet_watcher_add(JanetWatcher *watcher, const char *path, uint32_t Janet pathv = janet_wrap_string(ow->dir_path); ow->flags = flags | watcher->default_flags; ow->watcher = watcher; - ow->overlapped.hEvent = CreateEvent(NULL, FALSE, 0, NULL); /* Do we need this */ + ow->overlapped.as.overlapped.hEvent = CreateEvent(NULL, FALSE, 0, NULL); /* Do we need this */ Janet streamv = janet_wrap_pointer(ow); janet_table_put(watcher->watch_descriptors, pathv, streamv); if (watcher->is_watching) { diff --git a/src/core/net.c b/src/core/net.c index 379baf97..096e491d 100644 --- a/src/core/net.c +++ b/src/core/net.c @@ -140,6 +140,35 @@ static int net_get_address_family(Janet x) { } /* State machine for async connect */ +#ifdef JANET_WINDOWS + +typedef struct NetStateConnect { + /* Only used for ConnectEx */ + JanetOverlapped overlapped; +} NetStateConnect; + +static LPFN_CONNECTEX lazy_get_connectex(JSock sock) { + /* Get ConnectEx */ + if (janet_vm.connect_ex_loaded) { + return janet_vm.connect_ex; + } + GUID guid = WSAID_CONNECTEX; + LPFN_CONNECTEX connect_ex_ptr = NULL; + DWORD byte_len = 0; + int success = WSAIoctl(sock, SIO_GET_EXTENSION_FUNCTION_POINTER, + (void*)&guid, sizeof(guid), + (void*)&connect_ex_ptr, sizeof(connect_ex_ptr), + &byte_len, NULL, NULL); + if (success) { + janet_vm.connect_ex = connect_ex_ptr; + } else { + janet_vm.connect_ex = NULL; + } + janet_vm.connect_ex_loaded = 1; + return janet_vm.connect_ex; +} + +#endif void net_callback_connect(JanetFiber *fiber, JanetAsyncEvent event) { JanetStream *stream = fiber->ev_stream; @@ -159,15 +188,21 @@ void net_callback_connect(JanetFiber *fiber, JanetAsyncEvent event) { return; } #ifdef JANET_WINDOWS + /* We should be using ConnectEx here */ int res = 0; int size = sizeof(res); - int r = getsockopt((SOCKET)stream->handle, SOL_SOCKET, SO_ERROR, (char *)&res, &size); + int r = getsockopt((SOCKET)stream->handle, SOL_SOCKET, SO_CONNECT_TIME, (char *)&res, &size); + if (r == NO_ERROR && res == 0xFFFFFFFF) { + return; /* This apparently indicates we haven't yet gotten a connection */ + } + const int no_error = NO_ERROR; #else int res = 0; - socklen_t size = sizeof res; + socklen_t size = sizeof(res); int r = getsockopt(stream->handle, SOL_SOCKET, SO_ERROR, &res, &size); + const int no_error = 0; #endif - if (r == 0) { + if (r == no_error) { if (res == 0) { janet_schedule(fiber, janet_wrap_abstract(stream)); } else { @@ -181,8 +216,8 @@ void net_callback_connect(JanetFiber *fiber, JanetAsyncEvent event) { janet_async_end(fiber); } -static JANET_NO_RETURN void net_sched_connect(JanetStream *stream) { - janet_async_start(stream, JANET_ASYNC_LISTEN_WRITE, net_callback_connect, NULL); +static JANET_NO_RETURN void net_sched_connect(JanetStream *stream, void *state) { + janet_async_start(stream, JANET_ASYNC_LISTEN_WRITE, net_callback_connect, state); } /* State machine for accepting connections. */ @@ -190,7 +225,7 @@ static JANET_NO_RETURN void net_sched_connect(JanetStream *stream) { #ifdef JANET_WINDOWS typedef struct { - WSAOVERLAPPED overlapped; + JanetOverlapped overlapped; JanetFunction *function; JanetStream *lstream; JanetStream *astream; @@ -253,7 +288,7 @@ void net_callback_accept(JanetFiber *fiber, JanetAsyncEvent event) { JANET_NO_RETURN static void janet_sched_accept(JanetStream *stream, JanetFunction *fun) { Janet err; NetStateAccept *state = janet_malloc(sizeof(NetStateAccept)); - memset(&state->overlapped, 0, sizeof(WSAOVERLAPPED)); + memset(&state->overlapped, 0, sizeof(JanetOverlapped)); memset(&state->buf, 0, 1024); state->function = fun; state->lstream = stream; @@ -274,7 +309,7 @@ static int net_sched_accept_impl(NetStateAccept *state, JanetFiber *fiber, Janet JanetStream *astream = make_stream(asock, JANET_STREAM_READABLE | JANET_STREAM_WRITABLE); state->astream = astream; int socksize = sizeof(SOCKADDR_STORAGE) + 16; - if (FALSE == AcceptEx(lsock, asock, state->buf, 0, socksize, socksize, NULL, &state->overlapped)) { + if (FALSE == AcceptEx(lsock, asock, state->buf, 0, socksize, socksize, NULL, &state->overlapped.as.wsaoverlapped)) { int code = WSAGetLastError(); if (code == WSA_IO_PENDING) { /* indicates io is happening async */ @@ -572,11 +607,39 @@ JANET_CORE_FN(cfun_net_connect, /* Connect to socket */ #ifdef JANET_WINDOWS - int status = WSAConnect(sock, addr, addrlen, NULL, NULL, NULL, NULL); - int err = WSAGetLastError(); - freeaddrinfo(ai); - /* Set up the socket for non-blocking IO after connecting on windows by default */ - janet_net_socknoblock(sock); + int status = 0; + int err = 0; + LPFN_CONNECTEX connect_ex = NULL; + if (socktype == SOCK_STREAM && ((connect_ex = lazy_get_connectex(sock)))) { + /* Prefer ConnecEx as it works well with overlapped IO. */ + janet_net_socknoblock(sock); + NetStateConnect *state = janet_malloc(sizeof(NetStateConnect)); + memset(state, 0, sizeof(NetStateConnect)); + BOOL success = connect_ex(sock, addr, addrlen, NULL, 0, NULL, &state->overlapped.as.overlapped); + freeaddrinfo(ai); + if (success) { + /* Did not fail */ + } else { + int err = WSAGetLastError(); + if (err == ERROR_IO_PENDING) { + /* Did not actually fail yet */ + } else { + janet_free(state); + Janet lasterr = janet_ev_lasterr(); + janet_panicf("could not connect socket (ConnectEx): %V", lasterr); + } + } + + net_sched_connect(stream, state); + } else { + /* Default to blocking connect if ConnectEx not available */ + status = WSAConnect(sock, addr, addrlen, NULL, NULL, NULL, NULL); + err = WSAGetLastError(); + freeaddrinfo(ai); + /* Set up the socket for non-blocking IO after connecting on windows by default */ + janet_net_socknoblock(sock); + } + #else /* Set up the socket for non-blocking IO before connecting */ janet_net_socknoblock(sock); @@ -613,7 +676,7 @@ JANET_CORE_FN(cfun_net_connect, } } - net_sched_connect(stream); + net_sched_connect(stream, NULL); } JANET_CORE_FN(cfun_net_socket, @@ -1213,6 +1276,8 @@ void janet_net_init(void) { #ifdef JANET_WINDOWS WSADATA wsaData; janet_assert(!WSAStartup(MAKEWORD(2, 2), &wsaData), "could not start winsock"); + janet_vm.connect_ex_loaded = 0; + janet_vm.connect_ex = NULL; #endif } diff --git a/src/core/state.h b/src/core/state.h index a606eb76..737b2b49 100644 --- a/src/core/state.h +++ b/src/core/state.h @@ -182,6 +182,8 @@ struct JanetVM { JanetTable signal_handlers; #ifdef JANET_WINDOWS void **iocp; + void *connect_ex; /* MSWsock extension if available */ + int connect_ex_loaded; #elif defined(JANET_EV_EPOLL) pthread_attr_t new_thread_attr; JanetHandle selfpipe[2]; diff --git a/src/core/util.h b/src/core/util.h index 874ef7b0..7702b275 100644 --- a/src/core/util.h +++ b/src/core/util.h @@ -203,6 +203,21 @@ char *get_processed_name(const char *name); #define RETRY_EINTR(RC, CALL) do { (RC) = CALL; } while((RC) < 0 && errno == EINTR) #endif +#ifdef JANET_EV +#ifdef JANET_WINDOWS +#include +#include +#include +typedef struct { + union { + OVERLAPPED overlapped; + WSAOVERLAPPED wsaoverlapped; + } as; + uint32_t bytes_transfered; +} JanetOverlapped; +#endif +#endif + /* Initialize builtin libraries */ void janet_lib_io(JanetTable *env); void janet_lib_math(JanetTable *env); diff --git a/test/suite-os.janet b/test/suite-os.janet index 880de4ef..965dff1a 100644 --- a/test/suite-os.janet +++ b/test/suite-os.janet @@ -148,11 +148,10 @@ # os/execute with empty environment # pr #1686 -# native MinGW can't find system DLLs without PATH and so fails -(assert (= (if (and (= :mingw (os/which)) - (nil? (os/stat "C:\\windows\\system32\\wineboot.exe"))) - -1073741515 0) - (os/execute [;run janet "-e" "(+ 1 2 3)"] :pe {})) +# native MinGW can't find system DLLs without PATH, SystemRoot, etc. and so fails +# Also fails for address sanitizer builds on windows. +(def result (os/execute [;run janet "-e" "(+ 1 2 3)"] :pe {})) +(assert (or (= result -1073741515) (= result 0)) "os/execute with minimal env") # os/execute regressions From aa63adccb46ce4e0870599ab710f1b7469ba000f Mon Sep 17 00:00:00 2001 From: Calvin Rose Date: Mon, 2 Mar 2026 19:39:14 -0600 Subject: [PATCH 198/307] Update CHANGELOG.md --- CHANGELOG.md | 2 ++ 1 file changed, 2 insertions(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index aafe0b9c..40d38651 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -3,6 +3,8 @@ All notable changes to this project will be documented in this file. ## Unreleased - ??? - Documentation fixes +- ev/thread-chan deadlock bug fixed +- Re-add removed support for non-blocking net/connect on windows. ## 1.41.2 - 2026-02-18 - Fix regressions in `put` for arrays and buffers. From b96350f132c9e2bf9bededf759ba60731b7efce6 Mon Sep 17 00:00:00 2001 From: McSinyx Date: Fri, 6 Mar 2026 10:34:05 +0900 Subject: [PATCH 199/307] Align items in multiline pretty format (%m and %p) (#1721) Indentation levels are not sufficiently clear for deeply nested data structures. Instead, align items in a collection with its opening bracket, also known as hanging indentation. To avoid guessing the length of a "short" collection to print it on one line, items are now always printed on separate lines. --- src/core/pp.c | 105 ++++++++++++++++++++++++++------------------ test/suite-pp.janet | 65 ++++++++++++++++++++++++++- 2 files changed, 127 insertions(+), 43 deletions(-) diff --git a/src/core/pp.c b/src/core/pp.c index 6a839362..16fd51a4 100644 --- a/src/core/pp.c +++ b/src/core/pp.c @@ -72,7 +72,7 @@ static int count_dig10(int32_t x) { } } -static void integer_to_string_b(JanetBuffer *buffer, int32_t x) { +static int32_t integer_to_string_b(JanetBuffer *buffer, int32_t x) { janet_buffer_extra(buffer, BUFSIZE); uint8_t *buf = buffer->data + buffer->count; int32_t neg = 0; @@ -80,7 +80,7 @@ static void integer_to_string_b(JanetBuffer *buffer, int32_t x) { if (x == 0) { buf[0] = '0'; buffer->count++; - return; + return 1; } if (x > 0) { x = -x; @@ -96,6 +96,7 @@ static void integer_to_string_b(JanetBuffer *buffer, int32_t x) { x /= 10; } buffer->count += len + neg; + return len + neg; } #define HEX(i) (((uint8_t *) janet_base64)[(i)]) @@ -134,43 +135,55 @@ static void string_description_b(JanetBuffer *buffer, const char *title, void *p #undef POINTSIZE } -static void janet_escape_string_impl(JanetBuffer *buffer, const uint8_t *str, int32_t len) { +static int janet_escape_string_impl(JanetBuffer *buffer, const uint8_t *str, int32_t len) { janet_buffer_push_u8(buffer, '"'); + int align = 1; for (int32_t i = 0; i < len; ++i) { uint8_t c = str[i]; switch (c) { case '"': janet_buffer_push_bytes(buffer, (const uint8_t *)"\\\"", 2); + align += 2; break; case '\n': janet_buffer_push_bytes(buffer, (const uint8_t *)"\\n", 2); + align += 2; break; case '\r': janet_buffer_push_bytes(buffer, (const uint8_t *)"\\r", 2); + align += 2; break; case '\0': janet_buffer_push_bytes(buffer, (const uint8_t *)"\\0", 2); + align += 2; break; case '\f': janet_buffer_push_bytes(buffer, (const uint8_t *)"\\f", 2); + align += 2; break; case '\v': janet_buffer_push_bytes(buffer, (const uint8_t *)"\\v", 2); + align += 2; break; case '\a': janet_buffer_push_bytes(buffer, (const uint8_t *)"\\a", 2); + align += 2; break; case '\b': janet_buffer_push_bytes(buffer, (const uint8_t *)"\\b", 2); + align += 2; break; case 27: janet_buffer_push_bytes(buffer, (const uint8_t *)"\\e", 2); + align += 2; break; case '\\': janet_buffer_push_bytes(buffer, (const uint8_t *)"\\\\", 2); + align += 2; break; case '\t': janet_buffer_push_bytes(buffer, (const uint8_t *)"\\t", 2); + align += 2; break; default: if (c < 32 || c > 126) { @@ -180,13 +193,16 @@ static void janet_escape_string_impl(JanetBuffer *buffer, const uint8_t *str, in buf[2] = janet_base64[(c >> 4) & 0xF]; buf[3] = janet_base64[c & 0xF]; janet_buffer_push_bytes(buffer, buf, 4); + align += 4; } else { janet_buffer_push_u8(buffer, c); + align++; } break; } } janet_buffer_push_u8(buffer, '"'); + return align + 1; } static void janet_escape_string_b(JanetBuffer *buffer, const uint8_t *str) { @@ -358,7 +374,7 @@ const uint8_t *janet_to_string(Janet x) { struct pretty { JanetBuffer *buffer; int depth; - int indent; + int align; int flags; int32_t bufstartlen; int32_t *keysort_buffer; @@ -450,14 +466,15 @@ static int print_jdn_one(struct pretty *S, Janet x, int depth) { return 0; } -static void print_newline(struct pretty *S, int just_a_space) { +static void print_newline(struct pretty *S, int align) { int i; - if (just_a_space || (S->flags & JANET_PRETTY_ONELINE)) { + S->align = align; + if (S->flags & JANET_PRETTY_ONELINE) { janet_buffer_push_u8(S->buffer, ' '); return; } janet_buffer_push_u8(S->buffer, '\n'); - for (i = 0; i < S->indent; i++) { + for (i = 0; i < S->align; i++) { janet_buffer_push_u8(S->buffer, ' '); } } @@ -484,14 +501,12 @@ static const char *janet_pretty_colors[] = { "\x1B[36m" }; -#define JANET_PRETTY_DICT_ONELINE 4 -#define JANET_PRETTY_IND_ONELINE 10 #define JANET_PRETTY_DICT_LIMIT 30 #define JANET_PRETTY_DICT_KEYSORT_LIMIT 2000 #define JANET_PRETTY_ARRAY_LIMIT 160 /* Helper for pretty printing */ -static void janet_pretty_one(struct pretty *S, Janet x, int is_dict_value) { +static void janet_pretty_one(struct pretty *S, Janet x) { /* Add to seen */ switch (janet_type(x)) { case JANET_NIL: @@ -506,7 +521,7 @@ static void janet_pretty_one(struct pretty *S, Janet x, int is_dict_value) { janet_buffer_push_cstring(S->buffer, janet_cycle_color); } janet_buffer_push_cstring(S->buffer, "buffer, janet_unwrap_integer(seenid)); + S->align += 8 + integer_to_string_b(S->buffer, janet_unwrap_integer(seenid)); janet_buffer_push_u8(S->buffer, '>'); if (S->flags & JANET_PRETTY_COLOR) { janet_buffer_push_cstring(S->buffer, "\x1B[0m"); @@ -528,9 +543,11 @@ static void janet_pretty_one(struct pretty *S, Janet x, int is_dict_value) { if (janet_checktype(x, JANET_BUFFER) && janet_unwrap_buffer(x) == S->buffer) { janet_buffer_ensure(S->buffer, S->buffer->count + S->bufstartlen * 4 + 3, 1); janet_buffer_push_u8(S->buffer, '@'); - janet_escape_string_impl(S->buffer, S->buffer->data, S->bufstartlen); + S->align += 1 + janet_escape_string_impl(S->buffer, S->buffer->data, S->bufstartlen); } else { + S->align -= S->buffer->count; janet_description_b(S->buffer, x); + S->align += S->buffer->count; } if (color && (S->flags & JANET_PRETTY_COLOR)) { janet_buffer_push_cstring(S->buffer, "\x1B[0m"); @@ -547,35 +564,34 @@ static void janet_pretty_one(struct pretty *S, Janet x, int is_dict_value) { const char *startstr = isarray ? "@[" : hasbrackets ? "[" : "("; const char endchar = isarray ? ']' : hasbrackets ? ']' : ')'; janet_buffer_push_cstring(S->buffer, startstr); + const int align = S->align += strlen(startstr); S->depth--; - S->indent += 2; if (S->depth == 0) { janet_buffer_push_cstring(S->buffer, "..."); + S->align += 3; } else { - if (!isarray && !(S->flags & JANET_PRETTY_ONELINE) && len >= JANET_PRETTY_IND_ONELINE) - janet_buffer_push_u8(S->buffer, ' '); - if (is_dict_value && len >= JANET_PRETTY_IND_ONELINE) print_newline(S, 0); if (len > JANET_PRETTY_ARRAY_LIMIT && !(S->flags & JANET_PRETTY_NOTRUNC)) { for (i = 0; i < 3; i++) { - if (i) print_newline(S, 0); - janet_pretty_one(S, arr[i], 0); + if (i) print_newline(S, align); + janet_pretty_one(S, arr[i]); } - print_newline(S, 0); + print_newline(S, align); janet_buffer_push_cstring(S->buffer, "..."); - for (i = 0; i < 3; i++) { - print_newline(S, 0); - janet_pretty_one(S, arr[len - 3 + i], 0); + S->align += 3; + for (i = len - 3; i < len; i++) { + print_newline(S, align); + janet_pretty_one(S, arr[i]); } } else { for (i = 0; i < len; i++) { - if (i) print_newline(S, len < JANET_PRETTY_IND_ONELINE); - janet_pretty_one(S, arr[i], 0); + if (i) print_newline(S, align); + janet_pretty_one(S, arr[i]); } } } - S->indent -= 2; S->depth++; janet_buffer_push_u8(S->buffer, endchar); + S->align++; break; } case JANET_STRUCT: @@ -586,6 +602,7 @@ static void janet_pretty_one(struct pretty *S, Janet x, int is_dict_value) { if (istable) { JanetTable *t = janet_unwrap_table(x); JanetTable *proto = t->proto; + S->align++; janet_buffer_push_cstring(S->buffer, "@"); if (NULL != proto) { Janet name = janet_table_get(proto, janet_ckeywordv("_name")); @@ -596,6 +613,7 @@ static void janet_pretty_one(struct pretty *S, Janet x, int is_dict_value) { janet_buffer_push_cstring(S->buffer, janet_class_color); } janet_buffer_push_bytes(S->buffer, n, len); + S->align += len; if (S->flags & JANET_PRETTY_COLOR) { janet_buffer_push_cstring(S->buffer, "\x1B[0m"); } @@ -613,25 +631,24 @@ static void janet_pretty_one(struct pretty *S, Janet x, int is_dict_value) { janet_buffer_push_cstring(S->buffer, janet_class_color); } janet_buffer_push_bytes(S->buffer, n, len); + S->align += len; if (S->flags & JANET_PRETTY_COLOR) { janet_buffer_push_cstring(S->buffer, "\x1B[0m"); } } } } - janet_buffer_push_cstring(S->buffer, "{"); + janet_buffer_push_u8(S->buffer, '{'); + const int align = ++S->align; S->depth--; - S->indent += 2; if (S->depth == 0) { janet_buffer_push_cstring(S->buffer, "..."); + S->align += 3; } else { int32_t len = 0, cap = 0; const JanetKV *kvs = NULL; janet_dictionary_view(x, &kvs, &len, &cap); - if (!istable && !(S->flags & JANET_PRETTY_ONELINE) && len >= JANET_PRETTY_DICT_ONELINE) - janet_buffer_push_u8(S->buffer, ' '); - if (is_dict_value && len >= JANET_PRETTY_DICT_ONELINE) print_newline(S, 0); int32_t ks_start = S->keysort_start; int truncated = 0; @@ -644,15 +661,17 @@ static void janet_pretty_one(struct pretty *S, Janet x, int is_dict_value) { int32_t j = 0; for (int32_t i = 0; i < len; i++) { while (janet_checktype(kvs[j].key, JANET_NIL)) j++; - if (i) print_newline(S, len < JANET_PRETTY_DICT_ONELINE); - janet_pretty_one(S, kvs[j].key, 0); + if (i) print_newline(S, align); + janet_pretty_one(S, kvs[j].key); janet_buffer_push_u8(S->buffer, ' '); - janet_pretty_one(S, kvs[j].value, 1); + S->align++; + janet_pretty_one(S, kvs[j].value); j++; } if (truncated) { - print_newline(S, 0); + print_newline(S, align); janet_buffer_push_cstring(S->buffer, "..."); + S->align += 3; } } else { /* Sorted keys dictionaries */ @@ -685,24 +704,26 @@ static void janet_pretty_one(struct pretty *S, Janet x, int is_dict_value) { } for (int32_t i = 0; i < len; i++) { - if (i) print_newline(S, len < JANET_PRETTY_DICT_ONELINE); + if (i) print_newline(S, align); int32_t j = S->keysort_buffer[i + ks_start]; - janet_pretty_one(S, kvs[j].key, 0); + janet_pretty_one(S, kvs[j].key); janet_buffer_push_u8(S->buffer, ' '); - janet_pretty_one(S, kvs[j].value, 1); + S->align++; + janet_pretty_one(S, kvs[j].value); } if (truncated) { - print_newline(S, 0); + print_newline(S, align); janet_buffer_push_cstring(S->buffer, "..."); + S->align += 3; } } S->keysort_start = ks_start; } - S->indent -= 2; S->depth++; janet_buffer_push_u8(S->buffer, '}'); + S->align++; break; } } @@ -718,14 +739,14 @@ static JanetBuffer *janet_pretty_(JanetBuffer *buffer, int depth, int flags, Jan } S.buffer = buffer; S.depth = depth; - S.indent = 0; + S.align = 0; S.flags = flags; S.bufstartlen = startlen; S.keysort_capacity = 0; S.keysort_buffer = NULL; S.keysort_start = 0; janet_table_init(&S.seen, 10); - janet_pretty_one(&S, x, 0); + janet_pretty_one(&S, x); janet_table_deinit(&S.seen); return S.buffer; } @@ -743,7 +764,7 @@ static JanetBuffer *janet_jdn_(JanetBuffer *buffer, int depth, Janet x, int32_t } S.buffer = buffer; S.depth = depth; - S.indent = 0; + S.align = 0; S.flags = 0; S.bufstartlen = startlen; S.keysort_capacity = 0; diff --git a/test/suite-pp.janet b/test/suite-pp.janet index be17d195..f11dc51f 100644 --- a/test/suite-pp.janet +++ b/test/suite-pp.janet @@ -61,5 +61,68 @@ (check-jdn "a string") (check-jdn @"a buffer") +# Test multiline pretty specifiers +(let [tup [:keyword "string" @"buffer"] + tab @{true (table/setproto @{:bar tup + :baz 42} + @{:_name "Foo"})}] + (set (tab tup) tab) + (assert (= (string/format "%m" {tup @[tup tab] + 'symbol tup}) + ` +{symbol (:keyword + "string" + @"buffer") + (:keyword + "string" + @"buffer") @[(:keyword + "string" + @"buffer") + @{true @Foo{:bar (:keyword + "string" + @"buffer") + :baz 42} + (:keyword + "string" + @"buffer") }]}`)) + (assert (= (string/format "%p" {(freeze (zipcoll (range 42) + (range -42 0))) tab}) + ` +{{0 -42 + 1 -41 + 2 -40 + 3 -39 + 4 -38 + 5 -37 + 6 -36 + 7 -35 + 8 -34 + 9 -33 + 10 -32 + 11 -31 + 12 -30 + 13 -29 + 14 -28 + 15 -27 + 16 -26 + 17 -25 + 18 -24 + 19 -23 + 20 -22 + 21 -21 + 22 -20 + 23 -19 + 24 -18 + 25 -17 + 26 -16 + 27 -15 + 28 -14 + 29 -13 + ...} @{true @Foo{:bar (:keyword + "string" + @"buffer") + :baz 42} + (:keyword + "string" + @"buffer") }}`))) (end-suite) - From decd7078af107eb1ba808c71b0d3c41f8267608f Mon Sep 17 00:00:00 2001 From: PinieP <59698589+PinieP@users.noreply.github.com> Date: Sat, 7 Mar 2026 22:01:03 +0100 Subject: [PATCH 200/307] fix janet_make_threaded to return threaded channel (#1724) janet_make_threaded previously created a channel with threaded set to 0 --- src/core/ev.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/core/ev.c b/src/core/ev.c index 6c71a4f2..b7077ffb 100644 --- a/src/core/ev.c +++ b/src/core/ev.c @@ -1172,7 +1172,7 @@ JanetChannel *janet_channel_make(uint32_t limit) { JanetChannel *janet_channel_make_threaded(uint32_t limit) { janet_assert(limit <= INT32_MAX, "bad limit"); JanetChannel *channel = janet_abstract_threaded(&janet_channel_type, sizeof(JanetChannel)); - janet_chan_init(channel, (int32_t) limit, 0); + janet_chan_init(channel, (int32_t) limit, 1); return channel; } From 8f446736edb302ce57d554d41a81a783e8b36872 Mon Sep 17 00:00:00 2001 From: Calvin Rose Date: Sat, 7 Mar 2026 15:18:47 -0600 Subject: [PATCH 201/307] Fix #1723 - handle prompt longer than terminal is wide. When this happened, we tried to print a buffer with a negative length that resulted in invalid memmove call. This fix both checks that we don't tried to append a negative buffer, and truncates a prompt that is too long for the termimal so that one can still enter text with at least 16 visible characters for data entry. --- src/mainclient/shell.c | 36 ++++++++++++++++++++++++++++-------- 1 file changed, 28 insertions(+), 8 deletions(-) diff --git a/src/mainclient/shell.c b/src/mainclient/shell.c index 4dadc041..b0f73915 100644 --- a/src/mainclient/shell.c +++ b/src/mainclient/shell.c @@ -26,6 +26,7 @@ #include #include +#include #ifdef _WIN32 #include @@ -362,33 +363,50 @@ static void clear(void) { } } +static int getplen(void) { + int _plen = gbl_plen; + /* Ensure at least 16 characters of data entry; */ + while (_plen && (_plen + 16 > gbl_cols)) { + _plen--; + } + return _plen; +} + static void refresh(void) { char seq[64]; JanetBuffer b; + /* If prompt is too long, truncate */ + int _plen = getplen(); + /* Keep cursor position on screen */ char *_buf = gbl_buf; int _len = gbl_len; int _pos = gbl_pos; - while ((gbl_plen + _pos) >= gbl_cols) { + + while ((_plen + _pos) >= gbl_cols) { _buf++; _len--; _pos--; } - while ((gbl_plen + _len) > gbl_cols) { + + while ((_plen + _len) > gbl_cols) { _len--; } + janet_buffer_init(&b, 0); /* Cursor to left edge, gbl_prompt and buffer */ janet_buffer_push_u8(&b, '\r'); - janet_buffer_push_cstring(&b, gbl_prompt); - janet_buffer_push_bytes(&b, (uint8_t *) _buf, _len); + janet_buffer_push_bytes(&b, (const uint8_t *) gbl_prompt, _plen); + if (_len > 0) { + janet_buffer_push_bytes(&b, (uint8_t *) _buf, _len); + } /* Erase to right */ janet_buffer_push_cstring(&b, "\x1b[0K\r"); /* Move cursor to original position. */ - if (_pos + gbl_plen) { - snprintf(seq, 64, "\x1b[%dC", (int)(_pos + gbl_plen)); + if (_pos + _plen) { + snprintf(seq, 64, "\x1b[%dC", (int)(_pos + _plen)); janet_buffer_push_cstring(&b, seq); } if (write_console((char *) b.data, b.count) == -1) { @@ -414,7 +432,8 @@ static int insert(char c, int draw) { gbl_buf[gbl_pos++] = c; gbl_buf[++gbl_len] = '\0'; if (draw) { - if (gbl_plen + gbl_len < gbl_cols) { + int _plen = getplen(); + if (_plen + gbl_len < gbl_cols) { /* Avoid a full update of the line in the * trivial case. */ if (write_console(&c, 1) == -1) return -1; @@ -925,11 +944,12 @@ static int line() { gbl_len = 0; gbl_pos = 0; while (gbl_prompt[gbl_plen]) gbl_plen++; + int _plen = getplen(); gbl_buf[0] = '\0'; addhistory(); - if (write_console((char *) gbl_prompt, gbl_plen) == -1) return -1; + if (write_console((char *) gbl_prompt, _plen) == -1) return -1; for (;;) { char c; char seq[5]; From a0cc867f14a8518e2387ef07546f6dce373aeb20 Mon Sep 17 00:00:00 2001 From: Calvin Rose Date: Mon, 9 Mar 2026 19:46:55 -0500 Subject: [PATCH 202/307] Improve macro hygiene in boot.janet. --- src/boot/boot.janet | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/boot/boot.janet b/src/boot/boot.janet index 72970bfc..7f03aa48 100644 --- a/src/boot/boot.janet +++ b/src/boot/boot.janet @@ -698,14 +698,14 @@ See `loop` for details.`` [head key-body & value-body] (def $accum (gensym)) - ~(do (def ,$accum @{}) (loop ,head (,put ,$accum ,key-body (do ,;value-body))) ,$accum)) + ~(do (def ,$accum @{}) (as-macro ,loop ,head (,put ,$accum ,key-body (do ,;value-body))) ,$accum)) (defmacro generate ``Create a generator expression using the `loop` syntax. Returns a fiber that yields all values inside the loop in order. See `loop` for details.`` [head & body] (check-empty-body body) - ~(,fiber/new (fn :generate [] (loop ,head (yield (do ,;body)))) :yi)) + ~(,fiber/new (fn :generate [] (as-macro ,loop ,head (,yield (do ,;body)))) :yi)) (defmacro coro "A wrapper for making fibers that may yield multiple values (coroutine). Same as `(fiber/new (fn [] ;body) :yi)`." From b160f4f5c091dda6e82ff5b654f59307529a6b75 Mon Sep 17 00:00:00 2001 From: sogaiu <33044872+sogaiu@users.noreply.github.com> Date: Thu, 12 Mar 2026 09:06:43 +0900 Subject: [PATCH 203/307] Macro hygiene tweaks (#1727) * Tweak hygiene for when-with, if-with, when-let * Tweak hygiene for loop1 helper * Tweak hygiene for repeat * Tweak hygiene for seq and catseq * Tweak hygiene for -?> and -?>> * Tweak hygiene for varfn * Tweak hygiene for ev/with-deadline * Tweak hygiene for ffi/defbind-alias + ffi/defbind --------- Co-authored-by: sogaiu <983021772@users.noreply.github.com> --- src/boot/boot.janet | 86 ++++++++++++++++++++++----------------------- 1 file changed, 43 insertions(+), 43 deletions(-) diff --git a/src/boot/boot.janet b/src/boot/boot.janet index 7f03aa48..d6a6794c 100644 --- a/src/boot/boot.janet +++ b/src/boot/boot.janet @@ -443,11 +443,36 @@ (def ,binding ,ctor) ,(defer-impl :with [(or dtor :close) binding] body))) +# declare ahead of time +(var- macexvar nil) + +(defmacro if-let + ``Make multiple bindings, and if all are truthy, + evaluate the `tru` form. If any are false or nil, evaluate + the `fal` form. Bindings have the same syntax as the `let` macro.`` + [bindings tru &opt fal] + (def len (length bindings)) + (if (= 0 len) (error "expected at least 1 binding")) + (if (odd? len) (error "expected an even number of bindings")) + (def fal2 (if macexvar (macexvar fal) fal)) + (defn aux [i] + (if (>= i len) + tru + (do + (def bl (in bindings i)) + (def br (in bindings (+ 1 i))) + (if (symbol? bl) + ~(if (def ,bl ,br) ,(aux (+ 2 i)) ,fal2) + ~(if (def ,(def sym (gensym)) ,br) + (do (def ,bl ,sym) ,(aux (+ 2 i))) + ,fal2))))) + (aux 0)) + (defmacro when-with ``Similar to with, but if binding is false or nil, returns nil without evaluating the body. Otherwise, the same as `with`.`` [[binding ctor dtor] & body] - ~(if-let [,binding ,ctor] + ~(as-macro ,if-let [,binding ,ctor] ,(defer-impl :when-with [(or dtor :close) binding] body))) (defmacro if-with @@ -455,7 +480,7 @@ the falsey path. Otherwise, evaluates the truthy path. In both cases, `ctor` is bound to binding.`` [[binding ctor dtor] truthy &opt falsey] - ~(if-let [,binding ,ctor] + ~(as-macro ,if-let [,binding ,ctor] ,(defer-impl :if-with [(or dtor :close) binding] [truthy]) ,falsey)) @@ -539,13 +564,13 @@ (case binding :until ~(do (if ,verb (break) nil) ,rest) :while ~(do (if ,verb nil (break)) ,rest) - :let ~(let ,verb (do ,rest)) + :let ~(as-macro ,let ,verb (do ,rest)) :after ~(do ,rest ,verb nil) :before ~(do ,verb ,rest nil) :repeat (with-syms [iter] - ~(do (var ,iter ,verb) (while (> ,iter 0) ,rest (-- ,iter)))) - :when ~(when ,verb ,rest) - :unless ~(unless ,verb ,rest) + ~(do (var ,iter ,verb) (while (,> ,iter 0) ,rest (as-macro ,-- ,iter)))) + :when ~(as-macro ,when ,verb ,rest) + :unless ~(as-macro ,unless ,verb ,rest) (error (string "unexpected loop modifier " binding)))))) # 3 term expression @@ -587,7 +612,7 @@ "Evaluate body n times. If n is negative, body will be evaluated 0 times. Evaluates to nil." [n & body] (with-syms [iter] - ~(do (var ,iter ,n) (while (> ,iter 0) ,;body (-- ,iter))))) + ~(do (var ,iter ,n) (while (,> ,iter 0) ,;body (as-macro ,-- ,iter))))) (defmacro forever "Evaluate body forever in a loop, or until a break statement." @@ -683,7 +708,7 @@ [head & body] (def $accum (gensym)) (check-empty-body body) - ~(do (def ,$accum @[]) (loop ,head (,array/push ,$accum (do ,;body))) ,$accum)) + ~(do (def ,$accum @[]) (as-macro ,loop ,head (,array/push ,$accum (do ,;body))) ,$accum)) (defmacro catseq ``Similar to `loop`, but concatenates each element from the loop body into an array and returns that. @@ -691,7 +716,7 @@ [head & body] (def $accum (gensym)) (check-empty-body body) - ~(do (def ,$accum @[]) (loop ,head (,array/concat ,$accum (do ,;body))) ,$accum)) + ~(do (def ,$accum @[]) (as-macro ,loop ,head (,array/concat ,$accum (do ,;body))) ,$accum)) (defmacro tabseq ``Similar to `loop`, but accumulates key value pairs into a table. @@ -754,35 +779,10 @@ (each x xs (*= accum x)) accum) -# declare ahead of time -(var- macexvar nil) - -(defmacro if-let - ``Make multiple bindings, and if all are truthy, - evaluate the `tru` form. If any are false or nil, evaluate - the `fal` form. Bindings have the same syntax as the `let` macro.`` - [bindings tru &opt fal] - (def len (length bindings)) - (if (= 0 len) (error "expected at least 1 binding")) - (if (odd? len) (error "expected an even number of bindings")) - (def fal2 (if macexvar (macexvar fal) fal)) - (defn aux [i] - (if (>= i len) - tru - (do - (def bl (in bindings i)) - (def br (in bindings (+ 1 i))) - (if (symbol? bl) - ~(if (def ,bl ,br) ,(aux (+ 2 i)) ,fal2) - ~(if (def ,(def sym (gensym)) ,br) - (do (def ,bl ,sym) ,(aux (+ 2 i))) - ,fal2))))) - (aux 0)) - (defmacro when-let "Same as `(if-let bindings (do ;body))`." [bindings & body] - ~(if-let ,bindings (do ,;body))) + ~(as-macro ,if-let ,bindings (do ,;body))) (defn comp `Takes multiple functions and returns a function that is the composition @@ -1432,7 +1432,7 @@ (tuple n @[]))) (def sym (gensym)) (def parts (array/concat @[h sym] t)) - ~(let [,sym ,last] (if ,sym ,(keep-syntax! n parts)))) + ~(as-macro ,let [,sym ,last] (if ,sym ,(keep-syntax! n parts)))) (reduce fop x forms)) (defmacro -?>> @@ -1448,7 +1448,7 @@ (tuple n @[]))) (def sym (gensym)) (def parts (array/concat @[h] t @[sym])) - ~(let [,sym ,last] (if ,sym ,(keep-syntax! n parts)))) + ~(as-macro ,let [,sym ,last] (if ,sym ,(keep-syntax! n parts)))) (reduce fop x forms)) (defn- walk-ind [f form] @@ -2411,8 +2411,8 @@ (dictionary? m) (merge-into metadata m) (error (string "invalid metadata " m)))) (with-syms [entry old-entry f] - ~(let [,old-entry (,dyn ',name)] - (def ,entry (or ,old-entry @{:ref @[nil]})) + ~(as-macro ,let [,old-entry (,dyn ',name)] + (def ,entry (as-macro ,or ,old-entry @{:ref @[nil]})) (,setdyn ',name ,entry) (def ,f ,fbody) (,put-in ,entry [:ref 0] ,f) @@ -3953,7 +3953,7 @@ `` [sec & body] (with-syms [f] - ~(let [,f (coro ,;body)] + ~(as-macro ,let [,f (as-macro ,coro ,;body)] (,ev/deadline ,sec nil ,f) (,resume ,f)))) @@ -4085,15 +4085,15 @@ (defn make-ptr [] (assertf (ffi/lookup (if lazy (llib) lib) raw-symbol) "failed to find ffi symbol %v" raw-symbol)) (if lazy - ~(defn ,alias ,;meta [,;formal-args] + ~(as-macro ,defn ,alias ,;meta [,;formal-args] (,ffi/call (,(delay (make-ptr))) (,(delay (make-sig))) ,;formal-args)) - ~(defn ,alias ,;meta [,;formal-args] + ~(as-macro ,defn ,alias ,;meta [,;formal-args] (,ffi/call ,(make-ptr) ,(make-sig) ,;formal-args)))) (defmacro ffi/defbind :flycheck "Generate bindings for native functions in a convenient manner." [name ret-type & body] - ~(ffi/defbind-alias ,name ,name ,ret-type ,;body))) + ~(as-macro ,ffi/defbind-alias ,name ,name ,ret-type ,;body))) ### ### From df56efbae0757bbe4249233366a66018c3b1cd34 Mon Sep 17 00:00:00 2001 From: PinieP <59698589+PinieP@users.noreply.github.com> Date: Fri, 13 Mar 2026 01:25:51 +0100 Subject: [PATCH 204/307] Fix: add missing :symbolmap keyword to disasm function (#1729) --- src/core/asm.c | 1 + 1 file changed, 1 insertion(+) diff --git a/src/core/asm.c b/src/core/asm.c index 171754c0..a563d633 100644 --- a/src/core/asm.c +++ b/src/core/asm.c @@ -1110,6 +1110,7 @@ JANET_CORE_FN(cfun_disasm, if (!janet_cstrcmp(kw, "structarg")) return janet_disasm_structarg(f->def); if (!janet_cstrcmp(kw, "namedargs")) return janet_disasm_namedargs(f->def); if (!janet_cstrcmp(kw, "slotcount")) return janet_disasm_slotcount(f->def); + if (!janet_cstrcmp(kw, "symbolmap")) return janet_disasm_symbolslots(f->def); if (!janet_cstrcmp(kw, "constants")) return janet_disasm_constants(f->def); if (!janet_cstrcmp(kw, "sourcemap")) return janet_disasm_sourcemap(f->def); if (!janet_cstrcmp(kw, "environments")) return janet_disasm_environments(f->def); From 612971503d22be43cd86bc0519ee8341027ad39b Mon Sep 17 00:00:00 2001 From: Calvin Rose Date: Sat, 14 Mar 2026 23:13:29 -0500 Subject: [PATCH 205/307] Add LLM, AI and tool usage section to contribution guide. (#1730) * Add LLM, AI and tool usage section to contribution guide. * Word order and punctuation. * Add suggestions. - No bot PRs - Define "Large" code contribution Also try to disuade users from using AI for one-line or simple changes, instead preferring to treat that as "feedback" and rewrite instead. * Remove wiggle room for any AI code in the Janet runtime. Still leave open the possibity for AI / tool usage for static analysis and bug repots. However, the 5-15 lines of code limitation is fuzzy and arbitrary. We can just say no. * Remove word. --- CONTRIBUTING.md | 21 +++++++++++++++++++++ 1 file changed, 21 insertions(+) diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md index 2be36b1d..a7a12a58 100644 --- a/CONTRIBUTING.md +++ b/CONTRIBUTING.md @@ -37,6 +37,12 @@ may require changes before being merged. do this indentation, or approximate as close as possible. There is a janet formatter in [spork](https://github.com/janet-lang/spork.git) that can be used to format code as well. +Bot pull requests will not be accepted, and anonymous submissions, including +new accounts, unknown emails, and first time contributors will be subjected +to greater scrutiny and code reivew. Automatically generated and filed bug +reports MAY be ok, if they are of consistent and good quality, such as +OSSFuzz or well constructed CI pipelines. + ## C style For changes to the VM and Core code, you will probably need to know C. Janet is programmed with @@ -90,3 +96,18 @@ timely manner. In short, if you want extra functionality now, then build it. * Include a good description of the problem that is being solved * Include descriptions of potential solutions if you have some in mind. + +## LLMs, Tool Usage, and Transparency + +All usage of Large Language Models (LLMs), Neural Networks, "AI" tools, and +other tools such as software fuzzers or static analyzers must be disclosed. +This applies to pull requests, email patches, bug reports, and any other +meaningful contribution to Janet's source code. Please also refrain from using +generative AI for code that will be embedded in the Janet runtime, which include +all C source files as well as boot.janet. All code should be well +and completely understood by the human author, including test cases. Large and +obviously AI-driven changes will be rejected. Be mindful and transparent on the +copyright implications of any submitted code. We will use discretion when +accepting generated test cases for bug reproductions, one-line bug +fixes, or typo fixes. Often, these can be trivially rewritten by a human to +avoid the problem. From c63c6740d916e86ce92304c9cb1b3303db69cd65 Mon Sep 17 00:00:00 2001 From: Calvin Rose Date: Sun, 15 Mar 2026 11:24:07 -0500 Subject: [PATCH 206/307] First pass at linting binding shadowing. Prevent redefining bindings by accident. There are a few cases where we want to allow this, such as the `default` macro, so we allow a keyword :shadow to be included in the `def` expression to turn off this lint. TODO: * Clean up test suite to remove binding shadowing * Make sure that we don't get lints with *redef* turned on * Add positive and negative tests for lint messages. * Add location of shadowed binding in message --- src/boot/boot.janet | 5 ++--- src/core/compile.c | 51 ++++++++++++++++++++++++++++++--------------- src/core/compile.h | 11 +++++++--- src/core/specials.c | 40 ++++++++++++++++++++++------------- test/helper.janet | 12 ++++++----- 5 files changed, 77 insertions(+), 42 deletions(-) diff --git a/src/boot/boot.janet b/src/boot/boot.janet index d6a6794c..e4b51f78 100644 --- a/src/boot/boot.janet +++ b/src/boot/boot.janet @@ -46,7 +46,6 @@ (defn defmacro :macro :flycheck "Define a macro." [name & more] - (setdyn name @{}) # override old macro definitions in the case of a recursive macro (apply defn name :macro more)) (defmacro as-macro @@ -219,9 +218,9 @@ (defmacro default ``Define a default value for an optional argument. - Expands to `(def sym (if (= nil sym) val sym))`.`` + Expands to `(def sym :shadow (if (= nil sym) val sym))`.`` [sym val] - ~(def ,sym (if (,= nil ,sym) ,val ,sym))) + ~(def ,sym :shadow (if (,= nil ,sym) ,val ,sym))) (defmacro comment "Ignores the body of the comment." diff --git a/src/core/compile.c b/src/core/compile.c index cddf14de..29824be9 100644 --- a/src/core/compile.c +++ b/src/core/compile.c @@ -91,29 +91,28 @@ void janetc_freeslot(JanetCompiler *c, JanetSlot s) { } /* Add a slot to a scope with a symbol associated with it (def or var). */ -void janetc_nameslot(JanetCompiler *c, const uint8_t *sym, JanetSlot s) { +void janetc_nameslot(JanetCompiler *c, const uint8_t *sym, JanetSlot s, uint32_t flags) { + if (!(flags & JANET_DEFFLAG_NO_SHADOWCHECK)) { + if (sym[0] != '_') { + int check = janetc_shadowcheck(c, sym); + if (check == 2) { + janetc_lintf(c, JANET_C_LINT_NORMAL, "binding %q is shadowing a local binding", janet_wrap_symbol(sym)); + } else if (check) { + janetc_lintf(c, JANET_C_LINT_NORMAL, "binding %q is shadowing a global binding", janet_wrap_symbol(sym)); + } + } + } SymPair sp; int32_t cnt = janet_v_count(c->buffer); sp.sym = sym; sp.sym2 = sym; sp.slot = s; sp.keep = 0; - sp.referenced = sym[0] == '_'; /* Fake ref if symbol is _ to avoid lints */ - sp.slot.flags |= JANET_SLOT_NAMED; - sp.birth_pc = cnt ? cnt - 1 : 0; - sp.death_pc = UINT32_MAX; - janet_v_push(c->scope->syms, sp); -} - -/* Same as janetc_nameslot, but don't have a lint for unused bindings. */ -void janetc_nameslot_no_unused(JanetCompiler *c, const uint8_t *sym, JanetSlot s) { - SymPair sp; - int32_t cnt = janet_v_count(c->buffer); - sp.sym = sym; - sp.sym2 = sym; - sp.slot = s; - sp.keep = 0; - sp.referenced = 1; + if (flags & JANET_DEFFLAG_NO_UNUSED) { + sp.referenced = 1; + } else { + sp.referenced = sym[0] == '_'; /* Fake ref if symbol starts with _ to avoid lints */ + } sp.slot.flags |= JANET_SLOT_NAMED; sp.birth_pc = cnt ? cnt - 1 : 0; sp.death_pc = UINT32_MAX; @@ -260,6 +259,24 @@ static int lookup_missing( return 1; } +/* Check if a binding is defined in an upper scope. This let's us check for + * variable shadowing. */ +int janetc_shadowcheck(JanetCompiler *c, const uint8_t *sym) { + /* Check locals */ + JanetScope *scope = c->scope; + while (scope) { + int32_t len = janet_v_count(scope->syms); + for (int32_t i = len - 1; i >= 0; i--) { + SymPair *pair = scope->syms + i; + if (pair->sym == sym) return 2; + } + scope = scope->parent; + } + /* Check globals */ + JanetBinding binding = janet_resolve_ext(c->env, sym); + return binding.type != JANET_BINDING_NONE; +} + /* Allow searching for symbols. Return information about the symbol */ JanetSlot janetc_resolve( JanetCompiler *c, diff --git a/src/core/compile.h b/src/core/compile.h index e7739767..48839a22 100644 --- a/src/core/compile.h +++ b/src/core/compile.h @@ -221,9 +221,11 @@ const JanetFunOptimizer *janetc_funopt(uint32_t flags); /* Get a special. Return NULL if none exists */ const JanetSpecial *janetc_special(const uint8_t *name); +#define JANET_DEFFLAG_NO_SHADOWCHECK 1 +#define JANET_DEFFLAG_NO_UNUSED 2 + void janetc_freeslot(JanetCompiler *c, JanetSlot s); -void janetc_nameslot(JanetCompiler *c, const uint8_t *sym, JanetSlot s); -void janetc_nameslot_no_unused(JanetCompiler *c, const uint8_t *sym, JanetSlot s); +void janetc_nameslot(JanetCompiler *c, const uint8_t *sym, JanetSlot s, uint32_t flags); JanetSlot janetc_farslot(JanetCompiler *c); /* Throw away some code after checking that it is well formed. */ @@ -267,9 +269,12 @@ JanetFuncDef *janetc_pop_funcdef(JanetCompiler *c); /* Create a destroy slot */ JanetSlot janetc_cslot(Janet x); -/* Search for a symbol */ +/* Search for a symbol, and mark any found symbols as "used" for dead code elimintation and linting */ JanetSlot janetc_resolve(JanetCompiler *c, const uint8_t *sym); +/* Check if a symbol is already in scope for shadowing lints */ +int janetc_shadowcheck(JanetCompiler *c, const uint8_t *sym); + /* Bytecode optimization */ void janet_bytecode_movopt(JanetFuncDef *def); void janet_bytecode_remove_noops(JanetFuncDef *def); diff --git a/src/core/specials.c b/src/core/specials.c index 770de49a..3ebc46c5 100644 --- a/src/core/specials.c +++ b/src/core/specials.c @@ -404,7 +404,7 @@ SlotHeadPair *dohead_destructure(JanetCompiler *c, SlotHeadPair *into, JanetFopt } /* Def or var a symbol in a local scope */ -static int namelocal(JanetCompiler *c, const uint8_t *head, int32_t flags, JanetSlot ret, int no_unused) { +static int namelocal(JanetCompiler *c, const uint8_t *head, int32_t flags, JanetSlot ret, uint32_t def_flags) { int isUnnamedRegister = !(ret.flags & JANET_SLOT_NAMED) && ret.index > 0 && ret.envindex >= 0; @@ -425,11 +425,10 @@ static int namelocal(JanetCompiler *c, const uint8_t *head, int32_t flags, Janet ret = localslot; } ret.flags |= flags; - if ((c->scope->flags & JANET_SCOPE_TOP) || no_unused) { - janetc_nameslot_no_unused(c, head, ret); - } else { - janetc_nameslot(c, head, ret); + if (c->scope->flags & JANET_SCOPE_TOP) { + def_flags |= JANET_DEFFLAG_NO_UNUSED; } + janetc_nameslot(c, head, ret, def_flags); return !isUnnamedRegister; } @@ -443,6 +442,7 @@ static int varleaf( JanetSlot refslot; JanetTable *entry = janet_table_clone(reftab); + /* TODO - hoist to top level "compile" entry */ int is_redef = janet_truthy(janet_table_get_keyword(c->env, "redef")); JanetArray *ref; @@ -464,7 +464,11 @@ static int varleaf( return 1; } else { int no_unused = reftab && reftab->count && janet_truthy(janet_table_get_keyword(reftab, "unused")); - return namelocal(c, sym, JANET_SLOT_MUTABLE, s, no_unused); + int no_shadow = reftab && reftab->count && janet_truthy(janet_table_get_keyword(reftab, "shadow")); + uint32_t def_flags = 0; + if (no_unused) def_flags |= JANET_DEFFLAG_NO_UNUSED; + if (no_shadow) def_flags |= JANET_DEFFLAG_NO_SHADOWCHECK; + return namelocal(c, sym, JANET_SLOT_MUTABLE, s, def_flags); } } @@ -505,8 +509,9 @@ static int defleaf( const uint8_t *sym, JanetSlot s, JanetTable *tab) { + JanetTable *entry = NULL; if (c->scope->flags & JANET_SCOPE_TOP) { - JanetTable *entry = janet_table_clone(tab); + entry = janet_table_clone(tab); janet_table_put(entry, janet_ckeywordv("source-map"), janet_wrap_tuple(janetc_make_sourcemap(c))); @@ -530,12 +535,18 @@ static int defleaf( JanetSlot tabslot = janetc_cslot(janet_wrap_table(entry)); janetc_emit_sss(c, JOP_PUT, tabslot, valsym, s, 0); } - - /* Add env entry to env */ - janet_table_put(c->env, janet_wrap_symbol(sym), janet_wrap_table(entry)); } int no_unused = tab && tab->count && janet_truthy(janet_table_get_keyword(tab, "unused")); - return namelocal(c, sym, 0, s, no_unused); + int no_shadow = tab && tab->count && janet_truthy(janet_table_get_keyword(tab, "shadow")); + uint32_t def_flags = 0; + if (no_unused) def_flags |= JANET_DEFFLAG_NO_UNUSED; + if (no_shadow) def_flags |= JANET_DEFFLAG_NO_SHADOWCHECK; + int result = namelocal(c, sym, 0, s, def_flags); + if (entry) { + /* Add env entry to env AFTER namelocal to avoid the shadowcheck false positive */ + janet_table_put(c->env, janet_wrap_symbol(sym), janet_wrap_table(entry)); + } + return result; } static JanetSlot janetc_def(JanetFopts opts, int32_t argn, const Janet *argv) { @@ -1066,10 +1077,10 @@ static JanetSlot janetc_fn(JanetFopts opts, int32_t argn, const Janet *argv) { named_table = janet_table(10); named_slot = janetc_farslot(c); } else { - janetc_nameslot(c, sym, janetc_farslot(c)); + janetc_nameslot(c, sym, janetc_farslot(c), 0); } } else { - janetc_nameslot(c, sym, janetc_farslot(c)); + janetc_nameslot(c, sym, janetc_farslot(c), 0); } } else { janet_v_push(destructed_params, janetc_farslot(c)); @@ -1118,7 +1129,8 @@ static JanetSlot janetc_fn(JanetFopts opts, int32_t argn, const Janet *argv) { JanetSlot slot = janetc_farslot(c); slot.flags = JANET_SLOT_NAMED | JANET_FUNCTION; janetc_emit_s(c, JOP_LOAD_SELF, slot, 1); - janetc_nameslot_no_unused(c, sym, slot); + /* TODO - be smarted about the shadowcheck */ + janetc_nameslot(c, sym, slot, JANET_DEFFLAG_NO_UNUSED | JANET_DEFFLAG_NO_SHADOWCHECK); } } diff --git a/test/helper.janet b/test/helper.janet index aadf1084..7a5ce156 100644 --- a/test/helper.janet +++ b/test/helper.janet @@ -27,9 +27,11 @@ (def line-info (string/format "%s:%d" (frame :source) (frame :source-line))) (if x - (when is-verbose (eprintf "\e[32m✔\e[0m %s: %s: %v" line-info (describe e) x)) + (when is-verbose + (eprintf "\e[32m✔\e[0m %s: %s: %v" line-info (describe e) x) + (eflush) (flush)) (do - (eprintf "\e[31m✘\e[0m %s: %s: %v" line-info (describe e) x) (eflush))) + (eprintf "\e[31m✘\e[0m %s: %s: %v" line-info (describe e) x) (eflush) (flush))) x) (defn skip-asserts @@ -38,7 +40,7 @@ (+= skip-n n) nil) -(defmacro assert +(defmacro assert :shadow [x &opt e] (def xx (gensym)) (default e (string/format "%j" x)) @@ -50,12 +52,12 @@ (defmacro assert-error [msg & forms] (def errsym (keyword (gensym))) - ~(assert (= ,errsym (try (do ,;forms) ([_] ,errsym))) ,msg)) + ~(as-macro ,assert (= ,errsym (try (do ,;forms) ([_] ,errsym))) ,msg)) (defmacro assert-error-value [msg errval & forms] (def e (gensym)) - ~(assert (= ,errval (try (do ,;forms) ([,e] ,e))) ,msg)) + ~(as-macro ,assert (= ,errval (try (do ,;forms) ([,e] ,e))) ,msg)) (defn check-compile-error [form] From 1197cfe433e59959e1407282cc61b4b83f617cba Mon Sep 17 00:00:00 2001 From: Calvin Rose Date: Sun, 15 Mar 2026 12:56:10 -0500 Subject: [PATCH 207/307] Check redef only once per compile call. --- src/core/compile.c | 1 + src/core/compile.h | 3 +++ src/core/specials.c | 8 ++++---- 3 files changed, 8 insertions(+), 4 deletions(-) diff --git a/src/core/compile.c b/src/core/compile.c index 29824be9..c7a9cfb2 100644 --- a/src/core/compile.c +++ b/src/core/compile.c @@ -1120,6 +1120,7 @@ static void janetc_init(JanetCompiler *c, JanetTable *env, const uint8_t *where, c->current_mapping.line = -1; c->current_mapping.column = -1; c->lints = lints; + c->is_redef = janet_truthy(janet_table_get_keyword(c->env, "redef")); /* Init result */ c->result.error = NULL; c->result.status = JANET_COMPILE_OK; diff --git a/src/core/compile.h b/src/core/compile.h index 48839a22..3701129b 100644 --- a/src/core/compile.h +++ b/src/core/compile.h @@ -184,6 +184,9 @@ struct JanetCompiler { /* Collect linting results */ JanetArray *lints; + + /* Cached version of (dyn *redef*) */ + int is_redef; }; #define JANET_FOPTS_TAIL 0x10000 diff --git a/src/core/specials.c b/src/core/specials.c index 3ebc46c5..57f9cf64 100644 --- a/src/core/specials.c +++ b/src/core/specials.c @@ -442,8 +442,7 @@ static int varleaf( JanetSlot refslot; JanetTable *entry = janet_table_clone(reftab); - /* TODO - hoist to top level "compile" entry */ - int is_redef = janet_truthy(janet_table_get_keyword(c->env, "redef")); + int is_redef = c->is_redef; JanetArray *ref; JanetBinding old_binding; @@ -510,12 +509,13 @@ static int defleaf( JanetSlot s, JanetTable *tab) { JanetTable *entry = NULL; + int is_redef = 0; if (c->scope->flags & JANET_SCOPE_TOP) { entry = janet_table_clone(tab); janet_table_put(entry, janet_ckeywordv("source-map"), janet_wrap_tuple(janetc_make_sourcemap(c))); - int is_redef = janet_truthy(janet_table_get_keyword(c->env, "redef")); + is_redef = c->is_redef; if (is_redef) janet_table_put(entry, janet_ckeywordv("redef"), janet_wrap_true()); if (is_redef) { @@ -537,7 +537,7 @@ static int defleaf( } } int no_unused = tab && tab->count && janet_truthy(janet_table_get_keyword(tab, "unused")); - int no_shadow = tab && tab->count && janet_truthy(janet_table_get_keyword(tab, "shadow")); + int no_shadow = is_redef || (tab && tab->count && janet_truthy(janet_table_get_keyword(tab, "shadow"))); uint32_t def_flags = 0; if (no_unused) def_flags |= JANET_DEFFLAG_NO_UNUSED; if (no_shadow) def_flags |= JANET_DEFFLAG_NO_SHADOWCHECK; From 980999c97bf73ed5fcc6dba05b82a186051dfec5 Mon Sep 17 00:00:00 2001 From: Calvin Rose Date: Sun, 15 Mar 2026 13:14:10 -0500 Subject: [PATCH 208/307] Make test suite not shadow any variables. --- examples/error.janet | 6 ++--- examples/iterate-fiber.janet | 6 ++--- examples/lazyseqs.janet | 28 +++++++++---------- examples/life.janet | 4 +-- test/suite-array.janet | 6 ++--- test/suite-buffer.janet | 8 +++--- test/suite-compile.janet | 16 +++++------ test/suite-ev2.janet | 6 ++--- test/suite-io.janet | 6 ++--- test/suite-marsh.janet | 22 +++++++-------- test/suite-parse.janet | 32 +++++++++++----------- test/suite-peg.janet | 52 ++++++++++++++++++------------------ test/suite-pp.janet | 4 +-- test/suite-vm.janet | 10 +++---- 14 files changed, 103 insertions(+), 103 deletions(-) diff --git a/examples/error.janet b/examples/error.janet index e8b9e759..c337ba4a 100644 --- a/examples/error.janet +++ b/examples/error.janet @@ -3,10 +3,10 @@ (defn bork [x] - (defn bark [x] + (defn bark [y] (print "Woof!") - (print x) - (error x) + (print y) + (error y) (print "Woof!")) (bark (* 2 x)) diff --git a/examples/iterate-fiber.janet b/examples/iterate-fiber.janet index 9d6a49cf..79df4b32 100644 --- a/examples/iterate-fiber.janet +++ b/examples/iterate-fiber.janet @@ -7,13 +7,13 @@ (print "simple yielding") (each item f (print "got: " item ", now " (fiber/status f))) -(def f +(def f2 (coro (for i 0 10 (yield (string "yield " i)) (ev/sleep 0)))) (print "complex yielding") -(each item f (print "got: " item ", now " (fiber/status f))) +(each item f2 (print "got: " item ", now " (fiber/status f2))) -(print (fiber/status f)) +(print (fiber/status f2)) diff --git a/examples/lazyseqs.janet b/examples/lazyseqs.janet index 1e7452a0..481bc3ed 100644 --- a/examples/lazyseqs.janet +++ b/examples/lazyseqs.janet @@ -4,7 +4,7 @@ # that must be called (realizing it), and the memoized. # Use with (import "./path/to/this/file" :prefix "seq.") -(defmacro delay +(defmacro dolazy "Lazily evaluate a series of expressions. Returns a function that returns the result of the last expression. Will only evaluate the body once, and then memoizes the result." @@ -35,7 +35,7 @@ (def x (tuple h t)) (fn [] x)) -(defn empty? +(defn lazy-empty? "Check if a sequence is empty." [s] (not (s))) @@ -55,14 +55,14 @@ [start end &] (if end (if (< start end) - (delay (tuple start (lazy-range (+ 1 start) end))) + (dolazy (tuple start (lazy-range (+ 1 start) end))) empty-seq) (lazy-range 0 start))) (defn lazy-map "Return a sequence that is the result of applying f to each value in s." [f s] - (delay + (dolazy (def x (s)) (if x (tuple (f (get x HEAD)) (map f (get x TAIL)))))) @@ -76,31 +76,31 @@ [f s] (when (s) (f (head s)) (realize-map f (tail s)))) -(defn drop +(defn lazy-drop "Ignores the first n values of the sequence and returns the rest." [n s] - (delay + (dolazy (def x (s)) - (if (and x (pos? n)) ((drop (- n 1) (get x TAIL)))))) + (if (and x (pos? n)) ((lazy-drop (- n 1) (get x TAIL)))))) -(defn take +(defn lazy-take "Returns at most the first n values of s." [n s] - (delay + (dolazy (def x (s)) (if (and x (pos? n)) - (tuple (get x HEAD) (take (- n 1) (get x TAIL)))))) + (tuple (get x HEAD) (lazy-take (- n 1) (get x TAIL)))))) (defn randseq "Return a sequence of random numbers." [] - (delay (tuple (math/random) (randseq)))) + (dolazy (tuple (math/random) (randseq)))) -(defn take-while +(defn lazy-take-while "Returns a sequence of values until the predicate is false." [pred s] - (delay + (dolazy (def x (s)) (when x (def thehead (get HEAD x)) - (if thehead (tuple thehead (take-while pred (get TAIL x))))))) + (if thehead (tuple thehead (lazy-take-while pred (get TAIL x))))))) diff --git a/examples/life.janet b/examples/life.janet index ac1f8c99..c6483f25 100644 --- a/examples/life.janet +++ b/examples/life.janet @@ -16,8 +16,8 @@ (def cell-set (frequencies state)) (def neighbor-set (frequencies (mapcat neighbors state))) (seq [coord :keys neighbor-set - :let [count (get neighbor-set coord)] - :when (or (= count 3) (and (get cell-set coord) (= count 2)))] + :let [ncount (get neighbor-set coord)] + :when (or (= ncount 3) (and (get cell-set coord) (= ncount 2)))] coord)) (defn draw diff --git a/test/suite-array.janet b/test/suite-array.janet index 929135ab..35569aaf 100644 --- a/test/suite-array.janet +++ b/test/suite-array.janet @@ -70,9 +70,9 @@ (assert (= (array/pop @[]) nil) "array/pop empty") # Code coverage -(def a @[1]) -(array/pop a) -(array/trim a) +(def a1 @[1]) +(array/pop a1) +(array/trim a1) (array/ensure @[1 1] 6 2) # array/join diff --git a/test/suite-buffer.janet b/test/suite-buffer.janet index f0d2407e..07c5c027 100644 --- a/test/suite-buffer.janet +++ b/test/suite-buffer.janet @@ -48,8 +48,8 @@ (assert (deep= (buffer/push @"AA" @"BB") @"AABB") "buffer/push buffer") (assert (deep= (buffer/push @"AA" 66 66) @"AABB") "buffer/push int") -(def b @"AA") -(assert (deep= (buffer/push b b) @"AAAA") "buffer/push buffer self") +(def b1 @"AA") +(assert (deep= (buffer/push b1 b1) @"AAAA") "buffer/push buffer self") # buffer/push-byte (assert (deep= (buffer/push-byte @"AA" 66) @"AAB") "buffer/push-byte") @@ -145,8 +145,8 @@ # Regression #301 # a3d4ecddb -(def b (buffer/new-filled 128 0x78)) -(assert (= 38 (length (buffer/blit @"" b -1 90))) "buffer/blit 1") +(def b8 (buffer/new-filled 128 0x78)) +(assert (= 38 (length (buffer/blit @"" b8 -1 90))) "buffer/blit 1") (def a @"abcdefghijklm") (assert (deep= @"abcde" (buffer/blit @"" a -1 0 5)) "buffer/blit 2") diff --git a/test/suite-compile.janet b/test/suite-compile.janet index ec543a1c..5f7270c0 100644 --- a/test/suite-compile.janet +++ b/test/suite-compile.janet @@ -84,23 +84,23 @@ (assert (get result :error) "bad sum3 fuzz issue valgrind") # Issue #1700 -(def result +(def result1 (compile '(defn fuzz-case-1 [start end &] (if end (if e start (lazy-range (+ 1 start) end))) 1))) -(assert (get result :error) "fuzz case issue #1700") +(assert (get result1 :error) "fuzz case issue #1700") # Issue #1702 - fuzz case with upvalues -(def result +(def result2 (compile '(each item [1 2 3] # Generate a lot of upvalues (more than 224) (def ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;out-buf @"") (with-dyns [:out out-buf] 1)))) -(assert result "bad upvalues fuzz case") +(assert result2 "bad upvalues fuzz case") # Named argument linting # Enhancement for #1654 @@ -117,14 +117,14 @@ (defn check-good-compile [code msg] (def lints @[]) - (def result (compile code (curenv) "suite-compile.janet" lints)) - (assert (and (function? result) (empty? lints)) msg)) + (def result4 (compile code (curenv) "suite-compile.janet" lints)) + (assert (and (function? result4) (empty? lints)) msg)) (defn check-lint-compile [code msg] (def lints @[]) - (def result (compile code (curenv) "suite-compile.janet" lints)) - (assert (and (function? result) (next lints)) msg)) + (def result4 (compile code (curenv) "suite-compile.janet" lints)) + (assert (and (function? result4) (next lints)) msg)) (check-good-compile '(fnamed) "named no args") (check-good-compile '(fnamed :x 1 :y 2 :z 3) "named full args") diff --git a/test/suite-ev2.janet b/test/suite-ev2.janet index caa323a4..24a2e0c8 100644 --- a/test/suite-ev2.janet +++ b/test/suite-ev2.janet @@ -43,9 +43,9 @@ (assert (deep= '(:error "deadline expired" nil) (ev/take super)) "deadline expirataion") # Another variant -(def thread-channel (ev/thread-chan 100)) -(def super (ev/thread-chan 10)) -(defn worker [] +(def thread-channel :shadow (ev/thread-chan 100)) +(def super :shadow (ev/thread-chan 10)) +(defn worker :shadow [] (while true (def item (ev/take thread-channel)) (when (= item :deadline) diff --git a/test/suite-io.janet b/test/suite-io.janet index 6a0513c2..23672f62 100644 --- a/test/suite-io.janet +++ b/test/suite-io.janet @@ -37,7 +37,7 @@ # Printing to functions # 4e263b8c3 -(def out-buf @"") +(def out-buf :shadow @"") (defn prepend [x] (with-dyns [:out out-buf] (prin "> " x))) @@ -59,8 +59,8 @@ # issue #1055 - 2c927ea76 (let [b @""] - (defn dummy [a b c] - (+ a b c)) + (defn dummy [a bb c] + (+ a bb c)) (trace dummy) (defn errout [arg] (buffer/push b arg)) diff --git a/test/suite-marsh.janet b/test/suite-marsh.janet index c0cd6b0d..b4fdacba 100644 --- a/test/suite-marsh.janet +++ b/test/suite-marsh.janet @@ -95,11 +95,11 @@ (do (defn f1 [a] - (defn f1 [] (++ (a 0))) + (defn f1 :shadow [] (++ (a 0))) (defn f2 [] (++ (a 0))) (error [f1 f2])) (def [_ tup] (protect (f1 @[0]))) - (def [f1 f2] (unmarshal (marshal tup make-image-dict) load-image-dict)) + (def [f1 f2] :shadow (unmarshal (marshal tup make-image-dict) load-image-dict)) (assert (= 1 (f1)) "marshal-non-resumable-closure 1") (assert (= 2 (f2)) "marshal-non-resumable-closure 2")) @@ -108,10 +108,10 @@ (do (defn f1 [a] - (defn f1 [] (++ (a 0))) - (defn f2 [] (++ (a 0))) + (defn f1 :shadow [] (++ (a 0))) + (defn f2 :shadow [] (++ (a 0))) (marshal [f1 f2] make-image-dict)) - (def [f1 f2] (unmarshal (f1 @[0]) load-image-dict)) + (def [f1 f2] :shadow (unmarshal (f1 @[0]) load-image-dict)) (assert (= 1 (f1)) "marshal-live-closure 1") (assert (= 2 (f2)) "marshal-live-closure 2")) @@ -189,11 +189,11 @@ neldb\0\0\0\xD8\x05printG\x01\0\xDE\xDE\xDE'\x03\0marshal_tes/\x02 (assert (deep= t tclone) "table/weak marsh 7") # table weak keys -(def t (table/weak-keys 1)) +(def t :shadow (table/weak-keys 1)) (put t @"" keep-value) (put t :key @"") (assert (= 2 (length t)) "table/weak-keys marsh 1") -(def tclone (-> t marshal unmarshal)) +(def tclone :shadow (-> t marshal unmarshal)) (assert (= 2 (length tclone)) "table/weak-keys marsh 2") (gccollect) (assert (= 1 (length tclone)) "table/weak-keys marsh 3") @@ -201,23 +201,23 @@ neldb\0\0\0\xD8\x05printG\x01\0\xDE\xDE\xDE'\x03\0marshal_tes/\x02 (assert (deep= t tclone) "table/weak-keys marsh 5") # table weak values -(def t (table/weak-values 1)) +(def t :shadow (table/weak-values 1)) (put t @"" keep-value) (put t :key @"") (assert (= 2 (length t)) "table/weak-values marsh 1") -(def tclone (-> t marshal unmarshal)) +(def tclone :shadow (-> t marshal unmarshal)) (assert (= 2 (length tclone)) "table/weak-values marsh 2") (gccollect) (assert (= 1 (length t)) "table/weak-value marsh 3") (assert (deep= (freeze t) (freeze tclone)) "table/weak-values marsh 4") # tables with prototypes -(def t (table/weak-values 1)) +(def t :shadow (table/weak-values 1)) (table/setproto t @{:abc 123}) (put t @"" keep-value) (put t :key @"") (assert (= 2 (length t)) "marsh weak tables with prototypes 1") -(def tclone (-> t marshal unmarshal)) +(def tclone :shadow (-> t marshal unmarshal)) (assert (= 2 (length tclone)) "marsh weak tables with prototypes 2") (gccollect) (assert (= 1 (length t)) "marsh weak tables with prototypes 3") diff --git a/test/suite-parse.janet b/test/suite-parse.janet index 7b410029..aff86fda 100644 --- a/test/suite-parse.janet +++ b/test/suite-parse.janet @@ -138,13 +138,13 @@ # Parser clone # 43520ac67 -(def p (parser/new)) -(assert (= 7 (parser/consume p "(1 2 3 ")) "parser 1") -(def p2 (parser/clone p)) +(def p0 (parser/new)) +(assert (= 7 (parser/consume p0 "(1 2 3 ")) "parser 1") +(def p2 (parser/clone p0)) (parser/consume p2 ") 1 ") -(parser/consume p ") 1 ") -(assert (deep= (parser/status p) (parser/status p2)) "parser 2") -(assert (deep= (parser/state p) (parser/state p2)) "parser 3") +(parser/consume p0 ") 1 ") +(assert (deep= (parser/status p0) (parser/status p2)) "parser 2") +(assert (deep= (parser/state p0) (parser/state p2)) "parser 3") # Parser errors # 976dfc719 @@ -179,11 +179,11 @@ (parser/consume p1 step1) (loop [_ :iterate (parser/produce p1)]) (parser/state p1) -(def p2 (parser/clone p1)) -(parser/state p2) -(parser/consume p2 step2) -(loop [_ :iterate (parser/produce p2)]) -(parser/state p2) +(def p3 (parser/clone p1)) +(parser/state p3) +(parser/consume p3 step2) +(loop [_ :iterate (parser/produce p3)]) +(parser/state p3) # parser delimiter errors (defn test-error [delim fmt] @@ -202,11 +202,11 @@ (parser/consume p ")") (assert (= (parser/produce p) ["hello"])) -(def p (parser/new)) -(parser/consume p `("hel`) -(parser/insert p `lo`) -(parser/consume p `")`) -(assert (= (parser/produce p) ["hello"])) +(def p4 (parser/new)) +(parser/consume p4 `("hel`) +(parser/insert p4 `lo`) +(parser/consume p4 `")`) +(assert (= (parser/produce p4) ["hello"])) # Hex floats (assert (= math/pi +0x1.921fb54442d18p+0001)) diff --git a/test/suite-peg.janet b/test/suite-peg.janet index fb194498..0058e490 100644 --- a/test/suite-peg.janet +++ b/test/suite-peg.janet @@ -84,10 +84,10 @@ # Substitution test with peg # d7626f8c5 -(def grammar '(accumulate (any (+ (/ "dog" "purple panda") (<- 1))))) +(def grammar1 '(accumulate (any (+ (/ "dog" "purple panda") (<- 1))))) (defn try-grammar [text] (assert (= (string/replace-all "dog" "purple panda" text) - (0 (peg/match grammar text))) text)) + (0 (peg/match grammar1 text))) text)) (try-grammar "i have a dog called doug the dog. he is good.") (try-grammar "i have a dog called doug the dog. he is a good boy.") @@ -336,7 +336,7 @@ # unref # 96513665d -(def grammar +(def grammar2 (peg/compile ~{:main (* :tagged -1) :tagged (unref (replace (* :open-tag :value :close-tag) ,struct)) @@ -344,9 +344,9 @@ :value (* (constant :value) (group (any (+ :tagged :untagged)))) :close-tag (* "") :untagged (capture (any (if-not "<" 1)))})) -(check-deep grammar "

foobar

" +(check-deep grammar2 "

foobar

" @[{:tag "p" :value @[{:tag "em" :value @["foobar"]}]}]) -(check-deep grammar "

foobar

" @[{:tag "p" :value @["foobar"]}]) +(check-deep grammar2 "

foobar

" @[{:tag "p" :value @["foobar"]}]) # Using a large test grammar # cf05ff610 @@ -369,7 +369,7 @@ (def sym (symbol text)) [(if (or (root-env sym) (specials sym)) :coresym :symbol) text]) -(def grammar +(def grammar3 ~{:ws (set " \v\t\r\f\n\0") :readermac (set "';~,") :symchars (+ (range "09" "AZ" "az" "\x80\xFF") @@ -408,13 +408,13 @@ :dict (* '"@" :struct) :main (+ :root (error ""))}) -(def p (peg/compile grammar)) +(def porig (peg/compile grammar3)) # Just make sure is valgrind clean. -(def p (-> p make-image load-image)) +(def pprime (-> porig make-image load-image)) -(assert (peg/match p "abc") "complex peg grammar 1") -(assert (peg/match p "[1 2 3 4]") "complex peg grammar 2") +(assert (peg/match pprime "abc") "complex peg grammar 1") +(assert (peg/match pprime "[1 2 3 4]") "complex peg grammar 2") ### ### Compiling brainfuck to Janet. @@ -565,8 +565,8 @@ "peg/replace-all function") # 9dc7e8ed3 -(defn peg-test [name f peg subst text expected] - (assert (= (string (f peg subst text)) expected) name)) +(defn peg-test [name f pegg subst text expected] + (assert (= (string (f pegg subst text)) expected) name)) (peg-test "peg/replace has access to captures" peg/replace @@ -602,10 +602,10 @@ # Marshal and unmarshal pegs # 446ab037b -(def p (-> "abcd" peg/compile marshal unmarshal)) -(assert (peg/match p "abcd") "peg marshal 1") -(assert (peg/match p "abcdefg") "peg marshal 2") -(assert (not (peg/match p "zabcdefg")) "peg marshal 3") +(def p3 (-> "abcd" peg/compile marshal unmarshal)) +(assert (peg/match p3 "abcd") "peg marshal 1") +(assert (peg/match p3 "abcdefg") "peg marshal 2") +(assert (not (peg/match p3 "zabcdefg")) "peg marshal 3") # to/thru bug # issue #971 - a895219d2 @@ -669,10 +669,10 @@ (peg/match '(if (not (* (constant 7) "a")) "hello") "hello") @[]) "peg if not") -(defn test [name peg input expected] - (assert-no-error "compile peg" (peg/compile peg)) - (assert-no-error "marshal/unmarshal peg" (-> peg marshal unmarshal)) - (assert (deep= (peg/match peg input) expected) name)) +(defn test [name pegg input expected] + (assert-no-error "compile peg" (peg/compile pegg)) + (assert-no-error "marshal/unmarshal peg" (-> pegg marshal unmarshal)) + (assert (deep= (peg/match pegg input) expected) name)) (test "sub: matches the same input twice" ~(sub "abcd" "abc") @@ -852,20 +852,20 @@ @[["b" "b" "b"]]) # Debug and ?? tests. -(defn test-stderr [name peg input expected-matches expected-stderr] +(defn test-stderr [name pegg input expected-matches expected-stderr] (with-dyns [:err @""] - (test name peg input expected-matches)) + (test name pegg input expected-matches)) (def actual @"") (with-dyns [:err actual *err-color* true] - (peg/match peg input)) + (peg/match pegg input)) (assert (deep= (string actual) expected-stderr))) -(defn test-stderr-no-color [name peg input expected-matches expected-stderr] +(defn test-stderr-no-color [name pegg input expected-matches expected-stderr] (with-dyns [:err @""] - (test name peg input expected-matches)) + (test name pegg input expected-matches)) (def actual @"") (with-dyns [:err actual *err-color* false] - (peg/match peg input)) + (peg/match pegg input)) (assert (deep= (string actual) expected-stderr))) (test-stderr diff --git a/test/suite-pp.janet b/test/suite-pp.janet index f11dc51f..e3767b5e 100644 --- a/test/suite-pp.janet +++ b/test/suite-pp.janet @@ -44,8 +44,8 @@ (def buftemp @"abcd") (assert (= (string (buffer/format buftemp "---%p---" buftemp)) `abcd---@"abcd"---`) "buffer/format on self 1") -(def buftemp @"abcd") -(assert (= (string (buffer/format buftemp "---%p %p---" buftemp buftemp)) +(def buftemp2 @"abcd") +(assert (= (string (buffer/format buftemp2 "---%p %p---" buftemp2 buftemp2)) `abcd---@"abcd" @"abcd"---`) "buffer/format on self 2") # 5c364e0 diff --git a/test/suite-vm.janet b/test/suite-vm.janet index 56bddcc0..8ffdcd3a 100644 --- a/test/suite-vm.janet +++ b/test/suite-vm.janet @@ -132,11 +132,11 @@ # Cancel test # 28439d822 -(def f (fiber/new (fn [&] (yield 1) (yield 2) (yield 3) 4) :yti)) -(assert (= 1 (resume f)) "cancel resume 1") -(assert (= 2 (resume f)) "cancel resume 2") -(assert (= :hi (cancel f :hi)) "cancel resume 3") -(assert (= :error (fiber/status f)) "cancel resume 4") +(def fc (fiber/new (fn [&] (yield 1) (yield 2) (yield 3) 4) :yti)) +(assert (= 1 (resume fc)) "cancel resume 1") +(assert (= 2 (resume fc)) "cancel resume 2") +(assert (= :hi (cancel fc :hi)) "cancel resume 3") +(assert (= :error (fiber/status fc)) "cancel resume 4") (end-suite) From 6c2f08ef4991df5d39305507bcaf41a2315491f5 Mon Sep 17 00:00:00 2001 From: Calvin Rose Date: Sun, 15 Mar 2026 13:34:12 -0500 Subject: [PATCH 209/307] Add unit tests for variable shadowing. --- test/suite-compile.janet | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) diff --git a/test/suite-compile.janet b/test/suite-compile.janet index 5f7270c0..5b0c5d54 100644 --- a/test/suite-compile.janet +++ b/test/suite-compile.janet @@ -150,5 +150,10 @@ (check-lint-compile '(g 1 2 :z) "g lint 1") (check-lint-compile '(g 1 2 :z 4 5) "g lint 2") -(end-suite) +# Variable shadowing linting +(def outer1 "a") +(check-lint-compile '(def outer1 "b") "shadow global-to-global") +(check-lint-compile '(let [outer1 "b"] outer1) "shadow local-to-global") +(check-lint-compile '(do (def x "b") (def x "c")) "shadow local-to-local") +(end-suite) From 079776d39ef6d07b80b4b5ea9275e0dbbf530509 Mon Sep 17 00:00:00 2001 From: Calvin Rose Date: Sun, 15 Mar 2026 15:34:52 -0500 Subject: [PATCH 210/307] Elaborate comment. --- src/core/specials.c | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/src/core/specials.c b/src/core/specials.c index 57f9cf64..df0621c8 100644 --- a/src/core/specials.c +++ b/src/core/specials.c @@ -1129,7 +1129,8 @@ static JanetSlot janetc_fn(JanetFopts opts, int32_t argn, const Janet *argv) { JanetSlot slot = janetc_farslot(c); slot.flags = JANET_SLOT_NAMED | JANET_FUNCTION; janetc_emit_s(c, JOP_LOAD_SELF, slot, 1); - /* TODO - be smarted about the shadowcheck */ + /* We should figure out a better way to avoid `(def x 1) (def x :shadow (fn x [...] ...))` triggering a + * shadow lint for the last x */ janetc_nameslot(c, sym, slot, JANET_DEFFLAG_NO_UNUSED | JANET_DEFFLAG_NO_SHADOWCHECK); } } From 8b8990129833ac30289a4a156f8180ec1f41bf28 Mon Sep 17 00:00:00 2001 From: Calvin Rose Date: Sun, 15 Mar 2026 18:27:13 -0500 Subject: [PATCH 211/307] Deduplicate lints. Why see the same message twice? --- src/boot/boot.janet | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/src/boot/boot.janet b/src/boot/boot.janet index e4b51f78..e7ba52e1 100644 --- a/src/boot/boot.janet +++ b/src/boot/boot.janet @@ -2674,17 +2674,17 @@ (var resumeval nil) (def f (fiber/new - (fn [] + (fn :compile-and-lint [] (array/clear lints) (def res (compile source env where lints)) - (unless (empty? lints) + (when (next lints) # Convert lint levels to numbers. (def levels (get env *lint-levels* lint-levels)) (def lint-error (get env *lint-error*)) (def lint-warning (get env *lint-warn*)) (def lint-error (or (get levels lint-error lint-error) 0)) (def lint-warning (or (get levels lint-warning lint-warning) 2)) - (each [level line col msg] lints + (each [level line col msg] (distinct lints) # some macros might cause code to be duplicated. Avoid repeated messages. (def lvl (get lint-levels level 0)) (cond (<= lvl lint-error) (do From df32109eea63bb41d9a7e2c00e1d081bdb19b4b7 Mon Sep 17 00:00:00 2001 From: Calvin Rose Date: Sun, 15 Mar 2026 20:22:20 -0500 Subject: [PATCH 212/307] Small typo fixes in comments. --- src/core/compile.c | 2 +- src/core/compile.h | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/src/core/compile.c b/src/core/compile.c index c7a9cfb2..37d2491c 100644 --- a/src/core/compile.c +++ b/src/core/compile.c @@ -259,7 +259,7 @@ static int lookup_missing( return 1; } -/* Check if a binding is defined in an upper scope. This let's us check for +/* Check if a binding is defined in an upper scope. This lets us check for * variable shadowing. */ int janetc_shadowcheck(JanetCompiler *c, const uint8_t *sym) { /* Check locals */ diff --git a/src/core/compile.h b/src/core/compile.h index 3701129b..45d15eb6 100644 --- a/src/core/compile.h +++ b/src/core/compile.h @@ -272,7 +272,7 @@ JanetFuncDef *janetc_pop_funcdef(JanetCompiler *c); /* Create a destroy slot */ JanetSlot janetc_cslot(Janet x); -/* Search for a symbol, and mark any found symbols as "used" for dead code elimintation and linting */ +/* Search for a symbol, and mark any found symbols as "used" for dead code elimination and linting */ JanetSlot janetc_resolve(JanetCompiler *c, const uint8_t *sym); /* Check if a symbol is already in scope for shadowing lints */ From 93436bf97305bf88a976d261a67744f75fcaa304 Mon Sep 17 00:00:00 2001 From: Calvin Rose Date: Tue, 17 Mar 2026 14:26:30 -0500 Subject: [PATCH 213/307] Weaken variable shadowing to a "strict" lint. Only get compiler lints/errors in strict mode. Also distinguish between different kinds of shadowing for slightly different warnings and lint-levels that better align with idiomatic code. --- src/core/compile.c | 40 ++++++++++++++++++++++++++++++++-------- src/core/compile.h | 11 ++++++++++- 2 files changed, 42 insertions(+), 9 deletions(-) diff --git a/src/core/compile.c b/src/core/compile.c index 37d2491c..86f737ad 100644 --- a/src/core/compile.c +++ b/src/core/compile.c @@ -94,11 +94,21 @@ void janetc_freeslot(JanetCompiler *c, JanetSlot s) { void janetc_nameslot(JanetCompiler *c, const uint8_t *sym, JanetSlot s, uint32_t flags) { if (!(flags & JANET_DEFFLAG_NO_SHADOWCHECK)) { if (sym[0] != '_') { - int check = janetc_shadowcheck(c, sym); - if (check == 2) { - janetc_lintf(c, JANET_C_LINT_NORMAL, "binding %q is shadowing a local binding", janet_wrap_symbol(sym)); - } else if (check) { - janetc_lintf(c, JANET_C_LINT_NORMAL, "binding %q is shadowing a global binding", janet_wrap_symbol(sym)); + switch (janetc_shadowcheck(c, sym)) { + default: + break; + case JANETC_SHADOW_MACRO: + janetc_lintf(c, JANET_C_LINT_NORMAL, "binding %q is shadowing a macro", janet_wrap_symbol(sym)); + break; + case JANETC_SHADOW_LOCAL_HIDES_LOCAL: + janetc_lintf(c, JANET_C_LINT_STRICT, "binding %q is shadowing a binding", janet_wrap_symbol(sym)); + break; + case JANETC_SHADOW_LOCAL_HIDES_GLOBAL: + janetc_lintf(c, JANET_C_LINT_STRICT, "binding %q is shadowing a top-level binding", janet_wrap_symbol(sym)); + break; + case JANETC_SHADOW_GLOBAL_HIDES_GLOBAL: + janetc_lintf(c, JANET_C_LINT_STRICT, "top-level binding %q is shadowing another top-level binding", janet_wrap_symbol(sym)); + break; } } } @@ -261,20 +271,34 @@ static int lookup_missing( /* Check if a binding is defined in an upper scope. This lets us check for * variable shadowing. */ -int janetc_shadowcheck(JanetCompiler *c, const uint8_t *sym) { +Shadowing janetc_shadowcheck(JanetCompiler *c, const uint8_t *sym) { /* Check locals */ JanetScope *scope = c->scope; + int is_global = (scope->flags & JANET_SCOPE_TOP); while (scope) { int32_t len = janet_v_count(scope->syms); for (int32_t i = len - 1; i >= 0; i--) { SymPair *pair = scope->syms + i; - if (pair->sym == sym) return 2; + if (pair->sym == sym) { + janet_assert(!is_global, "shadowing analysis is incorrect. compiler bug"); + return JANETC_SHADOW_LOCAL_HIDES_LOCAL; + } } scope = scope->parent; } /* Check globals */ JanetBinding binding = janet_resolve_ext(c->env, sym); - return binding.type != JANET_BINDING_NONE; + if (binding.type == JANET_BINDING_MACRO || binding.type == JANET_BINDING_DYNAMIC_MACRO) { + return JANETC_SHADOW_MACRO; + } else if (binding.type == JANET_BINDING_NONE) { + return JANETC_SHADOW_NONE; + } else { + if (is_global) { + return JANETC_SHADOW_GLOBAL_HIDES_GLOBAL; + } else { + return JANETC_SHADOW_LOCAL_HIDES_GLOBAL; + } + } } /* Allow searching for symbols. Return information about the symbol */ diff --git a/src/core/compile.h b/src/core/compile.h index 45d15eb6..9f120013 100644 --- a/src/core/compile.h +++ b/src/core/compile.h @@ -36,6 +36,15 @@ typedef enum { JANET_C_LINT_STRICT } JanetCompileLintLevel; +/* Kinds of variable shadowing for linting */ +typedef enum { + JANETC_SHADOW_NONE, + JANETC_SHADOW_MACRO, + JANETC_SHADOW_GLOBAL_HIDES_GLOBAL, + JANETC_SHADOW_LOCAL_HIDES_GLOBAL, + JANETC_SHADOW_LOCAL_HIDES_LOCAL +} Shadowing; + /* Tags for some functions for the prepared inliner */ #define JANET_FUN_DEBUG 1 #define JANET_FUN_ERROR 2 @@ -276,7 +285,7 @@ JanetSlot janetc_cslot(Janet x); JanetSlot janetc_resolve(JanetCompiler *c, const uint8_t *sym); /* Check if a symbol is already in scope for shadowing lints */ -int janetc_shadowcheck(JanetCompiler *c, const uint8_t *sym); +Shadowing janetc_shadowcheck(JanetCompiler *c, const uint8_t *sym); /* Bytecode optimization */ void janet_bytecode_movopt(JanetFuncDef *def); From e42b3c667ff14bf6d2315916a83d6fa590e00cd5 Mon Sep 17 00:00:00 2001 From: Calvin Rose Date: Wed, 18 Mar 2026 17:09:12 -0500 Subject: [PATCH 214/307] Reduce maximum size of parse number to something more manageable. Parsing large number can have non-linear runtime behavior, and is messing with our fuzzing. --- src/core/strtod.c | 9 ++++----- 1 file changed, 4 insertions(+), 5 deletions(-) diff --git a/src/core/strtod.c b/src/core/strtod.c index 5c59fa10..20ae54ac 100644 --- a/src/core/strtod.c +++ b/src/core/strtod.c @@ -49,6 +49,8 @@ #include #include +#define JANET_NUMBER_LENGTH_RIDICULOUS 0xFFFF + /* Lookup table for getting values of characters when parsing numbers. Handles * digits 0-9 and a-z (and A-Z). A-Z have values of 10 to 35. */ static uint8_t digit_lookup[128] = { @@ -266,7 +268,7 @@ int janet_scan_number_base( * the decimal point, exponent could wrap around and become positive. It's * easier to reject ridiculously large inputs than to check for overflows. * */ - if (len > INT32_MAX / 40) goto error; + if (len > JANET_NUMBER_LENGTH_RIDICULOUS) goto error; /* Get sign */ if (str >= end) goto error; @@ -410,10 +412,7 @@ static int scan_uint64( *neg = 0; *out = 0; uint64_t accum = 0; - /* len max is INT64_MAX in base 2 with _ between each bits */ - /* '2r' + 64 bits + 63 _ + sign = 130 => 150 for some leading */ - /* zeros */ - if (len > 150) return 0; + if (len > JANET_NUMBER_LENGTH_RIDICULOUS) return 0; /* Get sign */ if (str >= end) return 0; if (*str == '-') { From ec5a78d3dc01c9865c985ccfe2433b0f36745022 Mon Sep 17 00:00:00 2001 From: Calvin Rose Date: Mon, 23 Mar 2026 09:03:08 -0500 Subject: [PATCH 215/307] Add pointer shift option for more nanboxing on arm64. We now try to detect this and do it by default. This is important as arm64 becomes more common and dominant. --- .github/workflows/test.yml | 2 +- meson.build | 3 +++ meson_options.txt | 1 + src/conf/janetconf.h | 1 + src/core/bytecode.c | 2 +- src/core/corelib.c | 2 +- src/core/inttypes.c | 26 +++++++++--------- src/core/util.c | 21 +++++++++++++++ src/core/wrap.c | 11 ++++++++ src/include/janet.h | 54 ++++++++++++++++++++++++++++---------- 10 files changed, 93 insertions(+), 30 deletions(-) diff --git a/.github/workflows/test.yml b/.github/workflows/test.yml index 8e2be6ad..6b56aadb 100644 --- a/.github/workflows/test.yml +++ b/.github/workflows/test.yml @@ -12,7 +12,7 @@ jobs: runs-on: ${{ matrix.os }} strategy: matrix: - os: [ ubuntu-latest, macos-latest, macos-14, macos-15-intel ] + os: [ ubuntu-latest, ubuntu-24.04-arm, macos-latest, macos-14, macos-15-intel ] steps: - name: Checkout the repository uses: actions/checkout@master diff --git a/meson.build b/meson.build index bc773371..2a30ca94 100644 --- a/meson.build +++ b/meson.build @@ -72,6 +72,9 @@ conf.set_quoted('JANET_VERSION', meson.project_version()) # Use options conf.set_quoted('JANET_BUILD', get_option('git_hash')) conf.set('JANET_NO_NANBOX', not get_option('nanbox')) +if get_option('nanbox_pointer_shift') != -1 # -1 is auto-detect + conf.set('JANET_NANBOX_64_POINTER_SHIFT', get_option('nanbox_pointer_shift')) +endif conf.set('JANET_SINGLE_THREADED', get_option('single_threaded')) conf.set('JANET_NO_DYNAMIC_MODULES', not get_option('dynamic_modules')) conf.set('JANET_NO_DOCSTRINGS', not get_option('docstrings')) diff --git a/meson_options.txt b/meson_options.txt index d055c713..3a833f4f 100644 --- a/meson_options.txt +++ b/meson_options.txt @@ -2,6 +2,7 @@ option('git_hash', type : 'string', value : 'meson') option('single_threaded', type : 'boolean', value : false) option('nanbox', type : 'boolean', value : true) +option('nanbox_pointer_shift', type : 'integer', min : -1, max : 4, value : -1) option('dynamic_modules', type : 'boolean', value : true) option('docstrings', type : 'boolean', value : true) option('sourcemaps', type : 'boolean', value : true) diff --git a/src/conf/janetconf.h b/src/conf/janetconf.h index 92eaaa29..618a4c2c 100644 --- a/src/conf/janetconf.h +++ b/src/conf/janetconf.h @@ -16,6 +16,7 @@ /* #define JANET_THREAD_LOCAL _Thread_local */ /* #define JANET_NO_DYNAMIC_MODULES */ /* #define JANET_NO_NANBOX */ +/* #define JANET_NANBOX_64_POINTER_SHIFT 0 */ /* #define JANET_API __attribute__((visibility ("default"))) */ /* These settings should be specified before amalgamation is diff --git a/src/core/bytecode.c b/src/core/bytecode.c index 478f269a..6f7f2131 100644 --- a/src/core/bytecode.c +++ b/src/core/bytecode.c @@ -29,7 +29,7 @@ #endif /* Look up table for instructions */ -enum JanetInstructionType janet_instructions[JOP_INSTRUCTION_COUNT] = { +const enum JanetInstructionType janet_instructions[JOP_INSTRUCTION_COUNT] = { JINT_0, /* JOP_NOOP, */ JINT_S, /* JOP_ERROR, */ JINT_ST, /* JOP_TYPECHECK, */ diff --git a/src/core/corelib.c b/src/core/corelib.c index e420b736..8c39268a 100644 --- a/src/core/corelib.c +++ b/src/core/corelib.c @@ -70,7 +70,7 @@ JanetModule janet_native(const char *name, const uint8_t **error) { host.minor != modconf.minor || host.bits != modconf.bits) { char errbuf[128]; - snprintf(errbuf, sizeof(errbuf), "config mismatch - host %d.%.d.%d(%.4x) vs. module %d.%d.%d(%.4x)", + snprintf(errbuf, sizeof(errbuf), "config mismatch - host %d.%.d.%d(%.4x) vs. module %d.%d.%d(%.4x) - native needs to be recompiled!", host.major, host.minor, host.patch, diff --git a/src/core/inttypes.c b/src/core/inttypes.c index fbf4fedd..ad659faf 100644 --- a/src/core/inttypes.c +++ b/src/core/inttypes.c @@ -333,7 +333,7 @@ static int compare_uint64_double(uint64_t x, double y) { } } -static Janet cfun_it_s64_compare(int32_t argc, Janet *argv) { +static JANET_CFUNCTION_ALIGN Janet cfun_it_s64_compare(int32_t argc, Janet *argv) { janet_fixarity(argc, 2); if (janet_is_int(argv[0]) != JANET_INT_S64) { janet_panic("compare method requires int/s64 as first argument"); @@ -368,7 +368,7 @@ static Janet cfun_it_s64_compare(int32_t argc, Janet *argv) { return janet_wrap_nil(); } -static Janet cfun_it_u64_compare(int32_t argc, Janet *argv) { +static JANET_CFUNCTION_ALIGN Janet cfun_it_u64_compare(int32_t argc, Janet *argv) { janet_fixarity(argc, 2); if (janet_is_int(argv[0]) != JANET_INT_U64) { janet_panic("compare method requires int/u64 as first argument"); @@ -416,7 +416,7 @@ static Janet cfun_it_u64_compare(int32_t argc, Janet *argv) { * This will not affect the end result (property of twos complement). */ #define OPMETHOD(T, type, name, oper) \ -static Janet cfun_it_##type##_##name(int32_t argc, Janet *argv) { \ +static JANET_CFUNCTION_ALIGN Janet cfun_it_##type##_##name(int32_t argc, Janet *argv) { \ janet_arity(argc, 2, -1); \ T *box = janet_abstract(&janet_##type##_type, sizeof(T)); \ *box = janet_unwrap_##type(argv[0]); \ @@ -427,7 +427,7 @@ static Janet cfun_it_##type##_##name(int32_t argc, Janet *argv) { \ } \ #define OPMETHODINVERT(T, type, name, oper) \ -static Janet cfun_it_##type##_##name##i(int32_t argc, Janet *argv) { \ +static JANET_CFUNCTION_ALIGN Janet cfun_it_##type##_##name##i(int32_t argc, Janet *argv) { \ janet_fixarity(argc, 2); \ T *box = janet_abstract(&janet_##type##_type, sizeof(T)); \ *box = janet_unwrap_##type(argv[1]); \ @@ -437,7 +437,7 @@ static Janet cfun_it_##type##_##name##i(int32_t argc, Janet *argv) { \ } \ #define UNARYMETHOD(T, type, name, oper) \ -static Janet cfun_it_##type##_##name(int32_t argc, Janet *argv) { \ +static JANET_CFUNCTION_ALIGN Janet cfun_it_##type##_##name(int32_t argc, Janet *argv) { \ janet_fixarity(argc, 1); \ T *box = janet_abstract(&janet_##type##_type, sizeof(T)); \ *box = oper(janet_unwrap_##type(argv[0])); \ @@ -450,7 +450,7 @@ static Janet cfun_it_##type##_##name(int32_t argc, Janet *argv) { \ #define DIVZERO_mod return janet_wrap_abstract(box) #define DIVMETHOD(T, type, name, oper) \ -static Janet cfun_it_##type##_##name(int32_t argc, Janet *argv) { \ +static JANET_CFUNCTION_ALIGN Janet cfun_it_##type##_##name(int32_t argc, Janet *argv) { \ janet_arity(argc, 2, -1); \ T *box = janet_abstract(&janet_##type##_type, sizeof(T)); \ *box = janet_unwrap_##type(argv[0]); \ @@ -463,7 +463,7 @@ static Janet cfun_it_##type##_##name(int32_t argc, Janet *argv) { \ } \ #define DIVMETHODINVERT(T, type, name, oper) \ -static Janet cfun_it_##type##_##name##i(int32_t argc, Janet *argv) { \ +static JANET_CFUNCTION_ALIGN Janet cfun_it_##type##_##name##i(int32_t argc, Janet *argv) { \ janet_fixarity(argc, 2); \ T *box = janet_abstract(&janet_##type##_type, sizeof(T)); \ *box = janet_unwrap_##type(argv[1]); \ @@ -474,7 +474,7 @@ static Janet cfun_it_##type##_##name##i(int32_t argc, Janet *argv) { \ } \ #define DIVMETHOD_SIGNED(T, type, name, oper) \ -static Janet cfun_it_##type##_##name(int32_t argc, Janet *argv) { \ +static JANET_CFUNCTION_ALIGN Janet cfun_it_##type##_##name(int32_t argc, Janet *argv) { \ janet_arity(argc, 2, -1); \ T *box = janet_abstract(&janet_##type##_type, sizeof(T)); \ *box = janet_unwrap_##type(argv[0]); \ @@ -488,7 +488,7 @@ static Janet cfun_it_##type##_##name(int32_t argc, Janet *argv) { \ } \ #define DIVMETHODINVERT_SIGNED(T, type, name, oper) \ -static Janet cfun_it_##type##_##name##i(int32_t argc, Janet *argv) { \ +static JANET_CFUNCTION_ALIGN Janet cfun_it_##type##_##name##i(int32_t argc, Janet *argv) { \ janet_fixarity(argc, 2); \ T *box = janet_abstract(&janet_##type##_type, sizeof(T)); \ *box = janet_unwrap_##type(argv[1]); \ @@ -499,7 +499,7 @@ static Janet cfun_it_##type##_##name##i(int32_t argc, Janet *argv) { \ return janet_wrap_abstract(box); \ } \ -static Janet cfun_it_s64_divf(int32_t argc, Janet *argv) { +static JANET_CFUNCTION_ALIGN Janet cfun_it_s64_divf(int32_t argc, Janet *argv) { janet_fixarity(argc, 2); int64_t *box = janet_abstract(&janet_s64_type, sizeof(int64_t)); int64_t op1 = janet_unwrap_s64(argv[0]); @@ -510,7 +510,7 @@ static Janet cfun_it_s64_divf(int32_t argc, Janet *argv) { return janet_wrap_abstract(box); } -static Janet cfun_it_s64_divfi(int32_t argc, Janet *argv) { +static JANET_CFUNCTION_ALIGN Janet cfun_it_s64_divfi(int32_t argc, Janet *argv) { janet_fixarity(argc, 2); int64_t *box = janet_abstract(&janet_s64_type, sizeof(int64_t)); int64_t op2 = janet_unwrap_s64(argv[0]); @@ -521,7 +521,7 @@ static Janet cfun_it_s64_divfi(int32_t argc, Janet *argv) { return janet_wrap_abstract(box); } -static Janet cfun_it_s64_mod(int32_t argc, Janet *argv) { +static JANET_CFUNCTION_ALIGN Janet cfun_it_s64_mod(int32_t argc, Janet *argv) { janet_fixarity(argc, 2); int64_t *box = janet_abstract(&janet_s64_type, sizeof(int64_t)); int64_t op1 = janet_unwrap_s64(argv[0]); @@ -535,7 +535,7 @@ static Janet cfun_it_s64_mod(int32_t argc, Janet *argv) { return janet_wrap_abstract(box); } -static Janet cfun_it_s64_modi(int32_t argc, Janet *argv) { +static JANET_CFUNCTION_ALIGN Janet cfun_it_s64_modi(int32_t argc, Janet *argv) { janet_fixarity(argc, 2); int64_t *box = janet_abstract(&janet_s64_type, sizeof(int64_t)); int64_t op2 = janet_unwrap_s64(argv[0]); diff --git a/src/core/util.c b/src/core/util.c index c3d3e2d7..66df0bf0 100644 --- a/src/core/util.c +++ b/src/core/util.c @@ -573,8 +573,24 @@ static char *namebuf_name(NameBuf *namebuf, const char *suffix) { return (char *)(namebuf->buf); } +/* Add a little bit of safety when using nanboxing on arm. Instead of inserting run-time checks everywhere, we are + * only doing it during registration which has much less cost (1 shift and mask). */ +static void janet_check_pointer_align(void *p) { + (void) p; +#if defined(JANET_NANBOX_64) && JANET_NANBOX_64_POINTER_SHIFT != 0 + union { + void *p; + uintptr_t u; + } un; + un.p = p; + janet_assert(!(un.u & (uintptr_t) ((1 << JANET_NANBOX_64_POINTER_SHIFT) - 1)), + "unaligned pointer wrap - cfunction pointers and abstract types must be aligned with this nanboxing configuration."); +#endif +} + void janet_cfuns(JanetTable *env, const char *regprefix, const JanetReg *cfuns) { while (cfuns->name) { + janet_check_pointer_align(cfuns->cfun); Janet fun = janet_wrap_cfunction(cfuns->cfun); if (env) janet_def(env, cfuns->name, fun, cfuns->documentation); janet_registry_put(cfuns->cfun, cfuns->name, regprefix, NULL, 0); @@ -584,6 +600,7 @@ void janet_cfuns(JanetTable *env, const char *regprefix, const JanetReg *cfuns) void janet_cfuns_ext(JanetTable *env, const char *regprefix, const JanetRegExt *cfuns) { while (cfuns->name) { + janet_check_pointer_align(cfuns->cfun); Janet fun = janet_wrap_cfunction(cfuns->cfun); if (env) janet_def_sm(env, cfuns->name, fun, cfuns->documentation, cfuns->source_file, cfuns->source_line); janet_registry_put(cfuns->cfun, cfuns->name, regprefix, cfuns->source_file, cfuns->source_line); @@ -595,6 +612,7 @@ void janet_cfuns_prefix(JanetTable *env, const char *regprefix, const JanetReg * NameBuf nb; if (env) namebuf_init(&nb, regprefix); while (cfuns->name) { + janet_check_pointer_align(cfuns->cfun); Janet fun = janet_wrap_cfunction(cfuns->cfun); if (env) janet_def(env, namebuf_name(&nb, cfuns->name), fun, cfuns->documentation); janet_registry_put(cfuns->cfun, cfuns->name, regprefix, NULL, 0); @@ -607,6 +625,7 @@ void janet_cfuns_ext_prefix(JanetTable *env, const char *regprefix, const JanetR NameBuf nb; if (env) namebuf_init(&nb, regprefix); while (cfuns->name) { + janet_check_pointer_align(cfuns->cfun); Janet fun = janet_wrap_cfunction(cfuns->cfun); if (env) janet_def_sm(env, namebuf_name(&nb, cfuns->name), fun, cfuns->documentation, cfuns->source_file, cfuns->source_line); janet_registry_put(cfuns->cfun, cfuns->name, regprefix, cfuns->source_file, cfuns->source_line); @@ -623,6 +642,7 @@ void janet_register(const char *name, JanetCFunction cfun) { /* Abstract type introspection */ void janet_register_abstract_type(const JanetAbstractType *at) { + janet_check_pointer_align((void *) at); Janet sym = janet_csymbolv(at->name); Janet check = janet_table_get(janet_vm.abstract_registry, sym); if (!janet_checktype(check, JANET_NIL) && at != janet_unwrap_pointer(check)) { @@ -655,6 +675,7 @@ void janet_core_def_sm(JanetTable *env, const char *name, Janet x, const void *p void janet_core_cfuns_ext(JanetTable *env, const char *regprefix, const JanetRegExt *cfuns) { (void) regprefix; while (cfuns->name) { + janet_check_pointer_align(cfuns->cfun); Janet fun = janet_wrap_cfunction(cfuns->cfun); janet_table_put(env, janet_csymbolv(cfuns->name), fun); janet_registry_put(cfuns->cfun, cfuns->name, regprefix, cfuns->source_file, cfuns->source_line); diff --git a/src/core/wrap.c b/src/core/wrap.c index 2881326d..d44620e9 100644 --- a/src/core/wrap.c +++ b/src/core/wrap.c @@ -194,12 +194,18 @@ Janet janet_wrap_number_safe(double d) { void *janet_nanbox_to_pointer(Janet x) { x.i64 &= JANET_NANBOX_PAYLOADBITS; + x.u64 <<= JANET_NANBOX_64_POINTER_SHIFT; /* Alignment, usually 0 */ return x.pointer; } Janet janet_nanbox_from_pointer(void *p, uint64_t tagmask) { Janet ret; ret.pointer = p; + /* Should be noop when pointer shift is 0 */ + /* + janet_assert(!(ret.u64 & (uint64_t) ((1 << JANET_NANBOX_64_POINTER_SHIFT) - 1)), "unaligned pointer wrap"); + */ + ret.u64 >>= JANET_NANBOX_64_POINTER_SHIFT; /* Alignment, usually 0 */ ret.u64 |= tagmask; return ret; } @@ -207,6 +213,11 @@ Janet janet_nanbox_from_pointer(void *p, uint64_t tagmask) { Janet janet_nanbox_from_cpointer(const void *p, uint64_t tagmask) { Janet ret; ret.pointer = (void *)p; + /* Should be noop when pointer shift is 0 */ + /* + janet_assert(!(ret.u64 & (uint64_t) ((1 << JANET_NANBOX_64_POINTER_SHIFT) - 1)), "unaligned pointer wrap"); + */ + ret.u64 >>= JANET_NANBOX_64_POINTER_SHIFT; /* Alignment, usually 0 */ ret.u64 |= tagmask; return ret; } diff --git a/src/include/janet.h b/src/include/janet.h index ac2e3019..eb356024 100644 --- a/src/include/janet.h +++ b/src/include/janet.h @@ -307,25 +307,38 @@ extern "C" { * architectures (Nanboxing only tested on x86 and x64), comment out * the JANET_NANBOX define.*/ -#if defined(_M_ARM64) || defined(_M_ARM) || defined(__aarch64__) -#define JANET_NO_NANBOX -#endif - #ifndef JANET_NO_NANBOX #ifdef JANET_32 #define JANET_NANBOX_32 -#elif defined(__x86_64__) || defined(_WIN64) || defined(__riscv) +#elif defined(__x86_64__) || defined(_WIN64) || defined(__riscv) || defined(__aarch64__) || defined(_M_ARM64) /* We will only enable nanboxing by default on 64 bit systems - * for x64 and risc-v. This is mainly because the approach is tied to the + * for x64, risc-v, and arm64. This is mainly because the approach is tied to the * implicit 47 bit address space. Many arches allow/require this, but not all, - * and it requires cooperation from the OS. ARM should also work in many configurations. */ + * and it requires cooperation from the OS. ARM should also work in many configurations by taking advantage + * of pointer alignment to allow for 48 or 49 bits of address space. */ #define JANET_NANBOX_64 + +/* Allow 64-bit nanboxing to assume aligned pointers to get back some extra bits for representation. + * This is needed to use nanboxing on systems with larger than 47-bit address spaces, such as many + * aarch64 systems. */ +#ifndef JANET_NANBOX_64_POINTER_SHIFT +#if (defined(_M_ARM64) || defined(__aarch64__)) && !defined(JANET_APPLE) +/* All pointers, including function pointers, should be 4-byte aligned on aarch64 by default. + * The exception is aarch64 macos, as it uses the same 47-bit userland address-space as on amd64. */ +#define JANET_NANBOX_64_POINTER_SHIFT 2 #endif #endif +#endif +#endif + +/* Allow for custom pointer alignment as well */ +#if defined(JANET_NANBOX_64) && !defined(JANET_NANBOX_64_POINTER_SHIFT) +#define JANET_NANBOX_64_POINTER_SHIFT 0 +#endif /* Runtime config constants */ #ifdef JANET_NO_NANBOX -#define JANET_NANBOX_BIT 0 +#define JANET_NANBOX_BIT 0x0 #else #define JANET_NANBOX_BIT 0x1 #endif @@ -336,9 +349,16 @@ extern "C" { #define JANET_SINGLE_THREADED_BIT 0 #endif +#ifdef JANET_NANBOX_64_POINTER_SHIFT +#define JANET_NANBOX_POINTER_SHIFT_BITS (JANET_NANBOX_64_POINTER_SHIFT ? (0x4 << JANET_NANBOX_64_POINTER_SHIFT) : 0) +#else +#define JANET_NANBOX_POINTER_SHIFT_BITS 0 +#endif + #define JANET_CURRENT_CONFIG_BITS \ (JANET_SINGLE_THREADED_BIT | \ - JANET_NANBOX_BIT) + JANET_NANBOX_BIT | \ + JANET_NANBOX_POINTER_SHIFT_BITS) /* Represents the settings used to compile Janet, as well as the version */ typedef struct { @@ -1415,7 +1435,7 @@ enum JanetOpCode { }; /* Info about all instructions */ -extern enum JanetInstructionType janet_instructions[JOP_INSTRUCTION_COUNT]; +extern const enum JanetInstructionType janet_instructions[JOP_INSTRUCTION_COUNT]; /***** END SECTION OPCODES *****/ @@ -2063,8 +2083,14 @@ JANET_API Janet janet_resolve_core(const char *name); * * */ +#if defined(JANET_NANBOX_64) && (JANET_NANBOX_64_POINTER_SHIFT != 0) && !defined(JANET_MSVC) +#define JANET_CFUNCTION_ALIGN __attribute__((aligned(1 << JANET_NANBOX_64_POINTER_SHIFT))) +#else +#define JANET_CFUNCTION_ALIGN +#endif + /* Shorthand for janet C function declarations */ -#define JANET_CFUN(name) Janet name (int32_t argc, Janet *argv) +#define JANET_CFUN(name) JANET_CFUNCTION_ALIGN Janet name (int32_t argc, Janet *argv) /* Declare a C function with documentation and source mapping */ #define JANET_REG_END {NULL, NULL, NULL, NULL, 0} @@ -2080,7 +2106,7 @@ JANET_API Janet janet_resolve_core(const char *name); #define JANET_REG_S(JNAME, CNAME) {JNAME, CNAME, NULL, __FILE__, CNAME##_sourceline_} #define JANET_FN_S(CNAME, USAGE, DOCSTRING) \ static const int32_t CNAME##_sourceline_ = __LINE__; \ - Janet CNAME (int32_t argc, Janet *argv) + Janet JANET_CFUNCTION_ALIGN CNAME (int32_t argc, Janet *argv) #define JANET_DEF_S(ENV, JNAME, VAL, DOC) \ janet_def_sm(ENV, JNAME, VAL, NULL, __FILE__, __LINE__) @@ -2088,7 +2114,7 @@ JANET_API Janet janet_resolve_core(const char *name); #define JANET_REG_D(JNAME, CNAME) {JNAME, CNAME, CNAME##_docstring_, NULL, 0} #define JANET_FN_D(CNAME, USAGE, DOCSTRING) \ static const char CNAME##_docstring_[] = USAGE "\n\n" DOCSTRING; \ - Janet CNAME (int32_t argc, Janet *argv) + Janet JANET_CFUNCTION_ALIGN CNAME (int32_t argc, Janet *argv) #define JANET_DEF_D(ENV, JNAME, VAL, DOC) \ janet_def(ENV, JNAME, VAL, DOC) @@ -2097,7 +2123,7 @@ JANET_API Janet janet_resolve_core(const char *name); #define JANET_FN_SD(CNAME, USAGE, DOCSTRING) \ static const int32_t CNAME##_sourceline_ = __LINE__; \ static const char CNAME##_docstring_[] = USAGE "\n\n" DOCSTRING; \ - Janet CNAME (int32_t argc, Janet *argv) + Janet JANET_CFUNCTION_ALIGN CNAME (int32_t argc, Janet *argv) #define JANET_DEF_SD(ENV, JNAME, VAL, DOC) \ janet_def_sm(ENV, JNAME, VAL, DOC, __FILE__, __LINE__) From 29474b915dd8d97138199dd56b51fd89220c8679 Mon Sep 17 00:00:00 2001 From: Calvin Rose Date: Wed, 25 Mar 2026 16:45:43 -0500 Subject: [PATCH 216/307] Begin work on adding kqueue functionality to filewatch. kqueue-based filewatch is more limited than inotify, but is still useful in many cases. Notably absent is any ability to monitor directories automatically without wasting a lot of file descriptors. --- CHANGELOG.md | 2 + examples/filewatch.janet | 14 ++ src/core/ev.c | 3 +- src/core/filewatch.c | 294 ++++++++++++++++++++++++++++++++++++- src/core/util.c | 2 +- src/core/util.h | 4 +- test/suite-filewatch.janet | 30 ++++ 7 files changed, 338 insertions(+), 11 deletions(-) create mode 100644 examples/filewatch.janet diff --git a/CHANGELOG.md b/CHANGELOG.md index 40d38651..af01adaf 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -2,6 +2,8 @@ All notable changes to this project will be documented in this file. ## Unreleased - ??? +- Add linting support for shadowed bindings. +- Add nanboxing support for Linux on ARM64 and turn on nanboxing by default on macos on ARM64 (aarch64). - Documentation fixes - ev/thread-chan deadlock bug fixed - Re-add removed support for non-blocking net/connect on windows. diff --git a/examples/filewatch.janet b/examples/filewatch.janet new file mode 100644 index 00000000..0f0c99b1 --- /dev/null +++ b/examples/filewatch.janet @@ -0,0 +1,14 @@ +### +### example/filewatch.janet ...files +### +### Watch for all changes in a list of files and directories. Behavior +### depends on the filewatch module, and different operating systems will +### report different events. + +(def chan (ev/chan 1000)) +(def fw (filewatch/new chan)) +(each arg (drop 1 (dyn *args* [])) + (filewatch/add fw arg :all)) +(filewatch/listen fw) + +(forever (let [event (ev/take chan)] (pp event))) diff --git a/src/core/ev.c b/src/core/ev.c index b7077ffb..bbae9016 100644 --- a/src/core/ev.c +++ b/src/core/ev.c @@ -1962,7 +1962,7 @@ void janet_stream_level_triggered(JanetStream *stream) { janet_register_stream_impl(stream, 0); } -#define JANET_KQUEUE_MAX_EVENTS 64 +#define JANET_KQUEUE_MAX_EVENTS 512 void janet_loop1_impl(int has_timeout, JanetTimestamp timeout) { /* Poll for events */ @@ -2026,6 +2026,7 @@ void janet_loop1_impl(int has_timeout, JanetTimestamp timeout) { void janet_ev_init(void) { janet_ev_init_common(); + /* TODO - replace selfpipe with EVFILT_USER (or other events) */ janet_ev_setup_selfpipe(); janet_vm.kq = kqueue(); janet_vm.timer_enabled = 0; diff --git a/src/core/filewatch.c b/src/core/filewatch.c index a5f3785c..e9c790b3 100644 --- a/src/core/filewatch.c +++ b/src/core/filewatch.c @@ -38,6 +38,13 @@ #include #endif +#if defined(JANET_APPLE) || defined(JANET_BSD) +#include +#include +#include +#include +#endif + typedef struct { const char *name; uint32_t flag; @@ -89,7 +96,7 @@ static uint32_t decode_watch_flags(const Janet *options, int32_t n) { sizeof(JanetWatchFlagName), keyw); if (!result) { - janet_panicf("unknown inotify flag %v", options[i]); + janet_panicf("unknown linux flag %v", options[i]); } flags |= result->flag; } @@ -128,8 +135,11 @@ static void janet_watcher_add(JanetWatcher *watcher, const char *path, uint32_t static void janet_watcher_remove(JanetWatcher *watcher, const char *path) { if (watcher->stream == NULL) janet_panic("watcher closed"); - Janet check = janet_table_get(watcher->watch_descriptors, janet_cstringv(path)); - janet_assert(janet_checktype(check, JANET_NUMBER), "bad watch descriptor"); + Janet pathv = janet_cstringv(path); + Janet check = janet_table_get(watcher->watch_descriptors, pathv); + if (!janet_checktype(check, JANET_NUMBER)) { + janet_panic("bad watch descriptor"); + } int watch_handle = janet_unwrap_integer(check); int result; do { @@ -138,6 +148,10 @@ static void janet_watcher_remove(JanetWatcher *watcher, const char *path) { if (result == -1) { janet_panicv(janet_ev_lasterr()); } + /* + janet_table_put(watcher->watch_descriptors, pathv, janet_wrap_nil()); + janet_table_put(watcher->watch_descriptors, janet_wrap_integer(watch_handle), janet_wrap_nil()); + */ } static void watcher_callback_read(JanetFiber *fiber, JanetAsyncEvent event) { @@ -500,6 +514,254 @@ static void janet_watcher_unlisten(JanetWatcher *watcher) { janet_gcunroot(janet_wrap_abstract(watcher)); } +#elif defined(JANET_APPLE) || defined(JANET_BSD) + +/* kqueue implementation */ + +/* Cribbed from ev.c */ +#define EV_SETx(ev, a, b, c, d, e, f) EV_SET((ev), (a), (b), (c), (d), (e), ((__typeof__((ev)->udata))(f))) + +/* Different BSDs define different NOTE_* constants for different kinds of events. Use ifdef to + determine when they are available (assuming they are defines and not enums */ +static const JanetWatchFlagName watcher_flags_kqueue[] = { + { + "all", NOTE_ATTRIB | NOTE_DELETE | NOTE_EXTEND | NOTE_RENAME | NOTE_REVOKE | NOTE_WRITE | NOTE_LINK +#ifdef NOTE_CLOSE + | NOTE_CLOSE +#endif +#ifdef NOTE_CLOSE_WRITE + | NOTE_CLOSE_WRITE +#endif +#ifdef NOTE_OPEN + | NOTE_OPEN +#endif +#ifdef NOTE_READ + | NOTE_READ +#endif +#ifdef NOTE_FUNLOCK + | NOTE_FUNLOCK +#endif +#ifdef NOTE_TRUNCATE + | NOTE_TRUNCATE +#endif + }, + {"attrib", NOTE_ATTRIB}, +#ifdef NOTE_CLOSE + {"close", NOTE_CLOSE}, +#endif +#ifdef NOTE_CLOSE_WRITE + {"close-write", NOTE_CLOSE_WRITE}, +#endif + {"delete", NOTE_DELETE}, + {"extend", NOTE_EXTEND}, +#ifdef NOTE_FUNLOCK + {"funlock", NOTE_FUNLOCK}, +#endif + {"link", NOTE_LINK}, +#ifdef NOTE_OPEN + {"open", NOTE_OPEN}, +#endif +#ifdef NOTE_READ + {"read", NOTE_READ}, +#endif + {"rename", NOTE_RENAME}, + {"revoke", NOTE_REVOKE}, +#ifdef NOTE_TRUNCATE + {"truncate", NOTE_TRUNCATE}, +#endif + {"write", NOTE_WRITE}, +}; + +static uint32_t decode_watch_flags(const Janet *options, int32_t n) { + uint32_t flags = 0; + for (int32_t i = 0; i < n; i++) { + if (!(janet_checktype(options[i], JANET_KEYWORD))) { + janet_panicf("expected keyword, got %v", options[i]); + } + JanetKeyword keyw = janet_unwrap_keyword(options[i]); + const JanetWatchFlagName *result = janet_strbinsearch(watcher_flags_kqueue, + sizeof(watcher_flags_kqueue) / sizeof(JanetWatchFlagName), + sizeof(JanetWatchFlagName), + keyw); + if (!result) { + janet_panicf("unknown bsd flag %v", options[i]); + } + flags |= result->flag; + } + return flags; +} + +static void janet_watcher_init(JanetWatcher *watcher, JanetChannel *channel, uint32_t default_flags) { + int kq = kqueue(); + watcher->watch_descriptors = janet_table(0); + watcher->channel = channel; + watcher->default_flags = default_flags; + watcher->is_watching = 0; + watcher->stream = janet_stream(kq, JANET_STREAM_READABLE, NULL); + janet_stream_level_triggered(watcher->stream); +} + +static void janet_watcher_add(JanetWatcher *watcher, const char *path, uint32_t flags) { + if (watcher->stream == NULL) janet_panic("watcher closed"); + int kq = watcher->stream->handle; + struct kevent kev = {0}; + /* Get file descriptor for path */ + int file_fd; + do { + file_fd = open(path, O_RDONLY); + } while (file_fd == -1 && errno == EINTR); + if (file_fd == -1) { + janet_panicf("failed to open: %v", janet_ev_lasterr()); + } + /* Watch for EVFILT_VNODE on the file descriptor */ + EV_SETx(&kev, file_fd, EVFILT_VNODE, EV_ADD | EV_ENABLE | EV_CLEAR, flags, 0, NULL); + int status; + do { + status = kevent(kq, &kev, 1, NULL, 0, NULL); + } while (status == -1 && errno == EINTR); + if (status == -1) { + close(file_fd); + janet_panicf("failed to listen: %v", janet_ev_lasterr()); + } + /* Bookkeeping */ + Janet name = janet_cstringv(path); + Janet wd = janet_wrap_integer(file_fd); + janet_table_put(watcher->watch_descriptors, name, wd); + janet_table_put(watcher->watch_descriptors, wd, name); +} + +static void janet_watcher_remove(JanetWatcher *watcher, const char *path) { + if (watcher->stream == NULL) janet_panic("watcher closed"); + Janet pathv = janet_cstringv(path); + Janet check = janet_table_get(watcher->watch_descriptors, pathv); + if (!janet_checktype(check, JANET_NUMBER)) { + janet_panic("bad watch descriptor"); + } + /* Closing the file descriptor will also remove it from the kqueue */ + int wd = janet_unwrap_integer(check); + int result; + do { + result = close(wd); + } while (result != -1 && errno == EINTR); + if (result == -1) { + janet_panicv(janet_ev_lasterr()); + } + janet_table_put(watcher->watch_descriptors, pathv, janet_wrap_nil()); + janet_table_put(watcher->watch_descriptors, janet_wrap_integer(wd), janet_wrap_nil()); +} + +typedef struct { + JanetWatcher *watcher; + uint32_t cookie; +} KqueueWatcherState; + +static void watcher_callback_read(JanetFiber *fiber, JanetAsyncEvent event) { + JanetStream *stream = fiber->ev_stream; + KqueueWatcherState *state = fiber->ev_state; + JanetWatcher *watcher = state->watcher; + switch (event) { + case JANET_ASYNC_EVENT_MARK: + janet_mark(janet_wrap_abstract(watcher)); + break; + case JANET_ASYNC_EVENT_CLOSE: + janet_schedule(fiber, janet_wrap_nil()); + janet_async_end(fiber); + break; + case JANET_ASYNC_EVENT_ERR: { + janet_schedule(fiber, janet_wrap_nil()); + janet_async_end(fiber); + break; + } + case JANET_ASYNC_EVENT_HUP: + case JANET_ASYNC_EVENT_INIT: + break; + case JANET_ASYNC_EVENT_READ: { + /* Pump events from the sub kqueue */ + const int num_events = 512; /* Extra will be pumped after another event loop rotation. */ + struct kevent events[num_events]; + int kq = stream->handle; + int status; + do { + status = kevent(kq, NULL, 0, events, num_events, NULL); + } while (status == -1 && errno == EINTR); + if (status == -1) { + janet_schedule(fiber, janet_wrap_nil()); + janet_async_end(fiber); + break; + } + for (int i = 0; i < status; i++) { + state->cookie += 6700417; + struct kevent kev = events[i]; + /* TODO - avoid stat call here, maybe just when adding listener? */ + struct stat stat_buf = {0}; + int status; + do { + status = fstat(kev.ident, &stat_buf); + } while (status == -1 && errno == EINTR); + if (status == -1) continue; + int is_dir = S_ISDIR(stat_buf.st_mode); + Janet ident = janet_wrap_integer(kev.ident); + Janet path = janet_table_get(watcher->watch_descriptors, ident); + for (unsigned int j = 1; j < (sizeof(watcher_flags_kqueue) / sizeof(watcher_flags_kqueue[0])); j++) { + uint32_t flagcheck = watcher_flags_kqueue[j].flag; + if (kev.fflags & flagcheck) { + JanetKV *event = janet_struct_begin(6); + janet_struct_put(event, janet_ckeywordv("wd"), ident); + janet_struct_put(event, janet_ckeywordv("wd-path"), path); + janet_struct_put(event, janet_ckeywordv("cookie"), janet_wrap_number((double) state->cookie)); + janet_struct_put(event, janet_ckeywordv("type"), janet_ckeywordv(watcher_flags_kqueue[j].name)); + if (is_dir) { + /* Pass in directly */ + janet_struct_put(event, janet_ckeywordv("file-name"), janet_cstringv("")); + janet_struct_put(event, janet_ckeywordv("dir-name"), path); + } else { + /* Split path */ + JanetString spath = janet_unwrap_string(path); + const uint8_t *cursor = spath + janet_string_length(spath); + const uint8_t *cursor_end = cursor; + while (cursor > spath && cursor[0] != '/') { + cursor--; + } + if (cursor == spath) { + /* No path separators */ + janet_struct_put(event, janet_ckeywordv("dir-name"), janet_cstringv(".")); + janet_struct_put(event, janet_ckeywordv("file-name"), janet_wrap_string(spath)); + } else { + /* Found path separator */ + janet_struct_put(event, janet_ckeywordv("dir-name"), janet_wrap_string(janet_string(spath, (cursor - spath)))); + janet_struct_put(event, janet_ckeywordv("file-name"), janet_wrap_string(janet_string(cursor + 1, (cursor_end - cursor - 1)))); + } + } + Janet eventv = janet_wrap_struct(janet_struct_end(event)); + janet_channel_give(watcher->channel, eventv); + } + } + } + break; + } + default: + break; + } +} + +static void janet_watcher_listen(JanetWatcher *watcher) { + if (watcher->is_watching) janet_panic("already watching"); + watcher->is_watching = 1; + JanetFunction *thunk = janet_thunk_delay(janet_wrap_nil()); + JanetFiber *fiber = janet_fiber(thunk, 64, 0, NULL); + KqueueWatcherState *state = janet_malloc(sizeof(KqueueWatcherState)); + state->watcher = watcher; + janet_async_start_fiber(fiber, watcher->stream, JANET_ASYNC_LISTEN_READ, watcher_callback_read, state); + janet_gcroot(janet_wrap_abstract(watcher)); +} + +static void janet_watcher_unlisten(JanetWatcher *watcher) { + if (!watcher->is_watching) return; + watcher->is_watching = 0; + janet_stream_close(watcher->stream); + janet_gcunroot(janet_wrap_abstract(watcher)); +} + #else /* Default implementation */ @@ -582,10 +844,10 @@ JANET_CORE_FN(cfun_filewatch_make, "* `:dir-name` -- the directory name of the file that triggered the event.\n\n" "Events also will contain keys specific to the host OS.\n\n" "Windows has no extra properties on events.\n\n" - "Linux has the following extra properties on events:\n\n" - "* `:wd` -- the integer key returned by `filewatch/add` for the path that triggered this.\n\n" + "Linux and the BSDs have the following extra properties on events:\n\n" + "* `:wd` -- the integer key returned by `filewatch/add` for the path that triggered this. This is a file descriptor integer on BSD and macos.\n\n" "* `:wd-path` -- the string path for watched directory of file. For files, will be the same as `:file-name`, and for directories, will be the same as `:dir-name`.\n\n" - "* `:cookie` -- a randomized integer used to associate related events, such as :moved-from and :moved-to events.\n\n" + "* `:cookie` -- a semi-randomized integer used to associate related events, such as :moved-from and :moved-to events.\n\n" "") { janet_sandbox_assert(JANET_SANDBOX_FS_READ); janet_arity(argc, 1, -1); @@ -600,6 +862,7 @@ JANET_CORE_FN(cfun_filewatch_add, "(filewatch/add watcher path flag & more-flags)", "Add a path to the watcher. Available flags depend on the current OS, and are as follows:\n\n" "Windows/MINGW (flags correspond to `FILE_NOTIFY_CHANGE_*` flags in win32 documentation):\n\n" + "FLAGS\n\n" "* `:all` - trigger an event for all of the below triggers.\n\n" "* `:attributes` - `FILE_NOTIFY_CHANGE_ATTRIBUTES`\n\n" "* `:creation` - `FILE_NOTIFY_CHANGE_CREATION`\n\n" @@ -626,6 +889,22 @@ JANET_CORE_FN(cfun_filewatch_add, "* `:open` - `IN_OPEN`\n\n" "* `:q-overflow` - `IN_Q_OVERFLOW`\n\n" "* `:unmount` - `IN_UNMOUNT`\n\n\n" + "BSDs and macos (flags correspond to `NOTE_*` flags from ). Not all flags are available on all systems:\n\n" + "* `:all` - `All available NOTE_* flags on the current platform`\n\n" + "* `:attrib` - `NOTE_ATTRIB`\n\n" + "* `:close-write` - `NOTE_CLOSE_WRITE`\n\n" + "* `:close` - `NOTE_CLOSE`\n\n" + "* `:delete` - `NOTE_DELETE`\n\n" + "* `:extend` - `NOTE_EXTEND`\n\n" + "* `:funlock` - `NOTE_FUNLOCK`\n\n" + "* `:link` - `NOTE_LINK`\n\n" + "* `:open` - `NOTE_OPEN`\n\n" + "* `:read` - `NOTE_READ`\n\n" + "* `:rename` - `NOTE_RENAME`\n\n" + "* `:revoke` - `NOTE_REVOKE`\n\n" + "* `:truncate` - `NOTE_TRUNCATE`\n\n" + "* `:write` - `NOTE_WRITE`\n\n\n" + "EVENT TYPES\n\n" "On Windows, events will have the following possible types:\n\n" "* `:unknown`\n\n" "* `:added`\n\n" @@ -633,7 +912,7 @@ JANET_CORE_FN(cfun_filewatch_add, "* `:modified`\n\n" "* `:renamed-old`\n\n" "* `:renamed-new`\n\n" - "On Linux, events will have a `:type` corresponding to the possible flags, excluding `:all`.\n" + "On Linux and BSDs, events will have a `:type` corresponding to the possible flags, excluding `:all`.\n" "") { janet_arity(argc, 2, -1); JanetWatcher *watcher = janet_getabstract(argv, 0, &janet_filewatch_at); @@ -648,6 +927,7 @@ JANET_CORE_FN(cfun_filewatch_remove, "Remove a path from the watcher.") { janet_fixarity(argc, 2); JanetWatcher *watcher = janet_getabstract(argv, 0, &janet_filewatch_at); + /* TODO - pass string in directly to avoid extra allocation */ const char *path = janet_getcstring(argv, 1); janet_watcher_remove(watcher, path); return argv[0]; diff --git a/src/core/util.c b/src/core/util.c index 66df0bf0..66498384 100644 --- a/src/core/util.c +++ b/src/core/util.c @@ -584,7 +584,7 @@ static void janet_check_pointer_align(void *p) { } un; un.p = p; janet_assert(!(un.u & (uintptr_t) ((1 << JANET_NANBOX_64_POINTER_SHIFT) - 1)), - "unaligned pointer wrap - cfunction pointers and abstract types must be aligned with this nanboxing configuration."); + "unaligned pointer wrap - cfunction pointers and abstract types must be aligned with this nanboxing configuration."); #endif } diff --git a/src/core/util.h b/src/core/util.h index 7702b275..d0adc0dd 100644 --- a/src/core/util.h +++ b/src/core/util.h @@ -50,9 +50,9 @@ #ifndef JANET_EXIT #include #define JANET_EXIT(m) do { \ - fprintf(stderr, "janet internal error at line %d in file %s: %s\n",\ - __LINE__,\ + fprintf(stderr, "janet abort at %s:%d: %s\n",\ __FILE__,\ + __LINE__,\ (m));\ abort();\ } while (0) diff --git a/test/suite-filewatch.janet b/test/suite-filewatch.janet index fb5e3894..c8f9f19d 100644 --- a/test/suite-filewatch.janet +++ b/test/suite-filewatch.janet @@ -26,6 +26,8 @@ (def chan (ev/chan 1000)) (var is-win (or (= :mingw (os/which)) (= :windows (os/which)))) (var is-linux (= :linux (os/which))) +(def bsds [:freebsd :macos :openbsd :bsd :dragonfly :netbsd]) +(var is-kqueue (index-of (os/which) bsds)) # If not supported, exit early (def [supported msg] (protect (filewatch/new chan))) @@ -97,6 +99,10 @@ (filewatch/add fw (string td3 "/file3.txt") :close-write :create :delete) (filewatch/add fw td1 :close-write :create :delete) (filewatch/add fw td2 :close-write :create :delete :ignored)) +(when is-kqueue + (filewatch/add fw (string td3 "/file3.txt") :all) + (filewatch/add fw td1 :all) + (filewatch/add fw td2 :all)) (assert-no-error "filewatch/listen no error" (filewatch/listen fw)) # @@ -196,6 +202,30 @@ (expect-empty) (gccollect)) +# +# Macos and BSD file writing +# + +# TODO - kqueue capabilities here are a bit more limited than inotify and windows by default. +# This could be ammended with some heavier-weight functionality in userspace, though. +(when is-kqueue + (spit-file td1 "file1.txt") + (expect :wd-path td1 :type :write) + (expect-empty) + (gccollect) + (spit-file td1 "file1.txt") + # Currently, only operations that modify the parent vnode do anything + (expect-empty) + (gccollect) + # Check that we don't get anymore events from test directory 2 + (spit-file td2 "file2.txt") + (expect :wd-path td2 :type :write) + (expect-empty) + # Remove a file, then wait for remove event + (rmrf (string td1 "/file1.txt")) + (expect :type :write) # a "write" to the vnode + (expect-empty)) + (assert-no-error "filewatch/unlisten no error" (filewatch/unlisten fw)) (assert-no-error "cleanup 1" (rmrf td1)) (assert-no-error "cleanup 2" (rmrf td2)) From 16f4f40d8e01cd64c24d7135eb4689865c940518 Mon Sep 17 00:00:00 2001 From: Calvin Rose Date: Fri, 27 Mar 2026 11:34:50 -0500 Subject: [PATCH 217/307] Update CHANGELOG --- CHANGELOG.md | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index af01adaf..e5579639 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -2,11 +2,12 @@ All notable changes to this project will be documented in this file. ## Unreleased - ??? +- Add filewatch support to BSD and macos. - Add linting support for shadowed bindings. - Add nanboxing support for Linux on ARM64 and turn on nanboxing by default on macos on ARM64 (aarch64). - Documentation fixes - ev/thread-chan deadlock bug fixed -- Re-add removed support for non-blocking net/connect on windows. +- Re-add removed support for non-blocking net/connect on windows with bug fixes. ## 1.41.2 - 2026-02-18 - Fix regressions in `put` for arrays and buffers. From c67dee73292f7117c2b1e534a2f52592a3e0174d Mon Sep 17 00:00:00 2001 From: Calvin Rose Date: Sat, 28 Mar 2026 09:47:46 -0500 Subject: [PATCH 218/307] strncpy mitigations in mainclient.c --- src/mainclient/shell.c | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/src/mainclient/shell.c b/src/mainclient/shell.c index b0f73915..550c52c3 100644 --- a/src/mainclient/shell.c +++ b/src/mainclient/shell.c @@ -519,8 +519,8 @@ static void historymove(int delta) { } else if (gbl_historyi >= gbl_history_count) { gbl_historyi = gbl_history_count - 1; } + gbl_pos = gbl_len = (int) strlen(gbl_history[gbl_historyi]); strncpy(gbl_buf, gbl_history[gbl_historyi], JANET_LINE_MAX - 1); - gbl_pos = gbl_len = (int) strlen(gbl_buf); gbl_buf[gbl_len] = '\0'; refresh(); @@ -1240,6 +1240,7 @@ int main(int argc, char **argv) { #endif if (NULL != envvar) { strncpy((char *) hash_key, envvar, sizeof(hash_key) - 1); + hash_key[JANET_HASH_KEY_SIZE] = '\0'; /* in case copy didn't get null byte */ } else if (janet_cryptorand(hash_key, JANET_HASH_KEY_SIZE) != 0) { fputs("unable to initialize janet PRF hash function.\n", stderr); return 1; From 19e1dc494d57d932407abd34220dcc770ae5cf55 Mon Sep 17 00:00:00 2001 From: Calvin Rose Date: Sat, 28 Mar 2026 18:22:25 -0500 Subject: [PATCH 219/307] Mitigation against mutated repl history. --- src/mainclient/shell.c | 9 +++++++-- 1 file changed, 7 insertions(+), 2 deletions(-) diff --git a/src/mainclient/shell.c b/src/mainclient/shell.c index 550c52c3..cd918a7e 100644 --- a/src/mainclient/shell.c +++ b/src/mainclient/shell.c @@ -519,7 +519,12 @@ static void historymove(int delta) { } else if (gbl_historyi >= gbl_history_count) { gbl_historyi = gbl_history_count - 1; } - gbl_pos = gbl_len = (int) strlen(gbl_history[gbl_historyi]); + gbl_len = (int) strlen(gbl_history[gbl_historyi]); + /* If history element is longer the JANET_LINE_MAX - 1, truncate */ + if (gbl_len > JANET_LINE_MAX - 1) { + gbl_len = JANET_LINE_MAX - 1; + } + gbl_pos = gbl_len; strncpy(gbl_buf, gbl_history[gbl_historyi], JANET_LINE_MAX - 1); gbl_buf[gbl_len] = '\0'; @@ -1232,7 +1237,7 @@ int main(int argc, char **argv) { #endif #if defined(JANET_PRF) - uint8_t hash_key[JANET_HASH_KEY_SIZE + 1]; + uint8_t hash_key[JANET_HASH_KEY_SIZE + 1] = {0}; #ifdef JANET_REDUCED_OS char *envvar = NULL; #else From 0c512ab1280949b24fb51d9efc126ec7ed0b123f Mon Sep 17 00:00:00 2001 From: McSinyx Date: Thu, 2 Apr 2026 08:55:22 +0900 Subject: [PATCH 220/307] Increase pretty printing line density (#1733) * Increase pretty printing line density * Allow specifying width for multiline pretty format --- src/core/pp.c | 84 +++++++++++++++++++++++++++++++++++++++++---- src/core/string.c | 4 ++- test/suite-pp.janet | 33 ++++++------------ 3 files changed, 90 insertions(+), 31 deletions(-) diff --git a/src/core/pp.c b/src/core/pp.c index 16fd51a4..327003ac 100644 --- a/src/core/pp.c +++ b/src/core/pp.c @@ -374,7 +374,9 @@ const uint8_t *janet_to_string(Janet x) { struct pretty { JanetBuffer *buffer; int depth; + int width; int align; + int leaf_align; int flags; int32_t bufstartlen; int32_t *keysort_buffer; @@ -466,14 +468,64 @@ static int print_jdn_one(struct pretty *S, Janet x, int depth) { return 0; } +static void backtrack_newlines(const struct pretty *S) { + if (S->flags & JANET_PRETTY_ONELINE || S->buffer->count <= 0) + return; + switch (S->buffer->data[S->buffer->count - 1]) { + case ')': + case '}': + case ']': + break; + default: + return; + } + int32_t removed = 0; + int32_t offset = S->buffer->count; + for (int columns = S->width, align = 0; offset-- >= 0;) { + const char *s = offset < 0 ? "\n" : (char *)S->buffer->data + offset; + if (*s == '\n') { + if (align < S->leaf_align) + break; + columns += align; + removed += align; + align = 0; + } else if (*s == ' ') { + align++; + } else { + align = 0; + /* Don't count color sequences: \x1B(0|3\d)m */ + if (S->flags & JANET_PRETTY_COLOR && *s == 'm') { + if (offset >= 3 && strncmp("\x1B[0m", s - 3, 4) == 0) { + offset -= 3; + columns++; + } else if (offset >= 4 && strncmp("\x1B[3", s - 4, 3) == 0) { + offset -= 4; + columns++; + } + } + } + if (--columns <= 0) + return; + } + S->buffer->count -= removed; + for (int32_t i = ++offset; i < S->buffer->count; i++) + if (S->buffer->data[offset] == '\n') { + S->buffer->data[i] = ' '; + while (S->buffer->data[++offset] == ' '); + } else { + S->buffer->data[i] = S->buffer->data[offset++]; + } +} + static void print_newline(struct pretty *S, int align) { int i; - S->align = align; if (S->flags & JANET_PRETTY_ONELINE) { janet_buffer_push_u8(S->buffer, ' '); return; } + backtrack_newlines(S); janet_buffer_push_u8(S->buffer, '\n'); + S->leaf_align = S->align = align; for (i = 0; i < S->align; i++) { janet_buffer_push_u8(S->buffer, ' '); } @@ -564,7 +616,7 @@ static void janet_pretty_one(struct pretty *S, Janet x) { const char *startstr = isarray ? "@[" : hasbrackets ? "[" : "("; const char endchar = isarray ? ']' : hasbrackets ? ']' : ')'; janet_buffer_push_cstring(S->buffer, startstr); - const int align = S->align += strlen(startstr); + const int align = S->leaf_align = S->align += strlen(startstr); S->depth--; if (S->depth == 0) { janet_buffer_push_cstring(S->buffer, "..."); @@ -639,7 +691,7 @@ static void janet_pretty_one(struct pretty *S, Janet x) { } } janet_buffer_push_u8(S->buffer, '{'); - const int align = ++S->align; + const int align = S->leaf_align = ++S->align; S->depth--; if (S->depth == 0) { @@ -732,13 +784,17 @@ static void janet_pretty_one(struct pretty *S, Janet x) { return; } -static JanetBuffer *janet_pretty_(JanetBuffer *buffer, int depth, int flags, Janet x, int32_t startlen) { +#define JANET_COLUMNS 80 + +static JanetBuffer *janet_pretty_(JanetBuffer *buffer, int depth, int width, + int flags, Janet x, int32_t startlen) { struct pretty S; if (NULL == buffer) { buffer = janet_buffer(0); } S.buffer = buffer; S.depth = depth; + S.width = width; S.align = 0; S.flags = flags; S.bufstartlen = startlen; @@ -747,6 +803,7 @@ static JanetBuffer *janet_pretty_(JanetBuffer *buffer, int depth, int flags, Jan S.keysort_start = 0; janet_table_init(&S.seen, 10); janet_pretty_one(&S, x); + backtrack_newlines(&S); janet_table_deinit(&S.seen); return S.buffer; } @@ -754,7 +811,8 @@ static JanetBuffer *janet_pretty_(JanetBuffer *buffer, int depth, int flags, Jan /* Helper for printing a janet value in a pretty form. Not meant to be used * for serialization or anything like that. */ JanetBuffer *janet_pretty(JanetBuffer *buffer, int depth, int flags, Janet x) { - return janet_pretty_(buffer, depth, flags, x, buffer ? buffer->count : 0); + return janet_pretty_(buffer, depth, JANET_COLUMNS, flags, + x, buffer ? buffer->count : 0); } static JanetBuffer *janet_jdn_(JanetBuffer *buffer, int depth, Janet x, int32_t startlen) { @@ -986,11 +1044,17 @@ void janet_formatbv(JanetBuffer *b, const char *format, va_list args) { int has_color = (d == 'P') || (d == 'Q') || (d == 'M') || (d == 'N'); int has_oneline = (d == 'Q') || (d == 'q') || (d == 'N') || (d == 'n'); int has_notrunc = (d == 'M') || (d == 'm') || (d == 'N') || (d == 'n'); + int columns = atoi(width); + if (columns == 0) + columns = JANET_COLUMNS; + else if (columns < 0) + has_oneline = 1; int flags = 0; flags |= has_color ? JANET_PRETTY_COLOR : 0; flags |= has_oneline ? JANET_PRETTY_ONELINE : 0; flags |= has_notrunc ? JANET_PRETTY_NOTRUNC : 0; - janet_pretty_(b, depth, flags, va_arg(args, Janet), startlen); + janet_pretty_(b, depth, columns, flags, + va_arg(args, Janet), startlen); break; } case 'j': { @@ -1148,11 +1212,17 @@ void janet_buffer_format( int has_color = (d == 'P') || (d == 'Q') || (d == 'M') || (d == 'N'); int has_oneline = (d == 'Q') || (d == 'q') || (d == 'N') || (d == 'n'); int has_notrunc = (d == 'M') || (d == 'm') || (d == 'N') || (d == 'n'); + int columns = atoi(width); + if (columns == 0) + columns = JANET_COLUMNS; + else if (columns < 0) + has_oneline = 1; int flags = 0; flags |= has_color ? JANET_PRETTY_COLOR : 0; flags |= has_oneline ? JANET_PRETTY_ONELINE : 0; flags |= has_notrunc ? JANET_PRETTY_NOTRUNC : 0; - janet_pretty_(b, depth, flags, argv[arg], startlen); + janet_pretty_(b, depth, columns, flags, + argv[arg], startlen); break; } case 'j': { diff --git a/src/core/string.c b/src/core/string.c index 54c25562..53a04f9f 100644 --- a/src/core/string.c +++ b/src/core/string.c @@ -555,7 +555,9 @@ JANET_CORE_FN(cfun_string_format, "\n" "The following conversion specifiers are used for \"pretty-printing\", where the upper-case " "variants generate colored output. These specifiers can take a precision " - "argument to specify the maximum nesting depth to print.\n" + "argument to specify the maximum nesting depth to print. " + "The multiline specifiers can also take a width argument, " + "which defaults to 80 columns.\n" "- `p`, `P`: pretty format, truncating if necessary\n" "- `m`, `M`: pretty format without truncating.\n" "- `q`, `Q`: pretty format on one line, truncating if necessary.\n" diff --git a/test/suite-pp.janet b/test/suite-pp.janet index e3767b5e..d6c7f2f8 100644 --- a/test/suite-pp.janet +++ b/test/suite-pp.janet @@ -67,26 +67,18 @@ :baz 42} @{:_name "Foo"})}] (set (tab tup) tab) - (assert (= (string/format "%m" {tup @[tup tab] - 'symbol tup}) + (assert (= (string/format "%67m" {tup @[tup tab] + 'symbol tup}) ` -{symbol (:keyword - "string" - @"buffer") +{symbol (:keyword "string" @"buffer") (:keyword "string" - @"buffer") @[(:keyword - "string" - @"buffer") - @{true @Foo{:bar (:keyword - "string" - @"buffer") + @"buffer") @[(:keyword "string" @"buffer") + @{true @Foo{:bar (:keyword "string" @"buffer") :baz 42} - (:keyword - "string" - @"buffer") }]}`)) - (assert (= (string/format "%p" {(freeze (zipcoll (range 42) - (range -42 0))) tab}) + (:keyword "string" @"buffer") }]}`)) + (assert (= (string/format "%67p" {(freeze (zipcoll (range 42) + (range -42 0))) tab}) ` {{0 -42 1 -41 @@ -118,11 +110,6 @@ 27 -15 28 -14 29 -13 - ...} @{true @Foo{:bar (:keyword - "string" - @"buffer") - :baz 42} - (:keyword - "string" - @"buffer") }}`))) + ...} @{true @Foo{:bar (:keyword "string" @"buffer") :baz 42} + (:keyword "string" @"buffer") }}`))) (end-suite) From 9a9cf981ed9b1555e7d0836817579421b053e69b Mon Sep 17 00:00:00 2001 From: Calvin Rose Date: Wed, 1 Apr 2026 18:58:14 -0500 Subject: [PATCH 221/307] Add some afl stubs. --- src/core/pp.c | 12 ++++++------ tools/afl/pp_runner.janet | 6 ++++++ 2 files changed, 12 insertions(+), 6 deletions(-) create mode 100644 tools/afl/pp_runner.janet diff --git a/src/core/pp.c b/src/core/pp.c index 327003ac..71634f2a 100644 --- a/src/core/pp.c +++ b/src/core/pp.c @@ -472,12 +472,12 @@ static void backtrack_newlines(const struct pretty *S) { if (S->flags & JANET_PRETTY_ONELINE || S->buffer->count <= 0) return; switch (S->buffer->data[S->buffer->count - 1]) { - case ')': - case '}': - case ']': - break; - default: - return; + case ')': + case '}': + case ']': + break; + default: + return; } int32_t removed = 0; int32_t offset = S->buffer->count; diff --git a/tools/afl/pp_runner.janet b/tools/afl/pp_runner.janet new file mode 100644 index 00000000..e2757add --- /dev/null +++ b/tools/afl/pp_runner.janet @@ -0,0 +1,6 @@ +(def p (parser/new)) +(parser/consume p (slurp ((dyn :args) 1))) +(while (parser/has-more p) + (def x (parser/produce p)) + (printf "%m\n%99M\n%1m\n%0M" x x x x) + (protect (printf "%j" x))) From 2d1b54da37c2a5e9810e7a5dee0e079d0fb8f4a0 Mon Sep 17 00:00:00 2001 From: Calvin Rose Date: Wed, 1 Apr 2026 19:02:51 -0500 Subject: [PATCH 222/307] Add more printing cases to pp_runner fuzz harness. --- tools/afl/pp_runner.janet | 1 + 1 file changed, 1 insertion(+) diff --git a/tools/afl/pp_runner.janet b/tools/afl/pp_runner.janet index e2757add..3e87a886 100644 --- a/tools/afl/pp_runner.janet +++ b/tools/afl/pp_runner.janet @@ -3,4 +3,5 @@ (while (parser/has-more p) (def x (parser/produce p)) (printf "%m\n%99M\n%1m\n%0M" x x x x) + (printf "%q\n%99Q\n%1p\n%P" x x x x) (protect (printf "%j" x))) From a261a8f6b4afca9f44b69e85c623dac32dffef14 Mon Sep 17 00:00:00 2001 From: Calvin Rose Date: Thu, 2 Apr 2026 23:22:18 -0500 Subject: [PATCH 223/307] Update CHANGELOG.md --- CHANGELOG.md | 1 + 1 file changed, 1 insertion(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index e5579639..977ca238 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -2,6 +2,7 @@ All notable changes to this project will be documented in this file. ## Unreleased - ??? +- Improve pretty printing layout for %M and %m modifiers to be more code-like. - Add filewatch support to BSD and macos. - Add linting support for shadowed bindings. - Add nanboxing support for Linux on ARM64 and turn on nanboxing by default on macos on ARM64 (aarch64). From c6b802b082aa21b6e65e8c2729c85237b0cc8a24 Mon Sep 17 00:00:00 2001 From: Calvin Rose Date: Fri, 3 Apr 2026 08:38:12 -0500 Subject: [PATCH 224/307] Add #1737 Add some test cases and asserts for new pretty printing - we were not properly handing the case when print formatter was not at the start of the buffer. A simple fix is to add a lookback_barrier, which is an index into the buffer that backtracking print formatter can't go past. This ensures that, whatever newlines are replace with spaces, we don't mess with any explicit newlines that should be printed. Also some tweaks to logic to try an make invariants more obvious and simplify looping and indexing. --- src/core/pp.c | 60 +++++++++++++++++++++++++++++---------------- test/suite-pp.janet | 35 ++++++++++++++++++++++++-- 2 files changed, 72 insertions(+), 23 deletions(-) diff --git a/src/core/pp.c b/src/core/pp.c index 71634f2a..d70a3491 100644 --- a/src/core/pp.c +++ b/src/core/pp.c @@ -379,6 +379,7 @@ struct pretty { int leaf_align; int flags; int32_t bufstartlen; + int32_t lookback_barrier; int32_t *keysort_buffer; int32_t keysort_capacity; int32_t keysort_start; @@ -480,12 +481,17 @@ static void backtrack_newlines(const struct pretty *S) { return; } int32_t removed = 0; - int32_t offset = S->buffer->count; - for (int columns = S->width, align = 0; offset-- >= 0;) { - const char *s = offset < 0 ? "\n" : (char *)S->buffer->data + offset; + int32_t old_count = S->buffer->count; + int32_t offset = old_count; + int32_t b0 = S->lookback_barrier; + int32_t columns = S->width; + int32_t align = 0; + while (--offset >= b0) { + const char *s = (const char *)S->buffer->data + offset; if (*s == '\n') { - if (align < S->leaf_align) + if (align < S->leaf_align) { break; + } columns += align; removed += align; align = 0; @@ -495,26 +501,32 @@ static void backtrack_newlines(const struct pretty *S) { align = 0; /* Don't count color sequences: \x1B(0|3\d)m */ if (S->flags & JANET_PRETTY_COLOR && *s == 'm') { - if (offset >= 3 && strncmp("\x1B[0m", s - 3, 4) == 0) { + if (offset >= (3 + b0) && strncmp("\x1B[0m", s - 3, 4) == 0) { offset -= 3; columns++; - } else if (offset >= 4 && strncmp("\x1B[3", s - 4, 3) == 0) { + } else if (offset >= (4 + b0) && strncmp("\x1B[3", s - 4, 3) == 0) { offset -= 4; columns++; } } } - if (--columns <= 0) + if (--columns <= 0) { return; + } } + offset++; /* Don't mess with the last newline we found */ + janet_assert(offset >= b0, "bad buffer index"); S->buffer->count -= removed; - for (int32_t i = ++offset; i < S->buffer->count; i++) + for (int32_t i = offset; i < S->buffer->count; i++) { if (S->buffer->data[offset] == '\n') { S->buffer->data[i] = ' '; - while (S->buffer->data[++offset] == ' '); + while (S->buffer->data[++offset] == ' ') { + janet_assert(offset < old_count, "bad replacement of newline"); + } } else { S->buffer->data[i] = S->buffer->data[offset++]; } + } } static void print_newline(struct pretty *S, int align) { @@ -595,6 +607,7 @@ static void janet_pretty_one(struct pretty *S, Janet x) { if (janet_checktype(x, JANET_BUFFER) && janet_unwrap_buffer(x) == S->buffer) { janet_buffer_ensure(S->buffer, S->buffer->count + S->bufstartlen * 4 + 3, 1); janet_buffer_push_u8(S->buffer, '@'); + /* Use start len to print to self better */ S->align += 1 + janet_escape_string_impl(S->buffer, S->buffer->data, S->bufstartlen); } else { S->align -= S->buffer->count; @@ -616,7 +629,8 @@ static void janet_pretty_one(struct pretty *S, Janet x) { const char *startstr = isarray ? "@[" : hasbrackets ? "[" : "("; const char endchar = isarray ? ']' : hasbrackets ? ']' : ')'; janet_buffer_push_cstring(S->buffer, startstr); - const int align = S->leaf_align = S->align += strlen(startstr); + S->align += strlen(startstr); + const int align = S->leaf_align = S->align; S->depth--; if (S->depth == 0) { janet_buffer_push_cstring(S->buffer, "..."); @@ -787,7 +801,7 @@ static void janet_pretty_one(struct pretty *S, Janet x) { #define JANET_COLUMNS 80 static JanetBuffer *janet_pretty_(JanetBuffer *buffer, int depth, int width, - int flags, Janet x, int32_t startlen) { + int flags, Janet x, int32_t startlen, int32_t lookback_barrier) { struct pretty S; if (NULL == buffer) { buffer = janet_buffer(0); @@ -798,6 +812,7 @@ static JanetBuffer *janet_pretty_(JanetBuffer *buffer, int depth, int width, S.align = 0; S.flags = flags; S.bufstartlen = startlen; + S.lookback_barrier = lookback_barrier; S.keysort_capacity = 0; S.keysort_buffer = NULL; S.keysort_start = 0; @@ -812,10 +827,10 @@ static JanetBuffer *janet_pretty_(JanetBuffer *buffer, int depth, int width, * for serialization or anything like that. */ JanetBuffer *janet_pretty(JanetBuffer *buffer, int depth, int flags, Janet x) { return janet_pretty_(buffer, depth, JANET_COLUMNS, flags, - x, buffer ? buffer->count : 0); + x, buffer ? buffer->count : 0, buffer ? buffer->count : 0); } -static JanetBuffer *janet_jdn_(JanetBuffer *buffer, int depth, Janet x, int32_t startlen) { +static JanetBuffer *janet_jdn_(JanetBuffer *buffer, int depth, Janet x, int32_t startlen, int32_t lookback_barrier) { struct pretty S; if (NULL == buffer) { buffer = janet_buffer(0); @@ -825,6 +840,7 @@ static JanetBuffer *janet_jdn_(JanetBuffer *buffer, int depth, Janet x, int32_t S.align = 0; S.flags = 0; S.bufstartlen = startlen; + S.lookback_barrier = lookback_barrier; S.keysort_capacity = 0; S.keysort_buffer = NULL; S.keysort_start = 0; @@ -838,7 +854,7 @@ static JanetBuffer *janet_jdn_(JanetBuffer *buffer, int depth, Janet x, int32_t } JanetBuffer *janet_jdn(JanetBuffer *buffer, int depth, Janet x) { - return janet_jdn_(buffer, depth, x, buffer ? buffer->count : 0); + return janet_jdn_(buffer, depth, x, buffer ? buffer->count : 0, buffer ? buffer->count : 0); } static const char *typestr(Janet x) { @@ -1045,23 +1061,25 @@ void janet_formatbv(JanetBuffer *b, const char *format, va_list args) { int has_oneline = (d == 'Q') || (d == 'q') || (d == 'N') || (d == 'n'); int has_notrunc = (d == 'M') || (d == 'm') || (d == 'N') || (d == 'n'); int columns = atoi(width); - if (columns == 0) + if (columns == 0) { columns = JANET_COLUMNS; - else if (columns < 0) + } else if (columns < 0) { has_oneline = 1; + } int flags = 0; flags |= has_color ? JANET_PRETTY_COLOR : 0; flags |= has_oneline ? JANET_PRETTY_ONELINE : 0; flags |= has_notrunc ? JANET_PRETTY_NOTRUNC : 0; janet_pretty_(b, depth, columns, flags, - va_arg(args, Janet), startlen); + va_arg(args, Janet), startlen, b->count); break; } case 'j': { int depth = atoi(precision); - if (depth < 1) + if (depth < 1) { depth = JANET_RECURSION_GUARD; - janet_jdn_(b, depth, va_arg(args, Janet), startlen); + } + janet_jdn_(b, depth, va_arg(args, Janet), startlen, b->count); break; } default: { @@ -1222,14 +1240,14 @@ void janet_buffer_format( flags |= has_oneline ? JANET_PRETTY_ONELINE : 0; flags |= has_notrunc ? JANET_PRETTY_NOTRUNC : 0; janet_pretty_(b, depth, columns, flags, - argv[arg], startlen); + argv[arg], startlen, b->count); break; } case 'j': { int depth = atoi(precision); if (depth < 1) depth = JANET_RECURSION_GUARD; - janet_jdn_(b, depth, argv[arg], startlen); + janet_jdn_(b, depth, argv[arg], startlen, b->count); break; } default: { diff --git a/test/suite-pp.janet b/test/suite-pp.janet index d6c7f2f8..a75ba482 100644 --- a/test/suite-pp.janet +++ b/test/suite-pp.janet @@ -61,14 +61,27 @@ (check-jdn "a string") (check-jdn @"a buffer") +# Issue 1737 +(assert (deep= "@[]" (string/format "%M" @[]))) +(assert (deep= " @[]" (string/format " %M" @[]))) +(assert (deep= " @[]" (string/format " %M" @[]))) +(assert (deep= " @[]" (string/format " %M" @[]))) +(assert (deep= " @[]" (string/format " %M" @[]))) +(assert (deep= " @[]" (string/format " %M" @[]))) +(assert (deep= "@[1]" (string/format "%m" @[1]))) +(assert (deep= " @[2]" (string/format " %m" @[2]))) +(assert (deep= " @[3]" (string/format " %m" @[3]))) +(assert (deep= " @[4]" (string/format " %m" @[4]))) +(assert (deep= " @[5]" (string/format " %m" @[5]))) +(assert (deep= " @[6]" (string/format " %m" @[6]))) + # Test multiline pretty specifiers (let [tup [:keyword "string" @"buffer"] tab @{true (table/setproto @{:bar tup :baz 42} @{:_name "Foo"})}] (set (tab tup) tab) - (assert (= (string/format "%67m" {tup @[tup tab] - 'symbol tup}) + (assert (= (string/format "%67m" {tup @[tup tab] 'symbol tup}) ` {symbol (:keyword "string" @"buffer") (:keyword @@ -112,4 +125,22 @@ 29 -13 ...} @{true @Foo{:bar (:keyword "string" @"buffer") :baz 42} (:keyword "string" @"buffer") }}`))) + +# Issue 1737 +(def capture-buf @"") +(with-dyns [*err* capture-buf] + (peg/match ~(* (constant @[]) (??)) "a")) +(assert (deep= ``` + ?? at [a] (index 0) + stack [1]: + [0]: @[] + + ``` + (string capture-buf))) + +(assert (= + (string/format "?? at [bc] (index 2)\nstack [5]:\n [0]: %m\n [1]: %m\n [2]: %m\n [3]: %m\n [4]: %m\n" "a" 1 true {} @[]) + "?? at [bc] (index 2)\nstack [5]:\n [0]: \"a\"\n [1]: 1\n [2]: true\n [3]: {}\n [4]: @[]\n") + "pretty format should not eat explicit newlines") + (end-suite) From 5b5844b469286811f7ecd472a9ec9f5ce7533c97 Mon Sep 17 00:00:00 2001 From: sarna Date: Mon, 20 Apr 2026 15:31:55 +0200 Subject: [PATCH 225/307] Try to use clearer language in buffer docs (#1738) * Clarify buffer limits in docs * Wording --- src/core/buffer.c | 27 ++++++++++++++++----------- 1 file changed, 16 insertions(+), 11 deletions(-) diff --git a/src/core/buffer.c b/src/core/buffer.c index 20015f81..e8c1e85b 100644 --- a/src/core/buffer.c +++ b/src/core/buffer.c @@ -272,8 +272,8 @@ JANET_CORE_FN(cfun_buffer_trim, JANET_CORE_FN(cfun_buffer_u8, "(buffer/push-byte buffer & xs)", - "Append bytes to a buffer. Will expand the buffer as necessary. " - "Returns the modified buffer. Will throw an error if the buffer overflows.") { + "Append bytes to a buffer. Returns the modified buffer. " + "Expands the buffer as necessary. Throws an error if size limit is exceeded.") { int32_t i; janet_arity(argc, 1, -1); JanetBuffer *buffer = janet_getbuffer(argv, 0); @@ -286,8 +286,8 @@ JANET_CORE_FN(cfun_buffer_u8, JANET_CORE_FN(cfun_buffer_word, "(buffer/push-word buffer & xs)", "Append machine words to a buffer. The 4 bytes of the integer are appended " - "in twos complement, little endian order, unsigned for all x. Returns the modified buffer. Will " - "throw an error if the buffer overflows.") { + "in twos complement, little endian order, unsigned for all x. Returns the modified buffer. " + "Expands the buffer as necessary. Throws an error if size limit is exceeded.") { int32_t i; janet_arity(argc, 1, -1); JanetBuffer *buffer = janet_getbuffer(argv, 0); @@ -306,7 +306,7 @@ JANET_CORE_FN(cfun_buffer_chars, "Push byte sequences onto the end of a buffer. " "Will accept any of strings, keywords, symbols, and buffers. " "Returns the modified buffer. " - "Will throw an error if the buffer overflows.") { + "Expands the buffer as necessary. Throws an error if size limit is exceeded.") { int32_t i; janet_arity(argc, 1, -1); JanetBuffer *buffer = janet_getbuffer(argv, 0); @@ -368,7 +368,8 @@ static void reverse_u64(uint8_t bytes[8]) { JANET_CORE_FN(cfun_buffer_push_uint16, "(buffer/push-uint16 buffer order data)", "Push a 16 bit unsigned integer data onto the end of the buffer. " - "Returns the modified buffer.") { + "Returns the modified buffer." + "Expands the buffer as necessary. Throws an error if size limit is exceeded.") { janet_fixarity(argc, 3); JanetBuffer *buffer = janet_getbuffer(argv, 0); int reverse = should_reverse_bytes(argv, 1); @@ -387,7 +388,8 @@ JANET_CORE_FN(cfun_buffer_push_uint16, JANET_CORE_FN(cfun_buffer_push_uint32, "(buffer/push-uint32 buffer order data)", "Push a 32 bit unsigned integer data onto the end of the buffer. " - "Returns the modified buffer.") { + "Returns the modified buffer." + "Expands the buffer as necessary. Throws an error if size limit is exceeded.") { janet_fixarity(argc, 3); JanetBuffer *buffer = janet_getbuffer(argv, 0); int reverse = should_reverse_bytes(argv, 1); @@ -403,7 +405,8 @@ JANET_CORE_FN(cfun_buffer_push_uint32, JANET_CORE_FN(cfun_buffer_push_uint64, "(buffer/push-uint64 buffer order data)", "Push a 64 bit unsigned integer data onto the end of the buffer. " - "Returns the modified buffer.") { + "Returns the modified buffer." + "Expands the buffer as necessary. Throws an error if size limit is exceeded.") { janet_fixarity(argc, 3); JanetBuffer *buffer = janet_getbuffer(argv, 0); int reverse = should_reverse_bytes(argv, 1); @@ -419,7 +422,8 @@ JANET_CORE_FN(cfun_buffer_push_uint64, JANET_CORE_FN(cfun_buffer_push_float32, "(buffer/push-float32 buffer order data)", "Push the underlying bytes of a 32 bit float data onto the end of the buffer. " - "Returns the modified buffer.") { + "Returns the modified buffer." + "Expands the buffer as necessary. Throws an error if size limit is exceeded.") { janet_fixarity(argc, 3); JanetBuffer *buffer = janet_getbuffer(argv, 0); int reverse = should_reverse_bytes(argv, 1); @@ -435,7 +439,8 @@ JANET_CORE_FN(cfun_buffer_push_float32, JANET_CORE_FN(cfun_buffer_push_float64, "(buffer/push-float64 buffer order data)", "Push the underlying bytes of a 64 bit float data onto the end of the buffer. " - "Returns the modified buffer.") { + "Returns the modified buffer." + "Expands the buffer as necessary. Throws an error if size limit is exceeded.") { janet_fixarity(argc, 3); JanetBuffer *buffer = janet_getbuffer(argv, 0); int reverse = should_reverse_bytes(argv, 1); @@ -488,7 +493,7 @@ JANET_CORE_FN(cfun_buffer_push, "push the byte if x is an integer, otherwise push the bytesequence to the buffer. " "Thus, this function behaves like both `buffer/push-string` and `buffer/push-byte`. " "Returns the modified buffer. " - "Will throw an error if the buffer overflows.") { + "Expands the buffer as necessary. Throws an error if size limit is exceeded.") { janet_arity(argc, 1, -1); JanetBuffer *buffer = janet_getbuffer(argv, 0); buffer_push_impl(buffer, argv, 1, argc); From e118f57220e23ae4b0b4cea85f6ff4850781f9ab Mon Sep 17 00:00:00 2001 From: Calvin Rose Date: Wed, 22 Apr 2026 20:00:12 -0500 Subject: [PATCH 226/307] Add conservative checks large registers in emit.c We make some assumptions about the index range of slots in emit.c that is not tracked by the C type system, so add some asserts and conversions to be safe. --- src/core/emit.c | 13 ++++++++++++- 1 file changed, 12 insertions(+), 1 deletion(-) diff --git a/src/core/emit.c b/src/core/emit.c index 1a4c74bb..cadb94b9 100644 --- a/src/core/emit.c +++ b/src/core/emit.c @@ -114,6 +114,7 @@ static void janetc_loadconst(JanetCompiler *c, Janet k, int32_t reg) { static void janetc_movenear(JanetCompiler *c, int32_t dest, JanetSlot src) { + janet_assert(dest <= 255, "dest slot index must be <= 255"); if (src.flags & (JANET_SLOT_CONSTANT | JANET_SLOT_REF)) { janetc_loadconst(c, src.constant, dest); /* If we also are a reference, deref the one element array */ @@ -138,7 +139,7 @@ static void janetc_movenear(JanetCompiler *c, } } -/* Move a near register to a Slot. */ +/* Move a near or far register to a Slot. */ static void janetc_moveback(JanetCompiler *c, JanetSlot dest, int32_t src) { @@ -151,6 +152,11 @@ static void janetc_moveback(JanetCompiler *c, JOP_PUT_INDEX); janetc_regalloc_freetemp(&c->scope->ra, refreg, JANETC_REGTEMP_5); } else if (dest.envindex >= 0) { + /* Convert src to near reg */ + if (src > 255) { + janetc_emit(c, JOP_MOVE_NEAR | ((uint32_t)(src) << 16) | (JANETC_REGTEMP_5 << 8)); + src = JANETC_REGTEMP_5; + } janetc_emit(c, ((uint32_t)(dest.index) << 24) | ((uint32_t)(dest.envindex) << 16) | @@ -158,6 +164,11 @@ static void janetc_moveback(JanetCompiler *c, JOP_SET_UPVALUE); } else if (dest.index != src) { janet_assert(dest.index >= 0, "bad slot"); + /* Convert src to near reg */ + if (src > 255) { + janetc_emit(c, JOP_MOVE_NEAR | ((uint32_t)(src) << 16) | (JANETC_REGTEMP_5 << 8)); + src = JANETC_REGTEMP_5; + } janetc_emit(c, ((uint32_t)(dest.index) << 16) | ((uint32_t)(src) << 8) | From fcf3186c7d9c297d07df0c29563c8ec3b5aedba5 Mon Sep 17 00:00:00 2001 From: Calvin Rose Date: Thu, 23 Apr 2026 05:20:14 -0500 Subject: [PATCH 227/307] Add missing offset to temp registers. --- src/core/emit.c | 10 ++++++---- 1 file changed, 6 insertions(+), 4 deletions(-) diff --git a/src/core/emit.c b/src/core/emit.c index cadb94b9..290a8990 100644 --- a/src/core/emit.c +++ b/src/core/emit.c @@ -154,8 +154,9 @@ static void janetc_moveback(JanetCompiler *c, } else if (dest.envindex >= 0) { /* Convert src to near reg */ if (src > 255) { - janetc_emit(c, JOP_MOVE_NEAR | ((uint32_t)(src) << 16) | (JANETC_REGTEMP_5 << 8)); - src = JANETC_REGTEMP_5; + int32_t newsrc = JANETC_REGTEMP_5 + 0xF0; + janetc_emit(c, JOP_MOVE_NEAR | ((uint32_t)(src) << 16) | (newsrc << 8)); + src = newsrc; } janetc_emit(c, ((uint32_t)(dest.index) << 24) | @@ -166,8 +167,9 @@ static void janetc_moveback(JanetCompiler *c, janet_assert(dest.index >= 0, "bad slot"); /* Convert src to near reg */ if (src > 255) { - janetc_emit(c, JOP_MOVE_NEAR | ((uint32_t)(src) << 16) | (JANETC_REGTEMP_5 << 8)); - src = JANETC_REGTEMP_5; + int32_t newsrc = JANETC_REGTEMP_5 + 0xF0; + janetc_emit(c, JOP_MOVE_NEAR | ((uint32_t)(src) << 16) | ((uint32_t)newsrc << 8)); + src = newsrc; } janetc_emit(c, ((uint32_t)(dest.index) << 16) | From ed17dd2c5913a23fb1107251e44a9410a3c30cf5 Mon Sep 17 00:00:00 2001 From: Calvin Rose Date: Sun, 26 Apr 2026 08:53:06 -0500 Subject: [PATCH 228/307] Add runtime checks for bug #1743 This also expands janet_verify to check slotmapping (debuginfo) in function definitions. --- src/core/bytecode.c | 24 ++++++++++++++++++++++++ src/core/compile.c | 2 +- src/core/debug.c | 23 +++++++++++++++++------ 3 files changed, 42 insertions(+), 7 deletions(-) diff --git a/src/core/bytecode.c b/src/core/bytecode.c index 6f7f2131..d33da61f 100644 --- a/src/core/bytecode.c +++ b/src/core/bytecode.c @@ -496,6 +496,30 @@ int janet_verify(JanetFuncDef *def) { } } + /* Verify debug info - slotmapping, etc. */ + for (int32_t i = def->symbolmap_length - 1; i >= 0; i--) { + JanetSymbolMap jsm = def->symbolmap[i]; + if (jsm.birth_pc == UINT32_MAX) { + if (jsm.death_pc >= (uint32_t) def->environments_length) { + return 10; + /* We should also check jsm.slot_index */ + } + } else { + if (jsm.slot_index >= (uint32_t) def->slotcount) { + return 11; + } + if (jsm.birth_pc != UINT32_MAX && jsm.birth_pc >= (uint32_t) def->bytecode_length) { + return 12; + } + if (jsm.death_pc != UINT32_MAX && jsm.death_pc > (uint32_t) def->bytecode_length) { + return 13; + } + } + if (jsm.symbol == NULL) { + return 14; + } + } + return 0; } diff --git a/src/core/compile.c b/src/core/compile.c index 86f737ad..cedffaf2 100644 --- a/src/core/compile.c +++ b/src/core/compile.c @@ -1082,7 +1082,7 @@ JanetFuncDef *janetc_pop_funcdef(JanetCompiler *c) { if (pair.sym2) { JanetSymbolMap jsm; jsm.birth_pc = UINT32_MAX; - jsm.death_pc = j; + jsm.death_pc = j; /* Use death_pc to encode environment index */ jsm.slot_index = pair.slot.index; jsm.symbol = pair.sym2; janet_v_push(locals, jsm); diff --git a/src/core/debug.c b/src/core/debug.c index dc36961e..7b1da7e3 100644 --- a/src/core/debug.c +++ b/src/core/debug.c @@ -343,14 +343,25 @@ static Janet doframe(JanetStackFrame *frame) { Janet value = janet_wrap_nil(); uint32_t pc = (uint32_t)(frame->pc - def->bytecode); if (jsm.birth_pc == UINT32_MAX) { - JanetFuncEnv *env = frame->func->envs[jsm.death_pc]; - if (env->offset > 0) { - value = env->as.fiber->data[env->offset + jsm.slot_index]; - } else { - value = env->as.values[jsm.slot_index]; + /* death_pc has secondary meaning here, we use it to encode the environment index */ + if (jsm.death_pc < (uint32_t) def->environments_length) { + /* death_pc is in valid range */ + JanetFuncEnv *env = frame->func->envs[jsm.death_pc]; + if (jsm.slot_index < (uint32_t) env->length) { + /* slot_index range good */ + if (env->offset > 0) { + /* On stack */ + value = env->as.fiber->data[env->offset + jsm.slot_index]; + } else { + /* Off stack */ + value = env->as.values[jsm.slot_index]; + } + } } } else if (pc >= jsm.birth_pc && pc < jsm.death_pc) { - value = stack[jsm.slot_index]; + if (jsm.slot_index < (uint32_t) def->slotcount) { + value = stack[jsm.slot_index]; + } } janet_table_put(local_bindings, janet_wrap_symbol(jsm.symbol), value); } From d9b1d711ea1fde52ac73a82088b512a3e17bad0d Mon Sep 17 00:00:00 2001 From: Calvin Rose Date: Sun, 26 Apr 2026 13:20:56 -0500 Subject: [PATCH 229/307] Add fix for #1744 Usually, we try to pre-allocate a few extra stack values for convenience, but we don't actually need to. Avoid a buffer overflow in the case where this "extra" was causing us to go over INT32_MAX. --- src/core/marsh.c | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) diff --git a/src/core/marsh.c b/src/core/marsh.c index a30b09f2..bdf56c4a 100644 --- a/src/core/marsh.c +++ b/src/core/marsh.c @@ -1111,7 +1111,12 @@ static const uint8_t *unmarshal_one_fiber( } /* Allocate stack memory */ - fiber->capacity = fiber_stacktop + 10; + if (fiber_stacktop < INT32_MAX - 10) { + fiber->capacity = fiber_stacktop + 10; + } else { + /* Extra capacity is usually nice to avoid immediately reallocing on pushed arguments, but not needed */ + fiber->capacity = INT32_MAX; + } fiber->data = janet_malloc(sizeof(Janet) * fiber->capacity); if (!fiber->data) { JANET_OUT_OF_MEMORY; From 27807d4456465bfec55aecd096822155402ecff7 Mon Sep 17 00:00:00 2001 From: jsks Date: Sat, 2 May 2026 00:49:49 +0200 Subject: [PATCH 230/307] Fix mmap return value check (#1745) --- src/core/ffi.c | 9 +++++++-- 1 file changed, 7 insertions(+), 2 deletions(-) diff --git a/src/core/ffi.c b/src/core/ffi.c index 01024d53..d638e896 100644 --- a/src/core/ffi.c +++ b/src/core/ffi.c @@ -1628,7 +1628,11 @@ JANET_CORE_FN(cfun_ffi_jitfn, fn->size = 0; #ifdef JANET_WINDOWS void *ptr = VirtualAlloc(NULL, alloc_size, MEM_COMMIT | MEM_RESERVE, PAGE_READWRITE); -#elif defined(MAP_ANONYMOUS) + if (!ptr) { + janet_panic("failed to allocate writable memory"); + } +#else +#ifdef MAP_ANONYMOUS void *ptr = mmap(0, alloc_size, PROT_READ | PROT_WRITE, MAP_PRIVATE | MAP_ANONYMOUS, -1, 0); #elif defined(MAP_ANON) /* macos doesn't have MAP_ANONYMOUS */ @@ -1638,9 +1642,10 @@ JANET_CORE_FN(cfun_ffi_jitfn, /* #define MAP_ANONYMOUS 0x20 should work, though. */ void *ptr = mmap(0, alloc_size, PROT_READ | PROT_WRITE, MAP_PRIVATE, -1, 0); #endif - if (!ptr) { + if (ptr == MAP_FAILED) { janet_panic("failed to memory map writable memory"); } +#endif memcpy(ptr, bytes.bytes, bytes.len); #ifdef JANET_WINDOWS DWORD old = 0; From bef6cb5d741308bf95b6b8ada425b1b533a74ad2 Mon Sep 17 00:00:00 2001 From: Calvin Rose Date: Fri, 8 May 2026 18:15:15 -0500 Subject: [PATCH 231/307] Address issue #1746 This will have the side effect of breaking loading image dicts from older Janet versions with these contents. --- src/boot/boot.janet | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/src/boot/boot.janet b/src/boot/boot.janet index e7ba52e1..b620488e 100644 --- a/src/boot/boot.janet +++ b/src/boot/boot.janet @@ -4953,7 +4953,9 @@ # Build dictionary for loading images (def load-dict (env-lookup root-env)) (each [k v] (pairs load-dict) - (if (number? v) (put load-dict k nil))) + # Drop simple types, otherwise we get odd issues when loading foreign data from a different Janet version. + # It also doesn't usually save any space to encode these anyway. + (if (or (string? v) (keyword? v) (number? v)) (put load-dict k nil))) (merge-into load-image-dict load-dict) (def image From 4413d43b7aa79689162c1f143b1c4ce6781b816d Mon Sep 17 00:00:00 2001 From: Khang Nguyen Duy Date: Fri, 15 May 2026 06:52:24 +0700 Subject: [PATCH 232/307] Fix stale fiber stack pointers across realloc (#1751) Those cached pointers are not safe to use across Janet fiber operations, which may reallocate and invalidate those pointers. Use the fields directly. --- src/core/fiber.c | 5 +---- src/core/vm.c | 28 ++++++++++++++++------------ 2 files changed, 17 insertions(+), 16 deletions(-) diff --git a/src/core/fiber.c b/src/core/fiber.c index 1d12a59b..61c15f4b 100644 --- a/src/core/fiber.c +++ b/src/core/fiber.c @@ -346,9 +346,6 @@ int janet_fiber_funcframe_tail(JanetFiber *fiber, JanetFunction *func) { #endif } - Janet *stack = fiber->data + fiber->frame; - Janet *args = fiber->data + fiber->stackstart; - /* Detach old function */ if (NULL != janet_fiber_frame(fiber)->func) janet_env_detach(janet_fiber_frame(fiber)->env); @@ -378,7 +375,7 @@ int janet_fiber_funcframe_tail(JanetFiber *fiber, JanetFunction *func) { stacksize = fiber->stacktop - fiber->stackstart; } - if (stacksize) memmove(stack, args, stacksize * sizeof(Janet)); + if (stacksize) memmove(fiber->data + fiber->frame, fiber->data + fiber->stackstart, stacksize * sizeof(Janet)); /* Nil unset locals (Needed for functional correctness) */ for (i = fiber->frame + stacksize; i < nextframetop; ++i) diff --git a/src/core/vm.c b/src/core/vm.c index 1ae82b48..2b47d4cc 100644 --- a/src/core/vm.c +++ b/src/core/vm.c @@ -234,18 +234,22 @@ }\ } -/* Trace a function call */ -static void vm_do_trace(JanetFunction *func, int32_t argc, const Janet *argv) { - if (func->def->name) { - janet_eprintf("trace (%S", func->def->name); - } else { - janet_eprintf("trace (%p", janet_wrap_function(func)); - } - for (int32_t i = 0; i < argc; i++) { - janet_eprintf(" %p", argv[i]); - } - janet_eprintf(")\n"); -} +/* Trace a function call. + * This is a macro to avoid stale argv if janet_eprintf resizes the stack + */ +#define vm_do_trace(func, argc, argv) do { \ + JanetFunction* _func = (func);\ + if (_func->def->name) {\ + janet_eprintf("trace (%S", _func->def->name);\ + } else {\ + janet_eprintf("trace (%p", janet_wrap_function(_func));\ + }\ + int32_t _argc = (argc);\ + for (int32_t i = 0; i < _argc; i++) {\ + janet_eprintf(" %p", (argv)[i]);\ + }\ + janet_eprintf(")\n");\ +} while (0) /* Invoke a method once we have looked it up */ static Janet janet_method_invoke(Janet method, int32_t argc, Janet *argv) { From e663aa9ad21f7c6304164e2c5349c70a9dd2d9fb Mon Sep 17 00:00:00 2001 From: Khang Nguyen Duy Date: Sat, 16 May 2026 21:12:38 +0700 Subject: [PATCH 233/307] Hide JanetMarshalContext implementation details (#1747) This hides JanetMarshalContext behind an opaque pointer. User of this struct should use the public API instead of using those internal implementation details. --- src/core/ev.c | 4 ++-- src/core/io.c | 4 ++-- src/core/marsh.c | 16 ++++++++++++++++ src/include/janet.h | 10 +++------- 4 files changed, 23 insertions(+), 11 deletions(-) diff --git a/src/core/ev.c b/src/core/ev.c index bbae9016..5731bdd3 100644 --- a/src/core/ev.c +++ b/src/core/ev.c @@ -433,7 +433,7 @@ static int janet_stream_getter(void *p, Janet key, Janet *out) { static void janet_stream_marshal(void *p, JanetMarshalContext *ctx) { JanetStream *s = p; - if (!(ctx->flags & JANET_MARSHAL_UNSAFE)) { + if (!(janet_marshal_flags(ctx) & JANET_MARSHAL_UNSAFE)) { janet_panic("can only marshal stream with unsafe flag"); } janet_marshal_abstract(ctx, p); @@ -467,7 +467,7 @@ static void janet_stream_marshal(void *p, JanetMarshalContext *ctx) { } static void *janet_stream_unmarshal(JanetMarshalContext *ctx) { - if (!(ctx->flags & JANET_MARSHAL_UNSAFE)) { + if (!(janet_unmarshal_flags(ctx) & JANET_MARSHAL_UNSAFE)) { janet_panic("can only unmarshal stream with unsafe flag"); } JanetStream *p = janet_unmarshal_abstract(ctx, sizeof(JanetStream)); diff --git a/src/core/io.c b/src/core/io.c index cb731188..5a8476d8 100644 --- a/src/core/io.c +++ b/src/core/io.c @@ -411,7 +411,7 @@ static Janet io_file_next(void *p, Janet key) { static void io_file_marshal(void *p, JanetMarshalContext *ctx) { JanetFile *iof = (JanetFile *)p; - if (ctx->flags & JANET_MARSHAL_UNSAFE) { + if (janet_marshal_flags(ctx) & JANET_MARSHAL_UNSAFE) { janet_marshal_abstract(ctx, p); int fno = -1; #ifdef JANET_WINDOWS @@ -436,7 +436,7 @@ static void io_file_marshal(void *p, JanetMarshalContext *ctx) { } static void *io_file_unmarshal(JanetMarshalContext *ctx) { - if (ctx->flags & JANET_MARSHAL_UNSAFE) { + if (janet_unmarshal_flags(ctx) & JANET_MARSHAL_UNSAFE) { JanetFile *iof = janet_unmarshal_abstract(ctx, sizeof(JanetFile)); int32_t fd = janet_unmarshal_int(ctx); int32_t flags = janet_unmarshal_int(ctx); diff --git a/src/core/marsh.c b/src/core/marsh.c index bdf56c4a..a4178b52 100644 --- a/src/core/marsh.c +++ b/src/core/marsh.c @@ -30,6 +30,14 @@ #include "util.h" #endif +struct JanetMarshalContext { + void *m_state; + void *u_state; + int flags; + const uint8_t *data; + const JanetAbstractType *at; +}; + typedef struct { JanetBuffer *buf; JanetTable seen; @@ -433,6 +441,10 @@ void janet_marshal_abstract(JanetMarshalContext *ctx, void *abstract) { MARK_SEEN(); } +int janet_marshal_flags(JanetMarshalContext *ctx) { + return ctx->flags; +} + static void marshal_one_abstract(MarshalState *st, Janet x, int flags) { void *abstract = janet_unwrap_abstract(x); #ifdef JANET_EV @@ -1301,6 +1313,10 @@ void *janet_unmarshal_abstract_threaded(JanetMarshalContext *ctx, size_t size) { #endif } +int janet_unmarshal_flags(JanetMarshalContext *ctx) { + return ctx->flags; +} + static const uint8_t *unmarshal_one_abstract(UnmarshalState *st, const uint8_t *data, Janet *out, int flags) { Janet key; data = unmarshal_one(st, data, &key, flags + 1); diff --git a/src/include/janet.h b/src/include/janet.h index eb356024..3f2ace5f 100644 --- a/src/include/janet.h +++ b/src/include/janet.h @@ -1202,13 +1202,7 @@ struct JanetParser { }; /* A context for marshaling and unmarshaling abstract types */ -typedef struct { - void *m_state; - void *u_state; - int flags; - const uint8_t *data; - const JanetAbstractType *at; -} JanetMarshalContext; +typedef struct JanetMarshalContext JanetMarshalContext; /* Defines an abstract type. Use a const pointer to one of these structures * when creating abstract types. The memory for this pointer should not be free @@ -2285,6 +2279,7 @@ JANET_API void janet_marshal_byte(JanetMarshalContext *ctx, uint8_t value); JANET_API void janet_marshal_bytes(JanetMarshalContext *ctx, const uint8_t *bytes, size_t len); JANET_API void janet_marshal_janet(JanetMarshalContext *ctx, Janet x); JANET_API void janet_marshal_abstract(JanetMarshalContext *ctx, JanetAbstract abstract); +JANET_API int janet_marshal_flags(JanetMarshalContext *ctx); JANET_API void janet_unmarshal_ensure(JanetMarshalContext *ctx, size_t size); JANET_API size_t janet_unmarshal_size(JanetMarshalContext *ctx); @@ -2297,6 +2292,7 @@ JANET_API Janet janet_unmarshal_janet(JanetMarshalContext *ctx); JANET_API JanetAbstract janet_unmarshal_abstract(JanetMarshalContext *ctx, size_t size); JANET_API JanetAbstract janet_unmarshal_abstract_threaded(JanetMarshalContext *ctx, size_t size); JANET_API void janet_unmarshal_abstract_reuse(JanetMarshalContext *ctx, void *p); +JANET_API int janet_unmarshal_flags(JanetMarshalContext *ctx); JANET_API void janet_register_abstract_type(const JanetAbstractType *at); JANET_API const JanetAbstractType *janet_get_abstract_type(Janet key); From 2814fc53c23391d9ad490441a31b683dc5c1e286 Mon Sep 17 00:00:00 2001 From: Rybnikov Alex Date: Tue, 19 May 2026 07:51:14 -0500 Subject: [PATCH 234/307] Add bash, zsh, and fish shell completions for janet (#1753) Adds completion scripts for the janet interpreter covering all flags documented in the man page: -h -v -s -e -E -d -n -N -r -R -p -q -k -i -m -c -l -w -x --. Flag-aware completions: -m completes directories, -c/-l complete .janet files, -w/-x complete linting levels. Closes #1567 --- misc/completion/README.md | 33 ++++++++++++++++++++++++++++++ misc/completion/janet.bash | 42 ++++++++++++++++++++++++++++++++++++++ misc/completion/janet.fish | 28 +++++++++++++++++++++++++ misc/completion/janet.zsh | 35 +++++++++++++++++++++++++++++++ 4 files changed, 138 insertions(+) create mode 100644 misc/completion/README.md create mode 100644 misc/completion/janet.bash create mode 100644 misc/completion/janet.fish create mode 100644 misc/completion/janet.zsh diff --git a/misc/completion/README.md b/misc/completion/README.md new file mode 100644 index 00000000..c8c1a080 --- /dev/null +++ b/misc/completion/README.md @@ -0,0 +1,33 @@ +# Shell completions for Janet + +This directory contains shell completion scripts for the `janet` interpreter. + +## Bash + +```sh +# Temporary (current session only) +source /path/to/janet.bash + +# Permanent (system-wide) +sudo cp janet.bash /etc/bash_completion.d/janet + +# Permanent (user only) +cp janet.bash ~/.local/share/bash-completion/completions/janet +``` + +## Zsh + +```sh +# Copy to a directory in your $fpath, e.g.: +cp janet.zsh ~/.zsh/completions/_janet + +# Ensure the directory is in $fpath (add to ~/.zshrc if needed): +# fpath=(~/.zsh/completions $fpath) +# autoload -Uz compinit && compinit +``` + +## Fish + +```sh +cp janet.fish ~/.config/fish/completions/janet.fish +``` diff --git a/misc/completion/janet.bash b/misc/completion/janet.bash new file mode 100644 index 00000000..78dee7ed --- /dev/null +++ b/misc/completion/janet.bash @@ -0,0 +1,42 @@ +# Bash completion for janet +# Install: source this file, or place it in /etc/bash_completion.d/janet +# or ~/.local/share/bash-completion/completions/janet + +_janet() { + local cur prev words cword + _init_completion || return + + local flags="-h -v -s -e -E -d -n -N -r -R -p -q -k -m -c -i -l -w -x --" + + case "$prev" in + -e|-E) + # Argument is Janet source code — no file completion + return + ;; + -m) + # syspath: complete directories + _filedir -d + return + ;; + -c|-l) + # source file: complete .janet files + _filedir janet + return + ;; + -w|-x) + # linting level + COMPREPLY=($(compgen -W ":none :relaxed :normal :strict" -- "$cur")) + return + ;; + esac + + if [[ "$cur" == -* ]]; then + COMPREPLY=($(compgen -W "$flags" -- "$cur")) + return + fi + + # Default: complete .janet files and directories + _filedir janet +} + +complete -F _janet janet diff --git a/misc/completion/janet.fish b/misc/completion/janet.fish new file mode 100644 index 00000000..b8674492 --- /dev/null +++ b/misc/completion/janet.fish @@ -0,0 +1,28 @@ +# Fish completion for janet + +# Disable file completion by default; re-enable for specific flags +complete -c janet -f + +# Flags +complete -c janet -s h -d 'Show usage and exit' +complete -c janet -s v -d 'Show version and exit' +complete -c janet -s s -d 'Read raw stdin (no readline features)' +complete -c janet -s e -r -d 'Execute Janet source string' +complete -c janet -s E -r -d 'Execute Janet expression as short-fn with remaining args' +complete -c janet -s d -d 'Enable debug mode' +complete -c janet -s n -d 'Disable ANSI colors in REPL' +complete -c janet -s N -d 'Enable ANSI colors in REPL' +complete -c janet -s r -d 'Open REPL after executing sources' +complete -c janet -s R -d 'Disable loading user profile in REPL' +complete -c janet -s p -d 'Persistent mode (continue after errors)' +complete -c janet -s q -d 'Hide logo in REPL' +complete -c janet -s k -d 'Compile only (lint), do not execute' +complete -c janet -s i -d 'Treat script as a .jimage file' +complete -c janet -s m -r -d 'Set syspath for module loading' -a '(__fish_complete_directories)' +complete -c janet -s c -r -d 'Precompile source to .jimage' +complete -c janet -s l -r -d 'Import module before script/REPL' -a '(find . -name "*.janet" -printf "%f\n" 2>/dev/null)' +complete -c janet -s w -r -d 'Set warning linting level' -a ':none :relaxed :normal :strict' +complete -c janet -s x -r -d 'Set error linting level' -a ':none :relaxed :normal :strict' + +# File arguments: .janet files and directories +complete -c janet -F -a '*.janet' diff --git a/misc/completion/janet.zsh b/misc/completion/janet.zsh new file mode 100644 index 00000000..24058dc2 --- /dev/null +++ b/misc/completion/janet.zsh @@ -0,0 +1,35 @@ +#compdef janet + +# Zsh completion for janet + +_janet() { + local -a opts + + opts=( + '-h[Show usage and exit]' + '-v[Show version and exit]' + '-s[Read raw stdin (no readline features)]' + '-e[Execute Janet source string]:source code:' + '-E[Execute Janet expression as short-fn with remaining args]:expression:' + '-d[Enable debug mode]' + '-n[Disable ANSI colors in REPL]' + '-N[Enable ANSI colors in REPL]' + '-r[Open REPL after executing sources]' + '-R[Disable loading user profile in REPL]' + '-p[Persistent mode (continue after errors)]' + '-q[Hide logo in REPL]' + '-k[Compile only (lint), do not execute]' + '-i[Treat script as a .jimage file]' + '-m[Set syspath for module loading]:syspath:_directories' + '-c[Precompile source to .jimage]:source:_files -g "*.janet" :output:_files' + '-l[Import module before script/REPL]:module:_files -g "*.janet"' + '-w[Set warning linting level]:level:(none relaxed normal strict)' + '-x[Set error linting level]:level:(none relaxed normal strict)' + '--[End of options]' + '*:script:_files -g "*.janet"' + ) + + _arguments -s $opts +} + +_janet "$@" From 6292a69aac0914c8bbfddebed9a97cd83f140bbb Mon Sep 17 00:00:00 2001 From: Calvin Rose Date: Sat, 23 May 2026 06:13:27 -0500 Subject: [PATCH 235/307] Fix #1752 - add sandbox flag :exit This change makes it so top level signals in the main thread will result in expected error codes. For spawned threads, we will default to pthread_exit or ExitThread(-1) on Windows after a top level signal. Using `(sandbox :exit)` will restore the old behavior where a top level signal will just print an error and call pthread_exit leaving other thread untouched. --- src/core/capi.c | 9 ++++++++- src/core/corelib.c | 4 +++- src/core/ev.c | 2 +- src/core/os.c | 1 + src/include/janet.h | 1 + 5 files changed, 14 insertions(+), 3 deletions(-) diff --git a/src/core/capi.c b/src/core/capi.c index 7981a6eb..3e397721 100644 --- a/src/core/capi.c +++ b/src/core/capi.c @@ -50,11 +50,18 @@ JANET_NO_RETURN static void janet_top_level_signal(const char *msg) { JANET_TOP_LEVEL_SIGNAL(msg); #else fputs(msg, stdout); + if (!(janet_vm.sandbox_flags & JANET_SANDBOX_EXIT)) { + /* Exit is not forbidden */ + exit(EXIT_FAILURE); + } + /* If not able to signal, then select good default behavior - for single threaded programs, we have no + * choice but to exit. */ # ifdef JANET_SINGLE_THREADED - exit(-1); + exit(EXIT_FAILURE); # elif defined(JANET_WINDOWS) ExitThread(-1); # else + /* Other threads will continue as usual */ pthread_exit(NULL); # endif #endif diff --git a/src/core/corelib.c b/src/core/corelib.c index 8c39268a..93857b74 100644 --- a/src/core/corelib.c +++ b/src/core/corelib.c @@ -754,6 +754,7 @@ static const SandboxOption sandbox_options[] = { {"chroot", JANET_SANDBOX_CHROOT}, {"compile", JANET_SANDBOX_COMPILE}, {"env", JANET_SANDBOX_ENV}, + {"exit", JANET_SANDBOX_EXIT}, {"ffi", JANET_SANDBOX_FFI}, {"ffi-define", JANET_SANDBOX_FFI_DEFINE}, {"ffi-jit", JANET_SANDBOX_FFI_JIT}, @@ -784,6 +785,7 @@ JANET_CORE_FN(janet_core_sandbox, "* :chroot - disallow calling `os/posix-chroot`\n" "* :compile - disallow calling `compile`. This will disable a lot of functionality, such as `eval`.\n" "* :env - disallow reading and write env variables\n" + "* :exit - disallow calling `os/exit` or otherwise early exiting the process in trivial ways.\n" "* :ffi - disallow FFI (recommended if disabling anything else)\n" "* :ffi-define - disallow loading new FFI modules and binding new functions\n" "* :ffi-jit - disallow calling `ffi/jitfn`\n" @@ -801,7 +803,7 @@ JANET_CORE_FN(janet_core_sandbox, "* :signal - disallow adding or removing signal handlers\n" "* :subprocess - disallow running subprocesses\n" "* :threads - disallow spawning threads with `ev/thread`. Certain helper threads may still be spawned.\n" - "* :unmarshal - disallow calling the unmarshal function.\n") { + "* :unmarshal - disallow calling the `unmarshal` function.\n") { uint32_t flags = 0; for (int32_t i = 0; i < argc; i++) { JanetKeyword kw = janet_getkeyword(argv, i); diff --git a/src/core/ev.c b/src/core/ev.c index 5731bdd3..80bf486b 100644 --- a/src/core/ev.c +++ b/src/core/ev.c @@ -613,7 +613,7 @@ void janet_ev_init_common(void) { #if JANET_ANDROID static void janet_timeout_stop(int sig_num) { if (sig_num == SIGUSR1) { - pthread_exit(0); + pthread_exit(NULL); } } #endif diff --git a/src/core/os.c b/src/core/os.c index bc7b7bd6..c0ab59a4 100644 --- a/src/core/os.c +++ b/src/core/os.c @@ -276,6 +276,7 @@ JANET_CORE_FN(os_exit, "the exit with status equal the hash of x. If `force` is truthy will exit immediately and " "skip cleanup code.") { janet_arity(argc, 0, 2); + janet_sandbox_assert(JANET_SANDBOX_EXIT); int status; if (argc == 0) { status = EXIT_SUCCESS; diff --git a/src/include/janet.h b/src/include/janet.h index 3f2ace5f..56e8af8c 100644 --- a/src/include/janet.h +++ b/src/include/janet.h @@ -2024,6 +2024,7 @@ JANET_API void janet_stacktrace_ext(JanetFiber *fiber, Janet err, const char *pr #define JANET_SANDBOX_ASM 65536 #define JANET_SANDBOX_THREADS 131072 #define JANET_SANDBOX_UNMARSHAL 262144 +#define JANET_SANDBOX_EXIT 524288 #define JANET_SANDBOX_ALL (UINT32_MAX) JANET_API void janet_sandbox(uint32_t flags); JANET_API void janet_sandbox_assert(uint32_t forbidden_flags); From ceb569782b6a13d2fea2fd3f6dc341d998e97de4 Mon Sep 17 00:00:00 2001 From: pixelherodev Date: Tue, 9 Jun 2026 08:22:22 -0500 Subject: [PATCH 236/307] plan9 port maintenance (#1756) * plan9: fix regressions * plan9: unopaque struct pointer --------- Co-authored-by: Noam Preil --- src/core/io.c | 4 ++++ src/core/marsh.c | 7 ------- src/core/os.c | 4 ++++ src/include/janet.h | 7 +++++++ 4 files changed, 15 insertions(+), 7 deletions(-) diff --git a/src/core/io.c b/src/core/io.c index 5a8476d8..a9d54348 100644 --- a/src/core/io.c +++ b/src/core/io.c @@ -424,7 +424,11 @@ static void io_file_marshal(void *p, JanetMarshalContext *ctx) { if (iof->flags & JANET_FILE_NOT_CLOSEABLE) { fno = fileno(iof->file); } else { +#ifdef JANET_PLAN9 + fno = dup(fileno(iof->file), -1); +#else fno = dup(fileno(iof->file)); +#endif } #endif janet_marshal_int(ctx, fno); diff --git a/src/core/marsh.c b/src/core/marsh.c index a4178b52..fc9e4535 100644 --- a/src/core/marsh.c +++ b/src/core/marsh.c @@ -30,13 +30,6 @@ #include "util.h" #endif -struct JanetMarshalContext { - void *m_state; - void *u_state; - int flags; - const uint8_t *data; - const JanetAbstractType *at; -}; typedef struct { JanetBuffer *buf; diff --git a/src/core/os.c b/src/core/os.c index c0ab59a4..17747a2c 100644 --- a/src/core/os.c +++ b/src/core/os.c @@ -287,7 +287,11 @@ JANET_CORE_FN(os_exit, } janet_deinit(); if (argc >= 2 && janet_truthy(argv[1])) { +#ifdef JANET_PLAN9 + exits(nil); +#else _Exit(status); +#endif } else { exit(status); } diff --git a/src/include/janet.h b/src/include/janet.h index 56e8af8c..e7aad82c 100644 --- a/src/include/janet.h +++ b/src/include/janet.h @@ -1202,6 +1202,13 @@ struct JanetParser { }; /* A context for marshaling and unmarshaling abstract types */ +struct JanetMarshalContext { + void *m_state; + void *u_state; + int flags; + const uint8_t *data; + const JanetAbstractType *at; +}; typedef struct JanetMarshalContext JanetMarshalContext; /* Defines an abstract type. Use a const pointer to one of these structures From 8b883d1304d486842c3b7174b6928d4040325190 Mon Sep 17 00:00:00 2001 From: Arthur Chan Date: Wed, 10 Jun 2026 23:16:37 +0100 Subject: [PATCH 237/307] OSS-Fuzz: Add new fuzzer targets unmarshal (#1762) Signed-off-by: Arthur Chan --- test/fuzzers/fuzz_unmarshal.c | 25 +++++++++++++++++++++++++ 1 file changed, 25 insertions(+) create mode 100644 test/fuzzers/fuzz_unmarshal.c diff --git a/test/fuzzers/fuzz_unmarshal.c b/test/fuzzers/fuzz_unmarshal.c new file mode 100644 index 00000000..d25e6f2e --- /dev/null +++ b/test/fuzzers/fuzz_unmarshal.c @@ -0,0 +1,25 @@ +#include +#include + +/* Disable leak sanitizer */ +int __lsan_is_turned_off(void) { return 1; } + +int LLVMFuzzerTestOneInput(const uint8_t *data, size_t size) { + + /* init Janet */ + janet_init(); + + /* fuzz the binary unmarshal (deserialization) path in src/core/marsh.c */ + JanetTable *reg = janet_env_lookup(janet_core_env(NULL)); + JanetTryState tstate; + if (janet_try(&tstate) == JANET_SIGNAL_OK) { + const uint8_t *next = NULL; + janet_unmarshal(data, size, 0, reg, &next); + } + janet_restore(&tstate); + + /* cleanup Janet */ + janet_deinit(); + + return 0; +} From 004c0b0b4cf53b624ec2026d9f2c117cd632f64c Mon Sep 17 00:00:00 2001 From: Greg Slepak Date: Wed, 10 Jun 2026 15:16:55 -0700 Subject: [PATCH 238/307] Fix documentation for os_stat function (#1760) * Fix documentation for os_stat function * Refactor os_stat documentation for clarity --- src/core/os.c | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/src/core/os.c b/src/core/os.c index 17747a2c..3fa619e9 100644 --- a/src/core/os.c +++ b/src/core/os.c @@ -2506,8 +2506,9 @@ static Janet os_stat_or_lstat(int do_lstat, int32_t argc, Janet *argv) { JANET_CORE_FN(os_stat, "(os/stat path &opt tab|key)", - "Gets information about a file or directory. Returns a table if the second argument is a keyword, returns " - "only that information from stat. If the file or directory does not exist, returns nil. The keys are:\n\n" + "Gets information about a file or directory. Returns a table unless the second argument is a keyword, " + "in which case it returns only that field/value from stat. If the file or directory does not exist, returns nil." + "The keys are:\n\n" "* :dev - the device that the file is on\n\n" "* :mode - the type of file, one of :file, :directory, :block, :character, :fifo, :socket, :link, or :other\n\n" "* :int-permissions - A Unix permission integer like 8r744\n\n" From 0d3635f9f492a6a8f540db752f2606d466998800 Mon Sep 17 00:00:00 2001 From: Calvin Rose Date: Wed, 10 Jun 2026 17:22:22 -0500 Subject: [PATCH 239/307] Format. --- src/core/io.c | 2 +- src/core/os.c | 2 +- test/fuzzers/fuzz_unmarshal.c | 4 +++- 3 files changed, 5 insertions(+), 3 deletions(-) diff --git a/src/core/io.c b/src/core/io.c index a9d54348..61488cb8 100644 --- a/src/core/io.c +++ b/src/core/io.c @@ -425,7 +425,7 @@ static void io_file_marshal(void *p, JanetMarshalContext *ctx) { fno = fileno(iof->file); } else { #ifdef JANET_PLAN9 - fno = dup(fileno(iof->file), -1); + fno = dup(fileno(iof->file), -1); #else fno = dup(fileno(iof->file)); #endif diff --git a/src/core/os.c b/src/core/os.c index 3fa619e9..5ed9617b 100644 --- a/src/core/os.c +++ b/src/core/os.c @@ -288,7 +288,7 @@ JANET_CORE_FN(os_exit, janet_deinit(); if (argc >= 2 && janet_truthy(argv[1])) { #ifdef JANET_PLAN9 - exits(nil); + exits(nil); #else _Exit(status); #endif diff --git a/test/fuzzers/fuzz_unmarshal.c b/test/fuzzers/fuzz_unmarshal.c index d25e6f2e..2434c2dd 100644 --- a/test/fuzzers/fuzz_unmarshal.c +++ b/test/fuzzers/fuzz_unmarshal.c @@ -2,7 +2,9 @@ #include /* Disable leak sanitizer */ -int __lsan_is_turned_off(void) { return 1; } +int __lsan_is_turned_off(void) { + return 1; +} int LLVMFuzzerTestOneInput(const uint8_t *data, size_t size) { From fa4058cdfc679b70f928811630380763a1e40892 Mon Sep 17 00:00:00 2001 From: Calvin Rose Date: Thu, 18 Jun 2026 11:07:49 -0500 Subject: [PATCH 240/307] Avoid undefined behavior with signed left-shift in unmarshal. Can trigger ubsan on bad input. --- src/core/marsh.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/core/marsh.c b/src/core/marsh.c index fc9e4535..b05a8682 100644 --- a/src/core/marsh.c +++ b/src/core/marsh.c @@ -1486,7 +1486,7 @@ static const uint8_t *unmarshal_one( /* Tuple */ Janet *tup = janet_tuple_begin(len); int32_t flag = readint(st, &data); - janet_tuple_flag(tup) |= flag << 16; + janet_tuple_flag(tup) |= (int32_t) (((uint32_t) flag) << 16); /* Avoid left shift of negative value */ for (int32_t i = 0; i < len; i++) { data = unmarshal_one(st, data, tup + i, flags + 1); } From f2f77a4bd592dd598da184f4bede6a6dae33c32f Mon Sep 17 00:00:00 2001 From: Arthur Chan Date: Fri, 19 Jun 2026 00:53:11 +0100 Subject: [PATCH 241/307] OSS-Fuzz: Add fuzzer targets dobytes function (#1765) Signed-off-by: Arthur Chan --- test/fuzzers/fuzz_dobytes.c | 25 +++++++++++++++++++++++++ 1 file changed, 25 insertions(+) create mode 100644 test/fuzzers/fuzz_dobytes.c diff --git a/test/fuzzers/fuzz_dobytes.c b/test/fuzzers/fuzz_dobytes.c new file mode 100644 index 00000000..35257f6c --- /dev/null +++ b/test/fuzzers/fuzz_dobytes.c @@ -0,0 +1,25 @@ +#include +#include + +/* Disable leak sanitizer */ +int __lsan_is_turned_off(void) { return 1; } + +int LLVMFuzzerTestOneInput(const uint8_t *data, size_t size) { + + /* init Janet */ + janet_init(); + + /* fuzz the compile + bytecode VM path (parse, compile, run) in src/core/compile.c and src/core/vm.c */ + JanetTable *env = janet_core_env(NULL); + JanetTryState tstate; + if (janet_try(&tstate) == JANET_SIGNAL_OK) { + Janet out; + janet_dobytes(env, data, (int32_t) size, "", &out); + } + janet_restore(&tstate); + + /* cleanup Janet */ + janet_deinit(); + + return 0; +} From de3bb4ceb2cb2e223dff89e948d4644c978d7d9a Mon Sep 17 00:00:00 2001 From: Calvin Rose Date: Thu, 18 Jun 2026 20:15:23 -0500 Subject: [PATCH 242/307] Add oob read checks in peg bytecode unmarshal. These checks run before any references to rule[x] to avoid reading beyond the end of the buffer. This needs to happen inline, not only at the end of the loop where we check if we landed on the correct ending index. --- src/core/peg.c | 18 ++++++++++++++++++ 1 file changed, 18 insertions(+) diff --git a/src/core/peg.c b/src/core/peg.c index 5e488886..79fee92e 100644 --- a/src/core/peg.c +++ b/src/core/peg.c @@ -1630,6 +1630,8 @@ static size_t size_padded(size_t offset, size_t size) { return x - (x % size); } +#define OVERFLOW_CHECK(n) do { if (i > blen - (n)) goto bad; } while (0) /* overflow */ + static void *peg_unmarshal(JanetMarshalContext *ctx) { size_t bytecode_len = janet_unmarshal_size(ctx); uint32_t num_constants = (uint32_t) janet_unmarshal_int(ctx); @@ -1682,6 +1684,7 @@ static void *peg_unmarshal(JanetMarshalContext *ctx) { op_flags[i] |= 0x02; switch (instr) { case RULE_LITERAL: + OVERFLOW_CHECK(1); /* We only read rule[1] */ i += 2 + ((rule[1] + 3) >> 2); break; case RULE_DEBUG: @@ -1708,6 +1711,7 @@ static void *peg_unmarshal(JanetMarshalContext *ctx) { break; case RULE_LOOK: /* [offset, rule] */ + OVERFLOW_CHECK(3); if (rule[2] >= blen) goto bad; op_flags[rule[2]] |= 0x1; i += 3; @@ -1716,7 +1720,9 @@ static void *peg_unmarshal(JanetMarshalContext *ctx) { case RULE_SEQUENCE: /* [len, rules...] */ { + OVERFLOW_CHECK(2); uint32_t len = rule[1]; + OVERFLOW_CHECK(2 + len); for (uint32_t j = 0; j < len; j++) { if (rule[2 + j] >= blen) goto bad; op_flags[rule[2 + j]] |= 0x1; @@ -1728,6 +1734,7 @@ static void *peg_unmarshal(JanetMarshalContext *ctx) { case RULE_IFNOT: case RULE_LENPREFIX: /* [rule_a, rule_b (b if not a)] */ + OVERFLOW_CHECK(3); if (rule[1] >= blen) goto bad; if (rule[2] >= blen) goto bad; op_flags[rule[1]] |= 0x01; @@ -1736,6 +1743,7 @@ static void *peg_unmarshal(JanetMarshalContext *ctx) { break; case RULE_BETWEEN: /* [lo, hi, rule] */ + OVERFLOW_CHECK(4); if (rule[3] >= blen) goto bad; op_flags[rule[3]] |= 0x01; i += 4; @@ -1751,11 +1759,13 @@ static void *peg_unmarshal(JanetMarshalContext *ctx) { break; case RULE_CONSTANT: /* [constant, tag] */ + OVERFLOW_CHECK(3); if (rule[1] >= clen) goto bad; i += 3; break; case RULE_CAPTURE_NUM: /* [rule, base, tag] */ + OVERFLOW_CHECK(4); if (rule[1] >= blen) goto bad; op_flags[rule[1]] |= 0x01; i += 4; @@ -1765,6 +1775,7 @@ static void *peg_unmarshal(JanetMarshalContext *ctx) { case RULE_CAPTURE: case RULE_UNREF: /* [rule, tag] */ + OVERFLOW_CHECK(3); if (rule[1] >= blen) goto bad; op_flags[rule[1]] |= 0x01; i += 3; @@ -1773,6 +1784,7 @@ static void *peg_unmarshal(JanetMarshalContext *ctx) { case RULE_MATCHTIME: case RULE_MATCHSPLICE: /* [rule, constant, tag] */ + OVERFLOW_CHECK(4); if (rule[1] >= blen) goto bad; if (rule[2] >= clen) goto bad; op_flags[rule[1]] |= 0x01; @@ -1782,6 +1794,7 @@ static void *peg_unmarshal(JanetMarshalContext *ctx) { case RULE_TIL: case RULE_SPLIT: /* [rule, rule] */ + OVERFLOW_CHECK(3); if (rule[1] >= blen) goto bad; if (rule[2] >= blen) goto bad; op_flags[rule[1]] |= 0x01; @@ -1795,17 +1808,20 @@ static void *peg_unmarshal(JanetMarshalContext *ctx) { case RULE_TO: case RULE_THRU: /* [rule] */ + OVERFLOW_CHECK(2); if (rule[1] >= blen) goto bad; op_flags[rule[1]] |= 0x01; i += 2; break; case RULE_READINT: /* [ width | (endianness << 5) | (signedness << 6), tag ] */ + OVERFLOW_CHECK(3); if (rule[1] > JANET_MAX_READINT_WIDTH) goto bad; i += 3; break; case RULE_NTH: /* [nth, rule, tag] */ + OVERFLOW_CHECK(4); if (rule[2] >= blen) goto bad; op_flags[rule[2]] |= 0x01; i += 4; @@ -1835,6 +1851,8 @@ bad: janet_panic("invalid peg bytecode"); } +#undef OVERFLOW_CHECK + static int cfun_peg_getter(JanetAbstract a, Janet key, Janet *out); static Janet peg_next(void *p, Janet key); From ffb83301c58917c26efdd165cfd39ac676bec2d8 Mon Sep 17 00:00:00 2001 From: Calvin Rose Date: Sun, 21 Jun 2026 11:44:19 -0500 Subject: [PATCH 243/307] Handle bad ev/thread construction better. We were incorrectly handling bad arguments to create a new thread resulting in corruption. --- src/core/ev.c | 25 +++++++++++++++++++++---- test/fuzzers/fuzz_dobytes.c | 4 +++- test/suite-ev.janet | 4 ++++ 3 files changed, 28 insertions(+), 5 deletions(-) diff --git a/src/core/ev.c b/src/core/ev.c index 80bf486b..7fe8b17f 100644 --- a/src/core/ev.c +++ b/src/core/ev.c @@ -2326,6 +2326,14 @@ void janet_ev_threaded_call(JanetThreadedSubroutine fp, JanetEVGenericMessage ar /* Default callback for janet_ev_threaded_await. */ void janet_ev_default_threaded_callback(JanetEVGenericMessage return_value) { if (return_value.fiber == NULL) { + /* Clean up */ + switch (return_value.tag) { + default: + case JANET_EV_TCTAG_STRINGF: + case JANET_EV_TCTAG_ERR_STRINGF: + janet_free(return_value.argp); + break; + } return; } if (janet_fiber_can_resume(return_value.fiber)) { @@ -2340,7 +2348,6 @@ void janet_ev_default_threaded_callback(JanetEVGenericMessage return_value) { case JANET_EV_TCTAG_STRING: case JANET_EV_TCTAG_STRINGF: janet_schedule(return_value.fiber, janet_cstringv((const char *) return_value.argp)); - if (return_value.tag == JANET_EV_TCTAG_STRINGF) janet_free(return_value.argp); break; case JANET_EV_TCTAG_KEYWORD: janet_schedule(return_value.fiber, janet_ckeywordv((const char *) return_value.argp)); @@ -2348,7 +2355,6 @@ void janet_ev_default_threaded_callback(JanetEVGenericMessage return_value) { case JANET_EV_TCTAG_ERR_STRING: case JANET_EV_TCTAG_ERR_STRINGF: janet_cancel(return_value.fiber, janet_cstringv((const char *) return_value.argp)); - if (return_value.tag == JANET_EV_TCTAG_STRINGF) janet_free(return_value.argp); break; case JANET_EV_TCTAG_ERR_KEYWORD: janet_cancel(return_value.fiber, janet_ckeywordv((const char *) return_value.argp)); @@ -2358,6 +2364,14 @@ void janet_ev_default_threaded_callback(JanetEVGenericMessage return_value) { break; } } + /* Clean up */ + switch (return_value.tag) { + default: + case JANET_EV_TCTAG_STRINGF: + case JANET_EV_TCTAG_ERR_STRINGF: + janet_free(return_value.argp); + break; + } janet_gcunroot(janet_wrap_fiber(return_value.fiber)); } @@ -3054,6 +3068,7 @@ static JanetEVGenericMessage janet_go_thread_subr(JanetEVGenericMessage args) { const uint8_t *endbytes = nextbytes + buffer->count; uint32_t flags = args.tag; args.tag = 0; + args.argp = NULL; janet_init(); janet_vm.sandbox_flags = (uint32_t) args.argi; JanetTryState tstate; @@ -3109,7 +3124,7 @@ static JanetEVGenericMessage janet_go_thread_subr(JanetEVGenericMessage args) { janet_panicf("expected function or fiber, got %v", fiberv); } JanetFunction *func = janet_unwrap_function(fiberv); - fiber = janet_fiber(func, 64, func->def->min_arity, &value); + fiber = janet_fiber(func, 64, func->def->min_arity ? 1 : 0, &value); if (fiber == NULL) { janet_panicf("thread function must accept 0 or 1 arguments"); } @@ -3148,7 +3163,9 @@ static JanetEVGenericMessage janet_go_thread_subr(JanetEVGenericMessage args) { /* Make ev/thread call from parent thread error */ if (janet_checktype(tstate.payload, JANET_STRING)) { args.tag = JANET_EV_TCTAG_ERR_STRINGF; - args.argp = strdup((const char *) janet_unwrap_string(tstate.payload)); + JanetString msg = janet_unwrap_string(tstate.payload); + args.argp = janet_malloc(janet_string_length(msg) + 1); + memcpy(args.argp, msg, janet_string_length(msg) + 1); } else { args.tag = JANET_EV_TCTAG_ERR_STRING; args.argp = "failed to start thread"; diff --git a/test/fuzzers/fuzz_dobytes.c b/test/fuzzers/fuzz_dobytes.c index 35257f6c..4f0fb01d 100644 --- a/test/fuzzers/fuzz_dobytes.c +++ b/test/fuzzers/fuzz_dobytes.c @@ -2,7 +2,9 @@ #include /* Disable leak sanitizer */ -int __lsan_is_turned_off(void) { return 1; } +int __lsan_is_turned_off(void) { + return 1; +} int LLVMFuzzerTestOneInput(const uint8_t *data, size_t size) { diff --git a/test/suite-ev.janet b/test/suite-ev.janet index 77c8ad39..b1829681 100644 --- a/test/suite-ev.janet +++ b/test/suite-ev.janet @@ -605,4 +605,8 @@ (assert (zero? exit-code) "subprocess ran") (assert (= data "hi\nthere\n") "output is correct") +# Error handling +(assert-error "bad thread" (ev/thread in)) +(assert-error "bad thread 2" (ev/thread (fn [x y] x) 1)) + (end-suite) From 7cf9bd952433fb725a46c0c91ac4a2f58e4dafd4 Mon Sep 17 00:00:00 2001 From: Calvin Rose Date: Sun, 21 Jun 2026 12:50:41 -0500 Subject: [PATCH 244/307] Add checks for bytecode jumps in compilers. In the case where we have long jumps, emit a compiler error instead of silently miscompiling. --- src/core/bytecode.c | 4 ++++ src/core/specials.c | 40 ++++++++++++++++++++++++++++++++++++++-- 2 files changed, 42 insertions(+), 2 deletions(-) diff --git a/src/core/bytecode.c b/src/core/bytecode.c index d33da61f..8424efd6 100644 --- a/src/core/bytecode.c +++ b/src/core/bytecode.c @@ -139,6 +139,8 @@ void janet_bytecode_remove_noops(JanetFuncDef *def) { case JOP_JUMP: /* relative pc is in DS field of instruction */ old_jump_target = i + (((int32_t)instr) >> 8); + janet_assert(old_jump_target >= 0, "bounds"); + janet_assert(old_jump_target < def->bytecode_length, "bounds"); new_jump_target = pc_map[old_jump_target]; instr += (uint32_t)(new_jump_target - old_jump_target + (i - j)) << 8; break; @@ -148,6 +150,8 @@ void janet_bytecode_remove_noops(JanetFuncDef *def) { case JOP_JUMP_IF_NOT_NIL: /* relative pc is in ES field of instruction */ old_jump_target = i + (((int32_t)instr) >> 16); + janet_assert(old_jump_target >= 0, "bounds"); + janet_assert(old_jump_target < def->bytecode_length, "bounds"); new_jump_target = pc_map[old_jump_target]; instr += (uint32_t)(new_jump_target - old_jump_target + (i - j)) << 16; break; diff --git a/src/core/specials.c b/src/core/specials.c index df0621c8..5f843ac2 100644 --- a/src/core/specials.c +++ b/src/core/specials.c @@ -29,6 +29,30 @@ #include "emit.h" #endif +static int bad_16bit_jump(int32_t lab1, int32_t lab2) { + if (lab2 - lab1 > INT16_MAX) return 1; + if (lab2 - lab1 < INT16_MIN) return 1; + return 0; +} + +static void check_16bit_jump(JanetCompiler *c, int32_t lab1, int32_t lab2) { + if (bad_16bit_jump(lab1, lab2)) { + janetc_cerror(c, "bad 16-bit jump, too large"); + } +} + +static int bad_24bit_jump(int32_t lab1, int32_t lab2) { + if (lab2 - lab1 > 0xFFFFFF) return 1; + if (lab2 - lab1 < -0x1000000) return 1; + return 0; +} + +static void check_24bit_jump(JanetCompiler *c, int32_t lab1, int32_t lab2) { + if (bad_24bit_jump(lab1, lab2)) { + janetc_cerror(c, "bad 24-bit jump, too large"); + } +} + static JanetSlot janetc_quote(JanetFopts opts, int32_t argn, const Janet *argv) { if (argn != 1) { janetc_cerror(opts.compiler, "expected 1 argument to quote"); @@ -209,6 +233,8 @@ static int destructure(JanetCompiler *c, int32_t label_loop_exit = janet_v_count(c->buffer); /* avoid shifting negative numbers */ + check_16bit_jump(c, label_loop_cond_jump, label_loop_exit); + check_24bit_jump(c, label_loop_start, label_loop_loop); c->buffer[label_loop_cond_jump] |= (uint32_t)(label_loop_exit - label_loop_cond_jump) << 16; c->buffer[label_loop_loop] |= (uint32_t)(label_loop_start - label_loop_loop) << 8; @@ -696,8 +722,12 @@ static JanetSlot janetc_if(JanetFopts opts, int32_t argn, const Janet *argv) { /* Write jumps - only add jump lengths if jump actually emitted */ labeld = janet_v_count(c->buffer); if (labeljr < labeld) { + check_16bit_jump(c, labeljr, labelr); c->buffer[labeljr] |= (labelr - labeljr) << 16; - if (!tail && labeljd < labeld) c->buffer[labeljd] |= (labeld - labeljd) << 8; + if (!tail && labeljd < labeld) { + check_24bit_jump(c, labeljd, labeld); + c->buffer[labeljd] |= (labeld - labeljd) << 8; + } } if (tail) target.flags |= JANET_SLOT_RETURNED; @@ -938,12 +968,18 @@ static JanetSlot janetc_while(JanetFopts opts, int32_t argn, const Janet *argv) /* Calculate jumps */ labeld = janet_v_count(c->buffer); - if (!infinite) c->buffer[labelc] |= (uint32_t)(labeld - labelc) << 16; + if (!infinite) { + check_16bit_jump(c, labelc, labeld); + c->buffer[labelc] |= (uint32_t)(labeld - labelc) << 16; + } + + check_24bit_jump(c, labeljt, labelwt); c->buffer[labeljt] |= (uint32_t)(labelwt - labeljt) << 8; /* Calculate breaks */ for (int32_t i = labelwt; i < labeld; i++) { if (c->buffer[i] == (0x80 | JOP_JUMP)) { + check_24bit_jump(c, i, labeld); c->buffer[i] = JOP_JUMP | ((labeld - i) << 8); } } From c6e163b07ed77782e894e3758e06407ca1e3b32e Mon Sep 17 00:00:00 2001 From: Calvin Rose Date: Sun, 21 Jun 2026 12:54:41 -0500 Subject: [PATCH 245/307] Remove read after janet_deinit --- src/core/os.c | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/src/core/os.c b/src/core/os.c index 5ed9617b..b4cc51ae 100644 --- a/src/core/os.c +++ b/src/core/os.c @@ -285,8 +285,9 @@ JANET_CORE_FN(os_exit, } else { status = EXIT_FAILURE; } + int force = (argc >= 2 && janet_truthy(argv[1])); janet_deinit(); - if (argc >= 2 && janet_truthy(argv[1])) { + if (force) { #ifdef JANET_PLAN9 exits(nil); #else From 0e0cbc3ab0787c67e1ca81ca6cf6b97e9e378b4d Mon Sep 17 00:00:00 2001 From: Arthur Chan Date: Fri, 26 Jun 2026 03:13:02 +0100 Subject: [PATCH 246/307] OSS-Fuzz: Add fuzzer targets compile process (#1767) Signed-off-by: Arthur Chan --- test/fuzzers/fuzz_compile.c | 41 +++++++++++++++++++++++++++++++++++++ 1 file changed, 41 insertions(+) create mode 100644 test/fuzzers/fuzz_compile.c diff --git a/test/fuzzers/fuzz_compile.c b/test/fuzzers/fuzz_compile.c new file mode 100644 index 00000000..953bb090 --- /dev/null +++ b/test/fuzzers/fuzz_compile.c @@ -0,0 +1,41 @@ +#include +#include + +/* Disable leak sanitizer */ +int __lsan_is_turned_off(void) { + return 1; +} + +int LLVMFuzzerTestOneInput(const uint8_t *data, size_t size) { + + /* init Janet */ + janet_init(); + + /* fuzz the compiler in src/core/compile.c: parse the untrusted source and + compile each produced form to bytecode */ + JanetTable *env = janet_core_env(NULL); + JanetParser parser; + janet_parser_init(&parser); + + JanetTryState tstate; + if (janet_try(&tstate) == JANET_SIGNAL_OK) { + for (size_t i = 0; i < size; i++) { + if (janet_parser_status(&parser) == JANET_PARSE_ERROR) + break; + janet_parser_consume(&parser, data[i]); + while (janet_parser_has_more(&parser)) { + Janet form = janet_parser_produce(&parser); + JanetCompileResult res = janet_compile(form, env, janet_cstring("fuzz")); + (void) res; + } + } + } + janet_restore(&tstate); + + janet_parser_deinit(&parser); + + /* cleanup Janet */ + janet_deinit(); + + return 0; +} From 88c42fff4850d29f96ec30e602913a39c8ff1afb Mon Sep 17 00:00:00 2001 From: Calvin Rose Date: Sat, 27 Jun 2026 14:00:07 -0500 Subject: [PATCH 247/307] Address fuzzer issue with undefined behavior on memory overflow. --- src/core/util.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/core/util.c b/src/core/util.c index 66498384..43649822 100644 --- a/src/core/util.c +++ b/src/core/util.c @@ -277,7 +277,7 @@ int32_t janet_tablen(int32_t n) { n |= n >> 4; n |= n >> 8; n |= n >> 16; - return n + 1; + return n == INT32_MAX ? INT32_MAX : n + 1; } /* Avoid some undefined behavior that was common in the code base. */ From 8dbe828504c9fd9c075fedb3620c8f082cf2860e Mon Sep 17 00:00:00 2001 From: Calvin Rose Date: Sat, 27 Jun 2026 14:24:37 -0500 Subject: [PATCH 248/307] Add casts to u32 before left shifts to address ubsan edge cases. Common idiom that wasn't always applied, especially in emit.c and cfuns.c --- src/core/cfuns.c | 4 ++-- src/core/compile.c | 6 +++++- src/core/corelib.c | 17 ++++++++++------- src/core/emit.c | 34 +++++++++++++++++----------------- src/core/specials.c | 14 +++++++------- test/suite-compile.janet | 2 ++ test/suite-corelib.janet | 2 ++ 7 files changed, 45 insertions(+), 34 deletions(-) diff --git a/src/core/cfuns.c b/src/core/cfuns.c index 2c09ead4..571b3622 100644 --- a/src/core/cfuns.c +++ b/src/core/cfuns.c @@ -188,7 +188,7 @@ static JanetSlot do_get(JanetFopts opts, JanetSlot *args) { janetc_copy(c, t, dflt_slot); if (target_is_default) janetc_freeslot(c, dflt_slot); int32_t current = janet_v_count(c->buffer); - c->buffer[label] |= (current - label) << 16; + c->buffer[label] |= (uint32_t)(current - label) << 16; return t; } else { return opreduce(opts, args, JOP_GET, 0, janet_wrap_nil(), janet_wrap_nil()); @@ -345,7 +345,7 @@ static JanetSlot compreduce( int32_t end = janet_v_count(c->buffer); for (i = 0; i < janet_v_count(labels); i++) { int32_t label = labels[i]; - c->buffer[label] |= ((end - label) << 16); + c->buffer[label] |= ((uint32_t)(end - label) << 16); } janet_v_free(labels); return t; diff --git a/src/core/compile.c b/src/core/compile.c index cedffaf2..1ded7d26 100644 --- a/src/core/compile.c +++ b/src/core/compile.c @@ -280,7 +280,11 @@ Shadowing janetc_shadowcheck(JanetCompiler *c, const uint8_t *sym) { for (int32_t i = len - 1; i >= 0; i--) { SymPair *pair = scope->syms + i; if (pair->sym == sym) { - janet_assert(!is_global, "shadowing analysis is incorrect. compiler bug"); + if (is_global) { + /* This can happen in a top level form like (def [x [x y]] [1 [2 3]])` where a symbol is reused in one expression */ + /* janet_assert(!is_global, "shadowing analysis is incorrect. compiler bug"); */ + return JANETC_SHADOW_GLOBAL_HIDES_GLOBAL; + } return JANETC_SHADOW_LOCAL_HIDES_LOCAL; } } diff --git a/src/core/corelib.c b/src/core/corelib.c index 93857b74..bf8401bb 100644 --- a/src/core/corelib.c +++ b/src/core/corelib.c @@ -441,19 +441,22 @@ JANET_CORE_FN(janet_core_range, start = janet_getnumber(argv, 0); stop = janet_getnumber(argv, 1); step = janet_getnumber(argv, 2); - count = (step > 0) ? (stop - start) / step : - ((step < 0) ? (stop - start) / step : 0); + count = (step > 0.0) ? (stop - start) / step : + ((step < 0.0) ? (stop - start) / step : 0.0); } else if (argc == 2) { - start = janet_getnumber(argv, 0); - stop = janet_getnumber(argv, 1); + start = janet_getnumber(argv, 0.0); + stop = janet_getnumber(argv, 1.0); count = stop - start; } else { - stop = janet_getnumber(argv, 0); + stop = janet_getnumber(argv, 0.0); count = stop; } - count = (count > 0) ? count : 0; + if (isinf(step)) { + janet_panic("infinite step not allowed"); + } + count = (count > 0.0) ? count : 0.0; int32_t int_count; - janet_assert(count >= 0, "bad range code"); + janet_assert(count >= 0.0, "bad range code"); if (count > (double) INT32_MAX) { janet_panicf("range is too large, %f elements", count); } else { diff --git a/src/core/emit.c b/src/core/emit.c index 290a8990..32e20fc9 100644 --- a/src/core/emit.c +++ b/src/core/emit.c @@ -78,10 +78,10 @@ static int32_t janetc_const(JanetCompiler *c, Janet x) { static void janetc_loadconst(JanetCompiler *c, Janet k, int32_t reg) { switch (janet_type(k)) { case JANET_NIL: - janetc_emit(c, (reg << 8) | JOP_LOAD_NIL); + janetc_emit(c, ((uint32_t) reg << 8) | JOP_LOAD_NIL); break; case JANET_BOOLEAN: - janetc_emit(c, (reg << 8) | + janetc_emit(c, ((uint32_t) reg << 8) | (janet_unwrap_boolean(k) ? JOP_LOAD_TRUE : JOP_LOAD_FALSE)); break; case JANET_NUMBER: { @@ -93,8 +93,8 @@ static void janetc_loadconst(JanetCompiler *c, Janet k, int32_t reg) { goto do_constant; uint32_t iu = (uint32_t)i; janetc_emit(c, - (iu << 16) | - (reg << 8) | + ((uint32_t) iu << 16) | + ((uint32_t) reg << 8) | JOP_LOAD_INTEGER); break; } @@ -102,8 +102,8 @@ static void janetc_loadconst(JanetCompiler *c, Janet k, int32_t reg) { do_constant: { int32_t cindex = janetc_const(c, k); janetc_emit(c, - (cindex << 16) | - (reg << 8) | + ((uint32_t) cindex << 16) | + ((uint32_t) reg << 8) | JOP_LOAD_CONSTANT); break; } @@ -120,8 +120,8 @@ static void janetc_movenear(JanetCompiler *c, /* If we also are a reference, deref the one element array */ if (src.flags & JANET_SLOT_REF) { janetc_emit(c, - (dest << 16) | - (dest << 8) | + ((uint32_t) dest << 16) | + ((uint32_t) dest << 8) | JOP_GET_INDEX); } } else if (src.envindex >= 0) { @@ -147,15 +147,15 @@ static void janetc_moveback(JanetCompiler *c, int32_t refreg = janetc_regalloc_temp(&c->scope->ra, JANETC_REGTEMP_5); janetc_loadconst(c, dest.constant, refreg); janetc_emit(c, - (src << 16) | - (refreg << 8) | + ((uint32_t) src << 16) | + ((uint32_t) refreg << 8) | JOP_PUT_INDEX); janetc_regalloc_freetemp(&c->scope->ra, refreg, JANETC_REGTEMP_5); } else if (dest.envindex >= 0) { /* Convert src to near reg */ if (src > 255) { int32_t newsrc = JANETC_REGTEMP_5 + 0xF0; - janetc_emit(c, JOP_MOVE_NEAR | ((uint32_t)(src) << 16) | (newsrc << 8)); + janetc_emit(c, JOP_MOVE_NEAR | ((uint32_t)(src) << 16) | ((uint32_t) newsrc << 8)); src = newsrc; } janetc_emit(c, @@ -199,7 +199,7 @@ static int32_t janetc_regfar(JanetCompiler *c, JanetSlot s, JanetcRegisterTemp t janetc_movenear(c, nearreg, s); if (nearreg >= 0xF0) { reg = janetc_allocfar(c); - janetc_emit(c, JOP_MOVE_FAR | (nearreg << 8) | (reg << 16)); + janetc_emit(c, JOP_MOVE_FAR | ((uint32_t) nearreg << 8) | ((uint32_t) reg << 16)); janetc_regalloc_freetemp(&c->scope->ra, nearreg, tag); } else { reg = nearreg; @@ -268,7 +268,7 @@ void janetc_copy( static int32_t emit1s(JanetCompiler *c, uint8_t op, JanetSlot s, int32_t rest, int wr) { int32_t reg = janetc_regnear(c, s, JANETC_REGTEMP_0); int32_t label = janet_v_count(c->buffer); - janetc_emit(c, op | (reg << 8) | ((uint32_t)rest << 16)); + janetc_emit(c, op | ((uint32_t) reg << 8) | ((uint32_t) rest << 16)); if (wr) janetc_moveback(c, s, reg); janetc_free_regnear(c, s, reg, JANETC_REGTEMP_0); @@ -278,7 +278,7 @@ static int32_t emit1s(JanetCompiler *c, uint8_t op, JanetSlot s, int32_t rest, i int32_t janetc_emit_s(JanetCompiler *c, uint8_t op, JanetSlot s, int wr) { int32_t reg = janetc_regfar(c, s, JANETC_REGTEMP_0); int32_t label = janet_v_count(c->buffer); - janetc_emit(c, op | (reg << 8)); + janetc_emit(c, op | ((uint32_t) reg << 8)); if (wr) janetc_moveback(c, s, reg); janetc_free_regnear(c, s, reg, JANETC_REGTEMP_0); @@ -310,7 +310,7 @@ static int32_t emit2s(JanetCompiler *c, uint8_t op, JanetSlot s1, JanetSlot s2, int32_t reg1 = janetc_regnear(c, s1, JANETC_REGTEMP_0); int32_t reg2 = janetc_regnear(c, s2, JANETC_REGTEMP_1); int32_t label = janet_v_count(c->buffer); - janetc_emit(c, op | (reg1 << 8) | (reg2 << 16) | ((uint32_t)rest << 24)); + janetc_emit(c, op | ((uint32_t) reg1 << 8) | ((uint32_t) reg2 << 16) | ((uint32_t)rest << 24)); janetc_free_regnear(c, s2, reg2, JANETC_REGTEMP_1); if (wr) janetc_moveback(c, s1, reg1); @@ -322,7 +322,7 @@ int32_t janetc_emit_ss(JanetCompiler *c, uint8_t op, JanetSlot s1, JanetSlot s2, int32_t reg1 = janetc_regnear(c, s1, JANETC_REGTEMP_0); int32_t reg2 = janetc_regfar(c, s2, JANETC_REGTEMP_1); int32_t label = janet_v_count(c->buffer); - janetc_emit(c, op | (reg1 << 8) | (reg2 << 16)); + janetc_emit(c, op | ((uint32_t) reg1 << 8) | ((uint32_t) reg2 << 16)); janetc_free_regnear(c, s2, reg2, JANETC_REGTEMP_1); if (wr) janetc_moveback(c, s1, reg1); @@ -343,7 +343,7 @@ int32_t janetc_emit_sss(JanetCompiler *c, uint8_t op, JanetSlot s1, JanetSlot s2 int32_t reg2 = janetc_regnear(c, s2, JANETC_REGTEMP_1); int32_t reg3 = janetc_regnear(c, s3, JANETC_REGTEMP_2); int32_t label = janet_v_count(c->buffer); - janetc_emit(c, op | (reg1 << 8) | (reg2 << 16) | ((uint32_t)reg3 << 24)); + janetc_emit(c, op | ((uint32_t) reg1 << 8) | ((uint32_t) reg2 << 16) | ((uint32_t) reg3 << 24)); janetc_free_regnear(c, s2, reg2, JANETC_REGTEMP_1); janetc_free_regnear(c, s3, reg3, JANETC_REGTEMP_2); if (wr) diff --git a/src/core/specials.c b/src/core/specials.c index 5f843ac2..646f0ad1 100644 --- a/src/core/specials.c +++ b/src/core/specials.c @@ -723,10 +723,10 @@ static JanetSlot janetc_if(JanetFopts opts, int32_t argn, const Janet *argv) { labeld = janet_v_count(c->buffer); if (labeljr < labeld) { check_16bit_jump(c, labeljr, labelr); - c->buffer[labeljr] |= (labelr - labeljr) << 16; + c->buffer[labeljr] |= (uint32_t) (labelr - labeljr) << 16; if (!tail && labeljd < labeld) { check_24bit_jump(c, labeljd, labeld); - c->buffer[labeljd] |= (labeld - labeljd) << 8; + c->buffer[labeljd] |= (uint32_t) (labeld - labeljd) << 8; } } @@ -945,8 +945,8 @@ static JanetSlot janetc_while(JanetFopts opts, int32_t argn, const Janet *argv) } /* But now add tail recursion */ int32_t tempself = janetc_regalloc_temp(&tempscope.ra, JANETC_REGTEMP_0); - janetc_emit(c, JOP_LOAD_SELF | (tempself << 8)); - janetc_emit(c, JOP_TAILCALL | (tempself << 8)); + janetc_emit(c, JOP_LOAD_SELF | ((uint32_t) tempself << 8)); + janetc_emit(c, JOP_TAILCALL | ((uint32_t) tempself << 8)); janetc_regalloc_freetemp(&c->scope->ra, tempself, JANETC_REGTEMP_0); /* Compile function */ JanetFuncDef *def = janetc_pop_funcdef(c); @@ -955,8 +955,8 @@ static JanetSlot janetc_while(JanetFopts opts, int32_t argn, const Janet *argv) int32_t defindex = janetc_addfuncdef(c, def); /* And then load the closure and call it. */ int32_t cloreg = janetc_regalloc_temp(&c->scope->ra, JANETC_REGTEMP_0); - janetc_emit(c, JOP_CLOSURE | (cloreg << 8) | (defindex << 16)); - janetc_emit(c, JOP_CALL | (cloreg << 8) | (cloreg << 16)); + janetc_emit(c, JOP_CLOSURE | ((uint32_t) cloreg << 8) | ((uint32_t) defindex << 16)); + janetc_emit(c, JOP_CALL | ((uint32_t) cloreg << 8) | ((uint32_t) cloreg << 16)); janetc_regalloc_freetemp(&c->scope->ra, cloreg, JANETC_REGTEMP_0); c->scope->flags |= JANET_SCOPE_CLOSURE; return janetc_cslot(janet_wrap_nil()); @@ -980,7 +980,7 @@ static JanetSlot janetc_while(JanetFopts opts, int32_t argn, const Janet *argv) for (int32_t i = labelwt; i < labeld; i++) { if (c->buffer[i] == (0x80 | JOP_JUMP)) { check_24bit_jump(c, i, labeld); - c->buffer[i] = JOP_JUMP | ((labeld - i) << 8); + c->buffer[i] = JOP_JUMP | ((uint32_t) (labeld - i) << 8); } } diff --git a/test/suite-compile.janet b/test/suite-compile.janet index 5b0c5d54..bb815ad5 100644 --- a/test/suite-compile.janet +++ b/test/suite-compile.janet @@ -156,4 +156,6 @@ (check-lint-compile '(let [outer1 "b"] outer1) "shadow local-to-global") (check-lint-compile '(do (def x "b") (def x "c")) "shadow local-to-local") +(check-lint-compile '(def [xxx [xxx yyy]] [1 [2 3]]) "shadow global-to-global one form") + (end-suite) diff --git a/test/suite-corelib.janet b/test/suite-corelib.janet index 2454cb85..a8120573 100644 --- a/test/suite-corelib.janet +++ b/test/suite-corelib.janet @@ -207,4 +207,6 @@ '(fn :short-fn [] (set (my-table [2 1]) (quote foo)))) "Macro expand inside set preserves tuple type correctly") +(assert-error "no infinite step range" (range 3 3 math/inf)) + (end-suite) From 54411dc53622739cbdca81da4cb1a5fc33460922 Mon Sep 17 00:00:00 2001 From: Jacob Mealey Date: Sun, 28 Jun 2026 09:51:27 -0400 Subject: [PATCH 249/307] Fix typos in boot.janet (#1774) --- src/boot/boot.janet | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/boot/boot.janet b/src/boot/boot.janet index b620488e..e6a48776 100644 --- a/src/boot/boot.janet +++ b/src/boot/boot.janet @@ -1115,7 +1115,7 @@ no `inds` are provided. Multiple data structures can be handled if each `inds` is a data structure and `f` is a function of arity one more than the number of `inds`. Note that `f` is only - applied to values at indeces up to the largest index of the + applied to values at indices up to the largest index of the shortest of `ind` and each of `inds`. ``` [f ind & inds] @@ -1140,7 +1140,7 @@ provided. Multiple data structures can be handled if each `inds` is a data structure and `pred` is a function of arity one more than the number of `inds`. Note that `pred` is only applied to - values at indeces up to the largest index of the shortest of + values at indices up to the largest index of the shortest of `ind` and each of `inds`. ``` [pred ind & inds] From 0773444faaf6ad252cc5af6df84b83392b99f9d4 Mon Sep 17 00:00:00 2001 From: Samuel Martineau Date: Sun, 28 Jun 2026 16:44:55 -0400 Subject: [PATCH 250/307] Unregister stream from EV upon closure (#1770) * Prototype fix for #1769 * Add fix for kqueue * Refactor to fix compilation error on macOS/BSD --- src/core/ev.c | 57 ++++++++++++++++++++++++++++++++++++++++++--------- 1 file changed, 47 insertions(+), 10 deletions(-) diff --git a/src/core/ev.c b/src/core/ev.c index 7fe8b17f..1429ce48 100644 --- a/src/core/ev.c +++ b/src/core/ev.c @@ -326,6 +326,9 @@ static void janet_stream_checktoclose(JanetStream *stream) { /* Forward declaration */ static void janet_register_stream(JanetStream *stream); +#ifndef JANET_WINDOWS +static void janet_unregister_stream(JanetStream *stream); +#endif static const JanetMethod ev_default_stream_methods[] = { {"close", janet_cfun_stream_close}, @@ -357,6 +360,7 @@ JanetStream *janet_stream(JanetHandle handle, uint32_t flags, const JanetMethod static void janet_stream_close_impl(JanetStream *stream) { stream->flags |= JANET_STREAM_CLOSED; int canclose = !(stream->flags & JANET_STREAM_NOT_CLOSEABLE); + int canunregister = !(stream->flags & JANET_STREAM_UNREGISTERED); #ifdef JANET_WINDOWS if (stream->handle != INVALID_HANDLE_VALUE) { #ifdef JANET_NET @@ -371,18 +375,9 @@ static void janet_stream_close_impl(JanetStream *stream) { } #else if (stream->handle != -1) { + if (canunregister) janet_unregister_stream(stream); if (canclose) close(stream->handle); stream->handle = -1; -#ifdef JANET_EV_POLL - uint32_t i = stream->index; - size_t j = janet_vm.stream_count - 1; - JanetStream *last = janet_vm.streams[j]; - struct pollfd lastfd = janet_vm.fds[j + 1]; - janet_vm.fds[i + 1] = lastfd; - janet_vm.streams[i] = last; - last->index = stream->index; - janet_vm.stream_count--; -#endif } #endif } @@ -1786,6 +1781,17 @@ void janet_stream_level_triggered(JanetStream *stream) { janet_register_stream_impl(stream, 1, 0); } +void janet_unregister_stream(JanetStream *stream) { + int status; + do { + status = epoll_ctl(janet_vm.epoll, EPOLL_CTL_DEL, stream->handle, NULL); + } while (status == -1 && errno == EINTR); + if (status == -1) { + janet_panicv(janet_ev_lasterr()); + } + stream->flags |= JANET_STREAM_UNREGISTERED; +} + #define JANET_EPOLL_MAX_EVENTS 64 void janet_loop1_impl(int has_timeout, JanetTimestamp timeout) { struct itimerspec its; @@ -1962,6 +1968,25 @@ void janet_stream_level_triggered(JanetStream *stream) { janet_register_stream_impl(stream, 0); } +void janet_unregister_stream(JanetStream *stream) { + 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); + if (status == -1) { + janet_panicv(janet_ev_lasterr()); + } + stream->flags |= JANET_STREAM_UNREGISTERED; +} + #define JANET_KQUEUE_MAX_EVENTS 512 void janet_loop1_impl(int has_timeout, JanetTimestamp timeout) { @@ -2085,6 +2110,18 @@ void janet_register_stream(JanetStream *stream) { janet_vm.stream_count = new_count; } +void janet_unregister_stream(JanetStream *stream) { + uint32_t i = stream->index; + size_t j = janet_vm.stream_count - 1; + JanetStream *last = janet_vm.streams[j]; + struct pollfd lastfd = janet_vm.fds[j + 1]; + janet_vm.fds[i + 1] = lastfd; + janet_vm.streams[i] = last; + last->index = stream->index; + janet_vm.stream_count--; + stream->flags |= JANET_STREAM_UNREGISTERED; +} + void janet_stream_edge_triggered(JanetStream *stream) { (void) stream; } From 604545a19849d90c359eda26984cea312246a7f6 Mon Sep 17 00:00:00 2001 From: Calvin Rose Date: Sun, 28 Jun 2026 12:37:33 -0500 Subject: [PATCH 251/307] Try tweaks to go_thread_subr for mingw --- src/core/ev.c | 8 +++++--- 1 file changed, 5 insertions(+), 3 deletions(-) diff --git a/src/core/ev.c b/src/core/ev.c index 1429ce48..7ff4af7c 100644 --- a/src/core/ev.c +++ b/src/core/ev.c @@ -3161,10 +3161,11 @@ static JanetEVGenericMessage janet_go_thread_subr(JanetEVGenericMessage args) { janet_panicf("expected function or fiber, got %v", fiberv); } JanetFunction *func = janet_unwrap_function(fiberv); - fiber = janet_fiber(func, 64, func->def->min_arity ? 1 : 0, &value); - if (fiber == NULL) { + if (func->def->min_arity > 1 || func->def->min_arity < 0) { janet_panicf("thread function must accept 0 or 1 arguments"); } + fiber = janet_fiber(func, 64, func->def->min_arity, &value); + janet_assert(fiber != NULL, "bad fiber in thread setup"); fiber->flags |= JANET_FIBER_MASK_ERROR | JANET_FIBER_MASK_USER0 | @@ -3183,7 +3184,9 @@ static JanetEVGenericMessage janet_go_thread_subr(JanetEVGenericMessage args) { janet_schedule(fiber, value); janet_loop(); args.tag = JANET_EV_TCTAG_NIL; + janet_restore(&tstate); } else { + janet_restore(&tstate); void *supervisor = janet_vm.user; if (NULL != supervisor) { /* Got a supervisor, write error there */ @@ -3209,7 +3212,6 @@ static JanetEVGenericMessage janet_go_thread_subr(JanetEVGenericMessage args) { } } } - janet_restore(&tstate); janet_buffer_deinit(buffer); janet_free(buffer); janet_deinit(); From cbb6a912d1bf1b7394ffd2adbed8b890f1fa931b Mon Sep 17 00:00:00 2001 From: Calvin Rose Date: Sun, 28 Jun 2026 15:36:00 -0500 Subject: [PATCH 252/307] Avoid panics in thread setup. Move validation to the parent thread as much as possible. --- src/core/ev.c | 25 ++++++++++++++----------- src/core/net.c | 2 +- 2 files changed, 15 insertions(+), 12 deletions(-) diff --git a/src/core/ev.c b/src/core/ev.c index 7ff4af7c..ecea3c31 100644 --- a/src/core/ev.c +++ b/src/core/ev.c @@ -3116,7 +3116,7 @@ static JanetEVGenericMessage janet_go_thread_subr(JanetEVGenericMessage args) { if (!(flags & 0x2)) { Janet aregv = janet_unmarshal(nextbytes, endbytes - nextbytes, JANET_MARSHAL_UNSAFE, NULL, &nextbytes); - if (!janet_checktype(aregv, JANET_TABLE)) janet_panic("expected table for abstract registry"); + janet_assert(janet_checktype(aregv, JANET_TABLE), "expected table for abstract registry"); janet_vm.abstract_registry = janet_unwrap_table(aregv); janet_gcroot(janet_wrap_table(janet_vm.abstract_registry)); } @@ -3136,9 +3136,7 @@ static JanetEVGenericMessage janet_go_thread_subr(JanetEVGenericMessage args) { memcpy(&count1, nextbytes, sizeof(count1)); size_t count = (size_t) count1; /* Use division to avoid overflowing size_t */ - if (count > (endbytes - nextbytes - sizeof(count1)) / sizeof(JanetCFunRegistry)) { - janet_panic("thread message invalid"); - } + janet_assert(count <= (endbytes - nextbytes - sizeof(count1)) / sizeof(JanetCFunRegistry), "thread message invalid"); janet_vm.registry_count = count; janet_vm.registry_cap = count; janet_vm.registry = janet_malloc(count * sizeof(JanetCFunRegistry)); @@ -3157,13 +3155,11 @@ static JanetEVGenericMessage janet_go_thread_subr(JanetEVGenericMessage args) { JANET_MARSHAL_UNSAFE, NULL, &nextbytes); JanetFiber *fiber; if (!janet_checktype(fiberv, JANET_FIBER)) { - if (!janet_checktype(fiberv, JANET_FUNCTION)) { - janet_panicf("expected function or fiber, got %v", fiberv); - } + janet_assert(janet_checktype(fiberv, JANET_FUNCTION), "expected function or fiber"); JanetFunction *func = janet_unwrap_function(fiberv); - if (func->def->min_arity > 1 || func->def->min_arity < 0) { - janet_panicf("thread function must accept 0 or 1 arguments"); - } + /* TODO - normal panics here do not seem to work correctly on Wine + Mingw. This needs to be investigated, and while it appears to be related to longjmp behavior + * on the platform not working correctly, it is not obvious the issue is. That said, we probably should assert and hard-exit anyway if there is an issue there. */ + janet_assert(func->def->min_arity >= 0 && func->def->min_arity <= 1, "thread function must accept 0 or 1 arguments"); fiber = janet_fiber(func, 64, func->def->min_arity, &value); janet_assert(fiber != NULL, "bad fiber in thread setup"); fiber->flags |= @@ -3233,7 +3229,14 @@ JANET_CORE_FN(cfun_ev_thread, janet_sandbox_assert(JANET_SANDBOX_THREADS); janet_arity(argc, 1, 4); Janet value = argc >= 2 ? argv[1] : janet_wrap_nil(); - if (!janet_checktype(argv[0], JANET_FUNCTION)) janet_getfiber(argv, 0); + if (janet_checktype(argv[0], JANET_FUNCTION)) { + JanetFunction *func = janet_getfunction(argv, 0); + if (func->def->arity < 0 || func->def->min_arity > 1) { + janet_panic("function must take 0 or 1 arguments"); + } + } else { + janet_getfiber(argv, 0); /* arg check for fiber */ + } uint64_t flags = 0; if (argc >= 3) { flags = janet_getflags(argv, 2, "nact"); diff --git a/src/core/net.c b/src/core/net.c index 096e491d..693dfc61 100644 --- a/src/core/net.c +++ b/src/core/net.c @@ -192,7 +192,7 @@ void net_callback_connect(JanetFiber *fiber, JanetAsyncEvent event) { int res = 0; int size = sizeof(res); int r = getsockopt((SOCKET)stream->handle, SOL_SOCKET, SO_CONNECT_TIME, (char *)&res, &size); - if (r == NO_ERROR && res == 0xFFFFFFFF) { + if (r == NO_ERROR && res == (~0)) { return; /* This apparently indicates we haven't yet gotten a connection */ } const int no_error = NO_ERROR; From 9eae852ab6012272cbf9af013b6dd1c5f6d4971c Mon Sep 17 00:00:00 2001 From: Calvin Rose Date: Sun, 28 Jun 2026 16:38:35 -0500 Subject: [PATCH 253/307] Add JANET_STREAM_NODUPS optimization for net code. Saves a lot of syscalls in most cases. Also, remove janet_panic call from inside ev code. --- src/core/ev.c | 10 ++++++---- src/core/net.c | 2 +- src/core/os.c | 9 ++++++++- src/include/janet.h | 1 + 4 files changed, 16 insertions(+), 6 deletions(-) diff --git a/src/core/ev.c b/src/core/ev.c index ecea3c31..b73dd181 100644 --- a/src/core/ev.c +++ b/src/core/ev.c @@ -360,7 +360,8 @@ JanetStream *janet_stream(JanetHandle handle, uint32_t flags, const JanetMethod static void janet_stream_close_impl(JanetStream *stream) { stream->flags |= JANET_STREAM_CLOSED; int canclose = !(stream->flags & JANET_STREAM_NOT_CLOSEABLE); - int canunregister = !(stream->flags & JANET_STREAM_UNREGISTERED); + /* If we are positive that the stream is the last instance of the underlying file description (such that "dup" was never called on the file description) + * then skip unregister to save a syscall */ #ifdef JANET_WINDOWS if (stream->handle != INVALID_HANDLE_VALUE) { #ifdef JANET_NET @@ -374,6 +375,7 @@ static void janet_stream_close_impl(JanetStream *stream) { stream->handle = INVALID_HANDLE_VALUE; } #else + int canunregister = !(stream->flags & JANET_STREAM_UNREGISTERED) && !(stream->flags & JANET_STREAM_NODUPS); if (stream->handle != -1) { if (canunregister) janet_unregister_stream(stream); if (canclose) close(stream->handle); @@ -431,6 +433,7 @@ static void janet_stream_marshal(void *p, JanetMarshalContext *ctx) { if (!(janet_marshal_flags(ctx) & JANET_MARSHAL_UNSAFE)) { janet_panic("can only marshal stream with unsafe flag"); } + s->flags &= ~JANET_STREAM_NODUPS; /* This stream now might be duplicated, invalidates some EV optimizations */ janet_marshal_abstract(ctx, p); janet_marshal_int(ctx, (int32_t) s->flags); janet_marshal_ptr(ctx, s->methods); @@ -1981,9 +1984,7 @@ void janet_unregister_stream(JanetStream *stream) { do { status = kevent(janet_vm.kq, kevs, length, NULL, 0, NULL); } while (status == -1 && errno == EINTR); - if (status == -1) { - janet_panicv(janet_ev_lasterr()); - } + /* Status might be -1 on BSDs for subprocesses */ stream->flags |= JANET_STREAM_UNREGISTERED; } @@ -3573,6 +3574,7 @@ static JanetFile *get_file_for_stream(JanetStream *stream) { } if (index == 0) return NULL; /* duplicate handle when converting stream to file */ + stream->flags &= ~JANET_STREAM_NODUPS; #ifdef JANET_WINDOWS int htype = 0; if (fmt[0] == 'r' && fmt[1] == '+') { diff --git a/src/core/net.c b/src/core/net.c index 693dfc61..ba610622 100644 --- a/src/core/net.c +++ b/src/core/net.c @@ -1245,7 +1245,7 @@ static const JanetMethod net_stream_methods[] = { }; static JanetStream *make_stream(JSock handle, uint32_t flags) { - return janet_stream((JanetHandle) handle, flags | JANET_STREAM_SOCKET, net_stream_methods); + return janet_stream((JanetHandle) handle, flags | JANET_STREAM_SOCKET | JANET_STREAM_NODUPS, net_stream_methods); } void janet_lib_net(JanetTable *env) { diff --git a/src/core/os.c b/src/core/os.c index b4cc51ae..45f87e74 100644 --- a/src/core/os.c +++ b/src/core/os.c @@ -1634,7 +1634,14 @@ JANET_CORE_FN(os_shell, ? janet_getcstring(argv, 0) : NULL; #ifdef JANET_EV - janet_ev_threaded_await(os_shell_subr, 0, argc, cmd ? strdup(cmd) : NULL); + char *cmd_copy = NULL; + if (cmd != NULL) { + size_t cmdlen = strlen(cmd); + cmd_copy = janet_malloc(cmdlen + 1); + memcpy(cmd_copy, cmd, cmdlen); + cmd_copy[cmdlen] = '\0'; + } + janet_ev_threaded_await(os_shell_subr, 0, argc, cmd_copy); #else int stat = system(cmd); return argc diff --git a/src/include/janet.h b/src/include/janet.h index e7aad82c..582e9c81 100644 --- a/src/include/janet.h +++ b/src/include/janet.h @@ -623,6 +623,7 @@ typedef void *JanetAbstract; #define JANET_STREAM_UDPSERVER 0x1000 #define JANET_STREAM_NOT_CLOSEABLE 0x2000 #define JANET_STREAM_TOCLOSE 0x10000 +#define JANET_STREAM_NODUPS 0x20000 typedef enum { JANET_ASYNC_EVENT_INIT = 0, From b070f951805bef351766a3189596cd9c7c3f7844 Mon Sep 17 00:00:00 2001 From: Calvin Rose Date: Sun, 28 Jun 2026 16:58:49 -0500 Subject: [PATCH 254/307] Fix poll implementation. --- src/core/ev.c | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/src/core/ev.c b/src/core/ev.c index b73dd181..491cd873 100644 --- a/src/core/ev.c +++ b/src/core/ev.c @@ -375,7 +375,7 @@ static void janet_stream_close_impl(JanetStream *stream) { stream->handle = INVALID_HANDLE_VALUE; } #else - int canunregister = !(stream->flags & JANET_STREAM_UNREGISTERED) && !(stream->flags & JANET_STREAM_NODUPS); + int canunregister = !(stream->flags & JANET_STREAM_UNREGISTERED); if (stream->handle != -1) { if (canunregister) janet_unregister_stream(stream); if (canclose) close(stream->handle); @@ -1785,6 +1785,7 @@ void janet_stream_level_triggered(JanetStream *stream) { } void janet_unregister_stream(JanetStream *stream) { + if (stream->flags & JANET_STREAM_NODUPS) return; int status; do { status = epoll_ctl(janet_vm.epoll, EPOLL_CTL_DEL, stream->handle, NULL); @@ -1972,6 +1973,7 @@ void janet_stream_level_triggered(JanetStream *stream) { } void janet_unregister_stream(JanetStream *stream) { + if (stream->flags & JANET_STREAM_NODUPS) return; struct kevent kevs[2]; int length = 0; if (stream->flags & (JANET_STREAM_READABLE | JANET_STREAM_ACCEPTABLE)) { From c0b32d4e3798d0ceaf4131e642e59602a31ce151 Mon Sep 17 00:00:00 2001 From: Samuel Martineau Date: Wed, 1 Jul 2026 09:44:11 -0400 Subject: [PATCH 255/307] Possible fix for #1772 (#1773) --- src/core/gc.c | 2 ++ 1 file changed, 2 insertions(+) diff --git a/src/core/gc.c b/src/core/gc.c index 82d68780..be11f347 100644 --- a/src/core/gc.c +++ b/src/core/gc.c @@ -336,6 +336,8 @@ static void janet_deinit_block(JanetGCObject *mem) { if (f->ev_state && !(f->flags & JANET_FIBER_EV_FLAG_IN_FLIGHT)) { janet_ev_dec_refcount(); janet_free(f->ev_state); + } else if (f->gc.flags & JANET_FIBER_EV_FLAG_SUSPENDED) { + janet_ev_dec_refcount(); } #endif janet_free(f->data); From f362e8f0fa6e4f2a1daf95912f73d997ded6e19e Mon Sep 17 00:00:00 2001 From: sogaiu <33044872+sogaiu@users.noreply.github.com> Date: Sun, 2 Aug 2026 21:56:20 +0000 Subject: [PATCH 256/307] Improve comprehensiveness of first docstring (#1789) Co-authored-by: sogaiu <983021772@users.noreply.github.com> --- src/boot/boot.janet | 19 ++++++++++++++++--- 1 file changed, 16 insertions(+), 3 deletions(-) diff --git a/src/boot/boot.janet b/src/boot/boot.janet index e6a48776..5e396cff 100644 --- a/src/boot/boot.janet +++ b/src/boot/boot.janet @@ -841,9 +841,22 @@ [args] (do-extreme < args)) (defn first - "Get the first element from an indexed data structure." - [xs] - (get xs 0)) + `` + Get the first value in `x`. + + If `x` is a non-empty bytes or indexed type value, return the first + element. + + If `x` is a fiber, return the fiber's last value or `nil` if there + isn't one yet. + + If `x` is an abstract type value with a `get` method, call the method + with a key of `0`. + + In all other cases, return `nil`. + `` + [x] + (get x 0)) (defn last "Get the last element from an indexed data structure." From 331e8544bc2ab221648a26ddbb56e41e3e7ffaa9 Mon Sep 17 00:00:00 2001 From: sogaiu <33044872+sogaiu@users.noreply.github.com> Date: Sat, 8 Aug 2026 14:30:20 +0000 Subject: [PATCH 257/307] Simplify frequencies implementation (#1793) * Add tests for frequencies * Simplify frequencies implementation --------- Co-authored-by: sogaiu <983021772@users.noreply.github.com> --- src/boot/boot.janet | 3 +-- test/suite-boot.janet | 8 ++++++++ 2 files changed, 9 insertions(+), 2 deletions(-) diff --git a/src/boot/boot.janet b/src/boot/boot.janet index 5e396cff..ce117b4f 100644 --- a/src/boot/boot.janet +++ b/src/boot/boot.janet @@ -1764,8 +1764,7 @@ [ind] (def freqs @{}) (each x ind - (def n (in freqs x)) - (set (freqs x) (if n (+ 1 n) 1))) + (put freqs x (inc (in freqs x 0)))) freqs) (defn group-by diff --git a/test/suite-boot.janet b/test/suite-boot.janet index c24d4c50..07543a20 100644 --- a/test/suite-boot.janet +++ b/test/suite-boot.janet @@ -1032,4 +1032,12 @@ # regression test for #1659 (assert (= (try (error :error) ([_ _] :caught)) :caught)) +# frequencies +(assert (deep= (frequencies :lol) @{108 2 111 1})) +(assert (deep= (frequencies [:duck :duck :duck :goose]) @{:duck 3 :goose 1})) +(assert (deep= (frequencies {1 :a 2 :x 3 :x 8 :a}) @{:a 2 :x 2})) +(assert (deep= (frequencies @{}) @{})) +(assert (deep= (frequencies (coro (yield 1) (yield 8) (yield 2) (yield 8))) + @{1 1 2 1 8 2})) + (end-suite) From 4370ae8acc5f1141a241becb8b2a2badc75ac8fe Mon Sep 17 00:00:00 2001 From: Calvin Rose Date: Sat, 8 Aug 2026 09:32:02 -0500 Subject: [PATCH 258/307] Address #1792 - Allow janet_collect to be called outside interpreter loop. --- src/core/gc.c | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/src/core/gc.c b/src/core/gc.c index be11f347..229097ea 100644 --- a/src/core/gc.c +++ b/src/core/gc.c @@ -592,7 +592,9 @@ void janet_collect(void) { #ifdef JANET_EV janet_ev_mark(); #endif - janet_mark_fiber(janet_vm.root_fiber); + if (janet_vm.root_fiber != NULL) { /* Can be NULL if janet_collect called outside of interpreter loop */ + janet_mark_fiber(janet_vm.root_fiber); + } for (i = 0; i < orig_rootcount; i++) janet_mark(janet_vm.roots[i]); while (orig_rootcount < janet_vm.root_count) { From ef69c8922138a89bcd546b8116af3229fd43b347 Mon Sep 17 00:00:00 2001 From: Calvin Rose Date: Thu, 13 Aug 2026 17:09:12 -0500 Subject: [PATCH 259/307] Initial support for GNU Hurd. Simply stubs out the operating system keywords. Many ifdefs for Linux can likely be adapted for GNU Hurd. --- src/core/abstract.c | 2 +- src/core/capi.c | 8 ++++---- src/core/ev.c | 2 +- src/core/features.h | 3 +++ src/core/filewatch.c | 18 +++++++++--------- src/core/net.c | 6 +++--- src/core/os.c | 10 +++++++++- src/include/janet.h | 5 +++++ 8 files changed, 35 insertions(+), 19 deletions(-) diff --git a/src/core/abstract.c b/src/core/abstract.c index cbda2296..b1b57480 100644 --- a/src/core/abstract.c +++ b/src/core/abstract.c @@ -37,7 +37,7 @@ /* Create new userdata */ void *janet_abstract_begin(const JanetAbstractType *atype, size_t size) { JanetAbstractHead *header = janet_gcalloc(JANET_MEMORY_NONE, - sizeof(JanetAbstractHead) + size); + sizeof(JanetAbstractHead) + size); header->size = size; header->type = atype; return (void *) & (header->data); diff --git a/src/core/capi.c b/src/core/capi.c index 3e397721..90b39547 100644 --- a/src/core/capi.c +++ b/src/core/capi.c @@ -584,7 +584,7 @@ JanetAtomicInt janet_atomic_inc(JanetAtomicInt volatile *x) { #elif defined(JANET_USE_STDATOMIC) return atomic_fetch_add_explicit(x, 1, memory_order_relaxed) + 1; #elif defined(JANET_PLAN9) - return aincl((void*)x, 1); + return aincl((void *)x, 1); #else return __atomic_add_fetch(x, 1, __ATOMIC_RELAXED); #endif @@ -596,7 +596,7 @@ JanetAtomicInt janet_atomic_dec(JanetAtomicInt volatile *x) { #elif defined(JANET_USE_STDATOMIC) return atomic_fetch_add_explicit(x, -1, memory_order_acq_rel) - 1; #elif defined(JANET_PLAN9) - return aincl((void*)x, -1); + return aincl((void *)x, -1); #else return __atomic_add_fetch(x, -1, __ATOMIC_ACQ_REL); #endif @@ -606,7 +606,7 @@ JanetAtomicInt janet_atomic_load(JanetAtomicInt volatile *x) { #ifdef _MSC_VER return _InterlockedOr(x, 0); #elif defined(JANET_PLAN9) - return agetl((void*)x); + return agetl((void *)x); #elif defined(JANET_USE_STDATOMIC) return atomic_load_explicit(x, memory_order_acquire); #else @@ -618,7 +618,7 @@ JanetAtomicInt janet_atomic_load_relaxed(JanetAtomicInt volatile *x) { #ifdef _MSC_VER return _InterlockedOr(x, 0); #elif defined(JANET_PLAN9) - return agetl((void*)x); + return agetl((void *)x); #elif defined(JANET_USE_STDATOMIC) return atomic_load_explicit(x, memory_order_relaxed); #else diff --git a/src/core/ev.c b/src/core/ev.c index 491cd873..a1c2efaa 100644 --- a/src/core/ev.c +++ b/src/core/ev.c @@ -1553,7 +1553,7 @@ JanetFiber *janet_loop1(void) { } else if (sig == JANET_SIGNAL_OK || (task.fiber->flags & (1 << sig))) { JanetChannel *chan = janet_channel_unwrap(sv); janet_channel_push(chan, make_supervisor_event(janet_signal_names[sig], - task.fiber, chan->is_threaded), 2); + task.fiber, chan->is_threaded), 2); } else if (!is_suspended) { janet_stacktrace_ext(task.fiber, res, ""); } diff --git a/src/core/features.h b/src/core/features.h index 61032004..19562a74 100644 --- a/src/core/features.h +++ b/src/core/features.h @@ -45,6 +45,9 @@ #ifdef __linux__ #define _GNU_SOURCE #endif +#ifdef __gnu_hurd__ +#define _GNU_SOURCE +#endif #if defined(WIN32) || defined(_WIN32) #define WIN32_LEAN_AND_MEAN diff --git a/src/core/filewatch.c b/src/core/filewatch.c index e9c790b3..7bf948f9 100644 --- a/src/core/filewatch.c +++ b/src/core/filewatch.c @@ -92,9 +92,9 @@ static uint32_t decode_watch_flags(const Janet *options, int32_t n) { } JanetKeyword keyw = janet_unwrap_keyword(options[i]); const JanetWatchFlagName *result = janet_strbinsearch(watcher_flags_linux, - sizeof(watcher_flags_linux) / sizeof(JanetWatchFlagName), - sizeof(JanetWatchFlagName), - keyw); + sizeof(watcher_flags_linux) / sizeof(JanetWatchFlagName), + sizeof(JanetWatchFlagName), + keyw); if (!result) { janet_panicf("unknown linux flag %v", options[i]); } @@ -316,9 +316,9 @@ static uint32_t decode_watch_flags(const Janet *options, int32_t n) { } JanetKeyword keyw = janet_unwrap_keyword(options[i]); const JanetWatchFlagName *result = janet_strbinsearch(watcher_flags_windows, - sizeof(watcher_flags_windows) / sizeof(JanetWatchFlagName), - sizeof(JanetWatchFlagName), - keyw); + sizeof(watcher_flags_windows) / sizeof(JanetWatchFlagName), + sizeof(JanetWatchFlagName), + keyw); if (!result) { janet_panicf("unknown windows filewatch flag %v", options[i]); } @@ -580,9 +580,9 @@ static uint32_t decode_watch_flags(const Janet *options, int32_t n) { } JanetKeyword keyw = janet_unwrap_keyword(options[i]); const JanetWatchFlagName *result = janet_strbinsearch(watcher_flags_kqueue, - sizeof(watcher_flags_kqueue) / sizeof(JanetWatchFlagName), - sizeof(JanetWatchFlagName), - keyw); + sizeof(watcher_flags_kqueue) / sizeof(JanetWatchFlagName), + sizeof(JanetWatchFlagName), + keyw); if (!result) { janet_panicf("unknown bsd flag %v", options[i]); } diff --git a/src/core/net.c b/src/core/net.c index ba610622..a1baa9a0 100644 --- a/src/core/net.c +++ b/src/core/net.c @@ -156,8 +156,8 @@ static LPFN_CONNECTEX lazy_get_connectex(JSock sock) { LPFN_CONNECTEX connect_ex_ptr = NULL; DWORD byte_len = 0; int success = WSAIoctl(sock, SIO_GET_EXTENSION_FUNCTION_POINTER, - (void*)&guid, sizeof(guid), - (void*)&connect_ex_ptr, sizeof(connect_ex_ptr), + (void *)&guid, sizeof(guid), + (void *)&connect_ex_ptr, sizeof(connect_ex_ptr), &byte_len, NULL, NULL); if (success) { janet_vm.connect_ex = connect_ex_ptr; @@ -746,7 +746,7 @@ static const char *serverify_socket(JSock sfd, int reuse_addr, int reuse_port) { } } if (reuse_port) { -#ifdef SO_REUSEPORT +#if defined(SO_REUSEPORT) && !JANET_GNU_HURD if (setsockopt(sfd, SOL_SOCKET, SO_REUSEPORT, &enable, sizeof(int)) < 0) { return "setsockopt(SO_REUSEPORT) failed"; } diff --git a/src/core/os.c b/src/core/os.c index 45f87e74..1f1d919c 100644 --- a/src/core/os.c +++ b/src/core/os.c @@ -50,6 +50,11 @@ #include #endif +#ifdef JANET_GNU_HURD +/* Ideally we would try to dynamically allocate */ +#define PATH_MAX 8192 +#endif + #ifdef JANET_WINDOWS #include #include @@ -151,6 +156,7 @@ JANET_CORE_FN(os_which, "* :macos\n\n" "* :web - Web assembly (emscripten)\n\n" "* :linux\n\n" + "* :hurd\n\n" "* :freebsd\n\n" "* :openbsd\n\n" "* :netbsd\n\n" @@ -177,6 +183,8 @@ JANET_CORE_FN(os_which, return janet_ckeywordv("web"); #elif defined(JANET_LINUX) return janet_ckeywordv("linux"); +#elif defined(JANET_GNU_HURD) + return janet_ckeywordv("hurd"); #elif defined(__FreeBSD__) return janet_ckeywordv("freebsd"); #elif defined(__NetBSD__) @@ -2250,7 +2258,7 @@ JANET_CORE_FN(os_readlink, (void) argv; janet_panic("not supported on Windows"); #else - static char buffer[PATH_MAX]; + char buffer[PATH_MAX]; const char *path = janet_getcstring(argv, 0); ssize_t len = readlink(path, buffer, sizeof buffer); if (len < 0 || (size_t)len >= sizeof buffer) diff --git a/src/include/janet.h b/src/include/janet.h index 582e9c81..007659f7 100644 --- a/src/include/janet.h +++ b/src/include/janet.h @@ -67,6 +67,11 @@ extern "C" { #define JANET_LINUX 1 #endif +/* Check for GNU Hurd */ +#ifdef __gnu_hurd__ +#define JANET_GNU_HURD 1 +#endif + /* Check for Android */ #ifdef __ANDROID__ #define JANET_ANDROID 1 From e5e13489c2b27deaba1ee23f2525c871f418be4f Mon Sep 17 00:00:00 2001 From: sogaiu <33044872+sogaiu@users.noreply.github.com> Date: Sat, 15 Aug 2026 13:38:43 +0000 Subject: [PATCH 260/307] Add tests for distinct (#1801) Co-authored-by: sogaiu <983021772@users.noreply.github.com> --- test/suite-boot.janet | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/test/suite-boot.janet b/test/suite-boot.janet index 07543a20..d2629b6a 100644 --- a/test/suite-boot.janet +++ b/test/suite-boot.janet @@ -1040,4 +1040,10 @@ (assert (deep= (frequencies (coro (yield 1) (yield 8) (yield 2) (yield 8))) @{1 1 2 1 8 2})) +# distinct +(assert (deep= (distinct :hello) @[104 101 108 111])) +(assert (deep= (distinct [1 0 1 0 1 1 0 1 1 1]) @[1 0])) +(assert (deep= (distinct @{0 :a 1 :b 2 :b 3 :a}) @[:a :b])) +(assert (deep= (distinct (coro (yield 8) (yield 11) (yield 8))) @[8 11])) + (end-suite) From 27000deed5f7899c8f659c2da6ed32c0b00ff618 Mon Sep 17 00:00:00 2001 From: Calvin Rose Date: Sat, 15 Aug 2026 11:31:42 -0500 Subject: [PATCH 261/307] Address #1790 - limit number of automatic parameters. This also improves the error behavior of malformed automatic parameters so that the error conditions are better defined. Previously, large indices like 88888888888888 would actually simply be converted to symbols and fail later. This change puts strict limitations on this conversion process - If a symbol is of the form "$" and (scan-number str) returns non-nil, the result must be a valid index. In this way, symbols like `$1.1` will result in an immediate macro expansion error rather than (usually) and unknown binding error. --- src/boot/boot.janet | 5 ++++- test/suite-corelib.janet | 9 +++++++++ 2 files changed, 13 insertions(+), 1 deletion(-) diff --git a/src/boot/boot.janet b/src/boot/boot.janet index ce117b4f..b57cc520 100644 --- a/src/boot/boot.janet +++ b/src/boot/boot.janet @@ -2436,6 +2436,7 @@ ### ### +(def- max-shortfn-params 9999) # Less than 0xFFFF so we have enough registers for internal use (defmacro short-fn ``` Shorthand for `fn`. Arguments are given as `$n`, where `n` is the @@ -2466,8 +2467,10 @@ :else (do (def num (scan-number (string/slice x 1))) - (if (nat? num) + (if num (do + (assertf (nat? num) "expected indexed parameter, got %s" x) + (assertf (<= num max-shortfn-params) "too many short-fn parameters (%s)" x) (saw-special-arg num) (symbol prefix x)) x))) diff --git a/test/suite-corelib.janet b/test/suite-corelib.janet index a8120573..357a721e 100644 --- a/test/suite-corelib.janet +++ b/test/suite-corelib.janet @@ -209,4 +209,13 @@ (assert-error "no infinite step range" (range 3 3 math/inf)) +# Issue #1790 +(assert-no-error "limit short-fn parameters 1" (macex1 '|$9999)) +(assert-error "limit short-fn parameters 2" (macex1 '|$10000)) +(assert-error "limit short-fn parameters 3" (macex1 '|$10001)) +(assert-error "limit short-fn parameters 4" (macex1 '|$100010)) +(assert-error "limit short-fn parameters 5" (macex1 '|$800010)) +(assert-error "limit short-fn parameters 6" (macex1 '|$8888888888888888888888888888888888888888888888888888888888888888888888888888888)) +(assert-error "limit short-fn parameters 7" (macex1 '|$8.8)) + (end-suite) From e3c6893080bd09241d473ef730aaf4fe309d1254 Mon Sep 17 00:00:00 2001 From: Calvin Rose Date: Sat, 15 Aug 2026 11:36:34 -0500 Subject: [PATCH 262/307] whitespace --- src/boot/boot.janet | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/boot/boot.janet b/src/boot/boot.janet index b57cc520..a4ba43e2 100644 --- a/src/boot/boot.janet +++ b/src/boot/boot.janet @@ -2467,7 +2467,7 @@ :else (do (def num (scan-number (string/slice x 1))) - (if num + (if num (do (assertf (nat? num) "expected indexed parameter, got %s" x) (assertf (<= num max-shortfn-params) "too many short-fn parameters (%s)" x) From 23e4ad4bd3b4e9d9d36d38648acb95b763299690 Mon Sep 17 00:00:00 2001 From: Dmitry Solomennikov Date: Sat, 15 Aug 2026 19:55:09 +0300 Subject: [PATCH 263/307] Add int8_t, uint8_t and float getters for consistency (#1799) Signed-off-by: Dmitry Solomennikov --- src/core/capi.c | 23 +++++++++++++++++++++++ src/core/util.c | 22 ++++++++++++++++++++++ src/include/janet.h | 9 +++++++++ 3 files changed, 54 insertions(+) diff --git a/src/core/capi.c b/src/core/capi.c index 90b39547..9d620650 100644 --- a/src/core/capi.c +++ b/src/core/capi.c @@ -346,6 +346,29 @@ uint16_t janet_getuinteger16(const Janet *argv, int32_t n) { return (uint16_t) janet_unwrap_number(x); } +int8_t janet_getinteger8(const Janet *argv, int32_t n) { + Janet x = argv[n]; + if (!janet_checkint8(x)) { + janet_panicf("bad slot #%d, expected 8 bit signed integer, got %v", n, x); + } + return (int16_t) janet_unwrap_number(x); +} + +uint8_t janet_getuinteger8(const Janet *argv, int32_t n) { + Janet x = argv[n]; + if (!janet_checkuint8(x)) { + janet_panicf("bad slot #%d, expected 8 bit unsigned integer, got %v", n, x); + } + return (uint16_t) janet_unwrap_number(x); +} + +float janet_getfloat(const Janet *argv, int32_t n) { + Janet x = argv[n]; + if (!janet_checkfloat(x)) { + janet_panicf("bad slot #%d, expected float number, got %v", n, x); + } + return (float) janet_unwrap_number(x); +} int64_t janet_getinteger64(const Janet *argv, int32_t n) { #ifdef JANET_INT_TYPES diff --git a/src/core/util.c b/src/core/util.c index 43649822..7b9a1af0 100644 --- a/src/core/util.c +++ b/src/core/util.c @@ -50,6 +50,7 @@ #endif #include +#include /* Base 64 lookup table for digits */ const char janet_base64[65] = @@ -912,6 +913,27 @@ int janet_checkuint16(Janet x) { return janet_checkuint16range(dval); } +int janet_checkint8(Janet x) { + if (!janet_checktype(x, JANET_NUMBER)) + return 0; + double dval = janet_unwrap_number(x); + return janet_checkint8range(dval); +} + +int janet_checkuint8(Janet x) { + if (!janet_checktype(x, JANET_NUMBER)) + return 0; + double dval = janet_unwrap_number(x); + return janet_checkuint8range(dval); +} + +int janet_checkfloat(Janet x) { + if (!janet_checktype(x, JANET_NUMBER)) + return 0; + double dval = janet_unwrap_number(x); + return janet_checkfloatrange(dval); +} + int janet_checksize(Janet x) { if (!janet_checktype(x, JANET_NUMBER)) return 0; diff --git a/src/include/janet.h b/src/include/janet.h index 007659f7..9857bfac 100644 --- a/src/include/janet.h +++ b/src/include/janet.h @@ -945,6 +945,9 @@ JANET_API Janet janet_nanbox32_from_tagp(uint32_t tag, void *pointer); /* End of tagged union implementation */ #endif +JANET_API int janet_checkfloat(Janet x); +JANET_API int janet_checkint8(Janet x); +JANET_API int janet_checkuint8(Janet x); JANET_API int janet_checkint16(Janet x); JANET_API int janet_checkuint16(Janet x); JANET_API int janet_checkint(Janet x); @@ -953,6 +956,9 @@ JANET_API int janet_checkint64(Janet x); JANET_API int janet_checkuint64(Janet x); JANET_API int janet_checksize(Janet x); JANET_API JanetAbstract janet_checkabstract(Janet x, const JanetAbstractType *at); +#define janet_checkfloatrange(x) ((x) >= FLT_MIN && (x) <= FLT_MAX && (x) == (float)(x)) +#define janet_checkint8range(x) ((x) >= INT8_MIN && (x) <= INT8_MAX && (x) == (int8_t)(x)) +#define janet_checkuint8range(x) ((x) >= 0 && (x) <= UINT8_MAX && (x) == (uint8_t)(x)) #define janet_checkint16range(x) ((x) >= INT16_MIN && (x) <= INT16_MAX && (x) == (int16_t)(x)) #define janet_checkuint16range(x) ((x) >= 0 && (x) <= UINT16_MAX && (x) == (uint16_t)(x)) #define janet_checkintrange(x) ((x) >= INT32_MIN && (x) <= INT32_MAX && (x) == (int32_t)(x)) @@ -2214,9 +2220,12 @@ JANET_API void *janet_getpointer(const Janet *argv, int32_t n); JANET_API int32_t janet_getnat(const Janet *argv, int32_t n); JANET_API int32_t janet_getinteger(const Janet *argv, int32_t n); +JANET_API float janet_getfloat(const Janet *argv, int32_t n); +JANET_API int8_t janet_getinteger8(const Janet *argv, int32_t n); JANET_API int16_t janet_getinteger16(const Janet *argv, int32_t n); JANET_API int64_t janet_getinteger64(const Janet *argv, int32_t n); JANET_API uint32_t janet_getuinteger(const Janet *argv, int32_t n); +JANET_API uint8_t janet_getuinteger8(const Janet *argv, int32_t n); JANET_API uint16_t janet_getuinteger16(const Janet *argv, int32_t n); JANET_API uint64_t janet_getuinteger64(const Janet *argv, int32_t n); JANET_API size_t janet_getsize(const Janet *argv, int32_t n); From ffb6648742e71d5e44f745b1b112c47b4f0ec7cb Mon Sep 17 00:00:00 2001 From: sogaiu <33044872+sogaiu@users.noreply.github.com> Date: Sat, 15 Aug 2026 17:04:18 +0000 Subject: [PATCH 264/307] Add tests for update (#1796) Co-authored-by: sogaiu <983021772@users.noreply.github.com> Co-authored-by: Calvin Rose --- test/suite-boot.janet | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/test/suite-boot.janet b/test/suite-boot.janet index d2629b6a..b305e091 100644 --- a/test/suite-boot.janet +++ b/test/suite-boot.janet @@ -1040,6 +1040,13 @@ (assert (deep= (frequencies (coro (yield 1) (yield 8) (yield 2) (yield 8))) @{1 1 2 1 8 2})) +# update +(assert (deep= (update @"hello" 0 (fn [old delta] (+ old delta)) 17) + @"yello")) +(assert (deep= (update @[:x :yy :z] 1 |(keyword (slice $ 0 1))) + @[:x :y :z])) +(assert (deep= (update @{:a 1 :b -1 :c 3} :b + 3) + @{:a 1 :b 2 :c 3})) # distinct (assert (deep= (distinct :hello) @[104 101 108 111])) (assert (deep= (distinct [1 0 1 0 1 1 0 1 1 1]) @[1 0])) From 0888a6dfb0eb7004b481cd6f69756f6ce9c75025 Mon Sep 17 00:00:00 2001 From: sogaiu <33044872+sogaiu@users.noreply.github.com> Date: Sat, 15 Aug 2026 17:05:01 +0000 Subject: [PATCH 265/307] Add tests for partition-by (#1795) Co-authored-by: sogaiu <983021772@users.noreply.github.com> Co-authored-by: Calvin Rose --- test/suite-boot.janet | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/test/suite-boot.janet b/test/suite-boot.janet index b305e091..d905b724 100644 --- a/test/suite-boot.janet +++ b/test/suite-boot.janet @@ -1040,6 +1040,11 @@ (assert (deep= (frequencies (coro (yield 1) (yield 8) (yield 2) (yield 8))) @{1 1 2 1 8 2})) +# partition-by +(assert (deep= (partition-by even? "hello") + @[@[104] @[101] @[108 108] @[111]])) +(assert (deep= (partition-by keyword? [:a "b" 'c :d]) + @[@[:a] @["b" 'c] @[:d]])) # update (assert (deep= (update @"hello" 0 (fn [old delta] (+ old delta)) 17) @"yello")) From 3f1b31a5289089abf9f43ed4f97d76d15585366a Mon Sep 17 00:00:00 2001 From: sogaiu <33044872+sogaiu@users.noreply.github.com> Date: Sat, 15 Aug 2026 17:05:39 +0000 Subject: [PATCH 266/307] Improve consistency of some docstrings (#1794) Co-authored-by: sogaiu <983021772@users.noreply.github.com> --- src/boot/boot.janet | 38 ++++++++++++++++++++++++-------------- 1 file changed, 24 insertions(+), 14 deletions(-) diff --git a/src/boot/boot.janet b/src/boot/boot.janet index a4ba43e2..33feb64b 100644 --- a/src/boot/boot.janet +++ b/src/boot/boot.janet @@ -1191,15 +1191,20 @@ ret) (defn find - ``Find the first value in an indexed collection that satisfies a predicate. Returns - `dflt` if not found.`` - [pred ind &opt dflt] + `` + Find the first value in `x` that satisfies `pred`. If not found, + returns `dflt` if given, or `nil` otherwise. + + `x` can be a bytes, indexed, dictionary, fiber, or abstract type + with suitable `get` and `next` methods. + `` + [pred x &opt dflt] (var k nil) (var ret dflt) (while true - (set k (next ind k)) + (set k (next x k)) (if (= k nil) (break)) - (def item (in ind k)) + (def item (in x k)) (when (pred item) (set ret item) (break))) @@ -1701,21 +1706,26 @@ (put ds key (func old ;args))) (defn merge-into - ``Merges multiple tables/structs into table `tab`. If a key appears in more than one - collection in `colls`, then later values replace any previous ones. Returns `tab`.`` - [tab & colls] - (loop [c :in colls + `` + Merges multiple dictionaries into table `tab`. If a key appears in + more than one dictionary in `dicts`, then later values replace any + previous ones. Returns `tab`. + `` + [tab & dicts] + (loop [c :in dicts key :keys c] (put tab key (in c key))) tab) (defn merge - ``Merges multiple tables/structs into one new table. If a key appears in more than one - collection in `colls`, then later values replace any previous ones. - Returns the new table.`` - [& colls] + `` + Merges multiple dictionaries into one new table. If a key appears in + more than one dictionary in `dicts` then later values replace any + previous ones. Returns the new table. + `` + [& dicts] (def container @{}) - (loop [c :in colls + (loop [c :in dicts key :keys c] (put container key (in c key))) container) From 1e91591c19adbad9f6aeb7ebcd7f9d60cb4f3cf5 Mon Sep 17 00:00:00 2001 From: sogaiu <33044872+sogaiu@users.noreply.github.com> Date: Sat, 15 Aug 2026 17:06:05 +0000 Subject: [PATCH 267/307] Improve completeness of some docstrings (#1791) Co-authored-by: sogaiu <983021772@users.noreply.github.com> --- src/boot/boot.janet | 22 ++++++++++++++++------ src/core/corelib.c | 44 ++++++++++++++++++++++++++++---------------- 2 files changed, 44 insertions(+), 22 deletions(-) diff --git a/src/boot/boot.janet b/src/boot/boot.janet index 33feb64b..93960de5 100644 --- a/src/boot/boot.janet +++ b/src/boot/boot.janet @@ -1349,14 +1349,24 @@ (tuple 'fn :juxt (tuple '& $args) (tuple/slice parts 0))) (defn has-key? - "Check if a data structure `ds` contains the key `key`." - [ds key] - (not= nil (get ds key))) + `` + Check if `x` maps `key` to a non-nil value. + + `x` can be a bytes, indexed, dictionary, fiber, or abstract type + with a suitable `get` method. + `` + [x key] + (not= nil (get x key))) (defn has-value? - "Check if a data structure `ds` contains the value `value`. Will run in time proportional to the size of `ds`." - [ds value] - (not= nil (index-of value ds))) + `` + Checks if `x` contains `value`. + + `x` can be a bytes, indexed, dictionary, fiber, or abstract type with + suitable `get` and `next` methods. + `` + [x value] + (not= nil (index-of value x))) (defdyn *defdyn-prefix* ``Optional namespace prefix to add to keywords declared with `defdyn`. Use this to prevent keyword collisions between dynamic bindings.``) diff --git a/src/core/corelib.c b/src/core/corelib.c index bf8401bb..c8228435 100644 --- a/src/core/corelib.c +++ b/src/core/corelib.c @@ -1175,12 +1175,16 @@ JanetTable *janet_core_env(JanetTable *replacements) { "than x, and 0 otherwise. To return 0, x and y must be the exact same type.")); janet_quick_asm(env, JANET_FUN_NEXT, "next", 2, 1, 2, 2, next_asm, sizeof(next_asm), - JDOC("(next ds &opt key)\n\n" - "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 " - "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 " - "returns nil, there are no more keys to iterate through.")); + JDOC("(next x &opt key)\n\n" + "Gets the next key in `x`. Can be used to iterate through " + "the keys of `x` in an unspecified order. Keys are guaranteed " + "to be seen only once per iteration if `x` is not mutated " + "during iteration. If `key` is `nil`, returns the first key. " + "If `nil` is returned, there are no more keys to iterate " + "through.\n" + "\n" + "`x` can be a bytes, indexed, dictionary, fiber, or abstract " + "type with a suitable `next` method.")); janet_quick_asm(env, JANET_FUN_PROP, "propagate", 2, 2, 2, 2, propagate_asm, sizeof(propagate_asm), JDOC("(propagate x fiber)\n\n" @@ -1221,18 +1225,26 @@ JanetTable *janet_core_env(JanetTable *replacements) { "the fiber's dispatch function, or the value from the next yield call in fiber.")); janet_quick_asm(env, JANET_FUN_IN, "in", 3, 2, 3, 4, in_asm, sizeof(in_asm), - JDOC("(in ds key &opt dflt)\n\n" - "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, " - "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.")); + JDOC("(in x key &opt dflt)\n\n" + "Get value in `x` at `key`. For bytes and indexed " + "types, `key` must be a non-negative interger in " + "bounds or an error is raised. For dictionaries " + "`key` must be a non-nil value and if not found, " + "will return `dflt` if provided or `nil` otherwise.\n" + "\n" + "`x` can be a bytes, indexed, dictionary, fiber, or " + "abstract type with a suitable `get` method.")); janet_quick_asm(env, JANET_FUN_GET, "get", 3, 2, 3, 4, get_asm, sizeof(in_asm), - 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. " - "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 " - "an error.")); + JDOC("(get x key &opt dflt)\n\n" + "Get the value mapped to `key` in `x`. Returns `dflt` " + "or `nil` if `key` is not found. Similar to `in`, but " + "will not throw an error if `key` is invalid for `x`. " + "However, if `x` is an abstract type, its getter may " + "throw an error.\n" + "\n" + "`x` can be a bytes, indexed, dictionary, fiber, or " + "abstract type with a suitable `get` method.")); janet_quick_asm(env, JANET_FUN_PUT, "put", 3, 3, 3, 3, put_asm, sizeof(put_asm), JDOC("(put ds key value)\n\n" From f6fd6bdc2989af2bfd46ee636af18be4d06aa414 Mon Sep 17 00:00:00 2001 From: sogaiu <33044872+sogaiu@users.noreply.github.com> Date: Sat, 15 Aug 2026 17:27:36 +0000 Subject: [PATCH 268/307] Improve completeness of sort* docstrings (#1798) Co-authored-by: sogaiu <983021772@users.noreply.github.com> --- src/boot/boot.janet | 43 +++++++++++++++++++++++++++++-------------- 1 file changed, 29 insertions(+), 14 deletions(-) diff --git a/src/boot/boot.janet b/src/boot/boot.janet index 93960de5..ee3a7b6e 100644 --- a/src/boot/boot.janet +++ b/src/boot/boot.janet @@ -980,29 +980,44 @@ a) (defn sort - ``Sorts `ind` in-place, and returns it. Uses quick-sort and is not a stable sort. - If a `before?` comparator function is provided, sorts elements using that, - otherwise uses `<`.`` - [ind &opt before?] + `` + Sorts `x` in-place, and returns it. Uses quick-sort and is not a + stable sort. If a `before?` comparator function is provided, sorts + elements using that, otherwise uses `<`. + + `x` can be a buffer, array, or abstract type with suitable `get`, + `put`, and `length` methods. + `` + [x &opt before?] (default before? <) - (sort-help ind 0 (- (length ind) 1) before?)) + (sort-help x 0 (- (length x) 1) before?)) (defn sort-by - ``Sorts `ind` in-place by calling a function `f` on each element and - comparing the result with `<`.`` - [f ind] - (sort ind (fn :sort-by-comp [x y] (< (f x) (f y))))) + `` + Sorts `x` in-place by calling a function `f` on each element and + comparing the results with `<`. + + `x` can be a buffer, array, or abstract type with suitable `get`, + `put`, and `length` methods. + `` + [f x] + (sort x (fn :sort-by-comp [i j] (< (f i) (f j))))) (defn sorted - ``Returns a new sorted array without modifying the old one. - If a `before?` comparator function is provided, sorts elements using that, - otherwise uses `<`.`` + `` + Returns a new sorted array based on an indexed type `ind`. If a + `before?` comparator function is provided, sorts elements using + that, otherwise uses `<`. + `` [ind &opt before?] (sort (array/slice ind) before?)) (defn sorted-by - ``Returns a new sorted array that compares elements by invoking - a function `f` on each element and comparing the result with `<`.`` + `` + Returns a new sorted array based on an indexed type `ind`. + Comparison is done by invoking a function `f` on each element and + comparing the results with `<`. + `` [f ind] (sorted ind (fn :sorted-by-comp [x y] (< (f x) (f y))))) From 291e2222bbdf4d107b1d71e5a7af28f127a9340d Mon Sep 17 00:00:00 2001 From: sogaiu <33044872+sogaiu@users.noreply.github.com> Date: Sat, 15 Aug 2026 19:38:33 +0000 Subject: [PATCH 269/307] Improve clarity of some docstrings (#1800) Co-authored-by: sogaiu <983021772@users.noreply.github.com> --- src/boot/boot.janet | 40 ++++++++++++++++++++++++++++++---------- src/core/corelib.c | 19 ++++++++++++------- 2 files changed, 42 insertions(+), 17 deletions(-) diff --git a/src/boot/boot.janet b/src/boot/boot.janet index ee3a7b6e..bf70e423 100644 --- a/src/boot/boot.janet +++ b/src/boot/boot.janet @@ -1646,14 +1646,18 @@ (reverse! (seq [v :in t] v)))) (defn invert - ``Given an associative data structure `ds`, returns a new table where the - keys of `ds` are the values, and the values are the keys. If multiple keys - in `ds` are mapped to the same value, only one of those values will - become a key in the returned table.`` - [ds] + `` + Returns a table where the keys of `x` are the values, and the values + are the keys. If multiple keys in `x` are mapped to the same value, + only one of those values will become a key in the returned table. + + `x` can be a bytes, indexed, dictionary, or abstract type with a + `suitable next` method. + `` + [x] (def ret @{}) - (loop [k :keys ds] - (put ret (in ds k) k)) + (loop [k :keys x] + (put ret (in x k) k)) ret) (defn zipcoll @@ -1756,7 +1760,12 @@ container) (defn keys - "Get the keys of an associative data structure." + `` + Get the keys of `x` as an array. + + `x` can be a bytes, indexed, dictionary, or abstract type with a + suitable `next` method. + `` [x] (if (lengthable? x) (do @@ -1769,7 +1778,12 @@ (seq [k :keys x] k))) (defn values - "Get the values of an associative data structure." + `` + Get the values of `x` as an array. + + `x` can be a bytes, indexed, dictionary, fiber, or abstract type + with suitable `get` and `next` methods. + `` [x] (if (lengthable? x) (do @@ -1782,7 +1796,13 @@ (seq [v :in x] v))) (defn pairs - "Get the key-value pairs of an associative data structure." + `` + Return an array of tuples of the key-value pairs of `x`. For bytes + and indexed types, the integer indices are considered the keys. + + `x` can be a bytes, indexed, dictionary, or abstract type with + suitable `get` and `in` methods. + `` [x] (if (lengthable? x) (do diff --git a/src/core/corelib.c b/src/core/corelib.c index c8228435..bbfc34ad 100644 --- a/src/core/corelib.c +++ b/src/core/corelib.c @@ -1247,13 +1247,18 @@ JanetTable *janet_core_env(JanetTable *replacements) { "abstract type with a suitable `get` method.")); janet_quick_asm(env, JANET_FUN_PUT, "put", 3, 3, 3, 3, put_asm, sizeof(put_asm), - JDOC("(put ds key value)\n\n" - "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 " - "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 " - "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.")); + JDOC("(put x key val)\n\n" + "Associate `key` with `val` for mutable `x`. Arrays " + "and buffers only accept non-negative integer keys, " + "and will expand if an out of bounds value is " + "provided. For an array, extra space will be filled " + "with `nil`s, while for buffers, 0 bytes are used " + "instead. For a table, putting a key that is in the " + "table prototype will hide the association defined by " + "the prototype, but will not mutate the prototype " + "table. Putting a `nil` value into a table will " + "remove the table's corresponding association. " + "Returns `x`.")); janet_quick_asm(env, JANET_FUN_LENGTH, "length", 1, 1, 1, 1, length_asm, sizeof(length_asm), JDOC("(length ds)\n\n" From 66be6fbf2b1288f3555fb7b3721c7aca279dc60f Mon Sep 17 00:00:00 2001 From: sogaiu <33044872+sogaiu@users.noreply.github.com> Date: Sun, 16 Aug 2026 14:22:03 +0000 Subject: [PATCH 270/307] Tweak type docstring (#1802) Co-authored-by: sogaiu <983021772@users.noreply.github.com> --- src/core/corelib.c | 29 +++++++++++++++-------------- 1 file changed, 15 insertions(+), 14 deletions(-) diff --git a/src/core/corelib.c b/src/core/corelib.c index bbfc34ad..84aa79a0 100644 --- a/src/core/corelib.c +++ b/src/core/corelib.c @@ -575,20 +575,21 @@ JANET_CORE_FN(janet_core_gcinterval, JANET_CORE_FN(janet_core_type, "(type x)", "Returns the type of `x` as a keyword. `x` is one of:\n\n" - "* :nil\n\n" - "* :boolean\n\n" - "* :number\n\n" - "* :array\n\n" - "* :tuple\n\n" - "* :table\n\n" - "* :struct\n\n" - "* :string\n\n" - "* :buffer\n\n" - "* :symbol\n\n" - "* :keyword\n\n" - "* :function\n\n" - "* :cfunction\n\n" - "* :fiber\n\n" + "* :number\n" + "* :nil\n" + "* :boolean\n" + "* :fiber\n" + "* :string\n" + "* :symbol\n" + "* :keyword\n" + "* :array\n" + "* :tuple\n" + "* :table\n" + "* :struct\n" + "* :buffer\n" + "* :function\n" + "* :cfunction\n" + "* :pointer\n\n" "or another keyword for an abstract type.") { janet_fixarity(argc, 1); JanetType t = janet_type(argv[0]); From 4aa21c05911f8ee5868974ae96b49fd718eb5d0c Mon Sep 17 00:00:00 2001 From: sogaiu <33044872+sogaiu@users.noreply.github.com> Date: Sun, 16 Aug 2026 14:22:16 +0000 Subject: [PATCH 271/307] Tweak flatten* docstrings (#1804) Co-authored-by: sogaiu <983021772@users.noreply.github.com> --- src/boot/boot.janet | 26 +++++++++++++++----------- 1 file changed, 15 insertions(+), 11 deletions(-) diff --git a/src/boot/boot.janet b/src/boot/boot.janet index bf70e423..451e5fe9 100644 --- a/src/boot/boot.janet +++ b/src/boot/boot.janet @@ -1867,20 +1867,24 @@ ret) (defn flatten-into - ``Takes a nested array (tree) `xs` and appends the depth first traversal of - `xs` to array `into`. Returns `into`.`` - [into xs] - (each x xs - (if (indexed? x) - (flatten-into into x) - (array/push into x))) + `` + Appends the depth-first traversal of an indexed type `ind` into a + given array `into`. Returns `into`. + `` + [into ind] + (each elt ind + (if (indexed? elt) + (flatten-into into elt) + (array/push into elt))) into) (defn flatten - ``Takes a nested array (tree) `xs` and returns the depth first traversal of - it. Returns a new array.`` - [xs] - (flatten-into @[] xs)) + `` + Returns a depth-first traversal of an indexed type `ind` as a new + array. + `` + [ind] + (flatten-into @[] ind)) (defn kvs ``Takes a table or struct and returns a new array of key value pairs From 5b438d94401257dd28d60ff408681e6530186487 Mon Sep 17 00:00:00 2001 From: sogaiu <33044872+sogaiu@users.noreply.github.com> Date: Sun, 16 Aug 2026 19:12:18 +0000 Subject: [PATCH 272/307] Tweak frequencies and group-by (#1806) Co-authored-by: sogaiu <983021772@users.noreply.github.com> --- src/boot/boot.janet | 37 ++++++++++++++++++++++++------------- 1 file changed, 24 insertions(+), 13 deletions(-) diff --git a/src/boot/boot.janet b/src/boot/boot.janet index 451e5fe9..79f8524a 100644 --- a/src/boot/boot.janet +++ b/src/boot/boot.janet @@ -1815,25 +1815,36 @@ (seq [p :pairs x] p))) (defn frequencies - "Get the number of occurrences of each value in an indexed data structure." - [ind] + `` + Returns a table of occurrences of each value in `x`. + + `x` can be a bytes, indexed, dictionary, fiber, or abstract type + with `get` and `next` methods. + `` + [x] (def freqs @{}) - (each x ind - (put freqs x (inc (in freqs x 0)))) + (each v x + (put freqs v (inc (in freqs v 0)))) freqs) (defn group-by - ``Group elements of `ind` by a function `f` and put the results into a new table. The keys of - the table are the distinct return values from calling `f` on the elements of `ind`. The values - of the table are arrays of all elements of `ind` for which `f` called on the element equals - that corresponding key.`` - [f ind] + `` + Group values of `x` by a function `f` and put the results into a new + table. The keys of the table are the distinct return values from + calling `f` on the values of `x`. The values of the table are arrays + of all values of `x` for which `f` called on the value equals that + corresponding key. + + `x` can be a bytes, indexed, dictionary, fiber, or abstract type + with suitable `get` and `next` methods. + `` + [f x] (def ret @{}) - (each x ind - (def y (f x)) + (each v x + (def y (f v)) (if-let [arr (get ret y)] - (array/push arr x) - (put ret y @[x]))) + (array/push arr v) + (put ret y @[v]))) ret) (defn partition-by From 5229244a1b659be872bb6a5ff37f90eabb19a114 Mon Sep 17 00:00:00 2001 From: sogaiu <33044872+sogaiu@users.noreply.github.com> Date: Sun, 16 Aug 2026 19:12:26 +0000 Subject: [PATCH 273/307] Improve completeness of *-in docstrings (#1805) Co-authored-by: sogaiu <983021772@users.noreply.github.com> --- src/boot/boot.janet | 63 +++++++++++++++++++++++++++++++-------------- 1 file changed, 44 insertions(+), 19 deletions(-) diff --git a/src/boot/boot.janet b/src/boot/boot.janet index 79f8524a..67603b86 100644 --- a/src/boot/boot.janet +++ b/src/boot/boot.janet @@ -1675,21 +1675,38 @@ (put res (in ks kk) (in vs vk))) res) -(defn get-in - ``Access a value in a nested data structure. Looks into the data structure via - a sequence of keys. If value is not found, and `dflt` is provided, returns `dflt`.`` - [ds ks &opt dflt] - (var d ds) - (loop [k :in ks :while (not (nil? d))] (set d (get d k))) +(defn get-in + `` + Use the keys `ks` to look up a nested value in `x`. If a value is + not found, will return `dflt` if provided, or `nil` otherwise. + + `x` can be a suitable indexed, dictionary, or abstract type with a + `get` method. + + `ks` can be an indexed or abstract type with `get` and `next` + methods. + `` + [x ks &opt dflt] + (var d x) + (loop [k :in ks :while (not (nil? d))] (set d (get d k))) (if (= nil d) dflt d)) (defn update-in - ``Update a value in a nested data structure `ds`. Looks into `ds` via a sequence of keys, - and replaces the value found there with `f` applied to that value. - Missing data structures will be replaced with tables. Returns - the modified, original data structure.`` - [ds ks f & args] - (var d ds) + `` + Update a value in `x`. Looks into a nested spot in `x` identified by + the keys `ks` and replaces the value found there with `f` applied to + that value and any values passed and available in `args`. Missing + spots will be replaced with tables and `f` is passed `nil` instead + of a found value. Returns `x` modified. + + `x` can be a suitable array, table or abstract type with `get` and + `put` methods. + + `ks` can be an indexed or abstract type with suitable `get` and + `length` methods. + `` + [x ks f & args] + (var d x) (def len-1 (- (length ks) 1)) (if (< len-1 0) (error "expected at least 1 key in ks")) (forv i 0 len-1 @@ -1703,14 +1720,22 @@ (def last-key (get ks len-1)) (def last-val (get d last-key)) (put d last-key (f last-val ;args)) - ds) + x) (defn put-in - ``Put a value into a nested data structure `ds`. Looks into `ds` via - a sequence of keys. Missing data structures will be replaced with tables. Returns - the modified, original data structure.`` - [ds ks v] - (var d ds) + `` + Use the keys `ks` to put a value `v` into a nested spot in + `x`. Missing spots will be replaced with tables. Returns `x` + modified. + + `x` can be a suitable array, table or abstract type with `get` and + `put` methods. + + `ks` can be an indexed or abstract type with suitable `get` and + `length` methods. + `` + [x ks v] + (var d x) (def len-1 (- (length ks) 1)) (if (< len-1 0) (error "expected at least 1 key in ks")) (forv i 0 len-1 @@ -1724,7 +1749,7 @@ (def last-key (get ks len-1)) (def last-val (get d last-key)) (put d last-key v) - ds) + x) (defn update ``For a given key in data structure `ds`, replace its corresponding value with the From 82b2bdc8e2d478328d2e4560b485db7050093a91 Mon Sep 17 00:00:00 2001 From: sogaiu <33044872+sogaiu@users.noreply.github.com> Date: Mon, 17 Aug 2026 01:49:01 +0000 Subject: [PATCH 274/307] Add tests for find-index (#1807) Co-authored-by: sogaiu <983021772@users.noreply.github.com> --- test/suite-boot.janet | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/test/suite-boot.janet b/test/suite-boot.janet index d905b724..7f37f2d5 100644 --- a/test/suite-boot.janet +++ b/test/suite-boot.janet @@ -1058,4 +1058,10 @@ (assert (deep= (distinct @{0 :a 1 :b 2 :b 3 :a}) @[:a :b])) (assert (deep= (distinct (coro (yield 8) (yield 11) (yield 8))) @[8 11])) +# find-index +(assert (= (find-index |(= (chr "c") $) "abc") 2)) +(assert (= (find-index |(= $ :goose) [:duck :duck :goose]) 2)) +(assert (= (find-index pos? @[-2 -1 0 -3] :surprise!) :surprise!)) +(assert (= (find-index zero? {:a 2 :b 1 :c 0}) :c)) + (end-suite) From c6ca76044da6fdbb07f9978307224ef1d3551305 Mon Sep 17 00:00:00 2001 From: sogaiu <33044872+sogaiu@users.noreply.github.com> Date: Mon, 17 Aug 2026 12:55:40 +0000 Subject: [PATCH 275/307] Add tests for find (#1808) Co-authored-by: sogaiu <983021772@users.noreply.github.com> --- test/suite-boot.janet | 10 ++++++++++ 1 file changed, 10 insertions(+) diff --git a/test/suite-boot.janet b/test/suite-boot.janet index 7f37f2d5..37d01ccf 100644 --- a/test/suite-boot.janet +++ b/test/suite-boot.janet @@ -1064,4 +1064,14 @@ (assert (= (find-index pos? @[-2 -1 0 -3] :surprise!) :surprise!)) (assert (= (find-index zero? {:a 2 :b 1 :c 0}) :c)) +# find +(assert (= (find |(= $ (chr "o")) "tomato") 111)) +(assert (= (find |(= $ (chr "m")) "potato") nil)) +(assert (= (find |(> $ (chr "y")) :ant :surprise) :surprise)) +(assert (= (find pos? [-1 0 11]) 11)) +(assert (= (find neg? @[0 1 2 3 5]) nil)) +(assert (= (find one? {:a 1 :b 2}) 1)) +(assert (= (find even? @{:x 11 :y 28 :z 33}) 28)) +(assert (= (find keyword? (coro (yield 'jump) (yield :wave))) :wave)) + (end-suite) From 3d03d083c77729b9a36e3cba9edcdc47fba824cd Mon Sep 17 00:00:00 2001 From: sogaiu <33044872+sogaiu@users.noreply.github.com> Date: Mon, 17 Aug 2026 12:56:24 +0000 Subject: [PATCH 276/307] Improve index-of docstring (#1809) Co-authored-by: sogaiu <983021772@users.noreply.github.com> --- src/boot/boot.janet | 19 ++++++++++++------- 1 file changed, 12 insertions(+), 7 deletions(-) diff --git a/src/boot/boot.janet b/src/boot/boot.janet index 67603b86..f8b9be1a 100644 --- a/src/boot/boot.janet +++ b/src/boot/boot.janet @@ -1226,15 +1226,20 @@ ret) (defn index-of - ``Find the first key associated with a value x in a data structure, acting like a reverse lookup. - Will not look at table prototypes. - Returns `dflt` if not found.`` - [x ind &opt dflt] - (var k (next ind nil)) + `` + Find the first key associated with a value `val` in `x`, acting like + a reverse lookup. Will not look at dictionary prototypes. If not + found, returns `dflt` if provided and `nil` otherwise. + + `x` can be a bytes, indexed, dictionary, fiber, or abstract type + with suitable `get` and `next` methods. + `` + [val x &opt dflt] + (var k (next x nil)) (var ret dflt) (while (not= nil k) - (when (= (in ind k) x) (set ret k) (break)) - (set k (next ind k))) + (when (= (in x k) val) (set ret k) (break)) + (set k (next x k))) ret) (defn- take-n-slice From 073a036b167f373a89e99397c5279dbb7c3699c6 Mon Sep 17 00:00:00 2001 From: Calvin Rose Date: Mon, 17 Aug 2026 07:57:53 -0500 Subject: [PATCH 277/307] Update CHANGELOG.md --- CHANGELOG.md | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 977ca238..ace09e28 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -2,11 +2,12 @@ All notable changes to this project will be documented in this file. ## Unreleased - ??? +- Numerous documentation improvements and fixes +- Add support for GNU/Hurd - Improve pretty printing layout for %M and %m modifiers to be more code-like. - Add filewatch support to BSD and macos. - Add linting support for shadowed bindings. - Add nanboxing support for Linux on ARM64 and turn on nanboxing by default on macos on ARM64 (aarch64). -- Documentation fixes - ev/thread-chan deadlock bug fixed - Re-add removed support for non-blocking net/connect on windows with bug fixes. From 65745d0237686570fbd270101c2b5389377a605d Mon Sep 17 00:00:00 2001 From: Calvin Rose Date: Mon, 17 Aug 2026 11:05:18 -0500 Subject: [PATCH 278/307] Fix some fuzzing bugs and add asserts. Passing in the wrong number of keyword arguments could be problematic and cause compialtion issues. Handle that more gracefully and at least be clean in valgrind. --- src/core/debug.c | 6 ++++-- src/core/fiber.c | 6 ++++-- src/core/vm.c | 3 ++- test/suite-compile.janet | 23 +++++++++++++++++++++++ 4 files changed, 33 insertions(+), 5 deletions(-) diff --git a/src/core/debug.c b/src/core/debug.c index 7b1da7e3..6c39dfe8 100644 --- a/src/core/debug.c +++ b/src/core/debug.c @@ -422,8 +422,10 @@ JANET_CORE_FN(cfun_debug_argstack, janet_fixarity(argc, 1); JanetFiber *fiber = janet_getfiber(argv, 0); JanetArray *array = janet_array(fiber->stacktop - fiber->stackstart); - memcpy(array->data, fiber->data + fiber->stackstart, array->capacity * sizeof(Janet)); - array->count = array->capacity; + if (array->capacity) { + memcpy(array->data, fiber->data + fiber->stackstart, array->capacity * sizeof(Janet)); + array->count = array->capacity; + } return janet_wrap_array(array); } diff --git a/src/core/fiber.c b/src/core/fiber.c index 61c15f4b..f70a2683 100644 --- a/src/core/fiber.c +++ b/src/core/fiber.c @@ -106,7 +106,7 @@ static void janet_fiber_refresh_memory(JanetFiber *fiber) { if (NULL == newData) { JANET_OUT_OF_MEMORY; } - memcpy(newData, fiber->data, fiber->capacity * sizeof(Janet)); + memcpy(newData, fiber->data, n * sizeof(Janet)); janet_free(fiber->data); fiber->data = newData; } @@ -180,7 +180,8 @@ void janet_fiber_pushn(JanetFiber *fiber, const Janet *arr, int32_t n) { /* Create a struct with n values. If n is odd, the last value is ignored. */ static Janet make_struct_n(const Janet *args, int32_t n) { int32_t i = 0; - JanetKV *st = janet_struct_begin(n & (~1)); + if (n & 1) n--; + JanetKV *st = janet_struct_begin(n); for (; i < n; i += 2) { janet_struct_put(st, args[i], args[i + 1]); } @@ -228,6 +229,7 @@ int janet_fiber_funcframe(JanetFiber *fiber, JanetFunction *func) { /* Check varargs */ if (func->def->flags & JANET_FUNCDEF_FLAG_VARARG) { int32_t tuplehead = fiber->frame + func->def->arity; + janet_assert(tuplehead > 0, "fiber stack overflow"); int st = func->def->flags & JANET_FUNCDEF_FLAG_STRUCTARG; if (tuplehead >= oldtop) { fiber->data[tuplehead] = st diff --git a/src/core/vm.c b/src/core/vm.c index 2b47d4cc..ac329b69 100644 --- a/src/core/vm.c +++ b/src/core/vm.c @@ -621,7 +621,8 @@ static JanetSignal run_vm(JanetFiber *fiber, Janet in) { if (fiber->flags & JANET_FIBER_RESUME_SIGNAL) { JanetSignal sig = (fiber->gc.flags & JANET_FIBER_STATUS_MASK) >> JANET_FIBER_STATUS_OFFSET; fiber->gc.flags &= ~JANET_FIBER_STATUS_MASK; - fiber->flags &= ~(JANET_FIBER_RESUME_SIGNAL | JANET_FIBER_FLAG_MASK); + fiber->flags &= JANET_FIBER_RESUME_NO_SKIP | JANET_FIBER_RESUME_NO_USEVAL | /* Leave these flags untouched */ + ~(JANET_FIBER_RESUME_SIGNAL | JANET_FIBER_FLAG_MASK); janet_vm.return_reg[0] = in; return sig; } diff --git a/test/suite-compile.janet b/test/suite-compile.janet index bb815ad5..1f85e851 100644 --- a/test/suite-compile.janet +++ b/test/suite-compile.janet @@ -158,4 +158,27 @@ (check-lint-compile '(def [xxx [xxx yyy]] [1 [2 3]]) "shadow global-to-global one form") +# Fuzz issue - odd keys +(defn f [x &keys ks] [x ks]) +(check-lint-compile + '(f 1 + = = = = = = = = = = + = = = = = = = = = = + = = = = = = = = = = + = = = = = = = = = = + = = =) + "odd keys") + +# Test for any issues with valgrind/asan +(setdyn *lint-warn* :relaxed) +(assert-no-error + "odd keys runtime" + (f 1 + = = = = = = = = = = + = = = = = = = = = = + = = = = = = = = = = + = = = = = = = = = = + = = =)) +(setdyn *lint-warn* nil) + (end-suite) From b09aec83a214cc59e64c341bd020d1b1da161b82 Mon Sep 17 00:00:00 2001 From: Calvin Rose Date: Mon, 17 Aug 2026 15:43:38 -0500 Subject: [PATCH 279/307] Add t-switch to allow easily using libraries as CLI tools. Rather than needing to create wrapper scripts for utilities, `janet -t mylib ` can be a simple, if somewhat awkward, alternative for portable command line tooling. Also use spork/fmt on boot.janet. --- src/boot/boot.janet | 63 +++++++++++++++++++++++++++------------------ 1 file changed, 38 insertions(+), 25 deletions(-) diff --git a/src/boot/boot.janet b/src/boot/boot.janet index f8b9be1a..1665e16f 100644 --- a/src/boot/boot.janet +++ b/src/boot/boot.janet @@ -472,7 +472,7 @@ nil without evaluating the body. Otherwise, the same as `with`.`` [[binding ctor dtor] & body] ~(as-macro ,if-let [,binding ,ctor] - ,(defer-impl :when-with [(or dtor :close) binding] body))) + ,(defer-impl :when-with [(or dtor :close) binding] body))) (defmacro if-with ``Similar to `with`, but if binding is false or nil, evaluates @@ -480,8 +480,8 @@ `ctor` is bound to binding.`` [[binding ctor dtor] truthy &opt falsey] ~(as-macro ,if-let [,binding ,ctor] - ,(defer-impl :if-with [(or dtor :close) binding] [truthy]) - ,falsey)) + ,(defer-impl :if-with [(or dtor :close) binding] [truthy]) + ,falsey)) (defn- for-var-template [i start stop step comparison delta body] @@ -1680,7 +1680,7 @@ (put res (in ks kk) (in vs vk))) res) -(defn get-in +(defn get-in `` Use the keys `ks` to look up a nested value in `x`. If a value is not found, will return `dflt` if provided, or `nil` otherwise. @@ -1691,9 +1691,9 @@ `ks` can be an indexed or abstract type with `get` and `next` methods. `` - [x ks &opt dflt] - (var d x) - (loop [k :in ks :while (not (nil? d))] (set d (get d k))) + [x ks &opt dflt] + (var d x) + (loop [k :in ks :while (not (nil? d))] (set d (get d k))) (if (= nil d) dflt d)) (defn update-in @@ -2523,12 +2523,12 @@ (error (string "invalid metadata " m)))) (with-syms [entry old-entry f] ~(as-macro ,let [,old-entry (,dyn ',name)] - (def ,entry (as-macro ,or ,old-entry @{:ref @[nil]})) - (,setdyn ',name ,entry) - (def ,f ,fbody) - (,put-in ,entry [:ref 0] ,f) - (,merge-into ,entry ',metadata) - ,f))) + (def ,entry (as-macro ,or ,old-entry @{:ref @[nil]})) + (,setdyn ',name ,entry) + (def ,f ,fbody) + (,put-in ,entry [:ref 0] ,f) + (,merge-into ,entry ',metadata) + ,f))) ### ### @@ -4068,8 +4068,8 @@ [sec & body] (with-syms [f] ~(as-macro ,let [,f (as-macro ,coro ,;body)] - (,ev/deadline ,sec nil ,f) - (,resume ,f)))) + (,ev/deadline ,sec nil ,f) + (,resume ,f)))) (defn- cancel-all [chan fibers reason] (each f fibers (ev/cancel f reason)) @@ -4200,9 +4200,9 @@ (assertf (ffi/lookup (if lazy (llib) lib) raw-symbol) "failed to find ffi symbol %v" raw-symbol)) (if lazy ~(as-macro ,defn ,alias ,;meta [,;formal-args] - (,ffi/call (,(delay (make-ptr))) (,(delay (make-sig))) ,;formal-args)) + (,ffi/call (,(delay (make-ptr))) (,(delay (make-sig))) ,;formal-args)) ~(as-macro ,defn ,alias ,;meta [,;formal-args] - (,ffi/call ,(make-ptr) ,(make-sig) ,;formal-args)))) + (,ffi/call ,(make-ptr) ,(make-sig) ,;formal-args)))) (defmacro ffi/defbind :flycheck "Generate bindings for native functions in a convenient manner." @@ -4783,7 +4783,7 @@ (if (= x "") nil x)) # empty string is coerced to nil (defn- run-main - [env subargs arg] + [env subargs] (when-let [main (module/value env 'main true)] (def guard (if (get env :debug) :ydt :y)) (defn wrap-main [&] @@ -4793,7 +4793,9 @@ (while (fiber/can-resume? f) (set res (resume f res)) (when (not= :dead (fiber/status f)) - ((debugger-on-status env) f res))))) + ((debugger-on-status env) f res))) + (break true)) + false) (defdyn *args* "Dynamic bindings that will contain command line arguments at program start.") @@ -4827,6 +4829,7 @@ "-nocolor" "n" "-color" "N" "-library" "l" + "-tool" "t" "-install" "b" "-reinstall" "B" "-uninstall" "u" @@ -4905,6 +4908,7 @@ --nocolor (-n) : Disable ANSI color output in the REPL --color (-N) : Enable ANSI color output in the REPL --library (-l) lib : Use a module before processing more arguments + --tool (-t) lib args... : Use a module before processing more arguments, and then call it's `main` function --lint-warn (-w) level : Set the lint warning level - default is "normal" --lint-error (-x) level : Set the lint error level - default is "none" --install (-b) dirpath : Install a bundle from a directory @@ -4927,7 +4931,7 @@ "n" (fn [&] (apply-color false) 1) "N" (fn [&] (apply-color true) 1) "m" (fn [i &] (setdyn *syspath* (in args (+ i 1))) 2) - "c" (fn c-switch [i &] + "c" (fn :c-switch [i &] (def path (in args (+ i 1))) (def e (dofile path)) (def output-path @@ -4940,15 +4944,24 @@ (set no-file false) 3) "-" (fn [&] (set handleopts false) 1) - "l" (fn l-switch [i &] + "l" (fn :l-switch [i &] (import* (in args (+ i 1)) :prefix "" :exit exit-on-error) 2) - "e" (fn e-switch [i &] + "t" (fn :t-switch [i &] + (set should-repl false) + (set no-file false) + (def lib (in args (+ i 1))) + (def tool-env (require lib :exit exit-on-error)) + (def subargs (tuple/slice args (+ i 2))) + (put tool-env *args* subargs) + (assertf (run-main tool-env subargs) "no main found for module %s" lib) + math/inf) + "e" (fn :e-switch [i &] (set no-file false) (eval-string (in args (+ i 1))) 2) - "E" (fn E-switch [i &] + "E" (fn :E-switch [i &] (set no-file false) (def subargs (array/slice args (+ i 2))) (def src ~(short-fn ,(parse (in args (+ i 1))) E-expression)) @@ -5004,7 +5017,7 @@ (do (def env (load-image (slurp arg))) (put env *args* subargs) - (run-main env subargs arg)) + (run-main env subargs)) (do (def env (make-env)) (put env *args* subargs) @@ -5012,7 +5025,7 @@ (flycheck arg :exit exit-on-error :env env) (do (dofile arg :exit exit-on-error :env env) - (run-main env subargs arg))))) + (run-main env subargs))))) (set i lenargs)))) (if (or should-repl no-file) From c4b2278602eb9cfa41b93d13c94023d483f472d2 Mon Sep 17 00:00:00 2001 From: Calvin Rose Date: Mon, 17 Aug 2026 20:21:58 -0500 Subject: [PATCH 280/307] Update CHANGELOG --- CHANGELOG.md | 1 + 1 file changed, 1 insertion(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index ace09e28..a227dda6 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -2,6 +2,7 @@ All notable changes to this project will be documented in this file. ## Unreleased - ??? +- Add `-t` (`--tool`) flag for using installed libraries as command line tools - Numerous documentation improvements and fixes - Add support for GNU/Hurd - Improve pretty printing layout for %M and %m modifiers to be more code-like. From 04b48de8b4a39af85f517fac70ac5b019fb16def Mon Sep 17 00:00:00 2001 From: sogaiu <33044872+sogaiu@users.noreply.github.com> Date: Tue, 18 Aug 2026 01:24:24 +0000 Subject: [PATCH 281/307] Align find* and index-of details (#1811) Co-authored-by: sogaiu <983021772@users.noreply.github.com> --- src/boot/boot.janet | 37 ++++++++++++++++++------------------- 1 file changed, 18 insertions(+), 19 deletions(-) diff --git a/src/boot/boot.janet b/src/boot/boot.janet index 1665e16f..1327838c 100644 --- a/src/boot/boot.janet +++ b/src/boot/boot.janet @@ -1192,44 +1192,43 @@ res) (defn find-index - ``Find the index of indexed type for which `pred` is true. Returns `dflt` if not found.`` - [pred ind &opt dflt] - (var k nil) + `` + Find the index of `x` for which `pred` is true. If not found, return + `dflt` if provided, or `nil` otherwise. + + `x` can be a bytes, indexed, dictionary, or abstract type with + suitable `get` and `next` methods. + `` + [pred x &opt dflt] + (var k (next x nil)) (var ret dflt) - (while true - (set k (next ind k)) - (if (= k nil) (break)) - (def item (in ind k)) - (when (pred item) - (set ret k) - (break))) + (while (not= nil k) + (when (pred (in x k)) (set ret k) (break)) + (set k (next x k))) ret) (defn find `` Find the first value in `x` that satisfies `pred`. If not found, - returns `dflt` if given, or `nil` otherwise. + return `dflt` if given, or `nil` otherwise. `x` can be a bytes, indexed, dictionary, fiber, or abstract type with suitable `get` and `next` methods. `` [pred x &opt dflt] - (var k nil) + (var k (next x nil)) (var ret dflt) - (while true - (set k (next x k)) - (if (= k nil) (break)) + (while (not= nil k) (def item (in x k)) - (when (pred item) - (set ret item) - (break))) + (when (pred item) (set ret item) (break)) + (set k (next x k))) ret) (defn index-of `` Find the first key associated with a value `val` in `x`, acting like a reverse lookup. Will not look at dictionary prototypes. If not - found, returns `dflt` if provided and `nil` otherwise. + found, return `dflt` if given, or `nil` otherwise. `x` can be a bytes, indexed, dictionary, fiber, or abstract type with suitable `get` and `next` methods. From eb1a7e2b2056d611c66a7e554c8ebcd13bac647c Mon Sep 17 00:00:00 2001 From: sogaiu <33044872+sogaiu@users.noreply.github.com> Date: Tue, 18 Aug 2026 13:14:47 +0000 Subject: [PATCH 282/307] Add tests for interpose (#1813) Co-authored-by: sogaiu <983021772@users.noreply.github.com> --- test/suite-boot.janet | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/test/suite-boot.janet b/test/suite-boot.janet index 37d01ccf..b39018d7 100644 --- a/test/suite-boot.janet +++ b/test/suite-boot.janet @@ -1074,4 +1074,11 @@ (assert (= (find even? @{:x 11 :y 28 :z 33}) 28)) (assert (= (find keyword? (coro (yield 'jump) (yield :wave))) :wave)) +# interpose +(assert (deep= (interpose ":" "hello") + @[104 ":" 101 ":" 108 ":" 108 ":" 111])) +(assert (deep= (interpose 0 [1 2 3]) @[1 0 2 0 3])) +(assert (deep= (interpose :goose (coro (yield :duck) (yield :duck))) + @[:duck :goose :duck])) + (end-suite) From f590dac4f93eaabc6e26528ee775310f86bf7079 Mon Sep 17 00:00:00 2001 From: sogaiu <33044872+sogaiu@users.noreply.github.com> Date: Tue, 18 Aug 2026 13:15:46 +0000 Subject: [PATCH 283/307] Add tests for filter (#1812) Co-authored-by: sogaiu <983021772@users.noreply.github.com> Co-authored-by: Calvin Rose --- test/suite-boot.janet | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/test/suite-boot.janet b/test/suite-boot.janet index b39018d7..245c75c4 100644 --- a/test/suite-boot.janet +++ b/test/suite-boot.janet @@ -1074,6 +1074,12 @@ (assert (= (find even? @{:x 11 :y 28 :z 33}) 28)) (assert (= (find keyword? (coro (yield 'jump) (yield :wave))) :wave)) +# filter +(assert (deep= (filter |(< $ (chr "m")) "azbycw") @[97 98 99])) +(assert (deep= (filter pos? @[-1 0 1]) @[1])) +(assert (deep= (sort (filter int? {:a 1 :b 2.3 :c 3})) @[1 3])) +(assert (deep= (filter even? (coro (yield 1) (yield 2) (yield 8))) @[2 8])) + # interpose (assert (deep= (interpose ":" "hello") @[104 ":" 101 ":" 108 ":" 108 ":" 111])) From c6f44b9c114aa9fa1989f98f09e7192889679e53 Mon Sep 17 00:00:00 2001 From: sogaiu <33044872+sogaiu@users.noreply.github.com> Date: Tue, 18 Aug 2026 13:16:20 +0000 Subject: [PATCH 284/307] Add tests for partition (#1814) Co-authored-by: sogaiu <983021772@users.noreply.github.com> Co-authored-by: Calvin Rose --- test/suite-boot.janet | 11 +++++++++++ 1 file changed, 11 insertions(+) diff --git a/test/suite-boot.janet b/test/suite-boot.janet index 245c75c4..fb2a9dd0 100644 --- a/test/suite-boot.janet +++ b/test/suite-boot.janet @@ -1074,6 +1074,17 @@ (assert (= (find even? @{:x 11 :y 28 :z 33}) 28)) (assert (= (find keyword? (coro (yield 'jump) (yield :wave))) :wave)) +# partition +(assert (deep= (partition 2 "hello!") @["he" "ll" "o!"])) +(assert (deep= (partition 3 @"hello!") @["hel" "lo!"])) +(assert (deep= (partition 4 'hello!) @["hell" "o!"])) +(assert (deep= (partition 5 :hello!) @["hello" "!"])) +(assert (deep= (partition 2 [:ant :bee :fox :elephant]) + @[[:ant :bee] [:fox :elephant]])) +(assert (deep= (partition 3 @['rock 'paper 'scissors 'spock]) + @[['rock 'paper 'scissors] ['spock]])) +(assert (deep= (partition 2 (coro (yield 0) (yield 1) (yield 2) (yield 3))) + @[[0 1] [2 3]])) # filter (assert (deep= (filter |(< $ (chr "m")) "azbycw") @[97 98 99])) (assert (deep= (filter pos? @[-1 0 1]) @[1])) From 59b72ba072ae65f9c287a091df1cd081ec5b60bf Mon Sep 17 00:00:00 2001 From: sogaiu <33044872+sogaiu@users.noreply.github.com> Date: Tue, 18 Aug 2026 13:17:16 +0000 Subject: [PATCH 285/307] Improve docstrings of filter and friends (#1815) Co-authored-by: sogaiu <983021772@users.noreply.github.com> --- src/boot/boot.janet | 88 +++++++++++++++++++++++++++------------------ 1 file changed, 54 insertions(+), 34 deletions(-) diff --git a/src/boot/boot.janet b/src/boot/boot.janet index 1327838c..500c1aae 100644 --- a/src/boot/boot.janet +++ b/src/boot/boot.janet @@ -1152,13 +1152,17 @@ res) (defn filter - ``Given a predicate, take only elements from an array or tuple for - which `(pred element)` is truthy. Returns a new array.`` - [pred ind] + `` + Given a predicate, take only elements from `x` for which `(pred + element)` is truthy. Returns a new array. + + `x` can be a bytes, indexed, dictionary, fiber, or abstract type + with suitable `get` and `next` methods. + `` + [pred x] (def res @[]) - (each item ind - (if (pred item) - (array/push res item))) + (each v x + (if (pred v) (array/push res v))) res) (defn count @@ -1877,20 +1881,25 @@ ret) (defn partition-by - ``Partition elements of a sequential data structure by a representative function `f`. Partitions - split when `(f x)` changes values when iterating to the next element `x` of `ind`. Returns a new array - of arrays.`` - [f ind] + `` + Partition values of `x` by a function `f`. Partitions split when `f` + applied to a value changes result when iterating to the next value + of `x`. Returns a new array of arrays. + + `x` can be a bytes, indexed, fiber, or abstract type with suitable + `get` and `next` methods. + `` + [f x] (def ret @[]) (var span nil) - (var category nil) - (var is-new true) - (each x ind - (def y (f x)) + (var categ nil) + (var new? true) + (each v x + (def y (f v)) (cond - is-new (do (set is-new false) (set category y) (set span @[x]) (array/push ret span)) - (= y category) (array/push span x) - (do (set category y) (set span @[x]) (array/push ret span)))) + new? (do (set new? false) (set categ y) (set span @[v]) (array/push ret span)) + (= categ y) (array/push span v) + (do (set categ y) (set span @[v]) (array/push ret span)))) ret) (defn interleave @@ -1944,24 +1953,28 @@ ret) (defn interpose - ``Returns a sequence of the elements of `ind` separated by - `sep`. Returns a new array.`` - [sep ind] - (var k (next ind nil)) + `` + Returns an array of the values of `x` separated by `sep`. + + `x` can be a bytes, indexed, fiber or abstract type with suitable + `get` and `next` methods. + `` + [sep x] + (var k (next x nil)) (if (not= nil k) - (if (lengthable? ind) + (if (lengthable? x) (do - (def ret (array/new-filled (- (* 2 (length ind)) 1) sep)) + (def ret (array/new-filled (- (* 2 (length x)) 1) sep)) (var i 0) (while (not= nil k) - (put ret i (in ind k)) - (set k (next ind k)) + (put ret i (in x k)) + (set k (next x k)) (+= i 2)) ret) (do - (def ret @[(in ind k)]) - (while (not= nil (set k (next ind k))) - (array/push ret sep (in ind k))) + (def ret @[(in x k)]) + (while (not= nil (set k (next x k))) + (array/push ret sep (in x k))) ret)) @[])) @@ -1980,13 +1993,20 @@ ret) (defn partition - ``Partition an indexed data structure `ind` into tuples - of size `n`. Returns a new array.`` - [n ind] + `` + Partition the values of `x` into tuples of size `n`. Returns a new + array. + + `n` should be a non-negative integer. + + `x` can be a bytes, indexed, fiber, or abstract type with suitable + `get` and `next` methods. + `` + [n x] (cond - (indexed? ind) (partition-slice tuple/slice n ind) - (bytes? ind) (partition-slice string/slice n ind) - (partition-slice tuple/slice n (values ind)))) + (indexed? x) (partition-slice tuple/slice n x) + (bytes? x) (partition-slice string/slice n x) + (partition-slice tuple/slice n (values x)))) ### ### From 5fb06039f376c7b3c2db2e65969784f3b6c6d5cf Mon Sep 17 00:00:00 2001 From: Calvin Rose Date: Tue, 18 Aug 2026 13:19:42 -0500 Subject: [PATCH 286/307] Correct flags in fiber.h and add comments! Flag collisions was causing some unexpected behavior in some situations. Removing the source of collisions resolve odd cancelation behavior in the ev/ module where multiple calls to ev/cancel on the same fiber would mess with the internal fiber flags and cause bad behavior and the wrong error message return (but still otherwise behave correctly, so unless you were parsing the returned error messages, things likely worked). Also leave comments and test to avoid regressions. --- src/core/ev.c | 16 ++++++++-------- src/core/fiber.h | 12 +++++++----- src/core/gc.c | 2 +- src/core/vm.c | 10 +++++++--- test/suite-ev.janet | 14 ++++++++++++++ 5 files changed, 37 insertions(+), 17 deletions(-) diff --git a/src/core/ev.c b/src/core/ev.c index a1c2efaa..1971d7bf 100644 --- a/src/core/ev.c +++ b/src/core/ev.c @@ -513,14 +513,14 @@ const JanetAbstractType janet_stream_type = { /* Register a fiber to resume with value */ static void janet_schedule_general(JanetFiber *fiber, Janet value, JanetSignal sig, int soon) { - if (fiber->gc.flags & JANET_FIBER_EV_FLAG_CANCELED) return; - if (!(fiber->gc.flags & JANET_FIBER_FLAG_ROOT)) { + if (fiber->gc.flags & JANET_FIBER_EV_GCFLAG_CANCELED) return; + if (!(fiber->gc.flags & JANET_FIBER_EV_GCFLAG_ROOT)) { + fiber->gc.flags |= JANET_FIBER_EV_GCFLAG_ROOT; Janet task_element = janet_wrap_fiber(fiber); janet_table_put(&janet_vm.active_tasks, task_element, janet_wrap_true()); } JanetTask t = { fiber, value, sig, ++fiber->sched_id }; - fiber->gc.flags |= JANET_FIBER_FLAG_ROOT; - if (sig == JANET_SIGNAL_ERROR) fiber->gc.flags |= JANET_FIBER_EV_FLAG_CANCELED; + if (sig == JANET_SIGNAL_ERROR) fiber->gc.flags |= JANET_FIBER_EV_GCFLAG_CANCELED; if (soon) { janet_assert(!janet_q_push_head(&janet_vm.spawn, &t, sizeof(t)), "schedule queue overflow"); } else { @@ -537,7 +537,7 @@ void janet_schedule_soon(JanetFiber *fiber, Janet value, JanetSignal sig) { } void janet_cancel(JanetFiber *fiber, Janet value) { - if (!(fiber->gc.flags & JANET_FIBER_FLAG_ROOT)) { + if (!(fiber->gc.flags & JANET_FIBER_EV_GCFLAG_ROOT)) { janet_panic("cannot cancel non-task fiber"); } janet_schedule_signal(fiber, value, JANET_SIGNAL_ERROR); @@ -1532,8 +1532,8 @@ JanetFiber *janet_loop1(void) { if (janet_atomic_load_relaxed(&janet_vm.auto_suspend)) break; JanetTask task = {NULL, janet_wrap_nil(), JANET_SIGNAL_OK, 0}; janet_q_pop(&janet_vm.spawn, &task, sizeof(task)); - if (task.fiber->gc.flags & JANET_FIBER_EV_FLAG_SUSPENDED) janet_ev_dec_refcount(); - task.fiber->gc.flags &= ~(JANET_FIBER_EV_FLAG_CANCELED | JANET_FIBER_EV_FLAG_SUSPENDED); + if (task.fiber->gc.flags & JANET_FIBER_EV_GCFLAG_SUSPENDED) janet_ev_dec_refcount(); + task.fiber->gc.flags &= ~(JANET_FIBER_EV_GCFLAG_CANCELED | JANET_FIBER_EV_GCFLAG_SUSPENDED); if (task.expected_sched_id != task.fiber->sched_id) continue; Janet res; JanetSignal sig = janet_continue_signal(task.fiber, task.value, &res, task.sig); @@ -1543,7 +1543,7 @@ JanetFiber *janet_loop1(void) { void *sv = task.fiber->supervisor_channel; int is_suspended = sig == JANET_SIGNAL_EVENT || sig == JANET_SIGNAL_YIELD || sig == JANET_SIGNAL_INTERRUPT; if (is_suspended) { - task.fiber->gc.flags |= JANET_FIBER_EV_FLAG_SUSPENDED; + task.fiber->gc.flags |= JANET_FIBER_EV_GCFLAG_SUSPENDED; janet_ev_inc_refcount(); } if (NULL == sv) { diff --git a/src/core/fiber.h b/src/core/fiber.h index 064e09c0..56c79174 100644 --- a/src/core/fiber.h +++ b/src/core/fiber.h @@ -56,12 +56,14 @@ #define JANET_FIBER_DID_LONGJUMP 0x8000000 #define JANET_FIBER_FLAG_MASK 0xF000000 -#define JANET_FIBER_EV_FLAG_CANCELED 0x10000 -#define JANET_FIBER_EV_FLAG_SUSPENDED 0x20000 -#define JANET_FIBER_FLAG_ROOT 0x40000 -#define JANET_FIBER_EV_FLAG_IN_FLIGHT 0x1 +/* Flags on the gc header (don't confuse flags with gc.flags!) */ +/* Don't collide with JANET_FIBER_STATUS_MASK to enable flag smuggling in vm.c */ +#define JANET_FIBER_EV_GCFLAG_CANCELED 0x1000000 +#define JANET_FIBER_EV_GCFLAG_SUSPENDED 0x2000000 +#define JANET_FIBER_EV_GCFLAG_ROOT 0x4000000 -/* used only on windows, should otherwise be unset */ +/* Not on gc header */ +#define JANET_FIBER_EV_FLAG_IN_FLIGHT 0x1 #define janet_fiber_set_status(f, s) do {\ (f)->flags &= ~JANET_FIBER_STATUS_MASK;\ diff --git a/src/core/gc.c b/src/core/gc.c index 229097ea..f609bcc2 100644 --- a/src/core/gc.c +++ b/src/core/gc.c @@ -336,7 +336,7 @@ static void janet_deinit_block(JanetGCObject *mem) { if (f->ev_state && !(f->flags & JANET_FIBER_EV_FLAG_IN_FLIGHT)) { janet_ev_dec_refcount(); janet_free(f->ev_state); - } else if (f->gc.flags & JANET_FIBER_EV_FLAG_SUSPENDED) { + } else if (f->gc.flags & JANET_FIBER_EV_GCFLAG_SUSPENDED) { janet_ev_dec_refcount(); } #endif diff --git a/src/core/vm.c b/src/core/vm.c index ac329b69..0cfa7ee9 100644 --- a/src/core/vm.c +++ b/src/core/vm.c @@ -618,11 +618,13 @@ static JanetSignal run_vm(JanetFiber *fiber, Janet in) { register uint32_t *pc; register JanetFunction *func; + /* NOTE: Flag smuggling! We "smuggle" the fiber sigal in the gc.flags field instead of the usual flags field */ if (fiber->flags & JANET_FIBER_RESUME_SIGNAL) { + /* Get the signal */ JanetSignal sig = (fiber->gc.flags & JANET_FIBER_STATUS_MASK) >> JANET_FIBER_STATUS_OFFSET; + /* Clear the signal */ fiber->gc.flags &= ~JANET_FIBER_STATUS_MASK; - fiber->flags &= JANET_FIBER_RESUME_NO_SKIP | JANET_FIBER_RESUME_NO_USEVAL | /* Leave these flags untouched */ - ~(JANET_FIBER_RESUME_SIGNAL | JANET_FIBER_FLAG_MASK); + fiber->flags &= ~JANET_FIBER_RESUME_SIGNAL; janet_vm.return_reg[0] = in; return sig; } @@ -1458,7 +1460,7 @@ static JanetSignal janet_check_can_resume(JanetFiber *fiber, Janet *out, int is_ } /* If a "task" fiber is trying to be used as a normal fiber, detect that. See bug #920. * Fibers must be marked as root fibers manually, or by the ev scheduler. */ - if (janet_vm.fiber != NULL && (fiber->gc.flags & JANET_FIBER_FLAG_ROOT)) { + if (janet_vm.fiber != NULL && (fiber->gc.flags & JANET_FIBER_EV_GCFLAG_ROOT)) { #ifdef JANET_EV *out = janet_cstringv(is_cancel ? "cannot cancel root fiber, use ev/cancel" @@ -1612,6 +1614,8 @@ JanetSignal janet_continue_signal(JanetFiber *fiber, Janet in, Janet *out, Janet if (sig != JANET_SIGNAL_OK) { JanetFiber *child = fiber; while (child->child) child = child->child; + /* NOTE: We are "smuggling" flags in for later use in an unusal place. This is odd but intentional and + * saves a bit of memory per fiber rather than creating a new field. There is likely a better way to do this. */ child->gc.flags &= ~JANET_FIBER_STATUS_MASK; child->gc.flags |= sig << JANET_FIBER_STATUS_OFFSET; child->flags |= JANET_FIBER_RESUME_SIGNAL; diff --git a/test/suite-ev.janet b/test/suite-ev.janet index b1829681..5bc04eec 100644 --- a/test/suite-ev.janet +++ b/test/suite-ev.janet @@ -609,4 +609,18 @@ (assert-error "bad thread" (ev/thread in)) (assert-error "bad thread 2" (ev/thread (fn [x y] x) 1)) +# Exec-slurp correct error (os/spawn and the :x flag) +(defn exec-slurp + [& args] + # Close the process pipes. If the process pipes are not closed, janet can run out of file descriptors. + (with [proc (os/spawn args :xp {:out :pipe})] + (let [[out] (ev/gather + (ev/read (proc :out) :all) + (os/proc-wait proc))] + (if out (string/trimr out) "")))) +(def [ok msg] (protect (exec-slurp ;run janet "-e" "(os/exit 1)"))) +(assert (not ok) "os/spawn :x 1") +(assert (not (string/has-prefix? "cannot cancel" msg)) "os/spawn :x 2") +(assert (string/has-prefix? "command failed" msg) "os/spawn :x 3") + (end-suite) From 16c6bb5fda0f88ef99e6d2504866dd21031acc31 Mon Sep 17 00:00:00 2001 From: sogaiu <33044872+sogaiu@users.noreply.github.com> Date: Wed, 19 Aug 2026 14:55:43 +0000 Subject: [PATCH 287/307] Improve docstrings for each and friends (#1816) Co-authored-by: sogaiu <983021772@users.noreply.github.com> --- src/boot/boot.janet | 40 +++++++++++++++++++++++++++++----------- 1 file changed, 29 insertions(+), 11 deletions(-) diff --git a/src/boot/boot.janet b/src/boot/boot.janet index 500c1aae..ed1bc714 100644 --- a/src/boot/boot.janet +++ b/src/boot/boot.janet @@ -598,14 +598,37 @@ (for-template i start stop 1 < + body)) (defmacro eachk - "Loop over each key in `ds`. Returns nil." - [x ds & body] - (each-template x ds :keys body)) + `` + Loop over each key in `x` with each key bound to `binding` with + destructuring support. Returns `nil`. + + `x` can be a bytes, indexed, dictionary, fiber, or abstract type + with a suitable `next` method. + `` + [binding x & body] + (each-template binding x :keys body)) (defmacro eachp - "Loop over each (key, value) pair in `ds`. Returns nil." - [x ds & body] - (each-template x ds :pairs body)) + `` + Loop over each (key, value) pair in `x` with each pair bound to + `binding` with destructuring support. Returns `nil`. + + `x` can be a bytes, indexed, dictionary, fiber, or abstract type + with suitable `get` and `next` methods. + `` + [binding x & body] + (each-template binding x :pairs body)) + +(defmacro each + `` + Loop over each value in `x` with each value of `x` bound to + `binding` with destructuring support. Returns `nil`. + + `x` can be a bytes, indexed, dictionary, fiber, or abstract type + with suitable `get` and `next` methods. + `` + [binding x & body] + (each-template binding x :each body)) (defmacro repeat "Evaluate body n times. If n is negative, body will be evaluated 0 times. Evaluates to nil." @@ -618,11 +641,6 @@ [& body] ~(while true ,;body)) -(defmacro each - "Loop over each value in `ds`. Returns nil." - [x ds & body] - (each-template x ds :each body)) - (defn- check-empty-body [body] (if (= (length body) 0) From f9f7a529b21ca1743b6499a02d5febef047f4f62 Mon Sep 17 00:00:00 2001 From: sogaiu <33044872+sogaiu@users.noreply.github.com> Date: Wed, 19 Aug 2026 14:56:07 +0000 Subject: [PATCH 288/307] Improve docstrings of reduce and friends (#1817) Co-authored-by: sogaiu <983021772@users.noreply.github.com> --- src/boot/boot.janet | 90 ++++++++++++++++++++++++++++----------------- 1 file changed, 56 insertions(+), 34 deletions(-) diff --git a/src/boot/boot.janet b/src/boot/boot.janet index ed1bc714..99ed2d90 100644 --- a/src/boot/boot.janet +++ b/src/boot/boot.janet @@ -1040,57 +1040,79 @@ (sorted ind (fn :sorted-by-comp [x y] (< (f x) (f y))))) (defn reduce - ``Reduce, also know as fold-left in many languages, transforms - an indexed type (array, tuple) with a function to produce a value by applying `f` to - each element in order. `f` is a function of 2 arguments, `(f accum el)`, where - `accum` is the initial value and `el` is the next value in the indexed type `ind`. - `f` returns a value that will be used as `accum` in the next call to `f`. `reduce` - returns the value of the final call to `f`.`` - [f init ind] - (var accum init) - (each el ind (set accum (f accum el))) - accum) + `` + Also know as fold-left in many languages, this function transforms + `x` by successively applying a function `f` to values from `x` and + an updating "accumulator" value. `f` is a function of 2 arguments, + `(f acc v)`, where `acc` starts as `init` and `v` is the next value + in `x`. `f` returns a value that will be used as `acc` in the next + call to `f`. The ultimate return value is that of the final call to + `f`. + + `x` can be a bytes, indexed, dictionary, fiber, or abstract type + with suitable `get` and `next` methods. + `` + [f init x] + (var acc init) + (each v x (set acc (f acc v))) + acc) (defn reduce2 - ``The 2-argument version of `reduce` that does not take an initialization value. - Instead, the first element of the array is used for initialization. If `ind` is empty, will evaluate to nil.`` - [f ind] - (var k (next ind)) + `` + The 2-argument version of `reduce`. Instead of taking an explicit + initial value, the first value of `x` is used instead. If `x` is + empty, returns `nil`. + + `x` can be a bytes, indexed, dictionary, fiber, or abstract type + with suitable `get` and `next` methods. + `` + [f x] + (var k (next x)) (if (= nil k) (break nil)) - (var res (in ind k)) - (set k (next ind k)) + (var res (in x k)) + (set k (next x k)) (while (not= nil k) - (set res (f res (in ind k))) - (set k (next ind k))) + (set res (f res (in x k))) + (set k (next x k))) res) (defn accumulate - ``Similar to `reduce`, but accumulates intermediate values into an array. - The last element in the array is what would be the return value from `reduce`. - The `init` value is not added to the array (the return value will have the same - number of elements as `ind`). - Returns a new array.`` - [f init ind] + `` + Similar to `reduce`, but accumulates successive results of applying + `f` into a new array. The last element in the array is what would be + the return value from `reduce`. The `init` value is not added + directly as the first value of the new array, i.e. the returned + array will have the same number of elements as `x`. + + `x` can be a bytes, indexed, dictionary, fiber, or abstract type + with suitable `get` and `next` methods. + `` + [f init x] (var res init) (def ret @[]) - (each x ind (array/push ret (set res (f res x)))) + (each v x (array/push ret (set res (f res v)))) ret) (defn accumulate2 - ``The 2-argument version of `accumulate` that does not take an initialization value. - The first value in `ind` will be added to the array as is, so the length of the - return value will be `(length ind)`.`` - [f ind] - (var k (next ind)) + `` + The 2-argument version of `accumulate`. Instead of taking an + explicit initial value, the first value of `x` is used instead. If + `x` is empty, returns an empty array. + + `x` can be a bytes, indexed, dictionary, fiber, or abstract type + with suitable `get` and `next` methods. + `` + [f x] + (var k (next x)) (def ret @[]) (if (= nil k) (break ret)) - (var res (in ind k)) + (var res (in x k)) (array/push ret res) - (set k (next ind k)) + (set k (next x k)) (while (not= nil k) - (set res (f res (in ind k))) + (set res (f res (in x k))) (array/push ret res) - (set k (next ind k))) + (set k (next x k))) ret) (defmacro- map-aggregator From 36db9988b04bb3caf5da58aa4a9756853147b406 Mon Sep 17 00:00:00 2001 From: sogaiu <33044872+sogaiu@users.noreply.github.com> Date: Wed, 19 Aug 2026 14:56:20 +0000 Subject: [PATCH 289/307] Improve docstrings for reverse and reverse! (#1818) Co-authored-by: sogaiu <983021772@users.noreply.github.com> --- src/boot/boot.janet | 41 +++++++++++++++++++++++++---------------- 1 file changed, 25 insertions(+), 16 deletions(-) diff --git a/src/boot/boot.janet b/src/boot/boot.janet index 99ed2d90..8b760ee5 100644 --- a/src/boot/boot.janet +++ b/src/boot/boot.janet @@ -1666,32 +1666,41 @@ res) (defn reverse! - `Reverses the order of the elements in a given array or buffer and returns it - mutated.` - [t] + `` + Reverses the order of the values in `x` and returns it mutated. + + `x` can be a buffer, array, or abstract type with suitable + `get`, `length`, and `put` methods. + `` + [x] (var i 0) - (var j (length t)) + (var j (length x)) (while (< i (-- j)) - (def ti (in t i)) - (put t i (in t j)) - (put t j ti) + (def v (in x i)) + (put x i (in x j)) + (put x j v) (++ i)) - t) + x) (defn reverse - `Reverses the order of the elements in a given array or tuple and returns - a new array. If a string or buffer is provided, returns a buffer instead.` - [t] - (if (lengthable? t) + `` + Reverses the order of the values in `x`. If `x` is a bytes type, + returns a buffer, otherwise returns an array. + + `x` can be a bytes, indexed, fiber, or abstract type with suitable + `get` and `next` methods. + `` + [x] + (if (lengthable? x) (do - (var n (length t)) - (def ret (if (bytes? t) + (var n (length x)) + (def ret (if (bytes? x) (buffer/new-filled n) (array/new-filled n))) - (each v t + (each v x (put ret (-- n) v)) ret) - (reverse! (seq [v :in t] v)))) + (reverse! (seq [v :in x] v)))) (defn invert `` From ef07dc347f9c2a8415f7037c7cce0b950e3479f7 Mon Sep 17 00:00:00 2001 From: sogaiu <33044872+sogaiu@users.noreply.github.com> Date: Thu, 20 Aug 2026 13:47:54 +0000 Subject: [PATCH 290/307] Improve docstrings of every? and any? (#1819) Co-authored-by: sogaiu <983021772@users.noreply.github.com> --- src/boot/boot.janet | 32 ++++++++++++++++++++++---------- 1 file changed, 22 insertions(+), 10 deletions(-) diff --git a/src/boot/boot.janet b/src/boot/boot.janet index 8b760ee5..29363a82 100644 --- a/src/boot/boot.janet +++ b/src/boot/boot.janet @@ -1648,21 +1648,33 @@ (fn :partial [& r] (f ;more ;r)))) (defn every? - ``Evaluates to the last element of `ind` if all preceding elements are truthy, - otherwise evaluates to the first falsey element.`` - [ind] + `` + Evaluates to the last element of `x` if all preceding elements are + truthy, true if `x` is empty, or otherwise to the first falsey + element. + + `x` can be a bytes, indexed. dictionary, fiber, or abstract type + with suitable `get` and `next` methods. + `` + [x] (var res true) - (loop [x :in ind :while res] - (set res x)) + (loop [v :in x :while res] + (set res v)) res) (defn any? - ``Evaluates to the last element of `ind` if all preceding elements are falsey, - otherwise evaluates to the first truthy element.`` - [ind] + `` + Evaluates to the last element of `x` if all preceding elements are + falsey, `nil` if `x` is empty, or otherwise to the first truthy + element. + + `x` can be a bytes, indexed. dictionary, fiber, or abstract type + with suitable `get` and `next` methods. + `` + [x] (var res nil) - (loop [x :in ind :until res] - (set res x)) + (loop [v :in x :until res] + (set res v)) res) (defn reverse! From 83558579ce35a2f645c51fd8ca9a9c8e6b4e76c0 Mon Sep 17 00:00:00 2001 From: sogaiu <33044872+sogaiu@users.noreply.github.com> Date: Thu, 20 Aug 2026 13:48:05 +0000 Subject: [PATCH 291/307] Improve docstrings for all, some, and count (#1820) Co-authored-by: sogaiu <983021772@users.noreply.github.com> --- src/boot/boot.janet | 84 ++++++++++++++++++++++++++------------------- 1 file changed, 49 insertions(+), 35 deletions(-) diff --git a/src/boot/boot.janet b/src/boot/boot.janet index 29363a82..816ccf10 100644 --- a/src/boot/boot.janet +++ b/src/boot/boot.janet @@ -1206,18 +1206,23 @@ res) (defn count - ``` - Count the number of values in a data structure `ind` for which - applying `pred` yields a truthy value, but only if no `inds` are - provided. Multiple data structures can be handled if each `inds` - is a data structure and `pred` is a function of arity one more - than the number of `inds`. Note that `pred` is only applied to - values at indices up to the largest index of the shortest of - `ind` and each of `inds`. - ``` - [pred ind & inds] + `` + Count the number of values in `x` for which applying `pred` yields a + truthy value. + + Also functions variadically, i.e. takes additional arguments, with + suitable changes to the arity of `pred`. `pred` is called with both + values from `x` and additional arguments, and only as many times as + the length of the shortest of `x` and each element of `xs`. If `x` + or any element of `xs` is empty, returns 0. + + `x` and elements of `xs` can be values of type bytes, indexed, + dictionary, fiber, or abstract with suitable `get` and `next` + methods. + `` + [pred x & xs] (var res 0) - (map-template :count res pred ind inds) + (map-template :count res pred x xs) res) (defn keep @@ -2445,35 +2450,44 @@ ret) (defn all - ``` - Returns true if applying `pred` to every value in a data - structure `ind` results in only truthy values, but only if no - `inds` are provided. Multiple data structures can be handled - if each `inds` is a data structure and `pred` is a function - of arity one more than the number of `inds`. Returns the first - falsey result encountered. Note that `pred` is only called as - many times as the length of the shortest of `ind` and each of - `inds`. If `ind` or any of `inds` are empty, returns true. - ``` - [pred ind & inds] + `` + Apply `pred` to values of `x`. Return true if all results are truthy; + if any result is falsey, stop applying `pred` and return the result. + + Also functions variadically, i.e. takes additional arguments, with + suitable changes to the arity of `pred`. `pred` is called with both + values from `x` and additional arguments, and only as many times as + the length of the shortest of `x` and each element of `xs`. If `x` + or any element of `xs` is empty, returns true. + + `x` and elements of `xs` can be values of type bytes, indexed, + dictionary, fiber, or abstract with suitable `get` and `next` + methods. + `` + [pred x & xs] (var res true) - (map-template :all res pred ind inds) + (map-template :all res pred x xs) res) (defn some - ``` - Returns nil if applying `pred` to every value in a data - structure `ind` results in only falsey values, but only if no - `inds` are provided. Multiple data structures can be handled - if each `inds` is a data structure and `pred` is a function - of arity one more than the number of `inds`. Returns the first - truthy result encountered. Note that `pred` is only called as - many times as the length of the shortest of `ind` and each of - `inds`. If `ind` or any of `inds` are empty, returns nil. - ``` - [pred ind & inds] + `` + Returns `nil` if applying `pred` to every value in `x` results in + only falsey values, otherwise returns the first truthy result + encountered. + + Also functions variadically, i.e. takes additional arguments, with + suitable changes to the arity of `f`. `f` is called with both values + from `x` and additional arguments, and only as many times as the + length of the shortest of `x` and each element of `xs`. If `x` or + any element of `xs` is empty, returns `nil`. + + `x` and elements of `xs` can be values of type bytes, indexed, + dictionary, fiber, or abstract with suitable `get` and `next` + methods. + `` + [pred x & xs] (var res nil) - (map-template :some res pred ind inds) + (map-template :some res pred x xs) res) (defn freeze From 325119747739fb9477c6a50efccdef1a384548ff Mon Sep 17 00:00:00 2001 From: sogaiu <33044872+sogaiu@users.noreply.github.com> Date: Thu, 20 Aug 2026 14:39:48 +0000 Subject: [PATCH 292/307] Improve docstrings for keep, map, and mapcat (#1821) Co-authored-by: sogaiu <983021772@users.noreply.github.com> --- src/boot/boot.janet | 80 +++++++++++++++++++++++++++------------------ 1 file changed, 48 insertions(+), 32 deletions(-) diff --git a/src/boot/boot.janet b/src/boot/boot.janet index 816ccf10..d1b14e58 100644 --- a/src/boot/boot.janet +++ b/src/boot/boot.janet @@ -1163,32 +1163,43 @@ (map-aggregator ,maptype ,res (,f x ;call-buffer))))))) (defn map - ``` - Map a function `f` over every value in a data structure `ind` - and return an array of results, but only if no `inds` are - provided. Multiple data structures can be handled if each - `inds` is a data structure and `f` is a function of arity - one more than the number of `inds`. The resulting array has - a length that is the shortest of `ind` and each of `inds`. - ``` - [f ind & inds] + `` + Map a function `f` over every value in `x` and return an array of + results. + + Also functions variadically, i.e. takes additional arguments, with + suitable changes to the arity of `f`. `f` is called with both values + from `x` and additional arguments, and only as many times as the + length of the shortest of `x` and each element of `xs`. If `x` or + any element of `xs` is empty, returns an empty array. + + `x` and elements of `xs` can be values of type bytes, indexed, + dictionary, fiber, or abstract with suitable `get` and `next` + methods. + `` + [f x & xs] (def res @[]) - (map-template :map res f ind inds) + (map-template :map res f x xs) res) (defn mapcat - ``` - Map a function `f` over every value in a data structure `ind` - and use `array/concat` to concatenate the results, but only if - no `inds` are provided. Multiple data structures can be handled - if each `inds` is a data structure and `f` is a function of - arity one more than the number of `inds`. Note that `f` is only - applied to values at indices up to the largest index of the - shortest of `ind` and each of `inds`. - ``` - [f ind & inds] + `` + Map a function `f` over every value in `x` and use `array/concat` to + concatenate the results. + + Also functions variadically, i.e. takes additional arguments, with + suitable changes to the arity of `f`. `f` is called with both values + from `x` and additional arguments, and only as many times as the + length of the shortest of `x` and each element of `xs`. If `x` or + any element of `xs` is empty, returns an empty array. + + `x` and elements of `xs` can be values of type bytes, indexed, + dictionary, fiber, or abstract with suitable `get` and `next` + methods. + `` + [f x & xs] (def res @[]) - (map-template :mapcat res f ind inds) + (map-template :mapcat res f x xs) res) (defn filter @@ -1226,18 +1237,23 @@ res) (defn keep - ``` - Given a predicate `pred`, return a new array containing the - truthy results of applying `pred` to each value in the data - structure `ind`, but only if no `inds` are provided. Multiple - data structures can be handled if each `inds` is a data - structure and `pred` is a function of arity one more than the - number of `inds`. The resulting array has a length that is no - longer than the shortest of `ind` and each of `inds`. - ``` - [pred ind & inds] + `` + Given a predicate `pred`, return a new array containing the truthy + results of applying `pred` to each value in `x`. + + Also functions variadically, i.e. takes additional arguments, with + suitable changes to the arity of `pred`. `pred` is called with both + values from `x` and additional arguments, and only as many times as + the length of the shortest of `x` and each element of `xs`. If `x` + or any element of `xs` is empty, returns an empty array. + + `x` and elements of `xs` can be values of type bytes, indexed, + dictionary, fiber, or abstract with suitable `get` and `next` + methods. + `` + [pred x & xs] (def res @[]) - (map-template :keep res pred ind inds) + (map-template :keep res pred x xs) res) (defn find-index From ec48673f7c66c04076cdb2591f440c97472db480 Mon Sep 17 00:00:00 2001 From: Calvin Rose Date: Thu, 20 Aug 2026 17:25:32 -0500 Subject: [PATCH 293/307] Revert float interface - #1822 It is superfluous since we have the double interface. Users can manually cast and be aware they are losing precision. --- src/core/capi.c | 8 -------- src/include/janet.h | 2 -- 2 files changed, 10 deletions(-) diff --git a/src/core/capi.c b/src/core/capi.c index 9d620650..d2b91116 100644 --- a/src/core/capi.c +++ b/src/core/capi.c @@ -362,14 +362,6 @@ uint8_t janet_getuinteger8(const Janet *argv, int32_t n) { return (uint16_t) janet_unwrap_number(x); } -float janet_getfloat(const Janet *argv, int32_t n) { - Janet x = argv[n]; - if (!janet_checkfloat(x)) { - janet_panicf("bad slot #%d, expected float number, got %v", n, x); - } - return (float) janet_unwrap_number(x); -} - int64_t janet_getinteger64(const Janet *argv, int32_t n) { #ifdef JANET_INT_TYPES return janet_unwrap_s64(argv[n]); diff --git a/src/include/janet.h b/src/include/janet.h index 9857bfac..c243e68a 100644 --- a/src/include/janet.h +++ b/src/include/janet.h @@ -945,7 +945,6 @@ JANET_API Janet janet_nanbox32_from_tagp(uint32_t tag, void *pointer); /* End of tagged union implementation */ #endif -JANET_API int janet_checkfloat(Janet x); JANET_API int janet_checkint8(Janet x); JANET_API int janet_checkuint8(Janet x); JANET_API int janet_checkint16(Janet x); @@ -956,7 +955,6 @@ JANET_API int janet_checkint64(Janet x); JANET_API int janet_checkuint64(Janet x); JANET_API int janet_checksize(Janet x); JANET_API JanetAbstract janet_checkabstract(Janet x, const JanetAbstractType *at); -#define janet_checkfloatrange(x) ((x) >= FLT_MIN && (x) <= FLT_MAX && (x) == (float)(x)) #define janet_checkint8range(x) ((x) >= INT8_MIN && (x) <= INT8_MAX && (x) == (int8_t)(x)) #define janet_checkuint8range(x) ((x) >= 0 && (x) <= UINT8_MAX && (x) == (uint8_t)(x)) #define janet_checkint16range(x) ((x) >= INT16_MIN && (x) <= INT16_MAX && (x) == (int16_t)(x)) From f47c7b9dce0b3688597a63303183086e11e65cdf Mon Sep 17 00:00:00 2001 From: Calvin Rose Date: Thu, 20 Aug 2026 17:27:49 -0500 Subject: [PATCH 294/307] Remove more extra function stubs from float checking. --- src/core/util.c | 7 ------- 1 file changed, 7 deletions(-) diff --git a/src/core/util.c b/src/core/util.c index 7b9a1af0..90e9a601 100644 --- a/src/core/util.c +++ b/src/core/util.c @@ -927,13 +927,6 @@ int janet_checkuint8(Janet x) { return janet_checkuint8range(dval); } -int janet_checkfloat(Janet x) { - if (!janet_checktype(x, JANET_NUMBER)) - return 0; - double dval = janet_unwrap_number(x); - return janet_checkfloatrange(dval); -} - int janet_checksize(Janet x) { if (!janet_checktype(x, JANET_NUMBER)) return 0; From 2c3995eb3367a4c994cd6795c0b7029f930272f7 Mon Sep 17 00:00:00 2001 From: Calvin Rose Date: Fri, 21 Aug 2026 07:21:24 -0500 Subject: [PATCH 295/307] Make order of path resolution from JANET_PATH more intuitive. Also match shell convention, with strict searching from left to right instead of right to left by default. The _first_ element will be bound to (dyn *syspath*), not the last. Potentially breaking change for complicated, custom setups --- src/boot/boot.janet | 21 ++++++++------------- 1 file changed, 8 insertions(+), 13 deletions(-) diff --git a/src/boot/boot.janet b/src/boot/boot.janet index d1b14e58..ed17473e 100644 --- a/src/boot/boot.janet +++ b/src/boot/boot.janet @@ -3164,7 +3164,6 @@ (array/insert mp 0 [":all:" loader (fn :check-ext [x] (string/has-suffix? ext x))]) mp) -# Don't expose this externally yet - could break if custom module/paths is setup. (defn module/add-syspath ``` Add a custom syspath to `module/paths` by duplicating all entries that being with `:sys:` and @@ -4428,19 +4427,15 @@ (def- seps {:windows "\\" :mingw "\\" :cygwin "\\"}) (defn- sep [] (get seps (os/which) "/")) - (defn- bundle-rpath - [path] - (os/realpath path)) - (defn- bundle-dir [&opt bundle-name] (def s (sep)) - (string (bundle-rpath (dyn *syspath*)) s "bundle" (if bundle-name s) bundle-name)) + (string (os/realpath (dyn *syspath*)) s "bundle" (if bundle-name s) bundle-name)) (defn- bundle-file [bundle-name filename] (def s (sep)) - (string (bundle-rpath (dyn *syspath*)) s "bundle" s bundle-name s filename)) + (string (os/realpath (dyn *syspath*)) s "bundle" s bundle-name s filename)) (defn- get-manifest-filename [bundle-name] @@ -4519,7 +4514,7 @@ (def manifest (bundle/manifest bundle-name)) (def dir (os/cwd)) (def workdir (get manifest :local-source ".")) - (def fixed-syspath (bundle-rpath (dyn *syspath*))) + (def fixed-syspath (os/realpath (dyn *syspath*))) (try (os/cd workdir) ([_] (print "cannot enter source directory " workdir " for bundle " bundle-name))) @@ -4652,7 +4647,7 @@ check). A user can register a hook by defining a function with the same name in the bundle script.`` [path &keys config] - (def path (bundle-rpath path)) + (def path (os/realpath path)) (def s (sep)) # Detect bundle name (def infofile-src1 (string path s "bundle" s "info.jdn")) @@ -4741,7 +4736,7 @@ (def install-hook (string dest-dir s "bundle" s "init.janet")) (edefer (rmrf dest-dir) # don't leave garbage on failure (def install-source @[]) - (def syspath (bundle-rpath (dyn *syspath*))) + (def syspath (os/realpath (dyn *syspath*))) (when is-backup (copyrf (bundle-dir bundle-name) (string dest-dir s "old-bundle"))) (each file files (def {:mode mode :permissions perm} (os/stat file)) @@ -4807,7 +4802,7 @@ "Given a file path, figure out which bundle installed it." [path] (var ret nil) - (def rpath (bundle-rpath path)) + (def rpath (os/realpath path)) (each bundle-name (bundle/list) (def files (get (bundle/manifest bundle-name) :files [])) (def has-file (index-of rpath files)) @@ -4978,8 +4973,8 @@ (when-let [jp (getenv-alias "JANET_PATH")] (def path-sep (if (index-of (os/which) [:windows :mingw]) ";" ":")) - (def paths (reverse! (string/split path-sep jp))) - (for i 1 (length paths) + (def paths (string/split path-sep jp)) + (loop [i :down-to [(- (length paths) 1) 1]] (module/add-syspath (get paths i))) (setdyn *syspath* (first paths))) (if-let [jprofile (getenv-alias "JANET_PROFILE")] (setdyn *profilepath* jprofile)) From 91ea14baa228a3a085fe76b908a80114a6d5984d Mon Sep 17 00:00:00 2001 From: Calvin Rose Date: Fri, 21 Aug 2026 08:08:52 -0500 Subject: [PATCH 296/307] Update changelog --- CHANGELOG.md | 1 + 1 file changed, 1 insertion(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index a227dda6..ca28cfa2 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -2,6 +2,7 @@ All notable changes to this project will be documented in this file. ## Unreleased - ??? +- JANET_PATH will now be searched strictly left-to-right for modules. - Add `-t` (`--tool`) flag for using installed libraries as command line tools - Numerous documentation improvements and fixes - Add support for GNU/Hurd From 2c630e0f5c7b61f675f62c0823f9dffa462c57b8 Mon Sep 17 00:00:00 2001 From: sogaiu <33044872+sogaiu@users.noreply.github.com> Date: Fri, 21 Aug 2026 13:26:26 +0000 Subject: [PATCH 297/307] Improve docstrings for sum and friends (#1824) Co-authored-by: sogaiu <983021772@users.noreply.github.com> --- src/boot/boot.janet | 56 +++++++++++++++++++++++++++++++-------------- 1 file changed, 39 insertions(+), 17 deletions(-) diff --git a/src/boot/boot.janet b/src/boot/boot.janet index ed17473e..4cf9733e 100644 --- a/src/boot/boot.janet +++ b/src/boot/boot.janet @@ -760,40 +760,62 @@ (tuple fiber/new (tuple 'fn :fiber-fn '[] ;body) flags)) (defn sum - "Returns the sum of xs. If xs is empty, returns 0." - [xs] + `` + Returns the sum of the values of `x`. If `x` is empty, returns 0. + + `x` can be a bytes, indexed, dictionary, fiber, or abstract type with + suitable `get` and `next` methods. + `` + [x] (var accum 0) - (each x xs (+= accum x)) + (each v x (+= accum v)) accum) (defn mean - "Returns the mean of xs. If empty, returns NaN." - [xs] - (if (lengthable? xs) - (/ (sum xs) (length xs)) + `` + Returns the mean of the values of `x`. If empty, returns `math/nan`. + + `x` can be a bytes, indexed, dictionary, fiber, or abstract type + with suitable `get` and `next` methods. + `` + [x] + (if (lengthable? x) + (/ (sum x) (length x)) (do (var [accum total] [0 0]) - (each x xs (+= accum x) (++ total)) + (each v x (+= accum v) (++ total)) (/ accum total)))) (defn geomean - "Returns the geometric mean of xs. If empty, returns NaN." - [xs] - (if (lengthable? xs) + `` + Returns the geometric mean of the values of `x`. If empty, returns + `math/nan`. + + `x` can be a bytes, indexed, dictionary, fiber, or abstract type + with suitable `get` and `next` methods. + `` + [x] + (if (lengthable? x) (do (var accum 0) - (each x xs (+= accum (math/log x))) - (math/exp (/ accum (length xs)))) + (each v x (+= accum (math/log v))) + (math/exp (/ accum (length x)))) (do (var [accum total] [0 0]) - (each x xs (+= accum (math/log x)) (++ total)) + (each v x (+= accum (math/log v)) (++ total)) (math/exp (/ accum total))))) (defn product - "Returns the product of xs. If xs is empty, returns 1." - [xs] + `` + Returns the product of the values of `x`. If `x` is empty, returns + 1. + + `x` can be a bytes, indexed, dictionary, fiber, or abstract type + with suitable `get` and `next` methods. + `` + [x] (var accum 1) - (each x xs (*= accum x)) + (each v x (*= accum v)) accum) (defmacro when-let From f545c91581d0473362354577f105995d75444fba Mon Sep 17 00:00:00 2001 From: sogaiu <33044872+sogaiu@users.noreply.github.com> Date: Sat, 22 Aug 2026 20:00:11 +0000 Subject: [PATCH 298/307] Improve docstring for interleave (#1825) Co-authored-by: sogaiu <983021772@users.noreply.github.com> --- src/boot/boot.janet | 14 +++++++++++--- 1 file changed, 11 insertions(+), 3 deletions(-) diff --git a/src/boot/boot.janet b/src/boot/boot.janet index 4cf9733e..47a5d82b 100644 --- a/src/boot/boot.janet +++ b/src/boot/boot.janet @@ -2007,9 +2007,17 @@ ret) (defn interleave - "Returns an array of the first elements of each col, then the second elements, etc." - [& cols] - (mapcat tuple ;cols)) + `` + Returns an array of the values of each element of `xs`, interleaved. + If the elements of `xs` do not all have the same number of values, + the shortest element of `xs` determines the number of values from + each element of `xs` that are contained in the returned array. + + Each element of `xs` can be a bytes, indexed, fiber, or abstract + type with suitable `get` and `next` methods. + `` + [& xs] + (mapcat tuple ;xs)) (defn distinct "Returns an array of the deduplicated values in `xs`." From 92580fd4534aab8cfe5e3154d3d907d9bebe2ee0 Mon Sep 17 00:00:00 2001 From: sogaiu <33044872+sogaiu@users.noreply.github.com> Date: Sat, 22 Aug 2026 20:00:27 +0000 Subject: [PATCH 299/307] Improve docstring for describe (#1826) Co-authored-by: sogaiu <983021772@users.noreply.github.com> --- src/core/corelib.c | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/src/core/corelib.c b/src/core/corelib.c index 84aa79a0..502324c2 100644 --- a/src/core/corelib.c +++ b/src/core/corelib.c @@ -317,10 +317,10 @@ JANET_CORE_FN(janet_core_native, JANET_CORE_FN(janet_core_describe, "(describe x)", - "Returns a string that is a human-readable description of `x`. " - "For recursive data structures, the string returned contains a " - "pointer value from which the identity of `x` " - "can be determined.") { + "Returns a string that is a human-readable description of " + "`x`. For indexed, dictionary, fiber, pointer, and some " + "abstract types, the string returned contains a pointer " + "value from which the identity of `x` can be determined.") { JanetBuffer *b = janet_buffer(0); for (int32_t i = 0; i < argc; ++i) janet_description_b(b, argv[i]); From 284990a04303a40e6ce42cf33e049d23873b5535 Mon Sep 17 00:00:00 2001 From: sogaiu <33044872+sogaiu@users.noreply.github.com> Date: Sat, 22 Aug 2026 20:00:40 +0000 Subject: [PATCH 300/307] Improve docstring for update (#1827) Co-authored-by: sogaiu <983021772@users.noreply.github.com> --- src/boot/boot.janet | 17 +++++++++++------ 1 file changed, 11 insertions(+), 6 deletions(-) diff --git a/src/boot/boot.janet b/src/boot/boot.janet index 47a5d82b..e94f7551 100644 --- a/src/boot/boot.janet +++ b/src/boot/boot.janet @@ -1864,12 +1864,17 @@ x) (defn update - ``For a given key in data structure `ds`, replace its corresponding value with the - result of calling `func` on that value. If `args` are provided, they will be passed - along to `func` as well. Returns `ds`, updated.`` - [ds key func & args] - (def old (get ds key)) - (put ds key (func old ;args))) + `` + For a given key in `x`, replace its associated value with the result + of applying `f` to that value. If `args` are provided, they will be + passed along to `f` as well. Returns updated `x`. + + `x` can be a buffer, array, table, or suitable abstract type with + `get` and `put` methods. + `` + [x key f & args] + (def old (get x key)) + (put x key (f old ;args))) (defn merge-into `` From 481581282749f5ba4544728ff43e540cb09fecd6 Mon Sep 17 00:00:00 2001 From: sogaiu <33044872+sogaiu@users.noreply.github.com> Date: Sun, 23 Aug 2026 16:04:49 +0000 Subject: [PATCH 301/307] Improve docstring for loop (#1830) Co-authored-by: sogaiu <983021772@users.noreply.github.com> --- src/boot/boot.janet | 94 +++++++++++++++++++++++++-------------------- 1 file changed, 52 insertions(+), 42 deletions(-) diff --git a/src/boot/boot.janet b/src/boot/boot.janet index e94f7551..a96a0eff 100644 --- a/src/boot/boot.janet +++ b/src/boot/boot.janet @@ -647,75 +647,85 @@ (maclintf :normal "empty loop body"))) (defmacro loop - ``` - A general purpose loop macro. This macro is similar to the Common Lisp loop - macro, although intentionally much smaller in scope. The head of the loop - should be a tuple that contains a sequence of either bindings or - conditionals. A binding is a sequence of three values that define something - to loop over. Bindings are written in the format: + `` + A general purpose loop macro. This macro is similar to the Common + Lisp `loop` macro, although intentionally much smaller in scope. + + The first argument, `head`, should be a tuple that contains a + sequence of either bindings or conditionals. A binding is a sequence + of three values that define something to loop over. Bindings are + written in the format: binding :verb object/expression - where `binding` is a binding as passed to def, `:verb` is one of a set of - keywords, and `object` is any expression. Each subsequent binding creates a - nested loop within the loop created by the previous binding. + where `binding` is a binding as passed to `def`, `:verb` is one of a + set of keywords, and `object` is any expression. Each subsequent + binding creates a nested loop within the loop created by the + previous binding. The available verbs are: - * `:iterate` -- repeatedly evaluate and bind to the expression while it is - truthy. + * `:iterate` -- repeatedly evaluate and bind to the expression while + it is truthy. - * `:range` -- loop over a range. The object should be a two-element tuple with - a start and end value, and an optional positive step. The range is half - open, [start, end). + * `:range` -- loop over a range. The object should be a two-element + tuple with a start and end value, and an optional positive + step. The range is half open, [start, end). - * `:range-to` -- same as :range, but the range is inclusive [start, end]. + * `:range-to` -- same as `:range`, but the range is inclusive + [start, end]. - * `:down` -- loop over a range, stepping downwards. The object should be a - two-element tuple with a start and (exclusive) end value, and an optional - (positive!) step size. + * `:down` -- loop over a range, stepping downwards. The object + should be a two-element tuple with a start and end value, and an + optional positive step size. The range is half open, [start, + end). - * `:down-to` -- same as :down, but the range is inclusive [start, end]. + * `:down-to` -- same as `:down`, but the range is inclusive [start, + end]. - * `:keys` -- iterate over the keys in a data structure. + * `:keys` -- iterate over the keys in a bytes, indexed, dictionary, + fiber, or abstract type with a suitable `next` method. - * `:pairs` -- iterate over the key-value pairs as tuples in a data structure. + * `:pairs` -- iterate over the key-value pairs as tuples in a bytes, + indexed, dictionary, fiber, or abstract type with suitable `get` + and `next` methods. - * `:in` -- iterate over the values in a data structure or fiber. + * `:in` -- iterate over the values in a bytes, indexed, dictionary, + fiber, or abstract type with suitable `get` and `next` methods. - `loop` also accepts conditionals to refine the looping further. Conditionals are of - the form: + `loop` also accepts conditionals to refine the looping + further. Conditionals are of the form: :modifier argument - where `:modifier` is one of a set of keywords, and `argument` is keyword-dependent. - `:modifier` can be one of: + where `:modifier` is one of a set of keywords, and `argument` is + keyword-dependent. `:modifier` can be one of: - * `:while expression` -- breaks from the current loop if `expression` is - falsey. + * `:while expression` -- breaks from the current loop if + `expression` is falsey. - * `:until expression` -- breaks from the current loop if `expression` is - truthy. + * `:until expression` -- breaks from the current loop if + `expression` is truthy. - * `:let bindings` -- defines bindings inside the current loop as passed to the - `let` macro. + * `:let bindings` -- defines bindings inside the current loop as + passed to the `let` macro. - * `:before form` -- evaluates a form for a side effect before the next inner - loop. - - * `:after form` -- same as `:before`, but the side effect happens after the + * `:before form` -- evaluates a form for a side effect before the next inner loop. + * `:after form` -- same as `:before`, but the side effect happens + after the next inner loop. + * `:repeat n` -- repeats the next inner loop `n` times. - * `:when condition` -- only evaluates the current loop body when `condition` - is truthy. + * `:when condition` -- only evaluates the current loop body when + `condition` is truthy. - * `:unless condition` -- only evaluates the current loop body when `condition` - is falsey. + * `:unless condition` -- only evaluates the current loop body when + `condition` is falsey. - The `loop` macro always evaluates to nil. - ``` + The `loop` macro always evaluates to `nil`. + `` [head & body] (loop1 body head 0)) From 96e07aab367d3c8062d2fc8151eb6e446d44a520 Mon Sep 17 00:00:00 2001 From: sogaiu <33044872+sogaiu@users.noreply.github.com> Date: Sun, 23 Aug 2026 16:05:00 +0000 Subject: [PATCH 302/307] Improve docstring for last (#1829) Co-authored-by: sogaiu <983021772@users.noreply.github.com> --- src/boot/boot.janet | 11 ++++++++--- 1 file changed, 8 insertions(+), 3 deletions(-) diff --git a/src/boot/boot.janet b/src/boot/boot.janet index a96a0eff..f206b990 100644 --- a/src/boot/boot.janet +++ b/src/boot/boot.janet @@ -909,9 +909,14 @@ (get x 0)) (defn last - "Get the last element from an indexed data structure." - [xs] - (get xs (- (length xs) 1))) + `` + Get the final element from `x`. + + `x` can be a bytes, indexed, or abstract type value with suitable + `get` and `length` methods. + `` + [x] + (get x (- (length x) 1))) ## Polymorphic comparisons From d63d952bc4125a76dad7b180480ad688be442fd1 Mon Sep 17 00:00:00 2001 From: sogaiu <33044872+sogaiu@users.noreply.github.com> Date: Sun, 23 Aug 2026 16:05:24 +0000 Subject: [PATCH 303/307] Improve docstring for length (#1828) Co-authored-by: sogaiu <983021772@users.noreply.github.com> --- src/core/corelib.c | 11 ++++++++--- 1 file changed, 8 insertions(+), 3 deletions(-) diff --git a/src/core/corelib.c b/src/core/corelib.c index 502324c2..c5e2119d 100644 --- a/src/core/corelib.c +++ b/src/core/corelib.c @@ -1262,9 +1262,14 @@ JanetTable *janet_core_env(JanetTable *replacements) { "Returns `x`.")); janet_quick_asm(env, JANET_FUN_LENGTH, "length", 1, 1, 1, 1, length_asm, sizeof(length_asm), - JDOC("(length ds)\n\n" - "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.")); + JDOC("(length x)\n\n" + "Returns the length or count of `x` as an integer. " + "Returns are guaranteed to be in constant time " + "except for abstract types. For dictionaries, " + "returns the number of key-value pairs.\n" + "\n" + "`x` can be a bytes, indexed, dictionary, or " + "abstract type with a suitable `length` method.")); janet_quick_asm(env, JANET_FUN_BNOT, "bnot", 1, 1, 1, 1, bnot_asm, sizeof(bnot_asm), JDOC("(bnot x)\n\nReturns the bit-wise inverse of integer x.")); From 5b07408181e3483e6045259003a09579d629c196 Mon Sep 17 00:00:00 2001 From: Calvin Rose Date: Sun, 23 Aug 2026 12:43:25 -0500 Subject: [PATCH 304/307] Undefined shift (literal 1 should be unsigned) --- src/core/string.c | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/core/string.c b/src/core/string.c index 53a04f9f..4e303d82 100644 --- a/src/core/string.c +++ b/src/core/string.c @@ -476,13 +476,13 @@ JANET_CORE_FN(cfun_string_checkset, /* Populate set */ for (int32_t i = 0; i < set.len; i++) { int index = set.bytes[i] >> 5; - uint32_t mask = 1 << (set.bytes[i] & 0x1F); + uint32_t mask = (uint32_t) 1 << (set.bytes[i] & 0x1F); bitset[index] |= mask; } /* Check set */ for (int32_t i = 0; i < str.len; i++) { int index = str.bytes[i] >> 5; - uint32_t mask = 1 << (str.bytes[i] & 0x1F); + uint32_t mask = (uint32_t) 1 << (str.bytes[i] & 0x1F); if (!(bitset[index] & mask)) { return janet_wrap_false(); } From d177131c84095f29d7af7f57a0a9f999f1153c95 Mon Sep 17 00:00:00 2001 From: Calvin Rose Date: Sun, 23 Aug 2026 12:54:23 -0500 Subject: [PATCH 305/307] Handle empty tuple as lhs in def/var without fatal error. While an odd construct, this should not cause an abort in the interpeter. Instead, we should just create no bindings and compile as normal. --- src/core/specials.c | 6 ++---- test/suite-compile.janet | 11 +++++++++++ 2 files changed, 13 insertions(+), 4 deletions(-) diff --git a/src/core/specials.c b/src/core/specials.c index 646f0ad1..35f6c854 100644 --- a/src/core/specials.c +++ b/src/core/specials.c @@ -519,8 +519,7 @@ static JanetSlot janetc_var(JanetFopts opts, int32_t argn, const Janet *argv) { janet_v_free(into); return janetc_cslot(janet_wrap_nil()); } - JanetSlot ret; - janet_assert(janet_v_count(into) > 0, "bad destructure"); + JanetSlot ret = janetc_cslot(janet_wrap_nil()); for (int32_t i = 0; i < janet_v_count(into); i++) { destructure(c, into[i].lhs, into[i].rhs, varleaf, attr_table); ret = into[i].rhs; @@ -589,8 +588,7 @@ static JanetSlot janetc_def(JanetFopts opts, int32_t argn, const Janet *argv) { janet_v_free(into); return janetc_cslot(janet_wrap_nil()); } - JanetSlot ret; - janet_assert(janet_v_count(into) > 0, "bad destructure"); + JanetSlot ret = janetc_cslot(janet_wrap_nil()); for (int32_t i = 0; i < janet_v_count(into); i++) { destructure(c, into[i].lhs, into[i].rhs, defleaf, attr_table); ret = into[i].rhs; diff --git a/test/suite-compile.janet b/test/suite-compile.janet index 1f85e851..fa4d11fd 100644 --- a/test/suite-compile.janet +++ b/test/suite-compile.janet @@ -120,6 +120,12 @@ (def result4 (compile code (curenv) "suite-compile.janet" lints)) (assert (and (function? result4) (empty? lints)) msg)) +(defn check-bad-compile + [code msg] + (def lints @[]) + (def result4 (compile code (curenv) "suite-compile.janet" lints)) + (assert (not (and (function? result4) (empty? lints))) msg)) + (defn check-lint-compile [code msg] (def lints @[]) @@ -181,4 +187,9 @@ = = =)) (setdyn *lint-warn* nil) +# Handle bad eachp +(check-good-compile '(eachp () 5) "good compile eachp 1") +(check-good-compile '(do (def [] [1]) nil) "def with empty tuple binding 1") +(check-good-compile '(do (def x (def [] [1])) x) "def with empty tuple binding 2") + (end-suite) From 021a17cda1f76c7f00f99f69b10fb83e818cae13 Mon Sep 17 00:00:00 2001 From: Calvin Rose Date: Sun, 23 Aug 2026 13:12:20 -0500 Subject: [PATCH 306/307] Fix stack overflow in deeply nested destructuring. Instead, do a recursion depth check and give a normal compile error. --- src/core/specials.c | 17 +++++++++++++++-- 1 file changed, 15 insertions(+), 2 deletions(-) diff --git a/src/core/specials.c b/src/core/specials.c index 35f6c854..1f154d8b 100644 --- a/src/core/specials.c +++ b/src/core/specials.c @@ -150,7 +150,7 @@ static JanetSlot janetc_quasiquote(JanetFopts opts, int32_t argn, const Janet *a janetc_cerror(opts.compiler, "expected 1 argument to quasiquote"); return janetc_cslot(janet_wrap_nil()); } - return quasiquote(opts, argv[0], JANET_RECURSION_GUARD, 0); + return quasiquote(opts, argv[0], opts.compiler->recursion_guard, 0); } static JanetSlot janetc_unquote(JanetFopts opts, int32_t argn, const Janet *argv) { @@ -171,6 +171,10 @@ static int destructure(JanetCompiler *c, JanetSlot s, JanetTable *attr), JanetTable *attr) { + if (c->recursion_guard <= 0) { + janetc_error(c, janet_cstring("C stack recursed too deeply")); + return 1; + } switch (janet_type(left)) { default: janetc_error(c, janet_formatc("unexpected type in destructuring, got %v", left)); @@ -255,8 +259,10 @@ static int destructure(JanetCompiler *c, JanetSlot k = janetc_cslot(janet_wrap_integer(i)); janetc_emit_sss(c, JOP_IN, nextright, right, k, 1); } + c->recursion_guard--; if (destructure(c, subval, nextright, leaf, attr)) janetc_freeslot(c, nextright); + c->recursion_guard++; } } return 1; @@ -270,8 +276,10 @@ static int destructure(JanetCompiler *c, JanetSlot nextright = janetc_farslot(c); JanetSlot k = janetc_value(janetc_fopts_default(c), kvs[i].key); janetc_emit_sss(c, JOP_IN, nextright, right, k, 1); + c->recursion_guard--; if (destructure(c, kvs[i].value, nextright, leaf, attr)) janetc_freeslot(c, nextright); + c->recursion_guard++; } } return 1; @@ -370,7 +378,10 @@ typedef struct SlotHeadPair { } SlotHeadPair; SlotHeadPair *dohead_destructure(JanetCompiler *c, SlotHeadPair *into, JanetFopts opts, Janet lhs, Janet rhs) { - + if (c->recursion_guard <= 0) { + janetc_error(c, janet_cstring("C stack recursed too deeply")); + return NULL; + } /* Detect if we can do an optimization to avoid some allocations */ int can_destructure_lhs = janet_checktype(lhs, JANET_TUPLE) || janet_checktype(lhs, JANET_ARRAY); @@ -415,7 +426,9 @@ SlotHeadPair *dohead_destructure(JanetCompiler *c, SlotHeadPair *into, JanetFopt if (!found_amp && !found_splice) { for (int32_t i = 0; i < view_lhs.len; i++) { Janet sub_rhs = view_rhs.len <= i ? janet_wrap_nil() : view_rhs.items[i]; + c->recursion_guard--; into = dohead_destructure(c, into, subopts, view_lhs.items[i], sub_rhs); + c->recursion_guard++; } return into; } From cea6dd22617a790ec9f31cd1d2f81057cc94b0e8 Mon Sep 17 00:00:00 2001 From: Calvin Rose Date: Sun, 23 Aug 2026 13:25:54 -0500 Subject: [PATCH 307/307] Handle degnerate range case gracefully. --- src/core/corelib.c | 12 +++++++----- test/suite-corelib.janet | 2 ++ 2 files changed, 9 insertions(+), 5 deletions(-) diff --git a/src/core/corelib.c b/src/core/corelib.c index c5e2119d..3f3ea57b 100644 --- a/src/core/corelib.c +++ b/src/core/corelib.c @@ -456,16 +456,18 @@ JANET_CORE_FN(janet_core_range, } count = (count > 0.0) ? count : 0.0; int32_t int_count; - janet_assert(count >= 0.0, "bad range code"); + janet_assert(count >= 0.0, "bad range code (count)"); if (count > (double) INT32_MAX) { janet_panicf("range is too large, %f elements", count); } else { int_count = (int32_t) ceil(count); } - if (step > 0.0) { - janet_assert(start + int_count * step >= stop, "bad range code"); - } else { - janet_assert(start + int_count * step <= stop, "bad range code"); + if (int_count) { /* e.g. (range 10 0 0) */ + if (step > 0.0) { + janet_assert(start + int_count * step >= stop, "bad range code (+step)"); + } else { + janet_assert(start + int_count * step <= stop, "bad range code (-step)"); + } } JanetArray *array = janet_array(int_count); for (int32_t i = 0; i < int_count; i++) { diff --git a/test/suite-corelib.janet b/test/suite-corelib.janet index 357a721e..5c470c33 100644 --- a/test/suite-corelib.janet +++ b/test/suite-corelib.janet @@ -75,6 +75,8 @@ (assert (deep= @[0 1 2 3] (range 3.999)) "range 3") (assert (deep= @[0.8 1.8 2.8 3.8] (range 0.8 3.999)) "range 4") (assert (deep= @[0.8 1.8 2.8 3.8] (range 0.8 3.999)) "range 5") +(assert (deep= @[] (range 10 0 0)) "range 6") +(assert (deep= @[] (range 0 10 0)) "range 7") (assert (< 1.0 nil false true (fiber/new (fn [] 1))