55import os
66import sys
77import platform
8+ import tempfile
89import codecs
10+ import numpy as np
911from dataclasses import dataclass , field
1012from typing import Any , Callable , Dict , List , Optional , Tuple , Union
13+ from numpy .typing import NDArray
1114
1215from lexer import ASMError , ASMParseError , Lexer
1316from parser import (
4447TYPE_STR = "STR"
4548TYPE_TNS = "TNS"
4649
50+ # On Windows, command lines over a certain length cause CreateProcess errors
51+ WINDOWS_COMMAND_LENGTH_LIMIT = 8000
4752
4853@dataclass (frozen = True )
4954class Tensor :
5055 shape : List [int ]
51- data : List [ "Value" ]
56+ data : NDArray [ Any ]
5257
5358
5459@dataclass
@@ -445,14 +450,38 @@ def _expect_tns(self, value: Value, rule: str, location: SourceLocation) -> Tens
445450 assert isinstance (value .value , Tensor )
446451 return value .value
447452
453+ def _extract_powershell_body (self , cmd : str ) -> Optional [str ]:
454+ """Best-effort extraction of the script text passed to -Command.
455+
456+ This keeps the heavy script contents out of the CreateProcess command
457+ line by allowing us to emit it into a temporary .ps1 file instead.
458+ """
459+ lower = cmd .lower ()
460+ idx = lower .find ("-command" )
461+ if idx == - 1 :
462+ return None
463+ tail = cmd [idx + len ("-command" ) :].lstrip ()
464+ if not tail :
465+ return None
466+ if tail [0 ] in ('"' , "'" ):
467+ quote = tail [0 ]
468+ tail = tail [1 :]
469+ end = tail .find (quote )
470+ if end == - 1 :
471+ return None
472+ return tail [:end ]
473+ # Fallback: take the next token
474+ parts = tail .split (None , 1 )
475+ return parts [0 ] if parts else None
476+
448477 def _as_bool_value (self , value : Value ) -> int :
449478 if value .type == TYPE_INT :
450479 return 0 if value .value == 0 else 1
451480 if value .type == TYPE_STR :
452481 return 0 if value .value == "" else 1
453482 if value .type == TYPE_TNS :
454483 assert isinstance (value .value , Tensor )
455- return 1 if any (self ._as_bool_value (item ) for item in value .value .data ) else 0
484+ return 1 if any (self ._as_bool_value (item ) for item in value .value .data . flat ) else 0
456485 return 0
457486
458487 def _condition_from_value (self , value : Value ) -> int :
@@ -1237,7 +1266,7 @@ def _shape_from_tensor(self, tensor: Tensor, rule: str, location: SourceLocation
12371266 if len (tensor .shape ) != 1 :
12381267 raise ASMRuntimeError (f"{ rule } shape must be a 1D tensor" , location = location , rewrite_rule = rule )
12391268 dims : List [int ] = []
1240- for entry in tensor .data :
1269+ for entry in tensor .data . flat :
12411270 dim = self ._expect_int (entry , rule , location )
12421271 if dim <= 0 :
12431272 raise ASMRuntimeError ("Tensor dimensions must be positive" , location = location , rewrite_rule = rule )
@@ -1249,22 +1278,24 @@ def _shape_from_tensor(self, tensor: Tensor, rule: str, location: SourceLocation
12491278 def _map_tensor_int_binary (self , x : Tensor , y : Tensor , rule : str , location : SourceLocation , op : Callable [[int , int ], int ]) -> Tensor :
12501279 if x .shape != y .shape :
12511280 raise ASMRuntimeError (f"{ rule } requires tensors with identical shapes" , location = location , rewrite_rule = rule )
1252- data : List [Value ] = []
1253- for a , b in zip (x .data , y .data ):
1254- ai = self ._expect_int (a , rule , location )
1255- bi = self ._expect_int (b , rule , location )
1256- data .append (Value (TYPE_INT , op (ai , bi )))
1281+ data = np .array (
1282+ [
1283+ Value (TYPE_INT , op (self ._expect_int (a , rule , location ), self ._expect_int (b , rule , location )))
1284+ for a , b in zip (x .data .flat , y .data .flat )
1285+ ],
1286+ dtype = object ,
1287+ )
12571288 return Tensor (shape = list (x .shape ), data = data )
12581289
12591290 def _map_tensor_int_scalar (self , tensor : Tensor , scalar : int , rule : str , location : SourceLocation , op : Callable [[int , int ], int ]) -> Tensor :
1260- data : List [ Value ] = []
1261- for entry in tensor .data :
1262- val = self . _expect_int ( entry , rule , location )
1263- data . append ( Value ( TYPE_INT , op ( val , scalar )) )
1291+ data = np . array (
1292+ [ Value ( TYPE_INT , op ( self . _expect_int ( entry , rule , location ), scalar )) for entry in tensor .data . flat ],
1293+ dtype = object ,
1294+ )
12641295 return Tensor (shape = list (tensor .shape ), data = data )
12651296
12661297 def _ensure_tensor_ints (self , tensor : Tensor , rule : str , location : SourceLocation ) -> None :
1267- for entry in tensor .data :
1298+ for entry in tensor .data . flat :
12681299 self ._expect_int (entry , rule , location )
12691300
12701301 # Tensor built-ins
@@ -1277,7 +1308,7 @@ def _shape(
12771308 location : SourceLocation ,
12781309 ) -> Value :
12791310 tensor = self ._expect_tns (args [0 ], "SHAPE" , location )
1280- shape_data = [Value (TYPE_INT , dim ) for dim in tensor .shape ]
1311+ shape_data = np . array ( [Value (TYPE_INT , dim ) for dim in tensor .shape ], dtype = object )
12811312 return Value (TYPE_TNS , Tensor (shape = [len (shape_data )], data = shape_data ))
12821313
12831314 def _tlen (
@@ -1304,9 +1335,12 @@ def _fill(
13041335 ) -> Value :
13051336 tensor = self ._expect_tns (args [0 ], "FILL" , location )
13061337 fill_value = args [1 ]
1307- if any (entry .type != fill_value .type for entry in tensor .data ):
1338+ if any (entry .type != fill_value .type for entry in tensor .data . flat ):
13081339 raise ASMRuntimeError ("FILL value type must match existing tensor element types" , location = location , rewrite_rule = "FILL" )
1309- new_data = [Value (fill_value .type , fill_value .value ) for _ in tensor .data ]
1340+ new_data = np .array (
1341+ [Value (fill_value .type , fill_value .value ) for _ in range (tensor .data .size )],
1342+ dtype = object ,
1343+ )
13101344 return Value (TYPE_TNS , Tensor (shape = list (tensor .shape ), data = new_data ))
13111345
13121346 def _tns (
@@ -1473,18 +1507,62 @@ def _cl(
14731507 # Capture stdout/stderr so the REPL can display results without
14741508 # spawning a visible console window on Windows.
14751509 cmd = self ._expect_str (args [0 ], "CL" , location )
1510+ cleanup_script : Optional [str ] = None
14761511 try :
14771512 # Use text mode for captured output.
1478- run_kwargs = {"shell" : True , "stdout" : subprocess .PIPE , "stderr" : subprocess .PIPE , "text" : True }
1513+ run_kwargs : Dict [str , object ] = {"stdout" : subprocess .PIPE , "stderr" : subprocess .PIPE , "text" : True }
1514+ # Default to shell=True to preserve existing behaviour for string commands.
1515+ run_kwargs ["shell" ] = True
1516+
14791517 # On Windows, avoid creating a visible console window for subprocesses.
14801518 if platform .system ().lower ().startswith ("win" ):
14811519 run_kwargs ["creationflags" ] = subprocess .CREATE_NO_WINDOW
1482- completed = subprocess .run (cmd , ** run_kwargs )
1520+
1521+ command_to_run : Union [str , List [str ]] = cmd
1522+ if platform .system ().lower ().startswith ("win" ) and len (cmd ) > WINDOWS_COMMAND_LENGTH_LIMIT :
1523+ stripped = cmd .lstrip ()
1524+ prefix = stripped .split (None , 1 )[0 ].lower () if stripped else ""
1525+
1526+ # If this is a PowerShell command, lift the -Command payload into
1527+ # a temporary .ps1 to avoid CreateProcess length limits.
1528+ if prefix in {"powershell" , "pwsh" }:
1529+ ps_body = self ._extract_powershell_body (cmd )
1530+ if ps_body is not None :
1531+ fd , script_path = tempfile .mkstemp (suffix = ".ps1" , text = True )
1532+ with os .fdopen (fd , "w" , encoding = "utf-8" ) as f :
1533+ f .write (ps_body )
1534+ cleanup_script = script_path
1535+ command_to_run = [prefix , "-NoProfile" , "-File" , script_path ]
1536+ run_kwargs ["shell" ] = False
1537+ else :
1538+ # Fall back to the generic .cmd approach if parsing failed.
1539+ fd , script_path = tempfile .mkstemp (suffix = ".cmd" , text = True )
1540+ with os .fdopen (fd , "w" , encoding = "utf-8" ) as f :
1541+ f .write (cmd )
1542+ cleanup_script = script_path
1543+ command_to_run = ["cmd" , "/c" , script_path ]
1544+ run_kwargs ["shell" ] = False
1545+ else :
1546+ # Generic long-command fallback: write to .cmd and execute it.
1547+ fd , script_path = tempfile .mkstemp (suffix = ".cmd" , text = True )
1548+ with os .fdopen (fd , "w" , encoding = "utf-8" ) as f :
1549+ f .write (cmd )
1550+ cleanup_script = script_path
1551+ command_to_run = ["cmd" , "/c" , script_path ]
1552+ run_kwargs ["shell" ] = False
1553+
1554+ completed = subprocess .run (command_to_run , ** run_kwargs )
14831555 code = completed .returncode
14841556 out = completed .stdout or ""
14851557 err = completed .stderr or ""
14861558 except OSError as exc :
14871559 raise ASMRuntimeError (f"CL failed: { exc } " , location = location , rewrite_rule = "CL" )
1560+ finally :
1561+ if cleanup_script :
1562+ try :
1563+ os .unlink (cleanup_script )
1564+ except OSError :
1565+ pass
14881566
14891567 # Record the CL event for deterministic logging/replay, including captured output.
14901568 interpreter .io_log .append ({"event" : "CL" , "cmd" : cmd , "code" : code , "stdout" : out , "stderr" : err })
@@ -1662,6 +1740,16 @@ def _execute_statement(self, statement: Statement, env: Environment) -> None:
16621740 location = statement .location ,
16631741 rewrite_rule = "ASSIGN" ,
16641742 )
1743+
1744+ # Respect identifier freezing: element assignment is still a form of reassignment.
1745+ env_found = env ._find_env (base_expr .name )
1746+ if env_found is not None and (base_expr .name in env_found .frozen or base_expr .name in env_found .permafrozen ):
1747+ raise ASMRuntimeError (
1748+ f"Identifier '{ base_expr .name } ' is frozen and cannot be reassigned" ,
1749+ location = statement .location ,
1750+ rewrite_rule = "ASSIGN" ,
1751+ )
1752+
16651753 base_val = self ._evaluate_expression (base_expr , env )
16661754 if base_val .type != TYPE_TNS :
16671755 raise ASMRuntimeError (
@@ -1672,8 +1760,7 @@ def _execute_statement(self, statement: Statement, env: Environment) -> None:
16721760 assert isinstance (base_val .value , Tensor )
16731761 indices = [self ._expect_int (self ._evaluate_expression (node , env ), "ASSIGN" , statement .location ) for node in index_nodes ]
16741762 new_value = self ._evaluate_expression (statement .value , env )
1675- updated = self ._set_tensor_value (base_val .value , indices , new_value , statement .location )
1676- env .set (base_expr .name , Value (TYPE_TNS , updated ))
1763+ self ._mutate_tensor_value (base_val .value , indices , new_value , statement .location )
16771764 return
16781765 if isinstance (statement , ExpressionStatement ):
16791766 self ._evaluate_expression (statement .expression , env )
@@ -1820,7 +1907,7 @@ def _tensor_total_size(self, shape: List[int]) -> int:
18201907 return size
18211908
18221909 def _tensor_truthy (self , tensor : Tensor ) -> bool :
1823- for item in tensor .data :
1910+ for item in tensor .data . flat :
18241911 if item .type == TYPE_INT and item .value != 0 :
18251912 return True
18261913 if item .type == TYPE_STR and item .value != "" :
@@ -1841,7 +1928,7 @@ def _values_equal(self, left: Value, right: Value) -> bool:
18411928 def _tensor_equal (self , left : Tensor , right : Tensor ) -> bool :
18421929 if left .shape != right .shape :
18431930 return False
1844- return all (self ._values_equal (a , b ) for a , b in zip (left .data , right .data ))
1931+ return all (self ._values_equal (a , b ) for a , b in zip (left .data . flat , right .data . flat ))
18451932
18461933 def _validate_tensor_shape (self , shape : List [int ], rule : str , location : SourceLocation ) -> None :
18471934 if not shape :
@@ -1877,17 +1964,25 @@ def _clone(val: Value) -> Value:
18771964 return Value (TYPE_TNS , val .value )
18781965 return Value (val .type , val .value )
18791966
1880- return Tensor (shape = list (shape ), data = [_clone (fill_value ) for _ in range (total )])
1967+ data = np .array ([_clone (fill_value ) for _ in range (total )], dtype = object )
1968+ return Tensor (shape = list (shape ), data = data )
18811969
18821970 def _set_tensor_value (self , tensor : Tensor , indices : List [int ], value : Value , location : SourceLocation ) -> Tensor :
18831971 offset = self ._tensor_flat_index (tensor , indices , "ASSIGN" , location )
18841972 current = tensor .data [offset ]
18851973 if current .type != value .type :
18861974 raise ASMRuntimeError ("Tensor element type mismatch" , location = location , rewrite_rule = "ASSIGN" )
1887- new_data = list ( tensor .data )
1975+ new_data = tensor .data . copy ( )
18881976 new_data [offset ] = value
18891977 return Tensor (shape = list (tensor .shape ), data = new_data )
18901978
1979+ def _mutate_tensor_value (self , tensor : Tensor , indices : List [int ], value : Value , location : SourceLocation ) -> None :
1980+ offset = self ._tensor_flat_index (tensor , indices , "ASSIGN" , location )
1981+ current = tensor .data [offset ]
1982+ if current .type != value .type :
1983+ raise ASMRuntimeError ("Tensor element type mismatch" , location = location , rewrite_rule = "ASSIGN" )
1984+ tensor .data [offset ] = value
1985+
18911986 def _build_tensor_from_literal (self , literal : TensorLiteral , env : Environment ) -> Tensor :
18921987 items = literal .items
18931988 if not items :
@@ -1901,7 +1996,7 @@ def _build_tensor_from_literal(self, literal: TensorLiteral, env: Environment) -
19011996 subshape = nested .shape
19021997 elif subshape != nested .shape :
19031998 raise ASMRuntimeError ("Inconsistent tensor shape" , location = item .location , rewrite_rule = "TNS" )
1904- flat .extend (nested .data )
1999+ flat .extend (list ( nested .data . flat ) )
19052000 else :
19062001 val = self ._evaluate_expression (item , env )
19072002 if subshape is None :
@@ -1914,7 +2009,7 @@ def _build_tensor_from_literal(self, literal: TensorLiteral, env: Environment) -
19142009 expected = self ._tensor_total_size (shape )
19152010 if len (flat ) != expected :
19162011 raise ASMRuntimeError ("Tensor literal size mismatch" , location = literal .location , rewrite_rule = "TNS" )
1917- return Tensor (shape = shape , data = flat )
2012+ return Tensor (shape = shape , data = np . array ( flat , dtype = object ) )
19182013
19192014 def _gather_index_chain (self , expr : Expression ) -> Tuple [Expression , List [Expression ]]:
19202015 indices : List [Expression ] = []
0 commit comments