|
1 | 1 | from __future__ import annotations |
2 | 2 | import json |
| 3 | +import subprocess |
3 | 4 | import math |
4 | 5 | import os |
5 | 6 | import sys |
@@ -286,13 +287,21 @@ def __init__(self) -> None: |
286 | 287 | self._register_int_only("CLOG", 1, self._safe_clog) |
287 | 288 | self._register_custom("INT", 1, 1, self._int_op) |
288 | 289 | self._register_custom("STR", 1, 1, self._str_op) |
| 290 | + self._register_custom("UPPER", 1, 1, self._upper) |
| 291 | + self._register_custom("LOWER", 1, 1, self._lower) |
289 | 292 | self._register_custom("MAIN", 0, 0, self._main) |
290 | 293 | self._register_custom("IMPORT", 1, 1, self._import) |
291 | 294 | self._register_custom("INPUT", 0, 0, self._input) |
292 | 295 | self._register_custom("PRINT", 0, None, self._print) |
293 | 296 | self._register_custom("ASSERT", 1, 1, self._assert) |
294 | 297 | self._register_custom("DEL", 1, 1, self._delete) |
295 | 298 | self._register_custom("EXIST", 1, 1, self._exist) |
| 299 | + self._register_custom("ISINT", 1, 1, self._isint) |
| 300 | + self._register_custom("ISSTR", 1, 1, self._isstr) |
| 301 | + self._register_custom("READFILE", 1, 1, self._readfile) |
| 302 | + self._register_custom("WRITEFILE", 2, 2, self._writefile) |
| 303 | + self._register_custom("EXISTFILE", 1, 1, self._existfile) |
| 304 | + self._register_custom("RUN", 1, 1, self._run) |
296 | 305 | self._register_custom("EXIT", 0, 1, self._exit) |
297 | 306 |
|
298 | 307 | def _register_int_only(self, name: str, arity: int, func: Callable[..., int]) -> None: |
@@ -540,6 +549,30 @@ def _str_op(self, _: "Interpreter", args: List[Value], __: List[Expression], ___ |
540 | 549 | return Value(TYPE_STR, "-" + format(-number, "b")) |
541 | 550 | return Value(TYPE_STR, format(number, "b")) |
542 | 551 |
|
| 552 | + def _upper( |
| 553 | + self, |
| 554 | + interpreter: "Interpreter", |
| 555 | + args: List[Value], |
| 556 | + __: List[Expression], |
| 557 | + ___: Environment, |
| 558 | + location: SourceLocation, |
| 559 | + ) -> Value: |
| 560 | + s = self._expect_str(args[0], "UPPER", location) |
| 561 | + # Convert ASCII letters to upper-case; other bytes are unchanged |
| 562 | + return Value(TYPE_STR, s.upper()) |
| 563 | + |
| 564 | + def _lower( |
| 565 | + self, |
| 566 | + interpreter: "Interpreter", |
| 567 | + args: List[Value], |
| 568 | + __: List[Expression], |
| 569 | + ___: Environment, |
| 570 | + location: SourceLocation, |
| 571 | + ) -> Value: |
| 572 | + s = self._expect_str(args[0], "LOWER", location) |
| 573 | + # Convert ASCII letters to lower-case; other bytes are unchanged |
| 574 | + return Value(TYPE_STR, s.lower()) |
| 575 | + |
543 | 576 | def _safe_log(self, value: int) -> int: |
544 | 577 | if value <= 0: |
545 | 578 | raise ASMRuntimeError("LOG argument must be > 0", rewrite_rule="LOG") |
@@ -712,6 +745,113 @@ def _exist( |
712 | 745 | name = arg_nodes[0].name |
713 | 746 | return Value(TYPE_INT, 1 if env.has(name) else 0) |
714 | 747 |
|
| 748 | + def _isint( |
| 749 | + self, |
| 750 | + interpreter: "Interpreter", |
| 751 | + args: List[Value], |
| 752 | + arg_nodes: List[Expression], |
| 753 | + env: Environment, |
| 754 | + location: SourceLocation, |
| 755 | + ) -> Value: |
| 756 | + # Expect an identifier node so we examine the symbol's binding and type |
| 757 | + if not arg_nodes or not isinstance(arg_nodes[0], Identifier): |
| 758 | + raise ASMRuntimeError("ISINT requires an identifier argument", location=location, rewrite_rule="ISINT") |
| 759 | + name = arg_nodes[0].name |
| 760 | + if not env.has(name): |
| 761 | + return Value(TYPE_INT, 0) |
| 762 | + try: |
| 763 | + val = env.get(name) |
| 764 | + except ASMRuntimeError as err: |
| 765 | + err.location = location |
| 766 | + raise |
| 767 | + return Value(TYPE_INT, 1 if val.type == TYPE_INT else 0) |
| 768 | + |
| 769 | + def _isstr( |
| 770 | + self, |
| 771 | + interpreter: "Interpreter", |
| 772 | + args: List[Value], |
| 773 | + arg_nodes: List[Expression], |
| 774 | + env: Environment, |
| 775 | + location: SourceLocation, |
| 776 | + ) -> Value: |
| 777 | + # Expect an identifier node so we examine the symbol's binding and type |
| 778 | + if not arg_nodes or not isinstance(arg_nodes[0], Identifier): |
| 779 | + raise ASMRuntimeError("ISSTR requires an identifier argument", location=location, rewrite_rule="ISSTR") |
| 780 | + name = arg_nodes[0].name |
| 781 | + if not env.has(name): |
| 782 | + return Value(TYPE_INT, 0) |
| 783 | + try: |
| 784 | + val = env.get(name) |
| 785 | + except ASMRuntimeError as err: |
| 786 | + err.location = location |
| 787 | + raise |
| 788 | + return Value(TYPE_INT, 1 if val.type == TYPE_STR else 0) |
| 789 | + |
| 790 | + def _readfile( |
| 791 | + self, |
| 792 | + interpreter: "Interpreter", |
| 793 | + args: List[Value], |
| 794 | + __: List[Expression], |
| 795 | + ___: Environment, |
| 796 | + location: SourceLocation, |
| 797 | + ) -> Value: |
| 798 | + path = self._expect_str(args[0], "READFILE", location) |
| 799 | + try: |
| 800 | + with open(path, "r", encoding="utf-8") as handle: |
| 801 | + data = handle.read() |
| 802 | + except OSError as exc: |
| 803 | + raise ASMRuntimeError(f"Failed to read '{path}': {exc}", location=location, rewrite_rule="READFILE") |
| 804 | + return Value(TYPE_STR, data) |
| 805 | + |
| 806 | + def _writefile( |
| 807 | + self, |
| 808 | + interpreter: "Interpreter", |
| 809 | + args: List[Value], |
| 810 | + __: List[Expression], |
| 811 | + ___: Environment, |
| 812 | + location: SourceLocation, |
| 813 | + ) -> Value: |
| 814 | + blob = self._expect_str(args[0], "WRITEFILE", location) |
| 815 | + path = self._expect_str(args[1], "WRITEFILE", location) |
| 816 | + try: |
| 817 | + with open(path, "w", encoding="utf-8") as handle: |
| 818 | + handle.write(blob) |
| 819 | + except OSError: |
| 820 | + return Value(TYPE_INT, 0) |
| 821 | + return Value(TYPE_INT, 1) |
| 822 | + |
| 823 | + def _existfile( |
| 824 | + self, |
| 825 | + interpreter: "Interpreter", |
| 826 | + args: List[Value], |
| 827 | + __: List[Expression], |
| 828 | + ___: Environment, |
| 829 | + location: SourceLocation, |
| 830 | + ) -> Value: |
| 831 | + path = self._expect_str(args[0], "EXISTFILE", location) |
| 832 | + return Value(TYPE_INT, 1 if os.path.exists(path) else 0) |
| 833 | + |
| 834 | + def _run( |
| 835 | + self, |
| 836 | + interpreter: "Interpreter", |
| 837 | + args: List[Value], |
| 838 | + __: List[Expression], |
| 839 | + ___: Environment, |
| 840 | + location: SourceLocation, |
| 841 | + ) -> Value: |
| 842 | + # Execute a shell command and return its exit code as INT. |
| 843 | + cmd = self._expect_str(args[0], "RUN", location) |
| 844 | + try: |
| 845 | + # Use the system shell to run the provided command string. |
| 846 | + completed = subprocess.run(cmd, shell=True) |
| 847 | + code = completed.returncode |
| 848 | + except OSError as exc: |
| 849 | + raise ASMRuntimeError(f"RUN failed: {exc}", location=location, rewrite_rule="RUN") |
| 850 | + # Record the run event for deterministic logging/replay |
| 851 | + interpreter.io_log.append({"event": "RUN", "cmd": cmd, "code": code}) |
| 852 | + # Normalize return to INT |
| 853 | + return Value(TYPE_INT, int(code)) |
| 854 | + |
715 | 855 | def _exit( |
716 | 856 | self, |
717 | 857 | interpreter: "Interpreter", |
|
0 commit comments