koskript 1.4.2__tar.gz → 1.5.0__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 (35) hide show
  1. {koskript-1.4.2 → koskript-1.5.0}/PKG-INFO +10 -12
  2. {koskript-1.4.2 → koskript-1.5.0}/README.md +7 -7
  3. {koskript-1.4.2 → koskript-1.5.0}/pyproject.toml +6 -11
  4. {koskript-1.4.2 → koskript-1.5.0}/src/koskript/__init__.py +26 -109
  5. koskript-1.5.0/src/koskript/headers.py +57 -0
  6. {koskript-1.4.2 → koskript-1.5.0}/src/koskript/lang/compiler.py +94 -26
  7. {koskript-1.4.2 → koskript-1.5.0}/src/koskript/lang/emtypes.py +1 -0
  8. {koskript-1.4.2 → koskript-1.5.0}/src/koskript/lang/interpreter.py +55 -30
  9. {koskript-1.4.2 → koskript-1.5.0}/src/koskript/lang/lexer.py +30 -6
  10. {koskript-1.4.2 → koskript-1.5.0}/src/koskript/lang/parser.py +70 -20
  11. koskript-1.5.0/src/koskript/lsp.py +205 -0
  12. {koskript-1.4.2 → koskript-1.5.0}/src/koskript/stdlib/core.py +160 -159
  13. {koskript-1.4.2 → koskript-1.5.0}/src/koskript.egg-info/PKG-INFO +10 -12
  14. {koskript-1.4.2 → koskript-1.5.0}/src/koskript.egg-info/SOURCES.txt +2 -4
  15. {koskript-1.4.2 → koskript-1.5.0}/tests/test_parser.py +133 -111
  16. koskript-1.4.2/src/koskript/grammar.lark +0 -225
  17. koskript-1.4.2/src/koskript/lang/astgen.py +0 -479
  18. koskript-1.4.2/src/koskript.egg-info/requires.txt +0 -1
  19. koskript-1.4.2/tests/test_grammar.py +0 -700
  20. {koskript-1.4.2 → koskript-1.5.0}/LICENSE +0 -0
  21. {koskript-1.4.2 → koskript-1.5.0}/setup.cfg +0 -0
  22. {koskript-1.4.2 → koskript-1.5.0}/src/koskript/lang/errors.py +0 -0
  23. {koskript-1.4.2 → koskript-1.5.0}/src/koskript/stdlib/__init__.py +0 -0
  24. {koskript-1.4.2 → koskript-1.5.0}/src/koskript/stdlib/arrays.py +0 -0
  25. {koskript-1.4.2 → koskript-1.5.0}/src/koskript/stdlib/byteslib.py +0 -0
  26. {koskript-1.4.2 → koskript-1.5.0}/src/koskript/stdlib/helpers.py +0 -0
  27. {koskript-1.4.2 → koskript-1.5.0}/src/koskript/stdlib/jsonlib.py +0 -0
  28. {koskript-1.4.2 → koskript-1.5.0}/src/koskript/stdlib/maps.py +0 -0
  29. {koskript-1.4.2 → koskript-1.5.0}/src/koskript/stdlib/mathlib.py +0 -0
  30. {koskript-1.4.2 → koskript-1.5.0}/src/koskript/stdlib/strings.py +0 -0
  31. {koskript-1.4.2 → koskript-1.5.0}/src/koskript.egg-info/dependency_links.txt +0 -0
  32. {koskript-1.4.2 → koskript-1.5.0}/src/koskript.egg-info/top_level.txt +0 -0
  33. {koskript-1.4.2 → koskript-1.5.0}/tests/test_language.py +0 -0
  34. {koskript-1.4.2 → koskript-1.5.0}/tests/test_modules.py +0 -0
  35. {koskript-1.4.2 → koskript-1.5.0}/tests/test_stdlib.py +0 -0
@@ -1,7 +1,7 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: koskript
3
- Version: 1.4.2
4
- Summary: A programming language fully embbeded on python.
3
+ Version: 1.5.0
4
+ Summary: A programming language fully embedded in Python.
5
5
  Author: alesis.buzz
6
6
  License: Mozilla Public License Version 2.0
7
7
  ==================================
@@ -378,7 +378,7 @@ License: Mozilla Public License Version 2.0
378
378
  defined by the Mozilla Public License, v. 2.0.
379
379
 
380
380
  Project-URL: Homepage, https://github.com/alesis-buzz/koskript
381
- Keywords: scripting,programming-language,embedded,interpreter,parser,lark
381
+ Keywords: scripting,programming-language,embedded,interpreter,parser
382
382
  Classifier: Development Status :: 3 - Alpha
383
383
  Classifier: Programming Language :: Python :: 3
384
384
  Classifier: Programming Language :: Python :: 3 :: Only
@@ -387,12 +387,10 @@ Classifier: Programming Language :: Python :: 3.11
387
387
  Classifier: Programming Language :: Python :: 3.12
388
388
  Classifier: Programming Language :: Python :: 3.13
389
389
  Classifier: Programming Language :: Python :: 3.14
390
- Classifier: License :: OSI Approved :: Mozilla Public License 2.0 (MPL 2.0)
391
390
  Classifier: Topic :: Software Development :: Interpreters
392
391
  Requires-Python: >=3.10
393
392
  Description-Content-Type: text/markdown
394
393
  License-File: LICENSE
395
- Requires-Dist: lark>=1.3.1
396
394
  Dynamic: license-file
397
395
 
398
396
  ![Koskript](https://raw.githubusercontent.com/alesisce/koskript/refs/heads/main/banner.png)
@@ -466,13 +464,13 @@ There is an experimental dependency-free parser behind
466
464
 
467
465
  | Document | What it covers |
468
466
  |---|---|
469
- | [Getting started](docs/getting-started.md) | Installation, first script, a complete example |
470
- | [Language reference](docs/language.md) | Types, variables, operators, control flow, errors, functions, wrappers, namespaces, closures, modules, loops, strings, bytes |
471
- | [Classes](docs/classes.md) | Inheritance, visibility, static members, constructors, `super` |
472
- | [Standard library](docs/standard-library.md) | Core builtins and the `map`, `array`, `string`, `bytes`, `math` and `json` namespaces |
473
- | [Python interop](docs/python-interop.md) | Exposing Python values, callables and classes to scripts |
474
- | [Embedding guide](docs/embedding.md) | `KoskriptRuntime`, `run()`, error handling and stdlib activation |
475
- | [Roadmap](docs/roadmap.md) | What is done, what is planned, known limitations |
467
+ | [Getting started](docs/docs/getting-started.md) | Installation, first script, a complete example |
468
+ | [Language reference](docs/docs/language.md) | Types, variables, operators, control flow, errors, functions, wrappers, namespaces, closures, modules, loops, strings, bytes |
469
+ | [Classes](docs/docs/classes.md) | Inheritance, visibility, static members, constructors, `super` |
470
+ | [Standard library](docs/docs/standard-library.md) | Core builtins and the `map`, `array`, `string`, `bytes`, `math` and `json` namespaces |
471
+ | [Python interop](docs/docs/python-interop.md) | Exposing Python values, callables and classes to scripts |
472
+ | [Embedding guide](docs/docs/embedding.md) | `KoskriptRuntime`, `run()`, error handling and stdlib activation |
473
+ | [Roadmap](docs/docs/roadmap.md) | What is done, what is planned, known limitations |
476
474
 
477
475
  ---
478
476
 
@@ -69,13 +69,13 @@ There is an experimental dependency-free parser behind
69
69
 
70
70
  | Document | What it covers |
71
71
  |---|---|
72
- | [Getting started](docs/getting-started.md) | Installation, first script, a complete example |
73
- | [Language reference](docs/language.md) | Types, variables, operators, control flow, errors, functions, wrappers, namespaces, closures, modules, loops, strings, bytes |
74
- | [Classes](docs/classes.md) | Inheritance, visibility, static members, constructors, `super` |
75
- | [Standard library](docs/standard-library.md) | Core builtins and the `map`, `array`, `string`, `bytes`, `math` and `json` namespaces |
76
- | [Python interop](docs/python-interop.md) | Exposing Python values, callables and classes to scripts |
77
- | [Embedding guide](docs/embedding.md) | `KoskriptRuntime`, `run()`, error handling and stdlib activation |
78
- | [Roadmap](docs/roadmap.md) | What is done, what is planned, known limitations |
72
+ | [Getting started](docs/docs/getting-started.md) | Installation, first script, a complete example |
73
+ | [Language reference](docs/docs/language.md) | Types, variables, operators, control flow, errors, functions, wrappers, namespaces, closures, modules, loops, strings, bytes |
74
+ | [Classes](docs/docs/classes.md) | Inheritance, visibility, static members, constructors, `super` |
75
+ | [Standard library](docs/docs/standard-library.md) | Core builtins and the `map`, `array`, `string`, `bytes`, `math` and `json` namespaces |
76
+ | [Python interop](docs/docs/python-interop.md) | Exposing Python values, callables and classes to scripts |
77
+ | [Embedding guide](docs/docs/embedding.md) | `KoskriptRuntime`, `run()`, error handling and stdlib activation |
78
+ | [Roadmap](docs/docs/roadmap.md) | What is done, what is planned, known limitations |
79
79
 
80
80
  ---
81
81
 
@@ -4,8 +4,8 @@ build-backend = "setuptools.build_meta"
4
4
 
5
5
  [project]
6
6
  name = "koskript"
7
- version = "1.4.2"
8
- description = "A programming language fully embbeded on python."
7
+ version = "1.5.0"
8
+ description = "A programming language fully embedded in Python."
9
9
  readme = "README.md"
10
10
  requires-python = ">=3.10"
11
11
  license = { file = "LICENSE" }
@@ -19,8 +19,7 @@ keywords = [
19
19
  "programming-language",
20
20
  "embedded",
21
21
  "interpreter",
22
- "parser",
23
- "lark"
22
+ "parser"
24
23
  ]
25
24
 
26
25
  classifiers = [
@@ -32,19 +31,15 @@ classifiers = [
32
31
  "Programming Language :: Python :: 3.12",
33
32
  "Programming Language :: Python :: 3.13",
34
33
  "Programming Language :: Python :: 3.14",
35
- "License :: OSI Approved :: Mozilla Public License 2.0 (MPL 2.0)",
34
+
36
35
  "Topic :: Software Development :: Interpreters",
37
36
  ]
38
37
 
39
38
 
40
- dependencies = [
41
- # only the default parser needs it; `native_parser_experiment=True`
42
- # runs without it
43
- "lark>=1.3.1"
44
- ]
39
+ dependencies = []
45
40
 
46
41
  [project.urls]
47
42
  Homepage = "https://github.com/alesis-buzz/koskript"
48
43
 
49
44
  [tool.setuptools.package-data]
50
- koskript = ["*.lark", "*.json", "*.txt"]
45
+ koskript = ["*.json", "*.txt"]
@@ -8,74 +8,15 @@ from .lang.errors import Errors
8
8
  from .stdlib import build_globals
9
9
  import os
10
10
 
11
- _grammar_path = os.path.join(pathlib.Path(__file__).resolve().parent,
12
- "grammar.lark")
13
-
14
11
 
15
12
  def _wrap(value):
16
13
  return value if isinstance(value, KoskriptObject) else KoskriptObject(value=value)
17
14
 
18
15
 
19
- def _require_lark():
20
- """Import Lark, with a message that points at the experimental parser."""
21
- try:
22
- from lark import Lark
23
- except ImportError:
24
- raise Errors.RuntimeError(
25
- "the default parser needs the 'lark' package: install it, or pass "
26
- "native_parser_experiment=True to use the native parser") from None
27
- return Lark
28
-
29
-
30
- class _Grammar(object):
31
- """The Lark grammar, with the LALR tables built on first use.
32
-
33
- Building them takes about 200 ms, so it is deferred until a script is
34
- actually parsed: runtimes created with ``native_parser_experiment=True``
35
- never pay for them.
36
- """
37
-
38
- __slots__ = ("path", "parser")
39
-
40
- def __init__(self, path):
41
- self.path = path
42
- self.parser = None
43
-
44
- def parse(self, code: str):
45
- """Parse ``code`` into a Lark tree."""
46
- if self.parser is None:
47
- with open(self.path, "r") as grammar_file:
48
- self.parser = _require_lark()(
49
- grammar_file.read(), parser="lalr", maybe_placeholders=False)
50
- return self.parser.parse(code)
51
-
52
-
53
- grammar = _Grammar(_grammar_path)
54
-
55
16
  # How many compiled sources a runtime keeps. Running the same script again is
56
17
  # then free of parsing and compiling; the oldest entry is dropped past this.
57
18
  _CHUNK_CACHE = 32
58
19
 
59
-
60
- def _format_syntax_error(code: str, error) -> str:
61
- from lark.exceptions import UnexpectedInput
62
-
63
- if not isinstance(error, UnexpectedInput) or error.line is None:
64
- return str(error)
65
-
66
- lines = [f"Syntax error at line {error.line}, column {error.column}:", error.get_context(code).rstrip()]
67
-
68
- token = getattr(error, "token", None)
69
- if token is not None and token.type != "$END":
70
- lines.append(f"Unexpected token '{token}'.")
71
-
72
- expected = getattr(error, "expected", None)
73
- if expected:
74
- lines.append("Expected one of: " + ", ".join(sorted(expected)))
75
-
76
- return "\n".join(lines)
77
-
78
-
79
20
  class KoskriptRuntime(object):
80
21
  """Embeddable Koskript runtime.
81
22
 
@@ -86,14 +27,11 @@ class KoskriptRuntime(object):
86
27
  Any Python value or callable exposed to scripts is wrapped automatically,
87
28
  so you can pass plain functions without building ``KoskriptObject`` by hand.
88
29
 
89
- Scripts are parsed with Lark, the default and stable front end, which
90
- needs the ``lark`` package. ``native_parser_experiment=True`` switches to
91
- the experimental pure Python scanner and parser instead: it needs no
92
- dependency, starts faster and parses about 6x quicker, and it is what
93
- will become the default once it has been validated in production.
30
+ Scripts are parsed with the native pure Python scanner and parser,
31
+ which needs no external dependency.
94
32
 
95
33
  >>> rt = KoskriptRuntime({"print": print})
96
- >>> rt.execute("local x = 10\\nx + 26")
34
+ >>> rt.execute("let x = 10\\nx + 26")
97
35
  36
98
36
  """
99
37
 
@@ -105,39 +43,21 @@ class KoskriptRuntime(object):
105
43
  self.globals = {}
106
44
  self.native_parser_experiment = bool(native_parser_experiment)
107
45
  self.__interpreter__ = KoskriptInterpreter()
108
- self._ast = None
109
46
  self.__interpreter__.module_loader = self._load_module
110
47
  self._modules = {}
111
48
  self._loading = []
112
49
  self._import_paths = []
113
50
  self._chunks = {}
114
51
 
115
- if not self.native_parser_experiment:
116
- # fail here instead of on the first script
117
- _require_lark()
118
-
119
52
  if stdlib:
120
53
  self.register_many(build_globals(self.__interpreter__.call_value))
121
54
 
122
55
  if globals_map:
123
56
  self.register_many(globals_map)
124
57
 
125
- @property
126
- def __ast__(self):
127
- """The Lark AST transformer, created the first time it is needed.
128
-
129
- It lives in :mod:`koskript.lang.astgen`, which imports Lark, so
130
- runtimes using the native parser never load it.
131
- """
132
- transformer = self._ast
133
- if transformer is None:
134
- from .lang.astgen import KoskriptTransformer
135
- transformer = self._ast = KoskriptTransformer()
136
- return transformer
137
-
138
- @__ast__.setter
139
- def __ast__(self, transformer):
140
- self._ast = transformer
58
+ # expose `loadcode` to scripts: compiles a source string into a
59
+ # callable that runs it in this runtime
60
+ self.register("loadcode", self._loadcode_host)
141
61
 
142
62
  def register(self, name: str, value):
143
63
  """Expose a Python value or callable to scripts.
@@ -210,6 +130,25 @@ class KoskriptRuntime(object):
210
130
  self._chunks[code] = chunk
211
131
  return chunk
212
132
 
133
+ def compile(self, code: str):
134
+ """Compile ``code`` once and return a callable that runs it.
135
+
136
+ The returned object can be called with no arguments; it executes the
137
+ already compiled chunk in this runtime.
138
+ """
139
+ chunk = self._chunk(code)
140
+
141
+ def run():
142
+ try:
143
+ return self.__interpreter__.run_chunk(chunk)
144
+ except ThrownSignal as signal:
145
+ raise self.__interpreter__._uncaught(signal) from None
146
+
147
+ return run
148
+
149
+ def _loadcode_host(self, code):
150
+ return self.compile(str(code))
151
+
213
152
  def execute_module(self, path: str):
214
153
  """Execute ``path`` as a Koskript module and return the module.
215
154
 
@@ -224,31 +163,9 @@ class KoskriptRuntime(object):
224
163
  except ThrownSignal as signal:
225
164
  raise self.__interpreter__._uncaught(signal) from None
226
165
 
227
- def _transform(self, tree):
228
- """Run the AST transformer, unwrapping clean syntax errors."""
229
- from lark.exceptions import VisitError
230
-
231
- transformer = self.__ast__
232
- try:
233
- return transformer.transform(tree)
234
- except VisitError as e:
235
- if isinstance(e.orig_exc, Errors.SyntaxError):
236
- raise e.orig_exc from None
237
- raise
238
-
239
166
  def _compile(self, code: str):
240
167
  """Parse ``code`` into the list of statements the compiler consumes."""
241
- if self.native_parser_experiment:
242
- return parse(code)
243
-
244
- from lark.exceptions import LarkError
245
-
246
- try:
247
- tree = grammar.parse(code)
248
- except LarkError as e:
249
- raise Errors.SyntaxError(_format_syntax_error(code, e)) from e
250
- ast = self._transform(tree)
251
-
168
+ ast = parse(code)
252
169
  if not isinstance(ast, list):
253
170
  ast = [ast]
254
171
  return ast
@@ -0,0 +1,57 @@
1
+ """Helpers for reading Koskript header files (``.kosh``).
2
+
3
+ A ``.kosh`` file is a normal Koskript source that only contains
4
+ declarations: ``fun``, ``class``, ``const``, ``namespace`` and ``import``.
5
+ Bodies are usually empty (``{}``), so the file can be parsed with the
6
+ standard parser without being executed.
7
+ """
8
+
9
+ from .lang.parser import parse
10
+ from .lang import emtypes
11
+
12
+
13
+ def _collect(statements, symbols):
14
+ for stmt in statements:
15
+ kind = type(stmt)
16
+ if kind is emtypes.FnDef:
17
+ symbols["functions"].append({
18
+ "name": stmt.name,
19
+ "params": list(stmt.params),
20
+ })
21
+ elif kind is emtypes.ClassDef:
22
+ methods = []
23
+ for method in stmt.methods:
24
+ methods.append({
25
+ "name": method.name,
26
+ "params": list(method.params),
27
+ "modifiers": list(method.modifiers),
28
+ })
29
+ symbols["classes"].append({
30
+ "name": stmt.name,
31
+ "parent": stmt.parent_name,
32
+ "methods": methods,
33
+ "fields": [
34
+ {"name": f.name, "modifiers": list(f.modifiers)}
35
+ for f in stmt.fields
36
+ ],
37
+ })
38
+ elif kind is emtypes.ConstDecl:
39
+ symbols["constants"].append(stmt.name)
40
+ elif kind is emtypes.NamespaceDef:
41
+ nested = {"functions": [], "classes": [], "constants": [], "namespaces": []}
42
+ _collect(stmt.body, nested)
43
+ symbols["namespaces"].append({"name": stmt.name, "symbols": nested})
44
+
45
+
46
+ def load_header(source_or_path):
47
+ """Parse a ``.kosh`` source or file path and return its symbol index."""
48
+ if "\n" not in source_or_path and source_or_path.endswith(".kosh"):
49
+ with open(source_or_path, "r", encoding="utf-8-sig") as handle:
50
+ code = handle.read()
51
+ else:
52
+ code = source_or_path
53
+
54
+ statements = parse(code, allow_bodyless=True)
55
+ symbols = {"functions": [], "classes": [], "constants": [], "namespaces": []}
56
+ _collect(statements, symbols)
57
+ return symbols
@@ -22,9 +22,9 @@ from .errors import Errors
22
22
 
23
23
  _IDENTIFIER_RE = re.compile(r"[A-Za-z_][A-Za-z0-9_]*\Z")
24
24
  _RESERVED_NAMES = frozenset((
25
- "if", "elseif", "else", "while", "for", "foreach", "fn", "wrapper",
25
+ "if", "elseif", "else", "while", "for", "foreach", "fun", "wrapper",
26
26
  "return",
27
- "local", "const", "true", "false", "null", "and", "or", "not", "in",
27
+ "let", "const", "true", "false", "null", "and", "or", "not", "in",
28
28
  "break", "continue", "class", "extends", "new", "static", "public",
29
29
  "private", "this", "super", "constructor", "import", "as",
30
30
  "error", "throw", "try", "catch", "finally", "namespace",
@@ -157,15 +157,36 @@ class _Emitter(object):
157
157
  def _arguments(self, args, env):
158
158
  return ", ".join(f"({self.expr(arg, env)})" for arg in args)
159
159
 
160
+ def _split_args(self, args, env):
161
+ positional = []
162
+ keywords = []
163
+ for arg in args:
164
+ if type(arg) is KwArg:
165
+ keywords.append((arg.name, self.expr(arg.value, env)))
166
+ else:
167
+ positional.append(self.expr(arg, env))
168
+ pos_code = ", ".join(f"({item})" for item in positional)
169
+ if keywords:
170
+ kw_code = "{" + ", ".join(
171
+ f"{name!r}: ({value})" for name, value in keywords) + "}"
172
+ return pos_code, kw_code
173
+ return pos_code, None
174
+
160
175
  def _call(self, node, env="env"):
161
- args = self._arguments(node.args, env)
176
+ pos_code, kw_code = self._split_args(node.args, env)
162
177
  callee_node = node.name
163
178
  if type(callee_node) is MemberAccess and len(callee_node.attrs) == 1:
164
179
  base = self.expr(callee_node.name, env)
165
180
  attr = callee_node.attrs[0].name
166
- return f"_i.call_member_values(({base}), {attr!r}, [{args}])"
181
+ if kw_code is None:
182
+ return (f"_i.call_member_values(({base}), {attr!r}, "
183
+ f"[{pos_code}])")
184
+ return (f"_i.call_member_values(({base}), {attr!r}, "
185
+ f"[{pos_code}], {kw_code})")
167
186
  callee = self.expr(callee_node, env)
168
- return f"_i.call_value(({callee}), [{args}])"
187
+ if kw_code is None:
188
+ return f"_i.call_value(({callee}), [{pos_code}])"
189
+ return f"_i.call_value(({callee}), [{pos_code}], {kw_code})"
169
190
 
170
191
  def expr(self, node, env="env"):
171
192
  kind = type(node)
@@ -221,20 +242,30 @@ class _Emitter(object):
221
242
  return self._call(node, env)
222
243
  if kind is NewExpr:
223
244
  target = self.expr(node.target, env)
224
- args = self._arguments(node.args, env)
225
- return f"_i.new_value({node.label!r}, ({target}), [{args}])"
245
+ pos_code, kw_code = self._split_args(node.args, env)
246
+ if kw_code is None:
247
+ return f"_i.new_value({node.label!r}, ({target}), [{pos_code}])"
248
+ return f"_i.new_value({node.label!r}, ({target}), [{pos_code}], {kw_code})"
226
249
  if kind is MethodCall:
227
- args = self._arguments(node.args, env)
228
- return f"_i._method_call_values({node.name!r}, [{args}])"
250
+ pos_code, kw_code = self._split_args(node.args, env)
251
+ if kw_code is None:
252
+ return f"_i._method_call_values({node.name!r}, [{pos_code}])"
253
+ return f"_i._method_call_values({node.name!r}, [{pos_code}], {kw_code})"
229
254
  if kind is SuperCall:
230
- args = self._arguments(node.args, env)
231
- return f"_i._super_call_values({node.name!r}, [{args}])"
255
+ pos_code, kw_code = self._split_args(node.args, env)
256
+ if kw_code is None:
257
+ return f"_i._super_call_values({node.name!r}, [{pos_code}])"
258
+ return f"_i._super_call_values({node.name!r}, [{pos_code}], {kw_code})"
232
259
  if kind is StaticRef:
233
260
  return f"_i._static_ref_value({node.name!r})"
234
261
  if kind is BoundMethodCall:
262
+ pos_code, kw_code = self._split_args(node.args, env)
235
263
  target = self.expr(node.target, env)
236
- args = self._arguments(node.args, env)
237
- return f"_i._bound_method_call_values(({target}), {node.name!r}, [{args}])"
264
+ if kw_code is None:
265
+ return (f"_i._bound_method_call_values(({target}), {node.name!r}, "
266
+ f"[{pos_code}])")
267
+ return (f"_i._bound_method_call_values(({target}), {node.name!r}, "
268
+ f"[{pos_code}], {kw_code})")
238
269
  if kind is ThisRef:
239
270
  return "_i.current_instance()"
240
271
  fallback = self._closure(node)
@@ -487,6 +518,10 @@ class _Unit(object):
487
518
  self.indent += 1
488
519
  self.line("env.values[0] = values[0]")
489
520
  self.indent -= 1
521
+ self.line("else:")
522
+ self.indent += 1
523
+ self.line("env.values[0] = None")
524
+ self.indent -= 1
490
525
  elif arity == 2:
491
526
  count = self.new_temp()
492
527
  self.line(f"{count} = len(values)")
@@ -495,6 +530,10 @@ class _Unit(object):
495
530
  self.indent += 1
496
531
  self.line(f"env.values[{index}] = values[{index}]")
497
532
  self.indent -= 1
533
+ self.line("else:")
534
+ self.indent += 1
535
+ self.line(f"env.values[{index}] = None")
536
+ self.indent -= 1
498
537
  elif arity == 3:
499
538
  count = self.new_temp()
500
539
  self.line(f"{count} = len(values)")
@@ -503,6 +542,10 @@ class _Unit(object):
503
542
  self.indent += 1
504
543
  self.line(f"env.values[{index}] = values[{index}]")
505
544
  self.indent -= 1
545
+ self.line("else:")
546
+ self.indent += 1
547
+ self.line(f"env.values[{index}] = None")
548
+ self.indent -= 1
506
549
  else:
507
550
  count = self.new_temp()
508
551
  key = self.new_temp()
@@ -512,6 +555,10 @@ class _Unit(object):
512
555
  self.indent += 1
513
556
  self.line(f"env.values[{key}] = values[{key}]")
514
557
  self.indent -= 1
558
+ self.line(f"for {key} in range({count}, {arity}):")
559
+ self.indent += 1
560
+ self.line(f"env.values[{key}] = None")
561
+ self.indent -= 1
515
562
 
516
563
  def _finish(self, name, header):
517
564
  source = header + "\n" + "\n".join(self.lines) + "\n"
@@ -725,15 +772,26 @@ class _Unit(object):
725
772
 
726
773
  loop_scope = ScopeInfo(scope)
727
774
  var_index = loop_scope.declare(node.var, readonly=True)
728
- loop_env = self.new_env(loop_scope, env)
729
775
  item = self.new_temp()
730
776
  body_scope = ScopeInfo(loop_scope)
731
777
  self.compiler._predeclare(node.body, body_scope)
732
- self.line(f"for {item} in {iterable}:")
733
- self.indent += 1
734
- self.line(f"{loop_env}.values[{var_index}] = {item}")
735
- self._emit_loop_body(node.body, body_scope, loop_scope, loop_env)
736
- self.indent -= 1
778
+ captures = _captures_scope(node.body)
779
+ if captures:
780
+ self.line(f"for {item} in {iterable}:")
781
+ self.indent += 1
782
+ # fresh scope per iteration so closures capture this iteration's value
783
+ loop_env = self.new_env(loop_scope, env)
784
+ self.line(f"{loop_env}.values[{var_index}] = {item}")
785
+ self._emit_loop_body(node.body, body_scope, loop_scope, loop_env)
786
+ self.indent -= 1
787
+ else:
788
+ # reuse a single scope: no closure captures this iteration's value
789
+ loop_env = self.new_env(loop_scope, env)
790
+ self.line(f"for {item} in {iterable}:")
791
+ self.indent += 1
792
+ self.line(f"{loop_env}.values[{var_index}] = {item}")
793
+ self._emit_loop_body(node.body, body_scope, loop_scope, loop_env)
794
+ self.indent -= 1
737
795
 
738
796
  def _emit_foreach(self, node, scope, env):
739
797
  mapping = self.new_temp()
@@ -746,17 +804,27 @@ class _Unit(object):
746
804
  loop_scope = ScopeInfo(scope)
747
805
  key_index = loop_scope.declare(node.key, readonly=True)
748
806
  value_index = loop_scope.declare(node.var, readonly=True)
749
- loop_env = self.new_env(loop_scope, env)
750
807
  key = self.new_temp()
751
808
  value = self.new_temp()
752
809
  body_scope = ScopeInfo(loop_scope)
753
810
  self.compiler._predeclare(node.body, body_scope)
754
- self.line(f"for {key}, {value} in {mapping}.items():")
755
- self.indent += 1
756
- self.line(f"{loop_env}.values[{key_index}] = {key}")
757
- self.line(f"{loop_env}.values[{value_index}] = {value}")
758
- self._emit_loop_body(node.body, body_scope, loop_scope, loop_env)
759
- self.indent -= 1
811
+ captures = _captures_scope(node.body)
812
+ if captures:
813
+ self.line(f"for {key}, {value} in {mapping}.items():")
814
+ self.indent += 1
815
+ loop_env = self.new_env(loop_scope, env)
816
+ self.line(f"{loop_env}.values[{key_index}] = {key}")
817
+ self.line(f"{loop_env}.values[{value_index}] = {value}")
818
+ self._emit_loop_body(node.body, body_scope, loop_scope, loop_env)
819
+ self.indent -= 1
820
+ else:
821
+ loop_env = self.new_env(loop_scope, env)
822
+ self.line(f"for {key}, {value} in {mapping}.items():")
823
+ self.indent += 1
824
+ self.line(f"{loop_env}.values[{key_index}] = {key}")
825
+ self.line(f"{loop_env}.values[{value_index}] = {value}")
826
+ self._emit_loop_body(node.body, body_scope, loop_scope, loop_env)
827
+ self.indent -= 1
760
828
 
761
829
  def _emit_loop_body(self, statements, body_scope, parent_scope, parent_env):
762
830
  # The try only exists to catch `break` / `continue`; a body without
@@ -53,6 +53,7 @@ ReturnStmt = namedtuple("ReturnStmt", ["value"])
53
53
  FnDef = namedtuple("FnDef", ["name", "params", "body", "decorators", "implicit_return"],
54
54
  defaults=((), False))
55
55
  FnCall = namedtuple("FnCall", ["name", "args"])
56
+ KwArg = namedtuple("KwArg", ["name", "value"])
56
57
  LambdaFnDef = namedtuple("LambdaFnDef", ["params", "body"])
57
58
  ImportStmt = namedtuple("ImportStmt", ["path", "name"])
58
59
  ErrorDef = namedtuple("ErrorDef", ["name", "params", "body"])