added variables

This commit is contained in:
IgorCielniak
2026-01-08 16:05:43 +01:00
parent b9098d9893
commit 1727bab944
5 changed files with 146 additions and 1 deletions

View File

@@ -25,6 +25,7 @@
- `lookup`: resolves token → word entry; can be replaced to build new namespaces or module systems. - `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. - **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. - **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. - **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. - **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 librarys `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. - **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 librarys `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
View File

@@ -215,6 +215,7 @@ class AsmDefinition:
@dataclass @dataclass
class Module: class Module:
forms: List[Any] forms: List[Any]
variables: Dict[str, str] = field(default_factory=dict)
@dataclass @dataclass
@@ -344,6 +345,8 @@ class Parser:
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
self.variable_labels: Dict[str, str] = {}
self.variable_words: Dict[str, str] = {}
self.compile_time_vm = CompileTimeVM(self) self.compile_time_vm = CompileTimeVM(self)
def inject_token_objects(self, tokens: Sequence[Token]) -> None: def inject_token_objects(self, tokens: Sequence[Token]) -> None:
@@ -364,6 +367,30 @@ class Parser:
def most_recent_definition(self) -> Optional[Word]: def most_recent_definition(self) -> Optional[Word]:
return self.last_defined 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: def _handle_end_control(self) -> None:
"""Handle unified 'end' for all block types""" """Handle unified 'end' for all block types"""
if not self.control_stack: if not self.control_stack:
@@ -394,7 +421,9 @@ class Parser:
self._token_iter_exhausted = False self._token_iter_exhausted = False
self.source = source self.source = source
self.pos = 0 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.definition_stack.clear()
self.last_defined = None self.last_defined = None
self.control_stack = [] self.control_stack = []
@@ -463,6 +492,7 @@ class Parser:
module = self.context_stack.pop() module = self.context_stack.pop()
if not isinstance(module, Module): # pragma: no cover - defensive if not isinstance(module, Module): # pragma: no cover - defensive
raise ParseError("internal parser state corrupt") raise ParseError("internal parser state corrupt")
module.variables = dict(self.variable_labels)
return module return module
# Internal helpers --------------------------------------------------------- # Internal helpers ---------------------------------------------------------
@@ -1545,6 +1575,8 @@ class Assembler:
for definition in runtime_defs: for definition in runtime_defs:
self._emit_definition(definition, emission.text) self._emit_definition(definition, emission.text)
self._emit_variables(module.variables)
if self._data_section is not None: if self._data_section is not None:
if not self._data_section: if not self._data_section:
self._data_section.append("data_start:") self._data_section.append("data_start:")
@@ -1554,6 +1586,21 @@ class Assembler:
self._data_section = None self._data_section = None
return emission 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: def _ensure_data_start(self) -> None:
if self._data_section is None: if self._data_section is None:
raise CompileError("data section is not initialized") raise CompileError("data section is not initialized")
@@ -1859,6 +1906,84 @@ def macro_compile_time(ctx: MacroContext) -> Optional[List[Op]]:
return None 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]]: def macro_begin_text_macro(ctx: MacroContext) -> Optional[List[Op]]:
parser = ctx.parser parser = ctx.parser
if parser._eof(): 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="immediate", immediate=True, macro=macro_immediate))
dictionary.register(Word(name="compile-only", immediate=True, macro=macro_compile_only)) 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="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="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_begin))
dictionary.register(Word(name=";struct", immediate=True, macro=macro_struct_end)) dictionary.register(Word(name=";struct", immediate=True, macro=macro_struct_end))

View File

@@ -0,0 +1,4 @@
4
3
6
5

13
tests/with_variables.sl Normal file
View 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

View File

@@ -0,0 +1 @@
python main.py tests/with_variables.sl -o /tmp/with_variables > /dev/null && /tmp/with_variables