tengwar 0.3.1__py3-none-any.whl

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
tengwar/interpreter.py ADDED
@@ -0,0 +1,1845 @@
1
+ """
2
+ TENGWAR Interpreter
3
+
4
+ Tree-walking evaluator for the TENGWAR AST.
5
+ Supports closures, recursion, pattern matching, mutable state,
6
+ and parallel execution.
7
+ """
8
+ import sys
9
+ import hashlib
10
+ import concurrent.futures
11
+ import json
12
+ from typing import Any, Dict, List, Optional
13
+ from .ast_nodes import *
14
+ from .errors import RuntimeError_, ProofError
15
+
16
+ # Allow deep recursion for recursive Tengwar programs
17
+ sys.setrecursionlimit(50000)
18
+
19
+
20
+ class TengwarValue:
21
+ """Base class for all TENGWAR runtime values"""
22
+ pass
23
+
24
+
25
+ class TengwarInt(TengwarValue):
26
+ def __init__(self, value: int):
27
+ self.value = value
28
+ def __repr__(self): return str(self.value)
29
+ def __eq__(self, other): return isinstance(other, TengwarInt) and self.value == other.value
30
+ def __hash__(self): return hash(self.value)
31
+
32
+
33
+ class TengwarFloat(TengwarValue):
34
+ def __init__(self, value: float):
35
+ self.value = value
36
+ def __repr__(self): return str(self.value)
37
+ def __eq__(self, other): return isinstance(other, TengwarFloat) and self.value == other.value
38
+ def __hash__(self): return hash(self.value)
39
+
40
+
41
+ class TengwarStr(TengwarValue):
42
+ def __init__(self, value: str):
43
+ self.value = value
44
+ def __repr__(self): return f'"{self.value}"'
45
+ def __eq__(self, other): return isinstance(other, TengwarStr) and self.value == other.value
46
+ def __hash__(self): return hash(self.value)
47
+
48
+
49
+ class TengwarBool(TengwarValue):
50
+ def __init__(self, value: bool):
51
+ self.value = value
52
+ def __repr__(self): return 'true' if self.value else 'false'
53
+ def __eq__(self, other): return isinstance(other, TengwarBool) and self.value == other.value
54
+ def __hash__(self): return hash(self.value)
55
+
56
+
57
+ class TengwarUnit(TengwarValue):
58
+ def __repr__(self): return '∅'
59
+ def __eq__(self, other): return isinstance(other, TengwarUnit)
60
+ def __hash__(self): return hash(None)
61
+
62
+
63
+ class TengwarTuple(TengwarValue):
64
+ def __init__(self, elements: list):
65
+ self.elements = elements
66
+ def __repr__(self): return '⟨' + ' '.join(repr(e) for e in self.elements) + '⟩'
67
+ def __eq__(self, other): return isinstance(other, TengwarTuple) and self.elements == other.elements
68
+
69
+
70
+ class TengwarVector(TengwarValue):
71
+ def __init__(self, elements: list):
72
+ self.elements = list(elements)
73
+ def __repr__(self): return '⟦' + ' '.join(repr(e) for e in self.elements) + '⟧'
74
+ def __eq__(self, other): return isinstance(other, TengwarVector) and self.elements == other.elements
75
+
76
+
77
+ class TengwarClosure(TengwarValue):
78
+ def __init__(self, params: list, body, env: 'Environment', name: str = ""):
79
+ self.params = params
80
+ self.body = body
81
+ self.env = env
82
+ self.name = name
83
+ def __repr__(self):
84
+ params = ' '.join(p for p in self.params)
85
+ return f'(λ {params} ...)'
86
+
87
+
88
+ class TengwarBuiltin(TengwarValue):
89
+ def __init__(self, name: str, func):
90
+ self.name = name
91
+ self.func = func
92
+ def __repr__(self): return f'<builtin:{self.name}>'
93
+
94
+
95
+ class TengwarMutableCell(TengwarValue):
96
+ def __init__(self, value: TengwarValue):
97
+ self.value = value
98
+ def __repr__(self): return f'(μ {repr(self.value)})'
99
+
100
+
101
+ class TengwarModule(TengwarValue):
102
+ def __init__(self, name: str, bindings: Dict[str, TengwarValue], addr: str = ""):
103
+ self.name = name
104
+ self.bindings = bindings
105
+ self.addr = addr
106
+ def __repr__(self): return f'(□ {self.name or self.addr})'
107
+
108
+
109
+ class TengwarDict(TengwarValue):
110
+ """Hash map / dictionary"""
111
+ def __init__(self, data: Dict = None):
112
+ self.data = data or {}
113
+ def __repr__(self):
114
+ pairs = ' '.join(f'{repr(k)} {repr(v)}' for k, v in self.data.items())
115
+ return f'%{{{pairs}}}'
116
+ def __eq__(self, other):
117
+ return isinstance(other, TengwarDict) and self.data == other.data
118
+
119
+
120
+ class TengwarPyObject(TengwarValue):
121
+ """Wrapper for Python objects for interop"""
122
+ def __init__(self, obj, name: str = ""):
123
+ self.obj = obj
124
+ self.name = name or type(obj).__name__
125
+ def __repr__(self): return f'<py:{self.name}>'
126
+ def __eq__(self, other):
127
+ return isinstance(other, TengwarPyObject) and self.obj == other.obj
128
+
129
+
130
+ class TailCall:
131
+ """Sentinel for tail call optimization trampolining"""
132
+ __slots__ = ('func', 'args')
133
+ def __init__(self, func, args):
134
+ self.func = func
135
+ self.args = args
136
+
137
+
138
+ class Environment:
139
+ """Lexically scoped environment with content-addressable store"""
140
+
141
+ def __init__(self, parent: Optional['Environment'] = None):
142
+ self.bindings: Dict[str, TengwarValue] = {}
143
+ self.parent = parent
144
+ self.content_store: Dict[str, TengwarValue] = {} # @addr → value
145
+
146
+ def get(self, name: str) -> TengwarValue:
147
+ if name in self.bindings:
148
+ return self.bindings[name]
149
+ if self.parent:
150
+ return self.parent.get(name)
151
+ raise RuntimeError_(f"Unbound identifier: {name}")
152
+
153
+ def set(self, name: str, value: TengwarValue):
154
+ self.bindings[name] = value
155
+
156
+ def store(self, addr: str, value: TengwarValue):
157
+ """Store a value at a content address"""
158
+ self.content_store[addr] = value
159
+ # Also propagate to root
160
+ env = self
161
+ while env.parent:
162
+ env = env.parent
163
+ if env is not self:
164
+ env.content_store[addr] = value
165
+
166
+ def lookup_addr(self, addr: str) -> TengwarValue:
167
+ """Look up a content-addressed value"""
168
+ if addr in self.content_store:
169
+ return self.content_store[addr]
170
+ if self.parent:
171
+ return self.parent.lookup_addr(addr)
172
+ raise RuntimeError_(f"Unknown address: {addr}")
173
+
174
+ def child(self) -> 'Environment':
175
+ return Environment(parent=self)
176
+
177
+
178
+ def content_hash(value: str) -> str:
179
+ """Generate a short content hash"""
180
+ return hashlib.sha256(value.encode()).hexdigest()[:8]
181
+
182
+
183
+ class Interpreter:
184
+ def __init__(self, sandbox=False):
185
+ self.global_env = Environment()
186
+ self.trace = False
187
+ # Sandbox controls
188
+ self.sandbox_allow_io = not sandbox
189
+ self.sandbox_allow_net = not sandbox
190
+ self.sandbox_allow_py = not sandbox
191
+ self.max_steps = 1000000 if sandbox else 0 # 0 = unlimited
192
+ self.step_count = 0
193
+ self._setup_stdlib()
194
+
195
+ def _setup_stdlib(self):
196
+ """Install standard library functions"""
197
+ env = self.global_env
198
+
199
+ # I/O
200
+ env.set('print', TengwarBuiltin('print', self._builtin_print))
201
+ env.set('println', TengwarBuiltin('println', self._builtin_println))
202
+ env.set('str', TengwarBuiltin('str', self._builtin_str))
203
+ env.set('repr', TengwarBuiltin('repr', self._builtin_repr))
204
+ env.set('input', TengwarBuiltin('input', self._builtin_input))
205
+
206
+ # Type checks
207
+ env.set('int?', TengwarBuiltin('int?', lambda args: TengwarBool(isinstance(args[0], TengwarInt))))
208
+ env.set('float?', TengwarBuiltin('float?', lambda args: TengwarBool(isinstance(args[0], TengwarFloat))))
209
+ env.set('str?', TengwarBuiltin('str?', lambda args: TengwarBool(isinstance(args[0], TengwarStr))))
210
+ env.set('bool?', TengwarBuiltin('bool?', lambda args: TengwarBool(isinstance(args[0], TengwarBool))))
211
+ env.set('unit?', TengwarBuiltin('unit?', lambda args: TengwarBool(isinstance(args[0], TengwarUnit))))
212
+ env.set('vec?', TengwarBuiltin('vec?', lambda args: TengwarBool(isinstance(args[0], TengwarVector))))
213
+ env.set('tuple?', TengwarBuiltin('tuple?', lambda args: TengwarBool(isinstance(args[0], TengwarTuple))))
214
+ env.set('fn?', TengwarBuiltin('fn?', lambda args: TengwarBool(isinstance(args[0], (TengwarClosure, TengwarBuiltin)))))
215
+
216
+ # Type conversions
217
+ env.set('to-int', TengwarBuiltin('to-int', self._builtin_to_int))
218
+ env.set('to-float', TengwarBuiltin('to-float', self._builtin_to_float))
219
+ env.set('to-str', TengwarBuiltin('to-str', self._builtin_to_str))
220
+
221
+ # Math
222
+ env.set('abs', TengwarBuiltin('abs', lambda args: self._num_op(args[0], abs)))
223
+ env.set('min', TengwarBuiltin('min', lambda args: self._num_cmp(args, min)))
224
+ env.set('max', TengwarBuiltin('max', lambda args: self._num_cmp(args, max)))
225
+ env.set('pow', TengwarBuiltin('pow', self._builtin_pow))
226
+ env.set('sqrt', TengwarBuiltin('sqrt', self._builtin_sqrt))
227
+ env.set('mod', TengwarBuiltin('mod', self._builtin_mod))
228
+
229
+ # String ops
230
+ env.set('len', TengwarBuiltin('len', self._builtin_len))
231
+ env.set('concat', TengwarBuiltin('concat', self._builtin_concat))
232
+ env.set('substr', TengwarBuiltin('substr', self._builtin_substr))
233
+ env.set('split', TengwarBuiltin('split', self._builtin_split))
234
+ env.set('join', TengwarBuiltin('join', self._builtin_join))
235
+ env.set('upper', TengwarBuiltin('upper', self._builtin_upper))
236
+ env.set('lower', TengwarBuiltin('lower', self._builtin_lower))
237
+ env.set('trim', TengwarBuiltin('trim', self._builtin_trim))
238
+ env.set('contains', TengwarBuiltin('contains', self._builtin_contains))
239
+ env.set('replace', TengwarBuiltin('replace', self._builtin_replace))
240
+
241
+ # Vector/sequence ops
242
+ env.set('vec', TengwarBuiltin('vec', lambda args: TengwarVector(list(args))))
243
+ env.set('push', TengwarBuiltin('push', self._builtin_push))
244
+ env.set('pop', TengwarBuiltin('pop', self._builtin_pop))
245
+ env.set('get', TengwarBuiltin('get', self._builtin_get))
246
+ env.set('set-at', TengwarBuiltin('set-at', self._builtin_set_at))
247
+ env.set('slice', TengwarBuiltin('slice', self._builtin_slice))
248
+ env.set('map', TengwarBuiltin('map', self._builtin_map))
249
+ env.set('filter', TengwarBuiltin('filter', self._builtin_filter))
250
+ env.set('reduce', TengwarBuiltin('reduce', self._builtin_reduce))
251
+ env.set('sum', TengwarBuiltin('sum', self._builtin_sum))
252
+ env.set('product', TengwarBuiltin('product', self._builtin_product))
253
+ env.set('count-if', TengwarBuiltin('count-if', self._builtin_count_if))
254
+ env.set('average', TengwarBuiltin('average', self._builtin_average))
255
+ env.set('range', TengwarBuiltin('range', self._builtin_range))
256
+ env.set('reverse', TengwarBuiltin('reverse', self._builtin_reverse))
257
+ env.set('sort', TengwarBuiltin('sort', self._builtin_sort))
258
+ env.set('flatten', TengwarBuiltin('flatten', self._builtin_flatten))
259
+ env.set('zip', TengwarBuiltin('zip', self._builtin_zip))
260
+ env.set('enumerate', TengwarBuiltin('enumerate', self._builtin_enumerate))
261
+ env.set('head', TengwarBuiltin('head', self._builtin_head))
262
+ env.set('tail', TengwarBuiltin('tail', self._builtin_tail))
263
+ env.set('empty?', TengwarBuiltin('empty?', self._builtin_empty))
264
+
265
+ # Tuple ops
266
+ env.set('tuple', TengwarBuiltin('tuple', lambda args: TengwarTuple(list(args))))
267
+ env.set('fst', TengwarBuiltin('fst', lambda args: self._expect_tuple(args[0]).elements[0]))
268
+ env.set('snd', TengwarBuiltin('snd', lambda args: self._expect_tuple(args[0]).elements[1]))
269
+ env.set('nth', TengwarBuiltin('nth', self._builtin_nth))
270
+
271
+ # Mutable cell ops
272
+ env.set('deref', TengwarBuiltin('deref', self._builtin_deref))
273
+ env.set('set!', TengwarBuiltin('set!', self._builtin_set_mut))
274
+
275
+ # Hash / content addressing
276
+ env.set('hash', TengwarBuiltin('hash', self._builtin_hash))
277
+
278
+ # Functional
279
+ env.set('compose', TengwarBuiltin('compose', self._builtin_compose))
280
+ env.set('apply', TengwarBuiltin('apply', self._builtin_apply))
281
+ env.set('identity', TengwarBuiltin('identity', lambda args: args[0]))
282
+ env.set('sum', TengwarBuiltin('sum', self._builtin_sum))
283
+ env.set('product', TengwarBuiltin('product', self._builtin_product))
284
+ env.set('count', TengwarBuiltin('count', self._builtin_count))
285
+ env.set('any', TengwarBuiltin('any', self._builtin_any))
286
+ env.set('all', TengwarBuiltin('all', self._builtin_all))
287
+
288
+ # Combo builtins — token-efficient patterns
289
+ env.set('sum-by', TengwarBuiltin('sum-by', self._builtin_sum_by))
290
+ env.set('min-by', TengwarBuiltin('min-by', self._builtin_min_by))
291
+ env.set('max-by', TengwarBuiltin('max-by', self._builtin_max_by))
292
+ env.set('filter-map', TengwarBuiltin('filter-map', self._builtin_filter_map))
293
+ env.set('flat', TengwarBuiltin('flat', self._builtin_flat))
294
+ env.set('zip', TengwarBuiltin('zip', self._builtin_zip))
295
+ env.set('enumerate', TengwarBuiltin('enumerate', self._builtin_enumerate))
296
+ env.set('range-map', TengwarBuiltin('range-map', self._builtin_range_map))
297
+
298
+ # Error handling
299
+ env.set('err', TengwarBuiltin('err', self._builtin_err))
300
+ env.set('try', TengwarBuiltin('try', self._builtin_try))
301
+
302
+ # Operators as first-class functions
303
+ env.set('+', TengwarBuiltin('+', lambda args: self._eval_binop('+', args[0], args[1])))
304
+ env.set('-', TengwarBuiltin('-', lambda args: self._eval_binop('-', args[0], args[1]) if len(args) == 2 else self._eval_unop('-', args[0])))
305
+ env.set('*', TengwarBuiltin('*', lambda args: self._eval_binop('*', args[0], args[1])))
306
+ env.set('/', TengwarBuiltin('/', lambda args: self._eval_binop('/', args[0], args[1])))
307
+ env.set('%', TengwarBuiltin('%', lambda args: self._eval_binop('%', args[0], args[1])))
308
+ env.set('=', TengwarBuiltin('=', lambda args: self._eval_binop('=', args[0], args[1])))
309
+ env.set('!=', TengwarBuiltin('!=', lambda args: self._eval_binop('!=', args[0], args[1])))
310
+ env.set('<', TengwarBuiltin('<', lambda args: self._eval_binop('<', args[0], args[1])))
311
+ env.set('>', TengwarBuiltin('>', lambda args: self._eval_binop('>', args[0], args[1])))
312
+ env.set('<=', TengwarBuiltin('<=', lambda args: self._eval_binop('<=', args[0], args[1])))
313
+ env.set('>=', TengwarBuiltin('>=', lambda args: self._eval_binop('>=', args[0], args[1])))
314
+ env.set('&', TengwarBuiltin('&', lambda args: self._eval_binop('&', args[0], args[1])))
315
+ env.set('|', TengwarBuiltin('|', lambda args: self._eval_binop('|', args[0], args[1])))
316
+ env.set('!', TengwarBuiltin('!', lambda args: self._eval_unop('!', args[0])))
317
+
318
+ # === DICT / HASHMAP ===
319
+ env.set('dict', TengwarBuiltin('dict', self._builtin_dict))
320
+ env.set('dict-get', TengwarBuiltin('dict-get', self._builtin_dict_get))
321
+ env.set('dict-set', TengwarBuiltin('dict-set', self._builtin_dict_set))
322
+ env.set('dict-del', TengwarBuiltin('dict-del', self._builtin_dict_del))
323
+ env.set('dict-keys', TengwarBuiltin('dict-keys', self._builtin_dict_keys))
324
+ env.set('dict-vals', TengwarBuiltin('dict-vals', self._builtin_dict_vals))
325
+ env.set('dict-pairs', TengwarBuiltin('dict-pairs', self._builtin_dict_pairs))
326
+ env.set('dict-has?', TengwarBuiltin('dict-has?', self._builtin_dict_has))
327
+ env.set('dict-merge', TengwarBuiltin('dict-merge', self._builtin_dict_merge))
328
+ env.set('dict-size', TengwarBuiltin('dict-size', self._builtin_dict_size))
329
+
330
+ # === EXTENDED FUNCTIONAL ===
331
+ env.set('flat-map', TengwarBuiltin('flat-map', self._builtin_flat_map))
332
+ env.set('find', TengwarBuiltin('find', self._builtin_find))
333
+ env.set('index-of', TengwarBuiltin('index-of', self._builtin_index_of))
334
+ env.set('take', TengwarBuiltin('take', self._builtin_take))
335
+ env.set('drop', TengwarBuiltin('drop', self._builtin_drop))
336
+ env.set('take-while', TengwarBuiltin('take-while', self._builtin_take_while))
337
+ env.set('drop-while', TengwarBuiltin('drop-while', self._builtin_drop_while))
338
+ env.set('zip-with', TengwarBuiltin('zip-with', self._builtin_zip_with))
339
+ env.set('group-by', TengwarBuiltin('group-by', self._builtin_group_by))
340
+ env.set('unique', TengwarBuiltin('unique', self._builtin_unique))
341
+ env.set('frequencies', TengwarBuiltin('frequencies', self._builtin_frequencies))
342
+ env.set('partition', TengwarBuiltin('partition', self._builtin_partition))
343
+ env.set('scan', TengwarBuiltin('scan', self._builtin_scan))
344
+ env.set('chunks', TengwarBuiltin('chunks', self._builtin_chunks))
345
+ env.set('interleave', TengwarBuiltin('interleave', self._builtin_interleave))
346
+ env.set('repeat', TengwarBuiltin('repeat', self._builtin_repeat))
347
+ env.set('iterate', TengwarBuiltin('iterate', self._builtin_iterate))
348
+ env.set('juxt', TengwarBuiltin('juxt', self._builtin_juxt))
349
+ env.set('min-by', TengwarBuiltin('min-by', self._builtin_min_by))
350
+ env.set('max-by', TengwarBuiltin('max-by', self._builtin_max_by))
351
+ env.set('sort-by', TengwarBuiltin('sort-by', self._builtin_sort_by))
352
+ env.set('for-each', TengwarBuiltin('for-each', self._builtin_for_each))
353
+
354
+ # === STRING EXTENDED ===
355
+ env.set('fmt', TengwarBuiltin('fmt', self._builtin_fmt))
356
+ env.set('starts-with?', TengwarBuiltin('starts-with?', self._builtin_starts_with))
357
+ env.set('ends-with?', TengwarBuiltin('ends-with?', self._builtin_ends_with))
358
+ env.set('chars', TengwarBuiltin('chars', self._builtin_chars))
359
+ env.set('char-at', TengwarBuiltin('char-at', self._builtin_char_at))
360
+ env.set('pad-left', TengwarBuiltin('pad-left', self._builtin_pad_left))
361
+ env.set('pad-right', TengwarBuiltin('pad-right', self._builtin_pad_right))
362
+
363
+ # === MATH EXTENDED ===
364
+ env.set('floor', TengwarBuiltin('floor', lambda args: TengwarInt(int(self._num_val(args[0]) // 1))))
365
+ env.set('ceil', TengwarBuiltin('ceil', lambda args: TengwarInt(int(-(-self._num_val(args[0]) // 1)))))
366
+ env.set('round', TengwarBuiltin('round', lambda args: TengwarInt(round(self._num_val(args[0])))))
367
+ env.set('abs', TengwarBuiltin('abs', lambda args: type(args[0])(abs(self._num_val(args[0])))))
368
+ env.set('min', TengwarBuiltin('min', self._builtin_min))
369
+ env.set('max', TengwarBuiltin('max', self._builtin_max))
370
+ env.set('clamp', TengwarBuiltin('clamp', self._builtin_clamp))
371
+ env.set('pi', TengwarFloat(3.141592653589793))
372
+ env.set('e', TengwarFloat(2.718281828459045))
373
+ env.set('inf', TengwarFloat(float('inf')))
374
+ env.set('nan', TengwarFloat(float('nan')))
375
+
376
+ # === TYPE CHECKING ===
377
+ env.set('int?', TengwarBuiltin('int?', lambda args: TengwarBool(isinstance(args[0], TengwarInt))))
378
+ env.set('float?', TengwarBuiltin('float?', lambda args: TengwarBool(isinstance(args[0], TengwarFloat))))
379
+ env.set('num?', TengwarBuiltin('num?', lambda args: TengwarBool(isinstance(args[0], (TengwarInt, TengwarFloat)))))
380
+ env.set('str?', TengwarBuiltin('str?', lambda args: TengwarBool(isinstance(args[0], TengwarStr))))
381
+ env.set('bool?', TengwarBuiltin('bool?', lambda args: TengwarBool(isinstance(args[0], TengwarBool))))
382
+ env.set('vec?', TengwarBuiltin('vec?', lambda args: TengwarBool(isinstance(args[0], TengwarVector))))
383
+ env.set('tuple?', TengwarBuiltin('tuple?', lambda args: TengwarBool(isinstance(args[0], TengwarTuple))))
384
+ env.set('dict?', TengwarBuiltin('dict?', lambda args: TengwarBool(isinstance(args[0], TengwarDict))))
385
+ env.set('fn?', TengwarBuiltin('fn?', lambda args: TengwarBool(isinstance(args[0], (TengwarClosure, TengwarBuiltin)))))
386
+ env.set('nil?', TengwarBuiltin('nil?', lambda args: TengwarBool(isinstance(args[0], TengwarUnit))))
387
+ env.set('py?', TengwarBuiltin('py?', lambda args: TengwarBool(isinstance(args[0], TengwarPyObject))))
388
+ env.set('type', TengwarBuiltin('type', self._builtin_type))
389
+
390
+ # === NUMERIC PREDICATES ===
391
+ env.set('zero?', TengwarBuiltin('zero?', lambda args: TengwarBool(self._num_val(args[0]) == 0)))
392
+ env.set('pos?', TengwarBuiltin('pos?', lambda args: TengwarBool(self._num_val(args[0]) > 0)))
393
+ env.set('neg?', TengwarBuiltin('neg?', lambda args: TengwarBool(self._num_val(args[0]) < 0)))
394
+ env.set('even?', TengwarBuiltin('even?', lambda args: TengwarBool(self._num_val(args[0]) % 2 == 0)))
395
+ env.set('odd?', TengwarBuiltin('odd?', lambda args: TengwarBool(self._num_val(args[0]) % 2 != 0)))
396
+ env.set('divides?', TengwarBuiltin('divides?', lambda args: TengwarBool(self._num_val(args[0]) % self._num_val(args[1]) == 0)))
397
+ env.set('between?', TengwarBuiltin('between?', lambda args: TengwarBool(self._num_val(args[1]) <= self._num_val(args[0]) <= self._num_val(args[2]))))
398
+ env.set('empty?', TengwarBuiltin('empty?', lambda args: TengwarBool(
399
+ (isinstance(args[0], TengwarVector) and len(args[0].elements) == 0) or
400
+ (isinstance(args[0], TengwarStr) and args[0].value == "") or
401
+ (isinstance(args[0], TengwarDict) and len(args[0].entries) == 0)
402
+ )))
403
+
404
+ # === COMPACT MATH (token-efficient) ===
405
+ env.set('inc', TengwarBuiltin('inc', lambda args: TengwarInt(self._num_val(args[0]) + 1) if isinstance(args[0], TengwarInt) else TengwarFloat(self._num_val(args[0]) + 1)))
406
+ env.set('dec', TengwarBuiltin('dec', lambda args: TengwarInt(self._num_val(args[0]) - 1) if isinstance(args[0], TengwarInt) else TengwarFloat(self._num_val(args[0]) - 1)))
407
+ env.set('sqr', TengwarBuiltin('sqr', lambda args: TengwarInt(self._num_val(args[0]) ** 2) if isinstance(args[0], TengwarInt) else TengwarFloat(self._num_val(args[0]) ** 2)))
408
+ env.set('cube', TengwarBuiltin('cube', lambda args: TengwarInt(self._num_val(args[0]) ** 3) if isinstance(args[0], TengwarInt) else TengwarFloat(self._num_val(args[0]) ** 3)))
409
+ env.set('pow', TengwarBuiltin('pow', lambda args: TengwarInt(int(self._num_val(args[0]) ** self._num_val(args[1]))) if isinstance(args[0], TengwarInt) and isinstance(args[1], TengwarInt) else TengwarFloat(self._num_val(args[0]) ** self._num_val(args[1]))))
410
+ env.set('sqrt', TengwarBuiltin('sqrt', lambda args: TengwarFloat(self._num_val(args[0]) ** 0.5)))
411
+ env.set('double', TengwarBuiltin('double', lambda args: TengwarInt(self._num_val(args[0]) * 2) if isinstance(args[0], TengwarInt) else TengwarFloat(self._num_val(args[0]) * 2)))
412
+ env.set('half', TengwarBuiltin('half', lambda args: TengwarFloat(self._num_val(args[0]) / 2)))
413
+ env.set('neg', TengwarBuiltin('neg', lambda args: TengwarInt(-self._num_val(args[0])) if isinstance(args[0], TengwarInt) else TengwarFloat(-self._num_val(args[0]))))
414
+ env.set('id', TengwarBuiltin('id', lambda args: args[0]))
415
+
416
+ # Memoization
417
+ def _memo(args):
418
+ fn = args[0]
419
+ cache = {}
420
+ def memoized(call_args):
421
+ # Create hashable key from args
422
+ key = tuple(self._display(a) for a in call_args)
423
+ if key not in cache:
424
+ cache[key] = self._call_function(fn, call_args)
425
+ return cache[key]
426
+ return TengwarBuiltin(f'memo({getattr(fn, "name", "fn")})', memoized)
427
+ env.set('memo', TengwarBuiltin('memo', _memo))
428
+
429
+ # === JSON ===
430
+ env.set('json-parse', TengwarBuiltin('json-parse', self._builtin_json_parse))
431
+ env.set('json-encode', TengwarBuiltin('json-encode', self._builtin_json_encode))
432
+
433
+ # === FILE I/O ===
434
+ env.set('read-file', TengwarBuiltin('read-file', self._builtin_read_file))
435
+ env.set('write-file', TengwarBuiltin('write-file', self._builtin_write_file))
436
+ env.set('append-file', TengwarBuiltin('append-file', self._builtin_append_file))
437
+ env.set('file-exists?', TengwarBuiltin('file-exists?', self._builtin_file_exists))
438
+
439
+ # === HTTP ===
440
+ env.set('http-get', TengwarBuiltin('http-get', self._builtin_http_get))
441
+ env.set('http-post', TengwarBuiltin('http-post', self._builtin_http_post))
442
+
443
+ # === PYTHON INTEROP ===
444
+ env.set('py-call', TengwarBuiltin('py-call', self._builtin_py_call))
445
+ env.set('py-attr', TengwarBuiltin('py-attr', self._builtin_py_attr))
446
+ env.set('py-eval', TengwarBuiltin('py-eval', self._builtin_py_eval))
447
+ env.set('to-py', TengwarBuiltin('to-py', self._builtin_to_py))
448
+ env.set('to-tw', TengwarBuiltin('to-tw', self._builtin_to_tw))
449
+
450
+ # === SYSTEM ===
451
+ env.set('time-ms', TengwarBuiltin('time-ms', self._builtin_time_ms))
452
+ env.set('sleep', TengwarBuiltin('sleep', self._builtin_sleep))
453
+ env.set('env-get', TengwarBuiltin('env-get', self._builtin_env_get))
454
+ env.set('rand', TengwarBuiltin('rand', self._builtin_rand))
455
+ env.set('rand-int', TengwarBuiltin('rand-int', self._builtin_rand_int))
456
+ env.set('uuid', TengwarBuiltin('uuid', self._builtin_uuid))
457
+ env.set('exit', TengwarBuiltin('exit', lambda args: exit(int(self._num_val(args[0])) if args else 0)))
458
+
459
+ # === CONVERSION ===
460
+ env.set('vec->tuple', TengwarBuiltin('vec->tuple', lambda args: TengwarTuple(args[0].elements) if isinstance(args[0], TengwarVector) else args[0]))
461
+ env.set('tuple->vec', TengwarBuiltin('tuple->vec', lambda args: TengwarVector(args[0].elements) if isinstance(args[0], TengwarTuple) else args[0]))
462
+ env.set('dict-to-vec', TengwarBuiltin('dict-to-vec', self._builtin_dict_to_vec))
463
+ env.set('vec-to-dict', TengwarBuiltin('vec-to-dict', self._builtin_vec_to_dict))
464
+
465
+ # === BUILTIN IMPLEMENTATIONS ===
466
+
467
+ def _builtin_print(self, args):
468
+ parts = [self._display(a) for a in args]
469
+ print(' '.join(parts), end='')
470
+ return TengwarUnit()
471
+
472
+ def _builtin_println(self, args):
473
+ parts = [self._display(a) for a in args]
474
+ print(' '.join(parts))
475
+ return TengwarUnit()
476
+
477
+ def _builtin_str(self, args):
478
+ return TengwarStr(self._display(args[0]))
479
+
480
+ def _builtin_repr(self, args):
481
+ return TengwarStr(repr(args[0]))
482
+
483
+ def _builtin_input(self, args):
484
+ prompt = self._display(args[0]) if args else ""
485
+ return TengwarStr(input(prompt))
486
+
487
+ def _builtin_to_int(self, args):
488
+ v = args[0]
489
+ if isinstance(v, TengwarInt): return v
490
+ if isinstance(v, TengwarFloat): return TengwarInt(int(v.value))
491
+ if isinstance(v, TengwarStr): return TengwarInt(int(v.value))
492
+ if isinstance(v, TengwarBool): return TengwarInt(1 if v.value else 0)
493
+ raise RuntimeError_(f"Cannot convert {type(v).__name__} to int")
494
+
495
+ def _builtin_to_float(self, args):
496
+ v = args[0]
497
+ if isinstance(v, TengwarFloat): return v
498
+ if isinstance(v, TengwarInt): return TengwarFloat(float(v.value))
499
+ if isinstance(v, TengwarStr): return TengwarFloat(float(v.value))
500
+ raise RuntimeError_(f"Cannot convert {type(v).__name__} to float")
501
+
502
+ def _builtin_to_str(self, args):
503
+ return TengwarStr(self._display(args[0]))
504
+
505
+ def _num_op(self, val, op):
506
+ if isinstance(val, TengwarInt): return TengwarInt(op(val.value))
507
+ if isinstance(val, TengwarFloat): return TengwarFloat(op(val.value))
508
+ raise RuntimeError_(f"Expected number, got {type(val).__name__}")
509
+
510
+ def _num_cmp(self, args, op):
511
+ nums = [a.value for a in args]
512
+ result = op(nums)
513
+ if isinstance(args[0], TengwarFloat) or isinstance(args[1] if len(args) > 1 else args[0], TengwarFloat):
514
+ return TengwarFloat(result)
515
+ return TengwarInt(result)
516
+
517
+ def _builtin_pow(self, args):
518
+ base, exp = args[0].value, args[1].value
519
+ if isinstance(args[0], TengwarFloat) or isinstance(args[1], TengwarFloat):
520
+ return TengwarFloat(base ** exp)
521
+ return TengwarInt(base ** exp)
522
+
523
+ def _builtin_sqrt(self, args):
524
+ import math
525
+ return TengwarFloat(math.sqrt(args[0].value))
526
+
527
+ def _builtin_mod(self, args):
528
+ return TengwarInt(args[0].value % args[1].value)
529
+
530
+ def _builtin_len(self, args):
531
+ v = args[0]
532
+ if isinstance(v, TengwarStr): return TengwarInt(len(v.value))
533
+ if isinstance(v, TengwarVector): return TengwarInt(len(v.elements))
534
+ if isinstance(v, TengwarTuple): return TengwarInt(len(v.elements))
535
+ raise RuntimeError_(f"Cannot get length of {type(v).__name__}")
536
+
537
+ def _builtin_concat(self, args):
538
+ if isinstance(args[0], TengwarStr):
539
+ return TengwarStr(''.join(a.value for a in args))
540
+ if isinstance(args[0], TengwarVector):
541
+ result = []
542
+ for a in args:
543
+ result.extend(a.elements)
544
+ return TengwarVector(result)
545
+ raise RuntimeError_(f"Cannot concat {type(args[0]).__name__}")
546
+
547
+ def _builtin_substr(self, args):
548
+ s, start = args[0].value, args[1].value
549
+ end = args[2].value if len(args) > 2 else len(s)
550
+ return TengwarStr(s[start:end])
551
+
552
+ def _builtin_split(self, args):
553
+ s, sep = args[0].value, args[1].value
554
+ return TengwarVector([TengwarStr(p) for p in s.split(sep)])
555
+
556
+ def _builtin_join(self, args):
557
+ sep = args[0].value
558
+ vec = args[1]
559
+ return TengwarStr(sep.join(self._display(e) for e in vec.elements))
560
+
561
+ def _builtin_upper(self, args):
562
+ return TengwarStr(args[0].value.upper())
563
+
564
+ def _builtin_lower(self, args):
565
+ return TengwarStr(args[0].value.lower())
566
+
567
+ def _builtin_trim(self, args):
568
+ return TengwarStr(args[0].value.strip())
569
+
570
+ def _builtin_contains(self, args):
571
+ if isinstance(args[0], TengwarStr):
572
+ return TengwarBool(args[1].value in args[0].value)
573
+ if isinstance(args[0], TengwarVector):
574
+ return TengwarBool(args[1] in args[0].elements)
575
+ raise RuntimeError_(f"Cannot check containment in {type(args[0]).__name__}")
576
+
577
+ def _builtin_replace(self, args):
578
+ return TengwarStr(args[0].value.replace(args[1].value, args[2].value))
579
+
580
+ def _builtin_push(self, args):
581
+ vec = args[0]
582
+ if not isinstance(vec, TengwarVector):
583
+ raise RuntimeError_("push requires a vector")
584
+ return TengwarVector(vec.elements + [args[1]])
585
+
586
+ def _builtin_pop(self, args):
587
+ vec = args[0]
588
+ if not isinstance(vec, TengwarVector):
589
+ raise RuntimeError_("pop requires a vector")
590
+ if not vec.elements:
591
+ raise RuntimeError_("Cannot pop from empty vector")
592
+ return TengwarTuple([TengwarVector(vec.elements[:-1]), vec.elements[-1]])
593
+
594
+ def _builtin_get(self, args):
595
+ col, idx = args[0], args[1].value
596
+ if isinstance(col, TengwarVector):
597
+ if idx < 0 or idx >= len(col.elements):
598
+ raise RuntimeError_(f"Index {idx} out of bounds (len={len(col.elements)})")
599
+ return col.elements[idx]
600
+ if isinstance(col, TengwarTuple):
601
+ return col.elements[idx]
602
+ if isinstance(col, TengwarStr):
603
+ return TengwarStr(col.value[idx])
604
+ raise RuntimeError_(f"Cannot index into {type(col).__name__}")
605
+
606
+ def _builtin_set_at(self, args):
607
+ vec, idx, val = args[0], args[1].value, args[2]
608
+ if not isinstance(vec, TengwarVector):
609
+ raise RuntimeError_("set-at requires a vector")
610
+ new_elements = list(vec.elements)
611
+ new_elements[idx] = val
612
+ return TengwarVector(new_elements)
613
+
614
+ def _builtin_slice(self, args):
615
+ vec, start = args[0], args[1].value
616
+ end = args[2].value if len(args) > 2 else len(vec.elements)
617
+ return TengwarVector(vec.elements[start:end])
618
+
619
+ def _builtin_map(self, args):
620
+ fn, vec = args[0], args[1]
621
+ if not isinstance(vec, TengwarVector):
622
+ raise RuntimeError_("map requires a vector")
623
+ return TengwarVector([self._call_function(fn, [e]) for e in vec.elements])
624
+
625
+ def _builtin_filter(self, args):
626
+ fn, vec = args[0], args[1]
627
+ result = []
628
+ for e in vec.elements:
629
+ if self._is_truthy(self._call_function(fn, [e])):
630
+ result.append(e)
631
+ return TengwarVector(result)
632
+
633
+ def _builtin_reduce(self, args):
634
+ fn, init, vec = args[0], args[1], args[2]
635
+ acc = init
636
+ for e in vec.elements:
637
+ acc = self._call_function(fn, [acc, e])
638
+ return acc
639
+
640
+ def _builtin_sum(self, args):
641
+ """(sum vec) — sum all numeric elements"""
642
+ vec = args[0]
643
+ total = 0
644
+ for e in vec.elements:
645
+ total += self._num_val(e)
646
+ return TengwarInt(total) if isinstance(total, int) else TengwarFloat(total)
647
+
648
+ def _builtin_product(self, args):
649
+ """(product vec) — multiply all numeric elements"""
650
+ vec = args[0]
651
+ total = 1
652
+ for e in vec.elements:
653
+ total *= self._num_val(e)
654
+ return TengwarInt(total) if isinstance(total, int) else TengwarFloat(total)
655
+
656
+ def _builtin_count_if(self, args):
657
+ """(count-if pred vec) — count elements matching predicate"""
658
+ pred, vec = args[0], args[1]
659
+ count = 0
660
+ for e in vec.elements:
661
+ if self._is_truthy(self._call_function(pred, [e])):
662
+ count += 1
663
+ return TengwarInt(count)
664
+
665
+ def _builtin_average(self, args):
666
+ """(average vec) — arithmetic mean"""
667
+ vec = args[0]
668
+ if not vec.elements:
669
+ raise RuntimeError_("Cannot average empty vector")
670
+ total = sum(self._num_val(e) for e in vec.elements)
671
+ return TengwarFloat(total / len(vec.elements))
672
+
673
+ def _builtin_range(self, args):
674
+ start = 0
675
+ step = 1
676
+ if len(args) == 1:
677
+ end = args[0].value
678
+ elif len(args) == 2:
679
+ start, end = args[0].value, args[1].value
680
+ else:
681
+ start, end, step = args[0].value, args[1].value, args[2].value
682
+ return TengwarVector([TengwarInt(i) for i in range(start, end, step)])
683
+
684
+ def _builtin_reverse(self, args):
685
+ v = args[0]
686
+ if isinstance(v, TengwarVector): return TengwarVector(list(reversed(v.elements)))
687
+ if isinstance(v, TengwarStr): return TengwarStr(v.value[::-1])
688
+ raise RuntimeError_(f"Cannot reverse {type(v).__name__}")
689
+
690
+ def _builtin_sort(self, args):
691
+ vec = args[0]
692
+ key_fn = args[1] if len(args) > 1 else None
693
+ if key_fn:
694
+ return TengwarVector(sorted(vec.elements, key=lambda e: self._call_function(key_fn, [e]).value))
695
+ return TengwarVector(sorted(vec.elements, key=lambda e: e.value))
696
+
697
+ def _builtin_flatten(self, args):
698
+ result = []
699
+ for e in args[0].elements:
700
+ if isinstance(e, TengwarVector):
701
+ result.extend(e.elements)
702
+ else:
703
+ result.append(e)
704
+ return TengwarVector(result)
705
+
706
+ def _builtin_zip(self, args):
707
+ vecs = [a.elements for a in args]
708
+ return TengwarVector([
709
+ TengwarTuple(list(elems)) for elems in zip(*vecs)
710
+ ])
711
+
712
+ def _builtin_enumerate(self, args):
713
+ vec = args[0]
714
+ return TengwarVector([
715
+ TengwarTuple([TengwarInt(i), e]) for i, e in enumerate(vec.elements)
716
+ ])
717
+
718
+ def _builtin_head(self, args):
719
+ vec = args[0]
720
+ if not vec.elements:
721
+ raise RuntimeError_("head of empty vector")
722
+ return vec.elements[0]
723
+
724
+ def _builtin_tail(self, args):
725
+ vec = args[0]
726
+ if not vec.elements:
727
+ raise RuntimeError_("tail of empty vector")
728
+ return TengwarVector(vec.elements[1:])
729
+
730
+ def _builtin_empty(self, args):
731
+ v = args[0]
732
+ if isinstance(v, TengwarVector): return TengwarBool(len(v.elements) == 0)
733
+ if isinstance(v, TengwarStr): return TengwarBool(len(v.value) == 0)
734
+ return TengwarBool(False)
735
+
736
+ def _builtin_nth(self, args):
737
+ col, idx = args[0], args[1].value
738
+ if isinstance(col, TengwarTuple): return col.elements[idx]
739
+ if isinstance(col, TengwarVector): return col.elements[idx]
740
+ raise RuntimeError_(f"Cannot nth into {type(col).__name__}")
741
+
742
+ def _builtin_deref(self, args):
743
+ cell = args[0]
744
+ if not isinstance(cell, TengwarMutableCell):
745
+ raise RuntimeError_("deref requires a mutable cell")
746
+ return cell.value
747
+
748
+ def _builtin_set_mut(self, args):
749
+ cell, value = args[0], args[1]
750
+ if not isinstance(cell, TengwarMutableCell):
751
+ raise RuntimeError_("set! requires a mutable cell")
752
+ cell.value = value
753
+ return value
754
+
755
+ def _builtin_hash(self, args):
756
+ return TengwarStr(content_hash(self._display(args[0])))
757
+
758
+ def _builtin_compose(self, args):
759
+ f, g = args[0], args[1]
760
+ def composed(call_args):
761
+ return self._call_function(f, [self._call_function(g, call_args)])
762
+ return TengwarBuiltin('composed', composed)
763
+
764
+ def _builtin_sum(self, args):
765
+ vec = args[0]
766
+ if not isinstance(vec, TengwarVector):
767
+ raise RuntimeError_("sum requires a vector")
768
+ total = 0
769
+ use_float = False
770
+ for e in vec.elements:
771
+ if isinstance(e, TengwarFloat):
772
+ use_float = True
773
+ total += e.value
774
+ return TengwarFloat(total) if use_float else TengwarInt(total)
775
+
776
+ def _builtin_product(self, args):
777
+ vec = args[0]
778
+ if not isinstance(vec, TengwarVector):
779
+ raise RuntimeError_("product requires a vector")
780
+ total = 1
781
+ use_float = False
782
+ for e in vec.elements:
783
+ if isinstance(e, TengwarFloat):
784
+ use_float = True
785
+ total *= e.value
786
+ return TengwarFloat(total) if use_float else TengwarInt(total)
787
+
788
+ def _builtin_count(self, args):
789
+ fn, vec = args[0], args[1]
790
+ if not isinstance(vec, TengwarVector):
791
+ raise RuntimeError_("count requires a vector")
792
+ return TengwarInt(sum(1 for e in vec.elements if self._is_truthy(self._call_function(fn, [e]))))
793
+
794
+ def _builtin_any(self, args):
795
+ fn, vec = args[0], args[1]
796
+ return TengwarBool(any(self._is_truthy(self._call_function(fn, [e])) for e in vec.elements))
797
+
798
+ def _builtin_all(self, args):
799
+ fn, vec = args[0], args[1]
800
+ return TengwarBool(all(self._is_truthy(self._call_function(fn, [e])) for e in vec.elements))
801
+
802
+ # === COMBO BUILTINS (token-efficient compound operations) ===
803
+
804
+ def _builtin_sum_by(self, args):
805
+ """(sum-by f vec) = (sum (map f vec)) in one call"""
806
+ fn, vec = args[0], args[1]
807
+ if not isinstance(vec, TengwarVector):
808
+ raise RuntimeError_("sum-by requires a vector")
809
+ total = 0
810
+ use_float = False
811
+ for e in vec.elements:
812
+ r = self._call_function(fn, [e])
813
+ if isinstance(r, TengwarFloat):
814
+ use_float = True
815
+ total += self._num_val(r)
816
+ return TengwarFloat(total) if use_float else TengwarInt(total)
817
+
818
+ def _builtin_min_by(self, args):
819
+ """(min-by f vec) = minimum of (map f vec)"""
820
+ fn, vec = args[0], args[1]
821
+ if not isinstance(vec, TengwarVector) or not vec.elements:
822
+ raise RuntimeError_("min-by requires a non-empty vector")
823
+ return min((self._call_function(fn, [e]) for e in vec.elements),
824
+ key=lambda x: self._num_val(x))
825
+
826
+ def _builtin_max_by(self, args):
827
+ """(max-by f vec) = maximum of (map f vec)"""
828
+ fn, vec = args[0], args[1]
829
+ if not isinstance(vec, TengwarVector) or not vec.elements:
830
+ raise RuntimeError_("max-by requires a non-empty vector")
831
+ return max((self._call_function(fn, [e]) for e in vec.elements),
832
+ key=lambda x: self._num_val(x))
833
+
834
+ def _builtin_filter_map(self, args):
835
+ """(filter-map pred f vec) = (map f (filter pred vec)) in one pass"""
836
+ pred, fn, vec = args[0], args[1], args[2]
837
+ if not isinstance(vec, TengwarVector):
838
+ raise RuntimeError_("filter-map requires a vector")
839
+ results = []
840
+ for e in vec.elements:
841
+ if self._is_truthy(self._call_function(pred, [e])):
842
+ results.append(self._call_function(fn, [e]))
843
+ return TengwarVector(results)
844
+
845
+ def _builtin_flat(self, args):
846
+ """(flat vec) = flatten one level"""
847
+ vec = args[0]
848
+ if not isinstance(vec, TengwarVector):
849
+ raise RuntimeError_("flat requires a vector")
850
+ result = []
851
+ for e in vec.elements:
852
+ if isinstance(e, TengwarVector):
853
+ result.extend(e.elements)
854
+ else:
855
+ result.append(e)
856
+ return TengwarVector(result)
857
+
858
+ def _builtin_zip(self, args):
859
+ """(zip v1 v2) = vector of tuples"""
860
+ v1, v2 = args[0], args[1]
861
+ if not isinstance(v1, TengwarVector) or not isinstance(v2, TengwarVector):
862
+ raise RuntimeError_("zip requires two vectors")
863
+ return TengwarVector([
864
+ TengwarTuple([a, b])
865
+ for a, b in zip(v1.elements, v2.elements)
866
+ ])
867
+
868
+ def _builtin_enumerate(self, args):
869
+ """(enumerate vec) = vector of (index, element) tuples"""
870
+ vec = args[0]
871
+ if not isinstance(vec, TengwarVector):
872
+ raise RuntimeError_("enumerate requires a vector")
873
+ return TengwarVector([
874
+ TengwarTuple([TengwarInt(i), e])
875
+ for i, e in enumerate(vec.elements)
876
+ ])
877
+
878
+ def _builtin_range_map(self, args):
879
+ """(range-map f a b) = (map f (range a b)) in one call"""
880
+ fn, a, b = args[0], self._num_val(args[1]), self._num_val(args[2])
881
+ return TengwarVector([
882
+ self._call_function(fn, [TengwarInt(i)])
883
+ for i in range(int(a), int(b))
884
+ ])
885
+
886
+ def _builtin_apply(self, args):
887
+ fn, arg_vec = args[0], args[1]
888
+ if isinstance(arg_vec, TengwarVector):
889
+ return self._call_function(fn, arg_vec.elements)
890
+ if isinstance(arg_vec, TengwarTuple):
891
+ return self._call_function(fn, arg_vec.elements)
892
+ return self._call_function(fn, [arg_vec])
893
+
894
+ def _builtin_err(self, args):
895
+ raise RuntimeError_(self._display(args[0]) if args else "error")
896
+
897
+ def _builtin_try(self, args):
898
+ fn, handler = args[0], args[1]
899
+ try:
900
+ result = self._call_function(fn, [])
901
+ return TengwarTuple([TengwarBool(True), result])
902
+ except (RuntimeError_, ProofError) as e:
903
+ return TengwarTuple([TengwarBool(False), TengwarStr(str(e))])
904
+
905
+ # === DICT BUILTINS ===
906
+
907
+ def _builtin_dict(self, args):
908
+ """(dict k1 v1 k2 v2 ...) or (dict vec-of-pairs)"""
909
+ if len(args) == 1 and isinstance(args[0], TengwarVector):
910
+ d = {}
911
+ for pair in args[0].elements:
912
+ if isinstance(pair, (TengwarTuple, TengwarVector)):
913
+ d[pair.elements[0]] = pair.elements[1]
914
+ return TengwarDict(d)
915
+ d = {}
916
+ for i in range(0, len(args) - 1, 2):
917
+ d[args[i]] = args[i + 1]
918
+ return TengwarDict(d)
919
+
920
+ def _builtin_dict_get(self, args):
921
+ d, k = args[0], args[1]
922
+ default = args[2] if len(args) > 2 else TengwarUnit()
923
+ return d.data.get(k, default)
924
+
925
+ def _builtin_dict_set(self, args):
926
+ d, k, v = args[0], args[1], args[2]
927
+ new_data = dict(d.data)
928
+ new_data[k] = v
929
+ return TengwarDict(new_data)
930
+
931
+ def _builtin_dict_del(self, args):
932
+ d, k = args[0], args[1]
933
+ new_data = dict(d.data)
934
+ new_data.pop(k, None)
935
+ return TengwarDict(new_data)
936
+
937
+ def _builtin_dict_keys(self, args):
938
+ return TengwarVector(list(args[0].data.keys()))
939
+
940
+ def _builtin_dict_vals(self, args):
941
+ return TengwarVector(list(args[0].data.values()))
942
+
943
+ def _builtin_dict_pairs(self, args):
944
+ return TengwarVector([TengwarTuple([k, v]) for k, v in args[0].data.items()])
945
+
946
+ def _builtin_dict_has(self, args):
947
+ return TengwarBool(args[1] in args[0].data)
948
+
949
+ def _builtin_dict_merge(self, args):
950
+ result = dict(args[0].data)
951
+ for a in args[1:]:
952
+ result.update(a.data)
953
+ return TengwarDict(result)
954
+
955
+ def _builtin_dict_size(self, args):
956
+ return TengwarInt(len(args[0].data))
957
+
958
+ # === EXTENDED FUNCTIONAL ===
959
+
960
+ def _builtin_flat_map(self, args):
961
+ fn, vec = args[0], args[1]
962
+ result = []
963
+ for e in vec.elements:
964
+ mapped = self._call_function(fn, [e])
965
+ if isinstance(mapped, TengwarVector):
966
+ result.extend(mapped.elements)
967
+ else:
968
+ result.append(mapped)
969
+ return TengwarVector(result)
970
+
971
+ def _builtin_find(self, args):
972
+ fn, vec = args[0], args[1]
973
+ for e in vec.elements:
974
+ if self._is_truthy(self._call_function(fn, [e])):
975
+ return e
976
+ return TengwarUnit()
977
+
978
+ def _builtin_index_of(self, args):
979
+ fn, vec = args[0], args[1]
980
+ for i, e in enumerate(vec.elements):
981
+ if self._is_truthy(self._call_function(fn, [e])):
982
+ return TengwarInt(i)
983
+ return TengwarInt(-1)
984
+
985
+ def _builtin_take(self, args):
986
+ n, vec = int(self._num_val(args[0])), args[1]
987
+ return TengwarVector(vec.elements[:n])
988
+
989
+ def _builtin_drop(self, args):
990
+ n, vec = int(self._num_val(args[0])), args[1]
991
+ return TengwarVector(vec.elements[n:])
992
+
993
+ def _builtin_take_while(self, args):
994
+ fn, vec = args[0], args[1]
995
+ result = []
996
+ for e in vec.elements:
997
+ if self._is_truthy(self._call_function(fn, [e])):
998
+ result.append(e)
999
+ else:
1000
+ break
1001
+ return TengwarVector(result)
1002
+
1003
+ def _builtin_drop_while(self, args):
1004
+ fn, vec = args[0], args[1]
1005
+ dropping = True
1006
+ result = []
1007
+ for e in vec.elements:
1008
+ if dropping and self._is_truthy(self._call_function(fn, [e])):
1009
+ continue
1010
+ dropping = False
1011
+ result.append(e)
1012
+ return TengwarVector(result)
1013
+
1014
+ def _builtin_zip_with(self, args):
1015
+ fn, v1, v2 = args[0], args[1], args[2]
1016
+ result = [self._call_function(fn, [a, b]) for a, b in zip(v1.elements, v2.elements)]
1017
+ return TengwarVector(result)
1018
+
1019
+ def _builtin_group_by(self, args):
1020
+ fn, vec = args[0], args[1]
1021
+ groups = {}
1022
+ order = []
1023
+ for e in vec.elements:
1024
+ key = self._call_function(fn, [e])
1025
+ key_r = repr(key)
1026
+ if key_r not in groups:
1027
+ groups[key_r] = (key, [])
1028
+ order.append(key_r)
1029
+ groups[key_r][1].append(e)
1030
+ return TengwarDict({groups[k][0]: TengwarVector(groups[k][1]) for k in order})
1031
+
1032
+ def _builtin_unique(self, args):
1033
+ seen = []
1034
+ result = []
1035
+ for e in args[0].elements:
1036
+ if e not in seen:
1037
+ seen.append(e)
1038
+ result.append(e)
1039
+ return TengwarVector(result)
1040
+
1041
+ def _builtin_frequencies(self, args):
1042
+ counts = {}
1043
+ for e in args[0].elements:
1044
+ r = repr(e)
1045
+ if r not in counts:
1046
+ counts[r] = (e, 0)
1047
+ counts[r] = (e, counts[r][1] + 1)
1048
+ return TengwarDict({e: TengwarInt(c) for _, (e, c) in counts.items()})
1049
+
1050
+ def _builtin_partition(self, args):
1051
+ fn, vec = args[0], args[1]
1052
+ yes, no = [], []
1053
+ for e in vec.elements:
1054
+ if self._is_truthy(self._call_function(fn, [e])):
1055
+ yes.append(e)
1056
+ else:
1057
+ no.append(e)
1058
+ return TengwarTuple([TengwarVector(yes), TengwarVector(no)])
1059
+
1060
+ def _builtin_scan(self, args):
1061
+ fn, init, vec = args[0], args[1], args[2]
1062
+ acc = init
1063
+ result = [acc]
1064
+ for e in vec.elements:
1065
+ acc = self._call_function(fn, [acc, e])
1066
+ result.append(acc)
1067
+ return TengwarVector(result)
1068
+
1069
+ def _builtin_chunks(self, args):
1070
+ n, vec = int(self._num_val(args[0])), args[1]
1071
+ els = vec.elements
1072
+ return TengwarVector([TengwarVector(els[i:i+n]) for i in range(0, len(els), n)])
1073
+
1074
+ def _builtin_interleave(self, args):
1075
+ vecs = [a.elements for a in args]
1076
+ result = []
1077
+ for items in zip(*vecs):
1078
+ result.extend(items)
1079
+ return TengwarVector(result)
1080
+
1081
+ def _builtin_repeat(self, args):
1082
+ n, val = int(self._num_val(args[0])), args[1]
1083
+ return TengwarVector([val] * n)
1084
+
1085
+ def _builtin_iterate(self, args):
1086
+ fn, init, n = args[0], args[1], int(self._num_val(args[2]))
1087
+ result = [init]
1088
+ val = init
1089
+ for _ in range(n - 1):
1090
+ val = self._call_function(fn, [val])
1091
+ result.append(val)
1092
+ return TengwarVector(result)
1093
+
1094
+ def _builtin_juxt(self, args):
1095
+ """(juxt f1 f2 ... val) — apply multiple fns to same value"""
1096
+ fns, val = args[:-1], args[-1]
1097
+ return TengwarVector([self._call_function(fn, [val]) for fn in fns])
1098
+
1099
+ def _builtin_min_by(self, args):
1100
+ fn, vec = args[0], args[1]
1101
+ return min(vec.elements, key=lambda e: self._num_val(self._call_function(fn, [e])))
1102
+
1103
+ def _builtin_max_by(self, args):
1104
+ fn, vec = args[0], args[1]
1105
+ return max(vec.elements, key=lambda e: self._num_val(self._call_function(fn, [e])))
1106
+
1107
+ def _builtin_sort_by(self, args):
1108
+ fn, vec = args[0], args[1]
1109
+ return TengwarVector(sorted(vec.elements, key=lambda e: self._num_val(self._call_function(fn, [e]))))
1110
+
1111
+ def _builtin_for_each(self, args):
1112
+ fn, vec = args[0], args[1]
1113
+ for e in vec.elements:
1114
+ self._call_function(fn, [e])
1115
+ return TengwarUnit()
1116
+
1117
+ # === STRING EXTENDED ===
1118
+
1119
+ def _builtin_fmt(self, args):
1120
+ """(fmt template arg1 arg2...) — {} placeholders"""
1121
+ template = args[0].value
1122
+ vals = [self._display(a) for a in args[1:]]
1123
+ try:
1124
+ return TengwarStr(template.format(*vals))
1125
+ except (IndexError, KeyError):
1126
+ return TengwarStr(template)
1127
+
1128
+ def _builtin_starts_with(self, args):
1129
+ return TengwarBool(args[0].value.startswith(args[1].value))
1130
+
1131
+ def _builtin_ends_with(self, args):
1132
+ return TengwarBool(args[0].value.endswith(args[1].value))
1133
+
1134
+ def _builtin_chars(self, args):
1135
+ return TengwarVector([TengwarStr(c) for c in args[0].value])
1136
+
1137
+ def _builtin_char_at(self, args):
1138
+ return TengwarStr(args[0].value[int(self._num_val(args[1]))])
1139
+
1140
+ def _builtin_pad_left(self, args):
1141
+ s, n = args[0].value, int(self._num_val(args[1]))
1142
+ ch = args[2].value if len(args) > 2 else ' '
1143
+ return TengwarStr(s.rjust(n, ch[0]))
1144
+
1145
+ def _builtin_pad_right(self, args):
1146
+ s, n = args[0].value, int(self._num_val(args[1]))
1147
+ ch = args[2].value if len(args) > 2 else ' '
1148
+ return TengwarStr(s.ljust(n, ch[0]))
1149
+
1150
+ # === MATH EXTENDED ===
1151
+
1152
+ def _builtin_min(self, args):
1153
+ if len(args) == 1 and isinstance(args[0], TengwarVector):
1154
+ return min(args[0].elements, key=lambda e: self._num_val(e))
1155
+ return min(args, key=lambda e: self._num_val(e))
1156
+
1157
+ def _builtin_max(self, args):
1158
+ if len(args) == 1 and isinstance(args[0], TengwarVector):
1159
+ return max(args[0].elements, key=lambda e: self._num_val(e))
1160
+ return max(args, key=lambda e: self._num_val(e))
1161
+
1162
+ def _builtin_clamp(self, args):
1163
+ val, lo, hi = self._num_val(args[0]), self._num_val(args[1]), self._num_val(args[2])
1164
+ result = max(lo, min(val, hi))
1165
+ return TengwarFloat(result) if isinstance(args[0], TengwarFloat) else TengwarInt(int(result))
1166
+
1167
+ # === TYPE ===
1168
+
1169
+ def _builtin_type(self, args):
1170
+ v = args[0]
1171
+ type_names = {
1172
+ TengwarInt: "int", TengwarFloat: "float", TengwarStr: "str",
1173
+ TengwarBool: "bool", TengwarUnit: "nil", TengwarVector: "vec",
1174
+ TengwarTuple: "tuple", TengwarDict: "dict", TengwarClosure: "fn",
1175
+ TengwarBuiltin: "builtin", TengwarModule: "module",
1176
+ TengwarMutableCell: "mut", TengwarPyObject: "py-object"
1177
+ }
1178
+ return TengwarStr(type_names.get(type(v), "unknown"))
1179
+
1180
+ def _builtin_memo(self, args):
1181
+ """Memoize a function: (memo fn) → memoized fn"""
1182
+ func = args[0]
1183
+ cache = {}
1184
+ def memoized(call_args):
1185
+ key = tuple(repr(a) for a in call_args)
1186
+ if key not in cache:
1187
+ cache[key] = self._call_function(func, call_args)
1188
+ return cache[key]
1189
+ return TengwarBuiltin(f'memo<{getattr(func, "name", "fn")}>', memoized)
1190
+
1191
+ # === JSON ===
1192
+
1193
+ def _builtin_json_parse(self, args):
1194
+ return self._py_to_tw(json.loads(args[0].value))
1195
+
1196
+ def _builtin_json_encode(self, args):
1197
+ return TengwarStr(json.dumps(self._tw_to_py(args[0])))
1198
+
1199
+ # === FILE I/O ===
1200
+
1201
+ def _builtin_read_file(self, args):
1202
+ if not self.sandbox_allow_io:
1203
+ raise RuntimeError_("File I/O disabled in sandbox mode")
1204
+ try:
1205
+ with open(args[0].value, 'r') as f:
1206
+ return TengwarStr(f.read())
1207
+ except Exception as e:
1208
+ raise RuntimeError_(f"read-file: {e}")
1209
+
1210
+ def _builtin_write_file(self, args):
1211
+ if not self.sandbox_allow_io:
1212
+ raise RuntimeError_("File I/O disabled in sandbox mode")
1213
+ try:
1214
+ with open(args[0].value, 'w') as f:
1215
+ f.write(self._display(args[1]))
1216
+ return TengwarBool(True)
1217
+ except Exception as e:
1218
+ raise RuntimeError_(f"write-file: {e}")
1219
+
1220
+ def _builtin_append_file(self, args):
1221
+ if not self.sandbox_allow_io:
1222
+ raise RuntimeError_("File I/O disabled in sandbox mode")
1223
+ try:
1224
+ with open(args[0].value, 'a') as f:
1225
+ f.write(self._display(args[1]))
1226
+ return TengwarBool(True)
1227
+ except Exception as e:
1228
+ raise RuntimeError_(f"append-file: {e}")
1229
+
1230
+ def _builtin_file_exists(self, args):
1231
+ import os
1232
+ return TengwarBool(os.path.exists(args[0].value))
1233
+
1234
+ # === HTTP ===
1235
+
1236
+ def _builtin_http_get(self, args):
1237
+ if not self.sandbox_allow_net:
1238
+ raise RuntimeError_("Network access disabled in sandbox mode")
1239
+ try:
1240
+ import urllib.request
1241
+ url = args[0].value
1242
+ headers = {}
1243
+ if len(args) > 1 and isinstance(args[1], TengwarDict):
1244
+ headers = {self._display(k): self._display(v) for k, v in args[1].data.items()}
1245
+ req = urllib.request.Request(url, headers=headers)
1246
+ with urllib.request.urlopen(req, timeout=30) as resp:
1247
+ body = resp.read().decode('utf-8')
1248
+ return TengwarDict({
1249
+ TengwarStr("status"): TengwarInt(resp.status),
1250
+ TengwarStr("body"): TengwarStr(body),
1251
+ TengwarStr("headers"): TengwarDict({
1252
+ TengwarStr(k): TengwarStr(v) for k, v in resp.headers.items()
1253
+ })
1254
+ })
1255
+ except Exception as e:
1256
+ raise RuntimeError_(f"http-get: {e}")
1257
+
1258
+ def _builtin_http_post(self, args):
1259
+ if not self.sandbox_allow_net:
1260
+ raise RuntimeError_("Network access disabled in sandbox mode")
1261
+ try:
1262
+ import urllib.request
1263
+ url = args[0].value
1264
+ body = self._display(args[1]) if len(args) > 1 else ""
1265
+ headers = {"Content-Type": "application/json"}
1266
+ if len(args) > 2 and isinstance(args[2], TengwarDict):
1267
+ headers.update({self._display(k): self._display(v) for k, v in args[2].data.items()})
1268
+ data = body.encode('utf-8')
1269
+ req = urllib.request.Request(url, data=data, headers=headers, method='POST')
1270
+ with urllib.request.urlopen(req, timeout=30) as resp:
1271
+ resp_body = resp.read().decode('utf-8')
1272
+ return TengwarDict({
1273
+ TengwarStr("status"): TengwarInt(resp.status),
1274
+ TengwarStr("body"): TengwarStr(resp_body),
1275
+ })
1276
+ except Exception as e:
1277
+ raise RuntimeError_(f"http-post: {e}")
1278
+
1279
+ # === PYTHON INTEROP ===
1280
+
1281
+ def _builtin_py_call(self, args):
1282
+ """(py-call obj method_name arg1 arg2...) or (py-call fn arg1 arg2...)"""
1283
+ if not self.sandbox_allow_py:
1284
+ raise RuntimeError_("Python interop disabled in sandbox mode")
1285
+ obj = args[0]
1286
+ if isinstance(obj, TengwarPyObject) and len(args) >= 2 and isinstance(args[1], TengwarStr):
1287
+ method = getattr(obj.obj, args[1].value)
1288
+ py_args = [self._tw_to_py(a) for a in args[2:]]
1289
+ result = method(*py_args)
1290
+ return self._py_to_tw(result)
1291
+ elif isinstance(obj, TengwarPyObject) and callable(obj.obj):
1292
+ py_args = [self._tw_to_py(a) for a in args[1:]]
1293
+ result = obj.obj(*py_args)
1294
+ return self._py_to_tw(result)
1295
+ raise RuntimeError_(f"py-call: expected py-object, got {type(obj).__name__}")
1296
+
1297
+ def _builtin_py_attr(self, args):
1298
+ """(py-attr obj attr_name)"""
1299
+ obj, attr = args[0], args[1].value
1300
+ if isinstance(obj, TengwarPyObject):
1301
+ val = getattr(obj.obj, attr)
1302
+ return self._py_to_tw(val)
1303
+ raise RuntimeError_(f"py-attr: expected py-object, got {type(obj).__name__}")
1304
+
1305
+ def _builtin_py_eval(self, args):
1306
+ """(py-eval \"python_expression\")"""
1307
+ if not self.sandbox_allow_py:
1308
+ raise RuntimeError_("Python eval disabled in sandbox mode")
1309
+ result = eval(args[0].value)
1310
+ return self._py_to_tw(result)
1311
+
1312
+ def _builtin_to_py(self, args):
1313
+ """(->py tengwar_value) — convert to Python object"""
1314
+ return TengwarPyObject(self._tw_to_py(args[0]))
1315
+
1316
+ def _builtin_to_tw(self, args):
1317
+ """(->tw py_object) — convert to Tengwar value"""
1318
+ if isinstance(args[0], TengwarPyObject):
1319
+ return self._py_to_tw(args[0].obj)
1320
+ return args[0]
1321
+
1322
+ # === SYSTEM ===
1323
+
1324
+ def _builtin_time_ms(self, args):
1325
+ import time
1326
+ return TengwarInt(int(time.time() * 1000))
1327
+
1328
+ def _builtin_sleep(self, args):
1329
+ import time
1330
+ time.sleep(self._num_val(args[0]) / 1000)
1331
+ return TengwarUnit()
1332
+
1333
+ def _builtin_env_get(self, args):
1334
+ import os
1335
+ val = os.environ.get(args[0].value, "")
1336
+ return TengwarStr(val) if val else TengwarUnit()
1337
+
1338
+ def _builtin_rand(self, args):
1339
+ import random
1340
+ return TengwarFloat(random.random())
1341
+
1342
+ def _builtin_rand_int(self, args):
1343
+ import random
1344
+ lo = int(self._num_val(args[0]))
1345
+ hi = int(self._num_val(args[1]))
1346
+ return TengwarInt(random.randint(lo, hi))
1347
+
1348
+ def _builtin_uuid(self, args):
1349
+ import uuid
1350
+ return TengwarStr(str(uuid.uuid4()))
1351
+
1352
+ # === CONVERSION HELPERS ===
1353
+
1354
+ def _builtin_dict_to_vec(self, args):
1355
+ d = args[0]
1356
+ return TengwarVector([TengwarTuple([k, v]) for k, v in d.data.items()])
1357
+
1358
+ def _builtin_vec_to_dict(self, args):
1359
+ vec = args[0]
1360
+ d = {}
1361
+ for pair in vec.elements:
1362
+ if isinstance(pair, (TengwarTuple, TengwarVector)) and len(pair.elements) >= 2:
1363
+ d[pair.elements[0]] = pair.elements[1]
1364
+ return TengwarDict(d)
1365
+
1366
+ # === PY <-> TW CONVERSION ===
1367
+
1368
+ def _tw_to_py(self, val):
1369
+ """Convert Tengwar value to Python value"""
1370
+ if isinstance(val, TengwarInt): return val.value
1371
+ if isinstance(val, TengwarFloat): return val.value
1372
+ if isinstance(val, TengwarStr): return val.value
1373
+ if isinstance(val, TengwarBool): return val.value
1374
+ if isinstance(val, TengwarUnit): return None
1375
+ if isinstance(val, TengwarVector): return [self._tw_to_py(e) for e in val.elements]
1376
+ if isinstance(val, TengwarTuple): return tuple(self._tw_to_py(e) for e in val.elements)
1377
+ if isinstance(val, TengwarDict): return {self._tw_to_py(k): self._tw_to_py(v) for k, v in val.data.items()}
1378
+ if isinstance(val, TengwarPyObject): return val.obj
1379
+ return val
1380
+
1381
+ def _py_to_tw(self, val):
1382
+ """Convert Python value to Tengwar value"""
1383
+ if val is None: return TengwarUnit()
1384
+ if isinstance(val, bool): return TengwarBool(val)
1385
+ if isinstance(val, int): return TengwarInt(val)
1386
+ if isinstance(val, float): return TengwarFloat(val)
1387
+ if isinstance(val, str): return TengwarStr(val)
1388
+ if isinstance(val, list): return TengwarVector([self._py_to_tw(e) for e in val])
1389
+ if isinstance(val, tuple): return TengwarTuple([self._py_to_tw(e) for e in val])
1390
+ if isinstance(val, dict): return TengwarDict({self._py_to_tw(k): self._py_to_tw(v) for k, v in val.items()})
1391
+ if isinstance(val, set): return TengwarVector([self._py_to_tw(e) for e in val])
1392
+ # Wrap anything else as py-object
1393
+ return TengwarPyObject(val)
1394
+
1395
+ def _num_val(self, v):
1396
+ """Extract numeric value from Tengwar value"""
1397
+ if isinstance(v, TengwarInt): return v.value
1398
+ if isinstance(v, TengwarFloat): return v.value
1399
+ raise RuntimeError_(f"Expected number, got {type(v).__name__}")
1400
+
1401
+ # === HELPERS ===
1402
+
1403
+ def _expect_tuple(self, v):
1404
+ if not isinstance(v, TengwarTuple):
1405
+ raise RuntimeError_(f"Expected tuple, got {type(v).__name__}")
1406
+ return v
1407
+
1408
+ def _display(self, v: TengwarValue) -> str:
1409
+ if isinstance(v, TengwarStr): return v.value
1410
+ return repr(v)
1411
+
1412
+ def _is_truthy(self, v: TengwarValue) -> bool:
1413
+ if isinstance(v, TengwarBool): return v.value
1414
+ if isinstance(v, TengwarUnit): return False
1415
+ if isinstance(v, TengwarInt): return v.value != 0
1416
+ if isinstance(v, TengwarStr): return len(v.value) > 0
1417
+ if isinstance(v, TengwarVector): return len(v.elements) > 0
1418
+ if isinstance(v, TengwarDict): return len(v.data) > 0
1419
+ if isinstance(v, TengwarPyObject): return bool(v.obj)
1420
+ return True
1421
+
1422
+ def _call_function(self, fn: TengwarValue, args: list) -> TengwarValue:
1423
+ # Trampoline loop for TCO
1424
+ while True:
1425
+ if isinstance(fn, TengwarBuiltin):
1426
+ return fn.func(args)
1427
+ if isinstance(fn, TengwarPyObject) and callable(fn.obj):
1428
+ py_args = [self._tw_to_py(a) for a in args]
1429
+ return self._py_to_tw(fn.obj(*py_args))
1430
+ # Handle CompiledClosure from VM
1431
+ try:
1432
+ from .vm import CompiledClosure, VM
1433
+ if isinstance(fn, CompiledClosure):
1434
+ child_env = Environment(parent=fn.env)
1435
+ for param, arg in zip(fn.code_obj.params, args):
1436
+ child_env.set(param, arg)
1437
+ vm = VM(self)
1438
+ return vm.execute(fn.code_obj, child_env)
1439
+ except ImportError:
1440
+ pass
1441
+ if isinstance(fn, TengwarClosure):
1442
+ child_env = fn.env.child()
1443
+ for param, arg in zip(fn.params, args):
1444
+ child_env.set(param, arg)
1445
+ # Support partial application
1446
+ if len(args) < len(fn.params):
1447
+ remaining = fn.params[len(args):]
1448
+ return TengwarClosure(remaining, fn.body, child_env, fn.name)
1449
+ # TCO: if body is a tail call, trampoline instead of recursing
1450
+ result = self.eval(fn.body, child_env)
1451
+ if isinstance(result, TailCall):
1452
+ fn = result.func
1453
+ args = result.args
1454
+ continue
1455
+ return result
1456
+ raise RuntimeError_(f"Cannot call {type(fn).__name__}")
1457
+
1458
+ def _get_identifier_name(self, node: ASTNode) -> str:
1459
+ if isinstance(node, Symbol): return node.name
1460
+ if isinstance(node, HashId): return node.hash
1461
+ if isinstance(node, AddrRef): return node.addr
1462
+ raise RuntimeError_(f"Expected identifier, got {type(node).__name__}")
1463
+
1464
+ def _match_pattern(self, pattern: ASTNode, value: TengwarValue, env: Environment) -> bool:
1465
+ """Try to match a pattern against a value, binding variables in env"""
1466
+ # Wildcard
1467
+ if isinstance(pattern, Symbol) and pattern.name == '_':
1468
+ return True
1469
+
1470
+ # Literal matching
1471
+ if isinstance(pattern, IntLit):
1472
+ return isinstance(value, TengwarInt) and value.value == pattern.value
1473
+ if isinstance(pattern, FloatLit):
1474
+ return isinstance(value, TengwarFloat) and value.value == pattern.value
1475
+ if isinstance(pattern, StrLit):
1476
+ return isinstance(value, TengwarStr) and value.value == pattern.value
1477
+ if isinstance(pattern, BoolLit):
1478
+ return isinstance(value, TengwarBool) and value.value == pattern.value
1479
+ if isinstance(pattern, UnitLit):
1480
+ return isinstance(value, TengwarUnit)
1481
+
1482
+ # Variable binding
1483
+ if isinstance(pattern, Symbol):
1484
+ env.set(pattern.name, value)
1485
+ return True
1486
+ if isinstance(pattern, HashId):
1487
+ env.set(pattern.hash, value)
1488
+ return True
1489
+
1490
+ # Tuple pattern
1491
+ if isinstance(pattern, Tuple) and isinstance(value, TengwarTuple):
1492
+ if len(pattern.elements) != len(value.elements):
1493
+ return False
1494
+ for p, v in zip(pattern.elements, value.elements):
1495
+ if not self._match_pattern(p, v, env):
1496
+ return False
1497
+ return True
1498
+
1499
+ # Vector pattern
1500
+ if isinstance(pattern, Vector) and isinstance(value, TengwarVector):
1501
+ if len(pattern.elements) != len(value.elements):
1502
+ return False
1503
+ for p, v in zip(pattern.elements, value.elements):
1504
+ if not self._match_pattern(p, v, env):
1505
+ return False
1506
+ return True
1507
+
1508
+ return False
1509
+
1510
+ # === EVAL ===
1511
+
1512
+ def eval(self, node: ASTNode, env: Optional[Environment] = None) -> TengwarValue:
1513
+ if env is None:
1514
+ env = self.global_env
1515
+
1516
+ if self.trace:
1517
+ print(f" EVAL: {type(node).__name__}")
1518
+
1519
+ # Sandbox step limit
1520
+ if self.max_steps > 0:
1521
+ self.step_count += 1
1522
+ if self.step_count > self.max_steps:
1523
+ raise RuntimeError_("Execution step limit exceeded (sandbox)")
1524
+
1525
+ # Literals
1526
+ if isinstance(node, IntLit):
1527
+ return TengwarInt(node.value)
1528
+ if isinstance(node, FloatLit):
1529
+ return TengwarFloat(node.value)
1530
+ if isinstance(node, StrLit):
1531
+ return TengwarStr(node.value)
1532
+ if isinstance(node, BoolLit):
1533
+ return TengwarBool(node.value)
1534
+ if isinstance(node, UnitLit):
1535
+ return TengwarUnit()
1536
+
1537
+ # Identifiers
1538
+ if isinstance(node, Symbol):
1539
+ name = node.name
1540
+ # Dot indexing: s.1 → (nth s 1), s.0.name → chained access
1541
+ if '.' in name and not name.startswith('.') and not name.endswith('.'):
1542
+ parts = name.split('.')
1543
+ val = env.get(parts[0])
1544
+ for part in parts[1:]:
1545
+ if part.isdigit():
1546
+ idx = int(part)
1547
+ if isinstance(val, TengwarTuple):
1548
+ val = val.elements[idx]
1549
+ elif isinstance(val, TengwarVector):
1550
+ val = val.elements[idx]
1551
+ elif isinstance(val, TengwarStr):
1552
+ val = TengwarStr(val.value[idx])
1553
+ else:
1554
+ raise RuntimeError_(f"Cannot index into {type(val).__name__}")
1555
+ elif isinstance(val, TengwarModule) and part in val.bindings:
1556
+ val = val.bindings[part]
1557
+ elif isinstance(val, TengwarDict):
1558
+ key = TengwarStr(part)
1559
+ if key in val.data:
1560
+ val = val.data[key]
1561
+ else:
1562
+ raise RuntimeError_(f"Dict has no key '{part}'")
1563
+ elif isinstance(val, TengwarPyObject):
1564
+ attr = getattr(val.obj, part, None)
1565
+ if attr is not None:
1566
+ val = self._py_to_tw(attr) if not callable(attr) else TengwarPyObject(attr, f"{val.name}.{part}")
1567
+ else:
1568
+ raise RuntimeError_(f"Python object has no attribute '{part}'")
1569
+ else:
1570
+ raise RuntimeError_(f"Cannot access .{part} on {type(val).__name__}")
1571
+ return val
1572
+ return env.get(name)
1573
+ if isinstance(node, HashId):
1574
+ return env.get(node.hash)
1575
+ if isinstance(node, AddrRef):
1576
+ return env.lookup_addr(node.addr)
1577
+
1578
+ # Lambda
1579
+ if isinstance(node, Lambda):
1580
+ param_names = [self._get_identifier_name(p) for p in node.params]
1581
+ return TengwarClosure(param_names, node.body, env)
1582
+
1583
+ # Bind: expr → target
1584
+ if isinstance(node, Bind):
1585
+ value = self.eval(node.expr, env)
1586
+ if isinstance(node.target, AddrRef):
1587
+ # Content-address store
1588
+ addr = node.target.addr
1589
+ env.store(addr, value)
1590
+ # Also bind the addr as a name for convenience
1591
+ env.set(addr, value)
1592
+ elif isinstance(node.target, HashId):
1593
+ env.set(node.target.hash, value)
1594
+ elif isinstance(node.target, Symbol):
1595
+ env.set(node.target.name, value)
1596
+ elif isinstance(node.target, Tuple):
1597
+ # Destructuring bind
1598
+ if isinstance(value, TengwarTuple):
1599
+ for pat, val in zip(node.target.elements, value.elements):
1600
+ name = self._get_identifier_name(pat)
1601
+ env.set(name, val)
1602
+ else:
1603
+ raise RuntimeError_("Cannot destructure non-tuple")
1604
+ else:
1605
+ raise RuntimeError_(f"Invalid bind target: {type(node.target).__name__}")
1606
+ return value
1607
+
1608
+ # Define
1609
+ if isinstance(node, Define):
1610
+ name = self._get_identifier_name(node.name)
1611
+ value = self.eval(node.value, env)
1612
+ # If defining a function, make it self-recursive
1613
+ if isinstance(value, TengwarClosure):
1614
+ value.name = name
1615
+ value.env.set(name, value)
1616
+ env.set(name, value)
1617
+ # Also compute content address
1618
+ addr = '@' + content_hash(name)
1619
+ env.store(addr, value)
1620
+ return value
1621
+
1622
+ # Conditional
1623
+ if isinstance(node, Cond):
1624
+ cond_val = self.eval(node.condition, env)
1625
+ if self._is_truthy(cond_val):
1626
+ return self.eval(node.then_branch, env)
1627
+ else:
1628
+ return self.eval(node.else_branch, env)
1629
+
1630
+ # Match
1631
+ if isinstance(node, Match):
1632
+ value = self.eval(node.expr, env)
1633
+ for pattern, body in node.cases:
1634
+ match_env = env.child()
1635
+ if self._match_pattern(pattern, value, match_env):
1636
+ return self.eval(body, match_env)
1637
+ raise RuntimeError_(f"No matching pattern for {repr(value)}")
1638
+
1639
+ # Sequence
1640
+ if isinstance(node, Seq):
1641
+ result = TengwarUnit()
1642
+ for expr in node.exprs:
1643
+ result = self.eval(expr, env)
1644
+ return result
1645
+
1646
+ # Parallel
1647
+ if isinstance(node, Parallel):
1648
+ with concurrent.futures.ThreadPoolExecutor() as executor:
1649
+ futures = [executor.submit(self.eval, expr, env) for expr in node.exprs]
1650
+ results = [f.result() for f in futures]
1651
+ return TengwarTuple(results)
1652
+
1653
+ # Module
1654
+ if isinstance(node, Module):
1655
+ mod_env = env.child()
1656
+ for expr in node.body:
1657
+ self.eval(expr, mod_env)
1658
+ module = TengwarModule(node.name, dict(mod_env.bindings))
1659
+ if node.name:
1660
+ env.set(node.name, module)
1661
+ return module
1662
+
1663
+ # Recurse
1664
+ if isinstance(node, Recurse):
1665
+ name = self._get_identifier_name(node.name)
1666
+ # Create a closure that references itself
1667
+ child_env = env.child()
1668
+ body_val = self.eval(node.body, child_env)
1669
+ if isinstance(body_val, TengwarClosure):
1670
+ body_val.name = name
1671
+ # Make closure recursive by binding name in its own env
1672
+ body_val.env.set(name, body_val)
1673
+ child_env.set(name, body_val)
1674
+ env.set(name, body_val)
1675
+ return body_val
1676
+
1677
+ # Proof
1678
+ if isinstance(node, Proof):
1679
+ result = self.eval(node.assertion, env)
1680
+ if not self._is_truthy(result):
1681
+ raise ProofError(f"Proof obligation failed: {repr(result)}")
1682
+ return result
1683
+
1684
+ # Mutate
1685
+ if isinstance(node, Mutate):
1686
+ name = self._get_identifier_name(node.name)
1687
+ value = self.eval(node.value, env)
1688
+ cell = TengwarMutableCell(value)
1689
+ env.set(name, cell)
1690
+ return cell
1691
+
1692
+ # Let — local bindings
1693
+ if isinstance(node, Let):
1694
+ child_env = env.child()
1695
+ for name_node, val_node in node.bindings:
1696
+ name = self._get_identifier_name(name_node)
1697
+ val = self.eval(val_node, child_env)
1698
+ child_env.set(name, val)
1699
+ return self.eval(node.body, child_env)
1700
+
1701
+ # Pipe — thread value through functions (Clojure-style ->>)
1702
+ # (pipe val f1 (f2 arg) {short-lambda})
1703
+ # - Symbol/lambda: called with val as sole arg
1704
+ # - Apply node: val is appended as last argument
1705
+ if isinstance(node, Pipe):
1706
+ val = self.eval(node.value, env)
1707
+ for fn_node in node.funcs:
1708
+ if isinstance(fn_node, Apply):
1709
+ # Thread: (reduce + 0) with piped val → (reduce + 0 val)
1710
+ fn = self.eval(fn_node.func, env)
1711
+ args = [self.eval(a, env) for a in fn_node.args]
1712
+ args.append(val)
1713
+ val = self._call_function(fn, args)
1714
+ else:
1715
+ fn = self.eval(fn_node, env)
1716
+ val = self._call_function(fn, [val])
1717
+ return val
1718
+
1719
+ # Throw
1720
+ if isinstance(node, Throw):
1721
+ val = self.eval(node.expr, env)
1722
+ raise RuntimeError_(self._display(val))
1723
+
1724
+ # Catch — (catch expr handler)
1725
+ if isinstance(node, Catch):
1726
+ try:
1727
+ return self.eval(node.expr, env)
1728
+ except (RuntimeError_, ProofError) as e:
1729
+ handler = self.eval(node.handler, env)
1730
+ return self._call_function(handler, [TengwarStr(str(e))])
1731
+
1732
+ # Python import
1733
+ if isinstance(node, PyImportNode):
1734
+ if not self.sandbox_allow_py:
1735
+ raise RuntimeError_("Python imports disabled in sandbox mode")
1736
+ mod_name = self.eval(node.module_name, env)
1737
+ if isinstance(mod_name, TengwarStr):
1738
+ import importlib
1739
+ try:
1740
+ py_mod = importlib.import_module(mod_name.value)
1741
+ tw_mod = TengwarPyObject(py_mod, mod_name.value)
1742
+ alias = node.alias or mod_name.value.split('.')[-1]
1743
+ env.set(alias, tw_mod)
1744
+ return tw_mod
1745
+ except ImportError as e:
1746
+ raise RuntimeError_(f"py-import: {e}")
1747
+ raise RuntimeError_("py-import requires string module name")
1748
+
1749
+ # Tuple
1750
+ if isinstance(node, Tuple):
1751
+ elements = [self.eval(e, env) for e in node.elements]
1752
+ return TengwarTuple(elements)
1753
+
1754
+ # Vector
1755
+ if isinstance(node, Vector):
1756
+ elements = [self.eval(e, env) for e in node.elements]
1757
+ return TengwarVector(elements)
1758
+
1759
+ # Application
1760
+ if isinstance(node, Apply):
1761
+ func = self.eval(node.func, env)
1762
+ # Handle dot access on modules
1763
+ if isinstance(func, TengwarModule):
1764
+ # (module.field args...)
1765
+ raise RuntimeError_("Direct module call not supported; use dot access")
1766
+
1767
+ args = [self.eval(a, env) for a in node.args]
1768
+ return self._call_function(func, args)
1769
+
1770
+ # Binary ops
1771
+ if isinstance(node, BinOp):
1772
+ left = self.eval(node.left, env)
1773
+ right = self.eval(node.right, env)
1774
+ return self._eval_binop(node.op, left, right)
1775
+
1776
+ # Unary ops
1777
+ if isinstance(node, UnOp):
1778
+ operand = self.eval(node.operand, env)
1779
+ return self._eval_unop(node.op, operand)
1780
+
1781
+ # Program
1782
+ if isinstance(node, Program):
1783
+ result = TengwarUnit()
1784
+ for expr in node.body:
1785
+ result = self.eval(expr, env)
1786
+ return result
1787
+
1788
+ raise RuntimeError_(f"Unknown node type: {type(node).__name__}")
1789
+
1790
+ def _eval_binop(self, op: str, left: TengwarValue, right: TengwarValue) -> TengwarValue:
1791
+ # String concatenation with +
1792
+ if op == '+' and isinstance(left, TengwarStr):
1793
+ return TengwarStr(left.value + self._display(right))
1794
+
1795
+ # Numeric operations
1796
+ if isinstance(left, (TengwarInt, TengwarFloat)) and isinstance(right, (TengwarInt, TengwarFloat)):
1797
+ lv, rv = left.value, right.value
1798
+ use_float = isinstance(left, TengwarFloat) or isinstance(right, TengwarFloat)
1799
+ wrap = TengwarFloat if use_float else TengwarInt
1800
+
1801
+ if op == '+': return wrap(lv + rv)
1802
+ if op == '-': return wrap(lv - rv)
1803
+ if op == '*': return wrap(lv * rv)
1804
+ if op == '/':
1805
+ if rv == 0: raise RuntimeError_("Division by zero")
1806
+ return TengwarFloat(lv / rv) if use_float else TengwarInt(lv // rv)
1807
+ if op == '%': return wrap(lv % rv)
1808
+ if op == '=': return TengwarBool(lv == rv)
1809
+ if op == '!=': return TengwarBool(lv != rv)
1810
+ if op == '<': return TengwarBool(lv < rv)
1811
+ if op == '>': return TengwarBool(lv > rv)
1812
+ if op == '<=': return TengwarBool(lv <= rv)
1813
+ if op == '>=': return TengwarBool(lv >= rv)
1814
+
1815
+ # Boolean operations
1816
+ if isinstance(left, TengwarBool) and isinstance(right, TengwarBool):
1817
+ if op == '&': return TengwarBool(left.value and right.value)
1818
+ if op == '|': return TengwarBool(left.value or right.value)
1819
+
1820
+ # Equality on any type
1821
+ if op == '=': return TengwarBool(left == right)
1822
+ if op == '!=': return TengwarBool(left != right)
1823
+
1824
+ raise RuntimeError_(f"Invalid operation: {repr(left)} {op} {repr(right)}")
1825
+
1826
+ def _eval_unop(self, op: str, operand: TengwarValue) -> TengwarValue:
1827
+ if op == '!' and isinstance(operand, TengwarBool):
1828
+ return TengwarBool(not operand.value)
1829
+ if op == '-' and isinstance(operand, TengwarInt):
1830
+ return TengwarInt(-operand.value)
1831
+ if op == '-' and isinstance(operand, TengwarFloat):
1832
+ return TengwarFloat(-operand.value)
1833
+ raise RuntimeError_(f"Invalid unary operation: {op} {repr(operand)}")
1834
+
1835
+ def run(self, program: Program) -> TengwarValue:
1836
+ """Run a parsed program"""
1837
+ return self.eval(program, self.global_env)
1838
+
1839
+ def run_source(self, source: str) -> TengwarValue:
1840
+ """Lex, parse, and run source code"""
1841
+ from .lexer import tokenize
1842
+ from .parser import parse
1843
+ tokens = tokenize(source)
1844
+ ast = parse(tokens)
1845
+ return self.run(ast)