---
date: 2025-04-24
url: https://hackmd.io/HjxQO3QqTVKHxR-jx8fCxA
---
# let chain grammar
Attempt by encoding precedence
This doens't entirely capture the precedence rules...
`if let true == true && x == true || false` (which is an error because the precedence of `||` is too low), need to somehow say when parsing the ConditionExpression that the precedence rules change.
```rust
IfExpression ->
`if` IfConditions BlockExpression
(`else` ( BlockExpression | IfExpression ) )?
IfConditions ->
IfCondition ( `&&` IfCondition )*
| Expression _except [StructExpression]_
// Changed: previously was Expression or `let`
// Includes exception because that is recursive, and I don't want to fork the
// entire expression grammar.
IfCondition -> ConditionExpression _except [StructExpression]_
Expression ->
ExpressionWithoutBlock
| ExpressionWithBlock
// Changed: Factored out expressions shared with ConditionExpression
ExpressionWithoutBlock ->
OuterAttribute*
(
CommonExpression
| OperatorExpression
| RangeExpression
| StructExpression
)
// New: Expressions shared with ConditionExpression
// (no LetExpression or OperatorExpression or StructExpression)
CommonExpression ->
LiteralExpression
| PathExpression
| GroupedExpression
| ArrayExpression
| AwaitExpression
| IndexExpression
| TupleExpression
| TupleIndexingExpression
| CallExpression
| MethodCallExpression
| FieldExpression
| ClosureExpression
| AsyncBlockExpression
| ContinueExpression
| BreakExpression
| ReturnExpression
| UnderscoreExpression
| MacroInvocation
ExpressionWithBlock ->
OuterAttribute*
(
BlockExpression
| ConstBlockExpression
| UnsafeBlockExpression
| LoopExpression
| IfExpression
| MatchExpression
)
// New: Everything allowed in a condition expression
// Forks OperatorExpression to exclude `||` or `&&` or `a..` or `a..b` or `a..=b`
// Excludes StructExpression
// Adds LetExpression
ConditionExpression ->
OuterAttribute*
(
CommonExpression
| CommonOperatorExpression
| NoPrefixRangeExpression
| LetExpression
)
// New
LetExpression -> `let` Pattern `=` LetScrutinee
// New: Expression that excludes `||` or `&&` or `a..` or `a..b` or `a..=b` or StructExpression
LetScrutinee ->
CommonExpression
| CommonOperatorExpression
| NoPrefixRangeExpression
// Changed: Factored out expressions shared with ConditionExpression
OperatorExpression ->
CommonOperatorExpression
| LazyBooleanExpression
| AssignmentExpression
| CompoundAssignmentExpression
// New: Factored out things.
CommonOperatorExpression ->
BorrowExpression
| DereferenceExpression
| ErrorPropagationExpression
| NegationExpression
| ArithmeticOrLogicalExpression
| ComparisonExpression
| TypeCastExpression
// Changed: Factored out NoPrefixRangeExpression
RangeExpression ->
RangeExpr
| RangeFromExpr
| RangeInclusiveExpr
| NoPrefixRangeExpression
// New: Range expressions that don't start with an expression
NoPrefixRangeExpression ->
| RangeToExpr
| RangeFullExpr
| RangeToInclusiveExpr
```
---
New attempt where precedence is specified using _except_.
```rust
IfExpression ->
`if` Conditions BlockExpression
(`else` ( BlockExpression | IfExpression ) )?
Conditions ->
Expression _except [StructExprStruct]_
| LetChain
LetChain -> LetChainCondition ( `&&` LetChainCondition )*
LetChainCondition ->
Expression _except [ExcludedConditions]_
| `let` Pattern `=` Scrutinee _except [ExcludedConditions]_
// Would need to somehow say "these exclusions are in effect until ...parse_expr_bottom?"
ExcludedConditions ->
StructExprStruct
| LazyBooleanExpression
| NoPrefixRangeExpression
| AssignmentExpression
| CompoundAssignmentExpression
NoPrefixRangeExpression ->
| RangeToExpr
| RangeFullExpr
| RangeToInclusiveExpr
```
TC: This is probably what we should do. It leans into infinite lookahead, but that's OK for our purposes here, at least for the moment.
---
TC: With respect to precedence handling, one very direct way to encode this might be to, in the DFA, separate out the binary operators at each level of precedence, and parse each separately, making sure to more tightly loop over the higher precedence ones. That's what my draft grammar did in a limited way, but it could be extended.
TC: (Or we just, over an edition, switch Rust to s-exprs. /s)
---
Use this instead of _except_.
Note that this does not explain the StructExprStruct restriction, which is quite complicated (and seems buggy to me). See also expr.struct.brace-restricted-positions.
This also doesn't explain the ambiguity between the two options in `Conditions`. We should probably define how those ambiguities are resolved in general for the entire grammar.
```rust
IfExpression ->
`if` Conditions BlockExpression
(`else` ( BlockExpression | IfExpression ) )?
Conditions ->
Expression _except [StructExpression]_
| LetChain
LetChain -> LetChainCondition ( `&&` LetChainCondition )*
LetChainCondition ->
Expression _see [expr.if.chains.parse-restriction]_
| OuterAttribute* `let` Pattern `=` Scrutinee _see [expr.if.chains.parse-restriction]_
```
[expr.if.chains.parse-restriction]
When parsing a chain of conditions separated with `&&`, the following expressions are not allowed:
- [StructExpression]
- [LazyBooleanExpression]
- [RangeExpr]
- [RangeFromExpr]
- [RangeInclusiveExpr]
- [AssignmentExpression]
- [CompoundAssignmentExpression]