Skip to content

Commit d42a5eb

Browse files
Add ILEN, SLEN, OS, rename RUN to CL, add lib/decimal.asmln
1 parent cce534d commit d42a5eb

File tree

8 files changed

+250
-92
lines changed

8 files changed

+250
-92
lines changed
-89 KB
Binary file not shown.

__pycache__/lexer.cpython-314.pyc

-12 KB
Binary file not shown.

__pycache__/parser.cpython-314.pyc

-23.4 KB
Binary file not shown.

asmln.exe

206 KB
Binary file not shown.

interpreter.py

Lines changed: 87 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,7 @@
44
import math
55
import os
66
import sys
7+
import platform
78
from dataclasses import dataclass, field
89
from 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

lib/decimal.asmln

Lines changed: 103 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,103 @@
1+
# Decimal <-> binary helpers
2+
3+
INT: DEC_BASE = 1010 # 10
4+
5+
FUNC DEC_CHAR_TO_INT(STR:ch):INT{
6+
IF(EQ(ch, "0")){ RETURN(0) }
7+
IF(EQ(ch, "1")){ RETURN(1) }
8+
IF(EQ(ch, "2")){ RETURN(10) }
9+
IF(EQ(ch, "3")){ RETURN(11) }
10+
IF(EQ(ch, "4")){ RETURN(100) }
11+
IF(EQ(ch, "5")){ RETURN(101) }
12+
IF(EQ(ch, "6")){ RETURN(110) }
13+
IF(EQ(ch, "7")){ RETURN(111) }
14+
IF(EQ(ch, "8")){ RETURN(1000) }
15+
IF(EQ(ch, "9")){ RETURN(1001) }
16+
ASSERT(0)
17+
RETURN(0)
18+
}
19+
20+
FUNC INT_TO_DEC_CHAR(INT:d):STR{
21+
IF(EQ(d, 0)){ RETURN("0") }
22+
IF(EQ(d, 1)){ RETURN("1") }
23+
IF(EQ(d, 10)){ RETURN("2") }
24+
IF(EQ(d, 11)){ RETURN("3") }
25+
IF(EQ(d, 100)){ RETURN("4") }
26+
IF(EQ(d, 101)){ RETURN("5") }
27+
IF(EQ(d, 110)){ RETURN("6") }
28+
IF(EQ(d, 111)){ RETURN("7") }
29+
IF(EQ(d, 1000)){ RETURN("8") }
30+
IF(EQ(d, 1001)){ RETURN("9") }
31+
ASSERT(0)
32+
RETURN("")
33+
}
34+
35+
FUNC DEC_TO_INT(STR:s):INT{
36+
INT: len = SLEN(s)
37+
ASSERT( GT(len, 0) )
38+
39+
INT: idx = 0
40+
# skip any leading spaces before the sign or first digit
41+
WHILE( AND( LT(idx, len), EQ(SLICE(s, SUB(len, ADD(idx, 1)), SUB(len, ADD(idx, 1))), " ") ) ){
42+
idx = ADD(idx, 1)
43+
}
44+
ASSERT( LT(idx, len) )
45+
46+
INT: is_neg = 0
47+
INT: ridx = SUB(len, ADD(idx, 1))
48+
STR: first = SLICE(s, ridx, ridx)
49+
# support optional leading '+' or '-' and allow spaces after the sign
50+
IF(EQ(first, "-")){
51+
is_neg = 1
52+
idx = ADD(idx, 1)
53+
} ELSE {
54+
IF(EQ(first, "+")){
55+
idx = ADD(idx, 1)
56+
}
57+
}
58+
59+
# skip spaces after the optional sign
60+
WHILE( AND( LT(idx, len), EQ(SLICE(s, SUB(len, ADD(idx, 1)), SUB(len, ADD(idx, 1))), " ") ) ){
61+
idx = ADD(idx, 1)
62+
}
63+
ASSERT( LT(idx, len) )
64+
65+
INT: acc = 0
66+
WHILE( LT(idx, len) ){
67+
INT: ridx2 = SUB(len, ADD(idx, 1))
68+
STR: ch = SLICE(s, ridx2, ridx2)
69+
INT: digit = DEC_CHAR_TO_INT(ch)
70+
acc = ADD( MUL(acc, DEC_BASE), digit )
71+
idx = ADD(idx, 1)
72+
}
73+
74+
IF(is_neg){
75+
RETURN( NEG(acc) )
76+
}
77+
RETURN(acc)
78+
}
79+
80+
FUNC INT_TO_DEC(INT:n):STR{
81+
IF(NOT(n)){
82+
RETURN("0")
83+
}
84+
85+
INT: is_neg = 0
86+
IF(LT(n, 0)){
87+
is_neg = 1
88+
n = ABS(n)
89+
}
90+
91+
STR: out = ""
92+
WHILE( GT(n, 0) ){
93+
INT: digit = MOD(n, DEC_BASE)
94+
n = DIV(n, DEC_BASE)
95+
STR: ch = INT_TO_DEC_CHAR(digit)
96+
out = JOIN(ch, out) # prepend to maintain most-significant-first order
97+
}
98+
99+
IF(is_neg){
100+
out = JOIN("-", out)
101+
}
102+
RETURN(out)
103+
}

0 commit comments

Comments
 (0)