Skip to content

Fix #365 and #366: parser unary minus on tensors and negative exponents - #464

Open
petlenz wants to merge 2 commits into
mainfrom
fix-365-366-parser-grammar
Open

petlenz wants to merge 2 commits into
mainfrom
fix-365-366-parser-grammar

Conversation

@petlenz

@petlenz petlenz commented Sep 17, 2026

Copy link
Copy Markdown
Member

Closes #365.
Closes #366.

Two independent grammar/action defects, one commit each.

#365 — unary minus rejected tensor and t2s operands

replace_top (src/numsim_cas/parser/actions.h) called require_scalar, so -A, -trace(A) and -det(A) raised type_mismatch_error: operator expects scalar operands even though negation is defined in all three domains (expression_holder::operator-, with neg_fn tag_invokes for tensor and t2s). Users had to write -1 * A.

It now visits the operand variant and negates whichever expression alternative it holds; only index_list_value is rejected, with a message matching the existing bracket-list wording used by the binary operators.

#366 — negative exponents unparseable

power_tail was seq<ws, caret_op, ws, power>, and power cannot start with -, so 2^-3 and x^-1 failed. The parser accepted the left operand and then choked, producing the misleading parse error at 1:2: parse error matching tao::pegtl::eof.

The right operand is now unary, which recurses back into power.

Precedence decisions (unchanged, and pinned by tests):

Input Parse Why
2^3^2 2^(3^2) = 512 unary → power keeps the right recursion
2^-3^2 2^(-(3^2)) = 2⁻⁹ the ^ tail binds before negating the exponent
-x^2 -(x^2) decided at the unary level above power; left operands stay at primary
2^3*4 (2^3)*4 = 32 ^ still binds tighter than *
2^-3*4 (2^-3)*4 = 0.5 same

x^-3 and x^(-3) now produce structurally identical nodes (hash and deep equality both checked).

Tests

10 new in ParserGrammar: NegativeExponentLiteral, NegativeExponentOnIdentifier (incl. x^-2.5), NegativeExponentMatchesParenthesizedForm, NegativeExponentKeepsRightAssociativity, UnaryMinusOnTensorMatchesCppNegation, UnaryMinusOnT2sMatchesCppNegation, UnaryMinusOnTensorSum, DoubleUnaryMinusOnTensorCancels, UnaryMinusOnDetIsT2s, UnaryMinusRejectsBracketList. The tensor/t2s cases compare the parsed expression against the C++-built node with deep ==, not just "parsing succeeded".

Negative control: against unfixed sources, 9 of the 10 fail (the bracket-list rejection passes both ways by design). The pre-existing precedence pins — PowerRightAssociative, PowerBindsTighterThanMul, PowerBindsTighterThanUnaryMinus, UnaryMinusBindsTighterThanMul, DoubleUnaryMinusCancels, BinaryMinusVsUnaryMinusAfterBinary — pass throughout; none was modified.

Error quality: verified against main that genuinely invalid inputs are unchanged — 2^, 2^*3 and x^- produce the same message and position as before (the internal PEGTL rule name leaking there is pre-existing, #367's scope).

Full parser-enabled suite: 2601/2601 (gcc-14 Debug, NUMSIM_CAS_BUILD_PARSER=ON). clang-format-18 clean.

replace_top required a scalar, so -A and -trace(A) raised a type
mismatch although negation is defined in all three domains. It now
visits the operand variant and negates any expression alternative;
only a bracket-list is rejected.

Signed-off-by: petlenz <peterlenz89.pl@gmail.com>
The '^' right operand was `power`, which cannot start with '-', so
2^-3 and x^-1 failed with a misleading eof error. It is now `unary`,
which recurses back into `power`: right associativity of 2^3^2 and
the -(x^2) reading of -x^2 are unchanged.

Tests cover both issues: negative exponents (literal, identifier,
decimal, parenthesized-equivalence, right associativity) and unary
minus on tensor and t2s operands compared against the C++-built
nodes.

Signed-off-by: petlenz <peterlenz89.pl@gmail.com>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

1 participant