Fix: add assignment as right-associative expression
`=` was missing from the Pratt table, causing `a = b;` to fail with "expected `;`, found `=`". Assignment is now BinaryOp::Assign with binding power (2, 2) — lowest precedence, right-associative — so `a = b = c` parses as `a = (b = c)`.
This commit is contained in:
22
GRAMMAR.ebnf
22
GRAMMAR.ebnf
@@ -33,7 +33,23 @@ top_level_def = func_def
|
|||||||
(* Expressions *)
|
(* 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) --- *)
|
(* --- 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. *)
|
(* if_stmt and while_stmt use expr_ns for their condition. *)
|
||||||
(* All other expression positions use the full expr. *)
|
(* 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 } ;
|
or_expr_ns = and_expr_ns , { "or" , and_expr_ns } ;
|
||||||
and_expr_ns = bitor_expr_ns , { "and" , bitor_expr_ns } ;
|
and_expr_ns = bitor_expr_ns , { "and" , bitor_expr_ns } ;
|
||||||
|
|||||||
47
SYNTAX.md
47
SYNTAX.md
@@ -37,6 +37,7 @@ appear as UPPERCASE terminals in `GRAMMAR.ebnf`.
|
|||||||
| `BANG` | `!` | Logical NOT |
|
| `BANG` | `!` | Logical NOT |
|
||||||
| `TILDE` | `~` | Bitwise NOT |
|
| `TILDE` | `~` | Bitwise NOT |
|
||||||
| `DOT` | `.` | Member access |
|
| `DOT` | `.` | Member access |
|
||||||
|
| `EQ` | `=` | Assignment |
|
||||||
|
|
||||||
### Keyword Tokens
|
### Keyword Tokens
|
||||||
|
|
||||||
@@ -128,33 +129,35 @@ tightly).
|
|||||||
|
|
||||||
| Level | Operators | Associativity | Description |
|
| Level | Operators | Associativity | Description |
|
||||||
| ----- | --------------------------- | -------------- | -------------------------------- |
|
| ----- | --------------------------- | -------------- | -------------------------------- |
|
||||||
| 1 | `or` | left | Logical OR (lowest) |
|
| 1 | `=` | right | Assignment (lowest) |
|
||||||
| 2 | `and` | left | Logical AND |
|
| 2 | `or` | left | Logical OR |
|
||||||
| 3 | `\|` | left | Bitwise OR |
|
| 3 | `and` | left | Logical AND |
|
||||||
| 4 | `^` | left | Bitwise XOR |
|
| 4 | `\|` | left | Bitwise OR |
|
||||||
| 5 | `&` | left | Bitwise AND |
|
| 5 | `^` | left | Bitwise XOR |
|
||||||
| 6 | `+` `-` | left | Addition, subtraction |
|
| 6 | `&` | left | Bitwise AND |
|
||||||
| 7 | `*` `/` `%` | left | Multiplication, division, modulo |
|
| 7 | `+` `-` | left | Addition, subtraction |
|
||||||
| 8 | `!` `~` `-` `*` `&` | right (unary) | Prefix unary operators |
|
| 8 | `*` `/` `%` | left | Multiplication, division, modulo |
|
||||||
| 9 | `.` `[…]` `(…)` | left (postfix) | Member access, index, call |
|
| 9 | `!` `~` `-` `*` `&` | right (unary) | Prefix unary operators |
|
||||||
| 10 | literals, identifiers, `()` | — | Primary expressions (highest) |
|
| 10 | `.` `[…]` `(…)` | left (postfix) | Member access, index, call |
|
||||||
|
| 11 | literals, identifiers, `()` | — | Primary expressions (highest) |
|
||||||
|
|
||||||
### Operator Descriptions
|
### Operator Descriptions
|
||||||
|
|
||||||
#### Binary Operators
|
#### Binary Operators
|
||||||
|
|
||||||
| Operator | Name | Example | Notes |
|
| Operator | Name | Example | Notes |
|
||||||
| -------- | -------------- | --------- | -------------------------------------------- |
|
| -------- | -------------- | --------- | ---------------------------------------------- |
|
||||||
| `or` | Logical OR | `a or b` | Short-circuits; both operands must be `bool` |
|
| `=` | Assignment | `a = b` | Right-associative; `a = b = c` → `a = (b = c)` |
|
||||||
| `and` | Logical AND | `a and b` | Short-circuits; both operands must be `bool` |
|
| `or` | Logical OR | `a or b` | Short-circuits; both operands must be `bool` |
|
||||||
| `\|` | Bitwise OR | `a \| b` | Integer types |
|
| `and` | Logical AND | `a and b` | Short-circuits; both operands must be `bool` |
|
||||||
| `^` | Bitwise XOR | `a ^ b` | Integer types |
|
| `\|` | Bitwise OR | `a \| b` | Integer types |
|
||||||
| `&` | Bitwise AND | `a & b` | Integer types (binary context) |
|
| `^` | Bitwise XOR | `a ^ b` | Integer types |
|
||||||
| `+` | Addition | `a + b` | |
|
| `&` | Bitwise AND | `a & b` | Integer types (binary context) |
|
||||||
| `-` | Subtraction | `a - b` | |
|
| `+` | Addition | `a + b` | |
|
||||||
| `*` | Multiplication | `a * b` | Binary context (both operands are values) |
|
| `-` | Subtraction | `a - b` | |
|
||||||
| `/` | Division | `a / b` | Integer division truncates toward zero |
|
| `*` | Multiplication | `a * b` | Binary context (both operands are values) |
|
||||||
| `%` | Modulo | `a % b` | Sign follows the dividend |
|
| `/` | Division | `a / b` | Integer division truncates toward zero |
|
||||||
|
| `%` | Modulo | `a % b` | Sign follows the dividend |
|
||||||
|
|
||||||
#### Unary Prefix Operators
|
#### Unary Prefix Operators
|
||||||
|
|
||||||
|
|||||||
@@ -33,6 +33,8 @@ pub enum BinaryOp {
|
|||||||
Mul, // `*`
|
Mul, // `*`
|
||||||
Div, // `/`
|
Div, // `/`
|
||||||
Rem, // `%`
|
Rem, // `%`
|
||||||
|
// Assignment (lowest precedence, right-associative)
|
||||||
|
Assign, // `=`
|
||||||
}
|
}
|
||||||
|
|
||||||
// ── Types ──────────────────────────────────────────────────────────────────────
|
// ── Types ──────────────────────────────────────────────────────────────────────
|
||||||
|
|||||||
@@ -35,6 +35,9 @@ impl fmt::Display for ParseError {
|
|||||||
|
|
||||||
fn infix_bp(kind: TokenKind) -> Option<(u8, u8)> {
|
fn infix_bp(kind: TokenKind) -> Option<(u8, u8)> {
|
||||||
let bp = match kind {
|
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::Or => (10, 11),
|
||||||
TokenKind::And => (20, 21),
|
TokenKind::And => (20, 21),
|
||||||
TokenKind::Pipe => (30, 31),
|
TokenKind::Pipe => (30, 31),
|
||||||
@@ -97,6 +100,7 @@ fn token_to_binary_op(kind: TokenKind) -> BinaryOp {
|
|||||||
TokenKind::Star => BinaryOp::Mul,
|
TokenKind::Star => BinaryOp::Mul,
|
||||||
TokenKind::Slash => BinaryOp::Div,
|
TokenKind::Slash => BinaryOp::Div,
|
||||||
TokenKind::Percent => BinaryOp::Rem,
|
TokenKind::Percent => BinaryOp::Rem,
|
||||||
|
TokenKind::Eq => BinaryOp::Assign,
|
||||||
_ => unreachable!("not a binary op: {:?}", kind),
|
_ => 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]
|
#[test]
|
||||||
fn deref_and_addrof() {
|
fn deref_and_addrof() {
|
||||||
assert!(matches!(
|
assert!(matches!(
|
||||||
|
|||||||
Reference in New Issue
Block a user