1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124
|
"""
TINY interpreter scaffold built using:
- parser defined in tiny_parser.py
- executable statement AST classes defined in tiny_ast.py
- execution engine defined in tiny_engine.py
This module currently provides:
- main(): CLI entry point that reads a .tiny source file, parses it using the
TINY grammar, and prepares for conversion to TinyNode-based AST.
Usage:
python -m examples.tiny.tiny_run path/to/program.tiny [--dump]
"""
from __future__ import annotations
import argparse
import sys
import pyparsing as pp
from .tiny_parser import parse_tiny
from .tiny_ast import TinyNode
from .tiny_engine import TinyEngine
def explain_parse_error(src: str, err: pp.ParseBaseException) -> str:
"""Return a helpful, context-rich parse error string."""
source_lines = src.splitlines()
error_lineno = err.lineno
lineno_len = len(str(error_lineno + 1))
# Guard against last-line errors
if err.lineno - 1 >= len(source_lines):
source_lines.append("")
*prelude, error_line, postlude = source_lines[max(error_lineno - 3, 0): error_lineno + 1]
fragment = "\n".join(
[
*(f"{prelineno:>{lineno_len}}: {line}" for prelineno, line in
enumerate(prelude, start=error_lineno - len(prelude))),
f"{error_lineno:>{lineno_len}}: >{error_line}",
f"{error_lineno + 1:>{lineno_len}}: {postlude}",
]
)
return fragment + "\n\n" + err.explain(depth=0)
def main(argv: list[str] | None = None) -> int:
parser = argparse.ArgumentParser(description="Run a TINY program using the TINY interpreter scaffold.")
parser.add_argument("source", help="Path to the .tiny source file")
parser.add_argument("--dump", action="store_true", help="Dump parsed structure (debug)")
args = parser.parse_args(argv)
try:
with open(args.source, "r", encoding="utf-8") as f:
source_text = f.read()
except OSError as exc:
print(f"Error reading {args.source}: {exc}", file=sys.stderr)
return 2
try:
parsed = parse_tiny(source_text)
except pp.ParseBaseException as exc:
# Print helpful location info
print(explain_parse_error(source_text, exc))
return 3
if args.dump:
# Pretty-print the primary structure for inspection
print(parsed.dump())
exit()
# Instantiate the engine that will hold globals, functions, and frames
engine = TinyEngine()
# initialize engine with parsed globals
initialize_engine(engine, parsed.program)
# Execute scripts "main" function
try:
main_node = engine.get_function("main")
main_node.execute(engine)
except Exception as exc:
print(f"{type(exc).__name__}: {exc}")
exit()
return 0
def initialize_engine(engine: TinyEngine, program: pp.ParseResults) -> None:
# Register all top-level function definitions: build function nodes and signatures
if "functions" in program:
for fdef in program.functions:
# Build a function node with a prebuilt body
fn_node_class = TinyNode.from_statement_type(fdef.type)
fn_node = fn_node_class.from_parsed(fdef)
# Register function node for runtime use
engine.register_function(fn_node.name, fn_node)
# Register any top-level globals if they exist (grammar may not provide these)
if "globals" in program:
for g in program.globals:
try:
dtype = str(g.datatype)
# Handle one or more variable declarations in a decl stmt
decls = g.decls if hasattr(g, "decls") else []
for d in decls:
name = d.name
init_val = d.get("init") if isinstance(d, pp.ParseResults) else None
engine.declare_global_var(name, dtype, init_val)
except Exception:
# Best-effort: skip malformed/unsupported global forms
continue
# Build AST node for main() and register it as a function
main_group = program.main
main_node_class = TinyNode.from_statement_type(main_group["type"])
main_node = main_node_class.from_parsed(main_group)
engine.register_function("main", main_node)
if __name__ == "__main__":
main()
|