@@ -73,11 +73,70 @@ def _read(path: str) -> str:
7373
7474
7575def cmd_run (args : argparse .Namespace ) -> int :
76+ # Runtime selection: Rust is the default in v2.0.0; Python is the
77+ # reference fallback via --runtime python.
78+ runtime = getattr (args , "runtime" , None ) or os .environ .get ("CODIFIDE_RUNTIME" , "rust" )
79+
80+ if runtime == "rust" :
81+ return _cmd_run_rust (args )
82+ return _cmd_run_python (args )
83+
84+
85+ def _cmd_run_rust (args : argparse .Namespace ) -> int :
86+ """Delegate execution to the Rust interpreter binary."""
87+ import shutil
88+ import subprocess
89+
90+ # Locate the Rust binary: prefer the release build next to this repo,
91+ # then fall back to PATH.
92+ repo_root = Path (__file__ ).resolve ().parent .parent
93+ rust_bin = repo_root / "target" / "release" / "codifide-run"
94+ if not rust_bin .exists ():
95+ rust_bin_path = shutil .which ("codifide-run" )
96+ if rust_bin_path is None :
97+ # Rust binary not available; fall back to Python silently.
98+ return _cmd_run_python (args )
99+ rust_bin = Path (rust_bin_path )
100+
101+ cmd = [str (rust_bin ), "run" , args .file ]
102+ if args .entry :
103+ cmd += ["--entry" , args .entry ]
104+
105+ result = subprocess .run (cmd , capture_output = True , text = True )
106+ # Forward stderr directly.
107+ if result .stderr :
108+ print (result .stderr .rstrip (), file = sys .stderr )
109+ if result .returncode != 0 :
110+ return result .returncode
111+
112+ # The binary prints io.say output on earlier lines and the JSON result
113+ # on the last line. Print io.say lines as-is; unwrap the JSON result
114+ # for display (matching Python's `print(result)` behavior).
115+ lines = result .stdout .splitlines ()
116+ if not lines :
117+ return 0
118+ # All lines except the last are io.say output — print them directly.
119+ for line in lines [:- 1 ]:
120+ print (line )
121+ # Last line is the JSON result — unwrap for display.
122+ last = lines [- 1 ]
123+ try :
124+ val = json .loads (last )
125+ # Print like Python's print(): strings unquoted, everything else as repr.
126+ if isinstance (val , str ):
127+ print (val )
128+ elif val is None :
129+ pass # None result: print nothing (matches Python behavior)
130+ else :
131+ print (val )
132+ except (json .JSONDecodeError , ValueError ):
133+ print (last )
134+ return 0
135+
136+
137+ def _cmd_run_python (args : argparse .Namespace ) -> int :
138+ """Original Python tree-walking interpreter."""
76139 try :
77- # Parse first without the store; if the source uses `from`
78- # imports, the parser will return an error that names the
79- # missing store. We open the store then and retry, so modules
80- # that do not need a store do not pay for opening one.
81140 src = _read (args .file )
82141 store : Optional [SymbolStore ] = None
83142 try :
@@ -86,12 +145,10 @@ def cmd_run(args: argparse.Namespace) -> int:
86145 store = SymbolStore (_store_root (args ))
87146 module = parse (src , store = store )
88147 if store is None and module .imports :
89- # Plain `import` at runtime still needs a store.
90148 store = SymbolStore (_store_root (args ))
91149 entry = args .entry or _default_entry (module )
92150 result = run (module , entry , store = store )
93151 if result is not None :
94- # Print the unwrapped result for scripting convenience.
95152 print (result )
96153 return 0
97154 except CodifideError as e :
@@ -523,6 +580,13 @@ def main(argv=None) -> int:
523580 p_run = sub .add_parser ("run" , help = "execute a Codifide program" )
524581 p_run .add_argument ("file" )
525582 p_run .add_argument ("--entry" , help = "entry definition (default: main or sole definition)" )
583+ p_run .add_argument (
584+ "--runtime" ,
585+ choices = ["rust" , "python" ],
586+ default = None ,
587+ help = "interpreter runtime: rust (default) or python (reference). "
588+ "Override with CODIFIDE_RUNTIME env var." ,
589+ )
526590 p_run .add_argument (
527591 "--store" ,
528592 help = "store root for import resolution (default: $CODIFIDE_STORE or ~/.codifide/store)" ,
0 commit comments