Transpiler: native mutable globals support, eliminate 5 bootstrap patches

transpiler.sx: ml-mutable-globals list + ml-is-mutable-global? predicate.
Symbol reads emit !_ref, set! emits _ref :=, define emits !_ref deref.
bootstrap.py: remove all mutable globals regex fixups (strict, prim-param-types).

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
This commit is contained in:
2026-04-03 13:43:55 +00:00
parent 21ad052272
commit 1498cc2bdb
2 changed files with 36 additions and 63 deletions

View File

@@ -211,60 +211,10 @@ def compile_spec_to_ml(spec_dir: str | None = None) -> str:
parts.append(FIXUPS)
output = "\n".join(parts)
# Post-process: fix mutable globals that the transpiler can't handle.
# The transpiler emits local refs for set! targets within functions,
# but top-level globals (*strict*, *prim-param-types*) need to use
# the pre-declared refs from the preamble.
# Mutable globals (*strict*, *prim-param-types*) are now handled by
# the transpiler directly — it emits !_ref for reads, _ref := for writes.
import re
# Fix *strict*: use _strict_ref instead of immutable let rec binding
output = re.sub(
r'and _strict_ =\n \(Bool false\)',
'and _strict_ = !_strict_ref',
output,
)
# Fix set-strict!: use _strict_ref instead of local ref
output = re.sub(
r'and set_strict_b val\' =\n let _strict_ = ref Nil in \(_strict_ := val\'; Nil\)',
"and set_strict_b val' =\n _strict_ref := val'; Nil",
output,
)
# Fix *prim-param-types*: use _prim_param_types_ref
output = re.sub(
r'and _prim_param_types_ =\n Nil',
'and _prim_param_types_ = !_prim_param_types_ref',
output,
)
# Fix set-prim-param-types!: use _prim_param_types_ref
output = re.sub(
r'and set_prim_param_types_b types =\n let _prim_param_types_ = ref Nil in \(_prim_param_types_ := types; Nil\)',
"and set_prim_param_types_b types =\n _prim_param_types_ref := types; Nil",
output,
)
# Fix all runtime reads of _strict_ and _prim_param_types_ to deref
# the mutable refs instead of using the stale let-rec bindings.
# This is needed because let-rec value bindings capture initial values.
# Use regex with word boundary to avoid replacing _strict_ref with
# !_strict_refref.
def fix_mutable_reads(text):
lines = text.split('\n')
fixed = []
for line in lines:
# Skip the definition lines
stripped = line.strip()
if stripped.startswith('and _strict_ =') or stripped.startswith('and _prim_param_types_ ='):
fixed.append(line)
continue
# Replace _strict_ as a standalone identifier only (not inside
# other names like set_strict_b). Match when preceded by space,
# paren, or start-of-line, and followed by space, paren, or ;.
line = re.sub(r'(?<=[ (])_strict_(?=[ );])', '!_strict_ref', line)
line = re.sub(r'(?<=[ (])_prim_param_types_(?=[ );])', '!_prim_param_types_ref', line)
fixed.append(line)
return '\n'.join(fixed)
output = fix_mutable_reads(output)
# Fix cek_call: the spec passes (make-env) as the env arg to
# continue_with_call, but the transpiler evaluates make-env at
# transpile time (it's a primitive), producing Dict instead of Env.