koatl 0.1.11__tar.gz → 0.1.13__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.
Files changed (112) hide show
  1. {koatl-0.1.11 → koatl-0.1.13}/Cargo.lock +2 -1
  2. {koatl-0.1.11 → koatl-0.1.13}/PKG-INFO +1 -2
  3. {koatl-0.1.11 → koatl-0.1.13}/koatl/Cargo.toml +3 -2
  4. koatl-0.1.13/koatl/python/koatl/__init__.py +15 -0
  5. {koatl-0.1.11 → koatl-0.1.13}/koatl/python/koatl/__main__.py +45 -18
  6. koatl-0.1.13/koatl/python/koatl/prelude/functional/async.tl +38 -0
  7. koatl-0.1.13/koatl/python/koatl/prelude/functional/async_util.py +15 -0
  8. {koatl-0.1.11 → koatl-0.1.13}/koatl/python/koatl/prelude/functional/ok.tl +1 -1
  9. {koatl-0.1.11 → koatl-0.1.13}/koatl/python/koatl/prelude/iterable.tl +2 -2
  10. koatl-0.1.13/koatl/python/koatl/runtime/__init__.py +42 -0
  11. koatl-0.1.11/koatl/python/koatl/runtime/__init__.py → koatl-0.1.13/koatl/python/koatl/runtime/helpers.py +15 -35
  12. koatl-0.1.13/koatl/python/koatl/runtime/record.py +88 -0
  13. koatl-0.1.13/koatl/python/koatl/runtime/virtual.py +111 -0
  14. koatl-0.1.13/koatl/src/lib.rs +184 -0
  15. {koatl-0.1.11 → koatl-0.1.13}/koatl/tests/e2e/base/coal.tl +3 -3
  16. koatl-0.1.13/koatl/tests/e2e/base/iterables.tl +21 -0
  17. {koatl-0.1.11 → koatl-0.1.13}/koatl/tests/e2e/base/match.tl +7 -7
  18. koatl-0.1.13/koatl/tests/e2e/base/placeholder.tl +37 -0
  19. {koatl-0.1.11 → koatl-0.1.13}/koatl/tests/e2e/prelude/async.tl +2 -2
  20. koatl-0.1.13/koatl/tests/e2e/prelude/virtual.tl +14 -0
  21. {koatl-0.1.11 → koatl-0.1.13}/koatl/tests/test_e2e.py +1 -1
  22. {koatl-0.1.11 → koatl-0.1.13}/koatl-core/parser/src/ast.rs +4 -4
  23. {koatl-0.1.11 → koatl-0.1.13}/koatl-core/parser/src/parser.rs +45 -19
  24. {koatl-0.1.11 → koatl-0.1.13}/koatl-core/parser/src/util.rs +5 -2
  25. {koatl-0.1.11 → koatl-0.1.13}/koatl-core/src/lib.rs +1 -1
  26. {koatl-0.1.11 → koatl-0.1.13}/koatl-core/src/main.rs +2 -2
  27. {koatl-0.1.11 → koatl-0.1.13}/koatl-core/src/transform.rs +195 -189
  28. {koatl-0.1.11 → koatl-0.1.13}/pyproject.toml +4 -3
  29. koatl-0.1.13/python/koatl/__init__.py +15 -0
  30. {koatl-0.1.11 → koatl-0.1.13}/python/koatl/__main__.py +45 -18
  31. koatl-0.1.13/python/koatl/prelude/functional/async.tl +38 -0
  32. koatl-0.1.13/python/koatl/prelude/functional/async_util.py +15 -0
  33. {koatl-0.1.11 → koatl-0.1.13}/python/koatl/prelude/functional/ok.tl +1 -1
  34. {koatl-0.1.11 → koatl-0.1.13}/python/koatl/prelude/iterable.tl +2 -2
  35. koatl-0.1.13/python/koatl/runtime/__init__.py +42 -0
  36. koatl-0.1.11/python/koatl/runtime/__init__.py → koatl-0.1.13/python/koatl/runtime/helpers.py +15 -35
  37. koatl-0.1.13/python/koatl/runtime/record.py +88 -0
  38. koatl-0.1.13/python/koatl/runtime/virtual.py +111 -0
  39. koatl-0.1.11/koatl/python/koatl/__init__.py +0 -7
  40. koatl-0.1.11/koatl/python/koatl/prelude/functional/async.tl +0 -18
  41. koatl-0.1.11/koatl/python/koatl/prelude/functional/async_util.py +0 -27
  42. koatl-0.1.11/koatl/python/koatl/runtime/traits.py +0 -66
  43. koatl-0.1.11/koatl/src/lib.rs +0 -310
  44. koatl-0.1.11/koatl/tests/e2e/base/iterables.tl +0 -21
  45. koatl-0.1.11/koatl/tests/e2e/base/placeholder.tl +0 -23
  46. koatl-0.1.11/koatl/tests/e2e/prelude/virtual.tl +0 -14
  47. koatl-0.1.11/python/koatl/__init__.py +0 -7
  48. koatl-0.1.11/python/koatl/prelude/functional/async.tl +0 -18
  49. koatl-0.1.11/python/koatl/prelude/functional/async_util.py +0 -27
  50. koatl-0.1.11/python/koatl/runtime/traits.py +0 -66
  51. {koatl-0.1.11 → koatl-0.1.13}/Cargo.toml +0 -0
  52. {koatl-0.1.11 → koatl-0.1.13}/README.md +0 -0
  53. {koatl-0.1.11 → koatl-0.1.13}/koatl/.github/workflows/CI.yml +0 -0
  54. {koatl-0.1.11 → koatl-0.1.13}/koatl/.gitignore +0 -0
  55. {koatl-0.1.11 → koatl-0.1.13}/koatl/LICENSE +0 -0
  56. {koatl-0.1.11 → koatl-0.1.13}/koatl/README.md +0 -0
  57. {koatl-0.1.11 → koatl-0.1.13}/koatl/python/koatl/cli.py +0 -0
  58. {koatl-0.1.11 → koatl-0.1.13}/koatl/python/koatl/notebook/__init__.py +0 -0
  59. {koatl-0.1.11 → koatl-0.1.13}/koatl/python/koatl/notebook/magic.py +0 -0
  60. {koatl-0.1.11 → koatl-0.1.13}/koatl/python/koatl/prelude/__init__.tl +0 -0
  61. {koatl-0.1.11 → koatl-0.1.13}/koatl/python/koatl/prelude/functional/__init__.tl +0 -0
  62. {koatl-0.1.11 → koatl-0.1.13}/koatl/python/koatl/prelude/functional/reader.tl +0 -0
  63. {koatl-0.1.11 → koatl-0.1.13}/koatl/python/koatl/runtime/meta_finder.py +0 -0
  64. {koatl-0.1.11 → koatl-0.1.13}/koatl/requirements.txt +0 -0
  65. {koatl-0.1.11 → koatl-0.1.13}/koatl/src/emit_py.rs +0 -0
  66. {koatl-0.1.11 → koatl-0.1.13}/koatl/tests/e2e/base/containers.tl +0 -0
  67. {koatl-0.1.11 → koatl-0.1.13}/koatl/tests/e2e/base/decorators.tl +0 -0
  68. {koatl-0.1.11 → koatl-0.1.13}/koatl/tests/e2e/base/destructure-for-and-fn.tl +0 -0
  69. {koatl-0.1.11 → koatl-0.1.13}/koatl/tests/e2e/base/destructure.tl +0 -0
  70. {koatl-0.1.11 → koatl-0.1.13}/koatl/tests/e2e/base/escape_ident.tl +0 -0
  71. {koatl-0.1.11 → koatl-0.1.13}/koatl/tests/e2e/base/fstr.tl +0 -0
  72. {koatl-0.1.11 → koatl-0.1.13}/koatl/tests/e2e/base/functions.tl +0 -0
  73. {koatl-0.1.11 → koatl-0.1.13}/koatl/tests/e2e/base/generator.tl +0 -0
  74. {koatl-0.1.11 → koatl-0.1.13}/koatl/tests/e2e/base/if_expr.tl +0 -0
  75. {koatl-0.1.11 → koatl-0.1.13}/koatl/tests/e2e/base/imports.tl +0 -0
  76. {koatl-0.1.11 → koatl-0.1.13}/koatl/tests/e2e/base/loops.tl +0 -0
  77. {koatl-0.1.11 → koatl-0.1.13}/koatl/tests/e2e/base/nary-list.tl +0 -0
  78. {koatl-0.1.11 → koatl-0.1.13}/koatl/tests/e2e/base/precedence.tl +0 -0
  79. {koatl-0.1.11 → koatl-0.1.13}/koatl/tests/e2e/base/semantic_whitespace.tl +0 -0
  80. {koatl-0.1.11 → koatl-0.1.13}/koatl/tests/e2e/base/slice.tl +0 -0
  81. {koatl-0.1.11 → koatl-0.1.13}/koatl/tests/e2e/base/try.tl +0 -0
  82. {koatl-0.1.11 → koatl-0.1.13}/koatl/tests/e2e/destructure.tl +0 -0
  83. {koatl-0.1.11 → koatl-0.1.13}/koatl/tests/e2e/prelude/ok.tl +0 -0
  84. {koatl-0.1.11 → koatl-0.1.13}/koatl/tests/e2e/prelude/reader.tl +0 -0
  85. {koatl-0.1.11 → koatl-0.1.13}/koatl/tests/e2e/util/__init__.py +0 -0
  86. {koatl-0.1.11 → koatl-0.1.13}/koatl/tests/e2e/util/module0.tl +0 -0
  87. {koatl-0.1.11 → koatl-0.1.13}/koatl/tests/e2e/util/module1.tl +0 -0
  88. {koatl-0.1.11 → koatl-0.1.13}/koatl/tests/e2e/util/module2.tl +0 -0
  89. {koatl-0.1.11 → koatl-0.1.13}/koatl/tests/parse/arith.tl +0 -0
  90. {koatl-0.1.11 → koatl-0.1.13}/koatl/tests/parse/assign.tl +0 -0
  91. {koatl-0.1.11 → koatl-0.1.13}/koatl/tests/parse/deco.tl +0 -0
  92. {koatl-0.1.11 → koatl-0.1.13}/koatl/tests/parse/func.tl +0 -0
  93. {koatl-0.1.11 → koatl-0.1.13}/koatl/tests/parse/matches.tl +0 -0
  94. {koatl-0.1.11 → koatl-0.1.13}/koatl/tests/test_parse.py +0 -0
  95. {koatl-0.1.11 → koatl-0.1.13}/koatl-core/Cargo.toml +0 -0
  96. {koatl-0.1.11 → koatl-0.1.13}/koatl-core/parser/Cargo.toml +0 -0
  97. {koatl-0.1.11 → koatl-0.1.13}/koatl-core/parser/src/lexer.rs +0 -0
  98. {koatl-0.1.11 → koatl-0.1.13}/koatl-core/parser/src/lib.rs +0 -0
  99. {koatl-0.1.11 → koatl-0.1.13}/koatl-core/parser/tests/lexer.rs +0 -0
  100. {koatl-0.1.11 → koatl-0.1.13}/koatl-core/src/linecol.rs +0 -0
  101. {koatl-0.1.11 → koatl-0.1.13}/koatl-core/src/parser.rs +0 -0
  102. {koatl-0.1.11 → koatl-0.1.13}/koatl-core/src/py/ast.rs +0 -0
  103. {koatl-0.1.11 → koatl-0.1.13}/koatl-core/src/py/emit.rs +0 -0
  104. {koatl-0.1.11 → koatl-0.1.13}/koatl-core/src/py/mod.rs +0 -0
  105. {koatl-0.1.11 → koatl-0.1.13}/koatl-core/src/py/util.rs +0 -0
  106. {koatl-0.1.11 → koatl-0.1.13}/python/koatl/cli.py +0 -0
  107. {koatl-0.1.11 → koatl-0.1.13}/python/koatl/notebook/__init__.py +0 -0
  108. {koatl-0.1.11 → koatl-0.1.13}/python/koatl/notebook/magic.py +0 -0
  109. {koatl-0.1.11 → koatl-0.1.13}/python/koatl/prelude/__init__.tl +0 -0
  110. {koatl-0.1.11 → koatl-0.1.13}/python/koatl/prelude/functional/__init__.tl +0 -0
  111. {koatl-0.1.11 → koatl-0.1.13}/python/koatl/prelude/functional/reader.tl +0 -0
  112. {koatl-0.1.11 → koatl-0.1.13}/python/koatl/runtime/meta_finder.py +0 -0
@@ -99,10 +99,11 @@ checksum = "f4c7245a08504955605670dbf141fceab975f15ca21570696aebe9d2e71576bd"
99
99
 
100
100
  [[package]]
101
101
  name = "koatl"
102
- version = "0.1.11"
102
+ version = "0.1.13"
103
103
  dependencies = [
104
104
  "ariadne",
105
105
  "koatl-core",
106
+ "once_cell",
106
107
  "pyo3",
107
108
  ]
108
109
 
@@ -1,10 +1,9 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: koatl
3
- Version: 0.1.11
3
+ Version: 0.1.13
4
4
  Classifier: Programming Language :: Rust
5
5
  Classifier: Programming Language :: Python :: Implementation :: CPython
6
6
  Classifier: Programming Language :: Python :: Implementation :: PyPy
7
- Requires-Dist: forbiddenfruit
8
7
  License-File: LICENSE
9
8
  Requires-Python: >=3.8
10
9
  Description-Content-Type: text/markdown; charset=UTF-8; variant=GFM
@@ -1,6 +1,6 @@
1
1
  [package]
2
2
  name = "koatl"
3
- version = "0.1.11"
3
+ version = "0.1.13"
4
4
  edition = "2021"
5
5
 
6
6
  [lib]
@@ -8,6 +8,7 @@ name = "koatl"
8
8
  crate-type = ["cdylib"]
9
9
 
10
10
  [dependencies]
11
+ koatl-core = { path = "../koatl-core" }
11
12
  pyo3 = "0.25.0"
12
13
  ariadne = "0.5.1"
13
- koatl-core = { path = "../koatl-core" }
14
+ once_cell = "1.19.0"
@@ -0,0 +1,15 @@
1
+ from . import _rs
2
+
3
+
4
+ def transpile(*args, **kwargs):
5
+ try:
6
+ return _rs.transpile(*args, **kwargs)
7
+ except SyntaxError as e:
8
+ raise SyntaxError(e.args[0].decode("utf8")) from None
9
+
10
+
11
+ def transpile_raw(*args, **kwargs):
12
+ try:
13
+ return _rs.transpile_raw(*args, **kwargs)
14
+ except SyntaxError as e:
15
+ raise SyntaxError(e.args[0].decode("utf8")) from None
@@ -3,13 +3,16 @@ import subprocess
3
3
  import os
4
4
  import time
5
5
 
6
+
6
7
  def main():
7
8
  mode = "script"
8
9
  transpile_only = False
9
10
 
10
11
  while True and len(sys.argv) > 1:
11
12
  if sys.argv[1] == "--help":
12
- print(f"Usage: python -m koatl [--trans] [--mode script|module|interactive] <filename.(py|tl)>")
13
+ print(
14
+ f"Usage: python -m koatl [--trans] [--mode script|module|interactive] <filename.(py|tl)>"
15
+ )
13
16
  sys.exit(1)
14
17
 
15
18
  if sys.argv[1] == "--mode":
@@ -31,37 +34,54 @@ def main():
31
34
  km = None
32
35
 
33
36
  try:
34
- km = KernelManager(kernel_name='koatl')
37
+ km = KernelManager(kernel_name="koatl")
35
38
  km.start_kernel()
36
39
 
37
40
  # Give the kernel a moment to fully initialize
38
41
  # time.sleep(1)
39
-
40
- jupyter_command = [sys.executable, '-m', 'jupyter_console', '--existing', km.connection_file]
42
+
43
+ jupyter_command = [
44
+ sys.executable,
45
+ "-m",
46
+ "jupyter_console",
47
+ "--existing",
48
+ km.connection_file,
49
+ ]
41
50
  subprocess.call(jupyter_command)
42
51
  except FileNotFoundError:
43
52
  print("\nError: 'jupyter' command not found.", file=sys.stderr)
44
- print("Please ensure 'jupyter_console' is installed (pip install jupyter_console) and in your PATH.", file=sys.stderr)
45
- print("Also, ensure 'koatl' kernel is installed for Jupyter.", file=sys.stderr)
53
+ print(
54
+ "Please ensure 'jupyter_console' is installed (pip install jupyter_console) and in your PATH.",
55
+ file=sys.stderr,
56
+ )
57
+ print(
58
+ "Also, ensure 'koatl' kernel is installed for Jupyter.", file=sys.stderr
59
+ )
46
60
  sys.exit(1)
47
61
  except Exception as e:
48
62
  print(f"\nAn unexpected error occurred: {e}", file=sys.stderr)
49
- print("Please ensure the 'koatl' kernel is correctly installed and registered with Jupyter.", file=sys.stderr)
63
+ print(
64
+ "Please ensure the 'koatl' kernel is correctly installed and registered with Jupyter.",
65
+ file=sys.stderr,
66
+ )
50
67
  sys.exit(1)
51
68
  finally:
52
69
  # 4. Clean up: Shutdown the kernel and remove its connection file
53
70
  if km and km.is_alive():
54
71
  print("Shutting down the kernel...")
55
- km.shutdown_kernel(now=True) # Forceful shutdown
72
+ km.shutdown_kernel(now=True) # Forceful shutdown
56
73
  print("Kernel shut down.")
57
-
74
+
58
75
  # Clean up the connection file if it still exists
59
76
  if km and km.connection_file and os.path.exists(km.connection_file):
60
77
  try:
61
78
  os.remove(km.connection_file)
62
79
  print(f"Removed connection file: {km.connection_file}")
63
80
  except OSError as e:
64
- print(f"Error removing connection file {km.connection_file}: {e}", file=sys.stderr)
81
+ print(
82
+ f"Error removing connection file {km.connection_file}: {e}",
83
+ file=sys.stderr,
84
+ )
65
85
  else:
66
86
  script_path = sys.argv[1]
67
87
  sys.argv = sys.argv[1:]
@@ -73,27 +93,34 @@ def main():
73
93
  print(f"Error: File not found at '{script_path}'")
74
94
  sys.exit(1)
75
95
 
76
- if script_path.endswith('.tl'):
96
+ if script_path.endswith(".tl"):
77
97
  import koatl.cli
98
+
78
99
  if transpile_only:
79
- print(koatl.cli.transpile_from_source(original_script_code, mode=mode, script_path=script_path))
100
+ print(
101
+ koatl.cli.transpile_from_source(
102
+ original_script_code, mode=mode, script_path=script_path
103
+ )
104
+ )
80
105
  else:
81
- koatl.cli.run_from_source(original_script_code, mode=mode, script_path=script_path)
106
+ koatl.cli.run_from_source(
107
+ original_script_code, mode=mode, script_path=script_path
108
+ )
82
109
  else:
83
- injected_code = \
84
- """
110
+ injected_code = """
85
111
  # -- injected code --
86
112
  from koatl.runtime import *
87
113
  """
88
114
 
89
115
  full_code = injected_code + original_script_code
90
- code_obj = compile(full_code, script_path, 'exec')
116
+ code_obj = compile(full_code, script_path, "exec")
91
117
 
92
118
  script_globals = {
93
- '__name__': '__main__',
119
+ "__name__": "__main__",
94
120
  }
95
121
 
96
122
  exec(code_obj, script_globals)
97
123
 
124
+
98
125
  if __name__ == "__main__":
99
- main()
126
+ main()
@@ -0,0 +1,38 @@
1
+ import functools.wraps
2
+ import asyncio
3
+ import .async_util
4
+
5
+ export Async = class:
6
+ __init__ = (self, awaitable) => self.generator = awaitable.__await__()
7
+
8
+ __await__ = self => self.generator
9
+
10
+ __repr__ = self => "Async(...)"
11
+
12
+ from_generator_fn = &[staticmethod] (generator_fn, *args, **kwargs) =>
13
+ m = object.__new__(Async)
14
+ m.generator = generator_fn(*args, **kwargs)
15
+ return m
16
+
17
+ run = self => asyncio.run(async_util.to_coro(self))
18
+
19
+ bind_once = (self, f) => Async.from_generator_fn(
20
+ () =>
21
+ result = f(yield from self.__await__())
22
+
23
+ if hasattr(result, "__await__"):
24
+ return yield from result.__await__()
25
+
26
+ return result
27
+ )
28
+
29
+ pure = &[staticmethod] x => Async.from_generator_fn(
30
+ () =>
31
+ return x
32
+ yield None
33
+ )
34
+
35
+ sleep = &[staticmethod] x => Async(asyncio.sleep(x))
36
+
37
+ #- TODO: Why is asyncio.gather eager? -#
38
+ gather = &[staticmethod] (*args) => Async.from_generator_fn(() => yield from asyncio.gather(*args))
@@ -0,0 +1,15 @@
1
+ """
2
+ At the time of writing, I haven't implemented async def
3
+ in Koatl, so this is a workaround.
4
+
5
+ This function converts a generic awaitable (perhaps a
6
+ vanilla generator) into a coroutine that can be used
7
+ by asyncio.run, which expects strictly a coroutine.
8
+
9
+ (In practice, async def shouldn't be needed anyways,
10
+ since we have the Async monad.)
11
+ """
12
+
13
+
14
+ async def to_coro(awaitable):
15
+ return await awaitable
@@ -1,5 +1,5 @@
1
1
  import functools.wraps
2
- import koatl.runtime.traits.register_global_attr
2
+ import koatl.runtime.virtual.register_global_attr
3
3
 
4
4
  OkMeta = class(type):
5
5
  __instancecheck__ = (cls, instance) => __tl__.ok(instance)
@@ -44,10 +44,10 @@ methods = {
44
44
  acc
45
45
 
46
46
  list: x =>
47
- list(x!iter)
47
+ list(x.iter)
48
48
 
49
49
  record: x =>
50
- Record(x!iter)
50
+ Record(x.iter)
51
51
  }
52
52
 
53
53
  # TODO merge this with typing.Iterable?
@@ -0,0 +1,42 @@
1
+ """
2
+ This module initializes the environment for koatl.
3
+ It sets up the meta-finder hook to enable importing .tl files,
4
+ and also declares functions that are required for certain tl features to work,
5
+ such as coalescing and module exports.
6
+
7
+ koatl.runtime should be written in Python only since otherwise
8
+ it would create a circular dependency.
9
+ """
10
+
11
+ import functools
12
+ from types import SimpleNamespace
13
+
14
+ from . import meta_finder
15
+
16
+ meta_finder.install_hook()
17
+
18
+
19
+ from .virtual import *
20
+ from .record import *
21
+ from .helpers import *
22
+
23
+
24
+ __tl__ = SimpleNamespace(
25
+ unpack_record=helpers.unpack_record,
26
+ set_exports=helpers.set_exports,
27
+ do=helpers.do,
28
+ vget=helpers.vget,
29
+ ok=helpers.ok,
30
+ partial=functools.partial,
31
+ **{name: helpers.__dict__[name] for name in helpers.__all__},
32
+ **{name: record.__dict__[name] for name in record.__all__},
33
+ **{name: virtual.__dict__[name] for name in virtual.__all__}
34
+ )
35
+
36
+
37
+ __all__ = [
38
+ "__tl__",
39
+ *helpers.__all__,
40
+ *record.__all__,
41
+ *virtual.__all__,
42
+ ]
@@ -1,17 +1,9 @@
1
- """
2
- This module initializes the environment for koatl.
3
- It sets up the meta-finder hook to enable importing .tl files,
4
- and also declares functions that are required for certain tl features to work,
5
- such as coalescing and module exports.
6
- """
7
-
8
1
  from functools import wraps
9
- from types import SimpleNamespace
2
+ from .virtual import vget
3
+ from .record import Record
10
4
 
11
- from . import meta_finder
12
- from .._rs import Record, vget, ok
13
5
 
14
- meta_finder.install_hook()
6
+ __all__ = ["MatchError"]
15
7
 
16
8
 
17
9
  class MatchError(Exception):
@@ -37,7 +29,10 @@ def set_exports(package_name, globals_dict, exports, module_star_exports):
37
29
 
38
30
  exports.add(name)
39
31
 
40
- globals_dict["__all__"] = tuple(exports)
32
+ if "__all__" not in globals_dict:
33
+ globals_dict["__all__"] = ()
34
+
35
+ globals_dict["__all__"] = tuple(set(globals_dict["__all__"]) | exports)
41
36
 
42
37
 
43
38
  def unpack_record(obj):
@@ -50,6 +45,14 @@ def unpack_record(obj):
50
45
  return Record(obj.__dict__)
51
46
 
52
47
 
48
+ def ok(obj):
49
+ if obj is None:
50
+ return False
51
+ if isinstance(obj, BaseException):
52
+ return False
53
+ return True
54
+
55
+
53
56
  def do(f):
54
57
  @wraps(f)
55
58
  def impl(*args, **kwargs):
@@ -74,26 +77,3 @@ def do(f):
74
77
  return vget(m, "bind_once")(recurse)
75
78
 
76
79
  return impl
77
-
78
-
79
- from .traits import *
80
-
81
-
82
- __tl__ = SimpleNamespace(
83
- Record=Record,
84
- MatchError=MatchError,
85
- unpack_record=unpack_record,
86
- set_exports=set_exports,
87
- vget=vget,
88
- ok=ok,
89
- do=do,
90
- **{name: traits.__dict__[name] for name in traits.__all__}
91
- )
92
-
93
-
94
- __all__ = [
95
- "__tl__",
96
- "Record",
97
- "MatchError",
98
- *traits.__all__,
99
- ]
@@ -0,0 +1,88 @@
1
+ import re
2
+
3
+ __all__ = ["Record"]
4
+
5
+
6
+ class Record(dict):
7
+ def __repr__(self):
8
+ return self._repr_with_visited(set())
9
+
10
+ def __getattr__(self, name):
11
+ try:
12
+ return self[name]
13
+ except KeyError:
14
+ raise AttributeError(
15
+ f"'{type(self).__name__}' object has no attribute '{name}'"
16
+ ) from None
17
+
18
+ def _repr_with_visited(self, visited):
19
+ # Handle cycles by checking if this object is already being processed
20
+ obj_id = id(self)
21
+ if obj_id in visited:
22
+ return "{...}"
23
+
24
+ visited.add(obj_id)
25
+ try:
26
+ if not self:
27
+ return "{}"
28
+
29
+ items = []
30
+ for key, value in self.items():
31
+ key_str = self._format_key(key)
32
+
33
+ # Handle value representation with cycle detection
34
+ if isinstance(value, Record):
35
+ value_str = value._repr_with_visited(visited.copy())
36
+ elif hasattr(value, "__dict__") and hasattr(value, "__class__"):
37
+ # For other objects that might contain cycles, use a simple repr
38
+ value_str = repr(value)
39
+ else:
40
+ value_str = repr(value)
41
+
42
+ items.append(f"{key_str}: {value_str}")
43
+
44
+ return "{" + ", ".join(items) + "}"
45
+ finally:
46
+ visited.remove(obj_id)
47
+
48
+ def _format_key(self, key):
49
+ if isinstance(key, str) and self._is_identifier(key):
50
+ # If key is an identifier, drop the quotes
51
+ return key
52
+
53
+ elif isinstance(key, (int, float, bool, type(None))):
54
+ # If key is a literal like 0, 1, True, False, None, use repr
55
+ return repr(key)
56
+
57
+ else:
58
+ # Otherwise, use f"({repr(key)})"
59
+ return f"({repr(key)})"
60
+
61
+ def _is_identifier(self, s):
62
+ return (
63
+ isinstance(s, str)
64
+ and re.match(r"^[a-zA-Z_][a-zA-Z0-9_]*$", s)
65
+ and s not in koatl_keywords
66
+ )
67
+
68
+
69
+ koatl_keywords = {
70
+ "if",
71
+ "else",
72
+ "while",
73
+ "for",
74
+ "def",
75
+ "class",
76
+ "return",
77
+ "import",
78
+ "from",
79
+ "as",
80
+ "with",
81
+ "try",
82
+ "except",
83
+ "finally",
84
+ "raise",
85
+ "assert",
86
+ "async",
87
+ "await",
88
+ }
@@ -0,0 +1,111 @@
1
+ from functools import partial
2
+ from itertools import count
3
+ from types import new_class
4
+ from .._rs import fast_vget, fast_vset, fast_vset_trait
5
+
6
+
7
+ def ExtensionProperty(f):
8
+ f.ext_prop = True
9
+ return f
10
+
11
+
12
+ def vget(obj, name):
13
+ try:
14
+ return getattr(obj, name)
15
+ except:
16
+ # special case for iter - this could be implemented using types and trait vtbls
17
+ # but this is simpler and probably faster
18
+ if name == "iter":
19
+ if isinstance(obj, slice):
20
+ start = obj.start if obj.start is not None else 0
21
+ step = obj.step if obj.step is not None else 1
22
+ if obj.stop is None:
23
+ return count(start, step)
24
+ else:
25
+ return range(start, obj.stop, step)
26
+
27
+ try:
28
+ return obj.items()
29
+ except AttributeError:
30
+ pass
31
+
32
+ try:
33
+ return iter(obj)
34
+ except TypeError:
35
+ pass
36
+
37
+ v = fast_vget(obj, name)
38
+ if v is not None:
39
+ if hasattr(v, "ext_prop"):
40
+ return v(obj)
41
+
42
+ return partial(v, obj)
43
+
44
+ raise AttributeError(
45
+ f"'{type(obj).__name__}' object has no v-attribute '{name}'"
46
+ ) from None
47
+
48
+
49
+ def Trait(module, name, methods, *, requires=[]):
50
+ def fix_methods(type_name, methods):
51
+ import inspect
52
+
53
+ for method_name, method in methods.items():
54
+ method.__name__ = method_name
55
+ method.__qualname__ = f"{type_name}.{method_name}"
56
+
57
+ if not isinstance(method, (staticmethod, classmethod)):
58
+ sig = inspect.signature(method)
59
+ params = list(sig.parameters.values())
60
+ params[0].replace(name="self")
61
+ sig = sig.replace(parameters=params)
62
+ method.__signature__ = sig
63
+
64
+ fix_methods(name, methods)
65
+
66
+ def instancecheck(cls, instance):
67
+ if cls != typ:
68
+ # if not checking for the trait itself, use the default behavior
69
+ return type.__instancecheck__(cls, instance)
70
+
71
+ for req in requires:
72
+ try:
73
+ vget(instance, req)
74
+ except AttributeError:
75
+ return False
76
+
77
+ return True
78
+
79
+ meta = type(
80
+ f"{name}Meta",
81
+ (type,),
82
+ {"__instancecheck__": instancecheck, "__module__": "types"},
83
+ )
84
+
85
+ def populate(ns):
86
+ for method_name, method in methods.items():
87
+ ns[method_name] = method
88
+ ns["__module__"] = module
89
+ ns["_methods"] = methods
90
+ ns["_requires"] = requires
91
+
92
+ typ = new_class(name, (), {"metaclass": meta}, populate)
93
+
94
+ return typ
95
+
96
+
97
+ def register_global_attr(type, name, value):
98
+ fast_vset(type, name, value)
99
+
100
+
101
+ def register_global_trait(type):
102
+ for name, method in type._methods.items():
103
+ fast_vset_trait(type.__name__, type._requires, name, method)
104
+
105
+
106
+ __all__ = [
107
+ "Trait",
108
+ "ExtensionProperty",
109
+ "register_global_attr",
110
+ "register_global_trait",
111
+ ]