small refactor and cleanup
This commit is contained in:
Binary file not shown.
295
main.py
295
main.py
@@ -187,30 +187,24 @@ class Reader:
|
|||||||
# ---------------------------------------------------------------------------
|
# ---------------------------------------------------------------------------
|
||||||
|
|
||||||
|
|
||||||
class ASTNode:
|
@dataclass
|
||||||
"""Base class for all AST nodes."""
|
class Op:
|
||||||
|
"""Flat operation used for both compile-time execution and emission."""
|
||||||
|
|
||||||
|
op: str
|
||||||
|
data: Any = None
|
||||||
|
|
||||||
|
|
||||||
@dataclass
|
@dataclass
|
||||||
class WordRef(ASTNode):
|
class Definition:
|
||||||
name: str
|
name: str
|
||||||
|
body: List[Op]
|
||||||
|
|
||||||
@dataclass
|
|
||||||
class Literal(ASTNode):
|
|
||||||
value: Any
|
|
||||||
|
|
||||||
|
|
||||||
@dataclass
|
|
||||||
class Definition(ASTNode):
|
|
||||||
name: str
|
|
||||||
body: List[ASTNode]
|
|
||||||
immediate: bool = False
|
immediate: bool = False
|
||||||
compile_only: bool = False
|
compile_only: bool = False
|
||||||
|
|
||||||
|
|
||||||
@dataclass
|
@dataclass
|
||||||
class AsmDefinition(ASTNode):
|
class AsmDefinition:
|
||||||
name: str
|
name: str
|
||||||
body: str
|
body: str
|
||||||
immediate: bool = False
|
immediate: bool = False
|
||||||
@@ -218,8 +212,8 @@ class AsmDefinition(ASTNode):
|
|||||||
|
|
||||||
|
|
||||||
@dataclass
|
@dataclass
|
||||||
class Module(ASTNode):
|
class Module:
|
||||||
forms: List[ASTNode]
|
forms: List[Any]
|
||||||
|
|
||||||
|
|
||||||
@dataclass
|
@dataclass
|
||||||
@@ -236,33 +230,6 @@ class StructField:
|
|||||||
size: int
|
size: int
|
||||||
|
|
||||||
|
|
||||||
@dataclass
|
|
||||||
class BranchZero(ASTNode):
|
|
||||||
target: str
|
|
||||||
|
|
||||||
|
|
||||||
@dataclass
|
|
||||||
class Jump(ASTNode):
|
|
||||||
target: str
|
|
||||||
|
|
||||||
|
|
||||||
@dataclass
|
|
||||||
class Label(ASTNode):
|
|
||||||
name: str
|
|
||||||
|
|
||||||
|
|
||||||
@dataclass
|
|
||||||
class ForBegin(ASTNode):
|
|
||||||
loop_label: str
|
|
||||||
end_label: str
|
|
||||||
|
|
||||||
|
|
||||||
@dataclass
|
|
||||||
class ForEnd(ASTNode):
|
|
||||||
loop_label: str
|
|
||||||
end_label: str
|
|
||||||
|
|
||||||
|
|
||||||
class MacroContext:
|
class MacroContext:
|
||||||
"""Small facade exposed to Python-defined macros."""
|
"""Small facade exposed to Python-defined macros."""
|
||||||
|
|
||||||
@@ -280,12 +247,12 @@ class MacroContext:
|
|||||||
return self._parser.peek_token()
|
return self._parser.peek_token()
|
||||||
|
|
||||||
def emit_literal(self, value: int) -> None:
|
def emit_literal(self, value: int) -> None:
|
||||||
self._parser.emit_node(Literal(value=value))
|
self._parser.emit_node(Op(op="literal", data=value))
|
||||||
|
|
||||||
def emit_word(self, name: str) -> None:
|
def emit_word(self, name: str) -> None:
|
||||||
self._parser.emit_node(WordRef(name=name))
|
self._parser.emit_node(Op(op="word", data=name))
|
||||||
|
|
||||||
def emit_node(self, node: ASTNode) -> None:
|
def emit_node(self, node: Op) -> None:
|
||||||
self._parser.emit_node(node)
|
self._parser.emit_node(node)
|
||||||
|
|
||||||
def inject_tokens(self, tokens: Sequence[str], template: Optional[Token] = None) -> None:
|
def inject_tokens(self, tokens: Sequence[str], template: Optional[Token] = None) -> None:
|
||||||
@@ -316,7 +283,7 @@ class MacroContext:
|
|||||||
return self._parser.most_recent_definition()
|
return self._parser.most_recent_definition()
|
||||||
|
|
||||||
|
|
||||||
MacroHandler = Callable[[MacroContext], Optional[List[ASTNode]]]
|
MacroHandler = Callable[[MacroContext], Optional[List[Op]]]
|
||||||
IntrinsicEmitter = Callable[["FunctionEmitter"], None]
|
IntrinsicEmitter = Callable[["FunctionEmitter"], None]
|
||||||
|
|
||||||
|
|
||||||
@@ -390,8 +357,8 @@ class Parser:
|
|||||||
self._ensure_tokens(self.pos)
|
self._ensure_tokens(self.pos)
|
||||||
return None if self._eof() else self.tokens[self.pos]
|
return None if self._eof() else self.tokens[self.pos]
|
||||||
|
|
||||||
def emit_node(self, node: ASTNode) -> None:
|
def emit_node(self, node: Op) -> None:
|
||||||
self._append_node(node)
|
self._append_op(node)
|
||||||
|
|
||||||
def most_recent_definition(self) -> Optional[Word]:
|
def most_recent_definition(self) -> Optional[Word]:
|
||||||
return self.last_defined
|
return self.last_defined
|
||||||
@@ -406,18 +373,18 @@ class Parser:
|
|||||||
if entry["type"] == "if":
|
if entry["type"] == "if":
|
||||||
# For if without else
|
# For if without else
|
||||||
if "false" in entry:
|
if "false" in entry:
|
||||||
self._append_node(Label(name=entry["false"]))
|
self._append_op(Op(op="label", data=entry["false"]))
|
||||||
elif entry["type"] == "else":
|
elif entry["type"] == "else":
|
||||||
self._append_node(Label(name=entry["end"]))
|
self._append_op(Op(op="label", data=entry["end"]))
|
||||||
elif entry["type"] == "while":
|
elif entry["type"] == "while":
|
||||||
self._append_node(Jump(target=entry["begin"]))
|
self._append_op(Op(op="jump", data=entry["begin"]))
|
||||||
self._append_node(Label(name=entry["end"]))
|
self._append_op(Op(op="label", data=entry["end"]))
|
||||||
elif entry["type"] == "for":
|
elif entry["type"] == "for":
|
||||||
# Emit ForEnd node for loop decrement
|
# Emit ForEnd node for loop decrement
|
||||||
self._append_node(ForEnd(loop_label=entry["loop"], end_label=entry["end"]))
|
self._append_op(Op(op="for_end", data={"loop": entry["loop"], "end": entry["end"]}))
|
||||||
elif entry["type"] == "begin":
|
elif entry["type"] == "begin":
|
||||||
self._append_node(Jump(target=entry["begin"]))
|
self._append_op(Op(op="jump", data=entry["begin"]))
|
||||||
self._append_node(Label(name=entry["end"]))
|
self._append_op(Op(op="label", data=entry["end"]))
|
||||||
|
|
||||||
# Parsing ------------------------------------------------------------------
|
# Parsing ------------------------------------------------------------------
|
||||||
def parse(self, tokens: Iterable[Token], source: str) -> Module:
|
def parse(self, tokens: Iterable[Token], source: str) -> Module:
|
||||||
@@ -608,12 +575,12 @@ class Parser:
|
|||||||
produced = word.macro(MacroContext(self))
|
produced = word.macro(MacroContext(self))
|
||||||
if produced:
|
if produced:
|
||||||
for node in produced:
|
for node in produced:
|
||||||
self._append_node(node)
|
self._append_op(node)
|
||||||
else:
|
else:
|
||||||
self._execute_immediate_word(word)
|
self._execute_immediate_word(word)
|
||||||
return
|
return
|
||||||
|
|
||||||
self._append_node(WordRef(name=token.lexeme))
|
self._append_op(Op(op="word", data=token.lexeme))
|
||||||
|
|
||||||
def _execute_immediate_word(self, word: Word) -> None:
|
def _execute_immediate_word(self, word: Word) -> None:
|
||||||
try:
|
try:
|
||||||
@@ -721,31 +688,31 @@ class Parser:
|
|||||||
|
|
||||||
def _handle_if_control(self) -> None:
|
def _handle_if_control(self) -> None:
|
||||||
false_label = self._new_label("if_false")
|
false_label = self._new_label("if_false")
|
||||||
self._append_node(BranchZero(target=false_label))
|
self._append_op(Op(op="branch_zero", data=false_label))
|
||||||
self._push_control({"type": "if", "false": false_label})
|
self._push_control({"type": "if", "false": false_label})
|
||||||
|
|
||||||
def _handle_else_control(self) -> None:
|
def _handle_else_control(self) -> None:
|
||||||
entry = self._pop_control(("if",))
|
entry = self._pop_control(("if",))
|
||||||
end_label = self._new_label("if_end")
|
end_label = self._new_label("if_end")
|
||||||
self._append_node(Jump(target=end_label))
|
self._append_op(Op(op="jump", data=end_label))
|
||||||
self._append_node(Label(name=entry["false"]))
|
self._append_op(Op(op="label", data=entry["false"]))
|
||||||
self._push_control({"type": "else", "end": end_label})
|
self._push_control({"type": "else", "end": end_label})
|
||||||
|
|
||||||
def _handle_for_control(self) -> None:
|
def _handle_for_control(self) -> None:
|
||||||
loop_label = self._new_label("for_loop")
|
loop_label = self._new_label("for_loop")
|
||||||
end_label = self._new_label("for_end")
|
end_label = self._new_label("for_end")
|
||||||
self._append_node(ForBegin(loop_label=loop_label, end_label=end_label))
|
self._append_op(Op(op="for_begin", data={"loop": loop_label, "end": end_label}))
|
||||||
self._push_control({"type": "for", "loop": loop_label, "end": end_label})
|
self._push_control({"type": "for", "loop": loop_label, "end": end_label})
|
||||||
|
|
||||||
def _handle_while_control(self) -> None:
|
def _handle_while_control(self) -> None:
|
||||||
begin_label = self._new_label("begin")
|
begin_label = self._new_label("begin")
|
||||||
end_label = self._new_label("end")
|
end_label = self._new_label("end")
|
||||||
self._append_node(Label(name=begin_label))
|
self._append_op(Op(op="label", data=begin_label))
|
||||||
self._push_control({"type": "begin", "begin": begin_label, "end": end_label})
|
self._push_control({"type": "begin", "begin": begin_label, "end": end_label})
|
||||||
|
|
||||||
def _handle_do_control(self) -> None:
|
def _handle_do_control(self) -> None:
|
||||||
entry = self._pop_control(("begin",))
|
entry = self._pop_control(("begin",))
|
||||||
self._append_node(BranchZero(target=entry["end"]))
|
self._append_op(Op(op="branch_zero", data=entry["end"]))
|
||||||
self._push_control(entry)
|
self._push_control(entry)
|
||||||
|
|
||||||
def _begin_definition(self, token: Token) -> None:
|
def _begin_definition(self, token: Token) -> None:
|
||||||
@@ -859,7 +826,7 @@ class Parser:
|
|||||||
def _py_exec_namespace(self) -> Dict[str, Any]:
|
def _py_exec_namespace(self) -> Dict[str, Any]:
|
||||||
return dict(PY_EXEC_GLOBALS)
|
return dict(PY_EXEC_GLOBALS)
|
||||||
|
|
||||||
def _append_node(self, node: ASTNode) -> None:
|
def _append_op(self, node: Op) -> None:
|
||||||
target = self.context_stack[-1]
|
target = self.context_stack[-1]
|
||||||
if isinstance(target, Module):
|
if isinstance(target, Module):
|
||||||
target.forms.append(node)
|
target.forms.append(node)
|
||||||
@@ -868,10 +835,10 @@ class Parser:
|
|||||||
else: # pragma: no cover - defensive
|
else: # pragma: no cover - defensive
|
||||||
raise ParseError("unknown parse context")
|
raise ParseError("unknown parse context")
|
||||||
|
|
||||||
def _try_literal(self, token: Token) -> None:
|
def _try_literal(self, token: Token) -> bool:
|
||||||
try:
|
try:
|
||||||
value = int(token.lexeme, 0)
|
value = int(token.lexeme, 0)
|
||||||
self._append_node(Literal(value=value))
|
self._append_op(Op(op="literal", data=value))
|
||||||
return True
|
return True
|
||||||
except ValueError:
|
except ValueError:
|
||||||
pass
|
pass
|
||||||
@@ -880,14 +847,14 @@ class Parser:
|
|||||||
try:
|
try:
|
||||||
if "." in token.lexeme or "e" in token.lexeme.lower():
|
if "." in token.lexeme or "e" in token.lexeme.lower():
|
||||||
value = float(token.lexeme)
|
value = float(token.lexeme)
|
||||||
self._append_node(Literal(value=value))
|
self._append_op(Op(op="literal", data=value))
|
||||||
return True
|
return True
|
||||||
except ValueError:
|
except ValueError:
|
||||||
pass
|
pass
|
||||||
|
|
||||||
string_value = _parse_string_literal(token)
|
string_value = _parse_string_literal(token)
|
||||||
if string_value is not None:
|
if string_value is not None:
|
||||||
self._append_node(Literal(value=string_value))
|
self._append_op(Op(op="literal", data=string_value))
|
||||||
return True
|
return True
|
||||||
|
|
||||||
return False
|
return False
|
||||||
@@ -1229,7 +1196,7 @@ class CompileTimeVM:
|
|||||||
raise ParseError(f"unknown word '{name}' during compile-time execution")
|
raise ParseError(f"unknown word '{name}' during compile-time execution")
|
||||||
self._call_word(word)
|
self._call_word(word)
|
||||||
|
|
||||||
def _execute_nodes(self, nodes: Sequence[ASTNode]) -> None:
|
def _execute_nodes(self, nodes: Sequence[Op]) -> None:
|
||||||
label_positions = self._label_positions(nodes)
|
label_positions = self._label_positions(nodes)
|
||||||
loop_pairs = self._for_pairs(nodes)
|
loop_pairs = self._for_pairs(nodes)
|
||||||
begin_pairs = self._begin_pairs(nodes)
|
begin_pairs = self._begin_pairs(nodes)
|
||||||
@@ -1238,12 +1205,16 @@ class CompileTimeVM:
|
|||||||
ip = 0
|
ip = 0
|
||||||
while ip < len(nodes):
|
while ip < len(nodes):
|
||||||
node = nodes[ip]
|
node = nodes[ip]
|
||||||
if isinstance(node, Literal):
|
kind = node.op
|
||||||
self.push(node.value)
|
data = node.data
|
||||||
|
|
||||||
|
if kind == "literal":
|
||||||
|
self.push(data)
|
||||||
ip += 1
|
ip += 1
|
||||||
continue
|
continue
|
||||||
if isinstance(node, WordRef):
|
|
||||||
name = node.name
|
if kind == "word":
|
||||||
|
name = str(data)
|
||||||
if name == "begin":
|
if name == "begin":
|
||||||
end_idx = begin_pairs.get(ip)
|
end_idx = begin_pairs.get(ip)
|
||||||
if end_idx is None:
|
if end_idx is None:
|
||||||
@@ -1270,9 +1241,9 @@ class CompileTimeVM:
|
|||||||
self._call_word_by_name(name)
|
self._call_word_by_name(name)
|
||||||
ip += 1
|
ip += 1
|
||||||
continue
|
continue
|
||||||
if isinstance(node, BranchZero):
|
|
||||||
|
if kind == "branch_zero":
|
||||||
condition = self.pop()
|
condition = self.pop()
|
||||||
flag: bool
|
|
||||||
if isinstance(condition, bool):
|
if isinstance(condition, bool):
|
||||||
flag = condition
|
flag = condition
|
||||||
elif isinstance(condition, int):
|
elif isinstance(condition, int):
|
||||||
@@ -1280,17 +1251,20 @@ class CompileTimeVM:
|
|||||||
else:
|
else:
|
||||||
raise ParseError("branch expects integer or boolean condition")
|
raise ParseError("branch expects integer or boolean condition")
|
||||||
if not flag:
|
if not flag:
|
||||||
ip = self._jump_to_label(label_positions, node.target)
|
ip = self._jump_to_label(label_positions, str(data))
|
||||||
else:
|
else:
|
||||||
ip += 1
|
ip += 1
|
||||||
continue
|
continue
|
||||||
if isinstance(node, Jump):
|
|
||||||
ip = self._jump_to_label(label_positions, node.target)
|
if kind == "jump":
|
||||||
|
ip = self._jump_to_label(label_positions, str(data))
|
||||||
continue
|
continue
|
||||||
if isinstance(node, Label):
|
|
||||||
|
if kind == "label":
|
||||||
ip += 1
|
ip += 1
|
||||||
continue
|
continue
|
||||||
if isinstance(node, ForBegin):
|
|
||||||
|
if kind == "for_begin":
|
||||||
count = self.pop_int()
|
count = self.pop_int()
|
||||||
if count <= 0:
|
if count <= 0:
|
||||||
match = loop_pairs.get(ip)
|
match = loop_pairs.get(ip)
|
||||||
@@ -1301,7 +1275,8 @@ class CompileTimeVM:
|
|||||||
self.loop_stack.append({"remaining": count, "begin": ip, "initial": count})
|
self.loop_stack.append({"remaining": count, "begin": ip, "initial": count})
|
||||||
ip += 1
|
ip += 1
|
||||||
continue
|
continue
|
||||||
if isinstance(node, ForEnd):
|
|
||||||
|
if kind == "for_end":
|
||||||
if not self.loop_stack:
|
if not self.loop_stack:
|
||||||
raise ParseError("'next' without matching 'for'")
|
raise ParseError("'next' without matching 'for'")
|
||||||
frame = self.loop_stack[-1]
|
frame = self.loop_stack[-1]
|
||||||
@@ -1312,22 +1287,23 @@ class CompileTimeVM:
|
|||||||
self.loop_stack.pop()
|
self.loop_stack.pop()
|
||||||
ip += 1
|
ip += 1
|
||||||
continue
|
continue
|
||||||
raise ParseError(f"unsupported compile-time AST node {node!r}")
|
|
||||||
|
|
||||||
def _label_positions(self, nodes: Sequence[ASTNode]) -> Dict[str, int]:
|
raise ParseError(f"unsupported compile-time op {node!r}")
|
||||||
|
|
||||||
|
def _label_positions(self, nodes: Sequence[Op]) -> Dict[str, int]:
|
||||||
positions: Dict[str, int] = {}
|
positions: Dict[str, int] = {}
|
||||||
for idx, node in enumerate(nodes):
|
for idx, node in enumerate(nodes):
|
||||||
if isinstance(node, Label):
|
if node.op == "label":
|
||||||
positions[node.name] = idx
|
positions[str(node.data)] = idx
|
||||||
return positions
|
return positions
|
||||||
|
|
||||||
def _for_pairs(self, nodes: Sequence[ASTNode]) -> Dict[int, int]:
|
def _for_pairs(self, nodes: Sequence[Op]) -> Dict[int, int]:
|
||||||
stack: List[int] = []
|
stack: List[int] = []
|
||||||
pairs: Dict[int, int] = {}
|
pairs: Dict[int, int] = {}
|
||||||
for idx, node in enumerate(nodes):
|
for idx, node in enumerate(nodes):
|
||||||
if isinstance(node, ForBegin):
|
if node.op == "for_begin":
|
||||||
stack.append(idx)
|
stack.append(idx)
|
||||||
elif isinstance(node, ForEnd):
|
elif node.op == "for_end":
|
||||||
if not stack:
|
if not stack:
|
||||||
raise ParseError("'next' without matching 'for'")
|
raise ParseError("'next' without matching 'for'")
|
||||||
begin_idx = stack.pop()
|
begin_idx = stack.pop()
|
||||||
@@ -1337,13 +1313,13 @@ class CompileTimeVM:
|
|||||||
raise ParseError("'for' without matching 'next'")
|
raise ParseError("'for' without matching 'next'")
|
||||||
return pairs
|
return pairs
|
||||||
|
|
||||||
def _begin_pairs(self, nodes: Sequence[ASTNode]) -> Dict[int, int]:
|
def _begin_pairs(self, nodes: Sequence[Op]) -> Dict[int, int]:
|
||||||
stack: List[int] = []
|
stack: List[int] = []
|
||||||
pairs: Dict[int, int] = {}
|
pairs: Dict[int, int] = {}
|
||||||
for idx, node in enumerate(nodes):
|
for idx, node in enumerate(nodes):
|
||||||
if isinstance(node, WordRef) and node.name == "begin":
|
if node.op == "word" and node.data == "begin":
|
||||||
stack.append(idx)
|
stack.append(idx)
|
||||||
elif isinstance(node, WordRef) and node.name == "again":
|
elif node.op == "word" and node.data == "again":
|
||||||
if not stack:
|
if not stack:
|
||||||
raise ParseError("'again' without matching 'begin'")
|
raise ParseError("'again' without matching 'begin'")
|
||||||
begin_idx = stack.pop()
|
begin_idx = stack.pop()
|
||||||
@@ -1611,48 +1587,57 @@ class Assembler:
|
|||||||
else:
|
else:
|
||||||
builder.emit("")
|
builder.emit("")
|
||||||
|
|
||||||
def _emit_node(self, node: ASTNode, builder: FunctionEmitter) -> None:
|
def _emit_node(self, node: Op, builder: FunctionEmitter) -> None:
|
||||||
if isinstance(node, Literal):
|
kind = node.op
|
||||||
if isinstance(node.value, int):
|
data = node.data
|
||||||
builder.push_literal(node.value)
|
|
||||||
|
if kind == "literal":
|
||||||
|
if isinstance(data, int):
|
||||||
|
builder.push_literal(data)
|
||||||
return
|
return
|
||||||
if isinstance(node.value, float):
|
if isinstance(data, float):
|
||||||
label = self._intern_float_literal(node.value)
|
label = self._intern_float_literal(data)
|
||||||
builder.push_float(label)
|
builder.push_float(label)
|
||||||
return
|
return
|
||||||
if isinstance(node.value, str):
|
if isinstance(data, str):
|
||||||
label, length = self._intern_string_literal(node.value)
|
label, length = self._intern_string_literal(data)
|
||||||
builder.push_label(label)
|
builder.push_label(label)
|
||||||
builder.push_literal(length)
|
builder.push_literal(length)
|
||||||
return
|
return
|
||||||
raise CompileError(f"unsupported literal type {type(node.value)!r}")
|
raise CompileError(f"unsupported literal type {type(data)!r}")
|
||||||
return
|
|
||||||
if isinstance(node, WordRef):
|
|
||||||
self._emit_wordref(node, builder)
|
|
||||||
return
|
|
||||||
if isinstance(node, BranchZero):
|
|
||||||
self._emit_branch_zero(node, builder)
|
|
||||||
return
|
|
||||||
if isinstance(node, Jump):
|
|
||||||
builder.emit(f" jmp {node.target}")
|
|
||||||
return
|
|
||||||
if isinstance(node, Label):
|
|
||||||
builder.emit(f"{node.name}:")
|
|
||||||
return
|
|
||||||
if isinstance(node, ForBegin):
|
|
||||||
self._emit_for_begin(node, builder)
|
|
||||||
return
|
|
||||||
if isinstance(node, ForEnd):
|
|
||||||
self._emit_for_next(node, builder)
|
|
||||||
return
|
|
||||||
raise CompileError(f"unsupported AST node {node!r}")
|
|
||||||
|
|
||||||
def _emit_wordref(self, ref: WordRef, builder: FunctionEmitter) -> None:
|
if kind == "word":
|
||||||
word = self.dictionary.lookup(ref.name)
|
self._emit_wordref(str(data), builder)
|
||||||
|
return
|
||||||
|
|
||||||
|
if kind == "branch_zero":
|
||||||
|
self._emit_branch_zero(str(data), builder)
|
||||||
|
return
|
||||||
|
|
||||||
|
if kind == "jump":
|
||||||
|
builder.emit(f" jmp {data}")
|
||||||
|
return
|
||||||
|
|
||||||
|
if kind == "label":
|
||||||
|
builder.emit(f"{data}:")
|
||||||
|
return
|
||||||
|
|
||||||
|
if kind == "for_begin":
|
||||||
|
self._emit_for_begin(data, builder)
|
||||||
|
return
|
||||||
|
|
||||||
|
if kind == "for_end":
|
||||||
|
self._emit_for_next(data, builder)
|
||||||
|
return
|
||||||
|
|
||||||
|
raise CompileError(f"unsupported op {node!r}")
|
||||||
|
|
||||||
|
def _emit_wordref(self, name: str, builder: FunctionEmitter) -> None:
|
||||||
|
word = self.dictionary.lookup(name)
|
||||||
if word is None:
|
if word is None:
|
||||||
raise CompileError(f"unknown word '{ref.name}'")
|
raise CompileError(f"unknown word '{name}'")
|
||||||
if word.compile_only:
|
if word.compile_only:
|
||||||
raise CompileError(f"word '{ref.name}' is compile-time only")
|
raise CompileError(f"word '{name}' is compile-time only")
|
||||||
if word.intrinsic:
|
if word.intrinsic:
|
||||||
word.intrinsic(builder)
|
word.intrinsic(builder)
|
||||||
return
|
return
|
||||||
@@ -1669,7 +1654,7 @@ class Assembler:
|
|||||||
ret_type = signature[1] if signature else None
|
ret_type = signature[1] if signature else None
|
||||||
|
|
||||||
if len(arg_types) != inputs and signature:
|
if len(arg_types) != inputs and signature:
|
||||||
raise CompileError(f"extern '{ref.name}' mismatch: {inputs} inputs vs {len(arg_types)} types")
|
raise CompileError(f"extern '{name}' mismatch: {inputs} inputs vs {len(arg_types)} types")
|
||||||
|
|
||||||
int_idx = 0
|
int_idx = 0
|
||||||
xmm_idx = 0
|
xmm_idx = 0
|
||||||
@@ -1679,19 +1664,19 @@ class Assembler:
|
|||||||
if not arg_types:
|
if not arg_types:
|
||||||
# Legacy/Raw mode: assume all ints
|
# Legacy/Raw mode: assume all ints
|
||||||
if inputs > 6:
|
if inputs > 6:
|
||||||
raise CompileError(f"extern '{ref.name}' has too many inputs ({inputs} > 6)")
|
raise CompileError(f"extern '{name}' has too many inputs ({inputs} > 6)")
|
||||||
for i in range(inputs):
|
for i in range(inputs):
|
||||||
mapping.append(("int", regs[i]))
|
mapping.append(("int", regs[i]))
|
||||||
else:
|
else:
|
||||||
for type_name in arg_types:
|
for type_name in arg_types:
|
||||||
if type_name in ("float", "double"):
|
if type_name in ("float", "double"):
|
||||||
if xmm_idx >= 8:
|
if xmm_idx >= 8:
|
||||||
raise CompileError(f"extern '{ref.name}' has too many float inputs")
|
raise CompileError(f"extern '{name}' has too many float inputs")
|
||||||
mapping.append(("float", xmm_regs[xmm_idx]))
|
mapping.append(("float", xmm_regs[xmm_idx]))
|
||||||
xmm_idx += 1
|
xmm_idx += 1
|
||||||
else:
|
else:
|
||||||
if int_idx >= 6:
|
if int_idx >= 6:
|
||||||
raise CompileError(f"extern '{ref.name}' has too many int inputs")
|
raise CompileError(f"extern '{name}' has too many int inputs")
|
||||||
mapping.append(("int", regs[int_idx]))
|
mapping.append(("int", regs[int_idx]))
|
||||||
int_idx += 1
|
int_idx += 1
|
||||||
|
|
||||||
@@ -1706,7 +1691,7 @@ class Assembler:
|
|||||||
builder.emit(" mov rbp, rsp")
|
builder.emit(" mov rbp, rsp")
|
||||||
builder.emit(" and rsp, -16")
|
builder.emit(" and rsp, -16")
|
||||||
builder.emit(f" mov al, {xmm_idx}")
|
builder.emit(f" mov al, {xmm_idx}")
|
||||||
builder.emit(f" call {ref.name}")
|
builder.emit(f" call {name}")
|
||||||
builder.emit(" leave")
|
builder.emit(" leave")
|
||||||
|
|
||||||
# Handle Return Value
|
# Handle Return Value
|
||||||
@@ -1721,30 +1706,34 @@ class Assembler:
|
|||||||
raise CompileError("extern only supports 0 or 1 output")
|
raise CompileError("extern only supports 0 or 1 output")
|
||||||
else:
|
else:
|
||||||
# Emit call to unresolved symbol (let linker resolve it)
|
# Emit call to unresolved symbol (let linker resolve it)
|
||||||
builder.emit(f" call {ref.name}")
|
builder.emit(f" call {name}")
|
||||||
else:
|
else:
|
||||||
builder.emit(f" call {sanitize_label(ref.name)}")
|
builder.emit(f" call {sanitize_label(name)}")
|
||||||
|
|
||||||
def _emit_branch_zero(self, node: BranchZero, 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")
|
||||||
builder.emit(f" jz {node.target}")
|
builder.emit(f" jz {target}")
|
||||||
|
|
||||||
def _emit_for_begin(self, node: ForBegin, builder: FunctionEmitter) -> None:
|
def _emit_for_begin(self, data: Dict[str, str], builder: FunctionEmitter) -> None:
|
||||||
|
loop_label = data["loop"]
|
||||||
|
end_label = data["end"]
|
||||||
builder.pop_to("rax")
|
builder.pop_to("rax")
|
||||||
builder.emit(" cmp rax, 0")
|
builder.emit(" cmp rax, 0")
|
||||||
builder.emit(f" jle {node.end_label}")
|
builder.emit(f" jle {end_label}")
|
||||||
builder.emit(" sub r13, 8")
|
builder.emit(" sub r13, 8")
|
||||||
builder.emit(" mov [r13], rax")
|
builder.emit(" mov [r13], rax")
|
||||||
builder.emit(f"{node.loop_label}:")
|
builder.emit(f"{loop_label}:")
|
||||||
|
|
||||||
def _emit_for_next(self, node: ForEnd, builder: FunctionEmitter) -> None:
|
def _emit_for_next(self, data: Dict[str, str], builder: FunctionEmitter) -> None:
|
||||||
|
loop_label = data["loop"]
|
||||||
|
end_label = data["end"]
|
||||||
builder.emit(" mov rax, [r13]")
|
builder.emit(" mov rax, [r13]")
|
||||||
builder.emit(" dec rax")
|
builder.emit(" dec rax")
|
||||||
builder.emit(" mov [r13], rax")
|
builder.emit(" mov [r13], rax")
|
||||||
builder.emit(f" jg {node.loop_label}")
|
builder.emit(f" jg {loop_label}")
|
||||||
builder.emit(" add r13, 8")
|
builder.emit(" add r13, 8")
|
||||||
builder.emit(f"{node.end_label}:")
|
builder.emit(f"{end_label}:")
|
||||||
|
|
||||||
def _runtime_prelude(self) -> List[str]:
|
def _runtime_prelude(self) -> List[str]:
|
||||||
return [
|
return [
|
||||||
@@ -1804,7 +1793,7 @@ class Assembler:
|
|||||||
# ---------------------------------------------------------------------------
|
# ---------------------------------------------------------------------------
|
||||||
|
|
||||||
|
|
||||||
def macro_immediate(ctx: MacroContext) -> Optional[List[ASTNode]]:
|
def macro_immediate(ctx: MacroContext) -> Optional[List[Op]]:
|
||||||
parser = ctx.parser
|
parser = ctx.parser
|
||||||
word = parser.most_recent_definition()
|
word = parser.most_recent_definition()
|
||||||
if word is None:
|
if word is None:
|
||||||
@@ -1815,7 +1804,7 @@ def macro_immediate(ctx: MacroContext) -> Optional[List[ASTNode]]:
|
|||||||
return None
|
return None
|
||||||
|
|
||||||
|
|
||||||
def macro_compile_only(ctx: MacroContext) -> Optional[List[ASTNode]]:
|
def macro_compile_only(ctx: MacroContext) -> Optional[List[Op]]:
|
||||||
parser = ctx.parser
|
parser = ctx.parser
|
||||||
word = parser.most_recent_definition()
|
word = parser.most_recent_definition()
|
||||||
if word is None:
|
if word is None:
|
||||||
@@ -1826,7 +1815,7 @@ def macro_compile_only(ctx: MacroContext) -> Optional[List[ASTNode]]:
|
|||||||
return None
|
return None
|
||||||
|
|
||||||
|
|
||||||
def macro_compile_time(ctx: MacroContext) -> Optional[List[ASTNode]]:
|
def macro_compile_time(ctx: MacroContext) -> Optional[List[Op]]:
|
||||||
"""Run the next word at compile time and still emit it for runtime."""
|
"""Run the next word at compile time and still emit it for runtime."""
|
||||||
parser = ctx.parser
|
parser = ctx.parser
|
||||||
if parser._eof():
|
if parser._eof():
|
||||||
@@ -1840,11 +1829,11 @@ def macro_compile_time(ctx: MacroContext) -> Optional[List[ASTNode]]:
|
|||||||
raise ParseError(f"word '{name}' is compile-time only")
|
raise ParseError(f"word '{name}' is compile-time only")
|
||||||
parser.compile_time_vm.invoke(word)
|
parser.compile_time_vm.invoke(word)
|
||||||
if isinstance(parser.context_stack[-1], Definition):
|
if isinstance(parser.context_stack[-1], Definition):
|
||||||
parser.emit_node(WordRef(name=name))
|
parser.emit_node(Op(op="word", data=name))
|
||||||
return None
|
return None
|
||||||
|
|
||||||
|
|
||||||
def macro_begin_text_macro(ctx: MacroContext) -> Optional[List[ASTNode]]:
|
def macro_begin_text_macro(ctx: MacroContext) -> Optional[List[Op]]:
|
||||||
parser = ctx.parser
|
parser = ctx.parser
|
||||||
if parser._eof():
|
if parser._eof():
|
||||||
raise ParseError("macro name missing after 'macro:'")
|
raise ParseError("macro name missing after 'macro:'")
|
||||||
@@ -1861,7 +1850,7 @@ def macro_begin_text_macro(ctx: MacroContext) -> Optional[List[ASTNode]]:
|
|||||||
return None
|
return None
|
||||||
|
|
||||||
|
|
||||||
def macro_end_text_macro(ctx: MacroContext) -> Optional[List[ASTNode]]:
|
def macro_end_text_macro(ctx: MacroContext) -> Optional[List[Op]]:
|
||||||
parser = ctx.parser
|
parser = ctx.parser
|
||||||
if parser.macro_recording is None:
|
if parser.macro_recording is None:
|
||||||
raise ParseError("';macro' without matching 'macro:'")
|
raise ParseError("';macro' without matching 'macro:'")
|
||||||
@@ -2458,13 +2447,7 @@ def _register_compile_time_primitives(dictionary: Dictionary) -> None:
|
|||||||
PY_EXEC_GLOBALS: Dict[str, Any] = {
|
PY_EXEC_GLOBALS: Dict[str, Any] = {
|
||||||
"MacroContext": MacroContext,
|
"MacroContext": MacroContext,
|
||||||
"Token": Token,
|
"Token": Token,
|
||||||
"Literal": Literal,
|
"Op": Op,
|
||||||
"WordRef": WordRef,
|
|
||||||
"BranchZero": BranchZero,
|
|
||||||
"Jump": Jump,
|
|
||||||
"Label": Label,
|
|
||||||
"ForBegin": ForBegin,
|
|
||||||
"ForEnd": ForEnd,
|
|
||||||
"StructField": StructField,
|
"StructField": StructField,
|
||||||
"Definition": Definition,
|
"Definition": Definition,
|
||||||
"Module": Module,
|
"Module": Module,
|
||||||
@@ -2474,7 +2457,7 @@ PY_EXEC_GLOBALS: Dict[str, Any] = {
|
|||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
def macro_struct_begin(ctx: MacroContext) -> Optional[List[ASTNode]]:
|
def macro_struct_begin(ctx: MacroContext) -> Optional[List[Op]]:
|
||||||
parser = ctx.parser
|
parser = ctx.parser
|
||||||
if parser._eof():
|
if parser._eof():
|
||||||
raise ParseError("struct name missing after 'struct:'")
|
raise ParseError("struct name missing after 'struct:'")
|
||||||
@@ -2529,7 +2512,7 @@ def macro_struct_begin(ctx: MacroContext) -> Optional[List[ASTNode]]:
|
|||||||
return None
|
return None
|
||||||
|
|
||||||
|
|
||||||
def macro_struct_end(ctx: MacroContext) -> Optional[List[ASTNode]]:
|
def macro_struct_end(ctx: MacroContext) -> Optional[List[Op]]:
|
||||||
raise ParseError("';struct' must follow a 'struct:' block")
|
raise ParseError("';struct' must follow a 'struct:' block")
|
||||||
|
|
||||||
|
|
||||||
|
|||||||
8
mem.sl
8
mem.sl
@@ -1,8 +0,0 @@
|
|||||||
import stdlib/stdlib.sl
|
|
||||||
import stdlib/io.sl
|
|
||||||
import stdlib/debug.sl
|
|
||||||
|
|
||||||
: main
|
|
||||||
mem dup 5 swap !
|
|
||||||
@ puti cr
|
|
||||||
;
|
|
||||||
15
stdlib/mem.sl
Normal file
15
stdlib/mem.sl
Normal file
@@ -0,0 +1,15 @@
|
|||||||
|
import stdlib.sl
|
||||||
|
|
||||||
|
: alloc
|
||||||
|
0 # addr hint (NULL)
|
||||||
|
swap # size
|
||||||
|
3 # prot (PROT_READ | PROT_WRITE)
|
||||||
|
34 # flags (MAP_PRIVATE | MAP_ANON)
|
||||||
|
-1 # fd
|
||||||
|
0 # offset
|
||||||
|
mmap
|
||||||
|
;
|
||||||
|
|
||||||
|
: free
|
||||||
|
munmap drop
|
||||||
|
;
|
||||||
Binary file not shown.
2
tests/alloc.expected
Normal file
2
tests/alloc.expected
Normal file
@@ -0,0 +1,2 @@
|
|||||||
|
111
|
||||||
|
222
|
||||||
@@ -1,19 +1,6 @@
|
|||||||
import stdlib/stdlib.sl
|
import ../stdlib/stdlib.sl
|
||||||
import stdlib/io.sl
|
import ../stdlib/io.sl
|
||||||
|
import ../stdlib/mem.sl
|
||||||
: alloc
|
|
||||||
0 # addr hint (NULL)
|
|
||||||
swap # size
|
|
||||||
3 # prot (PROT_READ | PROT_WRITE)
|
|
||||||
34 # flags (MAP_PRIVATE | MAP_ANON)
|
|
||||||
-1 # fd
|
|
||||||
0 # offset
|
|
||||||
mmap
|
|
||||||
;
|
|
||||||
|
|
||||||
: free
|
|
||||||
munmap drop
|
|
||||||
;
|
|
||||||
|
|
||||||
: test-mem-alloc
|
: test-mem-alloc
|
||||||
4096 alloc dup 1337 swap ! # allocate 4096 bytes, store 1337 at start
|
4096 alloc dup 1337 swap ! # allocate 4096 bytes, store 1337 at start
|
||||||
@@ -26,7 +13,7 @@ struct: Point
|
|||||||
field y 8
|
field y 8
|
||||||
;struct
|
;struct
|
||||||
|
|
||||||
: main2
|
: main
|
||||||
32 alloc # allocate 32 bytes (enough for a Point struct)
|
32 alloc # allocate 32 bytes (enough for a Point struct)
|
||||||
dup 111 swap Point.x!
|
dup 111 swap Point.x!
|
||||||
dup 222 swap Point.y!
|
dup 222 swap Point.y!
|
||||||
1
tests/alloc.test
Normal file
1
tests/alloc.test
Normal file
@@ -0,0 +1 @@
|
|||||||
|
python main.py tests/alloc.sl -o /tmp/alloc > /dev/null && /tmp/alloc
|
||||||
2
tests/call_syntax_parens.expected
Normal file
2
tests/call_syntax_parens.expected
Normal file
@@ -0,0 +1,2 @@
|
|||||||
|
42
|
||||||
|
3
|
||||||
16
tests/call_syntax_parens.sl
Normal file
16
tests/call_syntax_parens.sl
Normal file
@@ -0,0 +1,16 @@
|
|||||||
|
import ../stdlib/stdlib.sl
|
||||||
|
import ../stdlib/io.sl
|
||||||
|
import ../fn.sl
|
||||||
|
|
||||||
|
: main
|
||||||
|
2 40 +
|
||||||
|
puti cr
|
||||||
|
extend-syntax
|
||||||
|
foo(1, 2)
|
||||||
|
puti cr
|
||||||
|
0
|
||||||
|
;
|
||||||
|
|
||||||
|
fn foo(int a, int b){
|
||||||
|
return a + b;
|
||||||
|
}
|
||||||
1
tests/call_syntax_parens.test
Normal file
1
tests/call_syntax_parens.test
Normal file
@@ -0,0 +1 @@
|
|||||||
|
python main.py tests/call_syntax_parens.sl -o /tmp/call_syntax_parens > /dev/null && /tmp/call_syntax_parens
|
||||||
27
tests/fib.expected
Normal file
27
tests/fib.expected
Normal file
@@ -0,0 +1,27 @@
|
|||||||
|
1
|
||||||
|
1
|
||||||
|
2
|
||||||
|
3
|
||||||
|
5
|
||||||
|
8
|
||||||
|
13
|
||||||
|
21
|
||||||
|
34
|
||||||
|
55
|
||||||
|
89
|
||||||
|
144
|
||||||
|
233
|
||||||
|
377
|
||||||
|
610
|
||||||
|
987
|
||||||
|
1597
|
||||||
|
2584
|
||||||
|
4181
|
||||||
|
6765
|
||||||
|
10946
|
||||||
|
17711
|
||||||
|
28657
|
||||||
|
46368
|
||||||
|
75025
|
||||||
|
-------
|
||||||
|
25 numbers printed from the fibonaci sequence
|
||||||
@@ -1,5 +1,6 @@
|
|||||||
import stdlib/stdlib.sl
|
import ../stdlib/stdlib.sl
|
||||||
import stdlib/io.sl
|
import ../stdlib/io.sl
|
||||||
|
import ../stdlib/debug.sl
|
||||||
|
|
||||||
: main
|
: main
|
||||||
1 1 2dup 2dup puti cr puti cr
|
1 1 2dup 2dup puti cr puti cr
|
||||||
@@ -13,6 +14,7 @@ import stdlib/io.sl
|
|||||||
"-------" puts
|
"-------" puts
|
||||||
r> 3 + puti
|
r> 3 + puti
|
||||||
" numbers printed from the fibonaci sequence" puts
|
" numbers printed from the fibonaci sequence" puts
|
||||||
|
0
|
||||||
;
|
;
|
||||||
|
|
||||||
: main2
|
: main2
|
||||||
1
tests/fib.test
Normal file
1
tests/fib.test
Normal file
@@ -0,0 +1 @@
|
|||||||
|
python main.py tests/fib.sl -o /tmp/fib > /dev/null && /tmp/fib
|
||||||
33
tests/integration_core.expected
Normal file
33
tests/integration_core.expected
Normal file
@@ -0,0 +1,33 @@
|
|||||||
|
12
|
||||||
|
7
|
||||||
|
42
|
||||||
|
12
|
||||||
|
1
|
||||||
|
10
|
||||||
|
22
|
||||||
|
3
|
||||||
|
123
|
||||||
|
1337
|
||||||
|
81
|
||||||
|
99
|
||||||
|
13
|
||||||
|
111
|
||||||
|
60
|
||||||
|
5
|
||||||
|
123
|
||||||
|
1
|
||||||
|
0
|
||||||
|
1
|
||||||
|
0
|
||||||
|
1
|
||||||
|
0
|
||||||
|
1
|
||||||
|
0
|
||||||
|
1
|
||||||
|
0
|
||||||
|
1
|
||||||
|
0
|
||||||
|
111
|
||||||
|
222
|
||||||
|
16
|
||||||
|
70
|
||||||
@@ -1,6 +1,6 @@
|
|||||||
import stdlib/stdlib.sl
|
import ../stdlib/stdlib.sl
|
||||||
import stdlib/io.sl
|
import ../stdlib/io.sl
|
||||||
import fn.sl
|
import ../fn.sl
|
||||||
|
|
||||||
:asm mem-slot {
|
:asm mem-slot {
|
||||||
lea rax, [rel print_buf]
|
lea rax, [rel print_buf]
|
||||||
1
tests/integration_core.test
Normal file
1
tests/integration_core.test
Normal file
@@ -0,0 +1 @@
|
|||||||
|
python main.py tests/integration_core.sl -o /tmp/integration_core > /dev/null && /tmp/integration_core
|
||||||
1
tests/io_read_file.expected
Normal file
1
tests/io_read_file.expected
Normal file
@@ -0,0 +1 @@
|
|||||||
|
read_file works
|
||||||
@@ -1,8 +1,12 @@
|
|||||||
import stdlib/stdlib.sl
|
import ../stdlib/stdlib.sl
|
||||||
import stdlib/io.sl
|
import ../stdlib/io.sl
|
||||||
|
|
||||||
: main
|
: main
|
||||||
"/etc/hostname" # (addr len)
|
"/tmp/l2_read_file_test.txt"
|
||||||
|
"read_file works\n"
|
||||||
|
write_file drop
|
||||||
|
|
||||||
|
"/tmp/l2_read_file_test.txt" # (addr len)
|
||||||
read_file # (file_addr file_len)
|
read_file # (file_addr file_len)
|
||||||
dup 0 > if # if file_len > 0, success
|
dup 0 > if # if file_len > 0, success
|
||||||
write_buf # print file contents (file_len file_addr)
|
write_buf # print file contents (file_len file_addr)
|
||||||
1
tests/io_read_file.test
Normal file
1
tests/io_read_file.test
Normal file
@@ -0,0 +1 @@
|
|||||||
|
python main.py tests/io_read_file.sl -o /tmp/io_read_file > /dev/null && /tmp/io_read_file
|
||||||
1
tests/io_read_stdin.expected
Normal file
1
tests/io_read_stdin.expected
Normal file
@@ -0,0 +1 @@
|
|||||||
|
stdin via test
|
||||||
@@ -1,5 +1,5 @@
|
|||||||
import stdlib/stdlib.sl
|
import ../stdlib/stdlib.sl
|
||||||
import stdlib/io.sl
|
import ../stdlib/io.sl
|
||||||
|
|
||||||
: main
|
: main
|
||||||
1024
|
1024
|
||||||
1
tests/io_read_stdin.test
Normal file
1
tests/io_read_stdin.test
Normal file
@@ -0,0 +1 @@
|
|||||||
|
python main.py tests/io_read_stdin.sl -o /tmp/io_read_stdin > /dev/null && printf 'stdin via test\n' | /tmp/io_read_stdin
|
||||||
1
tests/io_write_buf.expected
Normal file
1
tests/io_write_buf.expected
Normal file
@@ -0,0 +1 @@
|
|||||||
|
hello from write_buf test
|
||||||
@@ -1,5 +1,5 @@
|
|||||||
import stdlib/stdlib.sl
|
import ../stdlib/stdlib.sl
|
||||||
import stdlib/io.sl
|
import ../stdlib/io.sl
|
||||||
|
|
||||||
: main
|
: main
|
||||||
"hello from write_buf test\n"
|
"hello from write_buf test\n"
|
||||||
1
tests/io_write_buf.test
Normal file
1
tests/io_write_buf.test
Normal file
@@ -0,0 +1 @@
|
|||||||
|
python main.py tests/io_write_buf.sl -o /tmp/io_write_buf > /dev/null && /tmp/io_write_buf
|
||||||
2
tests/io_write_file.expected
Normal file
2
tests/io_write_file.expected
Normal file
@@ -0,0 +1,2 @@
|
|||||||
|
wrote bytes:
|
||||||
|
27
|
||||||
@@ -1,9 +1,9 @@
|
|||||||
import stdlib/stdlib.sl
|
import ../stdlib/stdlib.sl
|
||||||
import stdlib/io.sl
|
import ../stdlib/io.sl
|
||||||
|
|
||||||
: main
|
: main
|
||||||
"/tmp/l2_test_write.txt" # push path (addr len)
|
"/tmp/l2_write_file_test.txt" # path
|
||||||
"hello from write_file test\n" # push buf (addr len)
|
"hello from write_file test\n" # buffer
|
||||||
write_file
|
write_file
|
||||||
dup 0 > if
|
dup 0 > if
|
||||||
"wrote bytes: " puts
|
"wrote bytes: " puts
|
||||||
1
tests/io_write_file.test
Normal file
1
tests/io_write_file.test
Normal file
@@ -0,0 +1 @@
|
|||||||
|
python main.py tests/io_write_file.sl -o /tmp/io_write_file > /dev/null && /tmp/io_write_file
|
||||||
10
tests/loop_while.expected
Normal file
10
tests/loop_while.expected
Normal file
@@ -0,0 +1,10 @@
|
|||||||
|
10
|
||||||
|
9
|
||||||
|
8
|
||||||
|
7
|
||||||
|
6
|
||||||
|
5
|
||||||
|
4
|
||||||
|
3
|
||||||
|
2
|
||||||
|
1
|
||||||
@@ -1,5 +1,5 @@
|
|||||||
import stdlib/stdlib.sl
|
import ../stdlib/stdlib.sl
|
||||||
import stdlib/io.sl
|
import ../stdlib/io.sl
|
||||||
|
|
||||||
: main
|
: main
|
||||||
10
|
10
|
||||||
1
tests/loop_while.test
Normal file
1
tests/loop_while.test
Normal file
@@ -0,0 +1 @@
|
|||||||
|
python main.py tests/loop_while.sl -o /tmp/loop_while > /dev/null && /tmp/loop_while
|
||||||
3
tests/loops_and_cmp.expected
Normal file
3
tests/loops_and_cmp.expected
Normal file
@@ -0,0 +1,3 @@
|
|||||||
|
5
|
||||||
|
1
|
||||||
|
0
|
||||||
13
tests/loops_and_cmp.sl
Normal file
13
tests/loops_and_cmp.sl
Normal file
@@ -0,0 +1,13 @@
|
|||||||
|
import ../stdlib/stdlib.sl
|
||||||
|
import ../stdlib/io.sl
|
||||||
|
|
||||||
|
: main
|
||||||
|
0
|
||||||
|
5 for
|
||||||
|
1 +
|
||||||
|
end
|
||||||
|
puti cr
|
||||||
|
5 5 == puti cr
|
||||||
|
5 4 == puti cr
|
||||||
|
0
|
||||||
|
;
|
||||||
1
tests/loops_and_cmp.test
Normal file
1
tests/loops_and_cmp.test
Normal file
@@ -0,0 +1 @@
|
|||||||
|
python main.py tests/loops_and_cmp.sl -o /tmp/loops_and_cmp > /dev/null && /tmp/loops_and_cmp
|
||||||
2
tests/mem.expected
Normal file
2
tests/mem.expected
Normal file
@@ -0,0 +1,2 @@
|
|||||||
|
5
|
||||||
|
6
|
||||||
@@ -1,5 +1,5 @@
|
|||||||
import stdlib/stdlib.sl
|
import ../stdlib/stdlib.sl
|
||||||
import stdlib/io.sl
|
import ../stdlib/io.sl
|
||||||
|
|
||||||
: main
|
: main
|
||||||
mem 5 swap !
|
mem 5 swap !
|
||||||
1
tests/mem.test
Normal file
1
tests/mem.test
Normal file
@@ -0,0 +1 @@
|
|||||||
|
python main.py tests/mem.sl -o /tmp/mem > /dev/null && /tmp/mem
|
||||||
1
tests/override_dup_compile_time.expected
Normal file
1
tests/override_dup_compile_time.expected
Normal file
@@ -0,0 +1 @@
|
|||||||
|
6
|
||||||
28
tests/override_dup_compile_time.sl
Normal file
28
tests/override_dup_compile_time.sl
Normal file
@@ -0,0 +1,28 @@
|
|||||||
|
import ../stdlib/stdlib.sl
|
||||||
|
import ../stdlib/io.sl
|
||||||
|
|
||||||
|
: dup
|
||||||
|
6
|
||||||
|
;
|
||||||
|
compile-only
|
||||||
|
|
||||||
|
: emit-overridden
|
||||||
|
"dup" use-l2-ct
|
||||||
|
42
|
||||||
|
dup
|
||||||
|
int>string
|
||||||
|
nil
|
||||||
|
token-from-lexeme
|
||||||
|
list-new
|
||||||
|
swap
|
||||||
|
list-append
|
||||||
|
inject-tokens
|
||||||
|
;
|
||||||
|
immediate
|
||||||
|
compile-only
|
||||||
|
|
||||||
|
: main
|
||||||
|
emit-overridden
|
||||||
|
puti cr
|
||||||
|
0
|
||||||
|
;
|
||||||
1
tests/override_dup_compile_time.test
Normal file
1
tests/override_dup_compile_time.test
Normal file
@@ -0,0 +1 @@
|
|||||||
|
python main.py tests/override_dup_compile_time.sl -o /tmp/override_dup_compile_time > /dev/null && /tmp/override_dup_compile_time
|
||||||
@@ -1,171 +0,0 @@
|
|||||||
#!/usr/bin/env python3
|
|
||||||
"""Simple end-to-end test runner for L2.
|
|
||||||
|
|
||||||
Each test case provides an L2 program source and an expected stdout. The runner
|
|
||||||
invokes the bootstrap compiler on the fly and executes the produced binary.
|
|
||||||
"""
|
|
||||||
|
|
||||||
from __future__ import annotations
|
|
||||||
|
|
||||||
import subprocess
|
|
||||||
import sys
|
|
||||||
import tempfile
|
|
||||||
from dataclasses import dataclass
|
|
||||||
from pathlib import Path
|
|
||||||
from typing import List
|
|
||||||
|
|
||||||
ROOT = Path(__file__).resolve().parents[1]
|
|
||||||
COMPILER = ROOT / "main.py"
|
|
||||||
PYTHON = Path(sys.executable)
|
|
||||||
|
|
||||||
|
|
||||||
@dataclass
|
|
||||||
class TestCase:
|
|
||||||
name: str
|
|
||||||
source: str
|
|
||||||
expected_stdout: str
|
|
||||||
|
|
||||||
|
|
||||||
CASES: List[TestCase] = [
|
|
||||||
TestCase(
|
|
||||||
name="call_syntax_parens",
|
|
||||||
source=f"""
|
|
||||||
import {ROOT / 'stdlib/stdlib.sl'}
|
|
||||||
import {ROOT / 'stdlib/io.sl'}
|
|
||||||
import {ROOT / 'fn.sl'}
|
|
||||||
|
|
||||||
: main
|
|
||||||
2 40 +
|
|
||||||
puti cr
|
|
||||||
extend-syntax
|
|
||||||
foo(1, 2)
|
|
||||||
puti cr
|
|
||||||
0
|
|
||||||
;
|
|
||||||
|
|
||||||
fn foo(int a, int b){{
|
|
||||||
return a + b;
|
|
||||||
}}
|
|
||||||
""",
|
|
||||||
expected_stdout="42\n3\n",
|
|
||||||
),
|
|
||||||
TestCase(
|
|
||||||
name="loops_and_cmp",
|
|
||||||
source=f"""
|
|
||||||
import {ROOT / 'stdlib/stdlib.sl'}
|
|
||||||
import {ROOT / 'stdlib/io.sl'}
|
|
||||||
|
|
||||||
: main
|
|
||||||
0
|
|
||||||
5 for
|
|
||||||
1 +
|
|
||||||
end
|
|
||||||
puti cr
|
|
||||||
5 5 == puti cr
|
|
||||||
5 4 == puti cr
|
|
||||||
0
|
|
||||||
;
|
|
||||||
""",
|
|
||||||
expected_stdout="5\n1\n0\n",
|
|
||||||
),
|
|
||||||
TestCase(
|
|
||||||
name="override_dup_compile_time",
|
|
||||||
source=f"""
|
|
||||||
import {ROOT / 'stdlib/stdlib.sl'}
|
|
||||||
import {ROOT / 'stdlib/io.sl'}
|
|
||||||
|
|
||||||
: dup
|
|
||||||
6
|
|
||||||
;
|
|
||||||
compile-only
|
|
||||||
|
|
||||||
: emit-overridden
|
|
||||||
"dup" use-l2-ct
|
|
||||||
42
|
|
||||||
dup
|
|
||||||
int>string
|
|
||||||
nil
|
|
||||||
token-from-lexeme
|
|
||||||
list-new
|
|
||||||
swap
|
|
||||||
list-append
|
|
||||||
inject-tokens
|
|
||||||
;
|
|
||||||
immediate
|
|
||||||
compile-only
|
|
||||||
|
|
||||||
: main
|
|
||||||
emit-overridden
|
|
||||||
puti cr
|
|
||||||
0
|
|
||||||
;
|
|
||||||
""",
|
|
||||||
expected_stdout="6\n",
|
|
||||||
),
|
|
||||||
TestCase(
|
|
||||||
name="string_puts",
|
|
||||||
source=f"""
|
|
||||||
import {ROOT / 'stdlib/stdlib.sl'}
|
|
||||||
import {ROOT / 'stdlib/io.sl'}
|
|
||||||
|
|
||||||
: main
|
|
||||||
\"hello world\" puts
|
|
||||||
\"line1\\nline2\" puts
|
|
||||||
\"\" puts
|
|
||||||
0
|
|
||||||
;
|
|
||||||
""",
|
|
||||||
expected_stdout="hello world\nline1\nline2\n\n",
|
|
||||||
),
|
|
||||||
]
|
|
||||||
|
|
||||||
|
|
||||||
def run_case(case: TestCase) -> None:
|
|
||||||
print(f"[run] {case.name}")
|
|
||||||
with tempfile.TemporaryDirectory() as tmp:
|
|
||||||
src_path = Path(tmp) / f"{case.name}.sl"
|
|
||||||
exe_path = Path(tmp) / f"{case.name}.out"
|
|
||||||
src_path.write_text(case.source.strip() + "\n", encoding="utf-8")
|
|
||||||
|
|
||||||
compile_cmd = [str(PYTHON), str(COMPILER), str(src_path), "-o", str(exe_path)]
|
|
||||||
compile_result = subprocess.run(
|
|
||||||
compile_cmd,
|
|
||||||
capture_output=True,
|
|
||||||
text=True,
|
|
||||||
cwd=ROOT,
|
|
||||||
)
|
|
||||||
if compile_result.returncode != 0:
|
|
||||||
sys.stderr.write("[fail] compile error\n")
|
|
||||||
sys.stderr.write(compile_result.stdout)
|
|
||||||
sys.stderr.write(compile_result.stderr)
|
|
||||||
raise SystemExit(compile_result.returncode)
|
|
||||||
|
|
||||||
run_result = subprocess.run(
|
|
||||||
[str(exe_path)],
|
|
||||||
capture_output=True,
|
|
||||||
text=True,
|
|
||||||
cwd=ROOT,
|
|
||||||
)
|
|
||||||
if run_result.returncode != 0:
|
|
||||||
sys.stderr.write("[fail] execution error\n")
|
|
||||||
sys.stderr.write(run_result.stdout)
|
|
||||||
sys.stderr.write(run_result.stderr)
|
|
||||||
raise SystemExit(run_result.returncode)
|
|
||||||
|
|
||||||
if run_result.stdout != case.expected_stdout:
|
|
||||||
sys.stderr.write(f"[fail] output mismatch for {case.name}\n")
|
|
||||||
sys.stderr.write("expected:\n" + case.expected_stdout)
|
|
||||||
sys.stderr.write("got:\n" + run_result.stdout)
|
|
||||||
raise SystemExit(1)
|
|
||||||
|
|
||||||
print(f"[ok] {case.name}")
|
|
||||||
|
|
||||||
|
|
||||||
def main() -> None:
|
|
||||||
for case in CASES:
|
|
||||||
run_case(case)
|
|
||||||
print("[all tests passed]")
|
|
||||||
|
|
||||||
|
|
||||||
if __name__ == "__main__":
|
|
||||||
main()
|
|
||||||
4
tests/string_puts.expected
Normal file
4
tests/string_puts.expected
Normal file
@@ -0,0 +1,4 @@
|
|||||||
|
hello world
|
||||||
|
line1
|
||||||
|
line2
|
||||||
|
|
||||||
9
tests/string_puts.sl
Normal file
9
tests/string_puts.sl
Normal file
@@ -0,0 +1,9 @@
|
|||||||
|
import ../stdlib/stdlib.sl
|
||||||
|
import ../stdlib/io.sl
|
||||||
|
|
||||||
|
: main
|
||||||
|
"hello world" puts
|
||||||
|
"line1\nline2" puts
|
||||||
|
"" puts
|
||||||
|
0
|
||||||
|
;
|
||||||
1
tests/string_puts.test
Normal file
1
tests/string_puts.test
Normal file
@@ -0,0 +1 @@
|
|||||||
|
python main.py tests/string_puts.sl -o /tmp/string_puts > /dev/null && /tmp/string_puts
|
||||||
66
tests/test.py
Normal file
66
tests/test.py
Normal file
@@ -0,0 +1,66 @@
|
|||||||
|
#!/usr/bin/python
|
||||||
|
import sys
|
||||||
|
import os
|
||||||
|
import subprocess
|
||||||
|
|
||||||
|
COLORS = {
|
||||||
|
"red": "\033[91m",
|
||||||
|
"green": "\033[92m",
|
||||||
|
"yellow": "\033[93m",
|
||||||
|
"blue": "\033[94m",
|
||||||
|
"reset": "\033[0m"
|
||||||
|
}
|
||||||
|
|
||||||
|
def print_colored(text, color):
|
||||||
|
print(COLORS.get(color, "") + text + COLORS["reset"], end="")
|
||||||
|
|
||||||
|
def run_tests():
|
||||||
|
test_dir = "tests"
|
||||||
|
any_failed = False
|
||||||
|
|
||||||
|
if not os.path.isdir(test_dir):
|
||||||
|
print("No 'tests' directory found.")
|
||||||
|
return 1
|
||||||
|
|
||||||
|
for file in sorted(os.listdir(test_dir)):
|
||||||
|
if file.endswith(".test"):
|
||||||
|
test_path = os.path.join(test_dir, file)
|
||||||
|
expected_path = test_path.replace(".test", ".expected")
|
||||||
|
|
||||||
|
if not os.path.isfile(expected_path):
|
||||||
|
print(f"Missing expected output file for {file}")
|
||||||
|
any_failed = True
|
||||||
|
continue
|
||||||
|
|
||||||
|
with open(test_path, "r") as test_file:
|
||||||
|
command = test_file.read().strip()
|
||||||
|
|
||||||
|
with open(expected_path, "r") as expected_file:
|
||||||
|
expected_output = expected_file.read().strip()
|
||||||
|
|
||||||
|
try:
|
||||||
|
result = subprocess.run(command, shell=True, text=True, capture_output=True)
|
||||||
|
actual_output = result.stdout.strip()
|
||||||
|
stderr_output = result.stderr.strip()
|
||||||
|
|
||||||
|
if result.returncode == 0 and actual_output == expected_output:
|
||||||
|
print_colored("[OK] ", "green")
|
||||||
|
print(f"{file} passed")
|
||||||
|
else:
|
||||||
|
print_colored("[ERR] ", "red")
|
||||||
|
print(f"{file} failed (exit {result.returncode})")
|
||||||
|
print(f"Expected:\n{expected_output}")
|
||||||
|
print(f"Got:\n{actual_output}")
|
||||||
|
if stderr_output:
|
||||||
|
print(f"Stderr:\n{stderr_output}")
|
||||||
|
any_failed = True
|
||||||
|
|
||||||
|
except Exception as e:
|
||||||
|
print_colored(f"Error running {file}: {e}", "red")
|
||||||
|
any_failed = True
|
||||||
|
|
||||||
|
return 1 if any_failed else 0
|
||||||
|
|
||||||
|
if __name__ == "__main__":
|
||||||
|
sys.exit(run_tests())
|
||||||
|
|
||||||
BIN
write_buf_test
BIN
write_buf_test
Binary file not shown.
Reference in New Issue
Block a user