diff --git a/README.md b/README.md
index ff99129..8809a3c 100644
--- a/README.md
+++ b/README.md
@@ -13,6 +13,8 @@
·
Changelog
·
+ Reference
+ ·
Sponsor
diff --git a/src/lexer.cpp b/src/lexer.cpp
index d319f7c..81752bc 100644
--- a/src/lexer.cpp
+++ b/src/lexer.cpp
@@ -88,6 +88,8 @@ void Lexer::identifier() {
addToken(TOK_CONST, text);
} else if (text == "var") {
addToken(TOK_VAR, text);
+ } else if (text == "mut") {
+ addToken(TOK_MUT, text);
} else if (text == "if") {
addToken(TOK_IF, text);
} else if (text == "else") {
diff --git a/src/parser.cpp b/src/parser.cpp
index 3b45150..bf8ae6f 100644
--- a/src/parser.cpp
+++ b/src/parser.cpp
@@ -168,23 +168,65 @@ NodeIdx Parser::parsePrimary() {
throw std::runtime_error("Expected primary expression");
}
+// Type grammar:
+//
+// *const T — single-item pointer, read-only pointee
+// *mut T — single-item pointer, writable pointee
+// *const[] T — many-item pointer, read-only pointee
+// *mut[] T — many-item pointer, writable pointee
+// []T — slice (mutability follows the binding)
+// [N]T — fixed-size array (mutability follows the binding;
+// `N` is part of the type)
+//
+// Pointer types require a `const` or `mut` qualifier (the type
+// alone cannot say whether the pointee is writable; the binding's
+// `var` / `const` only governs reassignment of the pointer itself).
+// An optional `[]` between the qualifier and the element type promotes
+// the pointer to many-item form.
+//
+// Slices `[]T` and fixed arrays `[N]T` take no qualifier — their
+// element mutability follows the binding (`var x: []u8` permits
+// `x[i] = …`; `const x: []u8` does not).
TypeIdx Parser::parseType() {
- if (match(TOK_STAR)) { return typePool->internPtrSingle(parseType()); }
+ if (match(TOK_STAR)) {
+ bool ptrConst = false;
+ if (match(TOK_CONST)) {
+ ptrConst = true;
+ } else if (!match(TOK_MUT)) {
+ throw std::runtime_error(
+ "Expected `const` or `mut` after `*` (e.g. `*const T`, "
+ "`*mut T`)");
+ }
+ // Optional `[]` promotes to many-item form. We need to commit to
+ // "many" only when the next two tokens are exactly `[ ]`; a `[`
+ // followed by a number is the start of a `[N]T` element type
+ // (single-item pointer to a fixed array).
+ bool isMany = false;
+ if (check(TOK_OPEN_BRACKET)) {
+ int saved = current;
+ advance(); // consume `[`
+ if (match(TOK_CLOSE_BRACKET)) {
+ isMany = true;
+ } else {
+ current = saved; // rewind: `[` belongs to element type
+ }
+ }
+ TypeIdx elem = parseType();
+ (void)ptrConst; // mutability not enforced yet
+ return isMany ? typePool->internPtrMany(elem)
+ : typePool->internPtrSingle(elem);
+ }
if (match(TOK_OPEN_BRACKET)) {
+ // `[]T` — slice. No tag.
if (match(TOK_CLOSE_BRACKET)) {
- (void)match(TOK_CONST);
return typePool->internSlice(parseType());
}
- if (match(TOK_STAR)) {
- consume(TOK_CLOSE_BRACKET, "Expected ']' after '[*'");
- return typePool->internPtrMany(parseType());
- }
- consume(TOK_NUMBER, "Expected size or ']' after '['");
+ // `[N]T` — fixed-size array. No tag.
+ consume(TOK_NUMBER, "Expected size or `]` after `[`");
uint32_t len = static_cast(std::stoul(previous().lexeme));
- consume(TOK_CLOSE_BRACKET, "Expected ']' after array size");
+ consume(TOK_CLOSE_BRACKET, "Expected `]` after array size");
return typePool->internArray(parseType(), len);
}
- if (match(TOK_CONST)) { return parseType(); }
if (match(TOK_TYPE)) {
const std::string &s = previous().lexeme;
if (s == "u8") return BuiltinType::U8;
diff --git a/src/token.h b/src/token.h
index 88c636d..5f5f371 100644
--- a/src/token.h
+++ b/src/token.h
@@ -28,6 +28,7 @@ enum TokenType {
TOK_NUMBER,
TOK_CONST,
TOK_VAR,
+ TOK_MUT,
TOK_EQUAL,
TOK_TYPE,
TOK_IF,
diff --git a/tests/cabi/README.md b/tests/cabi/README.md
index a916535..629f296 100644
--- a/tests/cabi/README.md
+++ b/tests/cabi/README.md
@@ -72,12 +72,12 @@ The previous `run_cabi_tests.sh` was removed — it masked failures with a final
```jam
// Future syntax for calling C functions
-extern "C" fn printf(format: *u8, ...) i32;
-extern "C" fn malloc(size: u64) *u8;
-extern "C" fn free(ptr: *u8);
+extern "C" fn printf(format: *const u8, ...) i32;
+extern "C" fn malloc(size: u64) *var u8;
+extern "C" fn free(ptr: *var u8);
fn main() u8 {
- var msg: *u8 = "Hello from Jam!\n";
+ var msg: *const u8 = "Hello from Jam!\n";
printf(msg);
return 0;
}
diff --git a/tests/unit/test_mixed_slices.jam b/tests/unit/test_mixed_slices.jam
index c5c2b85..7e27531 100644
--- a/tests/unit/test_mixed_slices.jam
+++ b/tests/unit/test_mixed_slices.jam
@@ -5,7 +5,6 @@ fn test_string_and_slice_params(message: str, data: []u8) str {
fn test_string_slice_conversion() []u8 {
const text: str = "convert me";
- // In the future, this would convert str to []u8
const bytes: []u8 = text;
return bytes;
}
@@ -39,4 +38,4 @@ fn test_slice_assignment() []u8 {
const original: []u8 = "original";
var copy: []u8 = original;
return copy;
-}
\ No newline at end of file
+}
diff --git a/tests/unit/test_pointer.jam b/tests/unit/test_pointer.jam
index 3556d2b..ee84490 100644
--- a/tests/unit/test_pointer.jam
+++ b/tests/unit/test_pointer.jam
@@ -2,13 +2,13 @@ const { assert } = import("test");
fn pointerToLocal() u8 {
var x: u8 = 99;
- var p: *u8 = &x;
+ var p: *const u8 = &x;
return p.*;
}
fn writeThroughPointer() u8 {
var x: u8 = 0;
- var p: *u8 = &x;
+ var p: *mut u8 = &x;
p.* = 77;
return x;
}
@@ -19,7 +19,7 @@ fn pointerToArrayElement() u8 {
arr[1] = 22;
arr[2] = 33;
arr[3] = 44;
- var p: *u8 = &arr[2];
+ var p: *const u8 = &arr[2];
return p.*;
}
@@ -29,12 +29,12 @@ fn writeThroughArrayElementPointer() u8 {
arr[1] = 2;
arr[2] = 3;
arr[3] = 4;
- var p: *u8 = &arr[1];
+ var p: *mut u8 = &arr[1];
p.* = 99;
return arr[1];
}
-fn loadFromPtrParam(p: *u8) u8 {
+fn loadFromPtrParam(p: *const u8) u8 {
return p.*;
}
@@ -52,7 +52,7 @@ fn addressOfStructField() u8 {
var pt: Point = undefined;
pt.x = 10;
pt.y = 20;
- var px: *u8 = &pt.x;
+ var px: *mut u8 = &pt.x;
px.* = 200;
return pt.x;
}
@@ -63,7 +63,7 @@ fn manyItemPointerIndex() u8 {
arr[1] = 6;
arr[2] = 7;
arr[3] = 8;
- var p: [*]u8 = &arr[1];
+ var p: *mut[] u8 = &arr[1];
return p[2];
}
@@ -73,7 +73,7 @@ fn manyItemPointerWrite() u8 {
arr[1] = 2;
arr[2] = 3;
arr[3] = 4;
- var p: [*]u8 = &arr[0];
+ var p: *mut[] u8 = &arr[0];
p[3] = 88;
return arr[3];
}
diff --git a/tests/unit/test_slice_projection.jam b/tests/unit/test_slice_projection.jam
index 11bf282..0d623c8 100644
--- a/tests/unit/test_slice_projection.jam
+++ b/tests/unit/test_slice_projection.jam
@@ -12,17 +12,17 @@ fn strLenLiteral() u64 {
fn sliceFirstByte() u8 {
const s: []u8 = "hi";
- var p: [*]u8 = s.ptr;
+ var p: *const[] u8 = s.ptr;
return p[0];
}
fn sliceSecondByte() u8 {
const s: []u8 = "hi";
- var p: [*]u8 = s.ptr;
+ var p: *const[] u8 = s.ptr;
return p[1];
}
-fn loadFirstByte(p: [*]u8) u8 {
+fn loadFirstByte(p: *const[] u8) u8 {
return p[0];
}
diff --git a/tests/unit/test_slices.jam b/tests/unit/test_slices.jam
index 12599d4..025b77f 100644
--- a/tests/unit/test_slices.jam
+++ b/tests/unit/test_slices.jam
@@ -1,4 +1,4 @@
-// Test byte slices []u8
+// Test byte slices ([]u8 / []u8)
fn test_u8_slice_basic() []u8 {
const data: []u8 = "hello";
return data;
@@ -25,8 +25,6 @@ fn test_u8_slice_const() []u8 {
// Test other slice types
fn test_u16_slice() []u16 {
- // This would need array literal syntax in the future
- // For now, just test the type parsing
var data: []u16 = undefined;
return data;
}
@@ -41,8 +39,8 @@ fn test_bool_slice() []bool {
return data;
}
-// Test nested slice types (for future)
+// Test nested slice types: outer-only mutability covers the inner.
fn test_slice_of_slices() [][]u8 {
var data: [][]u8 = undefined;
return data;
-}
\ No newline at end of file
+}
diff --git a/tests/unit/test_varargs.jam b/tests/unit/test_varargs.jam
index e3abcf7..c20edb8 100644
--- a/tests/unit/test_varargs.jam
+++ b/tests/unit/test_varargs.jam
@@ -1,4 +1,4 @@
-extern fn snprintf(buf: [*]u8, size: u64, format: [*]const u8, ...) i32;
+extern fn snprintf(buf: *mut[] u8, size: u64, format: *const[] u8, ...) i32;
fn snprintfNoExtra() i32 {
var buf: [16]u8 = undefined;