All notable changes to this project should be documented in this file.
The format is inspired by Keep a Changelog, and this project follows SemVer.
__ml_list_alloc(n)host→wasm list allocator. The runtime exposed__ml_str_alloc(host→wasm string bytes) and__ml_list_count/__ml_list_item(read-only wasm→host), but there was no way for a JS host to pass a numeric array into a list-parameter function — callers had to repurpose__ml_str_allocand hand-write the list layout.__ml_list_alloc(n)is the missing dual: it reserves a header f64 (count) plusnf64 items, writes the count, and returns the 8-aligned base as the list ABI pointer. The host fills items through a zero-copyFloat64Arrayview atbase+8and passes the pointer straight to a list-parameter export. Regression:test_ml_list_alloc_host_to_wasm_array.
- Heap allocations are now 8-aligned.
$ml_allocrounds the bump cursor up to 8 bytes before carving a fresh block, so every allocation (and therefore every list header/items region) is 8-aligned. Previously a__ml_str_alloc(len)of a non-(8k−4)length could leave the cursor odd, which forced JS hosts to read returned lists withDataViewinstead of zero-copyFloat64Arrayviews. Now both directions are zero-copy-safe. Class-recycled blocks stay aligned since they originate from the aligned bump. Regression:test_ml_alloc_returns_8_aligned_after_str_alloc.
math.logaccuracy corrected. Earlier notes conservatively cited ~1e-6; the implementation actually measures ~2e-10 max absolute/relative error acrosslog(0.5)→log(1e10)(verified via wasmtime vs CPythonmath.log).- Rounding semantics clarified.
round(x)lowers tof64.nearest, i.e. round-half-to-even (Python / IEEE-754 default) —round(2.5) == 2. This differs from JavaScript'sMath.round, which is round-half-up toward +∞ (Math.round(2.5) === 3). Code porting JSMath.roundshould usetrunc(x + 0.5)(bit-identical toMath.roundfor finite values), notround. No new builtin is added: a second JS-flavoured round would be ambiguous next to the Python-semanticsround.
- A rewrite clause can now fire probabilistically, via a new
chance(p, salt)predicate alongsidewhenandneighbor_count. This adds the one ingredient the deterministic rewrite/rate rules lacked — randomness — so stochastic programs (Eden growth, percolation, noisy cellular automata) are expressible for the first time. A clause carryingchance(p)matches only a fractionpof the time. - The randomness is deterministic and byte-identical across runtimes. There
is no PRNG state and no seed plumbing: a roll is a pure hash of
(locus, step, salt)(_hash01, a MurmurHash3-style 32-bit mix built from exact& 0xFFFFFFFF/Math.imularithmetic so CPython and the JS port agree to the bit). A stochastic trajectory is therefore a reproducible function of the manifest, and two clauses decorrelate via differentsalt. The step index is threaded throughstep/runto the predicate so randomness varies over time; a deterministic rule ignores it and steps exactly as before. - Mirrored in the JS port (
docs/browser/process-dynamics/process_core.js):hash01+ thechancebranch inclauseMatches, withstepIndexthreaded through every rewrite stepper.tests/process_core_js_test.pygrows an Eden cluster under Node and asserts it matches Python cell-for-cell across the run. - Eden-growth example, authored in
.multi(en + fr).examples/eden_growth.{multi,fr.multi}grow a stochastic accretion cluster with a rough fractal boundary from a single seed — the first stochastic program on any axis. Newtests/process_stochastic_test.pycovers the hash (range, purity, uniformity, per-input sensitivity), the predicate's gating (p=0never fires,p=1always,~pon average, salt decorrelation), and the example (monotone growth, determinism, Tier 2, en≡fr).
rate_rulegains aconstantsource/sink andproducts(nonlinear monomials) term. A field's time-derivative was previously a linear combination of the locus's own fields (self) and the mean over its neighbours (neighbor_mean) — enough for diffusion and decay, but not for any reaction. A rate now sums four optional contributions in a fixed order:self,neighbor_mean, a scalarconstant, andproducts— a list of monomials{"coeff": c, "factors": [f, ...]}each contributingc · own[f0] · own[f1] · …(factors repeat to raise a power). This reaches the nonlinear continuous systems the linear slice could not express: Gray-Scott, Lotka-Volterra predator-prey, FitzHugh-Nagumo. The terms are pure data — the engine still names no system. A rule that omitsconstantandproductsintegrates byte-for-byte as before (they are appended only when present, so no existing manifest drifts).- Bit-identical across runtimes. The new terms are mirrored in the JS port
(
docs/browser/process-dynamics/process_core.js), folding the monomial in the same factor order so the Python and JS continuous steppers stay byte-identical.tests/process_core_js_test.pyruns Gray-Scott under Node and asserts both reagent fields agree float-for-float across the trajectory. - Gray-Scott example, authored in
.multi(en + fr).examples/gray_scott.{multi,fr.multi}express the canonical pattern-forming reactionU + 2V -> 3Vend-to-end through the language — the first nonlinear program on the continuous axis. Both lower to a byte-identical core; new engine tests intests/process_continuous_test.pycover the constant term, product monomials, term composition order, and the autocatalysis igniting while the field stays bounded (still Tier 1).
semantic-core-v1= ⟨State, Topology, Rule, Schedule⟩ with one rewrite meta-primitive. Where v0 described a static scene, v1 describes how a scene evolves. The stepper is modality-free and has bit-identical Python and JS implementations.semantic-core-v0remains frozen; a v0→v1 migration projects any v0 core into a Tier-0 v1 core that round-trips the original entities exactly (process_migration.py,process_static_projection.py).- Process programs are authored in
.multi, not Python. A program defines aprocessvariable and is compiled with the newmultilingual process-buildCLI — the v1 layer is a first-class citizen of the multilingual language rather than a set of host scripts. Builtin aliases for the process vocabulary ship in all 16 languages. - Rule surface syntax is a free-function combinator DSL.
when/neighbor_count/becomes/fallback/symbol/clause/rewritelower torewrite_rule(RULE_REWRITE). A second rule kind,rate_rule(RULE_RATE), carries derivatives-as-data for continuous dynamics. - Three topologies and three schedules. Topologies: implicit grid
(spatial), open-population, and
graph_topology(arbitrary node/edge loci via a generalized_locus_key). Schedules: synchronous,asynchronous_schedule(sequential in-place, identity-on-no-match), andcontinuous_schedule(dt)which integratesrate_rules with an explicit Euler step (next = field + dt·rate). The continuous path uses a naive left-fold sum so CPython and JS stay bit-identical (the compensated-sumpath diverged). This completes the schedule axis. - Tier 0–4 capability classifier.
tierOf/TIER_NAMES(Python and JS) grade a process by expressive power. Tier is orthogonal to invertibility: the SIRnetwork_epidemiccore classifies as Tier 4 yetprocess_graph_projection.pyround-trips every node exactly. The tier line is shown on all five browser dynamics pages. - Example process programs (en + fr), each end-to-end through the v1
path with a value-aware projection and a browser page. Conway's Game of
Life (
game_of_life.multi), Lindenmayer L-systems (lindenmayer.multi, generative sequence rewriting), a cyclic-dominance ecosystem (ecosystem.multi, heterogeneous state + async schedule), a network SIR epidemic (network_epidemic.multi, graph topology), and heat diffusion (diffusion.multi, continuous-time, mass-conserved). Browser runtimes:docs/browser/process-dynamics/Life, L-system, ecosystem, graph, and diffusion pages. - A checked-in golden
semantic-corefixture plus value-aware field, sequence, and graph projections (process_field_projection.py,process_sequence_projection.py,process_graph_projection.py) and per-projection capability contracts (process_capabilities.py).
- Five peer modality projections of the same semantic core. The
polymodal architecture introduced after v0.7.0 now has five live
peers —
linear(1D timeline),spatial(2D canvas, unified under the semantic core; previously grandfathered),volumetric(3D scene rendered on canvas with a hand-rolled rotation and perspective, no WebGL/3D dependency),sonic(WebAudio), andmidi(discrete events). Each one ships with amultilingual <name>-buildCLI, aprogram.<name>.jsonmanifest, a browser runtime underdocs/browser/<name>-dynamics/, and a checked-inontology.jsonsidecar generated by the newmultilingual ontology-exportCLI so the JS and Python sides cannot drift. The cross-modal equivalence test intests/polymodal_equivalence_test.pynow asserts entity count, opcode ordering, intensity/phase/channel preservation, and ontology-name agreement across all five peers. - Containment relations derived at the semantic core. Previously
the
relationsfield onsemantic-core-v0was always empty. Eachcontain-opcode entity on channel C now contains the non-containentities on channel C, with the relation flowing through every projection so peers cannot silently disagree on structure. Coupling and temporal ordering are intentionally deferred until the source language gains explicit syntax. - Sonic round-trip — Python inverse and browser microphone capture.
multilingualprogramming/codegen/sonic_capture.pyreconstructs a semantic core from label-strippedObservedVoicerecords and is exercised bySonicRoundTripTestSuite. In the browser,docs/browser/sonic-dynamics/sonic_capture.jsmirrors the Python inverse (fetching the shared ontology sidecar) andmicrophone_capture.jsproducesObservedVoicerecords from real audio with a pure-vanilla DSP pipeline (spectral-flux onset detection, FFT peak picking, harmonic-ratio waveform classification, RMS envelope classification, pentatonic snapping). The sonic runtime gains a "capture" button that runs the pipeline and renders the recovered manifest. - Dimensionality guard vocabularies. Linear glyph names, spatial
shape names, and volumetric primitive names are deliberately
disjoint (no
ringin linear, nodotin volumetric, etc.), and tests reject accidental reuse. MIDI roles (note,drum,cc,program,bus) form a distinct event-oriented taxonomy.
math.atandouble range reduction + 12-term Taylor. The previous implementation used a 6-term Taylor with only the1/xreduction, soatan(1)returned ~0.747 vs the trueπ/4 ≈ 0.7854(~5% error, the worst case on|x| ≤ 1). Now adds a second reductionatan(x) = π/4 + atan((x − 1)/(x + 1))forx > tan(π/8) ≈ 0.4142, so the Taylor argument is always in[-tan(π/8), tan(π/8)]. Extended to 12 terms (degrees 1..23) — truncation error~ x^25/25 < 5e-12at the boundary. Verified ~10 decimal places acrossatan(0),atan(0.5),atan(1),atan(10),atan(1000), and negative arguments.math.atan2inherits the improvement directly. Regression test:tests/wat_generator_wasm_execution_test.py::test_math_atan_range_reduction_precision.
imul32(a, b),iadd32(a, b),shr_u32(a, k),u32_to_f64(x). The existing bitwise operators& | ^ << >>use signed-i32 conversions but stop short of multiplication (*is alwaysf64.mul) and unsigned right shift (>>is sign-extending). These builtins fill the gap :imul32mirrorsMath.imul(i32.mul with wraparound),iadd32adds with wraparound using an i64 intermediate (so the sum can exceed[-2^31, 2^31)before wrapping),shr_u32mirrors>>>(zero-fill right shift), andu32_to_f64reinterprets a signed-i32-as-f64 as unsigned-i32-as-f64 (maps negatives to[2^31, 2^32)). Required to port 32-bit hashes (FNV, CRC32) and PRNGs (mulberry32) to.multisource. Regression :test_imul32_iadd32_shr_u32_match_js_semantics.pow_f64integer-exponent sign bug fixed. The neg-flag forpow(base, exp)with integerexpwas inverted (neg = 0 < expinstead ofexp < 0), sopow(10, -3)returned 1000 andpow(10, 3)returned 0.001 — exactly swapped. The bug stayed hidden because the fractales code only ever used**with positive or[0, 1]exponents (interpolation). Exposed byformatter_exponentielin fractales partage (mantissa =x / pow(10, e)). Regression :test_pow_f64_negative_integer_exponent.
-
Multi-value returns (
retour (a, b, …)). WAT functions can now return N≥2 values on the stack via a(result f64 f64 …)signature.retour (a, b)pushes both values thenreturn;soit x, y = f(…)destructures vialocal.setin reverse order. Detected automatically per-function via_multi_value_return_arity(allretourstatements must agree on arity). Eliminates the_x/_yfunction-pair pattern across fractales (transforms, landmarks, attractors). Regression:test_multi_value_returns_tuple. -
String concat on FString/CallExpr RHS. Previously
s + f"…"ands + func_call()raisedUnsupported string expression for WAT concat, forcing a temp-local refactor. Now_emit_string_value_with_lenfalls through to$__last_str_len(set by both f-string evaluation and string-returning calls — and by$__str_concatitself, so chains of arbitrary length work). Also fixed_gen_string_len_exprBinaryOp recursion to bail (and rollback emitted instructions) if a child returns False. Regression:test_string_concat_rhs_fstring_and_call. -
pow_f64general real exponents. Previously returned NaN for any non-integer exponent outside{0, 0.5, 1, -0.5}. Now falls back toexp(b · ln(a))forbase > 0; negative base with non-integer exp still returns NaN (no real value). Precision is bounded bymath.exp/math.log(math.logmeasures ~2e-10; see its entry below). Regression:test_pow_f64_general_real_exponents. -
math.exprange reduction. Previously a 10-term Taylor applied directly tox(no reduction) — ~5% off at|x|=5, divergent at|x|>20. Now usesk = round(x/ln2), reduces tor ∈ [-ln2/2, ln2/2], applies the same Taylor onr, and multiplies by2^kvia IEEE-754 bit-pattern (exponent field =1023 + k, mantissa 0). Accurate to ~1e-15 across the practical range. Enables thepow_f64general case above. -
format_fixed(v, n)runtime builtin. Wheref"{v:.Nf}"requires N at compile time,format_fixed(v, n)accepts N at runtime (clamped to[0, 9]). Internally dispatches to the existing$__fmt_fixedN_tmpstrhelpers via an if/else cascade in$__fmt_fixed_dyn. Eliminates theformatter_fixe_2/3/5/6pattern (4 near-identical functions) in fractales. Registered inBUILTIN_STRING_RETURNERSsor = format_fixed(...)tracksras a string. Regression:test_format_fixed_dynamic_n. -
List ABI helpers
__ml_list_count(ptr),__ml_list_item(ptr, i). Exported runtime helpers that expose the heap-backed list layout (header f64 atptr+0= count, items atptr+8,ptr+16, …) to host callers. Eliminates thebase + 8 + 8 * (2 + 2 * k)magic-offset reads scattered across fractales JS code. Regression:test_ml_list_count_and_item_helpers. -
Architectural fix: orchestrator unions state instead of resetting.
_sequence_func_namesand_string_return_funcsare now seeded fromBUILTIN_LIST_RETURNERS/BUILTIN_STRING_RETURNERS(frozensets inwat_generator_support) — single source of truth. Adding a new builtin that returns a list/string requires editing ONE constant, not also patching the orchestrator's reset dict. Removes the dual-init smell I introduced when addingsimd_mandelbrot_pairin the prior session.
The following items in the section-C roadmap require type information that isn't yet tracked through expressions, and so are deferred to a future typed-IR pass :
- B2 —
v128/f64x2source type. A real SIMD type with operators+ - * < lelowering tof64x2.*. Currently scoped to one builtin (simd_mandelbrot_pair). Needs operand-type dispatch in_emit_numeric_binop. - B3 — i32 wraparound on
*and+when bitwise-shaped. Would eliminateimul32/iadd32builtins. Needs operand "shape" tracking. - B6 (full) — module-grouped manifest. Manifest currently emits a flat
function list. Grouping by source module name would let fractales drop
the hand-coded
wasmMetaPanelsrebundling inrenderer.js. Needs per-function module attribution through the IR.
simd_mandelbrot_pair(cx0, cy0, cx1, cy1, max_iter)builtin. First WebAssembly SIMD-using helper in the WAT backend. Iterates two Mandelbrot pixels in parallel viaf64x2.mul/add/sub,f64x2.le,i64x2.add, andv128.bitselect(escaped lanes are frozen). Returns a heap-allocated list pointer[iter0, iter1](multilingual list convention :f64length header atptr+0, items atptr+8, ptr+16). The builtin is pre-registered in_sequence_func_namesso the caller'sr = simd_mandelbrot_pair(...)registersras a tracked list pointer (enablesr[0],r[1]). Verified bit-equal (±1 iteration) to scalar across cardioid/bulb/escape cases viatest_simd_mandelbrot_pair_matches_scalar. On x86-64 (SSE2) / ARM64 (NEON) hosts, yields ~2× speedup on the inner loop. No new v128 type at the source level — this is a single-purpose builtin, scoped narrowly to keep the language surface stable.
math.logmantissa range reduction. The atanh series was applied directly tox, solog(10)returned ~2.255 instead of 2.302585 (~2% error for arguments far from 1). Now uses IEEE-754 bit manipulation (i64.reinterpret_f64) to splitx = m·2^e, reducesmfurther to[sqrt(0.5), sqrt(2)), and computeslog(m) + e·log(2). The atanh argument stays in[-0.172, 0.172]so the 5-term series converges to ~2.2e-8. Verified to ~2e-10 max absolute/relative error acrosslog(0.5)→log(1e10)(measured via wasmtime against CPythonmath.log; earlier notes conservatively cited ~1e-6).
- User functions returning lists are now tracked at the call site. A function whose body
returns a list literal (
retour [a, b]), a list-repeat (retour [0]*n), a tracked-list local, or another list-returning call is detected by_returns_list_likeand registered in_sequence_func_names. The caller'sx = func(...)therefore makesxa tracked list, sox[i]indexing lowers correctly (previously fell back tof64.const 0 ;; unsupported: IndexAccess on non-list). Unblocks pure-.multiDekker arithmetic (two_sum/two_productreturning[hi, lo]) and other helpers that return small fixed-shape lists.
__ml_str_alloc(len)exported (companion to__ml_str_len): JS calls this to allocate a length-prefixed string buffer for host→wasm string passing. Writes the byte length as a 4-byte header and returns the pointer to the bytes (header atptr-4). Used by the fractales L-système main-canvas migration to pass axiom/rules as real string args.
- Length-prefixed string parameters (cross-function string passing): string values carry no
length in their f64 pointer, so passing a string as a function argument previously lost its byte
length in the callee. Parameters annotated as strings (
s: str/s: chaîne, normalized to the identifierstr) are now passed as length-prefixed buffers: the caller copies the argument via the new$__str_make_headeredhelper, which writes the byte length as a 4-byte header atptr - 4, and the callee recovers it into a tracked<name>_strlenlocal at its prologue. This makeslen(s), indexing (s[i]), slicing (s[a:b]), char comparison (s[i] == "F"), and concatenation work on a string received as an argument — enabling multi-function string APIs. String-annotated parameters are also excluded from list-like inference sos[i]lowers as a string subscript rather than a stride-8 list load. Data offset0is now reserved (8 bytes) so no interned string can alias the null/None sentinel pointer.$__str_concatnow maintains$__last_str_len = len1 + len2, so concatenation results are self-describing for callers. - String content equality (
==/!=): comparisons where both operands are string-valued now lower to a new$__str_eqWAT helper that compares UTF-8 byte ranges, instead of comparing heap pointers. This makess[i] == "F",slice == literal, and string-valued method results compare by content. Variables bound to a string subscript/slice/method result (ch = s[i]) now retain string-length tracking, so equality on them compares content too. str(x)number-to-string conversion:str(42)→"42",str(3.14)→"3.14"via new$__str_from_f64WAT helper. Correctly formats integers without a decimal point and floats with up to 6 significant decimal digits (trailing zeros trimmed). String and string-variable arguments pass through unchanged.- F-string numeric interpolation:
f"{x}"wherexis a numericf64variable now calls$__str_from_f64, producing correct float output (f"{3.14}"→"3.14"rather than"3"). - String method
.upper()/.lower(): ASCII case conversion, returns heap-allocated copy. - String method
.startswith(prefix)/.endswith(suffix): Returns0.0/1.0. - String method
.count(needle): Counts non-overlapping occurrences; returns f64. - String method
.replace(old, new): Replaces all occurrences; returns heap-allocated copy. str()recognized as string-valued:_is_string_valueupdated sostr(x)composes correctly inside f-strings and string concatenation.
math.sin/math.cos/math.tan: Horner-polynomial approximations (6-term).math.exp: 10-term Horner polynomial for e^x.math.log/math.log2/math.log10: atanh-series natural log; scaled for base 2/10.math.atan/math.atan2: 6-term series with |x|>1 identity; quadrant-adjusted atan2.math.trunc/math.hypot/math.degrees/math.radians: Inline WAT lowering.math.pi/math.e/math.tau/math.inf/math.nan: Emitted asf64.constliterals.
ord(s)builtin: returns the first UTF-8 byte of a string as anf64(i32.load8_uat the string pointer). Enables storing characters as numeric codes — e.g. on anf64stack for iterative/DFS string algorithms.
- Runtime-sized list repeat
[elem] * n: a single-element list literal multiplied by a runtime count now allocates ann-length list (layout[length_f64, elem0, ...]) filled with the repeated element, instead of falling through to numeric multiplication. The result is tracked as a list, sobuf[i] = ...,buf[i], andlen(buf)work — enabling O(n) buffer fills (e.g. two-pass count-then-write) rather than O(n²)appendchains under the bump allocator. Recognised in either operand order ([0.0] * norn * [0.0]).
list.append(x):$__list_appendallocates a new block withcount+1slots, copies existing data, appends the element, and updates the local variable.list.pop():$__list_popdecrements the count in-place and returns the last element; works in both statement and expression contexts.list.extend(other):$__list_extendmerges two lists into a new heap block.list(existing_list): Produces a shallow copy when called with an existing list local.
enumerate(lst)inforloops:for i, x in enumerate(lst)lowers to a counted list loop that unpacks the two-element tuple target(i, x)via_emit_sequence_len_setup/_emit_sequence_value_load.list(map(fn, lst)): Applies a known WAT function to every element of a list local; produces a new list of the same length.list(filter(fn, lst)): Keeps elements wherefnreturns a truthy value; count updated after the loop.
dict.values(): Returns the dict pointer itself (dicts are stored as f64 value lists).dict.keys(): Allocates a new list of interned string pointers for each compile-time key.dict.items(): Allocates an outer list of 2-element[key_ptr, val]tuple pairs.dict.get(key)/dict.get(key, default): Compile-time string-literal key lookup; returns the element f64, the default expression, or0.0if the key is absent.
isinstance(obj, ClassName): Emits a type-tag check atobj_ptr - 8against the compile-time class ID from_class_ids. Returns1.0(true) or0.0(false) as f64.- OOP state reset fix:
_static_method_names,_property_getters,_class_ids, and_dispatch_func_namesare now reset betweengenerate()calls, eliminating stale OOP state that could corrupt subsequent compilations in the same generator instance.
dom_on(handle, event, callback): Registers a WAT function-table callback for a DOM event. The generated module exports__dom_dispatch(idx)which JS calls on the event;ml_dom_onhost import added to the DOM bridge.
json.dumps(list): Encodes a tracked f64 list as a JSON array string ([n1,n2,...]) using a new$__json_encode_listWAT helper.
yield from range(n): Now materializes correctly via Shape 1b in_simple_generator_spec.
math.sin/math.cosreturned wrong values (two bugs): (1) both helpers began with an uncompensatedx += piphase shift, so they computedsin(x+pi) = -sin(x)andcos(x+pi) = -cos(x)— e.g.math.cos(0.0)returned-1.0; (2) range reduction usedfloor(x/2pi)(reducing to[0, 2pi)), so angles in(3pi/2, 2pi)and negative angles got a spurious double reflection with the wrong sign — e.g.math.cos(-60deg)returned-0.5instead of+0.5. Fixed by removing the phase shift and reducing to[-pi, pi]viafloor(x/2pi + 0.5)so a single reflection suffices. Nowcos(0)=1,sin(pi/2)≈1,cos(4pi)=1,cos(-60deg)=cos(300deg)=0.5,cos(240deg)=-0.5.math.tan(defined assin/cos) is corrected transitively.- F-string float formatting: Default
{x}interpolation previously truncated floats to their integer part; now delegates to$__str_from_f64for correct output. math.atanWAT stack error: The conditional negation insideifwithout a declared result type caused a WASM validation failure; fixed by saving the result to(local $r f64).$__json_encode_listcopy-index clobbering: Inner copy loop used$i(element index) as its write pointer, corrupting output after the first element; fixed with a separate$ci.
- WAT/WASM OOP object model: Stateful classes (those with
self.attr = …assignments) now use a linear-memory bump allocator in generated WAT. Each field is anf64stored at a compile-time-computed byte offset. Object pointers are carried asf64values and converted at field-access sites viai32.trunc_f64_u/f64.convert_i32_u. - Heap-pointer global:
(global $__heap_ptr (mut i32) …)is emitted only when at least one stateful class is present;HEAP_BASEis aligned to 8 bytes after the string data section. self.attrstore/load lowering: Attribute writes (self.x = val) compile tof64.store; attribute reads (self.x) compile tof64.load. Compound assignments (self.x += delta) use a temporaryf64local to avoid address recomputation issues.- External
obj.attrreads:obj.attroutside the class body lowers tof64.loadwhen the variable's class is statically known from local assignments. - Stateful instance method calls: Instance method calls (
obj.method(…)) pass the actual heap address asself(asf64) for stateful classes; stateless classes keep the oldf64.const 0behavior. - Inheritance and method resolution in WAT/WASM: Subclass hierarchies are now resolved at WAT-emit time; inherited methods are dispatched to the correct parent-class WAT export.
withstatement lowering in WAT: Context-manager blocks (with expr as var) lower to WAT with enter/exit call sequences.try/exceptlowering in WAT: Try-except blocks compile to WAT control-flow blocks, providing structured exception handling in the WASM backend.lambdalowering in WAT: Lambda expressions lower to anonymous WAT functions with captured parameters forwarded as explicit arguments.match/caselowering in WAT: Structural pattern-matching statements lower to WATif/elsechains using equality comparisons.async/awaitlowering in WAT: Async function definitions andawaitexpressions lower to synchronous WAT equivalents with stub scheduling hooks.@propertysetter/getter in WAT: Property descriptors (@property,@x.setter) lower to distinct WAT getter/setter exports following the established class-method mangling scheme.- Bytes literals in WAT:
b"..."byte-string literals are stored in the WAT data section and referenced by pointer/length pairs. - WAT generation organized by themes: The WAT backend code is now split into focused modules by language construct theme (control flow, OOP, literals, builtins) for maintainability.
TupleLiteralcode generation fix: Tuple literals now wrap generated elements in parentheses, producing correctly parenthesized WAT output.- Updated builtin aliases: Expanded localized builtin alias coverage across supported languages.
- New WAT OOP tests in
tests/wat_generator_test.py:WATOopObjectModelTestSuite— 6 WAT pattern tests (no wasmtime required).- 3 new execution tests in
WATClassWasmExecutionTestSuite: single-field counter get, increment-then-get, and two independent instances.
docs/wat_oop_model.md: Reference document covering the object model design, memory layout, field layout rules, constructor sequence, store/load patterns, limitations, and a full end-to-end WAT example.AGENTS.md: Agent guidance document for AI-assisted development workflows in this repository.
- Stateless classes (no
self.attrassignments) are unaffected and keep thef64.const 0self path — no existing tests are broken. - WAT backend source split into themed sub-modules for clarity and maintainability.
TupleLiteralcode generation now wraps output in parentheses (previously emitted bare elements).- Various WAT backend correctness fixes surfaced during gap-filling and test stabilization.
- Update documentation
- Class lowering in WAT backend: Top-level
ClassDefmethods now lower to standalone WAT exports with deterministic mangled names. - Constructor and method call lowering:
ClassName(...)lowers to class__init__in WAT with implicitselfhandling.Class.method(...)lowers to mangled class method exports.obj = Class(...); obj.method(...)lowers via lightweight local class-type tracking.
- Executable WASM validation for complete features:
- New test
tests/complete_features_wasm_execution_test.pycompiles everyexamples/complete_features_*.multifrom WAT to binary WASM, materializes.wat/.wasmartifacts, instantiates modules, and executes__main.
- New test
- CI workflow gate for complete-feature WASM execution:
.github/workflows/wasm-backends-test.ymlnow runs the new complete-feature WAT/WASM execution test on primary WASM jobs.
- WAT symbol emission now Unicode-safe:
- Function, parameter, and local identifiers are sanitized to valid WAT symbols while preserving original export names.
- Fixes WAT→WASM compilation failures for non-Latin identifiers in multilingual examples.
- WASM execution test runtime bounded:
- Added Wasmtime fuel metering in complete-feature execution tests to prevent long CI hangs while still validating instantiation and execution.
- WAT/WASM class regressions:
- Fixed incorrect argument mapping for implicit
selfin constructor/instance lowering that could leave stack values and produce invalid WASM.
- Fixed incorrect argument mapping for implicit
- Tooling quality:
- Updated test/config and corpus fixtures to remove warning noise and stabilize CI signal.
- Release docs:
- Documented PyPI file-name immutability and the correct recovery path for
HTTP 400 File already existsuploads.
- Documented PyPI file-name immutability and the correct recovery path for
- WATCodeGenerator: New backend compiling the multilingual AST directly to WebAssembly Text (WAT); fully tested via
tests/complete_features_wat_test.pyacross all 17 languages. - Playground WAT/WASM tab: Interactive playground now shows generated WAT source and executes it in the browser via wabt.js + native
WebAssembly.instantiate(). - Playground Rust/Wasmtime tab: Playground shows generated Rust/Wasmtime bridge scaffold alongside the WAT view for local compilation workflows.
- REPL
:watcommand: Toggles display of generated WAT code before execution (alias:wasm). - REPL
:rustcommand: Toggles display of generated Rust/Wasmtime bridge code before execution (alias:wasmtime). - CLI
--show-wat/--show-rustflags: Startup equivalents of:watand:rustfor thereplsubcommand. - Python 3.12 feature completion: All 17
complete_features_XX.multiexample files updated with the full checklist — numeric literals (hex/octal/binary/scientific), augmented assignments, bitwise operators, chained assignment, type annotations, ternary expressions, default/variadic params, lambdas, list/dict/generator/nested/filtered comprehensions,try/except/else, exception chaining, multipleexcepthandlers,match/case/default, decorators, multiple inheritance,@staticmethod/@classmethod/@property, and docstrings. - New CLI tests:
language_completeness_cli_test.pyandoperators_cli_test.py. - WASM Backend: WebAssembly compilation target with significant performance gains on compute-intensive operations (benchmark-dependent).
- Python ↔ WASM Bridge: Type conversion and memory management for seamless interop between Python and WASM.
- Smart Backend Selector: Auto-detection and transparent routing between WASM and Python fallback execution paths.
- Python Fallback Implementations: 25+ pure Python implementations for guaranteed compatibility across all platforms.
- WASM Corpus Projects: 20 multilingual example projects (matrix operations, cryptography, image processing, JSON parsing, scientific computing) in 4 languages each.
- Comprehensive Test Suite: 33+ tests covering correctness, performance, fallback mechanisms, integration, and platform compatibility.
- PyPI Distribution Infrastructure: Complete packaging for PyPI with optional WASM dependencies and Python 3.12+ support.
- Documentation Suite: WASM architecture overview, installation guides, performance tuning, troubleshooting, and FAQ.
- CLI
multilingual run <file>.mlcorrectly handles.mlfile execution. - Python Version Support: 3.12+ (advanced features).
- Performance Profile: CPU-bound operations can execute substantially faster via WASM backend (benchmark-dependent).
- Dependency Model: WASM support now optional via
[wasm]extra; numpy support optional via[performance].
- Broken relative links in
docs/reference.mdanddocs/fr/programmation.md(../CHANGELOG.md,../USAGE.md,../examples/README.md). - Variable name clashes in Spanish (
y= AND keyword) and Danish/Swedish (i= IN keyword) comprehension examples. - Complete multilingual support for WASM infrastructure across all 17 languages.
- All 1671 tests passing, 2 skipped (WASM Rust compile, requires
rustcwasm32 target).
- Complete feature examples (
examples/complete_features_XX.multi) for all 17 supported languages. - Verified feature parity across all languages with comprehensive test coverage.
- Enhanced language coverage from 12 to 17 supported languages with complete examples.
- Test suite expanded with integrated complete feature validation for all languages.
- Zero regressions; all 85 tests passing.
- Advanced language feature support across parser/codegen/runtime tests.
- Expanded localized built-in alias coverage and compatibility fixtures.
- Python 3.12+ packaging baseline with 3.12/3.13/3.14 CI verification.
- Test module naming migrated away from milestone-oriented file names.
- Example programs refreshed to include newer feature coverage.