Skip to content

Commit 0fd0957

Browse files
Rename SCATTER to SCAT; fix REPL PRINT output error; rewrite stats.asmln; clean up the rest of the stdlib
1 parent 618478b commit 0fd0957

File tree

9 files changed

+383
-499
lines changed

9 files changed

+383
-499
lines changed

SPECIFICATION.html

Lines changed: 1 addition & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -453,15 +453,14 @@
453453
- `ALL(ANY: a1, ..., ANY: aN):INT` ; Boolean AND (empty string -> false, non-empty -> true)
454454
- `ANY(ANY: a1, ..., ANY: aN):INT` ; Boolean OR (empty string -> false, non-empty -> true)
455455
- `JOIN(INT|STR: a1, INT|STR: a2, ..., INT|STR: aN):INT|STR` ; `INT` -> concatenate binary spellings with consistent sign; `STR` -> concatenate strings; mixing `INT` and `STR` or supplying tensors raises an error
456-
- `PROD(INT: a1, ..., INT: aN):INT` ; product of the arguments
457456
- `PROD(INT|FLT: a1, ..., INT|FLT: aN):INT|FLT` ; product of the arguments (no mixing INT/FLT)
458457
459458
### Tensor operations
460459
- `SHAPE(TNS: tensor):TNS` — Returns the tensor's shape as a 1D `TNS` (vector) of `INT` lengths (one entry per dimension).
461460
- `TLEN(TNS: tensor, INT: dim):INT` — Returns the length of the specified 1-based dimension. Errors if `dim` is out of range.
462461
- `FLIP(INT|STR: obj):INT|STR` — For `INT` input, returns an `INT` whose binary-digit spelling is the reverse of the absolute-value binary spelling of `obj` (sign is preserved). For `STR` input, returns the character-reversed `STR`.
463462
- `TFLIP(TNS: obj, INT: dim):TNS` — Returns a new `TNS` with the elements along 1-based dimension `dim` reversed. Errors if `dim` is out of range.
464-
- `SCATTER(TNS: src, TNS: dst, TNS: ind):TNS` — Returns a copy of `dst` with a rectangular slice replaced by `src`. `ind` must be a 2D tensor of `INT` pairs with shape `[TLEN(dst, 1), 10]` (binary `10` = decimal 2), i.e., one `[lo, hi]` row per destination dimension (rank; for example `rank = TLEN(SHAPE(dst), 1)`). Indices are 1-based; negatives follow the tensor indexing rules (for example, `-1` is the last element) and `0` is invalid. For each dimension, the inclusive span `hi - lo + 1` must equal the corresponding `src` dimension length, and all bounds must fall within `dst`. Elements outside the slice are copied from `dst` unchanged.
463+
- `SCAT(TNS: src, TNS: dst, TNS: ind):TNS` — Returns a copy of `dst` with a rectangular slice replaced by `src`. `ind` must be a 2D tensor of `INT` pairs with shape `[TLEN(dst, 1), 10]` (binary `10` = decimal 2), i.e., one `[lo, hi]` row per destination dimension (rank; for example `rank = TLEN(SHAPE(dst), 1)`). Indices are 1-based; negatives follow the tensor indexing rules (for example, `-1` is the last element) and `0` is invalid. For each dimension, the inclusive span `hi - lo + 1` must equal the corresponding `src` dimension length, and all bounds must fall within `dst`. Elements outside the slice are copied from `dst` unchanged.
465464
- `FILL(TNS: tensor, ANY: value):TNS` — Returns a new tensor with the same shape as `tensor`, filled with `value`. The supplied value`s type must match the existing element type at every position.
466465
- `TNS(TNS: shape, ANY: value):TNS` Creates a new `TNS` with the shape described by a 1D `TNS` of positive `INT` lengths, filled with `value`.
467466
- `CONV(TNS: x, TNS: kernel):TNS` N-dimensional discrete convolution producing an output tensor with the same shape as `x`. The `kernel` must have the same rank as `x` and each kernel dimension length must be odd (so the kernel has a well-defined center). At the boundaries, out-of-range sample coordinates are clamped to the nearest valid index (replicate padding). Both tensors must contain only `INT` or only `FLT` elements (no mixed element types within a tensor). If both tensors are `INT`-valued, the output is an `INT` tensor; otherwise the output is a `FLT` tensor.

asm-lang.exe

27 Bytes
Binary file not shown.

asm-lang.py

Lines changed: 15 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -60,10 +60,19 @@ def run_repl(*, verbose: bool, services) -> int:
6060
print("\x1b[38;2;153;221;255mASM-Lang\033[0m REPL. Enter statements, blank line to run buffer.") # "ASM-Lang" in light blue
6161
# Use "<repl>" as the REPL's effective source filename so that MAIN() and imports behave
6262
had_output = False
63+
# Tracks whether any PRINT has occurred since the last REPL input.
64+
first_print_since_input = True
65+
6366
def _output_sink(text: str) -> None:
64-
nonlocal had_output
67+
nonlocal had_output, first_print_since_input
68+
# If this is not the first PRINT since the last REPL input,
69+
# emit a leading newline so outputs appear on separate lines.
70+
if not first_print_since_input:
71+
print()
72+
first_print_since_input = False
6573
had_output = True
66-
print(text, end="")
74+
# Print the text without appending a trailing newline.
75+
print(text, end="", flush=True)
6776

6877
picked = services.hook_registry.pick_repl() if services is not None else None
6978
if picked is not None:
@@ -120,6 +129,10 @@ def _output_sink(text: str) -> None:
120129
print()
121130
break
122131

132+
# Reset per-input PRINT tracking so the next PRINT is treated as
133+
# the first since this REPL input.
134+
first_print_since_input = True
135+
123136
stripped = line.strip()
124137

125138
is_block_start = False

interpreter.py

Lines changed: 13 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -523,7 +523,7 @@ def __init__(self) -> None:
523523
self._register_custom("CONV", 2, 2, self._convolve)
524524
self._register_custom("FLIP", 1, 1, self._flip)
525525
self._register_custom("TFLIP", 2, 2, self._tflip)
526-
self._register_custom("SCATTER", 3, 3, self._scatter)
526+
self._register_custom("SCAT", 3, 3, self._scatter)
527527
self._register_custom("PARALLEL", 1, None, self._parallel)
528528

529529
def _register_int_only(self, name: str, arity: int, func: Callable[..., int]) -> None:
@@ -2966,36 +2966,36 @@ def _scatter(
29662966
___: Environment,
29672967
location: SourceLocation,
29682968
) -> Value:
2969-
"""SCATTER(TNS: src, TNS: dst, TNS: ind):TNS
2969+
"""SCAT(TNS: src, TNS: dst, TNS: ind):TNS
29702970
29712971
Copy `src` into a slice of `dst` defined by per-dimension [lo, hi] pairs.
29722972
Returns a new tensor shaped like `dst` with the slice replaced.
29732973
"""
29742974

2975-
src = self._expect_tns(args[0], "SCATTER", location)
2976-
dst = self._expect_tns(args[1], "SCATTER", location)
2977-
ind = self._expect_tns(args[2], "SCATTER", location)
2975+
src = self._expect_tns(args[0], "SCAT", location)
2976+
dst = self._expect_tns(args[1], "SCAT", location)
2977+
ind = self._expect_tns(args[2], "SCAT", location)
29782978

29792979
rank = len(dst.shape)
29802980
if len(src.shape) != rank:
2981-
raise ASMRuntimeError("SCATTER requires src and dst to have the same rank", location=location, rewrite_rule="SCATTER")
2981+
raise ASMRuntimeError("SCAT requires src and dst to have the same rank", location=location, rewrite_rule="SCAT")
29822982
if len(ind.shape) != 2 or ind.shape[0] != rank or ind.shape[1] != 2:
2983-
raise ASMRuntimeError("SCATTER indices must have shape [rank, 2]", location=location, rewrite_rule="SCATTER")
2983+
raise ASMRuntimeError("SCAT indices must have shape [rank, 2]", location=location, rewrite_rule="SCAT")
29842984

29852985
pairs = ind.data.reshape((ind.shape[0], ind.shape[1]))
29862986
slices: List[Tuple[int, int]] = []
29872987

29882988
for axis in range(rank):
2989-
lo_raw = self._expect_int(pairs[axis, 0], "SCATTER", location)
2990-
hi_raw = self._expect_int(pairs[axis, 1], "SCATTER", location)
2989+
lo_raw = self._expect_int(pairs[axis, 0], "SCAT", location)
2990+
hi_raw = self._expect_int(pairs[axis, 1], "SCAT", location)
29912991
dim_len = dst.shape[axis]
2992-
lo = self._resolve_tensor_index(lo_raw, dim_len, "SCATTER", location)
2993-
hi = self._resolve_tensor_index(hi_raw, dim_len, "SCATTER", location)
2992+
lo = self._resolve_tensor_index(lo_raw, dim_len, "SCAT", location)
2993+
hi = self._resolve_tensor_index(hi_raw, dim_len, "SCAT", location)
29942994
if lo > hi:
2995-
raise ASMRuntimeError("SCATTER expects lo <= hi for each dimension", location=location, rewrite_rule="SCATTER")
2995+
raise ASMRuntimeError("SCAT expects lo <= hi for each dimension", location=location, rewrite_rule="SCAT")
29962996
span = hi - lo + 1
29972997
if span != src.shape[axis]:
2998-
raise ASMRuntimeError("SCATTER source shape must match index span", location=location, rewrite_rule="SCATTER")
2998+
raise ASMRuntimeError("SCAT source shape must match index span", location=location, rewrite_rule="SCAT")
29992999
slices.append((lo - 1, hi))
30003000

30013001
out_data = dst.data.copy()

lib/csprng.asmln

Lines changed: 96 additions & 121 deletions
Original file line numberDiff line numberDiff line change
@@ -1,33 +1,40 @@
1-
#ChaCha20-based cryptographically secure pseudorandom number generator
1+
# ChaCha20-based cryptographically secure pseudorandom number generator
22
#
33
# API:
4-
# CS_PRNG_SEED(INT:seed) -> INT
5-
# CS_PRNG_NEXT() -> INT
6-
# CS_PRNG_RANGE(INT:max) -> INT
7-
# CS_PRNG_RANGE_MIN_MAX(INT:min, INT:max) -> INT
4+
# SEED(INT: seed) -> INT
5+
# NEXT() -> INT
6+
# RANGE(INT: max) -> INT
7+
# RANGE_MIN_MAX(INT: min, INT: max) -> INT
88

99
INT: MASK32 = SUB( POW(10,100000), 1 ) # 2^32-1
1010

1111
# ChaCha20 constants ("expand 32-byte k")
12-
INT: CH_CONST0 = 01100001011100000111000001100101
13-
INT: CH_CONST1 = 00110011001000000110010001101110
14-
INT: CH_CONST2 = 01111001011000100010110100110010
15-
INT: CH_CONST3 = 01101011001000000110010101110100
16-
17-
TNS: ch_key = [0,0,0,0,0,0,0,0]
18-
TNS: ch_nonce = [0,0,0]
12+
MAP: CH_CONST = < ^
13+
0 = 01100001011100000111000001100101, ^
14+
1 = 00110011001000000110010001101110, ^
15+
10 = 01111001011000100010110100110010, ^
16+
11 = 01101011001000000110010101110100 ^
17+
>
18+
19+
TNS: ch_key = TNS([1000], 0) # 8 words
20+
TNS: ch_nonce = [0, 0, 0] # use literals for short TNS
1921
INT: ch_counter = 0
20-
TNS: ch_buf = [0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0]
21-
INT: ch_buf_pos = 10000 # 16 -> force refill
22+
TNS: ch_buf = TNS([10000], 0) # 16 words
23+
INT: ch_buf_pos = TLEN(ch_buf, 1) # set to buffer length to force refill
2224

23-
FUNC ROTL32(INT:x, INT:n):INT{
25+
FUNC ROTL32(INT: x, INT: n):INT{
2426
INT: x32 = BAND(x, MASK32)
25-
INT: left = BAND( SHL(x32, n), MASK32 )
26-
INT: right = DIV( x32, POW(10, SUB(100000, n)) )
27-
RETURN( BOR(left, right) )
27+
INT: left = BAND(SHL(x32, n), MASK32)
28+
INT: right = SHR(x32, SUB(100000, n)) # 32 - n
29+
RETURN( BAND(BOR(left, right), MASK32) )
2830
}
2931

30-
FUNC QR(INT:a, INT:b, INT:c, INT:d):TNS{
32+
FUNC QR(TNS: s, INT: ia, INT: ib, INT: ic, INT: id):INT{
33+
INT: a = s[ia]
34+
INT: b = s[ib]
35+
INT: c = s[ic]
36+
INT: d = s[id]
37+
3138
a = BAND( ADD(a, b), MASK32 )
3239
d = ROTL32( BXOR(d, a), 10000 ) # 16
3340
c = BAND( ADD(c, d), MASK32 )
@@ -36,154 +43,122 @@ FUNC QR(INT:a, INT:b, INT:c, INT:d):TNS{
3643
d = ROTL32( BXOR(d, a), 1000 ) # 8
3744
c = BAND( ADD(c, d), MASK32 )
3845
b = ROTL32( BXOR(b, c), 111 ) # 7
39-
TNS: coll = [a, b, c, d]
40-
RETURN(coll)
46+
47+
s[ia] = a
48+
s[ib] = b
49+
s[ic] = c
50+
s[id] = d
51+
RETURN(0)
4152
}
4253

43-
FUNC CHACHA_BLOCK():TNS{
44-
INT: x0 = CH_CONST0
45-
INT: x1 = CH_CONST1
46-
INT: x2 = CH_CONST2
47-
INT: x3 = CH_CONST3
48-
INT: x4 = ch_key[1]
49-
INT: x5 = ch_key[10]
50-
INT: x6 = ch_key[11]
51-
INT: x7 = ch_key[100]
52-
INT: x8 = ch_key[101]
53-
INT: x9 = ch_key[110]
54-
INT: x10 = ch_key[111]
55-
INT: x11 = ch_key[1000]
56-
INT: x12 = ch_counter
57-
INT: x13 = ch_nonce[1]
58-
INT: x14 = ch_nonce[10]
59-
INT: x15 = ch_nonce[11]
60-
INT: i = 0
61-
WHILE( LT(i, 1010) ){ # 10 double-rounds
62-
TNS: qr = QR(x0, x4, x8, x12)
63-
x0 = qr[1]
64-
x4 = qr[10]
65-
x8 = qr[11]
66-
x12 = qr[100]
67-
qr = QR(x1, x5, x9, x13)
68-
x1 = qr[1]
69-
x5 = qr[10]
70-
x9 = qr[11]
71-
x13 = qr[100]
72-
qr = QR(x2, x6, x10, x14)
73-
x2 = qr[1]
74-
x6 = qr[10]
75-
x10 = qr[11]
76-
x14 = qr[100]
77-
qr = QR(x3, x7, x11, x15)
78-
x3 = qr[1]
79-
x7 = qr[10]
80-
x11 = qr[11]
81-
x15 = qr[100]
82-
83-
qr = QR(x0, x5, x10, x15)
84-
x0 = qr[1]
85-
x5 = qr[10]
86-
x10 = qr[11]
87-
x15 = qr[100]
88-
qr = QR(x1, x6, x11, x12)
89-
x1 = qr[1]
90-
x6 = qr[10]
91-
x11 = qr[11]
92-
x12 = qr[100]
93-
qr = QR(x2, x7, x8, x13)
94-
x2 = qr[1]
95-
x7 = qr[10]
96-
x8 = qr[11]
97-
x13 = qr[100]
98-
qr = QR(x3, x4, x9, x14)
99-
x3 = qr[1]
100-
x4 = qr[10]
101-
x9 = qr[11]
102-
x14 = qr[100]
103-
104-
i = ADD(i, 1)
54+
FUNC CHACHA_BLOCK():INT{
55+
TNS: k = ch_key
56+
TNS: n = ch_nonce
57+
TNS: state = [ ^
58+
CH_CONST<0>, ^
59+
CH_CONST<1>, ^
60+
CH_CONST<10>, ^
61+
CH_CONST<11>, ^
62+
k[1], ^
63+
k[10], ^
64+
k[11], ^
65+
k[100], ^
66+
k[101], ^
67+
k[110], ^
68+
k[111], ^
69+
k[1000], ^
70+
ch_counter, ^
71+
n[1], ^
72+
n[10], ^
73+
n[11] ^
74+
]
75+
FOR(i, 1010){ # 10 double-rounds
76+
QR(state, 1, 101, 1001, 1101)
77+
QR(state, 10, 110, 1010, 1110)
78+
QR(state, 11, 111, 1011, 1111)
79+
QR(state, 100, 1000, 1100, 10000)
80+
81+
QR(state, 1, 110, 1011, 10000)
82+
QR(state, 10, 111, 1100, 1101)
83+
QR(state, 11, 1000, 1001, 1110)
84+
QR(state, 100, 101, 1010, 1111)
10585
}
10686

107-
x0 = BAND( ADD(x0, CH_CONST0), MASK32 )
108-
x1 = BAND( ADD(x1, CH_CONST1), MASK32 )
109-
x2 = BAND( ADD(x2, CH_CONST2), MASK32 )
110-
x3 = BAND( ADD(x3, CH_CONST3), MASK32 )
111-
x4 = BAND( ADD(x4, ch_key[1]), MASK32 )
112-
x5 = BAND( ADD(x5, ch_key[10]), MASK32 )
113-
x6 = BAND( ADD(x6, ch_key[11]), MASK32 )
114-
x7 = BAND( ADD(x7, ch_key[100]), MASK32 )
115-
x8 = BAND( ADD(x8, ch_key[101]), MASK32 )
116-
x9 = BAND( ADD(x9, ch_key[110]), MASK32 )
117-
x10 = BAND( ADD(x10, ch_key[111]), MASK32 )
118-
x11 = BAND( ADD(x11, ch_key[1000]), MASK32 )
119-
x12 = BAND( ADD(x12, ch_counter), MASK32 )
120-
x13 = BAND( ADD(x13, ch_nonce[1]), MASK32 )
121-
x14 = BAND( ADD(x14, ch_nonce[10]), MASK32 )
122-
x15 = BAND( ADD(x15, ch_nonce[11]), MASK32 )
123-
124-
TNS: out = [x0, x1, x2, x3, x4, x5, x6, x7, x8, x9, x10, x11, x12, x13, x14, x15]
125-
RETURN(out)
87+
ch_buf[1] = BAND( ADD(state[1], CH_CONST<0>), MASK32 )
88+
ch_buf[10] = BAND( ADD(state[10], CH_CONST<1>), MASK32 )
89+
ch_buf[11] = BAND( ADD(state[11], CH_CONST<10>), MASK32 )
90+
ch_buf[100] = BAND( ADD(state[100], CH_CONST<11>), MASK32 )
91+
ch_buf[101] = BAND( ADD(state[101], k[1]), MASK32 )
92+
ch_buf[110] = BAND( ADD(state[110], k[10]), MASK32 )
93+
ch_buf[111] = BAND( ADD(state[111], k[11]), MASK32 )
94+
ch_buf[1000] = BAND( ADD(state[1000], k[100]), MASK32 )
95+
ch_buf[1001] = BAND( ADD(state[1001], k[101]), MASK32 )
96+
ch_buf[1010] = BAND( ADD(state[1010], k[110]), MASK32 )
97+
ch_buf[1011] = BAND( ADD(state[1011], k[111]), MASK32 )
98+
ch_buf[1100] = BAND( ADD(state[1100], k[1000]), MASK32 )
99+
ch_buf[1101] = BAND( ADD(state[1101], ch_counter), MASK32 )
100+
ch_buf[1110] = BAND( ADD(state[1110], n[1]), MASK32 )
101+
ch_buf[1111] = BAND( ADD(state[1111], n[10]), MASK32 )
102+
ch_buf[10000] = BAND( ADD(state[10000], n[11]), MASK32 )
103+
104+
RETURN(0)
126105
}
127106

128107
FUNC REFILL_BUF():INT{
129-
ch_buf = CHACHA_BLOCK()
108+
CHACHA_BLOCK()
130109
ch_buf_pos = 0
131110
ch_counter = BAND( ADD(ch_counter, 1), MASK32 )
132111
RETURN(0)
133112
}
134113

135-
FUNC DERIVE_KEY_AND_NONCE(INT:seed):INT{
114+
FUNC DERIVE_KEY_AND_NONCE(INT: seed):INT{
136115
INT: s = BAND(seed, MASK32)
137-
INT: i = 0
138-
WHILE( LT(i, 1000) ){ # 8 key words (1000 == 8)
116+
FOR(i, 1000){ # 8 key words (1000 == 8)
139117
s = BAND( ADD( MUL(s, ^
140118
01000001110001100100111001101101), ^
141119
00000000000000000011000000111001 ), ^
142120
MASK32 ^
143121
)
144-
ch_key[ ADD(i, 1) ] = s
145-
i = ADD(i, 1)
122+
ch_key[i] = s
146123
}
147-
INT: j = 0
148-
WHILE( LT(j, 11) ){ # 3 nonce words (11 == 3)
124+
FOR(j, 11){ # 3 nonce words (11 == 3)
149125
s = BAND( ADD( MUL(s, ^
150126
01000001110001100100111001101101), ^
151127
00000000000000000011000000111001 ), ^
152128
MASK32 ^
153129
)
154-
ch_nonce[ ADD(j, 1) ] = s
155-
j = ADD(j, 1)
130+
ch_nonce[j] = s
156131
}
157132
RETURN(0)
158133
}
159134

160-
FUNC CS_PRNG_SEED(INT:seed):INT{
135+
FUNC SEED(INT: seed):INT{
161136
DERIVE_KEY_AND_NONCE(seed)
162137
ch_counter = 0
163-
ch_buf = [0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0]
164-
ch_buf_pos = 10000 # 16
138+
ch_buf = TNS([10000], 0)
139+
ch_buf_pos = TLEN(ch_buf, 1) # set to buffer length to force refill
165140
RETURN(ch_counter)
166141
}
167142

168-
FUNC CS_PRNG_NEXT():INT{
169-
IF( GTE(ch_buf_pos, 10000) ){ # 16
143+
FUNC NEXT():INT{
144+
IF( GTE(ch_buf_pos, TLEN(ch_buf, 1)) ){ # buffer length
170145
REFILL_BUF()
171146
}
172147
INT: v = ch_buf[ ADD(ch_buf_pos, 1) ]
173148
ch_buf_pos = ADD(ch_buf_pos, 1)
174149
RETURN(v)
175150
}
176151

177-
FUNC CS_PRNG_RANGE(INT:max):INT{
152+
FUNC RANGE(INT: max):INT{
178153
ASSERT( GT(max, 0) )
179-
RETURN( MOD(CS_PRNG_NEXT(), max) )
154+
RETURN( MOD(NEXT(), max) )
180155
}
181156

182-
FUNC CS_PRNG_RANGE_MIN_MAX(INT:min, INT:max):INT{
157+
FUNC RANGE_MIN_MAX(INT: min, INT: max):INT{
183158
ASSERT( LTE(min, max) )
184159
INT: range = SUB(max, min)
185160
IF( EQ(range, 0) ){ RETURN(min) }
186161
ASSERT( GT(range, 0) )
187-
INT: offset = CS_PRNG_RANGE(range)
162+
INT: offset = RANGE(range)
188163
RETURN( ADD(offset, min) )
189164
}

0 commit comments

Comments
 (0)