From 7cdcad7df3984f84631300628f403501669f6dae Mon Sep 17 00:00:00 2001 From: Jooris Hadeler Date: Wed, 11 Mar 2026 10:37:21 +0100 Subject: [PATCH] Feat: add mutable pointer type *mut T and *mut opaque - Grammar: update pointer_type to support optional mut keyword; LL(1) verified (56 named rules, no conflicts) - AST: update Type enum with mutable: bool field for Pointer and OpaquePointer variants - Parser: consume optional mut token in parse_type; update all existing pointer tests; add 4 new mut pointer tests (85 pass) - VSCode extension: add *mut capture-group pattern for syntax highlighting; update SYNTAX.md with pointer mutability table Co-Authored-By: Claude Sonnet 4.6 --- GRAMMAR.ebnf | 17 ++++--- SYNTAX.md | 38 +++++++++++----- fluxc/src/ast.rs | 8 ++-- fluxc/src/parser.rs | 55 +++++++++++++++++++---- vscode-flux/syntaxes/flux.tmLanguage.json | 8 ++++ 5 files changed, 97 insertions(+), 29 deletions(-) diff --git a/GRAMMAR.ebnf b/GRAMMAR.ebnf index 1af3291..78d8dee 100644 --- a/GRAMMAR.ebnf +++ b/GRAMMAR.ebnf @@ -271,13 +271,20 @@ named_type = IDENT ; (* --- Pointer types --- *) (* *) -(* "*" type — typed pointer; the pointee type is known. *) -(* "*opaque" — untyped/opaque pointer (no pointee type info). *) +(* "*" type — immutable typed pointer (read-only through ptr) *) +(* "*mut" type — mutable typed pointer (read-write through ptr) *) +(* "*opaque" — immutable untyped pointer (like C's const void* *) +(* "*mut opaque" — mutable untyped pointer (like C's void* *) (* *) -(* LL(1) note: after "*", "opaque" is not in FIRST(type), so the *) -(* two alternatives are always distinguishable with one token. *) +(* LL(1) note: after "*", peek at next token: *) +(* "mut" → mutable pointer; consume "mut", then parse *) +(* "opaque" | type *) +(* "opaque" → immutable opaque pointer *) +(* other → immutable typed pointer; parse type directly *) +(* "mut" is a keyword; it is not in FIRST(type) and is distinct *) +(* from "opaque", so all three cases are unambiguous with one token.*) -pointer_type = "*" , ( "opaque" | type ) ; +pointer_type = "*" , [ "mut" ] , ( "opaque" | type ) ; (* --- Array types --- *) diff --git a/SYNTAX.md b/SYNTAX.md index 9fa1cee..bf36cd2 100644 --- a/SYNTAX.md +++ b/SYNTAX.md @@ -291,20 +291,36 @@ Node // struct Node { value: i64, next: *Node } ### Pointer Types -A pointer type is written with a leading `*`. +A pointer type is written with a leading `*`. The optional `mut` keyword following +`*` marks the pointer as **mutable** — the pointee can be written through. +Without `mut` the pointer is **immutable** — the pointee can only be read. -| Syntax | Description | -| --------- | ------------------------------------------------------------------------------------- | -| `*T` | Typed pointer — points to a value of type `T` | -| `*opaque` | Opaque pointer — no compile-time pointee type information; equivalent to C's `void *` | +| Syntax | C equivalent | Description | +| ------------- | ----------------- | -------------------------------------------------- | +| `*T` | `const T *` | Immutable typed pointer — read-only through ptr | +| `*mut T` | `T *` | Mutable typed pointer — read-write through ptr | +| `*opaque` | `const void *` | Immutable untyped pointer — no pointee type info | +| `*mut opaque` | `void *` | Mutable untyped pointer — no pointee type info | -Pointer types may be nested: `**u8` is a pointer to a pointer to `u8`. +Pointer mutability (`*mut`) and binding mutability (`let mut`) are **independent**: + +| Declaration | Can reassign ptr? | Can write through ptr? | +| ------------------------- | ----------------- | ---------------------- | +| `let p: *T` | no | no | +| `let mut p: *T` | yes | no | +| `let p: *mut T` | no | yes | +| `let mut p: *mut T` | yes | yes | + +Pointer types may be nested. The `mut` qualifier applies only to the outermost +indirection; each `*` in the chain can independently carry `mut`. ```flux -*u8 // pointer to u8 -**i32 // pointer to pointer to i32 -*opaque // untyped pointer -**opaque // pointer to untyped pointer +*u8 // immutable pointer to u8 (cannot write *p) +*mut u8 // mutable pointer to u8 (can write *p) +**u8 // immutable pointer to immutable pointer to u8 +*mut *mut u8 // mutable pointer to mutable pointer to u8 +*opaque // immutable untyped pointer +*mut opaque // mutable untyped pointer ``` ### Array Types @@ -333,7 +349,7 @@ primitive_type = "u8" | "u16" | "u32" | "u64" | "i8" | "i16" | "i32" | "i64" | "f32" | "f64" | "bool" | "char" ; named_type = IDENT ; -pointer_type = "*" , ( "opaque" | type ) ; +pointer_type = "*" , [ "mut" ] , ( "opaque" | type ) ; array_type = "[" , type , ";" , INT_LIT , "]" ; ``` diff --git a/fluxc/src/ast.rs b/fluxc/src/ast.rs index 666e236..e8a342d 100644 --- a/fluxc/src/ast.rs +++ b/fluxc/src/ast.rs @@ -76,10 +76,10 @@ pub enum Type { Char, // User-defined named type (e.g. a struct) Named(String, Span), - // Typed pointer: `*type` - Pointer(Box), - // Opaque (untyped) pointer: `*opaque` - OpaquePointer, + // Typed pointer: `*type` (immutable) or `*mut type` (mutable) + Pointer { mutable: bool, pointee: Box }, + // Opaque (untyped) pointer: `*opaque` (immutable) or `*mut opaque` (mutable) + OpaquePointer { mutable: bool }, // Fixed-size array: `[type; INT_LIT]` Array { elem: Box, size: String }, // Error placeholder for recovery diff --git a/fluxc/src/parser.rs b/fluxc/src/parser.rs index 41bb9a0..4e2aa9d 100644 --- a/fluxc/src/parser.rs +++ b/fluxc/src/parser.rs @@ -223,13 +223,19 @@ impl<'src> Parser<'src> { // Named type (user-defined struct, etc.) TokenKind::Ident => Type::Named(tok.text.to_owned(), tok.span), - // Pointer: `*opaque` or `*` + // Pointer: `*[mut] opaque` or `*[mut] ` TokenKind::Star => { + let mutable = if self.current().kind == TokenKind::Mut { + self.advance(); + true + } else { + false + }; if self.current().kind == TokenKind::Opaque { self.advance(); - Type::OpaquePointer + Type::OpaquePointer { mutable } } else { - Type::Pointer(Box::new(self.parse_type())) + Type::Pointer { mutable, pointee: Box::new(self.parse_type()) } } } @@ -1177,12 +1183,34 @@ mod tests { #[test] fn type_pointer() { - assert!(matches!(parse_type_str("*i32"), Type::Pointer(_))); + assert!(matches!( + parse_type_str("*i32"), + Type::Pointer { mutable: false, .. } + )); + } + + #[test] + fn type_mut_pointer() { + assert!(matches!( + parse_type_str("*mut i32"), + Type::Pointer { mutable: true, .. } + )); } #[test] fn type_opaque_pointer() { - assert!(matches!(parse_type_str("*opaque"), Type::OpaquePointer)); + assert!(matches!( + parse_type_str("*opaque"), + Type::OpaquePointer { mutable: false } + )); + } + + #[test] + fn type_mut_opaque_pointer() { + assert!(matches!( + parse_type_str("*mut opaque"), + Type::OpaquePointer { mutable: true } + )); } #[test] @@ -1194,8 +1222,17 @@ mod tests { #[test] fn type_nested_pointer() { - // `**i32` → Pointer(Pointer(I32)) - assert!(matches!(parse_type_str("**i32"), Type::Pointer(_))); + // `**i32` → Pointer { Pointer { I32 } } + assert!(matches!(parse_type_str("**i32"), Type::Pointer { mutable: false, .. })); + } + + #[test] + fn type_nested_mut_pointer() { + // `*mut *mut i32` → Pointer { mutable: true, Pointer { mutable: true, I32 } } + assert!(matches!( + parse_type_str("*mut *mut i32"), + Type::Pointer { mutable: true, .. } + )); } // ── Statement tests ─────────────────────────────────────────────────────── @@ -1466,7 +1503,7 @@ mod tests { let d = top("fn foo(p: *i32) { }"); match &d.kind { TopLevelDefKind::Func(f) => { - assert!(matches!(f.params[0].ty, Type::Pointer(_))); + assert!(matches!(f.params[0].ty, Type::Pointer { mutable: false, .. })); } _ => panic!("expected func def"), } @@ -1506,7 +1543,7 @@ mod tests { let d = top("struct Node { value: i32, next: *Node }"); match &d.kind { TopLevelDefKind::Struct(s) => { - assert!(matches!(s.fields[1].ty, Type::Pointer(_))); + assert!(matches!(s.fields[1].ty, Type::Pointer { mutable: false, .. })); } _ => panic!("expected struct def"), } diff --git a/vscode-flux/syntaxes/flux.tmLanguage.json b/vscode-flux/syntaxes/flux.tmLanguage.json index 14191ea..6f93168 100644 --- a/vscode-flux/syntaxes/flux.tmLanguage.json +++ b/vscode-flux/syntaxes/flux.tmLanguage.json @@ -180,6 +180,14 @@ "name": "keyword.operator.assignment.flux", "match": "(?+\\-*/%&|^])=(?!=)" }, + { + "comment": "Mutable pointer type prefix *mut — must precede arithmetic *", + "match": "(\\*)(mut)\\b", + "captures": { + "1": { "name": "keyword.operator.type.pointer.flux" }, + "2": { "name": "storage.modifier.mut.flux" } + } + }, { "comment": "Arithmetic: + - * / %", "name": "keyword.operator.arithmetic.flux",