Skip to content

Print what the parser reads - #706

Merged
Rafael-SOWNet merged 1 commit into
ASC-Community:masterfrom
Rafael-SOWNet:fix/stringizer-round-trip
Aug 4, 2026
Merged

Rafael-SOWNet merged 1 commit into
ASC-Community:masterfrom
Rafael-SOWNet:fix/stringizer-round-trip

Conversation

@Rafael-SOWNet

Copy link
Copy Markdown
Member

Stringize's contract is that parsing what it prints gives back what was printed. Four kinds of expression broke it, and all four broke it silently — a wrong reading is still a valid expression, so nothing threw.

The one that changes a value

var e = MathS.FromString("(2 ^ 3) ^ 2");
e.Evaled                          // 64
e.Stringize()                     // "2 ^ 3 ^ 2"
e.Stringize().ToEntity().Evaled    // 512

Powers group to the right, so it is the base that needs bracketing when it is a power of its own — the mirror of the rule the left-associative operators use (Divf already does < on the left and <= on the right; Powf needs the opposite). Latexise had it right all along, which is how I knew where to look.

The three that come back as a different node

printed as came back as
lambda(x, x + 1) x -> x + 1 x implies x + 1 — -> is the implication operator
apply(lambda(x, x + 1), 2) (x -> x + 1) 2 (x implies x + 1) ^ 2
piecewise(1 provided x > 0) (1 if x > 0) 1 * if * x > 0 — if read as an undeclared variable
piecewise(1 provided x > 0, 2 provided x < 0) (1 if x > 0, 2 if x < 0) parse error

None of -> for lambda, juxtaposition for application, or if for piecewise is in the grammar. They now print as the function call the parser does have: lambda(x, x + 1), apply(f, 2), piecewise(a provided p, b provided q). The piecewise one is the same class of defect as #687 — a printed form that comes back as an implicit product of undeclared variables.

Latexise is untouched. Nothing parses LaTeX, so it is under no such obligation.

Tests

A round-trip test over 98 expressions covering every node kind — arithmetic and grouping, all the trigonometric and hyperbolic families, calculus nodes, booleans and comparisons, sets and intervals, matrices, numbers, and the four above. It went from 88/98 to 98/98.

Worth recording what the ten failures were, since two of them are not bugs: the hyperbolic and inverse-hyperbolic functions, cbrt and sqr are rewritten as they are parsed — sinh(x) is (e^x - e^(-x))/2 and arsinh(x) is ln(x + sqrt(x^2+1)) — so they never print back as themselves. What round-trips is the expression, not the spelling, and the test asserts that.

Also: a syntax reference

Sources/AngouriMath/Docs/Usage/Syntax.md. There was none, and the grammar was the only statement of what the language accepts — which is ANTLR source, and not something a user reads. It covers the precedence table, the round-trip contract, the function names, and a short list of the things that catch people out (^ groups right; an unknown name is a silent product; intervals use ; where vectors use ,; -> is implication). Every claim in it was checked against the parser rather than read off the grammar.

Suite: Failed: 0, Passed: 4461, Skipped: 14, Total: 4475 — no existing test had pinned the wrong output, so nothing needed its expectation changed.

Independent of #703 and #705.

Stringize's contract is that parsing what it prints gives back what was printed,
and four kinds of expression broke it -- silently, since a wrong reading is
still a valid expression.

  (2 ^ 3) ^ 2 printed as 2 ^ 3 ^ 2. Powers group to the right, so it is the base
  that needs bracketing when it is a power of its own, the mirror of the rule
  the left-associative operators use. The first is 64 and the second is 512.

  lambda(x, x + 1) printed as x -> x + 1, and -> is the implication operator, so
  it came back as x implies x + 1.

  apply(f, 2) printed by juxtaposition, which came back as a power.

  piecewise(1 provided x > 0) printed as (1 if x > 0), and there is no `if` in
  the grammar, so it came back as a product with `if` read as an undeclared
  variable. With two cases it did not parse at all.

The three without an operator spelling now print as the function call the parser
has. Latexise is unaffected: nothing parses LaTeX, and it had the power right
in any case.

Adds a round-trip test over 98 expressions covering every node kind, and a
syntax reference under Docs/Usage, since the grammar was the only statement of
what the language accepts and it is ANTLR source.

Suite 4461 passed, 0 failed -- no existing test had pinned the wrong output.
@codecov

codecov Bot commented Aug 4, 2026

Copy link
Copy Markdown

Codecov Report

✅ All modified and coverable lines are covered by tests.
✅ Project coverage is 81.79%. Comparing base (90c00a8) to head (b578dc7).
⚠️ Report is 79 commits behind head on master.

Additional details and impacted files
@@            Coverage Diff             @@
##           master     #706      +/-   ##
==========================================
+ Coverage   80.99%   81.79%   +0.80%     
==========================================
  Files         155      159       +4     
  Lines       13687    13795     +108     
  Branches     1957     2329     +372     
==========================================
+ Hits        11086    11284     +198     
+ Misses       1990     1858     -132     
- Partials      611      653      +42     

☔ View full report in Codecov by Harness.
📢 Have feedback on the report? Share it here.

🚀 New features to boost your workflow:
  • ❄️ Test Analytics: Detect flaky tests, report on failures, and find test suite problems.

@Rafael-SOWNet
Rafael-SOWNet merged commit d825b03 into ASC-Community:master Aug 4, 2026
26 checks passed
@Rafael-SOWNet
Rafael-SOWNet deleted the fix/stringizer-round-trip branch August 4, 2026 23:27
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant