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.
|
- `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 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.
|
- **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
|
@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))
|
||||||
|
|||||||
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