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;