Skip to content

Commit a1b4699

Browse files
Add EXIT, SLICE, enforce ASCII only namespace
1 parent aada4d1 commit a1b4699

File tree

3 files changed

+70
-5
lines changed

3 files changed

+70
-5
lines changed

interpreter.exe

963 Bytes
Binary file not shown.

interpreter.py

Lines changed: 52 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -28,6 +28,10 @@ class ReturnSignal(Exception):
2828
def __init__(self, value: int) -> None:
2929
super().__init__(value)
3030
self.value = value
31+
class ExitSignal(Exception):
32+
def __init__(self, code: int = 0) -> None:
33+
super().__init__(code)
34+
self.code = code
3135
@dataclass
3236
class SourceLocation:
3337
file: str
@@ -110,9 +114,18 @@ def _consume_identifier(self) -> Token:
110114
token_type:str = value if value in KEYWORDS else "IDENT"
111115
return(Token(token_type,value,line,col))
112116
def _is_identifier_start(self, ch: str) -> bool:
113-
return(ch.isalpha() or ch == "_")
117+
# Only allow ASCII letters and underscore as the start of an identifier.
118+
# This enforces the spec requirement that identifiers must be ASCII-only.
119+
return (ch == "_") or ("A" <= ch <= "Z") or ("a" <= ch <= "z")
114120
def _is_identifier_part(self, ch: str) -> bool:
115-
return(ch.isalpha() or ch == "_" or (ch.isdigit() and ch not in "01"))
121+
# Allow ASCII letters, underscore, and digits 2-9 (digits '0' and '1' are disallowed
122+
# per the language spec because they would collide with binary-literal syntax).
123+
return (
124+
(ch == "_")
125+
or ("A" <= ch <= "Z")
126+
or ("a" <= ch <= "z")
127+
or ("2" <= ch <= "9")
128+
)
116129
@property
117130
def _eof(self) -> bool:
118131
return(self.index >= len(self.text))
@@ -444,6 +457,11 @@ def __init__(self) -> None:
444457
self._register_fixed("BOR", 2, lambda a, b: a | b)
445458
self._register_fixed("BXOR", 2, lambda a, b: a ^ b)
446459
self._register_fixed("BNOT", 1, lambda a: ~a)
460+
# Return the inclusive bit-slice [hi:lo] of `a` as an unsigned integer.
461+
# Bits are numbered starting at 0 for the least-significant bit.
462+
# Implementation note: uses masking so negative values yield their
463+
# two's-complement low bits (consistent with other bitwise ops).
464+
self._register_fixed("SLICE", 3, self._slice)
447465
self._register_fixed("AND", 2, lambda a, b: 1 if (_as_bool(a) and _as_bool(b)) else 0)
448466
self._register_fixed("OR", 2, lambda a, b: 1 if (_as_bool(a) or _as_bool(b)) else 0)
449467
self._register_fixed("XOR", 2, lambda a, b: 1 if (_as_bool(a) ^ _as_bool(b)) else 0)
@@ -466,6 +484,7 @@ def __init__(self) -> None:
466484
self._register_custom("PRINT", 0, None, self._print)
467485
self._register_custom("ASSERT", 1, 1, self._assert)
468486
self._register_custom("DEL", 1, 1, self._delete)
487+
self._register_custom("EXIT", 0, 1, self._exit)
469488
def _register_fixed(self, name: str, arity: int, func: Callable[..., int]) -> None:
470489
def impl(interpreter: "Interpreter", args: List[int], _: List[Expression], __: Environment, ___: SourceLocation) -> int:
471490
return func(*args)
@@ -503,10 +522,13 @@ def _safe_div(self, a: int, b: int) -> int:
503522
def _safe_ceil(self, a: int, b: int) -> int:
504523
if b == 0:
505524
raise ASMRuntimeError("Division by zero", rewrite_rule="CEIL")
506-
q, r = divmod(a, b)
507-
if r == 0:
525+
# Compute ceil(a / b) using integer operations only.
526+
# Use Python's floor division to get the floor quotient `q`.
527+
q = a // b
528+
# If remainder is zero then the division is exact; otherwise ceil is q+1.
529+
if a % b == 0:
508530
return q
509-
return q + 1 if (a > 0) == (b > 0) else q
531+
return q + 1
510532
def _safe_mod(self, a: int, b: int) -> int:
511533
if b == 0:
512534
raise ASMRuntimeError("Division by zero", rewrite_rule="MOD")
@@ -534,6 +556,18 @@ def _safe_clog(self, value: int) -> int:
534556
if value & (value - 1) == 0:
535557
return value.bit_length() - 1
536558
return value.bit_length()
559+
560+
def _slice(self, a: int, hi: int, lo: int) -> int:
561+
if hi < lo:
562+
raise ASMRuntimeError("SLICE: hi must be >= lo", rewrite_rule="SLICE")
563+
if lo < 0 or hi < 0:
564+
raise ASMRuntimeError("SLICE: bit indices must be non-negative", rewrite_rule="SLICE")
565+
width = hi - lo + 1
566+
if width <= 0:
567+
return 0
568+
# mask off low (hi+1) bits, then shift down by lo
569+
mask = (1 << (hi + 1)) - 1
570+
return (a & mask) >> lo
537571
def _input(
538572
self,
539573
interpreter: "Interpreter",
@@ -589,6 +623,17 @@ def _delete(
589623
err.location = location
590624
raise
591625
return 0
626+
def _exit(
627+
self,
628+
interpreter: "Interpreter",
629+
args: List[int],
630+
__: List[Expression],
631+
___: Environment,
632+
____: SourceLocation,
633+
) -> int:
634+
code = args[0] if args else 0
635+
interpreter.io_log.append({"event": "EXIT", "code": code})
636+
raise ExitSignal(code)
592637
class Interpreter:
593638
def __init__(
594639
self,
@@ -851,6 +896,8 @@ def run_cli(argv: Optional[List[str]] = None) -> int:
851896
except ASMParseError as error:
852897
print(f"ParseError: {error}", file=sys.stderr)
853898
return 1
899+
except ExitSignal as sig:
900+
return sig.code
854901
except ASMRuntimeError as error:
855902
formatter = TracebackFormatter(interpreter)
856903
print(formatter.format_text(error, verbose=args.verbose), file=sys.stderr)

spec.txt

Lines changed: 18 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -112,6 +112,14 @@ and 'BNOT(a)' the bitwise complement. The interpretation of sign and bit
112112
width for these operations is implementation-defined but must be consistent
113113
across all bitwise operators.
114114

115+
Bitfield and slicing helpers: 'SLICE(a, hi, lo)' extracts the inclusive
116+
bit-range [hi:lo] from the binary representation of 'a' and returns it as an
117+
unsigned integer. Bits are numbered starting at 0 for the least-significant
118+
bit. If 'hi < lo' or either index is negative, the operation raises a
119+
runtime error. For negative values of 'a', implementations use the low-order
120+
bits of its two's-complement representation (consistent with other bitwise
121+
operations).
122+
115123
Arithmetic operators include 'ADD(a, b)' for integer addition, 'SUB(a, b)'
116124
for subtraction ('a - b'), and 'MUL(a, b)' for multiplication. 'DIV(a, b)'
117125
performs integer division with floor rounding, returning '⌊a / b⌋'; division
@@ -177,6 +185,14 @@ log for deterministic replay.
177185
non-zero, execution proceeds normally; if 'a' is '0', the program crashes
178186
with an assertion failure.
179187

188+
Program termination is exposed via 'EXIT'. 'EXIT()' or 'EXIT(code)' requests
189+
immediate termination of the interpreter. If an integer 'code' is supplied,
190+
it is used as the interpreter's process exit code; otherwise '0' is used.
191+
Execution stops immediately when 'EXIT' is executed (no further statements
192+
run), and an entry is recorded in the state log to make deterministic replay
193+
possible. Using 'EXIT' inside a function terminates the entire program (not
194+
just the function).
195+
180196
Memory-management and function-return behavior are also exposed via
181197
operators. 'DEL(x)' deletes the variable 'x' from the current environment,
182198
freeing its memory; any subsequent reference to 'x' is an error unless 'x'
@@ -546,6 +562,7 @@ Function / Operator Signatures (expression position)
546562
- 'PRINT(a1, a2, ..., aN)' : prints arguments (side-effect), return value discarded
547563
- 'ASSERT(a)' : crashes if 'a' is 0
548564
- 'DEL(x)' : delete variable 'x' from environment
565+
- 'EXIT()' or 'EXIT(code)' : terminate program immediately with optional exit code (default 0)
549566

550567
Arithmetic
551568
- 'ADD(a, b)' : a + b
@@ -565,6 +582,7 @@ Bitwise / Boolean
565582
- 'BOR(a, b)' : bitwise OR
566583
- 'BXOR(a, b)' : bitwise XOR
567584
- 'BNOT(a)' : bitwise complement
585+
- 'SLICE(a, hi, lo)' : inclusive bit-slice of `a` from bit index `hi` down to `lo` (LSB index 0)
568586
- 'AND(a, b)' : Boolean AND -> 0 or 1
569587
- 'OR(a, b)' : Boolean OR -> 0 or 1
570588
- 'XOR(a, b)' : Boolean XOR -> 0 or 1

0 commit comments

Comments
 (0)