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.
- {koskript-1.4.2 → koskript-1.5.0}/PKG-INFO +10 -12
- {koskript-1.4.2 → koskript-1.5.0}/README.md +7 -7
- {koskript-1.4.2 → koskript-1.5.0}/pyproject.toml +6 -11
- {koskript-1.4.2 → koskript-1.5.0}/src/koskript/__init__.py +26 -109
- koskript-1.5.0/src/koskript/headers.py +57 -0
- {koskript-1.4.2 → koskript-1.5.0}/src/koskript/lang/compiler.py +94 -26
- {koskript-1.4.2 → koskript-1.5.0}/src/koskript/lang/emtypes.py +1 -0
- {koskript-1.4.2 → koskript-1.5.0}/src/koskript/lang/interpreter.py +55 -30
- {koskript-1.4.2 → koskript-1.5.0}/src/koskript/lang/lexer.py +30 -6
- {koskript-1.4.2 → koskript-1.5.0}/src/koskript/lang/parser.py +70 -20
- koskript-1.5.0/src/koskript/lsp.py +205 -0
- {koskript-1.4.2 → koskript-1.5.0}/src/koskript/stdlib/core.py +160 -159
- {koskript-1.4.2 → koskript-1.5.0}/src/koskript.egg-info/PKG-INFO +10 -12
- {koskript-1.4.2 → koskript-1.5.0}/src/koskript.egg-info/SOURCES.txt +2 -4
- {koskript-1.4.2 → koskript-1.5.0}/tests/test_parser.py +133 -111
- koskript-1.4.2/src/koskript/grammar.lark +0 -225
- koskript-1.4.2/src/koskript/lang/astgen.py +0 -479
- koskript-1.4.2/src/koskript.egg-info/requires.txt +0 -1
- koskript-1.4.2/tests/test_grammar.py +0 -700
- {koskript-1.4.2 → koskript-1.5.0}/LICENSE +0 -0
- {koskript-1.4.2 → koskript-1.5.0}/setup.cfg +0 -0
- {koskript-1.4.2 → koskript-1.5.0}/src/koskript/lang/errors.py +0 -0
- {koskript-1.4.2 → koskript-1.5.0}/src/koskript/stdlib/__init__.py +0 -0
- {koskript-1.4.2 → koskript-1.5.0}/src/koskript/stdlib/arrays.py +0 -0
- {koskript-1.4.2 → koskript-1.5.0}/src/koskript/stdlib/byteslib.py +0 -0
- {koskript-1.4.2 → koskript-1.5.0}/src/koskript/stdlib/helpers.py +0 -0
- {koskript-1.4.2 → koskript-1.5.0}/src/koskript/stdlib/jsonlib.py +0 -0
- {koskript-1.4.2 → koskript-1.5.0}/src/koskript/stdlib/maps.py +0 -0
- {koskript-1.4.2 → koskript-1.5.0}/src/koskript/stdlib/mathlib.py +0 -0
- {koskript-1.4.2 → koskript-1.5.0}/src/koskript/stdlib/strings.py +0 -0
- {koskript-1.4.2 → koskript-1.5.0}/src/koskript.egg-info/dependency_links.txt +0 -0
- {koskript-1.4.2 → koskript-1.5.0}/src/koskript.egg-info/top_level.txt +0 -0
- {koskript-1.4.2 → koskript-1.5.0}/tests/test_language.py +0 -0
- {koskript-1.4.2 → koskript-1.5.0}/tests/test_modules.py +0 -0
- {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
|
-
Summary: A programming language fully
|
|
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
|
|
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
|

|
|
@@ -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.
|
|
8
|
-
description = "A programming language fully
|
|
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
|
-
|
|
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 = ["*.
|
|
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
|
|
90
|
-
needs
|
|
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("
|
|
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
|
-
|
|
126
|
-
|
|
127
|
-
""
|
|
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
|
-
|
|
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", "
|
|
25
|
+
"if", "elseif", "else", "while", "for", "foreach", "fun", "wrapper",
|
|
26
26
|
"return",
|
|
27
|
-
"
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
225
|
-
|
|
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
|
-
|
|
228
|
-
|
|
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
|
-
|
|
231
|
-
|
|
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
|
-
|
|
237
|
-
|
|
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
|
-
|
|
733
|
-
|
|
734
|
-
|
|
735
|
-
|
|
736
|
-
|
|
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
|
-
|
|
755
|
-
|
|
756
|
-
|
|
757
|
-
|
|
758
|
-
|
|
759
|
-
|
|
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"])
|