Skip to content

Commit e43bc4e

Browse files
committed
doc
1 parent cba3c3f commit e43bc4e

1 file changed

Lines changed: 102 additions & 154 deletions

File tree

CHANGELOG.md

Lines changed: 102 additions & 154 deletions
Original file line numberDiff line numberDiff line change
@@ -1,174 +1,122 @@
11
### [Unreleased]
22

3+
#### Runtime and Scoping
4+
35
- **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.
5015

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.
5419

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.
5923

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
6425

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.
6929

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.
7332

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
7644

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`.
8147

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.
8650

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.
9066

9167
### 0.52.1 _2026-02-19_
9268

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, `` 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
15570

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.
16173

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.
16576

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.
172120

173121
### 0.52.0 _2026-02-18_
174122

@@ -978,7 +926,7 @@ ce.simplificationRules.push({
978926
- `Hom` now evaluates/simplifies its arguments while preserving the symbolic
979927
`Hom(...)` form.
980928

981-
### LaTeX Parsing
929+
#### LaTeX Parsing
982930

983931
- **`arguments: 'implicit'` option for function dictionary entries**: Function
984932
entries in the LaTeX dictionary can now set `arguments: 'implicit'` to accept

0 commit comments

Comments
 (0)