Skip to content

Commit 87a90e7

Browse files
gh-6: Add object serialization operators.
1 parent 0da39c0 commit 87a90e7

File tree

3 files changed

+156
-0
lines changed

3 files changed

+156
-0
lines changed

asm-lang.exe

4.37 KB
Binary file not shown.

docs/SPECIFICATION.html

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -421,6 +421,8 @@
421421
- `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.
422422
- `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).
423423
- `SIGNATURE(SYMBOL: sym):STR` ; returns a textual signature for the identifier `sym`. If `sym` denotes a user-defined function (`FUNC`) the result is formatted in the canonical form used in this specification, e.g. `FUNC name(T1: arg1, T2: arg2 = default):R`. For other bound symbols the result is `TYPE: symbol` (for example `INT: x`). The argument must be a plain identifier.
424+
- `SER(INT|FLT|STR|TNS|MAP: obj):STR` ; return a `STR` containing a compact JSON representation of `obj` that encodes runtime type information. Encoding rules (summary): `INT` values are encoded as binary-digit strings (with a leading `-` for negatives), `FLT` values as decimal text via `repr()`, `STR` values as raw strings, `TNS` values as an object with `shape` (list of `INT` lengths) and a flat list of serialized elements, and `MAP` values as a list of key/value entries where keys are encoded with their native type (`INT`, `FLT`, or `STR`) and values are recursively serialized. `FUNC` and `THR` values are represented by a descriptive form but cannot be reliably reconstructed by `UNSER`.
425+
- `UNSER(STR: obj):INT|FLT|STR|TNS|MAP` ; reverse of `SER`. Given a string produced by `SER`, reconstruct the original runtime value for `INT`, `FLT`, `STR`, `TNS`, and `MAP` values. If the encoded value is a `FUNC` or `THR` (or the input is otherwise invalid), `UNSER` raises a runtime error.
424426
- `COPY(ANY: obj):ANY` ; return a shallow copy of `obj`. For `INT`, `FLT`, `STR`, and `FUNC` this produces a same-typed value wrapper. For `TNS` it returns a newly-allocated tensor with the same shape whose elements reference the original element values (shallow). For `MAP` it returns a new map with the same keys and the same value references (shallow).
425427
- `DEEPCOPY(ANY: obj):ANY` ; return a deep copy of `obj`. Recursively copies container values so that the returned value shares no mutable container objects with the original: tensors are duplicated element-by-element and maps are duplicated with their values deep-copied. Primitive values (`INT`, `FLT`, `STR`, and `FUNC`) are treated as atomic and copied by value.
426428
- `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.

interpreter.py

Lines changed: 154 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -576,6 +576,8 @@ def __init__(self) -> None:
576576
self._register_custom("TFLIP", 2, 2, self._tflip)
577577
self._register_custom("SCAT", 3, 3, self._scatter)
578578
self._register_custom("PARALLEL", 1, None, self._parallel)
579+
self._register_custom("SER", 1, 1, self._serialize)
580+
self._register_custom("UNSER", 1, 1, self._unserialize)
579581

580582
def _register_int_only(self, name: str, arity: int, func: Callable[..., int]) -> None:
581583
if arity == 1:
@@ -2502,6 +2504,158 @@ def _deepcopy(
25022504
raise ASMRuntimeError("DEEPCOPY expects one argument", location=location, rewrite_rule="DEEPCOPY")
25032505
return self._deep_copy_value(interpreter, args[0], location)
25042506

2507+
def _serialize(
2508+
self,
2509+
interpreter: "Interpreter",
2510+
args: List[Value],
2511+
__: List[Expression],
2512+
___: Environment,
2513+
location: SourceLocation,
2514+
) -> Value:
2515+
# SER(ANY: obj):STR -> JSON text representing typed value
2516+
if len(args) != 1:
2517+
raise ASMRuntimeError("SER expects one argument", location=location, rewrite_rule="SER")
2518+
2519+
def ser_val(v: Value):
2520+
t = v.type
2521+
if t == TYPE_INT:
2522+
assert isinstance(v.value, int)
2523+
n = v.value
2524+
if n == 0:
2525+
digits = "0"
2526+
else:
2527+
digits = bin(abs(n))[2:]
2528+
return {"t": "INT", "v": ("-" + digits) if n < 0 else digits}
2529+
if t == TYPE_FLT:
2530+
assert isinstance(v.value, float)
2531+
return {"t": "FLT", "v": repr(v.value)}
2532+
if t == TYPE_STR:
2533+
assert isinstance(v.value, str)
2534+
return {"t": "STR", "v": v.value}
2535+
if t == TYPE_TNS:
2536+
tensor = self._expect_tns(v, "SER", location)
2537+
flat = tensor.data.ravel()
2538+
serialized = [ser_val(item) for item in flat]
2539+
return {"t": "TNS", "shape": list(tensor.shape), "v": serialized}
2540+
if t == TYPE_MAP:
2541+
m = v.value
2542+
assert isinstance(m, Map)
2543+
items = []
2544+
for k, vv in m.data.items():
2545+
# k is (type, value)
2546+
kt, kv = k
2547+
if kt == TYPE_INT:
2548+
if kv == 0:
2549+
krep = "0"
2550+
else:
2551+
krep = ("-" + bin(abs(kv))[2:]) if kv < 0 else bin(kv)[2:]
2552+
key_ser = {"t": "INT", "v": krep}
2553+
elif kt == TYPE_FLT:
2554+
key_ser = {"t": "FLT", "v": repr(kv)}
2555+
elif kt == TYPE_STR:
2556+
key_ser = {"t": "STR", "v": kv}
2557+
else:
2558+
raise ASMRuntimeError("SER: unsupported map key type", location=location, rewrite_rule="SER")
2559+
items.append({"k": key_ser, "v": ser_val(vv)})
2560+
return {"t": "MAP", "v": items}
2561+
# For FUNC and THR and other values, emit a descriptive form.
2562+
return {"t": t, "repr": str(v.value)}
2563+
2564+
try:
2565+
body = ser_val(args[0])
2566+
text = json.dumps(body, separators=(",", ":"))
2567+
return Value(TYPE_STR, text)
2568+
except ASMRuntimeError:
2569+
raise
2570+
except Exception as exc:
2571+
raise ASMRuntimeError(f"SER failed: {exc}", location=location, rewrite_rule="SER")
2572+
2573+
def _unserialize(
2574+
self,
2575+
interpreter: "Interpreter",
2576+
args: List[Value],
2577+
__: List[Expression],
2578+
___: Environment,
2579+
location: SourceLocation,
2580+
) -> Value:
2581+
# UNSER(STR: obj):ANY -> reverse of SER
2582+
if len(args) != 1:
2583+
raise ASMRuntimeError("UNSER expects one argument", location=location, rewrite_rule="UNSER")
2584+
s = args[0]
2585+
text = self._expect_str(s, "UNSER", location)
2586+
2587+
def deser_val(obj) -> Value:
2588+
if not isinstance(obj, dict) or "t" not in obj:
2589+
raise ASMRuntimeError("UNSER: invalid serialized form", location=location, rewrite_rule="UNSER")
2590+
t = obj["t"]
2591+
if t == "INT":
2592+
raw = obj.get("v", "0")
2593+
neg = False
2594+
if isinstance(raw, str) and raw.startswith("-"):
2595+
neg = True
2596+
raw = raw[1:]
2597+
if raw == "":
2598+
ival = 0
2599+
else:
2600+
ival = int(raw, 2)
2601+
if neg:
2602+
ival = -ival
2603+
return Value(TYPE_INT, ival)
2604+
if t == "FLT":
2605+
raw = obj.get("v", "0.0")
2606+
try:
2607+
f = float(raw)
2608+
except Exception:
2609+
raise ASMRuntimeError("UNSER: invalid FLT literal", location=location, rewrite_rule="UNSER")
2610+
return Value(TYPE_FLT, f)
2611+
if t == "STR":
2612+
return Value(TYPE_STR, obj.get("v", ""))
2613+
if t == "TNS":
2614+
shape = obj.get("shape")
2615+
flat = obj.get("v", [])
2616+
if not isinstance(shape, list):
2617+
raise ASMRuntimeError("UNSER: invalid TNS shape", location=location, rewrite_rule="UNSER")
2618+
vals = [deser_val(x) for x in flat]
2619+
arr = np.array(vals, dtype=object)
2620+
return Value(TYPE_TNS, Tensor(shape=list(shape), data=arr))
2621+
if t == "MAP":
2622+
items = obj.get("v", [])
2623+
if not isinstance(items, list):
2624+
raise ASMRuntimeError("UNSER: invalid MAP body", location=location, rewrite_rule="UNSER")
2625+
m = Map()
2626+
for pair in items:
2627+
key_obj = pair.get("k")
2628+
val_obj = pair.get("v")
2629+
if not isinstance(key_obj, dict) or "t" not in key_obj:
2630+
raise ASMRuntimeError("UNSER: invalid MAP key", location=location, rewrite_rule="UNSER")
2631+
kt = key_obj["t"]
2632+
if kt == "INT":
2633+
raw = key_obj.get("v", "0")
2634+
neg = False
2635+
if isinstance(raw, str) and raw.startswith("-"):
2636+
neg = True
2637+
raw = raw[1:]
2638+
kval = int(raw, 2) if raw != "" else 0
2639+
if neg:
2640+
kval = -kval
2641+
elif kt == "FLT":
2642+
kval = float(key_obj.get("v", 0.0))
2643+
elif kt == "STR":
2644+
kval = key_obj.get("v", "")
2645+
else:
2646+
raise ASMRuntimeError("UNSER: unsupported MAP key type", location=location, rewrite_rule="UNSER")
2647+
vval = deser_val(val_obj)
2648+
m.data[(kt, kval)] = vval
2649+
return Value(TYPE_MAP, m)
2650+
# FUNC / THR cannot be reconstructed reliably
2651+
raise ASMRuntimeError(f"UNSER: cannot reconstruct type {t}", location=location, rewrite_rule="UNSER")
2652+
2653+
try:
2654+
obj = json.loads(text)
2655+
except Exception:
2656+
raise ASMRuntimeError("UNSER: invalid JSON", location=location, rewrite_rule="UNSER")
2657+
return deser_val(obj)
2658+
25052659
def _frozen(
25062660
self,
25072661
interpreter: "Interpreter",

0 commit comments

Comments
 (0)