added an option to redefine control structures
This commit is contained in:
7
SPEC.md
7
SPEC.md
@@ -40,9 +40,11 @@ This document reflects the implementation that ships in this repository today (`
|
|||||||
- **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.
|
- **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.
|
||||||
- **Priority-based redefinition** – Use `priority <int>` before `word`, `:asm`, `:py`, or `extern` to control conflicts for the same name. Higher priority wins; lower-priority definitions are ignored. Equal priority keeps last definition (with a redefinition warning). The compiler prints a note indicating which priority was selected.
|
- **Priority-based redefinition** – Use `priority <int>` before `word`, `:asm`, `:py`, or `extern` to control conflicts for the same name. Higher priority wins; lower-priority definitions are ignored. Equal priority keeps last definition (with a redefinition warning). The compiler prints a note indicating which priority was selected.
|
||||||
- **Control forms** – Built-in tokens drive code emission:
|
- **Control forms** – Built-in tokens drive code emission:
|
||||||
|
- Default parser-level implementations for `if`, `else`, `for`, `while`, and `do` are always available.
|
||||||
|
- Import `stdlib/control.sl` to override these defaults with custom compile-time words; when an override is active, the compiler warns and uses the custom implementation.
|
||||||
- `if ... end` and `if ... else ... end`. To express additional branches, place `if` on the same line as the preceding `else` (e.g., `else <condition> if ...`); the reader treats that form as an implicit chained clause, so each inline `if` consumes one flag and jumps past later clauses on success.
|
- `if ... end` and `if ... else ... end`. To express additional branches, place `if` on the same line as the preceding `else` (e.g., `else <condition> if ...`); the reader treats that form as an implicit chained clause, so each inline `if` consumes one flag and jumps past later clauses on success.
|
||||||
- `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 and cannot be used in runtime-emitted words.
|
||||||
- `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`.
|
- `&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).
|
||||||
@@ -57,6 +59,8 @@ This document reflects the implementation that ships in this repository today (`
|
|||||||
- Strings/numbers: `string=`, `string-length`, `string-append`, `string>number`, `int>string`.
|
- Strings/numbers: `string=`, `string-length`, `string-append`, `string>number`, `int>string`.
|
||||||
- Lexer utilities: `lexer-new`, `lexer-pop`, `lexer-peek`, `lexer-expect`, `lexer-collect-brace`, `lexer-push-back` (used by `libs/fn.sl` to parse signatures and infix expressions).
|
- Lexer utilities: `lexer-new`, `lexer-pop`, `lexer-peek`, `lexer-expect`, `lexer-collect-brace`, `lexer-push-back` (used by `libs/fn.sl` to parse signatures and infix expressions).
|
||||||
- Token management: `next-token`, `peek-token`, `inject-tokens`, `token-lexeme`, `token-from-lexeme`.
|
- Token management: `next-token`, `peek-token`, `inject-tokens`, `token-lexeme`, `token-from-lexeme`.
|
||||||
|
- Control-frame helpers: `ct-control-frame-new`, `ct-control-get`, `ct-control-set`, `ct-control-push`, `ct-control-pop`, `ct-control-peek`, `ct-control-depth`, `ct-control-add-close-op`, `ct-new-label`, `ct-emit-op`, `ct-last-token-line`.
|
||||||
|
- Control registration: `ct-register-block-opener`, `ct-unregister-block-opener`, `ct-register-control-override`, `ct-unregister-control-override`.
|
||||||
- Reader hooks: `set-token-hook` installs a word that receives each token (pushed as a `Token` object) and must leave a truthy handled flag; `clear-token-hook` disables it. `libs/fn.sl`'s `extend-syntax` demonstrates rewriting `foo(1, 2)` into ordinary word calls.
|
- Reader hooks: `set-token-hook` installs a word that receives each token (pushed as a `Token` object) and must leave a truthy handled flag; `clear-token-hook` disables it. `libs/fn.sl`'s `extend-syntax` demonstrates rewriting `foo(1, 2)` into ordinary word calls.
|
||||||
- Prelude/BSS control: `prelude-clear`, `prelude-append`, `prelude-set`, `bss-clear`, `bss-append`, `bss-set` let user code override the `_start` stub or `.bss` layout.
|
- Prelude/BSS control: `prelude-clear`, `prelude-append`, `prelude-set`, `bss-clear`, `bss-append`, `bss-set` let user code override the `_start` stub or `.bss` layout.
|
||||||
- Definition helpers: `emit-definition` injects a `word ... end` definition on the fly (used by the struct macro). `parse-error` raises a custom diagnostic.
|
- Definition helpers: `emit-definition` injects a `word ... end` definition on the fly (used by the struct macro). `parse-error` raises a custom diagnostic.
|
||||||
@@ -74,6 +78,7 @@ This document reflects the implementation that ships in this repository today (`
|
|||||||
|
|
||||||
## 8. Standard Library Overview (`stdlib/`)
|
## 8. Standard Library Overview (`stdlib/`)
|
||||||
- **`core.sl`** – Stack shuffles, integer arithmetic, comparisons, boolean ops, memory access, syscall stubs (`mmap`, `munmap`, `exit`), argument helpers (`argc`, `argv`, `argv@`), and pointer helpers (`mem`).
|
- **`core.sl`** – Stack shuffles, integer arithmetic, comparisons, boolean ops, memory access, syscall stubs (`mmap`, `munmap`, `exit`), argument helpers (`argc`, `argv`, `argv@`), and pointer helpers (`mem`).
|
||||||
|
- **`control.sl`** – Optional custom control-structure words (`if`, `else`, `for`, `while`, `do`) that can override parser defaults when imported.
|
||||||
- **`mem.sl`** – `alloc`/`free` wrappers around `mmap`/`munmap` plus a byte-wise `memcpy` used by higher-level utilities.
|
- **`mem.sl`** – `alloc`/`free` wrappers around `mmap`/`munmap` plus a byte-wise `memcpy` used by higher-level utilities.
|
||||||
- **`io.sl`** – `read_file`, `write_file`, `read_stdin`, `write_buf`, `ewrite_buf`, `putc`, `puti`, `puts`, `eputs`, and a smart `print` that detects `(addr,len)` pairs located inside the default `.data` region.
|
- **`io.sl`** – `read_file`, `write_file`, `read_stdin`, `write_buf`, `ewrite_buf`, `putc`, `puti`, `puts`, `eputs`, and a smart `print` that detects `(addr,len)` pairs located inside the default `.data` region.
|
||||||
- **`utils.sl`** – String and number helpers (`strcmp`, `strconcat`, `strlen`, `digitsN>num`, `toint`, `count_digits`, `tostr`).
|
- **`utils.sl`** – String and number helpers (`strcmp`, `strconcat`, `strlen`, `digitsN>num`, `toint`, `count_digits`, `tostr`).
|
||||||
|
|||||||
460
main.py
460
main.py
@@ -442,6 +442,7 @@ _PEEPHOLE_CANCEL_PAIRS = frozenset({
|
|||||||
("inc", "dec"), ("dec", "inc"),
|
("inc", "dec"), ("dec", "inc"),
|
||||||
})
|
})
|
||||||
_PEEPHOLE_SHIFT_OPS = frozenset({"shl", "shr", "sar"})
|
_PEEPHOLE_SHIFT_OPS = frozenset({"shl", "shr", "sar"})
|
||||||
|
_DEFAULT_CONTROL_WORDS = frozenset({"if", "else", "for", "while", "do"})
|
||||||
|
|
||||||
|
|
||||||
class Op:
|
class Op:
|
||||||
@@ -776,6 +777,9 @@ class Parser:
|
|||||||
self.source: str = ""
|
self.source: str = ""
|
||||||
self.macro_recording: Optional[MacroDefinition] = None
|
self.macro_recording: Optional[MacroDefinition] = None
|
||||||
self.control_stack: List[Dict[str, str]] = []
|
self.control_stack: List[Dict[str, str]] = []
|
||||||
|
self.block_openers: Set[str] = {"word", "with", "for", "while", "begin"}
|
||||||
|
self.control_overrides: Set[str] = set()
|
||||||
|
self._warned_control_overrides: Set[str] = set()
|
||||||
self.label_counter = 0
|
self.label_counter = 0
|
||||||
self.token_hook: Optional[str] = None
|
self.token_hook: Optional[str] = None
|
||||||
self._last_token: Optional[Token] = None
|
self._last_token: Optional[Token] = None
|
||||||
@@ -929,29 +933,43 @@ class Parser:
|
|||||||
return label, hidden_word
|
return label, hidden_word
|
||||||
|
|
||||||
def _handle_end_control(self) -> None:
|
def _handle_end_control(self) -> None:
|
||||||
"""Handle unified 'end' for all block types"""
|
"""Close one generic control frame pushed by compile-time words."""
|
||||||
if not self.control_stack:
|
if not self.control_stack:
|
||||||
raise ParseError("unexpected 'end' without matching block")
|
raise ParseError("unexpected 'end' without matching block")
|
||||||
|
|
||||||
entry = self.control_stack.pop()
|
entry = self.control_stack.pop()
|
||||||
|
if not isinstance(entry, dict):
|
||||||
|
raise ParseError("invalid control frame")
|
||||||
|
|
||||||
if entry["type"] in ("if", "elif"):
|
close_ops = entry.get("close_ops")
|
||||||
# For if/elif without a trailing else
|
if close_ops is None:
|
||||||
if "false" in entry:
|
return
|
||||||
self._append_op(_make_op("label", entry["false"]))
|
if not isinstance(close_ops, list):
|
||||||
if "end" in entry:
|
raise ParseError("control frame field 'close_ops' must be a list")
|
||||||
self._append_op(_make_op("label", entry["end"]))
|
|
||||||
elif entry["type"] == "else":
|
for spec in close_ops:
|
||||||
self._append_op(_make_op("label", entry["end"]))
|
op_name: Optional[str] = None
|
||||||
elif entry["type"] == "while":
|
data: Any = None
|
||||||
self._append_op(_make_op("jump", entry["begin"]))
|
if isinstance(spec, dict):
|
||||||
self._append_op(_make_op("label", entry["end"]))
|
candidate = spec.get("op")
|
||||||
elif entry["type"] == "for":
|
if isinstance(candidate, str):
|
||||||
# Emit ForEnd node for loop decrement
|
op_name = candidate
|
||||||
self._append_op(_make_op("for_end", {"loop": entry["loop"], "end": entry["end"]}))
|
if "data" in spec:
|
||||||
elif entry["type"] == "begin":
|
data = spec["data"]
|
||||||
self._append_op(_make_op("jump", entry["begin"]))
|
elif isinstance(spec, (list, tuple)):
|
||||||
self._append_op(_make_op("label", entry["end"]))
|
if not spec:
|
||||||
|
raise ParseError("close_ops contains empty sequence")
|
||||||
|
if isinstance(spec[0], str):
|
||||||
|
op_name = spec[0]
|
||||||
|
data = spec[1] if len(spec) > 1 else None
|
||||||
|
elif isinstance(spec, str):
|
||||||
|
op_name = spec
|
||||||
|
else:
|
||||||
|
raise ParseError(f"invalid close op descriptor: {spec!r}")
|
||||||
|
|
||||||
|
if not op_name:
|
||||||
|
raise ParseError(f"close op missing valid 'op' name: {spec!r}")
|
||||||
|
self._append_op(_make_op(op_name, data))
|
||||||
|
|
||||||
# Parsing ------------------------------------------------------------------
|
# Parsing ------------------------------------------------------------------
|
||||||
def parse(self, tokens: Iterable[Token], source: str) -> Module:
|
def parse(self, tokens: Iterable[Token], source: str) -> Module:
|
||||||
@@ -994,17 +1012,10 @@ class Parser:
|
|||||||
_KW_PY = 6
|
_KW_PY = 6
|
||||||
_KW_EXTERN = 7
|
_KW_EXTERN = 7
|
||||||
_KW_PRIORITY = 8
|
_KW_PRIORITY = 8
|
||||||
_KW_IF = 9
|
|
||||||
_KW_ELSE = 10
|
|
||||||
_KW_FOR = 11
|
|
||||||
_KW_WHILE = 12
|
|
||||||
_KW_DO = 13
|
|
||||||
_keyword_dispatch = {
|
_keyword_dispatch = {
|
||||||
"[": _KW_LIST_BEGIN, "]": _KW_LIST_END, "word": _KW_WORD,
|
"[": _KW_LIST_BEGIN, "]": _KW_LIST_END, "word": _KW_WORD,
|
||||||
"end": _KW_END, ":asm": _KW_ASM, ":py": _KW_PY,
|
"end": _KW_END, ":asm": _KW_ASM, ":py": _KW_PY,
|
||||||
"extern": _KW_EXTERN, "priority": _KW_PRIORITY,
|
"extern": _KW_EXTERN, "priority": _KW_PRIORITY,
|
||||||
"if": _KW_IF, "else": _KW_ELSE, "for": _KW_FOR,
|
|
||||||
"while": _KW_WHILE, "do": _KW_DO,
|
|
||||||
}
|
}
|
||||||
_kw_get = _keyword_dispatch.get
|
_kw_get = _keyword_dispatch.get
|
||||||
|
|
||||||
@@ -1054,16 +1065,8 @@ class Parser:
|
|||||||
self._parse_extern(token)
|
self._parse_extern(token)
|
||||||
elif kw == _KW_PRIORITY:
|
elif kw == _KW_PRIORITY:
|
||||||
self._parse_priority_directive(token)
|
self._parse_priority_directive(token)
|
||||||
elif kw == _KW_IF:
|
continue
|
||||||
self._handle_if_control()
|
if self._try_handle_builtin_control(token):
|
||||||
elif kw == _KW_ELSE:
|
|
||||||
self._handle_else_control()
|
|
||||||
elif kw == _KW_FOR:
|
|
||||||
self._handle_for_control()
|
|
||||||
elif kw == _KW_WHILE:
|
|
||||||
self._handle_while_control()
|
|
||||||
elif kw == _KW_DO:
|
|
||||||
self._handle_do_control()
|
|
||||||
continue
|
continue
|
||||||
if self._handle_token(token):
|
if self._handle_token(token):
|
||||||
_tokens = self.tokens
|
_tokens = self.tokens
|
||||||
@@ -1120,6 +1123,161 @@ class Parser:
|
|||||||
label = entry["label"]
|
label = entry["label"]
|
||||||
self._append_op(_make_op("list_end", label))
|
self._append_op(_make_op("list_end", label))
|
||||||
|
|
||||||
|
def _should_use_custom_control(self, lexeme: str) -> bool:
|
||||||
|
# Fast path: default parser controls unless explicitly overridden.
|
||||||
|
if lexeme not in self.control_overrides:
|
||||||
|
return False
|
||||||
|
word = self.dictionary.lookup(lexeme)
|
||||||
|
if word is None:
|
||||||
|
return False
|
||||||
|
return bool(word.immediate)
|
||||||
|
|
||||||
|
def _warn_control_override(self, token: Token, lexeme: str) -> None:
|
||||||
|
if lexeme in self._warned_control_overrides:
|
||||||
|
return
|
||||||
|
self._warned_control_overrides.add(lexeme)
|
||||||
|
sys.stderr.write(
|
||||||
|
f"[warn] default control structure ({lexeme}) has been overridden; using custom implementation\n"
|
||||||
|
)
|
||||||
|
|
||||||
|
def _try_handle_builtin_control(self, token: Token) -> bool:
|
||||||
|
lexeme = token.lexeme
|
||||||
|
if lexeme not in _DEFAULT_CONTROL_WORDS:
|
||||||
|
return False
|
||||||
|
if self._should_use_custom_control(lexeme):
|
||||||
|
self._warn_control_override(token, lexeme)
|
||||||
|
return False
|
||||||
|
if lexeme == "if":
|
||||||
|
self._handle_builtin_if(token)
|
||||||
|
return True
|
||||||
|
if lexeme == "else":
|
||||||
|
self._handle_builtin_else(token)
|
||||||
|
return True
|
||||||
|
if lexeme == "for":
|
||||||
|
self._handle_builtin_for(token)
|
||||||
|
return True
|
||||||
|
if lexeme == "while":
|
||||||
|
self._handle_builtin_while(token)
|
||||||
|
return True
|
||||||
|
if lexeme == "do":
|
||||||
|
self._handle_builtin_do(token)
|
||||||
|
return True
|
||||||
|
return False
|
||||||
|
|
||||||
|
def _handle_builtin_if(self, token: Token) -> None:
|
||||||
|
# Support shorthand `else <cond> if` by sharing the previous else-end label.
|
||||||
|
if self.control_stack:
|
||||||
|
top = self.control_stack[-1]
|
||||||
|
if (
|
||||||
|
top.get("type") == "else"
|
||||||
|
and isinstance(top.get("line"), int)
|
||||||
|
and top["line"] == token.line
|
||||||
|
):
|
||||||
|
prev_else = self._pop_control(("else",))
|
||||||
|
shared_end = prev_else.get("end")
|
||||||
|
if not isinstance(shared_end, str):
|
||||||
|
shared_end = self._new_label("if_end")
|
||||||
|
false_label = self._new_label("if_false")
|
||||||
|
self._append_op(_make_op("branch_zero", false_label))
|
||||||
|
self._push_control(
|
||||||
|
{
|
||||||
|
"type": "if",
|
||||||
|
"false": false_label,
|
||||||
|
"end": shared_end,
|
||||||
|
"close_ops": [
|
||||||
|
{"op": "label", "data": false_label},
|
||||||
|
{"op": "label", "data": shared_end},
|
||||||
|
],
|
||||||
|
"line": token.line,
|
||||||
|
"column": token.column,
|
||||||
|
}
|
||||||
|
)
|
||||||
|
return
|
||||||
|
|
||||||
|
false_label = self._new_label("if_false")
|
||||||
|
self._append_op(_make_op("branch_zero", false_label))
|
||||||
|
self._push_control(
|
||||||
|
{
|
||||||
|
"type": "if",
|
||||||
|
"false": false_label,
|
||||||
|
"end": None,
|
||||||
|
"close_ops": [{"op": "label", "data": false_label}],
|
||||||
|
"line": token.line,
|
||||||
|
"column": token.column,
|
||||||
|
}
|
||||||
|
)
|
||||||
|
|
||||||
|
def _handle_builtin_else(self, token: Token) -> None:
|
||||||
|
entry = self._pop_control(("if",))
|
||||||
|
false_label = entry.get("false")
|
||||||
|
if not isinstance(false_label, str):
|
||||||
|
raise ParseError("invalid if control frame")
|
||||||
|
end_label = entry.get("end")
|
||||||
|
if not isinstance(end_label, str):
|
||||||
|
end_label = self._new_label("if_end")
|
||||||
|
self._append_op(_make_op("jump", end_label))
|
||||||
|
self._append_op(_make_op("label", false_label))
|
||||||
|
self._push_control(
|
||||||
|
{
|
||||||
|
"type": "else",
|
||||||
|
"end": end_label,
|
||||||
|
"close_ops": [{"op": "label", "data": end_label}],
|
||||||
|
"line": token.line,
|
||||||
|
"column": token.column,
|
||||||
|
}
|
||||||
|
)
|
||||||
|
|
||||||
|
def _handle_builtin_for(self, token: Token) -> None:
|
||||||
|
loop_label = self._new_label("for_loop")
|
||||||
|
end_label = self._new_label("for_end")
|
||||||
|
frame = {"loop": loop_label, "end": end_label}
|
||||||
|
self._append_op(_make_op("for_begin", dict(frame)))
|
||||||
|
self._push_control(
|
||||||
|
{
|
||||||
|
"type": "for",
|
||||||
|
"loop": loop_label,
|
||||||
|
"end": end_label,
|
||||||
|
"close_ops": [{"op": "for_end", "data": dict(frame)}],
|
||||||
|
"line": token.line,
|
||||||
|
"column": token.column,
|
||||||
|
}
|
||||||
|
)
|
||||||
|
|
||||||
|
def _handle_builtin_while(self, token: Token) -> None:
|
||||||
|
begin_label = self._new_label("begin")
|
||||||
|
end_label = self._new_label("end")
|
||||||
|
self._append_op(_make_op("label", begin_label))
|
||||||
|
self._push_control(
|
||||||
|
{
|
||||||
|
"type": "while_open",
|
||||||
|
"begin": begin_label,
|
||||||
|
"end": end_label,
|
||||||
|
"line": token.line,
|
||||||
|
"column": token.column,
|
||||||
|
}
|
||||||
|
)
|
||||||
|
|
||||||
|
def _handle_builtin_do(self, token: Token) -> None:
|
||||||
|
entry = self._pop_control(("while_open",))
|
||||||
|
begin_label = entry.get("begin")
|
||||||
|
end_label = entry.get("end")
|
||||||
|
if not isinstance(begin_label, str) or not isinstance(end_label, str):
|
||||||
|
raise ParseError("invalid while control frame")
|
||||||
|
self._append_op(_make_op("branch_zero", end_label))
|
||||||
|
self._push_control(
|
||||||
|
{
|
||||||
|
"type": "while",
|
||||||
|
"begin": begin_label,
|
||||||
|
"end": end_label,
|
||||||
|
"close_ops": [
|
||||||
|
{"op": "jump", "data": begin_label},
|
||||||
|
{"op": "label", "data": end_label},
|
||||||
|
],
|
||||||
|
"line": token.line,
|
||||||
|
"column": token.column,
|
||||||
|
}
|
||||||
|
)
|
||||||
|
|
||||||
def _parse_priority_directive(self, token: Token) -> None:
|
def _parse_priority_directive(self, token: Token) -> None:
|
||||||
if self._eof():
|
if self._eof():
|
||||||
raise ParseError(f"priority value missing at {token.line}:{token.column}")
|
raise ParseError(f"priority value missing at {token.line}:{token.column}")
|
||||||
@@ -1469,52 +1627,6 @@ class Parser:
|
|||||||
handled = self.compile_time_vm.pop()
|
handled = self.compile_time_vm.pop()
|
||||||
return bool(handled)
|
return bool(handled)
|
||||||
|
|
||||||
def _handle_if_control(self) -> None:
|
|
||||||
token = self._last_token
|
|
||||||
if (
|
|
||||||
self.control_stack
|
|
||||||
and self.control_stack[-1]["type"] == "else"
|
|
||||||
and token is not None
|
|
||||||
and self.control_stack[-1].get("line") == token.line
|
|
||||||
):
|
|
||||||
entry = self.control_stack.pop()
|
|
||||||
end_label = entry.get("end")
|
|
||||||
if end_label is None:
|
|
||||||
end_label = self._new_label("if_end")
|
|
||||||
false_label = self._new_label("if_false")
|
|
||||||
self._append_op(_make_op("branch_zero", false_label))
|
|
||||||
self._push_control({"type": "elif", "false": false_label, "end": end_label})
|
|
||||||
return
|
|
||||||
false_label = self._new_label("if_false")
|
|
||||||
self._append_op(_make_op("branch_zero", false_label))
|
|
||||||
self._push_control({"type": "if", "false": false_label})
|
|
||||||
|
|
||||||
def _handle_else_control(self) -> None:
|
|
||||||
entry = self._pop_control(("if", "elif"))
|
|
||||||
end_label = entry.get("end")
|
|
||||||
if end_label is None:
|
|
||||||
end_label = self._new_label("if_end")
|
|
||||||
self._append_op(_make_op("jump", end_label))
|
|
||||||
self._append_op(_make_op("label", entry["false"]))
|
|
||||||
self._push_control({"type": "else", "end": end_label})
|
|
||||||
|
|
||||||
def _handle_for_control(self) -> None:
|
|
||||||
loop_label = self._new_label("for_loop")
|
|
||||||
end_label = self._new_label("for_end")
|
|
||||||
self._append_op(_make_op("for_begin", {"loop": loop_label, "end": end_label}))
|
|
||||||
self._push_control({"type": "for", "loop": loop_label, "end": end_label})
|
|
||||||
|
|
||||||
def _handle_while_control(self) -> None:
|
|
||||||
begin_label = self._new_label("begin")
|
|
||||||
end_label = self._new_label("end")
|
|
||||||
self._append_op(_make_op("label", begin_label))
|
|
||||||
self._push_control({"type": "begin", "begin": begin_label, "end": end_label})
|
|
||||||
|
|
||||||
def _handle_do_control(self) -> None:
|
|
||||||
entry = self._pop_control(("begin",))
|
|
||||||
self._append_op(_make_op("branch_zero", entry["end"]))
|
|
||||||
self._push_control(entry)
|
|
||||||
|
|
||||||
def _try_end_definition(self, token: Token) -> bool:
|
def _try_end_definition(self, token: Token) -> bool:
|
||||||
if len(self.context_stack) <= 1:
|
if len(self.context_stack) <= 1:
|
||||||
return False
|
return False
|
||||||
@@ -6702,7 +6814,8 @@ class Assembler:
|
|||||||
suffix = f" while emitting '{self._emit_stack[-1]}'" if self._emit_stack else ""
|
suffix = f" while emitting '{self._emit_stack[-1]}'" if self._emit_stack else ""
|
||||||
raise CompileError(f"unknown word '{name}'{suffix}")
|
raise CompileError(f"unknown word '{name}'{suffix}")
|
||||||
if word.compile_only:
|
if word.compile_only:
|
||||||
return # silently skip compile-time-only words during emission
|
suffix = f" while emitting '{self._emit_stack[-1]}'" if self._emit_stack else ""
|
||||||
|
raise CompileError(f"word '{name}' is compile-time only and cannot be used at runtime{suffix}")
|
||||||
if getattr(word, "inline", False):
|
if getattr(word, "inline", False):
|
||||||
if isinstance(word.definition, Definition):
|
if isinstance(word.definition, Definition):
|
||||||
if word.name in self._inline_stack:
|
if word.name in self._inline_stack:
|
||||||
@@ -6957,9 +7070,7 @@ def macro_with(ctx: MacroContext) -> Optional[List[Op]]:
|
|||||||
depth -= 1
|
depth -= 1
|
||||||
body.append(tok)
|
body.append(tok)
|
||||||
continue
|
continue
|
||||||
if tok.lexeme in ("with", "for", "while", "begin", "word"):
|
if tok.lexeme == "if":
|
||||||
depth += 1
|
|
||||||
elif tok.lexeme == "if":
|
|
||||||
# Support shorthand elif form `else <cond> if` inside with-blocks.
|
# Support shorthand elif form `else <cond> if` inside with-blocks.
|
||||||
# This inline `if` shares the same closing `end` as the preceding
|
# This inline `if` shares the same closing `end` as the preceding
|
||||||
# branch and therefore must not increment nesting depth.
|
# branch and therefore must not increment nesting depth.
|
||||||
@@ -6967,6 +7078,8 @@ def macro_with(ctx: MacroContext) -> Optional[List[Op]]:
|
|||||||
depth += 1
|
depth += 1
|
||||||
elif tok.lexeme == "else":
|
elif tok.lexeme == "else":
|
||||||
else_line = tok.line
|
else_line = tok.line
|
||||||
|
elif tok.lexeme in parser.block_openers:
|
||||||
|
depth += 1
|
||||||
body.append(tok)
|
body.append(tok)
|
||||||
|
|
||||||
helper_for: Dict[str, str] = {}
|
helper_for: Dict[str, str] = {}
|
||||||
@@ -7299,6 +7412,105 @@ def _ct_loop_index(vm: CompileTimeVM) -> None:
|
|||||||
vm.push(idx)
|
vm.push(idx)
|
||||||
|
|
||||||
|
|
||||||
|
def _ct_control_frame_new(vm: CompileTimeVM) -> None:
|
||||||
|
type_name = vm.pop_str()
|
||||||
|
vm.push({"type": type_name})
|
||||||
|
|
||||||
|
|
||||||
|
def _ct_control_get(vm: CompileTimeVM) -> None:
|
||||||
|
key = vm.pop_str()
|
||||||
|
frame = vm.pop()
|
||||||
|
if not isinstance(frame, dict):
|
||||||
|
raise ParseError("ct-control-get expects a control frame")
|
||||||
|
vm.push(frame.get(key))
|
||||||
|
|
||||||
|
|
||||||
|
def _ct_control_set(vm: CompileTimeVM) -> None:
|
||||||
|
value = vm.pop()
|
||||||
|
key = vm.pop_str()
|
||||||
|
frame = vm.pop()
|
||||||
|
if not isinstance(frame, dict):
|
||||||
|
raise ParseError("ct-control-set expects a control frame")
|
||||||
|
frame[key] = value
|
||||||
|
vm.push(frame)
|
||||||
|
|
||||||
|
|
||||||
|
def _ct_control_push(vm: CompileTimeVM) -> None:
|
||||||
|
frame = vm.pop()
|
||||||
|
if not isinstance(frame, dict):
|
||||||
|
raise ParseError("ct-control-push expects a control frame")
|
||||||
|
vm.parser._push_control(dict(frame))
|
||||||
|
|
||||||
|
|
||||||
|
def _ct_control_pop(vm: CompileTimeVM) -> None:
|
||||||
|
if not vm.parser.control_stack:
|
||||||
|
raise ParseError("control stack underflow")
|
||||||
|
vm.push(dict(vm.parser.control_stack.pop()))
|
||||||
|
|
||||||
|
|
||||||
|
def _ct_control_peek(vm: CompileTimeVM) -> None:
|
||||||
|
if not vm.parser.control_stack:
|
||||||
|
vm.push(None)
|
||||||
|
return
|
||||||
|
vm.push(dict(vm.parser.control_stack[-1]))
|
||||||
|
|
||||||
|
|
||||||
|
def _ct_control_depth(vm: CompileTimeVM) -> None:
|
||||||
|
vm.push(len(vm.parser.control_stack))
|
||||||
|
|
||||||
|
|
||||||
|
def _ct_new_label(vm: CompileTimeVM) -> None:
|
||||||
|
prefix = vm.pop_str()
|
||||||
|
vm.push(vm.parser._new_label(prefix))
|
||||||
|
|
||||||
|
|
||||||
|
def _ct_emit_op(vm: CompileTimeVM) -> None:
|
||||||
|
data = vm.pop()
|
||||||
|
op_name = vm.pop_str()
|
||||||
|
vm.parser.emit_node(_make_op(op_name, data))
|
||||||
|
|
||||||
|
|
||||||
|
def _ct_control_add_close_op(vm: CompileTimeVM) -> None:
|
||||||
|
data = vm.pop()
|
||||||
|
op_name = vm.pop_str()
|
||||||
|
frame = vm.pop()
|
||||||
|
if not isinstance(frame, dict):
|
||||||
|
raise ParseError("ct-control-add-close-op expects a control frame")
|
||||||
|
close_ops = frame.get("close_ops")
|
||||||
|
if close_ops is None:
|
||||||
|
close_ops = []
|
||||||
|
elif not isinstance(close_ops, list):
|
||||||
|
raise ParseError("control frame field 'close_ops' must be a list")
|
||||||
|
close_ops.append({"op": op_name, "data": data})
|
||||||
|
frame["close_ops"] = close_ops
|
||||||
|
vm.push(frame)
|
||||||
|
|
||||||
|
|
||||||
|
def _ct_last_token_line(vm: CompileTimeVM) -> None:
|
||||||
|
tok = vm.parser._last_token
|
||||||
|
vm.push(0 if tok is None else tok.line)
|
||||||
|
|
||||||
|
|
||||||
|
def _ct_register_block_opener(vm: CompileTimeVM) -> None:
|
||||||
|
name = vm.pop_str()
|
||||||
|
vm.parser.block_openers.add(name)
|
||||||
|
|
||||||
|
|
||||||
|
def _ct_unregister_block_opener(vm: CompileTimeVM) -> None:
|
||||||
|
name = vm.pop_str()
|
||||||
|
vm.parser.block_openers.discard(name)
|
||||||
|
|
||||||
|
|
||||||
|
def _ct_register_control_override(vm: CompileTimeVM) -> None:
|
||||||
|
name = vm.pop_str()
|
||||||
|
vm.parser.control_overrides.add(name)
|
||||||
|
|
||||||
|
|
||||||
|
def _ct_unregister_control_override(vm: CompileTimeVM) -> None:
|
||||||
|
name = vm.pop_str()
|
||||||
|
vm.parser.control_overrides.discard(name)
|
||||||
|
|
||||||
|
|
||||||
def _ct_list_get(vm: CompileTimeVM) -> None:
|
def _ct_list_get(vm: CompileTimeVM) -> None:
|
||||||
index = vm.pop_int()
|
index = vm.pop_int()
|
||||||
lst = _ensure_list(vm.pop())
|
lst = _ensure_list(vm.pop())
|
||||||
@@ -7897,6 +8109,21 @@ def _register_compile_time_primitives(dictionary: Dictionary) -> None:
|
|||||||
register("list-extend", _ct_list_extend, compile_only=True)
|
register("list-extend", _ct_list_extend, compile_only=True)
|
||||||
register("list-last", _ct_list_last, compile_only=True)
|
register("list-last", _ct_list_last, compile_only=True)
|
||||||
register("i", _ct_loop_index, compile_only=True)
|
register("i", _ct_loop_index, compile_only=True)
|
||||||
|
register("ct-control-frame-new", _ct_control_frame_new, compile_only=True)
|
||||||
|
register("ct-control-get", _ct_control_get, compile_only=True)
|
||||||
|
register("ct-control-set", _ct_control_set, compile_only=True)
|
||||||
|
register("ct-control-push", _ct_control_push, compile_only=True)
|
||||||
|
register("ct-control-pop", _ct_control_pop, compile_only=True)
|
||||||
|
register("ct-control-peek", _ct_control_peek, compile_only=True)
|
||||||
|
register("ct-control-depth", _ct_control_depth, compile_only=True)
|
||||||
|
register("ct-control-add-close-op", _ct_control_add_close_op, compile_only=True)
|
||||||
|
register("ct-new-label", _ct_new_label, compile_only=True)
|
||||||
|
register("ct-emit-op", _ct_emit_op, compile_only=True)
|
||||||
|
register("ct-last-token-line", _ct_last_token_line, compile_only=True)
|
||||||
|
register("ct-register-block-opener", _ct_register_block_opener, compile_only=True)
|
||||||
|
register("ct-unregister-block-opener", _ct_unregister_block_opener, compile_only=True)
|
||||||
|
register("ct-register-control-override", _ct_register_control_override, compile_only=True)
|
||||||
|
register("ct-unregister-control-override", _ct_unregister_control_override, compile_only=True)
|
||||||
|
|
||||||
register("prelude-clear", _ct_prelude_clear, compile_only=True)
|
register("prelude-clear", _ct_prelude_clear, compile_only=True)
|
||||||
register("prelude-append", _ct_prelude_append, compile_only=True)
|
register("prelude-append", _ct_prelude_append, compile_only=True)
|
||||||
@@ -10802,6 +11029,54 @@ def _run_docs_tui(
|
|||||||
" word <name> <body...> end\n"
|
" word <name> <body...> end\n"
|
||||||
" into the parser's token stream.\n"
|
" into the parser's token stream.\n"
|
||||||
"\n"
|
"\n"
|
||||||
|
" ── Control-frame helpers (for custom control structures)\n"
|
||||||
|
"\n"
|
||||||
|
" ct-control-frame-new [* | type] -> [* | frame]\n"
|
||||||
|
" Create a control frame map with a `type` field.\n"
|
||||||
|
"\n"
|
||||||
|
" ct-control-get [*, frame | key] -> [* | value]\n"
|
||||||
|
" Read key from a control frame map.\n"
|
||||||
|
"\n"
|
||||||
|
" ct-control-set [*, frame, key | value] -> [* | frame]\n"
|
||||||
|
" Write key/value into a control frame map.\n"
|
||||||
|
"\n"
|
||||||
|
" ct-control-push [* | frame] -> [*]\n"
|
||||||
|
" Push a frame onto the parser control stack.\n"
|
||||||
|
"\n"
|
||||||
|
" ct-control-pop [*] -> [* | frame]\n"
|
||||||
|
" Pop and return the top parser control frame.\n"
|
||||||
|
"\n"
|
||||||
|
" ct-control-peek [*] -> [* | frame] || [* | nil]\n"
|
||||||
|
" Return the top parser control frame without popping.\n"
|
||||||
|
"\n"
|
||||||
|
" ct-control-depth [*] -> [* | n]\n"
|
||||||
|
" Return parser control-stack depth.\n"
|
||||||
|
"\n"
|
||||||
|
" ct-control-add-close-op [*, frame, op | data] -> [* | frame]\n"
|
||||||
|
" Append a close operation descriptor to frame.close_ops.\n"
|
||||||
|
"\n"
|
||||||
|
" ct-new-label [* | prefix] -> [* | label]\n"
|
||||||
|
" Allocate a fresh internal label with the given prefix.\n"
|
||||||
|
"\n"
|
||||||
|
" ct-emit-op [*, op | data] -> [*]\n"
|
||||||
|
" Emit an internal op node directly into the current body.\n"
|
||||||
|
"\n"
|
||||||
|
" ct-last-token-line [*] -> [* | line]\n"
|
||||||
|
" Return line number of the last parser token (or 0).\n"
|
||||||
|
"\n"
|
||||||
|
" ct-register-block-opener [* | name] -> [*]\n"
|
||||||
|
" Mark a word name as a block opener for `with` nesting.\n"
|
||||||
|
"\n"
|
||||||
|
" ct-unregister-block-opener [* | name] -> [*]\n"
|
||||||
|
" Remove a word name from block opener registration.\n"
|
||||||
|
"\n"
|
||||||
|
" ct-register-control-override [* | name] -> [*]\n"
|
||||||
|
" Register a control word override so parser can delegate\n"
|
||||||
|
" built-in control handling to custom compile-time words.\n"
|
||||||
|
"\n"
|
||||||
|
" ct-unregister-control-override [* | name] -> [*]\n"
|
||||||
|
" Remove a control word override registration.\n"
|
||||||
|
"\n"
|
||||||
"\n"
|
"\n"
|
||||||
"━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━\n"
|
"━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━\n"
|
||||||
" § 7 LEXER OBJECTS\n"
|
" § 7 LEXER OBJECTS\n"
|
||||||
@@ -11020,6 +11295,21 @@ def _run_docs_tui(
|
|||||||
" add-token Token [* | s] -> [*]\n"
|
" add-token Token [* | s] -> [*]\n"
|
||||||
" add-token-chars Token [* | s] -> [*]\n"
|
" add-token-chars Token [* | s] -> [*]\n"
|
||||||
" emit-definition Token [*, name | body] -> [*]\n"
|
" emit-definition Token [*, name | body] -> [*]\n"
|
||||||
|
" ct-control-frame-new Control [* | type] -> [* | frame]\n"
|
||||||
|
" ct-control-get Control [*, frame | key] -> [* | value]\n"
|
||||||
|
" ct-control-set Control [*, frame, key | value] -> [* | frame]\n"
|
||||||
|
" ct-control-push Control [* | frame] -> [*]\n"
|
||||||
|
" ct-control-pop Control [*] -> [* | frame]\n"
|
||||||
|
" ct-control-peek Control [*] -> [* | frame]\n"
|
||||||
|
" ct-control-depth Control [*] -> [* | n]\n"
|
||||||
|
" ct-control-add-close-op Control [*, frame, op | data] -> [* | frame]\n"
|
||||||
|
" ct-new-label Control [* | prefix] -> [* | label]\n"
|
||||||
|
" ct-emit-op Control [*, op | data] -> [*]\n"
|
||||||
|
" ct-last-token-line Control [*] -> [* | line]\n"
|
||||||
|
" ct-register-block-opener Control [* | name] -> [*]\n"
|
||||||
|
" ct-unregister-block-opener Control [* | name] -> [*]\n"
|
||||||
|
" ct-register-control-override Control [* | name] -> [*]\n"
|
||||||
|
" ct-unregister-control-override Control [* | name] -> [*]\n"
|
||||||
" set-token-hook Hook [* | name] -> [*]\n"
|
" set-token-hook Hook [* | name] -> [*]\n"
|
||||||
" clear-token-hook Hook [*] -> [*]\n"
|
" clear-token-hook Hook [*] -> [*]\n"
|
||||||
" prelude-clear Assembly [*] -> [*]\n"
|
" prelude-clear Assembly [*] -> [*]\n"
|
||||||
|
|||||||
137
stdlib/control.sl
Normal file
137
stdlib/control.sl
Normal file
@@ -0,0 +1,137 @@
|
|||||||
|
# Optional control-structure overrides for L2 parser defaults.
|
||||||
|
# Import this file when you want custom compile-time implementations of
|
||||||
|
# if/else/for/while/do instead of the built-in Python parser behavior.
|
||||||
|
|
||||||
|
word ct-if-open
|
||||||
|
"if_false" ct-new-label
|
||||||
|
dup "branch_zero" swap ct-emit-op
|
||||||
|
"if" ct-control-frame-new
|
||||||
|
swap "false" swap ct-control-set
|
||||||
|
nil "end" swap ct-control-set
|
||||||
|
dup "false" ct-control-get "label" swap ct-control-add-close-op
|
||||||
|
ct-control-push
|
||||||
|
end
|
||||||
|
compile-only
|
||||||
|
|
||||||
|
word ct-if-open-with-end
|
||||||
|
"if_false" ct-new-label
|
||||||
|
dup "branch_zero" swap ct-emit-op
|
||||||
|
"if" ct-control-frame-new
|
||||||
|
swap "false" swap ct-control-set
|
||||||
|
swap "end" swap ct-control-set
|
||||||
|
dup "false" ct-control-get "label" swap ct-control-add-close-op
|
||||||
|
dup "end" ct-control-get "label" swap ct-control-add-close-op
|
||||||
|
ct-control-push
|
||||||
|
end
|
||||||
|
compile-only
|
||||||
|
|
||||||
|
word if-base ct-if-open end
|
||||||
|
immediate
|
||||||
|
compile-only
|
||||||
|
|
||||||
|
word if
|
||||||
|
ct-control-depth 0 > if-base
|
||||||
|
ct-control-peek
|
||||||
|
dup "type" ct-control-get "else" string= if-base
|
||||||
|
dup "line" ct-control-get ct-last-token-line == if-base
|
||||||
|
drop
|
||||||
|
ct-control-pop >r
|
||||||
|
r@ "end" ct-control-get dup nil? if-base
|
||||||
|
drop "if_end" ct-new-label
|
||||||
|
end
|
||||||
|
ct-if-open-with-end
|
||||||
|
r> drop
|
||||||
|
exit
|
||||||
|
end
|
||||||
|
end
|
||||||
|
drop
|
||||||
|
end
|
||||||
|
ct-if-open
|
||||||
|
end
|
||||||
|
immediate
|
||||||
|
compile-only
|
||||||
|
|
||||||
|
word else
|
||||||
|
ct-control-pop >r
|
||||||
|
r@ "end" ct-control-get dup nil? if-base
|
||||||
|
drop "if_end" ct-new-label
|
||||||
|
end
|
||||||
|
dup "jump" swap ct-emit-op
|
||||||
|
r@ "false" ct-control-get "label" swap ct-emit-op
|
||||||
|
"else" ct-control-frame-new
|
||||||
|
swap "end" swap ct-control-set
|
||||||
|
dup "end" ct-control-get "label" swap ct-control-add-close-op
|
||||||
|
ct-control-push
|
||||||
|
r> drop
|
||||||
|
end
|
||||||
|
immediate
|
||||||
|
compile-only
|
||||||
|
|
||||||
|
word for
|
||||||
|
"for_loop" ct-new-label
|
||||||
|
"for_end" ct-new-label
|
||||||
|
map-new
|
||||||
|
"loop" 3 pick map-set
|
||||||
|
"end" 2 pick map-set
|
||||||
|
"for_begin" swap ct-emit-op
|
||||||
|
"for" ct-control-frame-new
|
||||||
|
swap "end" swap ct-control-set
|
||||||
|
swap "loop" swap ct-control-set
|
||||||
|
dup "end" ct-control-get >r
|
||||||
|
dup "loop" ct-control-get >r
|
||||||
|
map-new
|
||||||
|
"loop" r> map-set
|
||||||
|
"end" r> map-set
|
||||||
|
"for_end" swap ct-control-add-close-op
|
||||||
|
ct-control-push
|
||||||
|
end
|
||||||
|
immediate
|
||||||
|
compile-only
|
||||||
|
|
||||||
|
word while
|
||||||
|
"begin" ct-new-label
|
||||||
|
"end" ct-new-label
|
||||||
|
over "label" swap ct-emit-op
|
||||||
|
"while_open" ct-control-frame-new
|
||||||
|
swap "end" swap ct-control-set
|
||||||
|
swap "begin" swap ct-control-set
|
||||||
|
ct-control-push
|
||||||
|
end
|
||||||
|
immediate
|
||||||
|
compile-only
|
||||||
|
|
||||||
|
word do
|
||||||
|
ct-control-pop >r
|
||||||
|
r@ "end" ct-control-get "branch_zero" swap ct-emit-op
|
||||||
|
"while" ct-control-frame-new
|
||||||
|
r@ "begin" ct-control-get "begin" swap ct-control-set
|
||||||
|
r@ "end" ct-control-get "end" swap ct-control-set
|
||||||
|
dup "begin" ct-control-get "jump" swap ct-control-add-close-op
|
||||||
|
dup "end" ct-control-get "label" swap ct-control-add-close-op
|
||||||
|
r> drop
|
||||||
|
ct-control-push
|
||||||
|
end
|
||||||
|
immediate
|
||||||
|
compile-only
|
||||||
|
|
||||||
|
word block-opener
|
||||||
|
next-token token-lexeme ct-register-block-opener
|
||||||
|
end
|
||||||
|
immediate
|
||||||
|
compile-only
|
||||||
|
|
||||||
|
word control-override
|
||||||
|
next-token token-lexeme ct-register-control-override
|
||||||
|
end
|
||||||
|
immediate
|
||||||
|
compile-only
|
||||||
|
|
||||||
|
block-opener if
|
||||||
|
block-opener for
|
||||||
|
block-opener while
|
||||||
|
|
||||||
|
control-override if
|
||||||
|
control-override else
|
||||||
|
control-override for
|
||||||
|
control-override while
|
||||||
|
control-override do
|
||||||
5
tests/custom_control_structures.expected
Normal file
5
tests/custom_control_structures.expected
Normal file
@@ -0,0 +1,5 @@
|
|||||||
|
11
|
||||||
|
22
|
||||||
|
33
|
||||||
|
5
|
||||||
|
3
|
||||||
36
tests/custom_control_structures.sl
Normal file
36
tests/custom_control_structures.sl
Normal file
@@ -0,0 +1,36 @@
|
|||||||
|
import stdlib/stdlib.sl
|
||||||
|
import stdlib/control.sl
|
||||||
|
|
||||||
|
word main
|
||||||
|
1 if
|
||||||
|
11 puti cr
|
||||||
|
else
|
||||||
|
99 puti cr
|
||||||
|
end
|
||||||
|
|
||||||
|
0 if
|
||||||
|
99 puti cr
|
||||||
|
else
|
||||||
|
22 puti cr
|
||||||
|
end
|
||||||
|
|
||||||
|
0 if
|
||||||
|
500 puti cr
|
||||||
|
else 1 if
|
||||||
|
33 puti cr
|
||||||
|
else
|
||||||
|
44 puti cr
|
||||||
|
end
|
||||||
|
|
||||||
|
0
|
||||||
|
5 for
|
||||||
|
1 +
|
||||||
|
end
|
||||||
|
puti cr
|
||||||
|
|
||||||
|
0
|
||||||
|
while dup 3 < do
|
||||||
|
1 +
|
||||||
|
end
|
||||||
|
puti cr
|
||||||
|
end
|
||||||
1
tests/i_non_compile_time.compile.expected
Normal file
1
tests/i_non_compile_time.compile.expected
Normal file
@@ -0,0 +1 @@
|
|||||||
|
[error] word 'i' is compile-time only and cannot be used at runtime while emitting 'main'
|
||||||
4
tests/i_non_compile_time.meta.json
Normal file
4
tests/i_non_compile_time.meta.json
Normal file
@@ -0,0 +1,4 @@
|
|||||||
|
{
|
||||||
|
"expect_compile_error": true,
|
||||||
|
"description": "'i' is compile-time only and rejected in runtime code"
|
||||||
|
}
|
||||||
8
tests/i_non_compile_time.sl
Normal file
8
tests/i_non_compile_time.sl
Normal file
@@ -0,0 +1,8 @@
|
|||||||
|
import stdlib/stdlib.sl
|
||||||
|
|
||||||
|
word main
|
||||||
|
0
|
||||||
|
3 for
|
||||||
|
i puti cr
|
||||||
|
end
|
||||||
|
end
|
||||||
Reference in New Issue
Block a user