File: tiny_run.py

package info (click to toggle)
pyparsing 3.3.2-1
  • links: PTS, VCS
  • area: main
  • in suites: experimental
  • size: 12,200 kB
  • sloc: python: 30,867; ansic: 422; sh: 112; makefile: 24
file content (124 lines) | stat: -rw-r--r-- 4,307 bytes parent folder | download
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()