added variables
This commit is contained in:
1
SPEC.md
1
SPEC.md
@@ -25,6 +25,7 @@
|
||||
- `lookup`: resolves token → word entry; can be replaced to build new namespaces or module systems.
|
||||
- **Definition form**: `word <name> ... end` is the required way to declare high-level words. Legacy `: <name> ... ;` definitions are no longer accepted.
|
||||
- **Text macros**: `macro <name> [param_count] ... ;` records tokens until the closing `;` and registers a macro that performs positional substitution (`$1`, `$2`, ...). The old `macro: ... ;macro` form is removed.
|
||||
- **Lexical stack aliases**: `with a b in ... end` rewrites the body so `a`/`b` expand to stable `rpick` accesses. Values are moved to the return stack on entry and released with `rdrop` on exit, giving cheap locally named slots while keeping the data stack free for intermediate results.
|
||||
- **Compile vs interpret**: Each word advertises stack effect + immediacy. Immediate words execute during compilation (macro behavior). Others emit code or inline asm.
|
||||
- **Syntax morphing**: Provide primitives `set-reader`, `with-reader`, and word-lists so layers (e.g., Lisp-like forms) can be composed.
|
||||
- **Inline Python hooks**: `:py name { ... } ;` executes the enclosed Python block immediately, then registers `name` as a word whose behavior is provided by that block. Define a `macro(ctx)` function to intercept compilation (receiving a `MacroContext` with helpers like `next_token`, `emit_literal`, `new_label`, `inject_tokens`, and direct access to the active parser), and/or an `intrinsic(builder)` function to emit custom assembly. This lets end users extend the language—parsing source, manipulating AST nodes, or writing NASM—without touching the bootstrap source. The standard library’s `extend-syntax` and `fn` forms are ordinary `:py` blocks built with these APIs, so users can clone or replace them entirely from L2 source files.
|
||||
|
||||
128
main.py
128
main.py
@@ -215,6 +215,7 @@ class AsmDefinition:
|
||||
@dataclass
|
||||
class Module:
|
||||
forms: List[Any]
|
||||
variables: Dict[str, str] = field(default_factory=dict)
|
||||
|
||||
|
||||
@dataclass
|
||||
@@ -344,6 +345,8 @@ class Parser:
|
||||
self.label_counter = 0
|
||||
self.token_hook: Optional[str] = None
|
||||
self._last_token: Optional[Token] = None
|
||||
self.variable_labels: Dict[str, str] = {}
|
||||
self.variable_words: Dict[str, str] = {}
|
||||
self.compile_time_vm = CompileTimeVM(self)
|
||||
|
||||
def inject_token_objects(self, tokens: Sequence[Token]) -> None:
|
||||
@@ -364,6 +367,30 @@ class Parser:
|
||||
def most_recent_definition(self) -> Optional[Word]:
|
||||
return self.last_defined
|
||||
|
||||
def allocate_variable(self, name: str) -> Tuple[str, str]:
|
||||
if name in self.variable_labels:
|
||||
label = self.variable_labels[name]
|
||||
else:
|
||||
base = sanitize_label(f"var_{name}")
|
||||
label = base
|
||||
suffix = 0
|
||||
existing = set(self.variable_labels.values())
|
||||
while label in existing:
|
||||
suffix += 1
|
||||
label = f"{base}_{suffix}"
|
||||
self.variable_labels[name] = label
|
||||
hidden_word = f"__with_{name}"
|
||||
self.variable_words[name] = hidden_word
|
||||
if self.dictionary.lookup(hidden_word) is None:
|
||||
word = Word(name=hidden_word)
|
||||
|
||||
def _intrinsic(builder: FunctionEmitter, target: str = label) -> None:
|
||||
builder.push_label(target)
|
||||
|
||||
word.intrinsic = _intrinsic
|
||||
self.dictionary.register(word)
|
||||
return label, hidden_word
|
||||
|
||||
def _handle_end_control(self) -> None:
|
||||
"""Handle unified 'end' for all block types"""
|
||||
if not self.control_stack:
|
||||
@@ -394,7 +421,9 @@ class Parser:
|
||||
self._token_iter_exhausted = False
|
||||
self.source = source
|
||||
self.pos = 0
|
||||
self.context_stack = [Module(forms=[])]
|
||||
self.variable_labels = {}
|
||||
self.variable_words = {}
|
||||
self.context_stack = [Module(forms=[], variables=self.variable_labels)]
|
||||
self.definition_stack.clear()
|
||||
self.last_defined = None
|
||||
self.control_stack = []
|
||||
@@ -463,6 +492,7 @@ class Parser:
|
||||
module = self.context_stack.pop()
|
||||
if not isinstance(module, Module): # pragma: no cover - defensive
|
||||
raise ParseError("internal parser state corrupt")
|
||||
module.variables = dict(self.variable_labels)
|
||||
return module
|
||||
|
||||
# Internal helpers ---------------------------------------------------------
|
||||
@@ -1545,6 +1575,8 @@ class Assembler:
|
||||
for definition in runtime_defs:
|
||||
self._emit_definition(definition, emission.text)
|
||||
|
||||
self._emit_variables(module.variables)
|
||||
|
||||
if self._data_section is not None:
|
||||
if not self._data_section:
|
||||
self._data_section.append("data_start:")
|
||||
@@ -1554,6 +1586,21 @@ class Assembler:
|
||||
self._data_section = None
|
||||
return emission
|
||||
|
||||
def _emit_variables(self, variables: Dict[str, str]) -> None:
|
||||
if not variables:
|
||||
return
|
||||
self._ensure_data_start()
|
||||
existing = set()
|
||||
if self._data_section is not None:
|
||||
for line in self._data_section:
|
||||
if ":" in line:
|
||||
label = line.split(":", 1)[0]
|
||||
existing.add(label.strip())
|
||||
for label in variables.values():
|
||||
if label in existing:
|
||||
continue
|
||||
self._data_section.append(f"{label}: dq 0")
|
||||
|
||||
def _ensure_data_start(self) -> None:
|
||||
if self._data_section is None:
|
||||
raise CompileError("data section is not initialized")
|
||||
@@ -1859,6 +1906,84 @@ def macro_compile_time(ctx: MacroContext) -> Optional[List[Op]]:
|
||||
return None
|
||||
|
||||
|
||||
def macro_with(ctx: MacroContext) -> Optional[List[Op]]:
|
||||
parser = ctx.parser
|
||||
|
||||
names: List[str] = []
|
||||
template: Optional[Token] = None
|
||||
seen: set[str] = set()
|
||||
while True:
|
||||
if parser._eof():
|
||||
raise ParseError("missing 'in' after 'with'")
|
||||
tok = parser.next_token()
|
||||
template = template or tok
|
||||
if tok.lexeme == "in":
|
||||
break
|
||||
if not _is_identifier(tok.lexeme):
|
||||
raise ParseError("invalid variable name in 'with'")
|
||||
if tok.lexeme in seen:
|
||||
raise ParseError("duplicate variable name in 'with'")
|
||||
seen.add(tok.lexeme)
|
||||
names.append(tok.lexeme)
|
||||
if not names:
|
||||
raise ParseError("'with' requires at least one variable name")
|
||||
|
||||
body: List[Token] = []
|
||||
depth = 0
|
||||
while True:
|
||||
if parser._eof():
|
||||
raise ParseError("unterminated 'with' block (missing 'end')")
|
||||
tok = parser.next_token()
|
||||
if tok.lexeme == "end":
|
||||
if depth == 0:
|
||||
break
|
||||
depth -= 1
|
||||
body.append(tok)
|
||||
continue
|
||||
if tok.lexeme in ("with", "if", "for", "while", "begin", "word"):
|
||||
depth += 1
|
||||
body.append(tok)
|
||||
|
||||
helper_for: Dict[str, str] = {}
|
||||
for name in names:
|
||||
_, helper = parser.allocate_variable(name)
|
||||
helper_for[name] = helper
|
||||
|
||||
emitted: List[str] = []
|
||||
|
||||
# Initialize variables by storing current stack values into their buffers
|
||||
for name in reversed(names):
|
||||
helper = helper_for[name]
|
||||
emitted.append(helper)
|
||||
emitted.append("!")
|
||||
|
||||
i = 0
|
||||
while i < len(body):
|
||||
tok = body[i]
|
||||
name = tok.lexeme
|
||||
helper = helper_for.get(name)
|
||||
if helper is not None:
|
||||
next_tok = body[i + 1] if i + 1 < len(body) else None
|
||||
if next_tok is not None and next_tok.lexeme == "!":
|
||||
emitted.append(helper)
|
||||
emitted.append("!")
|
||||
i += 2
|
||||
continue
|
||||
if next_tok is not None and next_tok.lexeme == "@":
|
||||
emitted.append(helper)
|
||||
i += 1
|
||||
continue
|
||||
emitted.append(helper)
|
||||
emitted.append("@")
|
||||
i += 1
|
||||
continue
|
||||
emitted.append(tok.lexeme)
|
||||
i += 1
|
||||
|
||||
ctx.inject_tokens(emitted, template=template)
|
||||
return None
|
||||
|
||||
|
||||
def macro_begin_text_macro(ctx: MacroContext) -> Optional[List[Op]]:
|
||||
parser = ctx.parser
|
||||
if parser._eof():
|
||||
@@ -2539,6 +2664,7 @@ def bootstrap_dictionary() -> Dictionary:
|
||||
dictionary.register(Word(name="immediate", immediate=True, macro=macro_immediate))
|
||||
dictionary.register(Word(name="compile-only", immediate=True, macro=macro_compile_only))
|
||||
dictionary.register(Word(name="compile-time", immediate=True, macro=macro_compile_time))
|
||||
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))
|
||||
|
||||
4
tests/with_variables.expected
Normal file
4
tests/with_variables.expected
Normal file
@@ -0,0 +1,4 @@
|
||||
4
|
||||
3
|
||||
6
|
||||
5
|
||||
13
tests/with_variables.sl
Normal file
13
tests/with_variables.sl
Normal file
@@ -0,0 +1,13 @@
|
||||
import ../stdlib/stdlib.sl
|
||||
import ../stdlib/io.sl
|
||||
|
||||
word main
|
||||
3 4 5 6
|
||||
with a b c d in
|
||||
b puti cr
|
||||
a puti cr
|
||||
d puti cr
|
||||
c puti cr
|
||||
end
|
||||
0
|
||||
end
|
||||
1
tests/with_variables.test
Normal file
1
tests/with_variables.test
Normal file
@@ -0,0 +1 @@
|
||||
python main.py tests/with_variables.sl -o /tmp/with_variables > /dev/null && /tmp/with_variables
|
||||
Reference in New Issue
Block a user