tengwar 0.3.1__tar.gz → 0.3.3__tar.gz
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-0.3.1/tengwar.egg-info → tengwar-0.3.3}/PKG-INFO +1 -1
- {tengwar-0.3.1 → tengwar-0.3.3}/pyproject.toml +1 -1
- {tengwar-0.3.1 → tengwar-0.3.3}/tengwar/__init__.py +1 -1
- {tengwar-0.3.1 → tengwar-0.3.3}/tengwar/ast_nodes.py +2 -1
- {tengwar-0.3.1 → tengwar-0.3.3}/tengwar/interpreter.py +66 -15
- {tengwar-0.3.1 → tengwar-0.3.3}/tengwar/lexer.py +3 -1
- {tengwar-0.3.1 → tengwar-0.3.3}/tengwar/parser.py +5 -2
- {tengwar-0.3.1 → tengwar-0.3.3/tengwar.egg-info}/PKG-INFO +1 -1
- {tengwar-0.3.1 → tengwar-0.3.3}/LICENSE +0 -0
- {tengwar-0.3.1 → tengwar-0.3.3}/README.md +0 -0
- {tengwar-0.3.1 → tengwar-0.3.3}/setup.cfg +0 -0
- {tengwar-0.3.1 → tengwar-0.3.3}/tengwar/__main__.py +0 -0
- {tengwar-0.3.1 → tengwar-0.3.3}/tengwar/binary_ast.py +0 -0
- {tengwar-0.3.1 → tengwar-0.3.3}/tengwar/errors.py +0 -0
- {tengwar-0.3.1 → tengwar-0.3.3}/tengwar/mcp_server.py +0 -0
- {tengwar-0.3.1 → tengwar-0.3.3}/tengwar/repl.py +0 -0
- {tengwar-0.3.1 → tengwar-0.3.3}/tengwar/vm.py +0 -0
- {tengwar-0.3.1 → tengwar-0.3.3}/tengwar.egg-info/SOURCES.txt +0 -0
- {tengwar-0.3.1 → tengwar-0.3.3}/tengwar.egg-info/dependency_links.txt +0 -0
- {tengwar-0.3.1 → tengwar-0.3.3}/tengwar.egg-info/entry_points.txt +0 -0
- {tengwar-0.3.1 → tengwar-0.3.3}/tengwar.egg-info/top_level.txt +0 -0
- {tengwar-0.3.1 → tengwar-0.3.3}/tests/test_all.py +0 -0
- {tengwar-0.3.1 → tengwar-0.3.3}/tests/test_v03.py +0 -0
|
@@ -215,8 +215,9 @@ class TypeAnn(ASTNode):
|
|
|
215
215
|
|
|
216
216
|
@dataclass
|
|
217
217
|
class Proof(ASTNode):
|
|
218
|
-
"""(⊢ assertion) - proof obligation"""
|
|
218
|
+
"""(⊢ assertion) or (⊢ assertion message) - proof obligation"""
|
|
219
219
|
assertion: Any = None
|
|
220
|
+
message: Any = None
|
|
220
221
|
def __post_init__(self):
|
|
221
222
|
self.type = NodeType.PROOF
|
|
222
223
|
|
|
@@ -74,6 +74,21 @@ class TengwarVector(TengwarValue):
|
|
|
74
74
|
def __eq__(self, other): return isinstance(other, TengwarVector) and self.elements == other.elements
|
|
75
75
|
|
|
76
76
|
|
|
77
|
+
class TengwarLazy(TengwarValue):
|
|
78
|
+
"""Lazy sequence — generated on demand by take/head/etc."""
|
|
79
|
+
def __init__(self, gen_fn, init_val):
|
|
80
|
+
self.gen_fn = gen_fn # function: val -> next_val
|
|
81
|
+
self.init_val = init_val
|
|
82
|
+
def take(self, n, call_fn):
|
|
83
|
+
result = [self.init_val]
|
|
84
|
+
val = self.init_val
|
|
85
|
+
for _ in range(n - 1):
|
|
86
|
+
val = call_fn(self.gen_fn, [val])
|
|
87
|
+
result.append(val)
|
|
88
|
+
return result
|
|
89
|
+
def __repr__(self): return f'<lazy-seq from {repr(self.init_val)}>'
|
|
90
|
+
|
|
91
|
+
|
|
77
92
|
class TengwarClosure(TengwarValue):
|
|
78
93
|
def __init__(self, params: list, body, env: 'Environment', name: str = ""):
|
|
79
94
|
self.params = params
|
|
@@ -241,6 +256,8 @@ class Interpreter:
|
|
|
241
256
|
# Vector/sequence ops
|
|
242
257
|
env.set('vec', TengwarBuiltin('vec', lambda args: TengwarVector(list(args))))
|
|
243
258
|
env.set('push', TengwarBuiltin('push', self._builtin_push))
|
|
259
|
+
env.set('cons', TengwarBuiltin('cons', self._builtin_cons))
|
|
260
|
+
env.set('prepend', TengwarBuiltin('prepend', self._builtin_cons))
|
|
244
261
|
env.set('pop', TengwarBuiltin('pop', self._builtin_pop))
|
|
245
262
|
env.set('get', TengwarBuiltin('get', self._builtin_get))
|
|
246
263
|
env.set('set-at', TengwarBuiltin('set-at', self._builtin_set_at))
|
|
@@ -314,6 +331,7 @@ class Interpreter:
|
|
|
314
331
|
env.set('&', TengwarBuiltin('&', lambda args: self._eval_binop('&', args[0], args[1])))
|
|
315
332
|
env.set('|', TengwarBuiltin('|', lambda args: self._eval_binop('|', args[0], args[1])))
|
|
316
333
|
env.set('!', TengwarBuiltin('!', lambda args: self._eval_unop('!', args[0])))
|
|
334
|
+
env.set('not', TengwarBuiltin('not', lambda args: self._eval_unop('!', args[0])))
|
|
317
335
|
|
|
318
336
|
# === DICT / HASHMAP ===
|
|
319
337
|
env.set('dict', TengwarBuiltin('dict', self._builtin_dict))
|
|
@@ -583,6 +601,13 @@ class Interpreter:
|
|
|
583
601
|
raise RuntimeError_("push requires a vector")
|
|
584
602
|
return TengwarVector(vec.elements + [args[1]])
|
|
585
603
|
|
|
604
|
+
def _builtin_cons(self, args):
|
|
605
|
+
"""(cons element vector) — prepend element to front"""
|
|
606
|
+
elem, vec = args[0], args[1]
|
|
607
|
+
if not isinstance(vec, TengwarVector):
|
|
608
|
+
raise RuntimeError_("cons requires a vector as second argument")
|
|
609
|
+
return TengwarVector([elem] + vec.elements)
|
|
610
|
+
|
|
586
611
|
def _builtin_pop(self, args):
|
|
587
612
|
vec = args[0]
|
|
588
613
|
if not isinstance(vec, TengwarVector):
|
|
@@ -695,13 +720,15 @@ class Interpreter:
|
|
|
695
720
|
return TengwarVector(sorted(vec.elements, key=lambda e: e.value))
|
|
696
721
|
|
|
697
722
|
def _builtin_flatten(self, args):
|
|
698
|
-
|
|
699
|
-
|
|
700
|
-
|
|
701
|
-
|
|
702
|
-
|
|
703
|
-
|
|
704
|
-
|
|
723
|
+
def _deep_flatten(elements):
|
|
724
|
+
result = []
|
|
725
|
+
for e in elements:
|
|
726
|
+
if isinstance(e, TengwarVector):
|
|
727
|
+
result.extend(_deep_flatten(e.elements))
|
|
728
|
+
else:
|
|
729
|
+
result.append(e)
|
|
730
|
+
return result
|
|
731
|
+
return TengwarVector(_deep_flatten(args[0].elements))
|
|
705
732
|
|
|
706
733
|
def _builtin_zip(self, args):
|
|
707
734
|
vecs = [a.elements for a in args]
|
|
@@ -717,6 +744,8 @@ class Interpreter:
|
|
|
717
744
|
|
|
718
745
|
def _builtin_head(self, args):
|
|
719
746
|
vec = args[0]
|
|
747
|
+
if isinstance(vec, TengwarLazy):
|
|
748
|
+
return vec.init_val
|
|
720
749
|
if not vec.elements:
|
|
721
750
|
raise RuntimeError_("head of empty vector")
|
|
722
751
|
return vec.elements[0]
|
|
@@ -983,8 +1012,10 @@ class Interpreter:
|
|
|
983
1012
|
return TengwarInt(-1)
|
|
984
1013
|
|
|
985
1014
|
def _builtin_take(self, args):
|
|
986
|
-
n,
|
|
987
|
-
|
|
1015
|
+
n, seq = int(self._num_val(args[0])), args[1]
|
|
1016
|
+
if isinstance(seq, TengwarLazy):
|
|
1017
|
+
return TengwarVector(seq.take(n, self._call_function))
|
|
1018
|
+
return TengwarVector(seq.elements[:n])
|
|
988
1019
|
|
|
989
1020
|
def _builtin_drop(self, args):
|
|
990
1021
|
n, vec = int(self._num_val(args[0])), args[1]
|
|
@@ -1083,6 +1114,11 @@ class Interpreter:
|
|
|
1083
1114
|
return TengwarVector([val] * n)
|
|
1084
1115
|
|
|
1085
1116
|
def _builtin_iterate(self, args):
|
|
1117
|
+
"""(iterate fn init n) → eager vector, (iterate fn init) → lazy sequence"""
|
|
1118
|
+
if len(args) == 2:
|
|
1119
|
+
# Lazy form: return a TengwarLazy that take/head can consume
|
|
1120
|
+
fn, init = args[0], args[1]
|
|
1121
|
+
return TengwarLazy(fn, init)
|
|
1086
1122
|
fn, init, n = args[0], args[1], int(self._num_val(args[2]))
|
|
1087
1123
|
result = [init]
|
|
1088
1124
|
val = init
|
|
@@ -1092,9 +1128,21 @@ class Interpreter:
|
|
|
1092
1128
|
return TengwarVector(result)
|
|
1093
1129
|
|
|
1094
1130
|
def _builtin_juxt(self, args):
|
|
1095
|
-
"""(juxt f1 f2 ...
|
|
1096
|
-
|
|
1097
|
-
|
|
1131
|
+
"""(juxt f1 f2 ...) → fn, or ((juxt f1 f2 ...) val) → vector of results"""
|
|
1132
|
+
def _is_tengwar_callable(x):
|
|
1133
|
+
return isinstance(x, (TengwarBuiltin, TengwarClosure))
|
|
1134
|
+
# If last arg is not callable, treat it as the value to apply to
|
|
1135
|
+
if len(args) >= 2 and not _is_tengwar_callable(args[-1]):
|
|
1136
|
+
fns, val = args[:-1], args[-1]
|
|
1137
|
+
return TengwarVector([self._call_function(fn, [val]) for fn in fns])
|
|
1138
|
+
else:
|
|
1139
|
+
# Return a closure that applies all functions to a value
|
|
1140
|
+
fns = list(args)
|
|
1141
|
+
interp = self
|
|
1142
|
+
def juxt_fn(inner_args):
|
|
1143
|
+
val = inner_args[0]
|
|
1144
|
+
return TengwarVector([interp._call_function(fn, [val]) for fn in fns])
|
|
1145
|
+
return TengwarBuiltin('juxt*', juxt_fn)
|
|
1098
1146
|
|
|
1099
1147
|
def _builtin_min_by(self, args):
|
|
1100
1148
|
fn, vec = args[0], args[1]
|
|
@@ -1678,6 +1726,9 @@ class Interpreter:
|
|
|
1678
1726
|
if isinstance(node, Proof):
|
|
1679
1727
|
result = self.eval(node.assertion, env)
|
|
1680
1728
|
if not self._is_truthy(result):
|
|
1729
|
+
if node.message:
|
|
1730
|
+
msg = self.eval(node.message, env)
|
|
1731
|
+
raise ProofError(f"Proof obligation failed: {msg}")
|
|
1681
1732
|
raise ProofError(f"Proof obligation failed: {repr(result)}")
|
|
1682
1733
|
return result
|
|
1683
1734
|
|
|
@@ -1788,9 +1839,9 @@ class Interpreter:
|
|
|
1788
1839
|
raise RuntimeError_(f"Unknown node type: {type(node).__name__}")
|
|
1789
1840
|
|
|
1790
1841
|
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 +
|
|
1842
|
+
# String concatenation with + (both must be strings — no implicit coercion)
|
|
1843
|
+
if op == '+' and isinstance(left, TengwarStr) and isinstance(right, TengwarStr):
|
|
1844
|
+
return TengwarStr(left.value + right.value)
|
|
1794
1845
|
|
|
1795
1846
|
# Numeric operations
|
|
1796
1847
|
if isinstance(left, (TengwarInt, TengwarFloat)) and isinstance(right, (TengwarInt, TengwarFloat)):
|
|
@@ -120,7 +120,7 @@ KEYWORDS = {
|
|
|
120
120
|
'do': TokenType.SEQ,
|
|
121
121
|
'par': TokenType.PARALLEL,
|
|
122
122
|
'nil': TokenType.UNIT,
|
|
123
|
-
'
|
|
123
|
+
'module': TokenType.MODULE,
|
|
124
124
|
# Note: let, pipe, throw, catch, try are handled as symbols
|
|
125
125
|
# by the parser since they need custom parse rules
|
|
126
126
|
}
|
|
@@ -130,6 +130,8 @@ SINGLE_CHAR = {
|
|
|
130
130
|
')': TokenType.RPAREN,
|
|
131
131
|
'{': TokenType.LBRACE,
|
|
132
132
|
'}': TokenType.RBRACE,
|
|
133
|
+
'[': TokenType.LBRACKET,
|
|
134
|
+
']': TokenType.RBRACKET,
|
|
133
135
|
'?': TokenType.COND,
|
|
134
136
|
'~': TokenType.MATCH,
|
|
135
137
|
'+': TokenType.PLUS,
|
|
@@ -426,11 +426,14 @@ class Parser:
|
|
|
426
426
|
return Recurse(name=name, body=body, line=line, col=col)
|
|
427
427
|
|
|
428
428
|
def parse_proof(self, line: int, col: int) -> Proof:
|
|
429
|
-
"""(⊢ assertion)"""
|
|
429
|
+
"""(⊢ assertion) or (⊢ assertion message)"""
|
|
430
430
|
self.advance() # consume ⊢
|
|
431
431
|
assertion = self.parse_expr()
|
|
432
|
+
message = None
|
|
433
|
+
if self.peek_type() != TokenType.RPAREN:
|
|
434
|
+
message = self.parse_expr()
|
|
432
435
|
self.expect(TokenType.RPAREN)
|
|
433
|
-
return Proof(assertion=assertion, line=line, col=col)
|
|
436
|
+
return Proof(assertion=assertion, message=message, line=line, col=col)
|
|
434
437
|
|
|
435
438
|
def parse_mutate(self, line: int, col: int) -> Mutate:
|
|
436
439
|
"""(μ name value)"""
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|