added function pointers
This commit is contained in:
1
SPEC.md
1
SPEC.md
@@ -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
61
main.py
@@ -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:
|
||||||
|
|||||||
1
tests/word_ptr_jmp.expected
Normal file
1
tests/word_ptr_jmp.expected
Normal file
@@ -0,0 +1 @@
|
|||||||
|
via word ptr
|
||||||
11
tests/word_ptr_jmp.sl
Normal file
11
tests/word_ptr_jmp.sl
Normal 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
|
||||||
Reference in New Issue
Block a user