klongpy 0.7.0__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 +0 -2
- klongpy/adverbs.py +84 -82
- klongpy/autograd.py +0 -9
- klongpy/backend.py +9 -142
- klongpy/backends/__init__.py +9 -77
- klongpy/backends/base.py +154 -5
- klongpy/backends/numpy_backend.py +2 -1
- klongpy/backends/registry.py +76 -0
- klongpy/backends/torch_backend.py +83 -31
- klongpy/cli.py +50 -7
- klongpy/core.py +113 -1094
- klongpy/db/sys_fn_db.py +3 -3
- klongpy/db/sys_fn_kvs.py +2 -4
- klongpy/dyads.py +203 -162
- klongpy/interpreter.py +32 -15
- klongpy/monads.py +99 -89
- klongpy/parser.py +328 -0
- klongpy/repl.py +2 -2
- klongpy/sys_fn.py +53 -15
- klongpy/sys_fn_ipc.py +4 -9
- klongpy/types.py +503 -0
- klongpy/writer.py +122 -0
- klongpy/ws/sys_fn_ws.py +5 -8
- {klongpy-0.7.0.dist-info → klongpy-0.7.1.dist-info}/METADATA +146 -95
- klongpy-0.7.1.dist-info/RECORD +52 -0
- klongpy-0.7.0.dist-info/RECORD +0 -48
- {klongpy-0.7.0.dist-info → klongpy-0.7.1.dist-info}/WHEEL +0 -0
- {klongpy-0.7.0.dist-info → klongpy-0.7.1.dist-info}/entry_points.txt +0 -0
- {klongpy-0.7.0.dist-info → klongpy-0.7.1.dist-info}/licenses/LICENSE +0 -0
- {klongpy-0.7.0.dist-info → klongpy-0.7.1.dist-info}/top_level.txt +0 -0
klongpy/core.py
CHANGED
|
@@ -1,1094 +1,113 @@
|
|
|
1
|
-
|
|
2
|
-
|
|
3
|
-
|
|
4
|
-
from
|
|
5
|
-
import
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
return self.klong.call(KGCall(current.a, [*fn_args], current.arity))
|
|
115
|
-
except KeyError:
|
|
116
|
-
# Symbol was deleted, fall through to original function
|
|
117
|
-
pass
|
|
118
|
-
|
|
119
|
-
if len(args) != self.fn.arity:
|
|
120
|
-
raise RuntimeError(f"Klong function called with {len(args)} but expected {self.fn.arity}")
|
|
121
|
-
fn_args = [np.asarray(x) if isinstance(x, list) else x for x in args]
|
|
122
|
-
return self.klong.call(KGCall(self.fn.a, [*fn_args], self.fn.arity))
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
class KGCall(KGFn):
|
|
126
|
-
def __str__(self):
|
|
127
|
-
return self.a.__str__() if issubclass(type(self.a), KGLambda) else super().__str__()
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
class KGOp:
|
|
131
|
-
def __init__(self, a, arity):
|
|
132
|
-
self.a = a
|
|
133
|
-
self.arity = arity
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
class KGAdverb:
|
|
137
|
-
def __init__(self, a, arity):
|
|
138
|
-
self.a = a
|
|
139
|
-
self.arity = arity
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
class KGChar(str):
|
|
143
|
-
pass
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
class KGCond(list):
|
|
147
|
-
pass
|
|
148
|
-
|
|
149
|
-
|
|
150
|
-
def safe_inspect(fn, follow_wrapped=True):
|
|
151
|
-
try:
|
|
152
|
-
return inspect.signature(fn, follow_wrapped=follow_wrapped).parameters
|
|
153
|
-
except ValueError:
|
|
154
|
-
return {"args":[]}
|
|
155
|
-
|
|
156
|
-
class KGLambda:
|
|
157
|
-
"""
|
|
158
|
-
KGLambda wraps a lambda and make it available to Klong, allowing for direct
|
|
159
|
-
integration of python functions in Klong.
|
|
160
|
-
|
|
161
|
-
Introspection is used to infer which parameters should be collected from the
|
|
162
|
-
current context and passed to the lambda. Parameter names must be x,y, or z
|
|
163
|
-
according to the klong convention. The value for these parameters are
|
|
164
|
-
extracted directly from the currentcontext.
|
|
165
|
-
|
|
166
|
-
If a lambda requires access to klong itself, that must be the first parameter.
|
|
167
|
-
|
|
168
|
-
Function arity is computed by examining the arguments.
|
|
169
|
-
|
|
170
|
-
e.g.
|
|
171
|
-
|
|
172
|
-
lambda x,y: x + y
|
|
173
|
-
lambda klong, x: klong(x)
|
|
174
|
-
|
|
175
|
-
"""
|
|
176
|
-
def __init__(self, fn, args=None, provide_klong=False, wildcard=False):
|
|
177
|
-
self.fn = fn
|
|
178
|
-
params = args or safe_inspect(fn)
|
|
179
|
-
self.args = [reserved_fn_symbol_map[x] for x in reserved_fn_args if x in params]
|
|
180
|
-
self._provide_klong = provide_klong or 'klong' in params
|
|
181
|
-
self._wildcard = wildcard
|
|
182
|
-
|
|
183
|
-
def _get_pos_args(self, ctx):
|
|
184
|
-
if self._wildcard:
|
|
185
|
-
pos_args = []
|
|
186
|
-
for sym in reserved_fn_symbols:
|
|
187
|
-
try:
|
|
188
|
-
pos_args.append(ctx[sym])
|
|
189
|
-
except KeyError:
|
|
190
|
-
break
|
|
191
|
-
else:
|
|
192
|
-
pos_args = [ctx[x] for x in self.args]
|
|
193
|
-
return pos_args
|
|
194
|
-
|
|
195
|
-
def __call__(self, klong, ctx):
|
|
196
|
-
pos_args = self._get_pos_args(ctx)
|
|
197
|
-
return self.fn(klong, *pos_args) if self._provide_klong else self.fn(*pos_args)
|
|
198
|
-
|
|
199
|
-
def call_with_kwargs(self, klong, ctx, kwargs):
|
|
200
|
-
pos_args = self._get_pos_args(ctx)
|
|
201
|
-
return self.fn(klong, *pos_args, **kwargs) if self._provide_klong else self.fn(*pos_args, **kwargs)
|
|
202
|
-
|
|
203
|
-
def get_arity(self):
|
|
204
|
-
return len(self.args)
|
|
205
|
-
|
|
206
|
-
def __str__(self):
|
|
207
|
-
return get_fn_arity_str(self.get_arity())
|
|
208
|
-
|
|
209
|
-
|
|
210
|
-
class KGChannelDir(Enum):
|
|
211
|
-
INPUT=1
|
|
212
|
-
OUTPUT=2
|
|
213
|
-
|
|
214
|
-
|
|
215
|
-
class KGChannel:
|
|
216
|
-
class FileHandler:
|
|
217
|
-
def __init__(self, raw, parent):
|
|
218
|
-
self._raw = raw
|
|
219
|
-
self._ref = weakref.ref(parent, self.close)
|
|
220
|
-
|
|
221
|
-
def close(self, *args):
|
|
222
|
-
self._raw.close()
|
|
223
|
-
|
|
224
|
-
def __init__(self, raw, channel_dir):
|
|
225
|
-
self.channel_dir = channel_dir
|
|
226
|
-
self.raw = raw
|
|
227
|
-
self._fh = KGChannel.FileHandler(raw, self)
|
|
228
|
-
self.at_eof = False
|
|
229
|
-
|
|
230
|
-
def __enter__(self):
|
|
231
|
-
return self
|
|
232
|
-
|
|
233
|
-
def __exit__(self, ext_type, exc_value, traceback):
|
|
234
|
-
self._fh.close()
|
|
235
|
-
|
|
236
|
-
|
|
237
|
-
class RangeError(Exception):
|
|
238
|
-
def __init__(self, i):
|
|
239
|
-
self.i = i
|
|
240
|
-
super().__init__()
|
|
241
|
-
|
|
242
|
-
|
|
243
|
-
reserved_fn_args = ['x','y','z']
|
|
244
|
-
reserved_fn_symbols = [KGSym(n) for n in reserved_fn_args]
|
|
245
|
-
reserved_fn_symbol_map = {n:KGSym(n) for n in reserved_fn_args}
|
|
246
|
-
reserved_dot_f_symbol = KGSym('.f')
|
|
247
|
-
|
|
248
|
-
|
|
249
|
-
def is_list(x):
|
|
250
|
-
return isinstance(x,list) or (np.isarray(x) and x.ndim > 0)
|
|
251
|
-
|
|
252
|
-
|
|
253
|
-
def is_iterable(x):
|
|
254
|
-
return is_list(x) or (isinstance(x,str) and not isinstance(x, (KGSym, KGChar)))
|
|
255
|
-
|
|
256
|
-
|
|
257
|
-
def is_empty(a):
|
|
258
|
-
return is_iterable(a) and len(a) == 0
|
|
259
|
-
|
|
260
|
-
|
|
261
|
-
def is_dict(x):
|
|
262
|
-
return isinstance(x, dict)
|
|
263
|
-
|
|
264
|
-
|
|
265
|
-
def to_list(a):
|
|
266
|
-
return a if isinstance(a, list) else a.tolist() if np.isarray(a) else [a]
|
|
267
|
-
|
|
268
|
-
|
|
269
|
-
def is_integer(x, backend):
|
|
270
|
-
if issubclass(type(x), (int, numpy.integer)):
|
|
271
|
-
return True
|
|
272
|
-
# Handle 0-dim numpy arrays
|
|
273
|
-
if isinstance(x, numpy.ndarray) and x.ndim == 0:
|
|
274
|
-
return numpy.issubdtype(x.dtype, numpy.integer)
|
|
275
|
-
# Handle backend-specific scalar integers (e.g., torch tensors)
|
|
276
|
-
return backend.is_scalar_integer(x)
|
|
277
|
-
|
|
278
|
-
|
|
279
|
-
def is_float(x, backend):
|
|
280
|
-
if issubclass(type(x), (float, numpy.floating, int)):
|
|
281
|
-
return True
|
|
282
|
-
# Handle 0-dim numpy arrays
|
|
283
|
-
if isinstance(x, numpy.ndarray) and x.ndim == 0:
|
|
284
|
-
return numpy.issubdtype(x.dtype, numpy.floating)
|
|
285
|
-
# Handle backend-specific scalar floats (e.g., torch tensors)
|
|
286
|
-
return backend.is_scalar_float(x)
|
|
287
|
-
|
|
288
|
-
|
|
289
|
-
def is_number(a, backend):
|
|
290
|
-
if is_float(a, backend) or is_integer(a, backend):
|
|
291
|
-
return True
|
|
292
|
-
# Handle 0-dim numpy arrays
|
|
293
|
-
if isinstance(a, numpy.ndarray) and a.ndim == 0:
|
|
294
|
-
return numpy.issubdtype(a.dtype, numpy.number)
|
|
295
|
-
# Handle 0-dim backend tensors as numbers
|
|
296
|
-
if backend.is_backend_array(a) and hasattr(a, 'ndim') and a.ndim == 0:
|
|
297
|
-
return True
|
|
298
|
-
return False
|
|
299
|
-
|
|
300
|
-
|
|
301
|
-
def str_is_float(b):
|
|
302
|
-
try:
|
|
303
|
-
float(b)
|
|
304
|
-
return True
|
|
305
|
-
except ValueError:
|
|
306
|
-
return False
|
|
307
|
-
|
|
308
|
-
def in_map(x, v):
|
|
309
|
-
try:
|
|
310
|
-
return x in v
|
|
311
|
-
except Exception:
|
|
312
|
-
return False
|
|
313
|
-
|
|
314
|
-
|
|
315
|
-
def kg_asarray(a, backend):
|
|
316
|
-
"""Convert input to array using the backend's kg_asarray method."""
|
|
317
|
-
return backend.kg_asarray(a)
|
|
318
|
-
|
|
319
|
-
|
|
320
|
-
def kg_equal(a, b, backend):
|
|
321
|
-
"""Compare two values or arrays for equality, handling nested arrays and tensors."""
|
|
322
|
-
if a is b:
|
|
323
|
-
return True
|
|
324
|
-
|
|
325
|
-
# Check for arrays (numpy or backend-specific)
|
|
326
|
-
is_numpy_a = isinstance(a, numpy.ndarray)
|
|
327
|
-
is_numpy_b = isinstance(b, numpy.ndarray)
|
|
328
|
-
is_backend_a = backend.is_backend_array(a)
|
|
329
|
-
is_backend_b = backend.is_backend_array(b)
|
|
330
|
-
|
|
331
|
-
na, nb = is_numpy_a or is_backend_a, is_numpy_b or is_backend_b
|
|
332
|
-
|
|
333
|
-
# Handle arrays with same dtype
|
|
334
|
-
if na and nb:
|
|
335
|
-
a_dtype = get_dtype_kind(a, backend)
|
|
336
|
-
b_dtype = get_dtype_kind(b, backend)
|
|
337
|
-
if a_dtype == b_dtype and a_dtype != 'O':
|
|
338
|
-
return bool(np.array_equal(a, b))
|
|
339
|
-
|
|
340
|
-
na, nb = na or isinstance(a, list), nb or isinstance(b, list)
|
|
341
|
-
|
|
342
|
-
if na != nb:
|
|
343
|
-
# One is array/list, the other is not - could be scalar tensor/array vs scalar
|
|
344
|
-
# Handle comparing 0-dim arrays/tensors with scalars
|
|
345
|
-
if is_numpy_a and a.ndim == 0 and not nb:
|
|
346
|
-
return kg_equal(a.item(), b, backend)
|
|
347
|
-
if is_numpy_b and b.ndim == 0 and not na:
|
|
348
|
-
return kg_equal(a, b.item(), backend)
|
|
349
|
-
if is_backend_a and hasattr(a, 'ndim') and a.ndim == 0 and not nb:
|
|
350
|
-
return kg_equal(backend.scalar_to_python(a), b, backend)
|
|
351
|
-
if is_backend_b and hasattr(b, 'ndim') and b.ndim == 0 and not na:
|
|
352
|
-
return kg_equal(a, backend.scalar_to_python(b), backend)
|
|
353
|
-
return False
|
|
354
|
-
|
|
355
|
-
if na:
|
|
356
|
-
# Handle 0-dim arrays/tensors - compare as scalars
|
|
357
|
-
a_is_0d = hasattr(a, 'ndim') and a.ndim == 0
|
|
358
|
-
b_is_0d = hasattr(b, 'ndim') and b.ndim == 0
|
|
359
|
-
if a_is_0d or b_is_0d:
|
|
360
|
-
a_val = backend.scalar_to_python(a) if a_is_0d else a
|
|
361
|
-
b_val = backend.scalar_to_python(b) if b_is_0d else b
|
|
362
|
-
return kg_equal(a_val, b_val, backend)
|
|
363
|
-
return len(a) == len(b) and all(kg_equal(x, y, backend) for x, y in zip(a, b))
|
|
364
|
-
|
|
365
|
-
if is_number(a, backend) and is_number(b, backend):
|
|
366
|
-
# Convert tensors to Python scalars for comparison
|
|
367
|
-
if backend.is_backend_array(a):
|
|
368
|
-
a = backend.scalar_to_python(a)
|
|
369
|
-
if backend.is_backend_array(b):
|
|
370
|
-
b = backend.scalar_to_python(b)
|
|
371
|
-
result = np.isclose(a, b)
|
|
372
|
-
# np.isclose might return an array/tensor, ensure we return bool
|
|
373
|
-
if hasattr(result, 'item'):
|
|
374
|
-
return bool(result.item())
|
|
375
|
-
return bool(result)
|
|
376
|
-
|
|
377
|
-
result = a == b
|
|
378
|
-
# Handle tensor/array result from comparison
|
|
379
|
-
if hasattr(result, 'all'):
|
|
380
|
-
# For arrays, check if all elements are equal
|
|
381
|
-
return bool(result.all())
|
|
382
|
-
if hasattr(result, 'item'):
|
|
383
|
-
return bool(result.item())
|
|
384
|
-
return bool(result)
|
|
385
|
-
|
|
386
|
-
def has_none(a):
|
|
387
|
-
if isinstance(a,list):
|
|
388
|
-
for q in a:
|
|
389
|
-
if q is None:
|
|
390
|
-
return True
|
|
391
|
-
return False
|
|
392
|
-
|
|
393
|
-
|
|
394
|
-
def cmatch(t, i, c):
|
|
395
|
-
return i < len(t) and t[i] == c
|
|
396
|
-
|
|
397
|
-
|
|
398
|
-
def cmatch2(t, i, a, b):
|
|
399
|
-
return cmatch(t, i, a) and cmatch(t, i+1, b)
|
|
400
|
-
|
|
401
|
-
|
|
402
|
-
def cpeek(t,i):
|
|
403
|
-
return t[i] if i < len(t) else None
|
|
404
|
-
|
|
405
|
-
|
|
406
|
-
def cpeek2(t,i):
|
|
407
|
-
return t[i:i+2] if i < (len(t)-1) else None
|
|
408
|
-
|
|
409
|
-
|
|
410
|
-
class UnexpectedChar(Exception):
|
|
411
|
-
def __init__(self, t, i, c):
|
|
412
|
-
super().__init__(f"t: {t[i-10:i+10]} pos: {i} char: {c}")
|
|
413
|
-
|
|
414
|
-
|
|
415
|
-
class UnexpectedEOF(Exception):
|
|
416
|
-
def __init__(self, t, i):
|
|
417
|
-
self.t = t
|
|
418
|
-
self.i = i
|
|
419
|
-
super().__init__(f"t: {t[i-10:]} pos: {i}")
|
|
420
|
-
|
|
421
|
-
|
|
422
|
-
def cexpect(t, i, c):
|
|
423
|
-
if cmatch(t, i, c):
|
|
424
|
-
return i + 1
|
|
425
|
-
raise UnexpectedChar(t, i, c)
|
|
426
|
-
|
|
427
|
-
|
|
428
|
-
def cexpect2(t, i, a, b):
|
|
429
|
-
if cmatch(t, i, a) and cmatch(t, i+1, b):
|
|
430
|
-
return i + 2
|
|
431
|
-
raise UnexpectedChar(t, i, b)
|
|
432
|
-
|
|
433
|
-
|
|
434
|
-
def safe_eq(a,b):
|
|
435
|
-
return isinstance(a,type(b)) and a == b
|
|
436
|
-
|
|
437
|
-
|
|
438
|
-
def rec_flatten(a):
|
|
439
|
-
if is_list(a) and len(a) > 0:
|
|
440
|
-
return np.concatenate([rec_flatten(x) if is_list(x) else np.array([x]) for x in a]).ravel()
|
|
441
|
-
return a
|
|
442
|
-
|
|
443
|
-
|
|
444
|
-
def rec_fn(a,f):
|
|
445
|
-
_backend = get_default_backend()
|
|
446
|
-
return _backend.kg_asarray([rec_fn(x, f) for x in a]) if is_list(a) else f(a)
|
|
447
|
-
|
|
448
|
-
|
|
449
|
-
def vec_fn(a, f, backend):
|
|
450
|
-
"""
|
|
451
|
-
Apply a function `f` to an array `a`, with support for both nested arrays and direct vectorized operation.
|
|
452
|
-
"""
|
|
453
|
-
if np.isarray(a) and a.dtype == 'O':
|
|
454
|
-
# For object arrays, process each element and preserve structure
|
|
455
|
-
result = [((vec_fn(x, f, backend)) if is_list(x) else f(x)) for x in a]
|
|
456
|
-
return numpy.asarray(result, dtype=object)
|
|
457
|
-
return f(a)
|
|
458
|
-
|
|
459
|
-
|
|
460
|
-
def vec_fn2(a, b, f):
|
|
461
|
-
"""
|
|
462
|
-
Apply function `f` recursively to the elements of `a` and `b`, which can be scalar values, vectors, or nested vectors.
|
|
463
|
-
|
|
464
|
-
This function distinguishes 8 cases based on the types and dimensions of `a` and `b`:
|
|
465
|
-
|
|
466
|
-
1. vec[A],vec[B]: `f` is applied directly to `a` and `b`.
|
|
467
|
-
2. vec[A],obj_vec[B]: `f` is applied recursively to pairs of elements in `a` and `b`.
|
|
468
|
-
3. vec[A],scalar[B]: `f` is applied directly to `a` and `b`.
|
|
469
|
-
4. obj_vec[A],vec[B]: `f` is applied recursively to pairs of elements in `a` and `b`.
|
|
470
|
-
5. obj_vec[A],scalar[B]: `f` is applied recursively to the elements in `a` and the scalar `b`.
|
|
471
|
-
6. scalar[A],vec[B]: `f` is applied directly to `a` and `b`.
|
|
472
|
-
7. scalar[A],obj_vec[B]: `f` is applied recursively to the scalar `a` and the elements in `b`.
|
|
473
|
-
8. scalar[A],scalar[B]: `f` is applied directly to `a` and `b`.
|
|
474
|
-
|
|
475
|
-
Parameters
|
|
476
|
-
----------
|
|
477
|
-
a, b : numpy.array or any type
|
|
478
|
-
The inputs to `f`. They can be numpy arrays of any data type. If they are arrays, they should have the same shape.
|
|
479
|
-
Non-array inputs can be of any type that `f` can accept.
|
|
480
|
-
|
|
481
|
-
f : callable
|
|
482
|
-
A function that takes two arguments and can handle the types and dimensions of `a` and `b`.
|
|
483
|
-
|
|
484
|
-
Returns
|
|
485
|
-
-------
|
|
486
|
-
numpy.array or any type
|
|
487
|
-
The result of applying `f` to `a` and `b`, which can be a scalar, a vector, or a nested vector depending on
|
|
488
|
-
the inputs and `f`.
|
|
489
|
-
|
|
490
|
-
Notes
|
|
491
|
-
-----
|
|
492
|
-
This function assumes that `f` can handle the types and dimensions of `a` and `b`, and that `a` and `b` have the same
|
|
493
|
-
shape if they are arrays. It does not check these conditions, so unexpected results or errors may occur if they are
|
|
494
|
-
not satisfied.
|
|
495
|
-
|
|
496
|
-
"""
|
|
497
|
-
_backend = get_default_backend()
|
|
498
|
-
_kg_asarray = _backend.kg_asarray
|
|
499
|
-
if np.isarray(a):
|
|
500
|
-
if a.dtype == 'O':
|
|
501
|
-
if np.isarray(b):
|
|
502
|
-
assert len(a) == len(b)
|
|
503
|
-
return _kg_asarray([vec_fn2(x, y, f) for x,y in zip(a,b)])
|
|
504
|
-
else:
|
|
505
|
-
return _kg_asarray([vec_fn2(x, b, f) for x in a])
|
|
506
|
-
elif np.isarray(b) and b.dtype == 'O':
|
|
507
|
-
assert len(a) == len(b)
|
|
508
|
-
return _kg_asarray([vec_fn2(x, y, f) for x,y in zip(a,b)])
|
|
509
|
-
elif np.isarray(b) and b.dtype == 'O':
|
|
510
|
-
return _kg_asarray([vec_fn2(a, x, f) for x in b])
|
|
511
|
-
return f(a,b)
|
|
512
|
-
|
|
513
|
-
|
|
514
|
-
def is_symbolic(c):
|
|
515
|
-
return isinstance(c, str) and (c.isalpha() or c.isdigit() or c == '.')
|
|
516
|
-
|
|
517
|
-
|
|
518
|
-
def is_char(x):
|
|
519
|
-
# Check for both core and backend KGChar classes
|
|
520
|
-
if isinstance(x, KGChar):
|
|
521
|
-
return True
|
|
522
|
-
# Also check for backend KGChar (in case they're different classes)
|
|
523
|
-
return type(x).__name__ == 'KGChar' and isinstance(x, str)
|
|
524
|
-
|
|
525
|
-
|
|
526
|
-
def is_atom(x):
|
|
527
|
-
""" All objects except for non-empty lists and non-empty strings are atoms. """
|
|
528
|
-
return is_empty(x) if is_iterable(x) else True
|
|
529
|
-
|
|
530
|
-
|
|
531
|
-
def kg_truth(x):
|
|
532
|
-
return x*1
|
|
533
|
-
|
|
534
|
-
|
|
535
|
-
def str_to_chr_arr(s, backend):
|
|
536
|
-
"""
|
|
537
|
-
Convert string to character array.
|
|
538
|
-
|
|
539
|
-
Parameters
|
|
540
|
-
----------
|
|
541
|
-
s : str
|
|
542
|
-
The string to convert.
|
|
543
|
-
backend : BackendProvider
|
|
544
|
-
The backend to use.
|
|
545
|
-
|
|
546
|
-
Returns
|
|
547
|
-
-------
|
|
548
|
-
array
|
|
549
|
-
Array of KGChar objects.
|
|
550
|
-
|
|
551
|
-
Raises
|
|
552
|
-
------
|
|
553
|
-
UnsupportedDtypeError
|
|
554
|
-
If the backend doesn't support string operations.
|
|
555
|
-
"""
|
|
556
|
-
return backend.str_to_char_array(s)
|
|
557
|
-
|
|
558
|
-
|
|
559
|
-
def read_num(t, i=0):
|
|
560
|
-
p = i
|
|
561
|
-
use_float = False
|
|
562
|
-
if t[i] == '-':
|
|
563
|
-
i += 1
|
|
564
|
-
while i < len(t):
|
|
565
|
-
if t[i] == '.':
|
|
566
|
-
use_float = True
|
|
567
|
-
elif t[i] == 'e':
|
|
568
|
-
use_float = True
|
|
569
|
-
if cmatch(t,i+1,'-'):
|
|
570
|
-
i += 2
|
|
571
|
-
elif not t[i].isnumeric():
|
|
572
|
-
break
|
|
573
|
-
i += 1
|
|
574
|
-
return i, float(t[p:i]) if use_float else int(t[p:i])
|
|
575
|
-
|
|
576
|
-
|
|
577
|
-
def read_char(t, i):
|
|
578
|
-
i = cexpect2(t, i, '0', 'c')
|
|
579
|
-
if i >= len(t):
|
|
580
|
-
raise UnexpectedEOF(t, i)
|
|
581
|
-
return i+1, KGChar(t[i])
|
|
582
|
-
|
|
583
|
-
|
|
584
|
-
def read_sym(t, i=0, module=None):
|
|
585
|
-
p = i
|
|
586
|
-
while i < len(t) and is_symbolic(t[i]):
|
|
587
|
-
i += 1
|
|
588
|
-
x = t[p:i]
|
|
589
|
-
return i, reserved_fn_symbol_map.get(x) or KGSym(x if x.startswith('.') or module is None else f"{x}`{module}")
|
|
590
|
-
|
|
591
|
-
|
|
592
|
-
def read_op(t, i=0):
|
|
593
|
-
if cmatch2(t,i,'\\','~') or cmatch2(t,i,'\\','*'):
|
|
594
|
-
return i+2,KGOp(t[i:i+2],arity=0)
|
|
595
|
-
return i+1,KGOp(t[i:i+1],arity=0)
|
|
596
|
-
|
|
597
|
-
|
|
598
|
-
def read_shifted_comment(t, i=0):
|
|
599
|
-
while i < len(t):
|
|
600
|
-
c = t[i]
|
|
601
|
-
if c == '"':
|
|
602
|
-
i += 1
|
|
603
|
-
if not cmatch(t,i,'"'):
|
|
604
|
-
break
|
|
605
|
-
i += 1
|
|
606
|
-
return i
|
|
607
|
-
|
|
608
|
-
|
|
609
|
-
def read_sys_comment(t,i,a):
|
|
610
|
-
"""
|
|
611
|
-
|
|
612
|
-
.comment(x) [Comment]
|
|
613
|
-
|
|
614
|
-
Read and discard lines until the current line starts with the
|
|
615
|
-
string specified in "x". Also discard the line containing the
|
|
616
|
-
end-of-comment marker and return "x".
|
|
617
|
-
|
|
618
|
-
Example: .comment("end-of-comment")
|
|
619
|
-
this will be ignored
|
|
620
|
-
this, too: *%(*^#)&(#
|
|
621
|
-
end-of-comment
|
|
622
|
-
|
|
623
|
-
NOTE: this is handled in the parsing phase and is not a runtime function
|
|
624
|
-
|
|
625
|
-
"""
|
|
626
|
-
try:
|
|
627
|
-
j = t[i:].index(a)
|
|
628
|
-
while t[i+j+1:].startswith(a):
|
|
629
|
-
j += 1
|
|
630
|
-
return i + j + len(a)
|
|
631
|
-
except ValueError:
|
|
632
|
-
return RuntimeError("end of comment not found")
|
|
633
|
-
|
|
634
|
-
|
|
635
|
-
def skip_space(t, i=0, ignore_newline=False):
|
|
636
|
-
"""
|
|
637
|
-
NOTE: a newline character translates to a semicolon in Klong,
|
|
638
|
-
except in functions, dictionaries, conditional expressions,
|
|
639
|
-
and lists. So
|
|
640
|
-
"""
|
|
641
|
-
while i < len(t) and (t[i].isspace() and (ignore_newline or t[i] != '\n')):
|
|
642
|
-
i += 1
|
|
643
|
-
return i
|
|
644
|
-
|
|
645
|
-
|
|
646
|
-
def skip(t, i=0, ignore_newline=False):
|
|
647
|
-
i = skip_space(t,i,ignore_newline=ignore_newline)
|
|
648
|
-
if cmatch2(t, i, ':', '"'):
|
|
649
|
-
i = read_shifted_comment(t, i+2)
|
|
650
|
-
i = skip(t, i)
|
|
651
|
-
return i
|
|
652
|
-
|
|
653
|
-
|
|
654
|
-
def read_list(t, delim, i=0, module=None, level=1):
|
|
655
|
-
"""
|
|
656
|
-
|
|
657
|
-
# A list is any number of class lexemes (or lists) delimited by
|
|
658
|
-
# square brackets.
|
|
659
|
-
|
|
660
|
-
L := '[' (C|L)* ']'
|
|
661
|
-
|
|
662
|
-
"""
|
|
663
|
-
backend = get_default_backend()
|
|
664
|
-
arr = []
|
|
665
|
-
i = skip(t,i,ignore_newline=True)
|
|
666
|
-
while not cmatch(t,i,delim) and i < len(t):
|
|
667
|
-
# we can knowingly read neg numbers in list context
|
|
668
|
-
i, q = kg_read(t, i, read_neg=True, ignore_newline=True, module=module, list_level=level+1)
|
|
669
|
-
if q is None:
|
|
670
|
-
break
|
|
671
|
-
if safe_eq(q, '['):
|
|
672
|
-
i,q = read_list(t, ']', i=i, module=module, level=level+1)
|
|
673
|
-
arr.append(q)
|
|
674
|
-
i = skip(t,i,ignore_newline=True)
|
|
675
|
-
if cmatch(t,i,delim):
|
|
676
|
-
i += 1
|
|
677
|
-
if level == 1:
|
|
678
|
-
try:
|
|
679
|
-
aa = kg_asarray(arr, backend)
|
|
680
|
-
if get_dtype_kind(aa, backend) not in ['O','i','f']:
|
|
681
|
-
aa = numpy.asarray(arr, dtype=object)
|
|
682
|
-
except TorchUnsupportedDtypeError:
|
|
683
|
-
# Backend can't handle this data - fall back to numpy object array
|
|
684
|
-
# Recursively convert inner lists to arrays, converting tensors to numpy
|
|
685
|
-
def convert_inner(x):
|
|
686
|
-
if isinstance(x, list):
|
|
687
|
-
try:
|
|
688
|
-
result = kg_asarray(x, backend)
|
|
689
|
-
# Convert tensor to numpy for object array compatibility
|
|
690
|
-
return to_numpy(result)
|
|
691
|
-
except TorchUnsupportedDtypeError:
|
|
692
|
-
return numpy.asarray([convert_inner(e) for e in x], dtype=object)
|
|
693
|
-
# Convert any tensors to numpy
|
|
694
|
-
return to_numpy(x)
|
|
695
|
-
aa = numpy.asarray([convert_inner(x) for x in arr], dtype=object)
|
|
696
|
-
else:
|
|
697
|
-
aa = arr
|
|
698
|
-
return i, aa
|
|
699
|
-
|
|
700
|
-
|
|
701
|
-
def read_string(t, i=0):
|
|
702
|
-
"""
|
|
703
|
-
|
|
704
|
-
".*" [String]
|
|
705
|
-
|
|
706
|
-
A string is (almost) any sequence of characters enclosed by
|
|
707
|
-
double quote characters. To include a double quote character in
|
|
708
|
-
a string, it has to be duplicated, so the above regex is not
|
|
709
|
-
entirely correct. A comment is a shifted string (see below).
|
|
710
|
-
Examples: ""
|
|
711
|
-
"hello, world"
|
|
712
|
-
"say ""hello""!"
|
|
713
|
-
|
|
714
|
-
Note: this comforms to the KG read_string impl.
|
|
715
|
-
perf tests show that the final join is fast for short strings
|
|
716
|
-
|
|
717
|
-
"""
|
|
718
|
-
r = []
|
|
719
|
-
while i < len(t):
|
|
720
|
-
c = t[i]
|
|
721
|
-
if c == '"':
|
|
722
|
-
i += 1
|
|
723
|
-
if not cmatch(t,i,'"'):
|
|
724
|
-
break
|
|
725
|
-
r.append(c)
|
|
726
|
-
i += 1
|
|
727
|
-
return i,"".join(r)
|
|
728
|
-
|
|
729
|
-
|
|
730
|
-
def read_cond(klong, t, i=0):
|
|
731
|
-
"""
|
|
732
|
-
|
|
733
|
-
# A conditional expression has two forms: :[e1;e2;e3] means "if
|
|
734
|
-
# e1 is true, evaluate to e2, else evaluate to e3".
|
|
735
|
-
# :[e1;e2:|e3;e4;e5] is short for :[e1;e2:[e3;e4;e5]], i.e. the
|
|
736
|
-
# ":|" acts as an "else-if" operator. There may be any number of
|
|
737
|
-
# ":|" operators in a conditional.
|
|
738
|
-
|
|
739
|
-
c := ':[' ( e ';' e ':|' )* e ';' e ';' e ']'
|
|
740
|
-
|
|
741
|
-
"""
|
|
742
|
-
r = []
|
|
743
|
-
i,n = klong._expr(t, i, ignore_newline=True)
|
|
744
|
-
r.append(n)
|
|
745
|
-
i = cexpect(t, i, ';')
|
|
746
|
-
i,n = klong._expr(t, i, ignore_newline=True)
|
|
747
|
-
r.append(n)
|
|
748
|
-
i = skip(t,i,ignore_newline=True)
|
|
749
|
-
if cmatch2(t,i,':','|'):
|
|
750
|
-
i,n = read_cond(klong,t,i+2)
|
|
751
|
-
r.append(n)
|
|
752
|
-
else:
|
|
753
|
-
i = cexpect(t, i, ';')
|
|
754
|
-
i,n = klong._expr(t, i, ignore_newline=True)
|
|
755
|
-
r.append(n)
|
|
756
|
-
i = skip(t,i,ignore_newline=True)
|
|
757
|
-
i = cexpect(t, i, ']')
|
|
758
|
-
return i, KGCond(r)
|
|
759
|
-
|
|
760
|
-
|
|
761
|
-
def list_to_dict(a):
|
|
762
|
-
return {x[0]:x[1] for x in a}
|
|
763
|
-
|
|
764
|
-
|
|
765
|
-
copy_lambda = KGLambda(lambda x: copy.deepcopy(x))
|
|
766
|
-
|
|
767
|
-
def kg_read(t, i=0, read_neg=False, ignore_newline=False, module=None, list_level=0):
|
|
768
|
-
"""
|
|
769
|
-
|
|
770
|
-
# Lexeme classes are the sets of the lexemes specified in the
|
|
771
|
-
# previous section, except for operators.
|
|
772
|
-
|
|
773
|
-
C := I # integer
|
|
774
|
-
| H # character
|
|
775
|
-
| R # real number
|
|
776
|
-
| S # string
|
|
777
|
-
| V # variable (symbol)
|
|
778
|
-
| Y # (quoted) symbol
|
|
779
|
-
|
|
780
|
-
NOTE: this function mirrors the klong implementation so that sys_read/write
|
|
781
|
-
match klong's as well. The grammar read here is a superset of C.
|
|
782
|
-
|
|
783
|
-
NOTE: a newline character translates to a semicolon in Klong,
|
|
784
|
-
except in functions, dictionaries, conditional expressions,
|
|
785
|
-
and lists. So
|
|
786
|
-
|
|
787
|
-
a()
|
|
788
|
-
b()
|
|
789
|
-
|
|
790
|
-
is equal to a();b(), but
|
|
791
|
-
|
|
792
|
-
[1
|
|
793
|
-
2
|
|
794
|
-
3]
|
|
795
|
-
|
|
796
|
-
is equal to [1 2 3] and
|
|
797
|
-
|
|
798
|
-
:[x;
|
|
799
|
-
y;
|
|
800
|
-
z]
|
|
801
|
-
|
|
802
|
-
is equal to :[x;y;z] and
|
|
803
|
-
|
|
804
|
-
f::{.d("hello ");
|
|
805
|
-
.p("world!");
|
|
806
|
-
[]}
|
|
807
|
-
|
|
808
|
-
is a valid function definition.
|
|
809
|
-
|
|
810
|
-
"""
|
|
811
|
-
i = skip(t, i, ignore_newline=ignore_newline)
|
|
812
|
-
if i >= len(t):
|
|
813
|
-
return i, None
|
|
814
|
-
a = t[i]
|
|
815
|
-
if a == '\n':
|
|
816
|
-
a = ';' # convert newlines to semicolons
|
|
817
|
-
if a in [';','(',')','{','}',']']:
|
|
818
|
-
return i+1,a
|
|
819
|
-
elif cmatch2(t, i, '0', 'c'):
|
|
820
|
-
return read_char(t, i)
|
|
821
|
-
elif a.isnumeric() or (read_neg and (a == '-' and (i+1) < len(t) and t[i+1].isnumeric())):
|
|
822
|
-
return read_num(t, i)
|
|
823
|
-
elif a == '"':
|
|
824
|
-
return read_string(t, i+1)
|
|
825
|
-
elif a == ':' and (i+1 < len(t)):
|
|
826
|
-
aa = t[i+1]
|
|
827
|
-
if aa.isalpha() or aa == '.':
|
|
828
|
-
return read_sym(t, i=i+1, module=module)
|
|
829
|
-
elif aa.isnumeric() or aa == '"':
|
|
830
|
-
return kg_read(t, i+1, ignore_newline=ignore_newline, module=module)
|
|
831
|
-
elif aa == '{':
|
|
832
|
-
i, d = read_list(t, '}', i=i+2, module=module, level=list_level+1)
|
|
833
|
-
d = list_to_dict(d)
|
|
834
|
-
return i, KGCall(copy_lambda,args=d,arity=0)
|
|
835
|
-
elif aa == '[':
|
|
836
|
-
return i+2,':['
|
|
837
|
-
elif aa == '|':
|
|
838
|
-
return i+2,':|'
|
|
839
|
-
return i+2,KGOp(f":{aa}",arity=0)
|
|
840
|
-
elif safe_eq(a, '['):
|
|
841
|
-
return read_list(t, ']', i=i+1, module=module, level=list_level+1)
|
|
842
|
-
elif is_symbolic(a):
|
|
843
|
-
return read_sym(t, i, module=module)
|
|
844
|
-
return read_op(t,i)
|
|
845
|
-
|
|
846
|
-
|
|
847
|
-
def kg_write_symbol(x, display=False):
|
|
848
|
-
return str(x) if display else f":{x}"
|
|
849
|
-
|
|
850
|
-
|
|
851
|
-
def kg_write_integer(x, display=False):
|
|
852
|
-
return str(int(x))
|
|
853
|
-
|
|
854
|
-
|
|
855
|
-
def kg_write_float(x, display=False):
|
|
856
|
-
return str(x)
|
|
857
|
-
|
|
858
|
-
|
|
859
|
-
def kg_write_char(c, display=False):
|
|
860
|
-
return c if display else f"0c{c}"
|
|
861
|
-
|
|
862
|
-
|
|
863
|
-
def kg_write_string(s, display=False):
|
|
864
|
-
if display:
|
|
865
|
-
return s
|
|
866
|
-
arr = ['"']
|
|
867
|
-
for c in s:
|
|
868
|
-
if c == '"':
|
|
869
|
-
arr.append('"')
|
|
870
|
-
arr.append(c)
|
|
871
|
-
arr.append('"')
|
|
872
|
-
return ''.join(arr)
|
|
873
|
-
|
|
874
|
-
|
|
875
|
-
def kg_write_dict(d, backend, display=False):
|
|
876
|
-
# determine if the object d has overwritten the default __str__ and call it
|
|
877
|
-
# if so, otherwise use the default dict str
|
|
878
|
-
if d.__class__.__name__ != 'dict':
|
|
879
|
-
return str(d)
|
|
880
|
-
return ''.join([':{', ' '.join([kg_write(list(e), backend, display=display) for e in d.items()]), '}'])
|
|
881
|
-
|
|
882
|
-
|
|
883
|
-
def kg_write_list(x, backend, display=False):
|
|
884
|
-
return ''.join(['[', ' '.join([kg_write(q, backend, display=display) for q in x]), ']'])
|
|
885
|
-
|
|
886
|
-
|
|
887
|
-
def kg_write_fn(x, display=False):
|
|
888
|
-
return str(x)
|
|
889
|
-
|
|
890
|
-
|
|
891
|
-
def kg_write_channel(x, display=False):
|
|
892
|
-
if x.channel_dir == KGChannelDir.INPUT:
|
|
893
|
-
return ":inchan.0"
|
|
894
|
-
return f":outchan.{2 if x.raw == sys.stderr else 1}"
|
|
895
|
-
|
|
896
|
-
|
|
897
|
-
def kg_write(a, backend, display=False):
|
|
898
|
-
_backend = backend
|
|
899
|
-
# Convert backend arrays (e.g., torch tensors) to display-friendly format
|
|
900
|
-
a = _backend.to_display(a)
|
|
901
|
-
if isinstance(a,KGSym):
|
|
902
|
-
return kg_write_symbol(a, display=display)
|
|
903
|
-
elif is_integer(a, _backend):
|
|
904
|
-
return kg_write_integer(a,display=display)
|
|
905
|
-
elif is_float(a, _backend):
|
|
906
|
-
return kg_write_float(a,display=display)
|
|
907
|
-
elif isinstance(a,KGChar):
|
|
908
|
-
return kg_write_char(a,display=display)
|
|
909
|
-
elif isinstance(a, str):
|
|
910
|
-
return kg_write_string(a,display=display)
|
|
911
|
-
elif isinstance(a,dict):
|
|
912
|
-
return kg_write_dict(a, _backend, display=display)
|
|
913
|
-
elif is_list(a):
|
|
914
|
-
return kg_write_list(a, _backend, display=display)
|
|
915
|
-
elif isinstance(a,KGFn):
|
|
916
|
-
return kg_write_fn(a,display=display)
|
|
917
|
-
elif isinstance(a,KGChannel):
|
|
918
|
-
return kg_write_channel(a,display=display)
|
|
919
|
-
elif hasattr(a,'__str__'):
|
|
920
|
-
return str(a)
|
|
921
|
-
elif safe_eq(a, np.inf):
|
|
922
|
-
return ":undefined"
|
|
923
|
-
|
|
924
|
-
|
|
925
|
-
def kg_argsort(a, backend, descending=False):
|
|
926
|
-
"""
|
|
927
|
-
|
|
928
|
-
Return the indices of the sorted array (may be nested) or a string. Duplicate elements are disambiguated by their position in the array.
|
|
929
|
-
|
|
930
|
-
argsort("foobar") => [4 3 0 1 2 5]
|
|
931
|
-
^ ^
|
|
932
|
-
arbitrary ordering resolved by index position
|
|
933
|
-
|
|
934
|
-
argsort("foobar",descending=True) => [5 2 1 0 3 4]
|
|
935
|
-
^ ^
|
|
936
|
-
arbitrary ordering resolved by index position
|
|
937
|
-
|
|
938
|
-
"""
|
|
939
|
-
if not is_iterable(a) or len(a) == 0:
|
|
940
|
-
return a
|
|
941
|
-
|
|
942
|
-
# Fast path: for simple 1D numeric arrays, use native argsort
|
|
943
|
-
if hasattr(a, 'ndim') and a.ndim == 1:
|
|
944
|
-
dtype_kind = get_dtype_kind(a, backend)
|
|
945
|
-
if dtype_kind in ('i', 'f', 'u'):
|
|
946
|
-
return backend.argsort(a, descending=descending)
|
|
947
|
-
|
|
948
|
-
# Slow path: nested arrays or strings need element-by-element comparison
|
|
949
|
-
def _e(x):
|
|
950
|
-
return (-np.inf,x) if is_empty(a[x]) else (np.max(a[x]),x) if is_list(a[x]) else (a[x],x)
|
|
951
|
-
return np.asarray(sorted(range(len(a)), key=_e, reverse=descending))
|
|
952
|
-
|
|
953
|
-
|
|
954
|
-
def peek_adverb(t,i=0):
|
|
955
|
-
x = cpeek2(t,i)
|
|
956
|
-
if is_adverb(x):
|
|
957
|
-
return i+2,x
|
|
958
|
-
x = cpeek(t,i)
|
|
959
|
-
if is_adverb(x):
|
|
960
|
-
return i+1,x
|
|
961
|
-
return i,None
|
|
962
|
-
|
|
963
|
-
|
|
964
|
-
def is_adverb(s):
|
|
965
|
-
return s in {
|
|
966
|
-
"'",
|
|
967
|
-
':\\',
|
|
968
|
-
":'",
|
|
969
|
-
':/',
|
|
970
|
-
'/',
|
|
971
|
-
':~',
|
|
972
|
-
':*',
|
|
973
|
-
'\\',
|
|
974
|
-
'\\~',
|
|
975
|
-
'\\*'
|
|
976
|
-
}
|
|
977
|
-
|
|
978
|
-
|
|
979
|
-
def get_adverb_arity(s, ctx):
|
|
980
|
-
if s == "'":
|
|
981
|
-
return ctx
|
|
982
|
-
elif s == ':\\':
|
|
983
|
-
return 2
|
|
984
|
-
elif s == ':\'':
|
|
985
|
-
return 2
|
|
986
|
-
elif s == ':/':
|
|
987
|
-
return 2
|
|
988
|
-
elif s == '/':
|
|
989
|
-
return 2
|
|
990
|
-
elif s == ':~':
|
|
991
|
-
return 1
|
|
992
|
-
elif s == ':*':
|
|
993
|
-
return 1
|
|
994
|
-
elif s == '\\':
|
|
995
|
-
return 2
|
|
996
|
-
elif s == '\\~':
|
|
997
|
-
return 1
|
|
998
|
-
elif s == '\\*':
|
|
999
|
-
return 1
|
|
1000
|
-
raise RuntimeError(f"unknown adverb: {s}")
|
|
1001
|
-
|
|
1002
|
-
|
|
1003
|
-
def merge_projections(arr):
|
|
1004
|
-
"""
|
|
1005
|
-
|
|
1006
|
-
A projection is a new function that is created by projecting an
|
|
1007
|
-
existing function onto at least one of its arguments, resulting
|
|
1008
|
-
in the partial application of the original function.
|
|
1009
|
-
|
|
1010
|
-
The notation of projection is that of function application where
|
|
1011
|
-
the arguments onto which the function is being projected are
|
|
1012
|
-
omitted. For instance,
|
|
1013
|
-
|
|
1014
|
-
Projection Equivalent function
|
|
1015
|
-
{x-y}(5;) {5-x}
|
|
1016
|
-
{x-y}(;5) {x-5}
|
|
1017
|
-
|
|
1018
|
-
and, given a ternary function f3:
|
|
1019
|
-
|
|
1020
|
-
Projection Equivalent function
|
|
1021
|
-
f3(1;2;) {f3(1;2;x)}
|
|
1022
|
-
f3(1;;3) {f3(1;x;3)}
|
|
1023
|
-
f3(;2;3) {f3(x;2;3)}
|
|
1024
|
-
f3(;;3) {f3(x;y;3)}
|
|
1025
|
-
f3(;2;) {f3(x;2;y)}
|
|
1026
|
-
f3(1;;) {f3(1;x;y)}
|
|
1027
|
-
|
|
1028
|
-
The projection of a triad is a dyad or a monad, depending on the
|
|
1029
|
-
number of arguments onto which the triad is being projected. The
|
|
1030
|
-
projection of a dyad is always a monad. There is no projection
|
|
1031
|
-
of a monad or nilad.
|
|
1032
|
-
|
|
1033
|
-
Alternatively, monads and nilads can be considered to be their
|
|
1034
|
-
own projections (onto zero arguments), but there is no special
|
|
1035
|
-
syntax for this case. Any function that is being projected onto
|
|
1036
|
-
all of its arguments is simply the function itself.
|
|
1037
|
-
|
|
1038
|
-
Projections are ordinary functions and can be used in all places
|
|
1039
|
-
where a verb is expected. For instance:
|
|
1040
|
-
|
|
1041
|
-
f::{x,y}
|
|
1042
|
-
f(;0)'[1 2 3] --> [[1 0] [2 0] [3 0]]
|
|
1043
|
-
f(0;)'[1 2 3] --> [[0 1] [0 2] [0 3]]
|
|
1044
|
-
|
|
1045
|
-
g::{x,y,z}
|
|
1046
|
-
1g(;2;)3 --> [1 2 3]
|
|
1047
|
-
|
|
1048
|
-
"""
|
|
1049
|
-
if len(arr) == 0:
|
|
1050
|
-
return arr
|
|
1051
|
-
if len(arr) == 1 or not has_none(arr[0]):
|
|
1052
|
-
return arr[0]
|
|
1053
|
-
sparse_fa = np.copy(arr[0])
|
|
1054
|
-
i = 0
|
|
1055
|
-
k = 1
|
|
1056
|
-
while i < len(sparse_fa) and k < len(arr):
|
|
1057
|
-
fa = arr[k]
|
|
1058
|
-
j = 0
|
|
1059
|
-
while i < len(sparse_fa) and j < len(fa):
|
|
1060
|
-
if sparse_fa[i] is None:
|
|
1061
|
-
sparse_fa[i] = fa[j]
|
|
1062
|
-
j += 1
|
|
1063
|
-
while j < len(fa) and safe_eq(fa[j], None):
|
|
1064
|
-
j += 1
|
|
1065
|
-
i += 1
|
|
1066
|
-
k += 1
|
|
1067
|
-
return sparse_fa
|
|
1068
|
-
|
|
1069
|
-
|
|
1070
|
-
def get_fn_arity(f):
|
|
1071
|
-
"""
|
|
1072
|
-
Examine a function AST and infer arity by looking for x,y and z.
|
|
1073
|
-
This arity is needed to populate the KGFn.
|
|
1074
|
-
|
|
1075
|
-
NOTE: TODO: it maybe easier / better to do this at parse time vs late.
|
|
1076
|
-
"""
|
|
1077
|
-
if isinstance(f,KGFn) and isinstance(f.a,KGSym) and not in_map(f.a,reserved_fn_symbols):
|
|
1078
|
-
return sum(1 for x in set(f.args) if in_map(x, reserved_fn_symbols) or (x is None))
|
|
1079
|
-
def _e(f, level=0):
|
|
1080
|
-
if isinstance(f,KGFn):
|
|
1081
|
-
x = _e(f.a, level=1)
|
|
1082
|
-
if isinstance(f.args,list):
|
|
1083
|
-
for q in f.args:
|
|
1084
|
-
x.update(_e(q,level=1))
|
|
1085
|
-
elif isinstance(f,list):
|
|
1086
|
-
x = set()
|
|
1087
|
-
for q in f:
|
|
1088
|
-
x.update(_e(q,level=1))
|
|
1089
|
-
elif isinstance(f,KGSym):
|
|
1090
|
-
x = set([f]) if f in reserved_fn_symbols else set()
|
|
1091
|
-
else:
|
|
1092
|
-
x = set()
|
|
1093
|
-
return x if level else len(x)
|
|
1094
|
-
return _e(f)
|
|
1
|
+
"""
|
|
2
|
+
KlongPy core module.
|
|
3
|
+
|
|
4
|
+
This module re-exports public symbols from the split modules.
|
|
5
|
+
New code should import directly from the specific modules:
|
|
6
|
+
- klongpy.types: Type definitions and type checking
|
|
7
|
+
- klongpy.parser: Parsing and lexing functions
|
|
8
|
+
- klongpy.writer: Output formatting functions
|
|
9
|
+
"""
|
|
10
|
+
|
|
11
|
+
# Re-export everything from the split modules
|
|
12
|
+
from .types import *
|
|
13
|
+
from .parser import *
|
|
14
|
+
from .writer import *
|
|
15
|
+
|
|
16
|
+
# Re-export backend numpy-like module for shared array helpers
|
|
17
|
+
from .backend import bknp
|
|
18
|
+
|
|
19
|
+
__all__ = [
|
|
20
|
+
# Types
|
|
21
|
+
'KlongException',
|
|
22
|
+
'KGSym',
|
|
23
|
+
'KGFn',
|
|
24
|
+
'KGFnWrapper',
|
|
25
|
+
'KGCall',
|
|
26
|
+
'KGOp',
|
|
27
|
+
'KGAdverb',
|
|
28
|
+
'KGChar',
|
|
29
|
+
'KGCond',
|
|
30
|
+
'KGUndefined',
|
|
31
|
+
'KLONG_UNDEFINED',
|
|
32
|
+
'KGLambda',
|
|
33
|
+
'KGChannel',
|
|
34
|
+
'KGChannelDir',
|
|
35
|
+
'RangeError',
|
|
36
|
+
# Type constants
|
|
37
|
+
'reserved_fn_args',
|
|
38
|
+
'reserved_fn_symbols',
|
|
39
|
+
'reserved_fn_symbol_map',
|
|
40
|
+
'reserved_dot_f_symbol',
|
|
41
|
+
# Type helpers
|
|
42
|
+
'get_fn_arity_str',
|
|
43
|
+
'safe_inspect',
|
|
44
|
+
# Type checking
|
|
45
|
+
'is_list',
|
|
46
|
+
'is_iterable',
|
|
47
|
+
'is_empty',
|
|
48
|
+
'is_dict',
|
|
49
|
+
'to_list',
|
|
50
|
+
'is_integer',
|
|
51
|
+
'is_float',
|
|
52
|
+
'is_number',
|
|
53
|
+
'str_is_float',
|
|
54
|
+
'is_symbolic',
|
|
55
|
+
'is_char',
|
|
56
|
+
'is_atom',
|
|
57
|
+
'kg_truth',
|
|
58
|
+
'str_to_chr_arr',
|
|
59
|
+
'get_dtype_kind',
|
|
60
|
+
# Utilities
|
|
61
|
+
'safe_eq',
|
|
62
|
+
'in_map',
|
|
63
|
+
'has_none',
|
|
64
|
+
'rec_flatten',
|
|
65
|
+
# Adverb utilities
|
|
66
|
+
'is_adverb',
|
|
67
|
+
'get_adverb_arity',
|
|
68
|
+
# Function utilities
|
|
69
|
+
'merge_projections',
|
|
70
|
+
'get_fn_arity',
|
|
71
|
+
# Parser - character matching
|
|
72
|
+
'cmatch',
|
|
73
|
+
'cmatch2',
|
|
74
|
+
'cpeek',
|
|
75
|
+
'cpeek2',
|
|
76
|
+
'cexpect',
|
|
77
|
+
'cexpect2',
|
|
78
|
+
'UnexpectedChar',
|
|
79
|
+
'UnexpectedEOF',
|
|
80
|
+
# Parser - skip
|
|
81
|
+
'skip_space',
|
|
82
|
+
'skip',
|
|
83
|
+
# Parser - comment
|
|
84
|
+
'read_shifted_comment',
|
|
85
|
+
'read_sys_comment',
|
|
86
|
+
# Parser - lexeme readers
|
|
87
|
+
'read_num',
|
|
88
|
+
'read_char',
|
|
89
|
+
'read_sym',
|
|
90
|
+
'read_op',
|
|
91
|
+
'read_string',
|
|
92
|
+
'read_list',
|
|
93
|
+
'kg_read',
|
|
94
|
+
'kg_read_array',
|
|
95
|
+
'read_cond',
|
|
96
|
+
'list_to_dict',
|
|
97
|
+
'copy_lambda',
|
|
98
|
+
'peek_adverb',
|
|
99
|
+
# Writer
|
|
100
|
+
'kg_write_symbol',
|
|
101
|
+
'kg_write_integer',
|
|
102
|
+
'kg_write_float',
|
|
103
|
+
'kg_write_char',
|
|
104
|
+
'kg_write_string',
|
|
105
|
+
'kg_write_dict',
|
|
106
|
+
'kg_write_list',
|
|
107
|
+
'kg_write_fn',
|
|
108
|
+
'kg_write_channel',
|
|
109
|
+
'kg_write',
|
|
110
|
+
'kg_argsort',
|
|
111
|
+
# Backend re-exports
|
|
112
|
+
'bknp',
|
|
113
|
+
]
|