44import math
55import os
66import sys
7+ import platform
78from dataclasses import dataclass , field
89from typing import Any , Callable , Dict , List , Optional , Union
910
@@ -282,6 +283,8 @@ def __init__(self) -> None:
282283 self ._register_variadic ("ANY" , 1 , self ._any )
283284 self ._register_variadic ("ALL" , 1 , self ._all )
284285 self ._register_variadic ("LEN" , 0 , self ._len )
286+ self ._register_custom ("SLEN" , 1 , 1 , self ._slen )
287+ self ._register_custom ("ILEN" , 1 , 1 , self ._ilen )
285288 self ._register_variadic ("JOIN" , 1 , self ._join )
286289 self ._register_int_only ("LOG" , 1 , self ._safe_log )
287290 self ._register_int_only ("CLOG" , 1 , self ._safe_clog )
@@ -290,6 +293,7 @@ def __init__(self) -> None:
290293 self ._register_custom ("UPPER" , 1 , 1 , self ._upper )
291294 self ._register_custom ("LOWER" , 1 , 1 , self ._lower )
292295 self ._register_custom ("MAIN" , 0 , 0 , self ._main )
296+ self ._register_custom ("OS" , 0 , 0 , self ._os )
293297 self ._register_custom ("IMPORT" , 1 , 1 , self ._import )
294298 self ._register_custom ("INPUT" , 0 , 0 , self ._input )
295299 self ._register_custom ("PRINT" , 0 , None , self ._print )
@@ -301,7 +305,7 @@ def __init__(self) -> None:
301305 self ._register_custom ("READFILE" , 1 , 1 , self ._readfile )
302306 self ._register_custom ("WRITEFILE" , 2 , 2 , self ._writefile )
303307 self ._register_custom ("EXISTFILE" , 1 , 1 , self ._existfile )
304- self ._register_custom ("RUN " , 1 , 1 , self ._run )
308+ self ._register_custom ("CL " , 1 , 1 , self ._cl )
305309 self ._register_custom ("EXIT" , 0 , 1 , self ._exit )
306310
307311 def _register_int_only (self , name : str , arity : int , func : Callable [..., int ]) -> None :
@@ -462,6 +466,30 @@ def _all(self, values: List[Value], _: SourceLocation) -> Value:
462466 def _len (self , values : List [Value ], _ : SourceLocation ) -> Value :
463467 return Value (TYPE_INT , len (values ))
464468
469+ def _slen (
470+ self ,
471+ _ : "Interpreter" ,
472+ args : List [Value ],
473+ __ : List [Expression ],
474+ ___ : Environment ,
475+ location : SourceLocation ,
476+ ) -> Value :
477+ text = self ._expect_str (args [0 ], "SLEN" , location )
478+ return Value (TYPE_INT , len (text ))
479+
480+ def _ilen (
481+ self ,
482+ _ : "Interpreter" ,
483+ args : List [Value ],
484+ __ : List [Expression ],
485+ ___ : Environment ,
486+ location : SourceLocation ,
487+ ) -> Value :
488+ number = self ._expect_int (args [0 ], "ILEN" , location )
489+ magnitude = abs (number )
490+ length = 1 if magnitude == 0 else magnitude .bit_length ()
491+ return Value (TYPE_INT , length )
492+
465493 def _join (self , values : List [Value ], location : SourceLocation ) -> Value :
466494 if not values :
467495 raise ASMRuntimeError ("JOIN requires at least one argument" , rewrite_rule = "JOIN" )
@@ -598,6 +626,39 @@ def _main(
598626 return Value (TYPE_INT , 1 if location .file == "<string>" else 0 )
599627 return Value (TYPE_INT , 1 if os .path .abspath (location .file ) == root else 0 )
600628
629+ def _os (
630+ self ,
631+ interpreter : "Interpreter" ,
632+ args : List [Value ],
633+ __ : List [Expression ],
634+ ___ : Environment ,
635+ __loc : SourceLocation ,
636+ ) -> Value :
637+ # Return a short lowercase host OS family string as STR.
638+ plat = sys .platform .lower ()
639+ if plat .startswith ("win" ) or plat .startswith ("cygwin" ):
640+ fam = "win"
641+ elif plat .startswith ("linux" ):
642+ fam = "linux"
643+ elif plat .startswith ("darwin" ):
644+ fam = "macos"
645+ elif plat .startswith ("aix" ) or plat .startswith ("freebsd" ) or plat .startswith ("openbsd" ):
646+ fam = "unix"
647+ else :
648+ # Fallback to platform.system() for additional hints
649+ p = platform .system ().lower ()
650+ if "windows" in p :
651+ fam = "win"
652+ elif "linux" in p :
653+ fam = "linux"
654+ elif "darwin" in p or "mac" in p :
655+ fam = "macos"
656+ elif p :
657+ fam = p
658+ else :
659+ fam = "unix"
660+ return Value (TYPE_STR , fam )
661+
601662 def _import (
602663 self ,
603664 interpreter : "Interpreter" ,
@@ -696,7 +757,7 @@ def _print(
696757 rendered .append (arg .value ) # type: ignore[arg-type]
697758 else :
698759 rendered .append (str (arg .value ))
699- text = " " .join (rendered )
760+ text = "" .join (rendered )
700761 interpreter .output_sink (text )
701762 interpreter .io_log .append ({"event" : "PRINT" , "values" : [arg .value for arg in args ]})
702763 return Value (TYPE_INT , 0 )
@@ -831,7 +892,7 @@ def _existfile(
831892 path = self ._expect_str (args [0 ], "EXISTFILE" , location )
832893 return Value (TYPE_INT , 1 if os .path .exists (path ) else 0 )
833894
834- def _run (
895+ def _cl (
835896 self ,
836897 interpreter : "Interpreter" ,
837898 args : List [Value ],
@@ -840,15 +901,32 @@ def _run(
840901 location : SourceLocation ,
841902 ) -> Value :
842903 # Execute a shell command and return its exit code as INT.
843- cmd = self ._expect_str (args [0 ], "RUN" , location )
904+ # Capture stdout/stderr so the REPL can display results without
905+ # spawning a visible console window on Windows.
906+ cmd = self ._expect_str (args [0 ], "CL" , location )
844907 try :
845- # Use the system shell to run the provided command string.
846- completed = subprocess .run (cmd , shell = True )
908+ # Use text mode for captured output.
909+ run_kwargs = {"shell" : True , "stdout" : subprocess .PIPE , "stderr" : subprocess .PIPE , "text" : True }
910+ # On Windows, avoid creating a visible console window for subprocesses.
911+ if platform .system ().lower ().startswith ("win" ):
912+ run_kwargs ["creationflags" ] = subprocess .CREATE_NO_WINDOW
913+ completed = subprocess .run (cmd , ** run_kwargs )
847914 code = completed .returncode
915+ out = completed .stdout or ""
916+ err = completed .stderr or ""
848917 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 })
918+ raise ASMRuntimeError (f"CL failed: { exc } " , location = location , rewrite_rule = "CL" )
919+
920+ # Record the CL event for deterministic logging/replay, including captured output.
921+ interpreter .io_log .append ({"event" : "CL" , "cmd" : cmd , "code" : code , "stdout" : out , "stderr" : err })
922+
923+ # Forward captured output to the interpreter's output sink so REPL users see it.
924+ if out :
925+ interpreter .output_sink (out )
926+ if err :
927+ # Send stderr to the same sink; callers can choose how to display it.
928+ interpreter .output_sink (err )
929+
852930 # Normalize return to INT
853931 return Value (TYPE_INT , int (code ))
854932
0 commit comments