@@ -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
3236class 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 )
592637class 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 )
0 commit comments