Skip to content

Commit c2f41c5

Browse files
Shorten image and type tooling; improve portability and docs
Add runtime TYPE builtin to return a value's type name (INT/FLT/STR/TNS), preserving extension types. Add OS-aware cleanup in tests: use del on Windows and rm on other platforms. Improve image extension: Ensure JPEG save unwraps interpreter Value objects safely with _expect_int before writing bytes. Expand supported image extension files list (image.py, win32.py) and library bindings. Add SHOW helper to stdlib lib/image.asmln to open saved images on Windows. Tidy README code block formatting for PowerShell install steps. Update SPECIFICATION wording for identifier character ranges and tensor literal types.
1 parent 0f80f1b commit c2f41c5

File tree

8 files changed

+77
-26
lines changed

8 files changed

+77
-26
lines changed

README.md

Lines changed: 7 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,8 @@
11
## Installation
2-
`Invoke-WebRequest -Uri "https://github.com/python-processing-unit/ASM-Lang/archive/refs/heads/main.zip" -OutFile "path\to\download\ASM-Lang.zip"`<br>
3-
`Expand-Archive -Path "path\to\download\ASM-Lang.zip" -DestinationPath "path\to\extract\ASM-Lang"`<br>
4-
`$old = [Environment]::GetEnvironmentVariable('Path','User')`<br>
5-
`if(-not $old.Split(';') -contains 'path\to\extract\ASM-Lang'){ [Environment]::SetEnvironmentVariable('Path',$old + ';path\to\extract\ASM-Lang','User') }`<br>
6-
`Remove-Item -Path "path\to\download\ASM-Lang.zip"`<br>
2+
<code>
3+
Invoke-WebRequest -Uri "https://github.com/python-processing-unit/ASM-Lang/archive/refs/heads/main.zip" -OutFile "path\to\download\ASM-Lang.zip"<br>
4+
Expand-Archive -Path "path\to\download\ASM-Lang.zip" -DestinationPath "path\to\extract\ASM-Lang"<br>
5+
$old = [Environment]::GetEnvironmentVariable('Path','User')<br>
6+
if(-not $old.Split(';') -contains 'path\to\extract\ASM-Lang'){ [Environment]::SetEnvironmentVariable('Path',$old + ';path\to\extract\ASM-Lang','User') }<br>
7+
Remove-Item -Path "path\to\download\ASM-Lang.zip"
8+
</code>

SPECIFICATION.html

Lines changed: 8 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -72,17 +72,17 @@
7272

7373
- The first character MUST NOT be `0` or `1`. Any other non-ASCII character is disallowed, but otherwise the lexer permits a broad set of ASCII punctuation and symbol characters in addition to letters and digits. In particular, the following characters are valid as the first character of an identifier:
7474

75-
- Lowercase letters `a``z`
76-
- Uppercase letters `A``Z`
77-
- Decimal digits `2``9`
75+
- Lowercase letters `a`-`z`
76+
- Uppercase letters `A`-`Z`
77+
- Decimal digits `2`-`9`
7878
- The punctuation and symbol characters
7979
`; / ! @ $ % & ~ _ + | < > ?`
8080

8181
- Subsequent characters in an identifier may be any of the following:
8282

83-
- Lowercase letters `a``z`
84-
- Uppercase letters `A``Z`
85-
- Decimal digits `0``9`
83+
- Lowercase letters `a`-`z`
84+
- Uppercase letters `A`-`Z`
85+
- Decimal digits `0`-`9`
8686
- The punctuation and symbol characters
8787
`; / ! @ $ % & ~ _ + | < > ?`
8888

@@ -113,7 +113,7 @@
113113
- `[[0,1,10],[0,10,100]]` has shape `[10,11]`.
114114
- `[[[0,1,10],[1,10,100]]]` has shape `[1,10,11]`.
115115

116-
Expressions inside a tensor literal may themselves evaluate to `INT`, `STR`, or `TNS`. If an element expression evaluates to a tensor value, it occupies a single position and does not contribute additional dimensions; only the explicit bracket structure determines the shape. A tensor literal that mixes sub-brackets of differing lengths is invalid (for example, `[[0,0,0],[1,1]]`).
116+
Expressions inside a tensor literal may themselves evaluate to `INT`, `FLT`, `STR`, or `TNS`. If an element expression evaluates to a tensor value, it occupies a single position and does not contribute additional dimensions; only the explicit bracket structure determines the shape. A tensor literal that mixes sub-brackets of differing lengths is invalid (for example, `[[0,0,0],[1,1]]`).
117117

118118
Tensor values carry a fixed shape (dimension count and length per dimension). Each location in a tensor is statically typed by the value it first receives; attempting to write a different type to that location is a runtime error. Tensors are indexed with one-based indices. Negative indices address from the end of the corresponding dimension (for example, index -1 is the last element of that dimension); index 0 is invalid. Tensors may be re-assigned in two ways: binding a new tensor to the variable name, or writing through an index such as `tensor[dim1,...,dimN] = expr`. The latter mutates the existing tensor value in place at the indexed location and does not construct a new tensor object.
119119

@@ -344,6 +344,7 @@
344344
- `ISINT(SYMBOL: x):INT` ; returns `1` if the identifier `x` exists and is of type `INT`, otherwise returns `0`. The argument must be an identifier; supplying any other expression is a runtime error.
345345
- `ISSTR(SYMBOL: x):INT` ; returns `1` if the identifier `x` exists and is of type `STR`, otherwise returns `0`. The argument must be an identifier; supplying any other expression is a runtime error.
346346
- `ISTNS(SYMBOL: x):INT` ; returns `1` if the identifier `x` exists and is of type `TNS`, otherwise returns `0`. The argument must be an identifier; supplying any other expression is a runtime error.
347+
- `TYPE(ANY: obj):STR` ; returns the runtime type name of `obj` as a `STR` — one of `INT`, `FLT`, `STR`, or `TNS` (extension-defined type names are returned unchanged when extensions are enabled).
347348
- `SLEN(STR: s):INT` returns the length of the supplied `STR` in characters as an `INT`. The argument must be a `STR`; passing an `INT` raises a runtime error.
348349
- `ILEN(INT: n):INT` returns the length in binary digits of the absolute value of the supplied `INT`. `ILEN(0)` returns 1. The argument must be an `INT`; passing a `STR` raises a runtime error.
349350

asm-lang.exe

1.26 KB
Binary file not shown.

ext/image.py

Lines changed: 7 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -974,14 +974,16 @@ def _op_save_jpeg(interpreter, args, _arg_nodes, _env, location):
974974
# Try GDI+
975975
if _load_with_gdiplus is not None:
976976
try:
977-
# _save_with_gdiplus expects RGBA list
977+
# _save_with_gdiplus expects an RGBA list of plain ints. Use the
978+
# interpreter helper to unwrap `Value` objects to ints rather than
979+
# calling `int()` on them directly.
978980
rgba = []
979981
for y in range(h):
980982
for x in range(w):
981-
rgba.append(int(arr[y, x, 0]) & 0xFF)
982-
rgba.append(int(arr[y, x, 1]) & 0xFF)
983-
rgba.append(int(arr[y, x, 2]) & 0xFF)
984-
rgba.append(int(arr[y, x, 3]) & 0xFF)
983+
rgba.append(interpreter._expect_int(arr[y, x, 0], "SAVE_JPEG", location) & 0xFF)
984+
rgba.append(interpreter._expect_int(arr[y, x, 1], "SAVE_JPEG", location) & 0xFF)
985+
rgba.append(interpreter._expect_int(arr[y, x, 2], "SAVE_JPEG", location) & 0xFF)
986+
rgba.append(interpreter._expect_int(arr[y, x, 3], "SAVE_JPEG", location) & 0xFF)
985987
_save_with_gdiplus(path, w, h, rgba, "JPEG", quality=int(quality))
986988
return Value(TYPE_STR, "OK")
987989
except Exception as exc:

interpreter.py

Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -386,6 +386,7 @@ def __init__(self) -> None:
386386
self._register_custom("ISFLT", 1, 1, self._isflt)
387387
self._register_custom("ISSTR", 1, 1, self._isstr)
388388
self._register_custom("ISTNS", 1, 1, self._istns)
389+
self._register_custom("TYPE", 1, 1, self._type)
389390
self._register_custom("ROUND", 1, 3, self._round)
390391
self._register_custom("READFILE", 1, 2, self._readfile)
391392
self._register_custom("BYTES", 1, 1, self._bytes)
@@ -1748,6 +1749,21 @@ def _istns(
17481749
raise
17491750
return Value(TYPE_INT, 1 if val.type == TYPE_TNS else 0)
17501751

1752+
def _type(
1753+
self,
1754+
interpreter: "Interpreter",
1755+
args: List[Value],
1756+
arg_nodes: List[Expression],
1757+
env: Environment,
1758+
location: SourceLocation,
1759+
) -> Value:
1760+
# TYPE(ANY: obj):STR -> return the runtime type name of the value as STR
1761+
if len(args) != 1:
1762+
raise ASMRuntimeError("TYPE expects one argument", location=location, rewrite_rule="TYPE")
1763+
val = args[0]
1764+
# Return the type string as STR; preserve extension type names if present
1765+
return Value(TYPE_STR, val.type)
1766+
17511767
def _frozen(
17521768
self,
17531769
interpreter: "Interpreter",

lib/image.asmln

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -25,6 +25,7 @@
2525
# BLIT(TNS: src, TNS: dest, INT: x, INT: y, INT: mixalpha = 1):TNS
2626
# GRAYSCALE(TNS: img):TNS # three-channel grayscale (rgb=luminance, alpha preserved)
2727
# BLUR(TNS: img, INT: radius):TNS # gaussian blur with integer radius
28+
# SHOW(TNS: img, STR: window):INT # saves to temp file and opens with default viewer (Windows only)
2829
#
2930
# The returned tensor layout is [row][column][channel] with 1-based indexing
3031
# in user code. Channels are ordered r,g,b,a and values are 0..255.
@@ -95,4 +96,14 @@ FUNC INVERT(TNS: img):TNS{
9596
DEL(b)
9697
DEL(a)
9798
POP(out)
99+
}
100+
101+
FUNC SHOW(TNS: img, STR: window):INT{
102+
# Save the image to the provided path and open it with the system default
103+
# viewer on Windows. `window` is treated as the target file path.
104+
SAVE_PNG(img, window, 0)
105+
# ShellExecuteW(hwnd, operation, file, params, dir, showcmd)
106+
# Use NULL hwnd (0), operation "open", empty params and dir, showcmd=1
107+
win32.WIN_CALL("shell32", "ShellExecuteW", "PSSSSI", "P", 0, "open", window, "", "", 1)
108+
RETURN(0)
98109
}

lib/image.asmxt

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1 +1,2 @@
1-
image.py
1+
image.py
2+
win32.py

test.asmln

Lines changed: 26 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -7,7 +7,7 @@ IMPORT(image)
77
IMPORT(waveforms)
88
IMPORT(stats)
99

10-
FUNC RUN_TESTS():INT{
10+
FUNC STDLIB_TESTS():INT{
1111
PRINT("Running library tests...")
1212

1313
# PRNG reproducibility (LCG)
@@ -247,22 +247,34 @@ FUNC RUN_TESTS():INT{
247247
# SAVE/LOAD roundtrip (BMP/PNG/JPEG)
248248
ASSERT(EQ(image.SAVE_BMP(bmp, out_bmp), "OK"))
249249
TNS: bmp_rt = image.LOAD_BMP(out_bmp)
250-
CL(JOIN('del "', REPLACE(out_bmp,'/','\'), '"'))
250+
IF(EQ(OS(),"win")){
251+
CL(JOIN('del "', REPLACE(out_bmp,'/','\'), '"'))
252+
} ELSE {
253+
CL(JOIN('rm "', out_bmp, '"'))
254+
}
251255
DEL(out_bmp)
252256
ASSERT(EQ(bmp_rt, bmp))
253257
DEL(bmp_rt)
254258

255259
ASSERT(EQ(image.SAVE_PNG(bmp, out_png, 1010), "OK"))
256260
TNS: png_rt = image.LOAD_PNG(out_png)
257-
CL(JOIN('del "', REPLACE(out_png,'/','\'), '"'))
261+
IF(EQ(OS(),"win")){
262+
CL(JOIN('del "', REPLACE(out_png,'/','\'), '"'))
263+
} ELSE {
264+
CL(JOIN('rm "', out_png, '"'))
265+
}
258266
DEL(out_png)
259267
ASSERT(EQ(png_rt, bmp))
260268
DEL(png_rt)
261269
# Use a high JPEG quality (95 -> binary 1011111). JPEG is lossy so
262270
# require dimensions match and per-channel values be close.
263271
ASSERT(EQ(image.SAVE_JPEG(bmp, out_jpeg, 1011111), "OK"))
264272
TNS: jpeg_rt2 = image.LOAD_JPEG(out_jpeg)
265-
CL(JOIN('del "', REPLACE(out_jpeg,'/','\'), '"'))
273+
IF(EQ(OS(),"win")){
274+
CL(JOIN('del "', REPLACE(out_jpeg,'/','\'), '"'))
275+
} ELSE {
276+
CL(JOIN('rm "', out_jpeg, '"'))
277+
}
266278
DEL(out_jpeg)
267279
ASSERT(EQ(image.WIDTH(jpeg_rt2), image.WIDTH(bmp)))
268280
ASSERT(EQ(image.HEIGHT(jpeg_rt2), image.HEIGHT(bmp)))
@@ -304,9 +316,11 @@ FUNC RUN_TESTS():INT{
304316
DEL(blit_out)
305317

306318
PRINT("image library tests passed.")
307-
308-
# Extension tests (requires interpreter to be started with these extensions loaded)
309319
PRINT()
320+
RETURN(0)
321+
}
322+
323+
FUNC STDEXT_TESTS():INT{
310324

311325
PRINT("Running extension tests...")
312326

@@ -332,9 +346,13 @@ FUNC RUN_TESTS():INT{
332346
ASSERT(LTE(volpct, 1100100)) # dec 100
333347
PRINT("wasapi extension tests passed.")
334348

335-
# If we reach here all tests passed; print success code and return
336349
PRINT()
337-
350+
RETURN(0)
351+
}
352+
353+
FUNC RUN_TESTS():INT{
354+
STDLIB_TESTS()
355+
STDEXT_TESTS()
338356
PRINT("All tests passed.")
339357
RETURN(0)
340358
}

0 commit comments

Comments
 (0)