|
1 | 1 | ### [Unreleased] |
2 | 2 |
|
| 3 | +#### Runtime and Scoping |
| 4 | + |
3 | 5 | - **True lexical scoping for `Function` expressions**: Functions now capture |
4 | | - their defining scope at canonicalization time and create a fresh scope per |
5 | | - call, with the defining scope as parent. Free variables resolve through the |
6 | | - scope chain where the function was defined, not where it is called. This |
7 | | - matches JavaScript/Python closure semantics. Previously, the engine used |
8 | | - dynamic scoping where the calling scope could shadow defining-scope variables. |
9 | | - |
10 | | -- **BigOp scope pollution fixed**: `Sum`, `Product`, and other big operators no |
11 | | - longer leak free variables (e.g., `M`, `x`) into their local scope. Only the |
12 | | - index variable is declared in the BigOp scope; other variables are |
13 | | - auto-declared in the enclosing scope via the new `noAutoDeclare` mechanism. |
14 | | - |
15 | | -- **Closure capture for nested functions**: When a function returns another |
16 | | - function, the inner function correctly captures outer parameter values. |
17 | | - Multi-level nesting (f returning g returning h) works correctly. |
18 | | - |
19 | | -- **`EvalContext.values` removed**: Symbol values now live exclusively in |
20 | | - `BoxedValueDefinition.value`. The per-frame shadow map that was the root |
21 | | - cause of dynamic scoping has been eliminated. The `withArguments` evaluation |
22 | | - option has also been removed. |
23 | | - |
24 | | -- **`forget()` now resets values set by `assume()`**: When `assume('x = 5')` |
25 | | - auto-declares `x` with a value, `forget('x')` now correctly resets `x`'s |
26 | | - value to `undefined` (in addition to clearing assumptions). |
27 | | - |
28 | | -- **New `Mandelbrot` and `Julia` functions**: Two new built-in operators for |
29 | | - escape-time fractal computation. |
30 | | - |
31 | | - `Mandelbrot(c, maxIter)` computes the Mandelbrot set membership for a complex |
32 | | - point `c`, iterating `z → z² + c` from `z₀ = 0`. `Julia(z, c, maxIter)` |
33 | | - does the same with a user-supplied starting point `z` and parameter `c`. |
34 | | - |
35 | | - Both return a smooth-colored normalized value in `[0, 1]`: `1.0` for points |
36 | | - inside the set, and a fractional value for escaping points (using the |
37 | | - `log₂(log₂(|z|²))` formula to produce continuous gradients rather than |
38 | | - banded integer counts). The caller is responsible for mapping the scalar to a |
39 | | - color. |
40 | | - |
41 | | - Both functions evaluate in JavaScript and compile to GLSL and WGSL shaders. |
42 | | - In a fragment shader, the typical usage is: |
43 | | - |
44 | | - ```glsl |
45 | | - // uniforms: vec2 pan, float zoom, int maxIter |
46 | | - vec2 c = (fragCoord / resolution - 0.5) * zoom + pan; |
47 | | - float t = _fractal_mandelbrot(c, maxIter); |
48 | | - // map t to a color |
49 | | - ``` |
| 6 | + their defining scope and resolve free variables from that scope chain (not |
| 7 | + the call site), with a fresh child scope on each call. |
| 8 | + |
| 9 | +- **BigOp scope pollution fixed**: `Sum`, `Product`, and other big operators now |
| 10 | + only declare their index variable locally. Other names are declared in the |
| 11 | + enclosing scope via `noAutoDeclare`. |
| 12 | + |
| 13 | +- **Closure capture for nested functions**: Returned functions now correctly |
| 14 | + capture outer parameters across multiple nesting levels. |
50 | 15 |
|
51 | | -- **Parse `\mleft`/`\mright` delimiters**: These alternative delimiters from the |
52 | | - `mleftright` LaTeX package are now recognized and behave identically to |
53 | | - `\left`/`\right`. |
| 16 | +- **`EvalContext.values` removed**: Symbol values now live only in |
| 17 | + `BoxedValueDefinition.value`. The per-frame shadow map and `withArguments` |
| 18 | + option were removed. |
54 | 19 |
|
55 | | -- **Parse `\color` in math mode**: The `\color{...}` command is now recognized |
56 | | - in math mode. The color argument is consumed and discarded, allowing the |
57 | | - subsequent expression to parse normally. Previously, `\color{red}3` produced |
58 | | - an `unexpected-command` error. |
| 20 | +- **`forget()` now resets values set by `assume()`**: `forget('x')` now clears |
| 21 | + values introduced by `assume('x = ...')` (value reset to `undefined`), in |
| 22 | + addition to clearing assumptions. |
59 | 23 |
|
60 | | -- **Parse `:` and `\colon` as infix operators**: A bare `:` or `\colon` outside |
61 | | - of quantifier contexts now parses as a `Colon` infix operator, useful for type |
62 | | - annotations and mapping notation (e.g., `f:[a,b]\to\R`). The `:=` assignment |
63 | | - operator and quantifier colon syntax are unaffected. |
| 24 | +#### Expressions and Equality |
64 | 25 |
|
65 | | -- **`expand()` now returns the input expression instead of `null`**: The |
66 | | - `expand()` free function and the internal `expand()` / `expandAll()` functions |
67 | | - now return the original expression when no expansion is possible, instead of |
68 | | - `null`. This eliminates the `expand(x) ?? x` pattern at every call site. |
| 26 | +- **`expand()` now returns the input expression instead of `null`**: Both the |
| 27 | + free function and internal `expand()`/`expandAll()` now return the original |
| 28 | + expression when no expansion is possible. |
69 | 29 |
|
70 | | -- **New `.toRational()` method**: Returns `[numerator, denominator]` as plain |
71 | | - integers for rational expressions, or `null` otherwise. Works on number |
72 | | - literals, `Divide`/`Rational` function expressions, and integers. |
| 30 | +- **New `.toRational()` method**: Returns `[numerator, denominator]` integers |
| 31 | + for rational expressions, or `null` otherwise. |
73 | 32 |
|
74 | | -- **New `.factors()` method**: Returns the multiplicative factors of an |
75 | | - expression as a flat array. Decomposes `Multiply` and `Negate` structurally. |
| 33 | +- **New `.factors()` method**: Returns multiplicative factors as a flat array by |
| 34 | + decomposing `Multiply` and `Negate` structurally. |
| 35 | + |
| 36 | +- **`.is()` now tries expansion**: After structural comparison, `.is()` expands |
| 37 | + both sides before numeric fallback, catching forms like `(x+1)^2` and |
| 38 | + `x^2+2x+1`. |
| 39 | + |
| 40 | +- **`.is()` is now symmetric**: `a.is(b) === b.is(a)` now holds across all |
| 41 | + expression types. |
| 42 | + |
| 43 | +#### LaTeX Parsing |
76 | 44 |
|
77 | | -- **`.is()` now tries expansion**: After the fast structural check, `.is()` |
78 | | - expands both sides (distributing products, multinomial theorem, etc.) before |
79 | | - falling back to numeric evaluation. This catches equivalences like |
80 | | - `(x+1)^2` vs `x^2+2x+1` even with free variables. |
| 45 | +- **Parse `\mleft`/`\mright` delimiters**: Alternative delimiters from the |
| 46 | + `mleftright` package are now treated like `\left`/`\right`. |
81 | 47 |
|
82 | | -- **`.is()` is now symmetric**: Previously, `expr.is(num)` and `num.is(expr)` |
83 | | - could return different results because `BoxedNumber.is()` only performed |
84 | | - structural comparison without numeric evaluation fallback. Now |
85 | | - `a.is(b) === b.is(a)` for all expression types. |
| 48 | +- **Parse `\color` in math mode**: `\color{...}` is now recognized in math mode; |
| 49 | + the color argument is consumed so the following math parses normally. |
86 | 50 |
|
87 | | -- **Parse `\dfrac`, `\tfrac`, and `\cfrac` as fractions**: These LaTeX fraction |
88 | | - variants are now recognized by the parser and produce the same MathJSON as |
89 | | - `\frac`. Previously, only `\frac` was handled. |
| 51 | +- **Parse `:` and `\colon` as infix operators**: Outside quantifier contexts, a |
| 52 | + bare `:`/`\colon` now parses as `Colon` (e.g. `f:[a,b]\to\R`), without |
| 53 | + affecting `:=` assignment or quantifier syntax. |
| 54 | + |
| 55 | +- **Parse `\dfrac`, `\tfrac`, and `\cfrac` as fractions**: These variants now |
| 56 | + parse the same as `\frac`. |
| 57 | + |
| 58 | +#### Fractals |
| 59 | + |
| 60 | +- **New `Mandelbrot` and `Julia` functions**: Added built-in escape-time fractal |
| 61 | + operators. |
| 62 | + `Mandelbrot(c, maxIter)` and `Julia(z, c, maxIter)` return a smooth, |
| 63 | + normalized value in `[0, 1]` (`1` for interior points, fractional for |
| 64 | + escaping points via `log₂(log₂(|z|²))` smoothing). |
| 65 | + Both evaluate in JavaScript and compile to GLSL/WGSL. |
90 | 66 |
|
91 | 67 | ### 0.52.1 _2026-02-19_ |
92 | 68 |
|
93 | | -- To check if a value is an exact number literal, you can now use |
94 | | - `isNumber(expr) && expr.isExact`. |
95 | | - |
96 | | -- When using the `raw` canonical form, preserve negation, i.e. `x-1` will now |
97 | | - parse as `["Subtract", "x", "1"]` rather than `["Add", "x", -1]`. |
98 | | - |
99 | | -- **Fix `;\;` parsing in semicolon blocks**: Semicolons followed by LaTeX visual |
100 | | - spacing commands (`\;`, `\,`, `\quad`, etc.) no longer produce spurious |
101 | | - `Nothing` nodes in the parse tree. Previously, `a \coloneq x^2;\; (a+1)` would |
102 | | - include a `Nothing` operand in the Block, making `isValid` return `false` and |
103 | | - causing compilation to fail. The parser now skips visual spacing after |
104 | | - semicolon separators. |
105 | | - |
106 | | -- **Fix `\text{if}` parsing with `\;` spacing**: The |
107 | | - `\text{if}\; x \geq 0 \;\text{then}\; 1 \;\text{else}\; 0` pattern now parses |
108 | | - correctly as an `If` expression. Previously, `\;` before `\text{then}` or |
109 | | - `\text{else}` prevented keyword detection, producing a `Tuple` instead. |
110 | | - |
111 | | -- **Block serializer uses `; ` separator**: The Block serializer now emits `; ` |
112 | | - instead of `;\; ` between statements, preventing round-trip serialization from |
113 | | - reintroducing the `\;` parsing issue. |
114 | | - |
115 | | -- **Block compiler filters `Nothing` operands**: As defense-in-depth, the Block |
116 | | - compiler now filters out `Nothing` symbols and empty compilation results |
117 | | - before generating the block IIFE. |
118 | | - |
119 | | -- **Subscripted variable names in blocks**: Subscripted identifiers like `r_1` |
120 | | - are now treated as compound symbols (not `Subscript` expressions) when the |
121 | | - base is not a known collection. This means `r_1 \coloneq x^2; \frac{1}{r_1}` |
122 | | - correctly declares and assigns to a local variable named `r_1`. |
123 | | - |
124 | | -- **Selective GLSL interval preamble**: The interval-GLSL compilation target now |
125 | | - emits only the preamble functions actually used by the compiled expression |
126 | | - (plus their transitive dependencies), instead of the full ~29KB library. |
127 | | - Typical preambles are 60–80% smaller. |
128 | | - |
129 | | -- **Selective WGSL interval preamble**: The interval-WGSL compilation target now |
130 | | - also emits only the preamble functions actually used by the compiled |
131 | | - expression, matching the GLSL target optimization. |
132 | | - |
133 | | -- **Fix recursive GLSL gamma function**: The `_gpu_gamma()` preamble in the GPU |
134 | | - and interval-GLSL compilation targets used recursion for the reflection |
135 | | - formula (z < 0.5), which is illegal in GLSL. Replaced with a non-recursive |
136 | | - implementation that inlines the Lanczos approximation for both branches. |
137 | | - |
138 | | -- **Non-strict parser supports exponents on bare functions**: In non-strict mode |
139 | | - (`strict: false`), bare function names like `sin`, `cos`, `tan` can now |
140 | | - include an exponent before the argument list. For example, `sin^2(x)` and |
141 | | - `cos^{10}(x)` are now correctly parsed as `["Power", ["Sin", "x"], 2]`, |
142 | | - matching the behavior of their LaTeX counterparts `\sin^2(x)` and |
143 | | - `\cos^{10}(x)`. |
144 | | - |
145 | | -- **Unicode superscript and subscript digit support**: The LaTeX parser now |
146 | | - recognizes Unicode superscript digits (`⁰¹²³⁴⁵⁶⁷⁸⁹⁻`) and subscript digits |
147 | | - (`₀₁₂₃₄₅₆₇₈₉₋`), converting them to `^{...}` and `_{...}` respectively. This |
148 | | - works in all parsing modes. For example, `x²` parses as `x^{2}`, `sin²(x)` as |
149 | | - `\sin^{2}(x)`, `x⁻²` as `x^{-2}`, and `x₁₂` as `x_{12}`. |
150 | | - |
151 | | -- **`.is()` now works with assigned variables**: Previously, `.is()` only |
152 | | - evaluated expressions made entirely of declared constants (like `Pi`). Now it |
153 | | - correctly evaluates any expression with no free variables, including those |
154 | | - containing variables with assigned values: |
| 69 | +#### Expressions |
155 | 70 |
|
156 | | - ```ts |
157 | | - ce.assign('v', 2); |
158 | | - ce.parse('1 + 4 / v').is(3); // true (was false before) |
159 | | - ce.parse('1 + 4 / x').is(3); // false (x is free) |
160 | | - ``` |
| 71 | +- **Exact number literal check**: Use `isNumber(expr) && expr.isExact` to test |
| 72 | + for exact numeric literals. |
161 | 73 |
|
162 | | -- **`.is()` accepts an optional `tolerance` parameter**: When provided, it |
163 | | - overrides `engine.tolerance` for the numeric comparison. This applies to both |
164 | | - evaluated expressions and literal numbers: |
| 74 | +- **`raw` form preserves subtraction**: `x-1` now parses as |
| 75 | + `["Subtract", "x", "1"]` (instead of `["Add", "x", -1]`) when using raw form. |
165 | 76 |
|
166 | | - ```ts |
167 | | - ce.parse('\\pi').is(3.14, 0.01); // true (within custom tolerance) |
168 | | - ce.parse('\\pi').is(3.14); // false (not within engine.tolerance) |
169 | | - ce.number(1e-17).is(0, 1e-16); // true (explicit tolerance on literal) |
170 | | - ce.number(1e-17).is(0); // false (no tolerance for literals) |
171 | | - ``` |
| 77 | +#### Parsing and Blocks |
| 78 | + |
| 79 | +- **Fix `;\;` parsing in semicolon blocks**: Spacing commands after semicolons |
| 80 | + (`\;`, `\,`, `\quad`, etc.) no longer create spurious `Nothing` operands. |
| 81 | + |
| 82 | +- **Fix `\text{if}` parsing with `\;` spacing**: |
| 83 | + `\text{if}\;...\;\text{then}\;...\;\text{else}\;...` now parses correctly as |
| 84 | + `If`. |
| 85 | + |
| 86 | +- **Block serializer now uses `; `**: Serialization emits `; ` (not `;\; `) to |
| 87 | + avoid reintroducing spacing-related parse issues on round-trip. |
| 88 | + |
| 89 | +- **Block compiler filters `Nothing` operands**: The Block compiler now removes |
| 90 | + `Nothing` symbols and empty compile results before generating code. |
| 91 | + |
| 92 | +- **Subscripted variable names in blocks**: Names like `r_1` are treated as |
| 93 | + compound symbols (not `Subscript`) when the base is not a known collection. |
| 94 | + |
| 95 | +- **Non-strict parser supports exponents on bare functions**: In `strict: false` |
| 96 | + mode, forms like `sin^2(x)` and `cos^{10}(x)` now parse correctly as powers. |
| 97 | + |
| 98 | +- **Unicode superscript/subscript digits supported**: Superscript and subscript |
| 99 | + Unicode digits now normalize to `^{...}` / `_{...}` in parsing. |
| 100 | + |
| 101 | +#### Compilation |
| 102 | + |
| 103 | +- **Selective GLSL interval preamble**: `interval-glsl` now emits only used |
| 104 | + helper functions (plus dependencies), typically reducing preamble size by |
| 105 | + 60-80%. |
| 106 | + |
| 107 | +- **Selective WGSL interval preamble**: `interval-wgsl` now applies the same |
| 108 | + used-only preamble strategy. |
| 109 | + |
| 110 | +- **Fix recursive GLSL gamma helper**: Replaced recursive `_gpu_gamma()` |
| 111 | + reflection logic (illegal in GLSL) with a non-recursive implementation. |
| 112 | + |
| 113 | +#### Equality |
| 114 | + |
| 115 | +- **`.is()` now works with assigned variables**: Numeric fallback now applies to |
| 116 | + expressions with no free variables, including variables with assigned values. |
| 117 | + |
| 118 | +- **`.is()` now accepts an optional `tolerance`**: A per-call tolerance can |
| 119 | + override `engine.tolerance` for numeric comparison. |
172 | 120 |
|
173 | 121 | ### 0.52.0 _2026-02-18_ |
174 | 122 |
|
@@ -978,7 +926,7 @@ ce.simplificationRules.push({ |
978 | 926 | - `Hom` now evaluates/simplifies its arguments while preserving the symbolic |
979 | 927 | `Hom(...)` form. |
980 | 928 |
|
981 | | -### LaTeX Parsing |
| 929 | +#### LaTeX Parsing |
982 | 930 |
|
983 | 931 | - **`arguments: 'implicit'` option for function dictionary entries**: Function |
984 | 932 | entries in the LaTeX dictionary can now set `arguments: 'implicit'` to accept |
|
0 commit comments