Sigil 0.22.12
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.
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
- Importing a macro through
only,renameorprefix, or through a renamed re-export, left it unbound under its new name: after(import (rename (only (demo source) allowed) (allowed selected))),(selected ...)failed withunbound variable 'selected'. Imported macros now follow the same selectors as values.
Known issues
- A macro that defines a builtin's name inside a top-level
(let () ...)and calls it still gets the builtin, and the definition leaks into the module (0.22.11 too). - In a built app (
sigil build, native or WebAssembly), a macro defined inside a procedure body whose template names one of that body's own defines fails when it runs, whatever the name:macro template refers to local 'helper', which a compiled app can't capture yet (a macro defined inside a body). 0.22.11 failed the same way, asunbound variable 'str'.sigil run,evaland the REPL call the local. - A macro defined inside a procedure body whose template names a
let-bound variable or a parameter with a builtin's name still gets the builtin: in(let ((car (lambda (x) 'mine))) (define-syntax m (syntax-rules () ((_ x) (car x)))) (m '(1 2))),(m '(1 2))gives1. - A body holds at most 64 local names for the purpose of telling a local from a macro: the 65th and later parameter or definition named like a macro is still expanded as the macro.
(do ((x 1) (i 0 (+ i 1))) ...), adovariable with no step, fails withsyntax-rules: no matching pattern.