Compare commits

...

3 Commits

Author SHA1 Message Date
eda4f92900 Docs: finalise SEMANTICS.md type system specification
All 12 sections are now fully defined. Removed incremental-progress
markers (Status: defined), updated intro and outline preamble, and
fixed two missing cases in the assigns() function (struct literals and
parenthesised expressions).

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-03-11 11:17:31 +01:00
7cdcad7df3 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 <noreply@anthropic.com>
2026-03-11 10:37:21 +01:00
53c66a3d03 Feat: update VSCode extension with compound assignment and shift operators
Add compound assignment operators (+=, -=, *=, /=, %=, &=, |=, ^=,
<<=, >>=) and shift operators (<<, >>) to the tmLanguage syntax
highlighting grammar. Patterns are ordered longest-first to prevent
shorter tokens from shadowing multi-character operators. Also update
fibonacci example to use += compound assignment.
2026-03-11 10:11:01 +01:00
7 changed files with 1500 additions and 34 deletions

View File

@@ -271,13 +271,20 @@ named_type = IDENT ;
(* --- Pointer types --- *) (* --- Pointer types --- *)
(* *) (* *)
(* "*" type typed pointer; the pointee type is known. *) (* "*" type immutable typed pointer (read-only through ptr) *)
(* "*opaque" untyped/opaque pointer (no pointee type info). *) (* "*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 *) (* LL(1) note: after "*", peek at next token: *)
(* two alternatives are always distinguishable with one 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 --- *) (* --- Array types --- *)

1388
SEMANTICS.md Normal file

File diff suppressed because it is too large Load Diff

View File

@@ -291,20 +291,36 @@ Node // struct Node { value: i64, next: *Node }
### Pointer Types ### 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 | | Syntax | C equivalent | Description |
| --------- | ------------------------------------------------------------------------------------- | | ------------- | ----------------- | -------------------------------------------------- |
| `*T` | Typed pointer — points to a value of type `T` | | `*T` | `const T *` | Immutable typed pointer — read-only through ptr |
| `*opaque` | Opaque pointer — no compile-time pointee type information; equivalent to C's `void *` | | `*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 ```flux
*u8 // pointer to u8 *u8 // immutable pointer to u8 (cannot write *p)
**i32 // pointer to pointer to i32 *mut u8 // mutable pointer to u8 (can write *p)
*opaque // untyped pointer **u8 // immutable pointer to immutable pointer to u8
**opaque // pointer to untyped pointer *mut *mut u8 // mutable pointer to mutable pointer to u8
*opaque // immutable untyped pointer
*mut opaque // mutable untyped pointer
``` ```
### Array Types ### Array Types
@@ -333,7 +349,7 @@ primitive_type = "u8" | "u16" | "u32" | "u64"
| "i8" | "i16" | "i32" | "i64" | "i8" | "i16" | "i32" | "i64"
| "f32" | "f64" | "bool" | "char" ; | "f32" | "f64" | "bool" | "char" ;
named_type = IDENT ; named_type = IDENT ;
pointer_type = "*" , ( "opaque" | type ) ; pointer_type = "*" , [ "mut" ] , ( "opaque" | type ) ;
array_type = "[" , type , ";" , INT_LIT , "]" ; array_type = "[" , type , ";" , INT_LIT , "]" ;
``` ```

View File

@@ -16,7 +16,7 @@ fn fibonacci_iter(n: u8) -> u64 {
a = b; a = b;
b = temp; b = temp;
counter = counter + 1; counter += 1;
} }
return a; return a;

View File

@@ -76,10 +76,10 @@ pub enum Type {
Char, Char,
// User-defined named type (e.g. a struct) // User-defined named type (e.g. a struct)
Named(String, Span), Named(String, Span),
// Typed pointer: `*type` // Typed pointer: `*type` (immutable) or `*mut type` (mutable)
Pointer(Box<Type>), Pointer { mutable: bool, pointee: Box<Type> },
// Opaque (untyped) pointer: `*opaque` // Opaque (untyped) pointer: `*opaque` (immutable) or `*mut opaque` (mutable)
OpaquePointer, OpaquePointer { mutable: bool },
// Fixed-size array: `[type; INT_LIT]` // Fixed-size array: `[type; INT_LIT]`
Array { elem: Box<Type>, size: String }, Array { elem: Box<Type>, size: String },
// Error placeholder for recovery // Error placeholder for recovery

View File

@@ -223,13 +223,19 @@ impl<'src> Parser<'src> {
// Named type (user-defined struct, etc.) // Named type (user-defined struct, etc.)
TokenKind::Ident => Type::Named(tok.text.to_owned(), tok.span), TokenKind::Ident => Type::Named(tok.text.to_owned(), tok.span),
// Pointer: `*opaque` or `*<type>` // Pointer: `*[mut] opaque` or `*[mut] <type>`
TokenKind::Star => { TokenKind::Star => {
let mutable = if self.current().kind == TokenKind::Mut {
self.advance();
true
} else {
false
};
if self.current().kind == TokenKind::Opaque { if self.current().kind == TokenKind::Opaque {
self.advance(); self.advance();
Type::OpaquePointer Type::OpaquePointer { mutable }
} else { } else {
Type::Pointer(Box::new(self.parse_type())) Type::Pointer { mutable, pointee: Box::new(self.parse_type()) }
} }
} }
@@ -1177,12 +1183,34 @@ mod tests {
#[test] #[test]
fn type_pointer() { 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] #[test]
fn type_opaque_pointer() { 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] #[test]
@@ -1194,8 +1222,17 @@ mod tests {
#[test] #[test]
fn type_nested_pointer() { fn type_nested_pointer() {
// `**i32` → Pointer(Pointer(I32)) // `**i32` → Pointer { Pointer { I32 } }
assert!(matches!(parse_type_str("**i32"), Type::Pointer(_))); 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 ─────────────────────────────────────────────────────── // ── Statement tests ───────────────────────────────────────────────────────
@@ -1466,7 +1503,7 @@ mod tests {
let d = top("fn foo(p: *i32) { }"); let d = top("fn foo(p: *i32) { }");
match &d.kind { match &d.kind {
TopLevelDefKind::Func(f) => { 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"), _ => panic!("expected func def"),
} }
@@ -1506,7 +1543,7 @@ mod tests {
let d = top("struct Node { value: i32, next: *Node }"); let d = top("struct Node { value: i32, next: *Node }");
match &d.kind { match &d.kind {
TopLevelDefKind::Struct(s) => { 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"), _ => panic!("expected struct def"),
} }

View File

@@ -156,19 +156,37 @@
"operators": { "operators": {
"patterns": [ "patterns": [
{ {
"comment": "Arrow (return type separator)", "comment": "Arrow (return type separator) — must precede - and -=",
"name": "keyword.operator.arrow.flux", "name": "keyword.operator.arrow.flux",
"match": "->" "match": "->"
}, },
{
"comment": "Compound assignment operators — must precede shift, comparison, and arithmetic",
"name": "keyword.operator.assignment.compound.flux",
"match": "<<=|>>=|\\+=|-=|\\*=|/=|%=|&=|\\|=|\\^="
},
{
"comment": "Shift operators — must precede < and >",
"name": "keyword.operator.bitwise.shift.flux",
"match": "<<|>>"
},
{ {
"comment": "Comparison operators", "comment": "Comparison operators",
"name": "keyword.operator.comparison.flux", "name": "keyword.operator.comparison.flux",
"match": "==|!=|<=|>=|<|>" "match": "==|!=|<=|>=|<|>"
}, },
{ {
"comment": "Assignment", "comment": "Assignment — negative lookbehind excludes all compound-assign prefixes",
"name": "keyword.operator.assignment.flux", "name": "keyword.operator.assignment.flux",
"match": "(?<![=!<>])=(?!=)" "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: + - * / %", "comment": "Arithmetic: + - * / %",
@@ -176,7 +194,7 @@
"match": "[+\\-*/%]" "match": "[+\\-*/%]"
}, },
{ {
"comment": "Bitwise: & | ^ ~ and unary ! ~", "comment": "Bitwise and unary: & | ^ ~ !",
"name": "keyword.operator.bitwise.flux", "name": "keyword.operator.bitwise.flux",
"match": "[&|^~!]" "match": "[&|^~!]"
} }