Skip to content

Conform Behavior Annex expression precedence, admissible forms, and grouping to AS5506/3 🤖 - #3220

Merged
lwrage merged 2 commits into
masterfrom
3177_conform_expression_precedence_grouping
Sep 12, 2026
Merged

Conform Behavior Annex expression precedence, admissible forms, and grouping to AS5506/3 🤖#3220
lwrage merged 2 commits into
masterfrom
3177_conform_expression_precedence_grouping

Conversation

@lwrage

@lwrage lwrage commented Sep 12, 2026

Copy link
Copy Markdown
Contributor

Fixes #3177

Cause and correction

The expression grammar had one rule per operator level, which did not match the AS5506/3 Rev. A D.7 productions:

  • LogicalAndExpression sat below LogicalOrExpression, so and bound tighter than or/xor. D.7 puts every logical operator on one precedence level and associates a level from left to right, making a or b and c mean (a or b) and c.
  • UnaryExpression was the operand of PowerExpression, so a sign bound inside the exponentiation. D.7 admits the unary adding operator only ahead of the first term of a simple_expression, so it binds below the multiplying and numeric operators: -2 ** 2 is -(2 ** 2) and -a mod b is -(a mod b).
  • PowerExpression and UnaryExpression were both recursive, accepting a ** b ** c, - -a, abs -a, and not not a. D.7's factor admits at most one exponentiation and a single value as the operand of abs or not, so those forms require parentheses.
  • Parentheses were unassigned keywords in PrimaryExpression, so a group left no object behind. DeclarativeToStrictTranslator.appendLogical therefore flattened logical expressions without regard for grouping, and toSimpleExpression/toFactor/toValue tried to recover the grouping from node text with an isParenthesized heuristic and a consumedParentheses re-entry guard.

The correction makes the grammar the D.7 productions themselves — value_expression, relation, simple_expression, term, factor, value — and adds an explicit ParenthesizedExpression. With a group present as an object, the text heuristic and its guard are deleted: appendLogical flattens only the left-associated chain the grammar builds, so a group stays one relation, and toValue turns it into the nested strict value expression D.7's value production admits.

Removing the heuristic also restores two operators the translator silently dropped. toFactor handled a UnaryExpression whose operator was neither abs nor not by setting no operator at all, so a unary minus inside a term vanished: -total mod 3 translated to total mod 3. And -2 ** 2 translated as (-2) ** 2.

Unary plus stays accepted. Rejecting it is #3170, tracked separately, and its conformance expectation remains skipped.

Two consequences worth noting for review. The serializer now emits the requested parentheses from the model instead of reconstructing them from the parse text, so an explicitly grouped expression survives a model-to-text round trip. And each expression object's reported source range now excludes the enclosing parentheses, because the group owns them.

Regression model and assertions

ba/org.osate.ba.tests/models/issue3177/ holds five fixtures in one project. Issue3177.aadl is the accepted case: one assignment sequence with mixed logical operators, a nested logical group, a signed exponentiation, a signed multiplying term, and a parenthesized exponentiation chain. PowerChain.aadl, RepeatedUnaryMinus.aadl, AbsNegatedOperand.aadl, and RepeatedNot.aadl each hold one form the standard grammar does not admit.

Issue3177Test renders the declarative model as a prefix tree and the strict model in the standard's value_expression/relation/simple_expression/term/factor nesting, parenthesizing a nested value expression only, so a grouping difference cannot be hidden by textual association. It asserts both renderings per expression, an unparse/reparse round trip through the registered annex unparser on a copy with no node model, and a syntax error for each rejected fixture. The group is read reflectively by eClass() name so the test compiles against the pre-fix model.

All 11 tests fail on the regression commit for their intended reasons:

Source Strict model before Required
left and (middle or right) left and middle or right — a changed meaning left and (middle or right)
-2 ** 2 (-2) ** 2 -2 ** 2
-total mod 3 total mod 3 — sign dropped -total mod 3
left or middle and right AST (or left (and middle right)) AST (and (or left middle) right)
four rejected forms accepted, no diagnostic syntax error

(total ** 2) ** 3 is the positive control: its structure was already correct and stays so.

Characterization baselines

25 new goldens for the new fixtures. Only four existing goldens change, all in expected/positions, and all because a parenthesized operand's source range moves onto the new group — for example not (input'fresh) in GrammarHazards.aadl, where the ReferenceExpression had claimed the parentheses. No diagnostics, diagnostics-validated, resolved-model, or unparse golden changes, because the model goldens record containment rather than operator attributes; that limitation is item 4 of "What the existing tests do and do not prove" in ba/doc/conformance.md, and it is why Issue3177Test asserts the attributes directly.

Validation

Focused regression, from the repository root:

mvn -T5 -s releng/osate.releng/settings.xml -Plocal -pl :org.osate.ba.tests \
  -Dtycho.localArtifacts=default -Dpr.build=true -Dsign=false \
  -Dspotbugs=false -Dcodecoverage=false -Djavadoc=false \
  -Dtest=Issue3177Test -DfailIfNoTests=false clean install

Tests run: 11, Failures: 0 after the fix; Tests run: 11, Failures: 11 on the regression commit alone.

Both Behavior Annex test bundles, run separately so a zero-match selection could not hide behind a green reactor: org.osate.ba.tests 144 tests and org.osate.xtext.aadl2.ba.tests 15 tests, 0 failures.

Clean root-reactor build with tests:

mvn -T5 -s releng/osate.releng/settings.xml -Plocal \
  -Dtycho.localArtifacts=ignore -Dpr.build=true -Dsign=false \
  -Dspotbugs=false -Dcodecoverage=false -Djavadoc=false \
  -DfailIfNoTests=false clean install

BUILD SUCCESS, 1604 tests, 0 failures, 0 errors, 2 skipped. The two skips are the conformance expectations tracked against #3170 and #3173, enforced by BehaviorAnnexConformanceTest.everyStandardExpectationNamesItsTrackingIssue.

The Xtext artifacts were regenerated with the GenerateBehaviorAnnex.mwe2 launch in Eclipse. A control run on the unmodified grammar reproduced the committed tree byte for byte beforehand, and the Eclipse and headless runs of the changed grammar produced byte-identical output, so the generated diff is exactly the generator's. org.osate.ba/model/aadlba.ecore needs no change: the strict ValueExpression is already a Value, and AadlBaTypeChecker.checkValue already recurses into a nested value expression.

Dependencies and merge order

None. The branch is based on current master (de416eaf4a, the #3219 merge) and touches no file that another open pull request changes. It does not depend on #3170.

Residual risk

  • The syntax errors for the rejected unary forms are reported by ANTLR recovery at the assignment's := rather than at the offending second operator, so the message location is coarser than ideal. The goldens record the exact text.
  • Any consumer that walked the declarative AST and assumed and binds tighter than or, or that a parenthesized expression leaves no object, sees different structure. The only consumers in the reactor are the translator and Issue3169Test; both are covered by the build above.
  • BehaviorAnnexProposalProvider overrides nothing for the renamed rules, so content assist follows the regenerated grammar access with no hand-written counterpart to update.
  • Documentation only, beyond the fix: the conformance.md tracking table and its closed-since-the-review list were inconsistent for findings closed by earlier pull requests. They now agree, verified by comparing the two sets, and the stale claim that G12 and its follow-ups were unmerged is gone.

🤖 Generated with Claude Code

sei-lwrage and others added 2 commits September 12, 2026 12:14
Model the AS5506/3 Rev A D.7 expression forms whose grouping the front end
does not preserve, and the forms its grammar admits although the standard
grammar does not.

The accepted fixture combines mixed logical operators, a parenthesized
logical group, a signed exponentiation, a signed multiplying term, and a
parenthesized exponentiation chain in one assignment sequence. The
assertions render the declarative model as a prefix tree and the strict
model in the standard's value_expression/relation/simple_expression/term/
factor nesting, so a grouping difference cannot be hidden by textual
association. They record that the declarative model currently associates
'or' below 'and' rather than left to right, that the strict model splices a
parenthesized logical group into the enclosing expression and so turns
'left and (middle or right)' into '(left and middle) or right', that
'-2 ** 2' becomes '(-2) ** 2', that the unary minus of '-total mod 3' is
dropped outright, and that the unparser drops explicit parentheses while
inventing parentheses for its own association.

Four separate fixtures cover the forms the standard grammar does not admit:
an unparenthesized exponentiation chain, a repeated unary adding operator,
a signed operand of 'abs', and a repeated 'not'. Each is currently accepted
without a diagnostic.

The parenthesized exponentiation chain is the positive control: its strict
structure is already correct and must stay so.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Replace the one-rule-per-operator expression grammar with the D.7
productions themselves: value_expression, relation, simple_expression,
term, factor, and value. All logical operators now share one precedence
level and associate from left to right, so 'a or b and c' is
'(a or b) and c'. The unary adding operator leads a whole simple
expression and therefore binds below the multiplying and numeric
operators, making '-a mod b' mean '-(a mod b)' and '-2 ** 2' mean
'-(2 ** 2)'. A factor admits at most one exponentiation and a single
value as the operand of 'abs' or 'not', so 'a ** b ** c', '- -a',
'abs -a', and 'not not a' are syntax errors as the standard grammar
requires. Unary plus stays accepted; rejecting it is issue #3170.

A parenthesized value expression becomes a ParenthesizedExpression of its
own, which lets the translator stop inferring grouping from node text.
appendLogical flattens only the left-associated chain the grammar builds,
so a group stays one relation instead of being spliced into the enclosing
expression, and toValue turns it into the nested strict value expression
D.7's value production admits. Deleting the text heuristic also restores
two operators the old translator dropped: a unary minus inside a term was
discarded outright, and a signed exponentiation base kept the sign in the
wrong place.

Because a group is now an object, the serializer emits the requested
parentheses from the model rather than reconstructing them from the parse
text, and each expression object's reported source range excludes the
enclosing parentheses. That range correction is the only change to the
existing characterization baselines: four positions goldens move a
parenthesized operand's span onto the new group, and no diagnostics,
resolved-model, or unparse golden changes, because the model goldens
record containment rather than operator attributes.

Regenerate the Xtext artifacts and update the conformance report.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
@lwrage
lwrage merged commit 638ec7c into master Sep 12, 2026
1 check passed
@lwrage
lwrage deleted the 3177_conform_expression_precedence_grouping branch September 12, 2026 18:36
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.

Conform Behavior Annex expression precedence and grouping to AS5506/3 🤖

2 participants