klongpy 0.6.9__py3-none-any.whl → 0.7.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.
- klongpy/__init__.py +17 -1
- klongpy/adverbs.py +84 -82
- klongpy/autograd.py +299 -0
- klongpy/backend.py +38 -103
- klongpy/backends/__init__.py +26 -0
- klongpy/backends/base.py +469 -0
- klongpy/backends/numpy_backend.py +123 -0
- klongpy/backends/registry.py +76 -0
- klongpy/backends/torch_backend.py +1047 -0
- klongpy-0.6.9.data/scripts/kgpy → klongpy/cli.py +110 -90
- klongpy/core.py +113 -974
- klongpy/db/sys_fn_db.py +7 -6
- klongpy/db/sys_fn_kvs.py +2 -4
- klongpy/dyads.py +332 -160
- klongpy/interpreter.py +60 -15
- klongpy/monads.py +121 -75
- klongpy/parser.py +328 -0
- klongpy/repl.py +23 -5
- klongpy/sys_fn.py +170 -21
- klongpy/sys_fn_autograd.py +290 -0
- klongpy/sys_fn_ipc.py +22 -15
- klongpy/sys_fn_timer.py +13 -3
- klongpy/types.py +503 -0
- klongpy/web/sys_fn_web.py +14 -4
- klongpy/writer.py +122 -0
- klongpy/ws/sys_fn_ws.py +5 -8
- klongpy-0.7.1.dist-info/METADATA +544 -0
- klongpy-0.7.1.dist-info/RECORD +52 -0
- {klongpy-0.6.9.dist-info → klongpy-0.7.1.dist-info}/WHEEL +1 -1
- klongpy-0.7.1.dist-info/entry_points.txt +2 -0
- {klongpy-0.6.9.dist-info → klongpy-0.7.1.dist-info}/top_level.txt +0 -1
- klongpy-0.6.9.dist-info/METADATA +0 -448
- klongpy-0.6.9.dist-info/RECORD +0 -77
- tests/__init__.py +0 -6
- tests/gen_join_over.py +0 -119
- tests/gen_py_suite.py +0 -77
- tests/gen_test_fn.py +0 -259
- tests/perf_async.py +0 -25
- tests/perf_avg.py +0 -18
- tests/perf_duckdb.py +0 -32
- tests/perf_gen.py +0 -38
- tests/perf_ipc_overhead.py +0 -34
- tests/perf_join.py +0 -53
- tests/perf_load.py +0 -17
- tests/perf_prog.py +0 -18
- tests/perf_serdes.py +0 -52
- tests/perf_sys_fn_db.py +0 -263
- tests/perf_vector.py +0 -40
- tests/test_accel.py +0 -227
- tests/test_df_cache.py +0 -85
- tests/test_eval_monad_list.py +0 -34
- tests/test_examples.py +0 -64
- tests/test_extra_suite.py +0 -382
- tests/test_file_cache.py +0 -185
- tests/test_interop.py +0 -180
- tests/test_kg_asarray.py +0 -94
- tests/test_kgtests.py +0 -65
- tests/test_known_bugs.py +0 -206
- tests/test_prog.py +0 -107
- tests/test_reshape_strings.py +0 -33
- tests/test_suite.py +0 -1480
- tests/test_suite_file.py +0 -153
- tests/test_sys_fn.py +0 -420
- tests/test_sys_fn_db.py +0 -88
- tests/test_sys_fn_ipc.py +0 -587
- tests/test_sys_fn_timer.py +0 -133
- tests/test_sys_fn_web.py +0 -50
- tests/test_util.py +0 -233
- tests/utils.py +0 -126
- {klongpy-0.6.9.dist-info → klongpy-0.7.1.dist-info}/licenses/LICENSE +0 -0
klongpy/parser.py
ADDED
|
@@ -0,0 +1,328 @@
|
|
|
1
|
+
"""
|
|
2
|
+
KlongPy parser and lexer functions.
|
|
3
|
+
|
|
4
|
+
This module contains all the parsing functions for the Klong language:
|
|
5
|
+
- Lexeme reading (numbers, strings, symbols, operators)
|
|
6
|
+
- List parsing
|
|
7
|
+
- Conditional expression parsing
|
|
8
|
+
- Comment handling
|
|
9
|
+
"""
|
|
10
|
+
import copy
|
|
11
|
+
|
|
12
|
+
from .types import (
|
|
13
|
+
KGSym, KGChar, KGOp, KGCond, KGCall, KGLambda,
|
|
14
|
+
reserved_fn_symbol_map,
|
|
15
|
+
safe_eq, is_symbolic, is_adverb
|
|
16
|
+
)
|
|
17
|
+
|
|
18
|
+
|
|
19
|
+
# Character matching utilities
|
|
20
|
+
|
|
21
|
+
def cmatch(t, i, c):
|
|
22
|
+
return i < len(t) and t[i] == c
|
|
23
|
+
|
|
24
|
+
|
|
25
|
+
def cmatch2(t, i, a, b):
|
|
26
|
+
return cmatch(t, i, a) and cmatch(t, i+1, b)
|
|
27
|
+
|
|
28
|
+
|
|
29
|
+
def cpeek(t, i):
|
|
30
|
+
return t[i] if i < len(t) else None
|
|
31
|
+
|
|
32
|
+
|
|
33
|
+
def cpeek2(t, i):
|
|
34
|
+
return t[i:i+2] if i < (len(t)-1) else None
|
|
35
|
+
|
|
36
|
+
|
|
37
|
+
class UnexpectedChar(Exception):
|
|
38
|
+
def __init__(self, t, i, c):
|
|
39
|
+
super().__init__(f"t: {t[i-10:i+10]} pos: {i} char: {c}")
|
|
40
|
+
|
|
41
|
+
|
|
42
|
+
class UnexpectedEOF(Exception):
|
|
43
|
+
def __init__(self, t, i):
|
|
44
|
+
self.t = t
|
|
45
|
+
self.i = i
|
|
46
|
+
super().__init__(f"t: {t[i-10:]} pos: {i}")
|
|
47
|
+
|
|
48
|
+
|
|
49
|
+
def cexpect(t, i, c):
|
|
50
|
+
if cmatch(t, i, c):
|
|
51
|
+
return i + 1
|
|
52
|
+
raise UnexpectedChar(t, i, c)
|
|
53
|
+
|
|
54
|
+
|
|
55
|
+
def cexpect2(t, i, a, b):
|
|
56
|
+
if cmatch(t, i, a) and cmatch(t, i+1, b):
|
|
57
|
+
return i + 2
|
|
58
|
+
raise UnexpectedChar(t, i, b)
|
|
59
|
+
|
|
60
|
+
|
|
61
|
+
# Comment handling
|
|
62
|
+
|
|
63
|
+
def read_shifted_comment(t, i=0):
|
|
64
|
+
while i < len(t):
|
|
65
|
+
c = t[i]
|
|
66
|
+
if c == '"':
|
|
67
|
+
i += 1
|
|
68
|
+
if not cmatch(t, i, '"'):
|
|
69
|
+
break
|
|
70
|
+
i += 1
|
|
71
|
+
return i
|
|
72
|
+
|
|
73
|
+
|
|
74
|
+
def read_sys_comment(t, i, a):
|
|
75
|
+
"""
|
|
76
|
+
.comment(x) [Comment]
|
|
77
|
+
|
|
78
|
+
Read and discard lines until the current line starts with the
|
|
79
|
+
string specified in "x". Also discard the line containing the
|
|
80
|
+
end-of-comment marker and return "x".
|
|
81
|
+
|
|
82
|
+
Example: .comment("end-of-comment")
|
|
83
|
+
this will be ignored
|
|
84
|
+
this, too: *%(*^#)&(#
|
|
85
|
+
end-of-comment
|
|
86
|
+
|
|
87
|
+
NOTE: this is handled in the parsing phase and is not a runtime function
|
|
88
|
+
"""
|
|
89
|
+
try:
|
|
90
|
+
j = t[i:].index(a)
|
|
91
|
+
while t[i+j+1:].startswith(a):
|
|
92
|
+
j += 1
|
|
93
|
+
return i + j + len(a)
|
|
94
|
+
except ValueError:
|
|
95
|
+
return RuntimeError("end of comment not found")
|
|
96
|
+
|
|
97
|
+
|
|
98
|
+
# Whitespace handling
|
|
99
|
+
|
|
100
|
+
def skip_space(t, i=0, ignore_newline=False):
|
|
101
|
+
"""
|
|
102
|
+
NOTE: a newline character translates to a semicolon in Klong,
|
|
103
|
+
except in functions, dictionaries, conditional expressions,
|
|
104
|
+
and lists. So
|
|
105
|
+
"""
|
|
106
|
+
while i < len(t) and (t[i].isspace() and (ignore_newline or t[i] != '\n')):
|
|
107
|
+
i += 1
|
|
108
|
+
return i
|
|
109
|
+
|
|
110
|
+
|
|
111
|
+
def skip(t, i=0, ignore_newline=False):
|
|
112
|
+
i = skip_space(t, i, ignore_newline=ignore_newline)
|
|
113
|
+
if cmatch2(t, i, ':', '"'):
|
|
114
|
+
i = read_shifted_comment(t, i+2)
|
|
115
|
+
i = skip(t, i)
|
|
116
|
+
return i
|
|
117
|
+
|
|
118
|
+
|
|
119
|
+
# Lexeme readers
|
|
120
|
+
|
|
121
|
+
def read_num(t, i=0):
|
|
122
|
+
p = i
|
|
123
|
+
use_float = False
|
|
124
|
+
if t[i] == '-':
|
|
125
|
+
i += 1
|
|
126
|
+
while i < len(t):
|
|
127
|
+
if t[i] == '.':
|
|
128
|
+
use_float = True
|
|
129
|
+
elif t[i] == 'e':
|
|
130
|
+
use_float = True
|
|
131
|
+
if cmatch(t, i+1, '-'):
|
|
132
|
+
i += 2
|
|
133
|
+
elif not t[i].isnumeric():
|
|
134
|
+
break
|
|
135
|
+
i += 1
|
|
136
|
+
return i, float(t[p:i]) if use_float else int(t[p:i])
|
|
137
|
+
|
|
138
|
+
|
|
139
|
+
def read_char(t, i):
|
|
140
|
+
i = cexpect2(t, i, '0', 'c')
|
|
141
|
+
if i >= len(t):
|
|
142
|
+
raise UnexpectedEOF(t, i)
|
|
143
|
+
return i+1, KGChar(t[i])
|
|
144
|
+
|
|
145
|
+
|
|
146
|
+
def read_sym(t, i=0, module=None):
|
|
147
|
+
p = i
|
|
148
|
+
while i < len(t) and is_symbolic(t[i]):
|
|
149
|
+
i += 1
|
|
150
|
+
x = t[p:i]
|
|
151
|
+
return i, reserved_fn_symbol_map.get(x) or KGSym(x if x.startswith('.') or module is None else f"{x}`{module}")
|
|
152
|
+
|
|
153
|
+
|
|
154
|
+
def read_op(t, i=0):
|
|
155
|
+
if cmatch2(t, i, '\\', '~') or cmatch2(t, i, '\\', '*'):
|
|
156
|
+
return i+2, KGOp(t[i:i+2], arity=0)
|
|
157
|
+
return i+1, KGOp(t[i:i+1], arity=0)
|
|
158
|
+
|
|
159
|
+
|
|
160
|
+
def read_string(t, i=0):
|
|
161
|
+
"""
|
|
162
|
+
".*" [String]
|
|
163
|
+
|
|
164
|
+
A string is (almost) any sequence of characters enclosed by
|
|
165
|
+
double quote characters. To include a double quote character in
|
|
166
|
+
a string, it has to be duplicated, so the above regex is not
|
|
167
|
+
entirely correct. A comment is a shifted string (see below).
|
|
168
|
+
Examples: ""
|
|
169
|
+
"hello, world"
|
|
170
|
+
"say ""hello""!"
|
|
171
|
+
|
|
172
|
+
Note: this comforms to the KG read_string impl.
|
|
173
|
+
perf tests show that the final join is fast for short strings
|
|
174
|
+
"""
|
|
175
|
+
r = []
|
|
176
|
+
while i < len(t):
|
|
177
|
+
c = t[i]
|
|
178
|
+
if c == '"':
|
|
179
|
+
i += 1
|
|
180
|
+
if not cmatch(t, i, '"'):
|
|
181
|
+
break
|
|
182
|
+
r.append(c)
|
|
183
|
+
i += 1
|
|
184
|
+
return i, "".join(r)
|
|
185
|
+
|
|
186
|
+
|
|
187
|
+
# Dictionary helper
|
|
188
|
+
|
|
189
|
+
def list_to_dict(a):
|
|
190
|
+
return {x[0]:x[1] for x in a}
|
|
191
|
+
|
|
192
|
+
|
|
193
|
+
# Lambda for copy operations (used in dict parsing)
|
|
194
|
+
copy_lambda = KGLambda(lambda x: copy.deepcopy(x))
|
|
195
|
+
|
|
196
|
+
|
|
197
|
+
def read_list(t, delim, i=0, module=None):
|
|
198
|
+
"""
|
|
199
|
+
Parse a list from string t starting at position i.
|
|
200
|
+
Returns a Python list (caller converts to array if needed).
|
|
201
|
+
|
|
202
|
+
L := '[' (C|L)* ']'
|
|
203
|
+
"""
|
|
204
|
+
arr = []
|
|
205
|
+
i = skip(t, i, ignore_newline=True)
|
|
206
|
+
while not cmatch(t, i, delim) and i < len(t):
|
|
207
|
+
i, q = kg_read(t, i, read_neg=True, ignore_newline=True, module=module)
|
|
208
|
+
if q is None:
|
|
209
|
+
break
|
|
210
|
+
if safe_eq(q, '['):
|
|
211
|
+
i, q = read_list(t, ']', i=i, module=module)
|
|
212
|
+
arr.append(q)
|
|
213
|
+
i = skip(t, i, ignore_newline=True)
|
|
214
|
+
if cmatch(t, i, delim):
|
|
215
|
+
i += 1
|
|
216
|
+
return i, arr
|
|
217
|
+
|
|
218
|
+
|
|
219
|
+
def kg_read(t, i, read_neg=False, ignore_newline=False, module=None):
|
|
220
|
+
"""
|
|
221
|
+
Read a Klong lexeme from string t starting at position i.
|
|
222
|
+
|
|
223
|
+
C := I | H | R | S | V | Y
|
|
224
|
+
"""
|
|
225
|
+
i = skip(t, i, ignore_newline=ignore_newline)
|
|
226
|
+
if i >= len(t):
|
|
227
|
+
return i, None
|
|
228
|
+
a = t[i]
|
|
229
|
+
if a == '\n':
|
|
230
|
+
a = ';'
|
|
231
|
+
if a in [';', '(', ')', '{', '}', ']']:
|
|
232
|
+
return i+1, a
|
|
233
|
+
elif cmatch2(t, i, '0', 'c'):
|
|
234
|
+
return read_char(t, i)
|
|
235
|
+
elif a.isnumeric() or (read_neg and (a == '-' and (i+1) < len(t) and t[i+1].isnumeric())):
|
|
236
|
+
return read_num(t, i)
|
|
237
|
+
elif a == '"':
|
|
238
|
+
return read_string(t, i+1)
|
|
239
|
+
elif a == ':' and (i+1 < len(t)):
|
|
240
|
+
aa = t[i+1]
|
|
241
|
+
if aa.isalpha() or aa == '.':
|
|
242
|
+
return read_sym(t, i=i+1, module=module)
|
|
243
|
+
elif aa.isnumeric() or aa == '"':
|
|
244
|
+
return kg_read(t, i+1, ignore_newline=ignore_newline, module=module)
|
|
245
|
+
elif aa == '{':
|
|
246
|
+
i, d = read_list(t, '}', i=i+2, module=module)
|
|
247
|
+
d = list_to_dict(d)
|
|
248
|
+
return i, KGCall(copy_lambda, args=d, arity=0)
|
|
249
|
+
elif aa == '[':
|
|
250
|
+
return i+2, ':['
|
|
251
|
+
elif aa == '|':
|
|
252
|
+
return i+2, ':|'
|
|
253
|
+
return i+2, KGOp(f":{aa}", arity=0)
|
|
254
|
+
elif safe_eq(a, '['):
|
|
255
|
+
return read_list(t, ']', i=i+1, module=module)
|
|
256
|
+
elif is_symbolic(a):
|
|
257
|
+
return read_sym(t, i, module=module)
|
|
258
|
+
return read_op(t, i)
|
|
259
|
+
|
|
260
|
+
|
|
261
|
+
def kg_read_array(t, i, backend, **kwargs):
|
|
262
|
+
"""
|
|
263
|
+
Read a value and convert lists to arrays using the provided backend.
|
|
264
|
+
|
|
265
|
+
This is a helper that wraps kg_read and handles list-to-array conversion,
|
|
266
|
+
centralizing the pattern used by the interpreter and eval_sys.
|
|
267
|
+
|
|
268
|
+
Parameters
|
|
269
|
+
----------
|
|
270
|
+
t : str
|
|
271
|
+
The string to read from.
|
|
272
|
+
i : int
|
|
273
|
+
Starting position in the string.
|
|
274
|
+
backend : BackendProvider
|
|
275
|
+
The backend to use for array conversion.
|
|
276
|
+
**kwargs
|
|
277
|
+
Additional arguments passed to kg_read (read_neg, ignore_newline, module).
|
|
278
|
+
|
|
279
|
+
Returns
|
|
280
|
+
-------
|
|
281
|
+
tuple
|
|
282
|
+
(new_position, value) where value is converted to an array if it was a list.
|
|
283
|
+
"""
|
|
284
|
+
i, a = kg_read(t, i, **kwargs)
|
|
285
|
+
if isinstance(a, list):
|
|
286
|
+
a = backend.kg_asarray(a)
|
|
287
|
+
return i, a
|
|
288
|
+
|
|
289
|
+
|
|
290
|
+
def read_cond(klong, t, i=0):
|
|
291
|
+
"""
|
|
292
|
+
# A conditional expression has two forms: :[e1;e2;e3] means "if
|
|
293
|
+
# e1 is true, evaluate to e2, else evaluate to e3".
|
|
294
|
+
# :[e1;e2:|e3;e4;e5] is short for :[e1;e2:[e3;e4;e5]], i.e. the
|
|
295
|
+
# ":|" acts as an "else-if" operator. There may be any number of
|
|
296
|
+
# ":|" operators in a conditional.
|
|
297
|
+
|
|
298
|
+
c := ':[' ( e ';' e ':|' )* e ';' e ';' e ']'
|
|
299
|
+
"""
|
|
300
|
+
r = []
|
|
301
|
+
i, n = klong._expr(t, i, ignore_newline=True)
|
|
302
|
+
r.append(n)
|
|
303
|
+
i = cexpect(t, i, ';')
|
|
304
|
+
i, n = klong._expr(t, i, ignore_newline=True)
|
|
305
|
+
r.append(n)
|
|
306
|
+
i = skip(t, i, ignore_newline=True)
|
|
307
|
+
if cmatch2(t, i, ':', '|'):
|
|
308
|
+
i, n = read_cond(klong, t, i+2)
|
|
309
|
+
r.append(n)
|
|
310
|
+
else:
|
|
311
|
+
i = cexpect(t, i, ';')
|
|
312
|
+
i, n = klong._expr(t, i, ignore_newline=True)
|
|
313
|
+
r.append(n)
|
|
314
|
+
i = skip(t, i, ignore_newline=True)
|
|
315
|
+
i = cexpect(t, i, ']')
|
|
316
|
+
return i, KGCond(r)
|
|
317
|
+
|
|
318
|
+
|
|
319
|
+
# Adverb peeking
|
|
320
|
+
|
|
321
|
+
def peek_adverb(t, i=0):
|
|
322
|
+
x = cpeek2(t, i)
|
|
323
|
+
if is_adverb(x):
|
|
324
|
+
return i+2, x
|
|
325
|
+
x = cpeek(t, i)
|
|
326
|
+
if is_adverb(x):
|
|
327
|
+
return i+1, x
|
|
328
|
+
return i, None
|
klongpy/repl.py
CHANGED
|
@@ -3,12 +3,30 @@ import threading
|
|
|
3
3
|
import time
|
|
4
4
|
import os
|
|
5
5
|
import importlib.resources
|
|
6
|
+
from typing import Optional
|
|
6
7
|
|
|
7
8
|
from . import KlongInterpreter
|
|
8
9
|
from .utils import CallbackEvent
|
|
9
10
|
|
|
10
11
|
|
|
11
|
-
|
|
12
|
+
class LoopStopper:
|
|
13
|
+
def __init__(self, loop: asyncio.AbstractEventLoop) -> None:
|
|
14
|
+
self._loop = loop
|
|
15
|
+
self._future = loop.create_future()
|
|
16
|
+
|
|
17
|
+
def set(self) -> None:
|
|
18
|
+
if self._future.done():
|
|
19
|
+
return
|
|
20
|
+
if self._loop.is_running():
|
|
21
|
+
self._loop.call_soon_threadsafe(self._future.set_result, None)
|
|
22
|
+
else:
|
|
23
|
+
self._future.set_result(None)
|
|
24
|
+
|
|
25
|
+
async def wait(self) -> None:
|
|
26
|
+
await self._future
|
|
27
|
+
|
|
28
|
+
|
|
29
|
+
def start_loop(loop: asyncio.AbstractEventLoop, stop_event: LoopStopper) -> None:
|
|
12
30
|
asyncio.set_event_loop(loop)
|
|
13
31
|
loop.run_until_complete(stop_event.wait())
|
|
14
32
|
|
|
@@ -18,13 +36,13 @@ def setup_async_loop(debug: bool = False, slow_callback_duration: float = 86400.
|
|
|
18
36
|
loop.slow_callback_duration = slow_callback_duration
|
|
19
37
|
if debug:
|
|
20
38
|
loop.set_debug(True)
|
|
21
|
-
stop_event =
|
|
39
|
+
stop_event = LoopStopper(loop)
|
|
22
40
|
thread = threading.Thread(target=start_loop, args=(loop, stop_event), daemon=True)
|
|
23
41
|
thread.start()
|
|
24
42
|
return loop, thread, stop_event
|
|
25
43
|
|
|
26
44
|
|
|
27
|
-
def cleanup_async_loop(loop: asyncio.AbstractEventLoop, loop_thread: threading.Thread, stop_event:
|
|
45
|
+
def cleanup_async_loop(loop: asyncio.AbstractEventLoop, loop_thread: threading.Thread, stop_event: LoopStopper, debug: bool = False, name: Optional[str] = None) -> None:
|
|
28
46
|
if loop.is_closed():
|
|
29
47
|
return
|
|
30
48
|
|
|
@@ -54,13 +72,13 @@ def append_pkg_resource_path_KLONGPATH() -> None:
|
|
|
54
72
|
os.environ['KLONGPATH'] = klongpath
|
|
55
73
|
|
|
56
74
|
|
|
57
|
-
def create_repl(debug: bool = False):
|
|
75
|
+
def create_repl(debug: bool = False, backend: Optional[str] = None, device: Optional[str] = None):
|
|
58
76
|
io_loop, io_thread, io_stop = setup_async_loop(debug=debug)
|
|
59
77
|
klong_loop, klong_thread, klong_stop = setup_async_loop(debug=debug)
|
|
60
78
|
|
|
61
79
|
append_pkg_resource_path_KLONGPATH()
|
|
62
80
|
|
|
63
|
-
klong = KlongInterpreter()
|
|
81
|
+
klong = KlongInterpreter(backend=backend, device=device)
|
|
64
82
|
shutdown_event = CallbackEvent()
|
|
65
83
|
klong['.system'] = {'ioloop': io_loop, 'klongloop': klong_loop, 'closeEvent': shutdown_event}
|
|
66
84
|
|
klongpy/sys_fn.py
CHANGED
|
@@ -12,10 +12,24 @@ from inspect import Parameter
|
|
|
12
12
|
import numpy
|
|
13
13
|
|
|
14
14
|
from .core import (KGChannel, KGChannelDir, KGLambda, KGSym, KlongException,
|
|
15
|
-
is_dict, is_empty, is_list,
|
|
15
|
+
bknp, is_dict, is_empty, is_list, kg_read_array, kg_write,
|
|
16
16
|
reserved_fn_args, reserved_fn_symbol_map, safe_eq, safe_inspect)
|
|
17
17
|
|
|
18
18
|
|
|
19
|
+
def _to_display_value(x, backend):
|
|
20
|
+
"""Convert backend tensors to numpy for cleaner display."""
|
|
21
|
+
# Convert backend arrays (tensors) to numpy
|
|
22
|
+
if backend.is_backend_array(x):
|
|
23
|
+
return backend.to_numpy(x)
|
|
24
|
+
# Handle numpy arrays with tensors inside (object arrays)
|
|
25
|
+
if isinstance(x, numpy.ndarray) and x.dtype == object:
|
|
26
|
+
return numpy.array([_to_display_value(item, backend) for item in x], dtype=object)
|
|
27
|
+
# Handle lists with tensors
|
|
28
|
+
if isinstance(x, list):
|
|
29
|
+
return [_to_display_value(item, backend) for item in x]
|
|
30
|
+
return x
|
|
31
|
+
|
|
32
|
+
|
|
19
33
|
def eval_sys_append_channel(x):
|
|
20
34
|
"""
|
|
21
35
|
|
|
@@ -47,10 +61,25 @@ def eval_sys_display(klong, x):
|
|
|
47
61
|
|
|
48
62
|
.d(x) [Display]
|
|
49
63
|
|
|
50
|
-
|
|
64
|
+
Display the object "x". Backend arrays are converted to numpy for cleaner output.
|
|
65
|
+
Use .bkd() for raw backend-specific display.
|
|
51
66
|
|
|
52
67
|
"""
|
|
53
|
-
|
|
68
|
+
x = _to_display_value(x, klong._backend)
|
|
69
|
+
r = kg_write(x, klong._backend, display=True)
|
|
70
|
+
klong['.sys.cout'].raw.write(r)
|
|
71
|
+
return r
|
|
72
|
+
|
|
73
|
+
|
|
74
|
+
def eval_sys_backend_display(klong, x):
|
|
75
|
+
"""
|
|
76
|
+
|
|
77
|
+
.bkd(x) [Backend-Display]
|
|
78
|
+
|
|
79
|
+
Display the object "x" in raw backend format (tensors shown as-is).
|
|
80
|
+
|
|
81
|
+
"""
|
|
82
|
+
r = kg_write(x, klong._backend, display=True)
|
|
54
83
|
klong['.sys.cout'].raw.write(r)
|
|
55
84
|
return r
|
|
56
85
|
|
|
@@ -272,10 +301,26 @@ def eval_sys_print(klong, x):
|
|
|
272
301
|
.p(x) [Print]
|
|
273
302
|
|
|
274
303
|
Pretty-print the object "x" (like Display) and then print a
|
|
275
|
-
newline sequence.
|
|
304
|
+
newline sequence. Backend arrays are converted to numpy for cleaner output.
|
|
305
|
+
Use .bkp() for raw backend-specific print.
|
|
276
306
|
|
|
277
307
|
"""
|
|
278
|
-
|
|
308
|
+
x = _to_display_value(x, klong._backend)
|
|
309
|
+
o = kg_write(x, klong._backend, display=True)
|
|
310
|
+
klong['.sys.cout'].raw.write(o+"\n")
|
|
311
|
+
return o
|
|
312
|
+
|
|
313
|
+
|
|
314
|
+
def eval_sys_backend_print(klong, x):
|
|
315
|
+
"""
|
|
316
|
+
|
|
317
|
+
.bkp(x) [Backend-Print]
|
|
318
|
+
|
|
319
|
+
Pretty-print the object "x" in raw backend format (tensors shown as-is)
|
|
320
|
+
and then print a newline sequence.
|
|
321
|
+
|
|
322
|
+
"""
|
|
323
|
+
o = kg_write(x, klong._backend, display=True)
|
|
279
324
|
klong['.sys.cout'].raw.write(o+"\n")
|
|
280
325
|
return o
|
|
281
326
|
|
|
@@ -341,20 +386,30 @@ def _handle_import(item):
|
|
|
341
386
|
if n_args <= len(reserved_fn_args):
|
|
342
387
|
item = KGLambda(item, args=reserved_fn_args[:n_args])
|
|
343
388
|
else:
|
|
344
|
-
|
|
345
|
-
if 'args' in
|
|
389
|
+
sig_args = safe_inspect(item, follow_wrapped=True)
|
|
390
|
+
if 'args' in sig_args:
|
|
346
391
|
item = KGLambda(item, args=None, wildcard=True)
|
|
347
392
|
n_args = 3
|
|
348
393
|
else:
|
|
349
|
-
|
|
350
|
-
|
|
351
|
-
#
|
|
352
|
-
|
|
353
|
-
|
|
354
|
-
|
|
355
|
-
|
|
356
|
-
|
|
357
|
-
|
|
394
|
+
# Get required args (no default)
|
|
395
|
+
required_args = [k for k,v in sig_args.items() if (v.kind == Parameter.POSITIONAL_OR_KEYWORD and v.default == Parameter.empty) or (v.kind == Parameter.POSITIONAL_ONLY)]
|
|
396
|
+
# Get optional args (have default)
|
|
397
|
+
optional_args = [k for k,v in sig_args.items() if v.kind == Parameter.POSITIONAL_OR_KEYWORD and v.default != Parameter.empty]
|
|
398
|
+
# Use required args count, but if there are optional args and no required args,
|
|
399
|
+
# use wildcard mode so the function can accept 0-3 args
|
|
400
|
+
if not required_args and optional_args:
|
|
401
|
+
item = KGLambda(item, args=None, wildcard=True)
|
|
402
|
+
n_args = 3
|
|
403
|
+
else:
|
|
404
|
+
args = required_args
|
|
405
|
+
n_args = len(args)
|
|
406
|
+
# if there are kwargs, then .pyc() must be used to call this function to override them
|
|
407
|
+
if 'klong' in args:
|
|
408
|
+
n_args -= 1
|
|
409
|
+
assert n_args <= len(reserved_fn_args)
|
|
410
|
+
item = KGLambda(item, args=reserved_fn_args[:n_args], provide_klong=True)
|
|
411
|
+
elif n_args <= len(reserved_fn_args):
|
|
412
|
+
item = KGLambda(item, args=reserved_fn_args[:n_args])
|
|
358
413
|
except Exception:
|
|
359
414
|
if hasattr(item, "__class__") and hasattr(item.__class__, '__module__') and item.__class__.__module__ == "builtins":
|
|
360
415
|
# LOOK AWAY. You didn't see this.
|
|
@@ -415,6 +470,17 @@ def _import_module(klong, x, from_set=None):
|
|
|
415
470
|
except Exception as e:
|
|
416
471
|
# TODO: this should be logged
|
|
417
472
|
print(f"failed to import function: {name}", e)
|
|
473
|
+
|
|
474
|
+
# For from_set imports, also check for lazy-loaded attributes not in __dict__
|
|
475
|
+
# (e.g., numpy.random in numpy 2.x)
|
|
476
|
+
if from_set is not None:
|
|
477
|
+
for name in from_set:
|
|
478
|
+
if name not in export_items and hasattr(module, name):
|
|
479
|
+
try:
|
|
480
|
+
item = getattr(module, name)
|
|
481
|
+
klong[name] = _handle_import(item)
|
|
482
|
+
except Exception as e:
|
|
483
|
+
print(f"failed to import function: {name}", e)
|
|
418
484
|
finally:
|
|
419
485
|
klong._context.push(ctx)
|
|
420
486
|
|
|
@@ -578,6 +644,49 @@ def eval_sys_python_from(klong, x, y):
|
|
|
578
644
|
return _import_module(klong, x, from_set=set(y))
|
|
579
645
|
|
|
580
646
|
|
|
647
|
+
def eval_sys_backend_fn(klong, x):
|
|
648
|
+
"""
|
|
649
|
+
|
|
650
|
+
.bkf(x) [Backend-Function]
|
|
651
|
+
|
|
652
|
+
Import functions from the current backend's array module.
|
|
653
|
+
This is similar to .pyf() but uses backend-aware functions that
|
|
654
|
+
work with both numpy and torch backends.
|
|
655
|
+
|
|
656
|
+
When using the torch backend, these functions preserve gradient
|
|
657
|
+
tracking for autograd.
|
|
658
|
+
|
|
659
|
+
Example:
|
|
660
|
+
|
|
661
|
+
.bkf("exp")
|
|
662
|
+
exp(1.0) --> 2.718...
|
|
663
|
+
|
|
664
|
+
.bkf(["exp";"sin";"cos"])
|
|
665
|
+
sin(1.0) --> 0.841...
|
|
666
|
+
|
|
667
|
+
Common functions available: exp, sin, cos, tan, tanh, sqrt, abs,
|
|
668
|
+
log, log10, floor, ceil, round
|
|
669
|
+
|
|
670
|
+
"""
|
|
671
|
+
if isinstance(x, str):
|
|
672
|
+
x = [x]
|
|
673
|
+
if not (is_list(x) and all(map(lambda p: isinstance(p, str), x))):
|
|
674
|
+
raise RuntimeError("function name(s) must be a string or list of strings")
|
|
675
|
+
|
|
676
|
+
backend = klong._backend
|
|
677
|
+
ctx = klong._context.pop()
|
|
678
|
+
try:
|
|
679
|
+
for fn_name in x:
|
|
680
|
+
if hasattr(backend.np, fn_name):
|
|
681
|
+
fn = getattr(backend.np, fn_name)
|
|
682
|
+
klong[fn_name] = _handle_import(fn)
|
|
683
|
+
else:
|
|
684
|
+
raise RuntimeError(f"Backend does not have function: {fn_name}")
|
|
685
|
+
finally:
|
|
686
|
+
klong._context.push(ctx)
|
|
687
|
+
return None
|
|
688
|
+
|
|
689
|
+
|
|
581
690
|
def eval_sys_random_number():
|
|
582
691
|
"""
|
|
583
692
|
|
|
@@ -586,7 +695,7 @@ def eval_sys_random_number():
|
|
|
586
695
|
Return a random number x, such that 0 <= x < 1.
|
|
587
696
|
|
|
588
697
|
"""
|
|
589
|
-
return
|
|
698
|
+
return bknp.random.random()
|
|
590
699
|
|
|
591
700
|
|
|
592
701
|
def eval_sys_read(klong):
|
|
@@ -607,7 +716,7 @@ def eval_sys_read(klong):
|
|
|
607
716
|
f.at_eof = True
|
|
608
717
|
return None
|
|
609
718
|
else:
|
|
610
|
-
i,a =
|
|
719
|
+
i,a = kg_read_array(r, 0, klong._backend, module=klong.current_module())
|
|
611
720
|
f.raw.seek(k+i,0)
|
|
612
721
|
return a
|
|
613
722
|
|
|
@@ -642,7 +751,7 @@ def eval_sys_read_lines(klong):
|
|
|
642
751
|
f = klong['.sys.cin']
|
|
643
752
|
r = f.raw.readlines()
|
|
644
753
|
f.at_eof = True
|
|
645
|
-
return kg_asarray(r)
|
|
754
|
+
return klong._backend.kg_asarray(r)
|
|
646
755
|
|
|
647
756
|
|
|
648
757
|
def eval_sys_read_string(klong, x):
|
|
@@ -656,7 +765,8 @@ def eval_sys_read_string(klong, x):
|
|
|
656
765
|
forms.
|
|
657
766
|
|
|
658
767
|
"""
|
|
659
|
-
|
|
768
|
+
_, a = kg_read_array(x, 0, klong._backend, module=klong.current_module(), read_neg=True)
|
|
769
|
+
return a
|
|
660
770
|
|
|
661
771
|
|
|
662
772
|
def eval_sys_system(x):
|
|
@@ -722,7 +832,7 @@ def eval_sys_write(klong, x):
|
|
|
722
832
|
sequence. Use .p (Print) to do so.
|
|
723
833
|
|
|
724
834
|
"""
|
|
725
|
-
r = kg_write(x)
|
|
835
|
+
r = kg_write(x, klong._backend)
|
|
726
836
|
klong['.sys.cout'].raw.write(r)
|
|
727
837
|
return x
|
|
728
838
|
|
|
@@ -742,6 +852,45 @@ def eval_sys_exit(x):
|
|
|
742
852
|
sys.exit(1)
|
|
743
853
|
|
|
744
854
|
|
|
855
|
+
def eval_sys_strict(klong):
|
|
856
|
+
"""
|
|
857
|
+
|
|
858
|
+
.strict() [Strict]
|
|
859
|
+
|
|
860
|
+
Enable strict mode for variable assignment (level 1).
|
|
861
|
+
In strict mode, functions cannot create new global variables
|
|
862
|
+
unless they are explicitly declared with the global: prefix.
|
|
863
|
+
|
|
864
|
+
Example:
|
|
865
|
+
.strict()
|
|
866
|
+
counter::0
|
|
867
|
+
increment::{counter::counter+1} :" ERROR"
|
|
868
|
+
increment::{[global:counter];counter::counter+1} :" OK"
|
|
869
|
+
|
|
870
|
+
"""
|
|
871
|
+
klong._context._strict_mode = 1
|
|
872
|
+
return 1
|
|
873
|
+
|
|
874
|
+
|
|
875
|
+
def eval_sys_unsafe(klong):
|
|
876
|
+
"""
|
|
877
|
+
|
|
878
|
+
.unsafe() [Unsafe]
|
|
879
|
+
|
|
880
|
+
Disable strict mode for variable assignment (level 0).
|
|
881
|
+
In unsafe mode, functions can create new global variables
|
|
882
|
+
without declaration (legacy behavior).
|
|
883
|
+
|
|
884
|
+
Example:
|
|
885
|
+
.unsafe()
|
|
886
|
+
f::{newvar::42} :" OK - creates global newvar"
|
|
887
|
+
f()
|
|
888
|
+
|
|
889
|
+
"""
|
|
890
|
+
klong._context._strict_mode = 0
|
|
891
|
+
return 0
|
|
892
|
+
|
|
893
|
+
|
|
745
894
|
def create_system_functions():
|
|
746
895
|
def _get_name(s):
|
|
747
896
|
i = s.index('.')
|