From 48fed5b9419562e4dc752b41199f9395681626a7 Mon Sep 17 00:00:00 2001 From: Rafael Vuijk Date: Tue, 4 Aug 2026 21:45:15 +0000 Subject: [PATCH] Record what the last three fixes taught Three additions, each from something that went wrong and was caught: Check the convention against the other systems rather than reasoning about it from memory. mod took the sign of the dividend until review pointed out that no mathematician means that by mod, and SymPy, Mathematica and Maxima all disagreed with it. Say "no answer" with null, not with an unevaluated node of the expression you were asked about. The latter looks honest and is a cycle that overflows the stack, which kills the process rather than raising anything catchable. Stringize has a contract -- parsing what it prints gives back what it printed -- and four node kinds broke it silently, one of them turning 64 into 512. Latexise is under no such obligation. Points at the new syntax reference, which is where what the parser accepts is now written down. --- AGENTS.md | 26 ++++++++++++++++++++++++++ 1 file changed, 26 insertions(+) diff --git a/AGENTS.md b/AGENTS.md index d085ada98..b66a3aba4 100644 --- a/AGENTS.md +++ b/AGENTS.md @@ -17,6 +17,12 @@ the library is wrong — even where the library's way is defensible in isolation thing; the inverse of `sinh` is an *area*, not an arc, so it is `arsinh` (#687). Follow the notation of the people who use the subject, not the notation that was easiest to parse. +When you have to choose a convention, **check what the other systems answer** — SymPy, +Mathematica, Maxima — and match the mathematics rather than the language you are writing in. +`mod` takes the sign of the divisor because that is what a mathematician means by mod and what +all three of those give; C's `%` truncates, but C's `%` is an operation on machine integers. +Check it, do not reason about it from memory: this exact case was got wrong first time round. + **Consistency is the point.** [#497](https://github.com/asc-community/AngouriMath/issues/497), the 2.0 paper, names inconsistency as the central defect: *"one may find it inconsistent in a lot of places in API, behaviour, and internal structure of code."* A rule that fires for `sin` but not @@ -41,6 +47,26 @@ does not exist. Returning an unevaluated node is honest. So, in order of preference: right answer > no answer > slow answer > wrong answer. A wrong answer is worse than a hang, because a hang is visible. +Say "no answer" by returning **`null`**, not by handing back an unevaluated node of the expression +you were asked about. `Limitf(this, ...)` looks like the honest answer and is in fact a cycle: the +caller evaluates it to compare, evaluating computes the limit, and computing arrives back where it +started. That overflows the stack, which kills the process rather than raising anything catchable. +`null` reads the same to the caller and terminates. + +## Output has a contract too + +**Parsing what `Stringize` prints must give back the expression it printed.** Anything else makes +the printed form a lie, and a silent one, since a wrong reading is usually still a valid +expression: `(2^3)^2` printed as `2^3^2` is 512 where the expression is 64, and a piecewise +printed with `if` came back as a product with `if` read as an undeclared variable. If a node's +usual notation is not in the grammar, print the function call the parser does have. +`StringizeRoundTripTest` is where that is enforced; add to it when you add a node. + +`Latexise` is under no such obligation — nothing parses LaTeX. + +The syntax the parser accepts is written down in `Sources/AngouriMath/Docs/Usage/Syntax.md`. +Keep it true when you change the grammar. + ## Verify the mathematics, not the string Never assert on printed form unless the printed form *is* the bug. Assert the property: