Sigil 0.22.12

release

A correctness release. Sigil ignored a program's own binding of a builtin's name, such as a local not or an internal (define (car x) ...), on every backend, and several older bugs mixed up local names with macro keywords. Both are fixed. Release configs also compile optimized C by default now.

Download 0.22.12Get started

Existing projects build without changes. Code that doesn't rebind a builtin compiles exactly as before: the release and web builds of Crash The Stack, Harkfell and Phantom Burn are byte-identical to 0.22.11's.

A binding of a builtin's name is used, not the builtin

Sigil compiled a call to car, not, +, list, raise, call/cc and about sixty other builtins straight to the builtin whenever the operator was spelled that way, whatever the program had bound to the name. So (let ((not (lambda (x) 42))) (not #f)) gave #t, an internal (define (car x) 99) was ignored, and ((lambda (car) (car 5)) (lambda (x) 77)) raised car: expected pair. The same happened with a library's own definitions, with names imported from a library other than (sigil core), and in the macro expander, where a parameter named like a macro was expanded as the macro: (define (g twice) (twice 3)) expanded a twice macro instead of calling its argument. The bytecode VM, native builds and WebAssembly all gave the same wrong answers. Now a binding the program introduces wins, apart from two cases involving a macro defined inside a procedure body (see Known issues), and code that doesn't rebind a builtin compiles exactly as before: every compiled module of the standard library and the CLI is byte-identical to 0.22.11's apart from the two below.

If your code relied on the old behavior, SIGIL_DEBUG_PRIMITIVE_SHADOW=1 prints each call to a builtin's name that now goes to your own binding, with the reason. Two places in Sigil itself did: the MCP server's project overview used its own keyword? from before keywords were their own type, so it never showed a project's name, description, dependencies or packages (it does now), and (sigil test) had its own list? that never ran.

Local bindings named like macro keywords

Three older bugs in the same area are fixed. A macro whose expansion uses when failed with syntax-rules: no matching pattern when the caller had a local named when, as in (let ((when 1)) (m)): syntax-rules now ignores the keyword position of a pattern, as R7RS says. After (let () (define foo ...) ...), a later foo in the same procedure read the let's variable and failed with call: not a procedure; it now refers to whatever foo means outside the let. And with-exception-handler passed as a value (through apply, or bound to another name) now installs a handler that raise and raise-continuable reach, exactly like a direct call.

Release builds compile optimized C by default

A config that leaves out optimize: now builds at level 2 when it is a release config: one named release, release-static, prerelease, prerelease-static or web, or one that says debug?: #f. Before, it compiled its C without -O and skipped the optimizer: a web app's runtime came out 6.6 MB and about four times slower than with optimize: 2. Any other config that leaves it out still builds at 0, and an explicit optimize: always wins. Every release config in the projects we checked already declares optimize:, so their builds don't change.

write prints compiled procedures as #<procedure>

0.22.11 made format print a compiled procedure as #<procedure>; write and display still printed #<object 0x...>. All three agree now. To tell compiled code from bytecode, use the new (procedure-native? p): #t for a procedure compiled by the native backend or a builtin written in C, #f for a bytecode closure or anything that isn't a procedure.

sgx appears on the first upgrade

Upgrading from 0.22.10 or earlier no longer needs a second sigil cli use to create sgx: the first command the new version runs writes $SIGIL_HOME/bin/sgx (and bin/sigil, if that is missing) and says so once on stderr.

Fixes

Known issues