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.
Files changed (70) hide show
  1. klongpy/__init__.py +17 -1
  2. klongpy/adverbs.py +84 -82
  3. klongpy/autograd.py +299 -0
  4. klongpy/backend.py +38 -103
  5. klongpy/backends/__init__.py +26 -0
  6. klongpy/backends/base.py +469 -0
  7. klongpy/backends/numpy_backend.py +123 -0
  8. klongpy/backends/registry.py +76 -0
  9. klongpy/backends/torch_backend.py +1047 -0
  10. klongpy-0.6.9.data/scripts/kgpy → klongpy/cli.py +110 -90
  11. klongpy/core.py +113 -974
  12. klongpy/db/sys_fn_db.py +7 -6
  13. klongpy/db/sys_fn_kvs.py +2 -4
  14. klongpy/dyads.py +332 -160
  15. klongpy/interpreter.py +60 -15
  16. klongpy/monads.py +121 -75
  17. klongpy/parser.py +328 -0
  18. klongpy/repl.py +23 -5
  19. klongpy/sys_fn.py +170 -21
  20. klongpy/sys_fn_autograd.py +290 -0
  21. klongpy/sys_fn_ipc.py +22 -15
  22. klongpy/sys_fn_timer.py +13 -3
  23. klongpy/types.py +503 -0
  24. klongpy/web/sys_fn_web.py +14 -4
  25. klongpy/writer.py +122 -0
  26. klongpy/ws/sys_fn_ws.py +5 -8
  27. klongpy-0.7.1.dist-info/METADATA +544 -0
  28. klongpy-0.7.1.dist-info/RECORD +52 -0
  29. {klongpy-0.6.9.dist-info → klongpy-0.7.1.dist-info}/WHEEL +1 -1
  30. klongpy-0.7.1.dist-info/entry_points.txt +2 -0
  31. {klongpy-0.6.9.dist-info → klongpy-0.7.1.dist-info}/top_level.txt +0 -1
  32. klongpy-0.6.9.dist-info/METADATA +0 -448
  33. klongpy-0.6.9.dist-info/RECORD +0 -77
  34. tests/__init__.py +0 -6
  35. tests/gen_join_over.py +0 -119
  36. tests/gen_py_suite.py +0 -77
  37. tests/gen_test_fn.py +0 -259
  38. tests/perf_async.py +0 -25
  39. tests/perf_avg.py +0 -18
  40. tests/perf_duckdb.py +0 -32
  41. tests/perf_gen.py +0 -38
  42. tests/perf_ipc_overhead.py +0 -34
  43. tests/perf_join.py +0 -53
  44. tests/perf_load.py +0 -17
  45. tests/perf_prog.py +0 -18
  46. tests/perf_serdes.py +0 -52
  47. tests/perf_sys_fn_db.py +0 -263
  48. tests/perf_vector.py +0 -40
  49. tests/test_accel.py +0 -227
  50. tests/test_df_cache.py +0 -85
  51. tests/test_eval_monad_list.py +0 -34
  52. tests/test_examples.py +0 -64
  53. tests/test_extra_suite.py +0 -382
  54. tests/test_file_cache.py +0 -185
  55. tests/test_interop.py +0 -180
  56. tests/test_kg_asarray.py +0 -94
  57. tests/test_kgtests.py +0 -65
  58. tests/test_known_bugs.py +0 -206
  59. tests/test_prog.py +0 -107
  60. tests/test_reshape_strings.py +0 -33
  61. tests/test_suite.py +0 -1480
  62. tests/test_suite_file.py +0 -153
  63. tests/test_sys_fn.py +0 -420
  64. tests/test_sys_fn_db.py +0 -88
  65. tests/test_sys_fn_ipc.py +0 -587
  66. tests/test_sys_fn_timer.py +0 -133
  67. tests/test_sys_fn_web.py +0 -50
  68. tests/test_util.py +0 -233
  69. tests/utils.py +0 -126
  70. {klongpy-0.6.9.dist-info → klongpy-0.7.1.dist-info}/licenses/LICENSE +0 -0
@@ -1,27 +1,29 @@
1
- #!python
1
+ #!/usr/bin/env python3
2
+ """
3
+ KlongPy REPL command-line interface.
4
+
5
+ See https://t3x.org/klong/klong-ref.txt.html for additional details.
6
+ """
2
7
 
3
8
  import argparse
4
9
  import asyncio
10
+ import importlib
11
+ import importlib.util
5
12
  import importlib.metadata
6
13
  import os
7
14
  import sys
8
15
  import threading
9
- import time
10
16
  import timeit
11
17
 
12
18
  import colorama
13
19
 
14
- from klongpy import KlongInterpreter
20
+ from klongpy import KlongInterpreter, list_backends
15
21
  from klongpy.core import kg_write
16
- from klongpy.utils import CallbackEvent
17
-
18
- import importlib.resources
19
-
20
- """
22
+ from klongpy.repl import cleanup_repl, create_repl
21
23
 
22
- KlongPy REPL: See https://t3x.org/klong/klong-ref.txt.html for additional details.
24
+ _READLINE_SPEC = importlib.util.find_spec("readline")
25
+ readline = importlib.import_module("readline") if _READLINE_SPEC else None
23
26
 
24
- """
25
27
 
26
28
  def sys_cmd_shell(klong, cmd):
27
29
  """
@@ -112,7 +114,7 @@ def sys_cmd_exit(klong, cmd):
112
114
 
113
115
  """
114
116
  print("bye!")
115
- sys.exit(0)
117
+ return ExitRequest(0)
116
118
 
117
119
 
118
120
  def sys_cmd_transcript(klong, cmd):
@@ -174,10 +176,17 @@ success = lambda input: f"{colorama.Fore.GREEN}{input}"
174
176
  failure = lambda input: f"{colorama.Fore.RED}{input}"
175
177
 
176
178
 
179
+ class ExitRequest:
180
+ def __init__(self, code=0):
181
+ self.code = 0 if code is None else code
182
+
183
+
177
184
  async def repl_eval(klong, p, verbose=True):
178
185
  try:
179
186
  r = klong(p)
180
- r = r if r is None else success(kg_write(r, display=False))
187
+ r = r if r is None else success(kg_write(r, klong._backend, display=False))
188
+ except SystemExit as exc:
189
+ return ExitRequest(exc.code)
181
190
  except Exception as e:
182
191
  r = failure(f"Error: {e.args}")
183
192
  if verbose:
@@ -187,12 +196,14 @@ async def repl_eval(klong, p, verbose=True):
187
196
  return r
188
197
 
189
198
 
190
- def show_repl_header(ipc_addr=None):
199
+ def show_repl_header(backend_name, device_name=None, ipc_addr=None):
191
200
  print()
192
201
  print(f"{colorama.Fore.GREEN}Welcome to KlongPy REPL v{importlib.metadata.distribution('klongpy').version}")
193
202
  print(f"{colorama.Fore.GREEN}Author: Brian Guarraci")
194
203
  print(f"{colorama.Fore.GREEN}Web: http://klongpy.org")
195
- print(f"{colorama.Fore.YELLOW}]h for help; crtl-d or ]q to quit")
204
+ device_str = f" ({device_name})" if device_name else ""
205
+ print(f"{colorama.Fore.CYAN}Backend: {backend_name}{device_str}")
206
+ print(f"{colorama.Fore.YELLOW}]h for help; Ctrl-D or ]q to quit")
196
207
  print()
197
208
  if ipc_addr:
198
209
  print(f"{colorama.Fore.RED}Running IPC server at {ipc_addr}")
@@ -209,7 +220,7 @@ def run_in_loop(klong_loop, coro):
209
220
 
210
221
  class ConsoleInputHandler:
211
222
  @staticmethod
212
- async def input_producer(console_loop, klong_loop, klong, verbose=False):
223
+ async def input_producer(console_loop, klong_loop, klong, verbose=False, exit_state=None):
213
224
  sys_cmds = create_sys_cmd_functions()
214
225
  try:
215
226
  while True:
@@ -225,13 +236,29 @@ class ConsoleInputHandler:
225
236
  continue
226
237
  else:
227
238
  r = run_in_loop(klong_loop, repl_eval(klong, s, verbose=verbose))
239
+ if isinstance(r, ExitRequest):
240
+ if exit_state is not None:
241
+ exit_state["code"] = r.code
242
+ break
228
243
  if r is not None:
229
244
  print(r)
230
245
  except EOFError:
231
246
  print("\rbye!")
232
247
  break
233
248
  except KeyboardInterrupt:
234
- print(failure("\nkg: error: interrupted"))
249
+ buf = ""
250
+ if readline is not None:
251
+ try:
252
+ buf = readline.get_line_buffer()
253
+ except Exception:
254
+ buf = ""
255
+ if buf:
256
+ print()
257
+ continue
258
+ print("\rbye!")
259
+ if exit_state is not None:
260
+ exit_state["code"] = 130
261
+ break
235
262
  except Exception as e:
236
263
  print(failure(f"Error: {e.args}"))
237
264
  import traceback
@@ -241,65 +268,24 @@ class ConsoleInputHandler:
241
268
 
242
269
 
243
270
  async def run_in_klong(klong, s):
244
- return klong(s)
271
+ try:
272
+ return klong(s)
273
+ except SystemExit as exc:
274
+ return ExitRequest(exc.code)
245
275
 
246
276
 
247
277
  def run_file(klong_loop, klong, fname, verbose=False):
278
+ # Add script directory to sys.path so .py/.pyf imports resolve sibling modules
279
+ script_dir = os.path.dirname(os.path.abspath(fname))
280
+ if script_dir not in sys.path:
281
+ sys.path.insert(0, script_dir)
248
282
  with open(fname, "r") as f:
249
283
  content = f.read()
250
- run_in_loop(klong_loop, run_in_klong(klong, content))
251
-
252
-
253
- def start_loop(loop, stop_event):
254
- asyncio.set_event_loop(loop)
255
- loop.run_until_complete(stop_event.wait())
284
+ return run_in_loop(klong_loop, run_in_klong(klong, content))
256
285
 
257
286
 
258
- def setup_async_loop(start_loop_func, debug=False, slow_callback_duration=86400) -> asyncio.AbstractEventLoop:
259
- loop = asyncio.new_event_loop()
260
- loop.slow_callback_duration = slow_callback_duration
261
- if debug:
262
- loop.set_debug(True)
263
- stop_event = asyncio.Event()
264
- thread = threading.Thread(target=lambda l=loop,e=stop_event: start_loop_func(l,e), args=(loop,), daemon=True)
265
- thread.start()
266
- return loop, thread, stop_event
267
-
268
-
269
- def cleanup_async_loop(loop: asyncio.AbstractEventLoop, loop_thread, stop_event, debug=True, name=None) -> None:
270
-
271
- if loop.is_closed():
272
- return
273
-
274
- loop.call_soon_threadsafe(stop_event.set)
275
- loop_thread.join()
276
-
277
- pending_tasks = asyncio.all_tasks(loop=loop)
278
- if len(pending_tasks) > 0:
279
- if name:
280
- print(f"WARNING: pending tasks in {name} loop")
281
- print(f"cancelling {len(pending_tasks)} pending tasks...")
282
- for task in pending_tasks:
283
- loop.call_soon_threadsafe(task.cancel)
284
- # wait for all tasks to be cancelled but we can't use run_until_complete because the loop is already running
285
- while len(asyncio.all_tasks(loop=loop)) > 0:
286
- time.sleep(0)
287
-
288
- loop.stop()
289
-
290
- if not loop.is_closed():
291
- loop.close()
292
-
293
-
294
- def append_pkg_resource_path_KLONGPATH():
295
- with importlib.resources.as_file(importlib.resources.files('klongpy')) as pkg_path:
296
- pkg_lib_path = os.path.join(pkg_path, "lib")
297
- klongpath = os.environ.get("KLONGPATH", ".:lib")
298
- klongpath = f"{klongpath}:{pkg_lib_path}" if klongpath else str(pkg_lib_path)
299
- os.environ["KLONGPATH"] = klongpath
300
-
301
-
302
- if __name__ == "__main__":
287
+ def main():
288
+ """Main entry point for the KlongPy REPL."""
303
289
  if '--' in sys.argv:
304
290
  index = sys.argv.index('--')
305
291
  main_args = sys.argv[:index]
@@ -318,37 +304,49 @@ if __name__ == "__main__":
318
304
  parser.add_argument('-t', '--test', help='test program from file')
319
305
  parser.add_argument('-v', '--verbose', help='enable verbose output', action="store_true")
320
306
  parser.add_argument('-d', '--debug', help='enable debug mode', action="store_true")
307
+ parser.add_argument('--backend', help='set array backend', type=str.lower, choices=list_backends())
308
+ parser.add_argument('--device', help='set backend device (torch only)', type=str)
321
309
  parser.add_argument('filename', nargs='?', help='filename to be run if no flags are specified')
322
310
 
323
311
  args = parser.parse_args(main_args[1:])
324
312
 
313
+ # Default to torch backend if available and not explicitly set
314
+ if args.backend is None:
315
+ available_backends = list_backends()
316
+ if 'torch' in available_backends:
317
+ args.backend = 'torch'
318
+
325
319
  if args.debug:
326
320
  print("args: ", args)
327
321
 
328
322
  if args.expr:
329
- print(KlongInterpreter()(args.expr))
330
- exit()
331
-
332
- klong = KlongInterpreter()
333
-
334
- io_loop, io_loop_thread, io_stop_event = setup_async_loop(start_loop, debug=args.debug)
335
- klong_loop, klong_loop_thread, klong_stop_event = setup_async_loop(start_loop, debug=args.debug)
336
-
337
- shutdown_event = CallbackEvent()
323
+ try:
324
+ klong = KlongInterpreter(backend=args.backend, device=args.device)
325
+ except ValueError as exc:
326
+ parser.error(str(exc))
327
+ result = klong(args.expr)
328
+ if result is not None:
329
+ print(kg_write(result, klong._backend, display=False))
330
+ return
338
331
 
339
- append_pkg_resource_path_KLONGPATH()
332
+ try:
333
+ klong, loops = create_repl(debug=args.debug, backend=args.backend, device=args.device)
334
+ except ValueError as exc:
335
+ parser.error(str(exc))
336
+ io_loop, _, _, klong_loop, _, _ = loops
337
+ shutdown_event = klong['.system']['closeEvent']
340
338
 
341
339
  klong['.helpdb'] = []
342
- klong['.system'] = {'ioloop': io_loop, 'klongloop': klong_loop, 'closeEvent': shutdown_event}
343
340
  klong['.os.env'] = dict(os.environ)
344
341
  klong['.os.argv'] = extras if extras else []
345
342
 
346
343
  run_repl = False
344
+ exit_code = None
347
345
 
348
346
  if args.server:
349
347
  r = klong(f".srv({args.server})")
350
348
  if r == 0:
351
- print(f"Failed to start server")
349
+ print("Failed to start server")
352
350
  elif args.test:
353
351
  print(f"Test: {args.test}")
354
352
  with open(args.test, "r") as f:
@@ -362,7 +360,9 @@ if __name__ == "__main__":
362
360
  if args.filename:
363
361
  if args.verbose:
364
362
  print(f"Running: {args.filename}")
365
- run_file(klong_loop, klong, args.filename, verbose=args.verbose)
363
+ result = run_file(klong_loop, klong, args.filename, verbose=args.verbose)
364
+ if isinstance(result, ExitRequest):
365
+ exit_code = result.code
366
366
 
367
367
  def gather_io_tasks(io_loop):
368
368
  done_event = threading.Event()
@@ -377,7 +377,8 @@ if __name__ == "__main__":
377
377
  done_event.set()
378
378
  return done_event
379
379
 
380
- gather_io_tasks(io_loop).wait()
380
+ if exit_code is None:
381
+ gather_io_tasks(io_loop).wait()
381
382
  else:
382
383
  run_repl = True
383
384
 
@@ -390,14 +391,33 @@ if __name__ == "__main__":
390
391
  if args.load:
391
392
  if args.verbose:
392
393
  print(f"Loading: {args.load}")
393
- run_file(klong_loop, klong, args.load, verbose=args.verbose)
394
- colorama.init(autoreset=True)
395
- show_repl_header(args.server)
396
- console_loop.create_task(ConsoleInputHandler.input_producer(console_loop, klong_loop, klong, args.verbose))
397
- console_loop.run_forever()
398
- console_loop.close()
394
+ result = run_file(klong_loop, klong, args.load, verbose=args.verbose)
395
+ if isinstance(result, ExitRequest):
396
+ exit_code = result.code
397
+ run_repl = False
398
+ if run_repl:
399
+ exit_state = {"code": None}
400
+ colorama.init(autoreset=True)
401
+ backend_name = klong._backend.name
402
+ device_name = str(klong._backend.device) if hasattr(klong._backend, 'device') else None
403
+ show_repl_header(backend_name, device_name, args.server)
404
+ console_loop.create_task(ConsoleInputHandler.input_producer(console_loop, klong_loop, klong, args.verbose, exit_state))
405
+ try:
406
+ console_loop.run_forever()
407
+ except KeyboardInterrupt:
408
+ exit_state["code"] = 130
409
+ console_loop.stop()
410
+ console_loop.close()
411
+ if exit_state["code"] is not None:
412
+ exit_code = exit_state["code"]
399
413
 
400
414
  shutdown_event.trigger()
401
415
 
402
- cleanup_async_loop(io_loop, io_loop_thread, stop_event=io_stop_event, debug=args.debug, name="io_loop")
403
- cleanup_async_loop(klong_loop, klong_loop_thread, stop_event=klong_stop_event, debug=args.debug, name="klong_loop")
416
+ cleanup_repl(loops, debug=args.debug)
417
+
418
+ if exit_code is not None:
419
+ sys.exit(exit_code)
420
+
421
+
422
+ if __name__ == "__main__":
423
+ main()