added function pointers

This commit is contained in:
igor
2026-02-18 10:59:37 +01:00
parent 28bc0e3842
commit 8bb292f49b
4 changed files with 70 additions and 4 deletions

View File

@@ -43,6 +43,7 @@ This document reflects the implementation that ships in this repository today (`
- `while <condition> do <body> end`; the conditional block lives between `while` and `do` and re-runs every iteration. - `while <condition> do <body> end`; the conditional block lives between `while` and `do` and re-runs every iteration.
- `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. - `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. - `label name` / `goto name` perform local jumps within a definition.
- `&name` pushes a pointer to word `name` (its callable code label). This is intended for indirect control flow; `&name jmp` performs a tail jump to that word and is compatible with `--ct-run-main`.
- **Text macros** `macro name [param_count] ... ;` records raw tokens until `;`. `$0`, `$1`, ... expand to positional arguments. Macro definitions cannot nest (attempting to start another `macro` while recording raises a parse error). - **Text macros** `macro name [param_count] ... ;` records raw tokens until `;`. `$0`, `$1`, ... expand to positional arguments. Macro definitions cannot nest (attempting to start another `macro` while recording raises a parse error).
- **Struct builder** `struct Foo ... end` emits `<Foo>.size`, `<Foo>.field.size`, `<Foo>.field.offset`, `<Foo>.field@`, and `<Foo>.field!` helpers. Layout is tightly packed with no implicit padding. - **Struct builder** `struct Foo ... end` emits `<Foo>.size`, `<Foo>.field.size`, `<Foo>.field.offset`, `<Foo>.field@`, and `<Foo>.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. - **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.

61
main.py
View File

@@ -742,6 +742,13 @@ class Parser:
if self._try_literal(token): if self._try_literal(token):
return return
if token.lexeme.startswith("&"):
target_name = token.lexeme[1:]
if not target_name:
raise ParseError(f"missing word name after '&' at {token.line}:{token.column}")
self._append_op(Op(op="word_ptr", data=target_name))
return
word = self.dictionary.lookup(token.lexeme) word = self.dictionary.lookup(token.lexeme)
if word and word.immediate: if word and word.immediate:
if word.macro: if word.macro:
@@ -1174,6 +1181,10 @@ class _CTVMJump(Exception):
self.target_ip = target_ip self.target_ip = target_ip
class _CTVMReturn(Exception):
"""Raised to return from the current word frame in _execute_nodes."""
class _CTVMExit(Exception): class _CTVMExit(Exception):
"""Raised by the ``exit`` intrinsic to stop compile-time execution.""" """Raised by the ``exit`` intrinsic to stop compile-time execution."""
@@ -1708,7 +1719,7 @@ class CompileTimeVM:
self._execute_nodes(definition.body, _defn=definition) self._execute_nodes(definition.body, _defn=definition)
except CompileTimeError: except CompileTimeError:
raise raise
except (_CTVMJump, _CTVMExit): except (_CTVMJump, _CTVMExit, _CTVMReturn):
raise raise
except ParseError as exc: except ParseError as exc:
raise CompileTimeError(f"{exc}\ncompile-time stack: {' -> '.join(self.call_stack)}") from None raise CompileTimeError(f"{exc}\ncompile-time stack: {' -> '.join(self.call_stack)}") from None
@@ -2341,6 +2352,9 @@ class CompileTimeVM:
self.call_stack.pop() self.call_stack.pop()
ip = jmp.target_ip ip = jmp.target_ip
continue continue
except _CTVMReturn:
self.call_stack.pop()
return
finally: finally:
if self.call_stack and self.call_stack[-1] == word.name: if self.call_stack and self.call_stack[-1] == word.name:
self.call_stack.pop() self.call_stack.pop()
@@ -2365,6 +2379,8 @@ class CompileTimeVM:
except _CTVMJump as jmp: except _CTVMJump as jmp:
ip = jmp.target_ip ip = jmp.target_ip
continue continue
except _CTVMReturn:
return
ip += 1 ip += 1
continue continue
@@ -2406,6 +2422,19 @@ class CompileTimeVM:
except _CTVMJump as jmp: except _CTVMJump as jmp:
ip = jmp.target_ip ip = jmp.target_ip
continue continue
except _CTVMReturn:
return
ip += 1
continue
if kind == "word_ptr":
target_name = str(node.data)
target_word = _dict_lookup(target_name)
if target_word is None:
raise ParseError(
f"unknown word '{target_name}' referenced by pointer during compile-time execution"
)
_push(self._handles.store(target_word))
ip += 1 ip += 1
continue continue
@@ -3002,7 +3031,7 @@ class Assembler:
refs: Set[str] = set() refs: Set[str] = set()
if isinstance(definition, Definition): if isinstance(definition, Definition):
for node in definition.body: for node in definition.body:
if node.op == "word": if node.op in {"word", "word_ptr"}:
refs.add(str(node.data)) refs.add(str(node.data))
edges[definition.name] = refs edges[definition.name] = refs
@@ -3261,6 +3290,10 @@ class Assembler:
self._emit_wordref(str(data), builder) self._emit_wordref(str(data), builder)
return return
if kind == "word_ptr":
self._emit_wordptr(str(data), builder)
return
if kind == "branch_zero": if kind == "branch_zero":
self._emit_branch_zero(str(data), builder) self._emit_branch_zero(str(data), builder)
return return
@@ -3438,6 +3471,16 @@ class Assembler:
else: else:
builder.emit(f" call {sanitize_label(name)}") builder.emit(f" call {sanitize_label(name)}")
def _emit_wordptr(self, name: str, builder: FunctionEmitter) -> None:
word = self.dictionary.lookup(name)
if word is None:
suffix = f" while emitting '{self._emit_stack[-1]}'" if self._emit_stack else ""
raise CompileError(f"unknown word '{name}'{suffix}")
if getattr(word, "is_extern", False):
builder.push_label(name)
return
builder.push_label(sanitize_label(name))
def _emit_branch_zero(self, target: str, builder: FunctionEmitter) -> None: def _emit_branch_zero(self, target: str, builder: FunctionEmitter) -> None:
builder.pop_to("rax") builder.pop_to("rax")
builder.emit(" test rax, rax") builder.emit(" test rax, rax")
@@ -4279,8 +4322,18 @@ def _rt_exit(vm: CompileTimeVM) -> None:
def _rt_jmp(vm: CompileTimeVM) -> None: def _rt_jmp(vm: CompileTimeVM) -> None:
target_ip = vm.pop_int() target = vm.pop()
raise _CTVMJump(target_ip) resolved = vm._resolve_handle(target)
if isinstance(resolved, Word):
vm._call_word(resolved)
raise _CTVMReturn()
if isinstance(resolved, bool):
raise _CTVMJump(int(resolved))
if not isinstance(resolved, int):
raise ParseError(
f"jmp expects an address or word pointer, got {type(resolved).__name__}: {resolved!r}"
)
raise _CTVMJump(resolved)
def _rt_syscall(vm: CompileTimeVM) -> None: def _rt_syscall(vm: CompileTimeVM) -> None:

View File

@@ -0,0 +1 @@
via word ptr

11
tests/word_ptr_jmp.sl Normal file
View File

@@ -0,0 +1,11 @@
import stdlib/stdlib.sl
import stdlib/io.sl
word target
"via word ptr\n" puts
end
word main
&target
jmp
end