koskript 1.2.0__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.
- koskript/__init__.py +86 -0
- koskript/lang/astgen.py +213 -0
- koskript/lang/emtypes.py +84 -0
- koskript/lang/errors.py +12 -0
- koskript/lang/interpreter.py +332 -0
- koskript-1.2.0.dist-info/METADATA +352 -0
- koskript-1.2.0.dist-info/RECORD +10 -0
- koskript-1.2.0.dist-info/WHEEL +5 -0
- koskript-1.2.0.dist-info/licenses/LICENSE +373 -0
- koskript-1.2.0.dist-info/top_level.txt +1 -0
koskript/__init__.py
ADDED
|
@@ -0,0 +1,86 @@
|
|
|
1
|
+
from lark import Lark
|
|
2
|
+
from .lang.emtypes import KoskriptObject
|
|
3
|
+
from .lang.interpreter import KoskripInterpreter
|
|
4
|
+
from .lang.astgen import KoskriptTransformer
|
|
5
|
+
from .lang.errors import Errors
|
|
6
|
+
import pathlib, os
|
|
7
|
+
|
|
8
|
+
_grammar_path = os.path.join(pathlib.Path(__file__).resolve().parent, "grammar.lark")
|
|
9
|
+
with open(_grammar_path, "r") as _grammar_file:
|
|
10
|
+
grammar = Lark(_grammar_file, parser="lalr", maybe_placeholders=False)
|
|
11
|
+
|
|
12
|
+
|
|
13
|
+
def _wrap(value):
|
|
14
|
+
return value if isinstance(value, KoskriptObject) else KoskriptObject(value=value)
|
|
15
|
+
|
|
16
|
+
|
|
17
|
+
class KoskriptRuntime(object):
|
|
18
|
+
"""Embeddable Koskript runtime.
|
|
19
|
+
|
|
20
|
+
Any Python value or callable exposed to scripts is wrapped automatically,
|
|
21
|
+
so you can pass plain functions without building ``KoskriptObject`` by hand.
|
|
22
|
+
|
|
23
|
+
>>> rt = KoskriptRuntime({"print": print})
|
|
24
|
+
>>> rt.execute("local x = 10\\nx + 26")
|
|
25
|
+
36
|
|
26
|
+
"""
|
|
27
|
+
|
|
28
|
+
def __init__(self, globals=None, _globals_=None):
|
|
29
|
+
if _globals_ is not None:
|
|
30
|
+
globals = {**(globals or {}), **_globals_}
|
|
31
|
+
|
|
32
|
+
self.globals = {}
|
|
33
|
+
self.__interpreter__ = KoskripInterpreter()
|
|
34
|
+
self.__ast__ = KoskriptTransformer()
|
|
35
|
+
|
|
36
|
+
if globals:
|
|
37
|
+
self.register_many(globals)
|
|
38
|
+
|
|
39
|
+
def register(self, name: str, value):
|
|
40
|
+
"""Expose a Python value or callable to scripts.
|
|
41
|
+
|
|
42
|
+
Returns the runtime so calls can be chained.
|
|
43
|
+
"""
|
|
44
|
+
obj = _wrap(value)
|
|
45
|
+
self.globals[name] = obj
|
|
46
|
+
self.__interpreter__.set_global(name, obj)
|
|
47
|
+
return self
|
|
48
|
+
|
|
49
|
+
def register_many(self, mapping: dict):
|
|
50
|
+
for name, value in mapping.items():
|
|
51
|
+
self.register(name, value)
|
|
52
|
+
return self
|
|
53
|
+
|
|
54
|
+
def __setitem__(self, name: str, value):
|
|
55
|
+
self.register(name, value)
|
|
56
|
+
|
|
57
|
+
def __getitem__(self, name: str):
|
|
58
|
+
return self.__interpreter__.get_global(name).value
|
|
59
|
+
|
|
60
|
+
def execute(self, code: str):
|
|
61
|
+
"""Parse and run ``code``.
|
|
62
|
+
|
|
63
|
+
Returns the value of the last evaluated expression, or the value of a
|
|
64
|
+
top-level ``return`` if the script uses one.
|
|
65
|
+
"""
|
|
66
|
+
tree = grammar.parse(code)
|
|
67
|
+
ast = self.__ast__.transform(tree)
|
|
68
|
+
|
|
69
|
+
if not isinstance(ast, list):
|
|
70
|
+
ast = [ast]
|
|
71
|
+
|
|
72
|
+
return self.__interpreter__.run(ast)
|
|
73
|
+
|
|
74
|
+
|
|
75
|
+
def run(code: str, globals: dict = None, **kwargs):
|
|
76
|
+
"""One-shot convenience helper.
|
|
77
|
+
|
|
78
|
+
>>> run("print(1 + 2)", print=print)
|
|
79
|
+
3
|
|
80
|
+
"""
|
|
81
|
+
merged = dict(globals or {})
|
|
82
|
+
merged.update(kwargs)
|
|
83
|
+
return KoskriptRuntime(merged).execute(code)
|
|
84
|
+
|
|
85
|
+
|
|
86
|
+
__all__ = ["KoskriptRuntime", "KoskriptObject", "Errors", "run"]
|
koskript/lang/astgen.py
ADDED
|
@@ -0,0 +1,213 @@
|
|
|
1
|
+
from lark import Transformer
|
|
2
|
+
from .emtypes import *
|
|
3
|
+
|
|
4
|
+
_ESCAPES = {
|
|
5
|
+
"n": "\n",
|
|
6
|
+
"t": "\t",
|
|
7
|
+
"r": "\r",
|
|
8
|
+
"0": "\0",
|
|
9
|
+
"\\": "\\",
|
|
10
|
+
'"': '"',
|
|
11
|
+
"'": "'",
|
|
12
|
+
}
|
|
13
|
+
|
|
14
|
+
|
|
15
|
+
def _unescape(raw: str) -> str:
|
|
16
|
+
out = []
|
|
17
|
+
i = 0
|
|
18
|
+
while i < len(raw):
|
|
19
|
+
ch = raw[i]
|
|
20
|
+
if ch == "\\" and i + 1 < len(raw):
|
|
21
|
+
nxt = raw[i + 1]
|
|
22
|
+
out.append(_ESCAPES.get(nxt, nxt))
|
|
23
|
+
i += 2
|
|
24
|
+
else:
|
|
25
|
+
out.append(ch)
|
|
26
|
+
i += 1
|
|
27
|
+
return "".join(out)
|
|
28
|
+
|
|
29
|
+
|
|
30
|
+
class KoskriptTransformer(Transformer):
|
|
31
|
+
def start(self, tree): return (tree)
|
|
32
|
+
|
|
33
|
+
def NUMBER(self, token):
|
|
34
|
+
text = str(token)
|
|
35
|
+
if "." in text:
|
|
36
|
+
return FloatLit(value=float(text))
|
|
37
|
+
return IntLit(value=int(text))
|
|
38
|
+
|
|
39
|
+
def NAME(self, token): return NameRef(name=str(token))
|
|
40
|
+
def STRING(self, token): return StrLit(value=_unescape(str(token)[1:-1]))
|
|
41
|
+
def bool_true(self, tree): return BoolLit(value=True)
|
|
42
|
+
def bool_false(self, tree): return BoolLit(value=False)
|
|
43
|
+
def null_lit(self, tree): return NullLit(value=None)
|
|
44
|
+
|
|
45
|
+
def lambda_fn(self, tree):
|
|
46
|
+
return LambdaFnDef(params=[], body=tree[0])
|
|
47
|
+
|
|
48
|
+
def lambda_fn_args(self, tree):
|
|
49
|
+
params = [obj.name for obj in tree[:-1]]
|
|
50
|
+
body = tree[-1]
|
|
51
|
+
return LambdaFnDef(params=params, body=body)
|
|
52
|
+
|
|
53
|
+
def array(self, tree): return ArrayLit(value=tree)
|
|
54
|
+
def map(self, tree): return MapLit(value=tree)
|
|
55
|
+
def map_obj(self, tree): return MapValue(key=tree[0], value=tree[1])
|
|
56
|
+
|
|
57
|
+
def arg_list(self, tree): return tree
|
|
58
|
+
|
|
59
|
+
def add_stmt(self, tree):
|
|
60
|
+
left, right = tree
|
|
61
|
+
return AddStmt(left=left, right=right)
|
|
62
|
+
|
|
63
|
+
def sub_stmt(self, tree):
|
|
64
|
+
left, right = tree
|
|
65
|
+
return SubStmt(left=left, right=right)
|
|
66
|
+
|
|
67
|
+
def mul_stmt(self, tree):
|
|
68
|
+
left, right = tree
|
|
69
|
+
return MulStmt(left=left, right=right)
|
|
70
|
+
|
|
71
|
+
def div_stmt(self, tree):
|
|
72
|
+
left, right = tree
|
|
73
|
+
return DivStmt(left=left, right=right)
|
|
74
|
+
|
|
75
|
+
def mod_stmt(self, tree):
|
|
76
|
+
left, right = tree
|
|
77
|
+
return ModStmt(left=left, right=right)
|
|
78
|
+
|
|
79
|
+
def neg_stmt(self, tree):
|
|
80
|
+
return NegStmt(value=tree[0])
|
|
81
|
+
|
|
82
|
+
def param_list(self, tree):
|
|
83
|
+
return [obj.name for obj in tree]
|
|
84
|
+
|
|
85
|
+
def block(self, tree):
|
|
86
|
+
return tree
|
|
87
|
+
|
|
88
|
+
# conditions and comparisons
|
|
89
|
+
def and_cond(self, tree):
|
|
90
|
+
left, right = tree
|
|
91
|
+
return AndCond(left=left, right=right)
|
|
92
|
+
|
|
93
|
+
def not_cond(self, tree):
|
|
94
|
+
return NotCond(tree)
|
|
95
|
+
|
|
96
|
+
def or_cond(self, tree):
|
|
97
|
+
left, right = tree
|
|
98
|
+
return OrCond(left=left, right=right)
|
|
99
|
+
|
|
100
|
+
|
|
101
|
+
def equ(self, tree):
|
|
102
|
+
left, right = tree
|
|
103
|
+
return EquComp(left=left, right=right)
|
|
104
|
+
def nequ(self, tree):
|
|
105
|
+
left, right = tree
|
|
106
|
+
return NequComp(left=left, right=right)
|
|
107
|
+
def gte(self, tree):
|
|
108
|
+
left, right = tree
|
|
109
|
+
return GteComp(left=left, right=right)
|
|
110
|
+
def lte(self, tree):
|
|
111
|
+
left, right = tree
|
|
112
|
+
return LteComp(left=left, right=right)
|
|
113
|
+
def lt(self, tree):
|
|
114
|
+
left, right = tree
|
|
115
|
+
return LtComp(left=left, right=right)
|
|
116
|
+
def gt(self, tree):
|
|
117
|
+
left, right = tree
|
|
118
|
+
return GtComp(left=left, right=right)
|
|
119
|
+
|
|
120
|
+
def member_access(self, tree):
|
|
121
|
+
value, attr = tree
|
|
122
|
+
return MemberAccess(name=value, attrs=[attr])
|
|
123
|
+
|
|
124
|
+
def index_access(self, tree):
|
|
125
|
+
value, index = tree
|
|
126
|
+
return IndexAccess(value=value, index=index)
|
|
127
|
+
|
|
128
|
+
def if_stmt(self, tree):
|
|
129
|
+
condition = tree[0]
|
|
130
|
+
block = tree[1]
|
|
131
|
+
anexed_ifs = tree[2:]
|
|
132
|
+
|
|
133
|
+
return IfStmt(
|
|
134
|
+
condition=condition,
|
|
135
|
+
body=block,
|
|
136
|
+
if_tree=anexed_ifs
|
|
137
|
+
)
|
|
138
|
+
|
|
139
|
+
def elseif_stmt(self, tree):
|
|
140
|
+
condition, block = tree
|
|
141
|
+
return ElseIfStmt(
|
|
142
|
+
condition=condition,
|
|
143
|
+
body=block
|
|
144
|
+
)
|
|
145
|
+
|
|
146
|
+
def elsestmt(self, tree):
|
|
147
|
+
return ElseStmt(
|
|
148
|
+
body=tree[0]
|
|
149
|
+
)
|
|
150
|
+
|
|
151
|
+
# declarations
|
|
152
|
+
def local_decl(self, tree):
|
|
153
|
+
name, expr = tree
|
|
154
|
+
|
|
155
|
+
return LocalDecl(
|
|
156
|
+
name=name.name,
|
|
157
|
+
value=expr
|
|
158
|
+
)
|
|
159
|
+
|
|
160
|
+
def decl(self, tree):
|
|
161
|
+
name, expr = tree
|
|
162
|
+
return DeclStmt(
|
|
163
|
+
name=name.name, value=expr
|
|
164
|
+
)
|
|
165
|
+
|
|
166
|
+
def fn_def(self, tree):
|
|
167
|
+
name, params, block = tree
|
|
168
|
+
return FnDef(
|
|
169
|
+
name=name.name,
|
|
170
|
+
params=params,
|
|
171
|
+
body=block
|
|
172
|
+
)
|
|
173
|
+
|
|
174
|
+
def fn_def_nargs(self, tree):
|
|
175
|
+
name, block = tree
|
|
176
|
+
return FnDef(
|
|
177
|
+
name=name.name,
|
|
178
|
+
params=[],
|
|
179
|
+
body=block
|
|
180
|
+
)
|
|
181
|
+
|
|
182
|
+
# flow
|
|
183
|
+
def return_value(self, tree):
|
|
184
|
+
if len(tree) >= 1:
|
|
185
|
+
return ReturnStmt(value=tree[0])
|
|
186
|
+
return ReturnStmt(value=None)
|
|
187
|
+
|
|
188
|
+
def return_void(self, tree):
|
|
189
|
+
return ReturnStmt(value=None)
|
|
190
|
+
|
|
191
|
+
def while_stmt(self, tree):
|
|
192
|
+
condition, block = tree
|
|
193
|
+
return WhileStmt(condition=condition, body=block)
|
|
194
|
+
|
|
195
|
+
def break_stmt(self, tree):
|
|
196
|
+
return BreakStmt()
|
|
197
|
+
|
|
198
|
+
def continue_stmt(self, tree):
|
|
199
|
+
return ContinueStmt()
|
|
200
|
+
|
|
201
|
+
def for_stmt(self, tree):
|
|
202
|
+
varname, iterable, block = tree
|
|
203
|
+
return ForStmt(var=varname.name, iterable=iterable, body=block)
|
|
204
|
+
|
|
205
|
+
def foritem_stmt(self, tree):
|
|
206
|
+
key, value, iterable, block = tree
|
|
207
|
+
return ForItemStmt(key=key.name, var=value.name, iterable=iterable, body=block)
|
|
208
|
+
|
|
209
|
+
# Otros
|
|
210
|
+
def fn_call(self, tree):
|
|
211
|
+
name = tree[0]
|
|
212
|
+
args = tree[1] if len(tree) > 1 else []
|
|
213
|
+
return FnCall(name=name, args=args)
|
koskript/lang/emtypes.py
ADDED
|
@@ -0,0 +1,84 @@
|
|
|
1
|
+
from collections import namedtuple
|
|
2
|
+
from .errors import Errors
|
|
3
|
+
from typing import Any
|
|
4
|
+
|
|
5
|
+
# Literals and references
|
|
6
|
+
IntLit = namedtuple("IntLit", ["value"])
|
|
7
|
+
FloatLit = namedtuple("FloatLit", ["value"])
|
|
8
|
+
StrLit = namedtuple("StrLit", ["value"])
|
|
9
|
+
BoolLit = namedtuple("BoolLit", ["value"])
|
|
10
|
+
NullLit = namedtuple("NullLit", ["value"])
|
|
11
|
+
ArrayLit = namedtuple("ArrayLit", ["value"])
|
|
12
|
+
MapLit = namedtuple("MapLit", ["value"])
|
|
13
|
+
MapValue = namedtuple("MapValue", ["key", "value"])
|
|
14
|
+
NameRef = namedtuple("NameRef", ["name"])
|
|
15
|
+
Function = namedtuple("Function", ["params", "body"])
|
|
16
|
+
MemberAccess = namedtuple("MemberAccess", ["name", "attrs"])
|
|
17
|
+
IndexAccess = namedtuple("IndexAccess", ["value", "index"])
|
|
18
|
+
|
|
19
|
+
# Control-flow signal raised by `return` so it can unwind out of
|
|
20
|
+
# if/while/for blocks and be caught by the enclosing function call.
|
|
21
|
+
class ReturnSignal(Exception):
|
|
22
|
+
def __init__(self, value=None):
|
|
23
|
+
super().__init__("return")
|
|
24
|
+
self.value = value
|
|
25
|
+
|
|
26
|
+
# Signals raised by `break` / `continue`, caught by the nearest loop.
|
|
27
|
+
class BreakSignal(Exception):
|
|
28
|
+
def __init__(self):
|
|
29
|
+
super().__init__("break")
|
|
30
|
+
|
|
31
|
+
class ContinueSignal(Exception):
|
|
32
|
+
def __init__(self):
|
|
33
|
+
super().__init__("continue")
|
|
34
|
+
|
|
35
|
+
# Node Objects
|
|
36
|
+
LocalDecl = namedtuple("LocalDecl", ["name", "value"])
|
|
37
|
+
DeclStmt = namedtuple("DeclStmt", ["name", "value"])
|
|
38
|
+
ReturnStmt = namedtuple("ReturnStmt", ["value"])
|
|
39
|
+
FnDef = namedtuple("FnDef", ["name", "params", "body"])
|
|
40
|
+
FnCall = namedtuple("FnCall", ["name", "args"])
|
|
41
|
+
LambdaFnDef = namedtuple("LambdaFnDef", ["params", "body"])
|
|
42
|
+
WhileStmt = namedtuple("WhileStmt", ["condition", "body"])
|
|
43
|
+
ForStmt = namedtuple("ForStmt", ["var", "iterable", "body"])
|
|
44
|
+
ForItemStmt = namedtuple("ForItemStmt", ["key", "var", "iterable", "body"])
|
|
45
|
+
BreakStmt = namedtuple("BreakStmt", [])
|
|
46
|
+
ContinueStmt = namedtuple("ContinueStmt", [])
|
|
47
|
+
AddStmt = namedtuple("AddStmt", ["left", "right"])
|
|
48
|
+
SubStmt = namedtuple("SubStmt", ["left", "right"])
|
|
49
|
+
MulStmt = namedtuple("MulStmt", ["left", "right"])
|
|
50
|
+
DivStmt = namedtuple("DivStmt", ["left", "right"])
|
|
51
|
+
ModStmt = namedtuple("ModStmt", ["left", "right"])
|
|
52
|
+
NegStmt = namedtuple("NegStmt", ["value"])
|
|
53
|
+
IfStmt = namedtuple("IfStmt", ["condition", "body", "if_tree"])
|
|
54
|
+
ElseIfStmt = namedtuple("ElseIfStmt", ["condition", "body"])
|
|
55
|
+
ElseStmt = namedtuple("ElseStmt", ["body"])
|
|
56
|
+
|
|
57
|
+
# Conditions
|
|
58
|
+
AndCond = namedtuple("AndCond", ["left", "right"])
|
|
59
|
+
OrCond = namedtuple("OrCond", ["left", "right"])
|
|
60
|
+
NotCond = namedtuple("NotCond", ["comparison"])
|
|
61
|
+
|
|
62
|
+
# Comparisons
|
|
63
|
+
EquComp = namedtuple("EquComp", ["left", "right"])
|
|
64
|
+
NequComp = namedtuple("NequComp", ["left", "right"])
|
|
65
|
+
LteComp = namedtuple("LteComp", ["left", "right"])
|
|
66
|
+
GteComp = namedtuple("GteComp", ["left", "right"])
|
|
67
|
+
LtComp = namedtuple("LtComp", ["left", "right"])
|
|
68
|
+
GtComp = namedtuple("GtComp", ["left", "right"])
|
|
69
|
+
|
|
70
|
+
|
|
71
|
+
class KoskriptObject(object):
|
|
72
|
+
def __init__(self, value: Any, read_only: bool = False):
|
|
73
|
+
self.value = value
|
|
74
|
+
self.read_only = read_only
|
|
75
|
+
|
|
76
|
+
def set_value(self, value: Any):
|
|
77
|
+
if self.read_only:
|
|
78
|
+
raise Errors.ProtectedObject("cannot modify a constant value.")
|
|
79
|
+
|
|
80
|
+
self.value = value
|
|
81
|
+
|
|
82
|
+
def __repr__(self):
|
|
83
|
+
return f"KoskriptObject(value={self.value}, read_only={self.read_only})"
|
|
84
|
+
|
koskript/lang/errors.py
ADDED
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
class Errors:
|
|
2
|
+
class MismatchType(Exception):
|
|
3
|
+
def __init__(self, *args):
|
|
4
|
+
super().__init__(*args)
|
|
5
|
+
|
|
6
|
+
class ProtectedObject(Exception):
|
|
7
|
+
def __init__(self, *args):
|
|
8
|
+
super().__init__(*args)
|
|
9
|
+
|
|
10
|
+
class RuntimeError(Exception):
|
|
11
|
+
def __init__(self, *args):
|
|
12
|
+
super().__init__(*args)
|