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/__init__.py +20 -0
- tengwar/__main__.py +8 -0
- tengwar/ast_nodes.py +351 -0
- tengwar/binary_ast.py +654 -0
- tengwar/errors.py +43 -0
- tengwar/interpreter.py +1845 -0
- tengwar/lexer.py +483 -0
- tengwar/mcp_server.py +496 -0
- tengwar/parser.py +603 -0
- tengwar/repl.py +152 -0
- tengwar/vm.py +425 -0
- tengwar-0.3.1.dist-info/METADATA +202 -0
- tengwar-0.3.1.dist-info/RECORD +17 -0
- tengwar-0.3.1.dist-info/WHEEL +5 -0
- tengwar-0.3.1.dist-info/entry_points.txt +2 -0
- tengwar-0.3.1.dist-info/licenses/LICENSE +21 -0
- tengwar-0.3.1.dist-info/top_level.txt +1 -0
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)
|