From e7abc47cdf5438cb77c4f6b36e6eb01ab7cc275e Mon Sep 17 00:00:00 2001 From: IgorCielniak Date: Thu, 8 Jan 2026 18:34:34 +0100 Subject: [PATCH] ststic arrays and dynamic arrays --- main.py | 90 +++++++++++++- stdlib/arr.sl | 248 +++++++++++++++++++++++++++++++++++++ tests/arr_dynamic.expected | 6 + tests/arr_dynamic.sl | 23 ++++ tests/arr_dynamic.test | 1 + tests/list.expected | 2 + tests/list.sl | 9 ++ tests/list.test | 1 + 8 files changed, 376 insertions(+), 4 deletions(-) create mode 100644 stdlib/arr.sl create mode 100644 tests/arr_dynamic.expected create mode 100644 tests/arr_dynamic.sl create mode 100644 tests/arr_dynamic.test create mode 100644 tests/list.expected create mode 100644 tests/list.sl create mode 100644 tests/list.test diff --git a/main.py b/main.py index 89a4e84..0920411 100644 --- a/main.py +++ b/main.py @@ -439,10 +439,12 @@ class Parser: if self._handle_macro_recording(token): continue lexeme = token.lexeme - if lexeme == ":": - raise ParseError( - f"':' definitions are no longer supported; use 'word ... end' at {token.line}:{token.column}" - ) + if lexeme == "[": + self._handle_list_begin() + continue + if lexeme == "]": + self._handle_list_end(token) + continue if lexeme == "word": self._begin_definition(token, terminator="end") continue @@ -495,6 +497,16 @@ class Parser: module.variables = dict(self.variable_labels) return module + def _handle_list_begin(self) -> None: + label = self._new_label("list") + self._append_op(Op(op="list_begin", data=label)) + self._push_control({"type": "list", "label": label}) + + def _handle_list_end(self, token: Token) -> None: + entry = self._pop_control(("list",)) + label = entry["label"] + self._append_op(Op(op="list_end", data=label)) + # Internal helpers --------------------------------------------------------- def _parse_extern(self, token: Token) -> None: @@ -1703,6 +1715,72 @@ class Assembler: self._emit_for_next(data, builder) return + if kind == "list_begin": + builder.comment("list begin") + builder.emit(" mov rax, [rel list_capture_sp]") + builder.emit(" lea rdx, [rel list_capture_stack]") + builder.emit(" mov [rdx + rax*8], r12") + builder.emit(" inc rax") + builder.emit(" mov [rel list_capture_sp], rax") + return + + if kind == "list_end": + base = str(data) + loop_label = f"{base}_copy_loop" + done_label = f"{base}_copy_done" + + builder.comment("list end") + # pop capture start pointer + builder.emit(" mov rax, [rel list_capture_sp]") + builder.emit(" dec rax") + builder.emit(" mov [rel list_capture_sp], rax") + builder.emit(" lea r11, [rel list_capture_stack]") + builder.emit(" mov rbx, [r11 + rax*8]") + # count = (start_r12 - r12) / 8 + builder.emit(" mov rcx, rbx") + builder.emit(" sub rcx, r12") + builder.emit(" shr rcx, 3") + builder.emit(" mov [rel list_capture_tmp], rcx") + + # bytes = (count + 1) * 8 + builder.emit(" mov rsi, rcx") + builder.emit(" inc rsi") + builder.emit(" shl rsi, 3") + + # mmap(NULL, bytes, PROT_READ|PROT_WRITE, MAP_PRIVATE|MAP_ANON, -1, 0) + builder.emit(" xor rdi, rdi") + builder.emit(" mov rdx, 3") + builder.emit(" mov r10, 34") + builder.emit(" mov r8, -1") + builder.emit(" xor r9, r9") + builder.emit(" mov rax, 9") + builder.emit(" syscall") + + # store length + builder.emit(" mov rdx, [rel list_capture_tmp]") + builder.emit(" mov [rax], rdx") + + # copy elements, preserving original push order + builder.emit(" xor rcx, rcx") + builder.emit(f"{loop_label}:") + builder.emit(" cmp rcx, rdx") + builder.emit(f" je {done_label}") + builder.emit(" mov r8, rdx") + builder.emit(" dec r8") + builder.emit(" sub r8, rcx") + builder.emit(" shl r8, 3") + builder.emit(" mov r9, [r12 + r8]") + builder.emit(" mov [rax + 8 + rcx*8], r9") + builder.emit(" inc rcx") + builder.emit(f" jmp {loop_label}") + builder.emit(f"{done_label}:") + + # drop captured values and push list pointer + builder.emit(" mov r12, rbx") + builder.emit(" sub r12, 8") + builder.emit(" mov [r12], rax") + return + raise CompileError(f"unsupported op {node!r}") def _emit_wordref(self, name: str, builder: FunctionEmitter) -> None: @@ -1855,6 +1933,10 @@ class Assembler: "print_buf_end:", "align 16", "persistent: resb 64", + "align 16", + "list_capture_sp: resq 1", + "list_capture_tmp: resq 1", + "list_capture_stack: resq 1024", ] def write_asm(self, emission: Emission, path: Path) -> None: diff --git a/stdlib/arr.sl b/stdlib/arr.sl new file mode 100644 index 0000000..3f513a0 --- /dev/null +++ b/stdlib/arr.sl @@ -0,0 +1,248 @@ +# Dynamic arrays (qword elements) +# +# Layout at address `arr`: +# [arr + 0] len (qword) +# [arr + 8] cap (qword) +# [arr + 16] data (qword) = arr + 24 +# [arr + 24] elements (cap * 8 bytes) +# +# Allocation: mmap; free: munmap. +# Growth: allocate new block, copy elements, munmap old block. + +# : arr_new ( cap -- arr ) +:asm arr_new { + mov r14, [r12] ; requested cap + cmp r14, 1 + jge .cap_ok + mov r14, 1 +.cap_ok: + ; bytes = 24 + cap*8 + mov rsi, r14 + shl rsi, 3 + add rsi, 24 + + ; mmap(NULL, bytes, PROT_READ|PROT_WRITE, MAP_PRIVATE|MAP_ANON, -1, 0) + xor rdi, rdi + mov rdx, 3 + mov r10, 34 + mov r8, -1 + xor r9, r9 + mov rax, 9 + syscall + + ; header + mov qword [rax], 0 + mov [rax + 8], r14 + lea rbx, [rax + 24] + mov [rax + 16], rbx + + ; replace cap with arr pointer + mov [r12], rax + ret +} +; + +# : arr_len ( arr -- len ) +:asm arr_len { + mov rax, [r12] + mov rax, [rax] + mov [r12], rax + ret +} +; + +# : arr_cap ( arr -- cap ) +:asm arr_cap { + mov rax, [r12] + mov rax, [rax + 8] + mov [r12], rax + ret +} +; + +# : arr_data ( arr -- ptr ) +:asm arr_data { + mov rax, [r12] + mov rax, [rax + 16] + mov [r12], rax + ret +} +; + +# : arr_free ( arr -- ) +:asm arr_free { + mov rbx, [r12] ; base + mov rcx, [rbx + 8] ; cap + mov rsi, rcx + shl rsi, 3 + add rsi, 24 + mov rdi, rbx + mov rax, 11 + syscall + add r12, 8 ; drop arr + ret +} +; + +# : arr_reserve ( cap arr -- arr ) +# Ensures capacity >= cap; returns (possibly moved) arr pointer. +:asm arr_reserve { + mov rbx, [r12] ; arr + mov r14, [r12 + 8] ; requested cap + cmp r14, 1 + jge .req_ok + mov r14, 1 +.req_ok: + mov rdx, [rbx + 8] ; old cap + cmp rdx, r14 + jae .no_change + + ; alloc new block: bytes = 24 + reqcap*8 + mov rsi, r14 + shl rsi, 3 + add rsi, 24 + xor rdi, rdi + mov rdx, 3 + mov r10, 34 + mov r8, -1 + xor r9, r9 + mov rax, 9 + syscall + + mov r10, rax ; new base + lea r9, [r10 + 24] ; new data + + ; header + mov r8, [rbx] ; len + mov [r10], r8 + mov [r10 + 8], r14 + mov [r10 + 16], r9 + + ; copy elements from old data + mov r11, [rbx + 16] ; old data + xor rcx, rcx ; i +.copy_loop: + cmp rcx, r8 + je .copy_done + mov rdx, [r11 + rcx*8] + mov [r9 + rcx*8], rdx + inc rcx + jmp .copy_loop +.copy_done: + + ; munmap old block + mov rsi, [rbx + 8] + shl rsi, 3 + add rsi, 24 + mov rdi, rbx + mov rax, 11 + syscall + + ; return new arr only + mov [r12 + 8], r10 + add r12, 8 + ret + +.no_change: + ; drop cap, keep arr + mov [r12 + 8], rbx + add r12, 8 + ret +} +; + +# : arr_push ( x arr -- arr ) +:asm arr_push { + mov rbx, [r12] ; arr + mov rcx, [rbx] ; len + mov rdx, [rbx + 8] ; cap + cmp rcx, rdx + jb .have_space + + ; grow: newcap = max(1, cap) * 2 + mov r14, rdx + cmp r14, 1 + jae .cap_ok + mov r14, 1 +.cap_ok: + shl r14, 1 + + ; alloc new block + mov rsi, r14 + shl rsi, 3 + add rsi, 24 + xor rdi, rdi + mov rdx, 3 + mov r10, 34 + mov r8, -1 + xor r9, r9 + mov rax, 9 + syscall + + mov r10, rax ; new base + lea r9, [r10 + 24] ; new data + + ; header + mov rcx, [rbx] ; len (reload; syscall clobbers rcx) + mov [r10], rcx + mov [r10 + 8], r14 + mov [r10 + 16], r9 + + ; copy old data + mov r11, [rbx + 16] ; old data + xor r8, r8 +.push_copy_loop: + cmp r8, rcx + je .push_copy_done + mov rdx, [r11 + r8*8] + mov [r9 + r8*8], rdx + inc r8 + jmp .push_copy_loop +.push_copy_done: + + ; munmap old block + mov rsi, [rbx + 8] + shl rsi, 3 + add rsi, 24 + mov rdi, rbx + mov rax, 11 + syscall + + ; switch to new base + mov rbx, r10 + +.have_space: + ; store element at data[len] + mov r9, [rbx + 16] + mov rax, [r12 + 8] ; x + mov rcx, [rbx] ; len + mov [r9 + rcx*8], rax + inc rcx + mov [rbx], rcx + + ; return arr only + mov [r12 + 8], rbx + add r12, 8 + ret +} +; + +# : arr_pop ( arr -- x arr ) +:asm arr_pop { + mov rbx, [r12] ; arr + mov rcx, [rbx] ; len + test rcx, rcx + jz .empty + dec rcx + mov [rbx], rcx + mov rdx, [rbx + 16] ; data + mov rax, [rdx + rcx*8] + jmp .push +.empty: + xor rax, rax +.push: + sub r12, 8 + mov [r12], rax + ret +} +; diff --git a/tests/arr_dynamic.expected b/tests/arr_dynamic.expected new file mode 100644 index 0000000..9062604 --- /dev/null +++ b/tests/arr_dynamic.expected @@ -0,0 +1,6 @@ +1 +3 +4 +10 +20 +30 \ No newline at end of file diff --git a/tests/arr_dynamic.sl b/tests/arr_dynamic.sl new file mode 100644 index 0000000..7784a05 --- /dev/null +++ b/tests/arr_dynamic.sl @@ -0,0 +1,23 @@ +import ../stdlib/stdlib.sl +import ../stdlib/io.sl +import ../stdlib/arr.sl + +word main + 0 arr_new + + dup arr_cap puti cr + + 10 swap arr_push + 20 swap arr_push + 30 swap arr_push + + dup arr_len puti cr + dup arr_cap puti cr + + # print elements via explicit offsets: data[i] = @ (arr_data + i*8) + dup arr_data 0 8 * + @ puti cr + dup arr_data 1 8 * + @ puti cr + dup arr_data 2 8 * + @ puti cr + + arr_free +end diff --git a/tests/arr_dynamic.test b/tests/arr_dynamic.test new file mode 100644 index 0000000..7082399 --- /dev/null +++ b/tests/arr_dynamic.test @@ -0,0 +1 @@ +python main.py tests/arr_dynamic.sl -o /tmp/arr_dynamic > /dev/null && /tmp/arr_dynamic \ No newline at end of file diff --git a/tests/list.expected b/tests/list.expected new file mode 100644 index 0000000..446a462 --- /dev/null +++ b/tests/list.expected @@ -0,0 +1,2 @@ +3 +2 \ No newline at end of file diff --git a/tests/list.sl b/tests/list.sl new file mode 100644 index 0000000..3704f21 --- /dev/null +++ b/tests/list.sl @@ -0,0 +1,9 @@ +import ../stdlib/stdlib.sl +import ../stdlib/io.sl + +word main + [ 1 2 3 4 ] + # element i is at: list_ptr + 8 + i*8 + dup 8 2 * 8 + + @ puti cr # index 2 = 3 + dup 8 1 * 8 + + @ puti cr # index 1 = 2 +end \ No newline at end of file diff --git a/tests/list.test b/tests/list.test new file mode 100644 index 0000000..65b40f4 --- /dev/null +++ b/tests/list.test @@ -0,0 +1 @@ +python main.py tests/list.sl -o /tmp/list > /dev/null && /tmp/list \ No newline at end of file