From ff6af0b783552a10a6fa49db62fcac02861adc7a Mon Sep 17 00:00:00 2001 From: IgorCielniak Date: Wed, 21 Jan 2026 14:26:30 +0100 Subject: [PATCH] update to structs --- SPEC.md | 4 ++-- main.py | 22 +++++++++------------- tests/alloc.sl | 4 ++-- tests/integration_core.sl | 4 ++-- 4 files changed, 15 insertions(+), 19 deletions(-) diff --git a/SPEC.md b/SPEC.md index 78ac540..82ce033 100644 --- a/SPEC.md +++ b/SPEC.md @@ -27,7 +27,7 @@ This document reflects the implementation that ships in this repository today (` - **Memory helpers** – `mem` returns the address of the `persistent` buffer (default 64 bytes). `argc`, `argv`, and `argv@` expose process arguments. `alloc`/`free` wrap `mmap`/`munmap` for general-purpose buffers, while `memcpy` performs byte-wise copies. - **BSS customization** – Compile-time words may call `bss-clear` followed by `bss-append`/`bss-set` to replace the default `.bss` layout (e.g., `tests/bss_override.sl` enlarges `persistent`). - **Strings & buffers** – IO helpers consume explicit `(addr len)` pairs only; there is no implicit NULL contract except for stored literals. -- **Structured data** – `struct:` blocks expand into constants and accessor words (`Foo.bar@`, `Foo.bar!`). Dynamic arrays in `stdlib/arr.sl` allocate `[len, cap, data_ptr, data...]` records via `mmap` and expose `arr_new`, `arr_len`, `arr_cap`, `arr_data`, `arr_push`, `arr_pop`, `arr_reserve`, `arr_free`. +- **Structured data** – `struct` blocks expand into constants and accessor words (`Foo.bar@`, `Foo.bar!`). Dynamic arrays in `stdlib/arr.sl` allocate `[len, cap, data_ptr, data...]` records via `mmap` and expose `arr_new`, `arr_len`, `arr_cap`, `arr_data`, `arr_push`, `arr_pop`, `arr_reserve`, `arr_free`. ## 5. Definitions, Control Flow, and Syntax Sugar - **Word definitions** – Always `word name ... end`. Redefinitions overwrite the previous entry (a warning prints to stderr). `inline word name ... end` marks the definition for inline expansion; recursive inline calls are rejected. `immediate` and `compile-only` apply to the most recently defined word. @@ -37,7 +37,7 @@ This document reflects the implementation that ships in this repository today (` - `n for ... end`; the loop count is popped, stored on the return stack, and decremented each pass. The compile-time word `i` exposes the loop index inside macros. - `label name` / `goto name` perform local jumps within a definition. - **Text macros** – `macro name [param_count] ... ;` records raw tokens until `;`. `$1`, `$2`, ... expand to positional arguments. Macro definitions cannot nest (attempting to start another `macro` while recording raises a parse error). -- **Struct builder** – `struct: Foo ... ;struct` emits `.size`, `.field.size`, `.field.offset`, `.field@`, and `.field!` helpers. Layout is tightly packed with no implicit padding. +- **Struct builder** – `struct Foo ... end` emits `.size`, `.field.size`, `.field.offset`, `.field@`, and `.field!` helpers. Layout is tightly packed with no implicit padding. - **With-blocks** – `with a b in ... end` rewrites occurrences of `a`/`b` into accesses against hidden global cells (`__with_a`). On entry the block pops the named values and stores them in those cells; reads compile to `@`, writes to `!`. Because the cells live in `.data`, the slots persist across calls and are not re-entrant. - **List literals** – `[ values ... ]` capture the current stack slice, allocate storage (`mmap`), copy the elements, and push the pointer. The record stores `len` at offset 0 and items afterwards so user code can fetch length via `@` and iterate. - **Compile-time execution** – `compile-time foo` runs `foo` immediately but still emits it (if inside a definition). Immediate words always execute during parsing; ordinary words emit `word` ops for later code generation. diff --git a/main.py b/main.py index d05ccbe..f3fba4b 100644 --- a/main.py +++ b/main.py @@ -3169,19 +3169,21 @@ PY_EXEC_GLOBALS: Dict[str, Any] = { def macro_struct_begin(ctx: MacroContext) -> Optional[List[Op]]: parser = ctx.parser if parser._eof(): - raise ParseError("struct name missing after 'struct:'") + raise ParseError("struct name missing after 'struct'") name_token = parser.next_token() struct_name = name_token.lexeme fields: List[StructField] = [] current_offset = 0 while True: if parser._eof(): - raise ParseError("unterminated struct definition (missing ';struct')") + raise ParseError("unterminated struct definition (missing 'end')") token = parser.next_token() - if token.lexeme == ";struct": + if token.lexeme == "end": break if token.lexeme != "field": - raise ParseError(f"expected 'field' or ';struct' in struct '{struct_name}' definition") + raise ParseError( + f"expected 'field' or 'end' in struct '{struct_name}' definition" + ) if parser._eof(): raise ParseError("field name missing in struct definition") field_name_token = parser.next_token() @@ -3220,11 +3222,6 @@ def macro_struct_begin(ctx: MacroContext) -> Optional[List[Op]]: parser.tokens[parser.pos:parser.pos] = generated return None - -def macro_struct_end(ctx: MacroContext) -> Optional[List[Op]]: - raise ParseError("';struct' must follow a 'struct:' block") - - def macro_here(ctx: MacroContext) -> Optional[List[Op]]: tok = ctx.parser._last_token if tok is None: @@ -3244,8 +3241,7 @@ def bootstrap_dictionary() -> Dictionary: dictionary.register(Word(name="here", immediate=True, macro=macro_here)) dictionary.register(Word(name="with", immediate=True, macro=macro_with)) dictionary.register(Word(name="macro", immediate=True, macro=macro_begin_text_macro)) - dictionary.register(Word(name="struct:", immediate=True, macro=macro_struct_begin)) - dictionary.register(Word(name=";struct", immediate=True, macro=macro_struct_end)) + dictionary.register(Word(name="struct", immediate=True, macro=macro_struct_begin)) _register_compile_time_primitives(dictionary) return dictionary @@ -3602,7 +3598,7 @@ def run_repl( print(" :seteditor [cmd] show/set editor command (default from $EDITOR or vim)") print(" :quit | :q exit the REPL") print("[repl] free-form input:") - print(" definitions (word/:asm/:py/extern/macro/struct:) extend the session") + print(" definitions (word/:asm/:py/extern/macro/struct) extend the session") print(" imports add to session imports") print(" other lines run immediately in an isolated temp program (not saved)") print(" multiline: end lines with \\ to continue; finish with a non-\\ line") @@ -3752,7 +3748,7 @@ def run_repl( block_stripped = block.lstrip() first_tok = block_stripped.split(None, 1)[0] if block_stripped else "" - is_definition = first_tok in {"word", ":asm", ":py", "extern", "macro", "struct:"} + is_definition = first_tok in {"word", ":asm", ":py", "extern", "macro", "struct"} is_import = first_tok == "import" if is_import: diff --git a/tests/alloc.sl b/tests/alloc.sl index 82b3dfd..e34f791 100644 --- a/tests/alloc.sl +++ b/tests/alloc.sl @@ -8,10 +8,10 @@ word test-mem-alloc 4096 free # free the memory end -struct: Point +struct Point field x 8 field y 8 -;struct +end word main 32 alloc # allocate 32 bytes (enough for a Point struct) diff --git a/tests/integration_core.sl b/tests/integration_core.sl index c5a894b..a6810bd 100644 --- a/tests/integration_core.sl +++ b/tests/integration_core.sl @@ -27,10 +27,10 @@ macro defadder 3 defconst MAGIC 99 defadder add13 5 8 -struct: Point +struct Point field x 8 field y 8 -;struct +end word test-add 5 7 + puti cr