diff --git a/GRAMMAR.ebnf b/GRAMMAR.ebnf index 9690b3d..8e07521 100644 --- a/GRAMMAR.ebnf +++ b/GRAMMAR.ebnf @@ -33,7 +33,23 @@ top_level_def = func_def (* Expressions *) (* ================================================================ *) -expr = or_expr ; +expr = assign_expr ; + + +(* --- Assignment (lowest-precedence binary operator) --- *) +(* *) +(* Uses token `=`; right-associative via recursion. *) +(* The optional form encodes at-most-one assignment target: chains *) +(* like `a = b = c` parse as `a = (b = c)` thanks to right *) +(* recursion. *) +(* *) +(* LL(1): after or_expr, peek at next token. *) +(* "=" → consume and recurse into assign_expr *) +(* other → return the or_expr as-is *) +(* "=" is not in FIRST(stmt), so expr_stmt can still be *) +(* distinguished from other statement kinds. *) + +assign_expr = or_expr , [ "=" , assign_expr ] ; (* --- Logical OR (lowest-precedence binary operator) --- *) @@ -165,7 +181,9 @@ arg_list = [ expr , { "," , expr } ] ; (* if_stmt and while_stmt use expr_ns for their condition. *) (* All other expression positions use the full expr. *) -expr_ns = or_expr_ns ; +expr_ns = assign_expr_ns ; + +assign_expr_ns = or_expr_ns , [ "=" , assign_expr_ns ] ; or_expr_ns = and_expr_ns , { "or" , and_expr_ns } ; and_expr_ns = bitor_expr_ns , { "and" , bitor_expr_ns } ; diff --git a/SYNTAX.md b/SYNTAX.md index 361d8b0..03321f3 100644 --- a/SYNTAX.md +++ b/SYNTAX.md @@ -37,6 +37,7 @@ appear as UPPERCASE terminals in `GRAMMAR.ebnf`. | `BANG` | `!` | Logical NOT | | `TILDE` | `~` | Bitwise NOT | | `DOT` | `.` | Member access | +| `EQ` | `=` | Assignment | ### Keyword Tokens @@ -128,33 +129,35 @@ tightly). | Level | Operators | Associativity | Description | | ----- | --------------------------- | -------------- | -------------------------------- | -| 1 | `or` | left | Logical OR (lowest) | -| 2 | `and` | left | Logical AND | -| 3 | `\|` | left | Bitwise OR | -| 4 | `^` | left | Bitwise XOR | -| 5 | `&` | left | Bitwise AND | -| 6 | `+` `-` | left | Addition, subtraction | -| 7 | `*` `/` `%` | left | Multiplication, division, modulo | -| 8 | `!` `~` `-` `*` `&` | right (unary) | Prefix unary operators | -| 9 | `.` `[…]` `(…)` | left (postfix) | Member access, index, call | -| 10 | literals, identifiers, `()` | — | Primary expressions (highest) | +| 1 | `=` | right | Assignment (lowest) | +| 2 | `or` | left | Logical OR | +| 3 | `and` | left | Logical AND | +| 4 | `\|` | left | Bitwise OR | +| 5 | `^` | left | Bitwise XOR | +| 6 | `&` | left | Bitwise AND | +| 7 | `+` `-` | left | Addition, subtraction | +| 8 | `*` `/` `%` | left | Multiplication, division, modulo | +| 9 | `!` `~` `-` `*` `&` | right (unary) | Prefix unary operators | +| 10 | `.` `[…]` `(…)` | left (postfix) | Member access, index, call | +| 11 | literals, identifiers, `()` | — | Primary expressions (highest) | ### Operator Descriptions #### Binary Operators -| Operator | Name | Example | Notes | -| -------- | -------------- | --------- | -------------------------------------------- | -| `or` | Logical OR | `a or b` | Short-circuits; both operands must be `bool` | -| `and` | Logical AND | `a and b` | Short-circuits; both operands must be `bool` | -| `\|` | Bitwise OR | `a \| b` | Integer types | -| `^` | Bitwise XOR | `a ^ b` | Integer types | -| `&` | Bitwise AND | `a & b` | Integer types (binary context) | -| `+` | Addition | `a + b` | | -| `-` | Subtraction | `a - b` | | -| `*` | Multiplication | `a * b` | Binary context (both operands are values) | -| `/` | Division | `a / b` | Integer division truncates toward zero | -| `%` | Modulo | `a % b` | Sign follows the dividend | +| Operator | Name | Example | Notes | +| -------- | -------------- | --------- | ---------------------------------------------- | +| `=` | Assignment | `a = b` | Right-associative; `a = b = c` → `a = (b = c)` | +| `or` | Logical OR | `a or b` | Short-circuits; both operands must be `bool` | +| `and` | Logical AND | `a and b` | Short-circuits; both operands must be `bool` | +| `\|` | Bitwise OR | `a \| b` | Integer types | +| `^` | Bitwise XOR | `a ^ b` | Integer types | +| `&` | Bitwise AND | `a & b` | Integer types (binary context) | +| `+` | Addition | `a + b` | | +| `-` | Subtraction | `a - b` | | +| `*` | Multiplication | `a * b` | Binary context (both operands are values) | +| `/` | Division | `a / b` | Integer division truncates toward zero | +| `%` | Modulo | `a % b` | Sign follows the dividend | #### Unary Prefix Operators diff --git a/fluxc/src/ast.rs b/fluxc/src/ast.rs index 782f877..9a3a472 100644 --- a/fluxc/src/ast.rs +++ b/fluxc/src/ast.rs @@ -33,6 +33,8 @@ pub enum BinaryOp { Mul, // `*` Div, // `/` Rem, // `%` + // Assignment (lowest precedence, right-associative) + Assign, // `=` } // ── Types ────────────────────────────────────────────────────────────────────── diff --git a/fluxc/src/parser.rs b/fluxc/src/parser.rs index ec91dc7..82a8922 100644 --- a/fluxc/src/parser.rs +++ b/fluxc/src/parser.rs @@ -35,6 +35,9 @@ impl fmt::Display for ParseError { fn infix_bp(kind: TokenKind) -> Option<(u8, u8)> { let bp = match kind { + // Assignment: lowest precedence, right-associative (left_bp == right_bp). + // `a = b = c` → `a = (b = c)`. + TokenKind::Eq => (2, 2), TokenKind::Or => (10, 11), TokenKind::And => (20, 21), TokenKind::Pipe => (30, 31), @@ -97,6 +100,7 @@ fn token_to_binary_op(kind: TokenKind) -> BinaryOp { TokenKind::Star => BinaryOp::Mul, TokenKind::Slash => BinaryOp::Div, TokenKind::Percent => BinaryOp::Rem, + TokenKind::Eq => BinaryOp::Assign, _ => unreachable!("not a binary op: {:?}", kind), } } @@ -1048,6 +1052,55 @@ mod tests { } } + #[test] + fn assignment_expr() { + let expr = parse("a = b"); + assert!(matches!( + expr.kind, + ExprKind::Binary { + op: BinaryOp::Assign, + .. + } + )); + } + + #[test] + fn assignment_right_associative() { + // `a = b = c` → `a = (b = c)` + let expr = parse("a = b = c"); + match &expr.kind { + ExprKind::Binary { + op: BinaryOp::Assign, + rhs, + .. + } => { + assert!(matches!( + rhs.kind, + ExprKind::Binary { + op: BinaryOp::Assign, + .. + } + )); + } + _ => panic!("expected assignment"), + } + } + + #[test] + fn assignment_stmt() { + let s = stmt("a = b + 1;"); + match &s.kind { + StmtKind::Expr(e) => assert!(matches!( + e.kind, + ExprKind::Binary { + op: BinaryOp::Assign, + .. + } + )), + _ => panic!("expected expr stmt with assignment"), + } + } + #[test] fn deref_and_addrof() { assert!(matches!(