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
|
@@ -0,0 +1,332 @@
|
|
|
1
|
+
from .emtypes import *
|
|
2
|
+
|
|
3
|
+
class KoskripInterpreter(object):
|
|
4
|
+
def __init__(self):
|
|
5
|
+
self.globals = {"_": {}}
|
|
6
|
+
self.scopes = []
|
|
7
|
+
self._scope_counter = 0
|
|
8
|
+
self._handlers = {
|
|
9
|
+
LocalDecl: self._local_decl,
|
|
10
|
+
DeclStmt: self._decl,
|
|
11
|
+
FnDef: self._fn_def,
|
|
12
|
+
FnCall: self._fn_call,
|
|
13
|
+
ReturnStmt: self._return_stmt,
|
|
14
|
+
WhileStmt: self._while_stmt,
|
|
15
|
+
ForStmt: self._for_stmt,
|
|
16
|
+
ForItemStmt: self._foritem_stmt,
|
|
17
|
+
BreakStmt: self._break_stmt,
|
|
18
|
+
ContinueStmt: self._continue_stmt,
|
|
19
|
+
IfStmt: self._if_stmt,
|
|
20
|
+
ElseIfStmt: self._else_if_stmt,
|
|
21
|
+
ElseStmt: self._else_stmt
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
def execute(self, ast: list):
|
|
25
|
+
result = None
|
|
26
|
+
for node in ast:
|
|
27
|
+
result = self.visit(node)
|
|
28
|
+
return result
|
|
29
|
+
|
|
30
|
+
def run(self, ast: list):
|
|
31
|
+
# Entry point: a top-level `return` stops execution and yields its value.
|
|
32
|
+
try:
|
|
33
|
+
return self.execute(ast)
|
|
34
|
+
except ReturnSignal as signal:
|
|
35
|
+
return signal.value
|
|
36
|
+
except (BreakSignal, ContinueSignal) as signal:
|
|
37
|
+
raise Errors.RuntimeError(f"'{signal}' outside of a loop")
|
|
38
|
+
|
|
39
|
+
def execute_block(self, body: list):
|
|
40
|
+
self._push_scope("block")
|
|
41
|
+
try:
|
|
42
|
+
self.execute(body)
|
|
43
|
+
finally:
|
|
44
|
+
self.scopes.pop()
|
|
45
|
+
|
|
46
|
+
def _push_scope(self, prefix: str) -> str:
|
|
47
|
+
self._scope_counter += 1
|
|
48
|
+
scope = f"{prefix}_{self._scope_counter}"
|
|
49
|
+
self.scopes.append(scope)
|
|
50
|
+
return scope
|
|
51
|
+
|
|
52
|
+
def get_global(self, name: str) -> KoskriptObject:
|
|
53
|
+
for scope in reversed(self.scopes):
|
|
54
|
+
if scope in self.globals and name in self.globals[scope]:
|
|
55
|
+
return self.globals[scope][name]
|
|
56
|
+
|
|
57
|
+
if name in self.globals["_"]:
|
|
58
|
+
return self.globals["_"][name]
|
|
59
|
+
|
|
60
|
+
raise NameError(f"'{name}' is not defined")
|
|
61
|
+
|
|
62
|
+
def set_global(self, name: str, value: KoskriptObject) -> None:
|
|
63
|
+
if self.scopes:
|
|
64
|
+
scope = self.scopes[-1]
|
|
65
|
+
if scope not in self.globals:
|
|
66
|
+
self.globals[scope] = {}
|
|
67
|
+
self.globals[scope][name] = value
|
|
68
|
+
return
|
|
69
|
+
|
|
70
|
+
self.globals["_"][name] = value
|
|
71
|
+
|
|
72
|
+
def visit(self, node):
|
|
73
|
+
handler = self._handlers.get(type(node))
|
|
74
|
+
if handler is None:
|
|
75
|
+
# Statements can also be bare expressions (e.g. `f(x)`, `1 + 2`).
|
|
76
|
+
return self.expr_eval(node)
|
|
77
|
+
return handler(node)
|
|
78
|
+
|
|
79
|
+
# EVALUATORS ####################################################################
|
|
80
|
+
|
|
81
|
+
def expr_eval(self, expr):
|
|
82
|
+
match expr:
|
|
83
|
+
case IntLit(value): return value
|
|
84
|
+
case FloatLit(value): return value
|
|
85
|
+
case StrLit(value): return value
|
|
86
|
+
case BoolLit(value): return value
|
|
87
|
+
case NullLit(value): return value
|
|
88
|
+
case NameRef(name): return self.get_global(name).value
|
|
89
|
+
case AddStmt(l, r): return self.expr_eval(l) + self.expr_eval(r)
|
|
90
|
+
case SubStmt(l, r): return self.expr_eval(l) - self.expr_eval(r)
|
|
91
|
+
case MulStmt(l, r): return self.expr_eval(l) * self.expr_eval(r)
|
|
92
|
+
case DivStmt(l, r): return self.expr_eval(l) / self.expr_eval(r)
|
|
93
|
+
case ModStmt(l, r): return self.expr_eval(l) % self.expr_eval(r)
|
|
94
|
+
case NegStmt(v): return -self.expr_eval(v)
|
|
95
|
+
case ArrayLit(array): return [self.expr_eval(i) for i in array]
|
|
96
|
+
|
|
97
|
+
case MapValue(key, value): return key, value
|
|
98
|
+
case MapLit(values):
|
|
99
|
+
res = {}
|
|
100
|
+
for val in values:
|
|
101
|
+
res[self.expr_eval(val.key)] = self.expr_eval(val.value)
|
|
102
|
+
return res
|
|
103
|
+
|
|
104
|
+
case MemberAccess(name, attrs):
|
|
105
|
+
value = self.expr_eval(name)
|
|
106
|
+
|
|
107
|
+
if not isinstance(value, dict):
|
|
108
|
+
raise Errors.MismatchType(f"member access only supported on map, got {value}")
|
|
109
|
+
|
|
110
|
+
for attr in attrs:
|
|
111
|
+
try:
|
|
112
|
+
value = value[attr.name]
|
|
113
|
+
except:
|
|
114
|
+
raise Errors.RuntimeError(f"no member with the value {attr} is defined on {value}")
|
|
115
|
+
|
|
116
|
+
return value
|
|
117
|
+
|
|
118
|
+
case IndexAccess(value, index):
|
|
119
|
+
container = self.expr_eval(value)
|
|
120
|
+
key = self.expr_eval(index)
|
|
121
|
+
|
|
122
|
+
try:
|
|
123
|
+
return container[key]
|
|
124
|
+
except (KeyError, IndexError, TypeError):
|
|
125
|
+
raise Errors.RuntimeError(
|
|
126
|
+
f"cannot index {type(container).__name__} with {key!r}")
|
|
127
|
+
|
|
128
|
+
case FnCall(n, arg): return self.fn_eval(n, arg)
|
|
129
|
+
case LambdaFnDef(p, body): return Function(params=p, body=body)
|
|
130
|
+
case EquComp() | NequComp() | LteComp() | GteComp() | GtComp() | LtComp() \
|
|
131
|
+
| AndCond() | OrCond() | NotCond():
|
|
132
|
+
return self.cond_eval(expr)
|
|
133
|
+
case _: raise RuntimeError(f"Unknown expr: {type(expr).__name__}")
|
|
134
|
+
|
|
135
|
+
def fn_eval(self, callee, args):
|
|
136
|
+
if isinstance(callee, NameRef):
|
|
137
|
+
func: KoskriptObject = self.get_global(callee.name)
|
|
138
|
+
else:
|
|
139
|
+
func = self.expr_eval(callee)
|
|
140
|
+
if not isinstance(func, KoskriptObject):
|
|
141
|
+
func = KoskriptObject(value=func)
|
|
142
|
+
|
|
143
|
+
if not func:
|
|
144
|
+
raise NameError(f"no define with the name {callee} exists.")
|
|
145
|
+
|
|
146
|
+
if callable(func.value):
|
|
147
|
+
return func.value(*[self.expr_eval(arg) for arg in args])
|
|
148
|
+
|
|
149
|
+
if type(func.value) != Function:
|
|
150
|
+
raise ValueError(f"{callee} is not callable.")
|
|
151
|
+
|
|
152
|
+
func_params = func.value.params
|
|
153
|
+
func_body = func.value.body
|
|
154
|
+
if type(func_body) != list: func_body = [func_body]
|
|
155
|
+
|
|
156
|
+
self._push_scope(f"func_{callee}")
|
|
157
|
+
try:
|
|
158
|
+
for index_param, param in enumerate(func_params):
|
|
159
|
+
try:
|
|
160
|
+
self.set_global(
|
|
161
|
+
name=param,
|
|
162
|
+
value=KoskriptObject(
|
|
163
|
+
self.expr_eval(args[index_param]),
|
|
164
|
+
read_only=True)
|
|
165
|
+
)
|
|
166
|
+
except IndexError:
|
|
167
|
+
break
|
|
168
|
+
|
|
169
|
+
self.execute(func_body)
|
|
170
|
+
except (BreakSignal, ContinueSignal) as signal:
|
|
171
|
+
raise Errors.RuntimeError(f"'{signal}' outside of a loop")
|
|
172
|
+
except ReturnSignal as signal:
|
|
173
|
+
return signal.value
|
|
174
|
+
finally:
|
|
175
|
+
self.scopes.pop()
|
|
176
|
+
return None
|
|
177
|
+
|
|
178
|
+
|
|
179
|
+
def cond_eval(self, condition):
|
|
180
|
+
match condition:
|
|
181
|
+
case EquComp(l, r): return self.expr_eval(l) == self.expr_eval(r)
|
|
182
|
+
case NequComp(l, r): return self.expr_eval(l) != self.expr_eval(r)
|
|
183
|
+
case LteComp(l, r): return self.expr_eval(l) <= self.expr_eval(r)
|
|
184
|
+
case GteComp(l, r): return self.expr_eval(l) >= self.expr_eval(r)
|
|
185
|
+
case GtComp(l, r): return self.expr_eval(l) > self.expr_eval(r)
|
|
186
|
+
case LtComp(l, r): return self.expr_eval(l) < self.expr_eval(r)
|
|
187
|
+
|
|
188
|
+
case AndCond(l, r): return self.cond_eval(l) and self.cond_eval(r)
|
|
189
|
+
case OrCond(l, r): return self.cond_eval(l) or self.cond_eval(r)
|
|
190
|
+
|
|
191
|
+
case NotCond(com): return not self.cond_eval(com[0])
|
|
192
|
+
|
|
193
|
+
case _: return bool(self.expr_eval(condition))
|
|
194
|
+
|
|
195
|
+
|
|
196
|
+
# HANDLERS ######################################################################
|
|
197
|
+
|
|
198
|
+
def _local_decl(self, node: LocalDecl):
|
|
199
|
+
value = self.expr_eval(node.value)
|
|
200
|
+
name = node.name
|
|
201
|
+
|
|
202
|
+
self.set_global(
|
|
203
|
+
name,
|
|
204
|
+
KoskriptObject(
|
|
205
|
+
value=value
|
|
206
|
+
)
|
|
207
|
+
)
|
|
208
|
+
|
|
209
|
+
|
|
210
|
+
def _decl(self, node: DeclStmt):
|
|
211
|
+
variable = self.get_global(node.name)
|
|
212
|
+
|
|
213
|
+
if not variable:
|
|
214
|
+
raise NameError(f"{node.name} is not declared.")
|
|
215
|
+
|
|
216
|
+
variable.set_value(self.expr_eval(node.value))
|
|
217
|
+
|
|
218
|
+
def _fn_def(self, node: FnDef):
|
|
219
|
+
self.set_global(
|
|
220
|
+
name=node.name,
|
|
221
|
+
value=KoskriptObject(
|
|
222
|
+
value=Function(
|
|
223
|
+
params=node.params,
|
|
224
|
+
body=node.body
|
|
225
|
+
))
|
|
226
|
+
)
|
|
227
|
+
|
|
228
|
+
def _fn_call(self, node: FnCall):
|
|
229
|
+
#print(node)
|
|
230
|
+
return self.fn_eval(node.name, args=node.args)
|
|
231
|
+
|
|
232
|
+
def _return_stmt(self, node: ReturnStmt):
|
|
233
|
+
value = self.expr_eval(node.value) if node.value is not None else None
|
|
234
|
+
raise ReturnSignal(value)
|
|
235
|
+
|
|
236
|
+
def _while_stmt(self, node: WhileStmt):
|
|
237
|
+
while self.cond_eval(node.condition):
|
|
238
|
+
try:
|
|
239
|
+
self.execute_block(node.body)
|
|
240
|
+
except ContinueSignal:
|
|
241
|
+
continue
|
|
242
|
+
except BreakSignal:
|
|
243
|
+
break
|
|
244
|
+
|
|
245
|
+
def _for_stmt(self, node: ForStmt):
|
|
246
|
+
array_value = self.expr_eval(node.iterable)
|
|
247
|
+
|
|
248
|
+
if type(array_value) != list and type(array_value) != dict:
|
|
249
|
+
raise NameError(f"for statement only supports maps or arrays.")
|
|
250
|
+
|
|
251
|
+
self._push_scope(f"for")
|
|
252
|
+
try:
|
|
253
|
+
var = KoskriptObject(None)
|
|
254
|
+
var.read_only = True
|
|
255
|
+
self.set_global(node.var, var)
|
|
256
|
+
|
|
257
|
+
variable = self.get_global(node.var)
|
|
258
|
+
for value in array_value:
|
|
259
|
+
variable.value = value
|
|
260
|
+
try:
|
|
261
|
+
self.execute_block(node.body)
|
|
262
|
+
except ContinueSignal:
|
|
263
|
+
continue
|
|
264
|
+
except BreakSignal:
|
|
265
|
+
break
|
|
266
|
+
finally:
|
|
267
|
+
self.scopes.pop()
|
|
268
|
+
|
|
269
|
+
def _foritem_stmt(self, node: ForItemStmt):
|
|
270
|
+
map_value = self.expr_eval(node.iterable)
|
|
271
|
+
|
|
272
|
+
if type(map_value) != dict:
|
|
273
|
+
raise ValueError(f"foreach statement only supports maps.")
|
|
274
|
+
|
|
275
|
+
self._push_scope(f"foreach")
|
|
276
|
+
try:
|
|
277
|
+
keyvalue = KoskriptObject(None)
|
|
278
|
+
varvalue = KoskriptObject(None)
|
|
279
|
+
|
|
280
|
+
keyvalue.read_only = True
|
|
281
|
+
varvalue.read_only = True
|
|
282
|
+
|
|
283
|
+
self.set_global(node.key, keyvalue)
|
|
284
|
+
self.set_global(node.var, varvalue)
|
|
285
|
+
|
|
286
|
+
kval = self.get_global(node.key)
|
|
287
|
+
vval = self.get_global(node.var)
|
|
288
|
+
for key, value in map_value.items():
|
|
289
|
+
kval.value = key
|
|
290
|
+
vval.value = value
|
|
291
|
+
try:
|
|
292
|
+
self.execute_block(node.body)
|
|
293
|
+
except ContinueSignal:
|
|
294
|
+
continue
|
|
295
|
+
except BreakSignal:
|
|
296
|
+
break
|
|
297
|
+
finally:
|
|
298
|
+
self.scopes.pop()
|
|
299
|
+
|
|
300
|
+
def _break_stmt(self, node: BreakStmt):
|
|
301
|
+
raise BreakSignal()
|
|
302
|
+
|
|
303
|
+
def _continue_stmt(self, node: ContinueStmt):
|
|
304
|
+
raise ContinueSignal()
|
|
305
|
+
|
|
306
|
+
def _if_stmt(self, node: IfStmt):
|
|
307
|
+
condition = self.cond_eval(node.condition)
|
|
308
|
+
|
|
309
|
+
if condition:
|
|
310
|
+
self.execute_block(node.body)
|
|
311
|
+
return
|
|
312
|
+
|
|
313
|
+
for obj in node.if_tree:
|
|
314
|
+
if type(obj) == ElseIfStmt:
|
|
315
|
+
res = self._else_if_stmt(obj)
|
|
316
|
+
if res:
|
|
317
|
+
break
|
|
318
|
+
else:
|
|
319
|
+
self._else_stmt(obj)
|
|
320
|
+
break
|
|
321
|
+
|
|
322
|
+
def _else_if_stmt(self, node: ElseIfStmt):
|
|
323
|
+
condition = self.cond_eval(node.condition)
|
|
324
|
+
|
|
325
|
+
if not condition:
|
|
326
|
+
return False
|
|
327
|
+
|
|
328
|
+
self.execute_block(node.body)
|
|
329
|
+
return True
|
|
330
|
+
|
|
331
|
+
def _else_stmt(self, node: ElseStmt):
|
|
332
|
+
self.execute_block(node.body)
|
|
@@ -0,0 +1,352 @@
|
|
|
1
|
+
Metadata-Version: 2.4
|
|
2
|
+
Name: koskript
|
|
3
|
+
Version: 1.2.0
|
|
4
|
+
Summary: A simple embedded scripting language written in Python
|
|
5
|
+
Requires-Python: >=3.10
|
|
6
|
+
Description-Content-Type: text/markdown
|
|
7
|
+
License-File: LICENSE
|
|
8
|
+
Requires-Dist: lark>=1.3.1
|
|
9
|
+
Dynamic: license-file
|
|
10
|
+
|
|
11
|
+

|
|
12
|
+
|
|
13
|
+
# Koskript
|
|
14
|
+
|
|
15
|
+
Koskript is a simple, embeddable, and lightweight scripting language designed to be used as a DSL inside Python applications. It features dynamic typing, lexical scoping, and native Python interop — letting you expose any Python function or object directly to your scripts.
|
|
16
|
+
|
|
17
|
+
> **NOTE:** Koskript is currently in early development. Features like module imports, more types, and performance improvements are on the way.
|
|
18
|
+
|
|
19
|
+
---
|
|
20
|
+
|
|
21
|
+
## Features
|
|
22
|
+
|
|
23
|
+
- Dynamic typing.
|
|
24
|
+
- Block-level lexical scoping with `local` declarations
|
|
25
|
+
- Native Python interop via `KoskriptObject`
|
|
26
|
+
- `if`, `elseif`, `else`
|
|
27
|
+
- `while`, `for`, `foreach` loops with `break` / `continue`
|
|
28
|
+
- Member access — `map.key.subkey`
|
|
29
|
+
- Index access — `array[0]`, `map["key"]`
|
|
30
|
+
- First-class functions and lambda expressions
|
|
31
|
+
- Arithmetic (`+`, `-`, `*`, `/`, `%`), comparison and logical operators
|
|
32
|
+
- Embeddable in any Python application
|
|
33
|
+
|
|
34
|
+
---
|
|
35
|
+
|
|
36
|
+
## Installation
|
|
37
|
+
|
|
38
|
+
Clone the repository and import it directly into your project:
|
|
39
|
+
|
|
40
|
+
```bash
|
|
41
|
+
git clone https://github.com/alesisce/koskript.git
|
|
42
|
+
```
|
|
43
|
+
|
|
44
|
+
> PyPI package coming soon.
|
|
45
|
+
|
|
46
|
+
---
|
|
47
|
+
|
|
48
|
+
## Quick Start
|
|
49
|
+
|
|
50
|
+
```python
|
|
51
|
+
from koskript import KoskriptRuntime
|
|
52
|
+
|
|
53
|
+
runtime = KoskriptRuntime({
|
|
54
|
+
"print": print
|
|
55
|
+
})
|
|
56
|
+
runtime.execute("""
|
|
57
|
+
local x = 10
|
|
58
|
+
local y = 26
|
|
59
|
+
|
|
60
|
+
print(x+y)
|
|
61
|
+
""")
|
|
62
|
+
```
|
|
63
|
+
|
|
64
|
+
Any Python value or callable you pass in is wrapped automatically — no need to build `KoskriptObject` yourself.
|
|
65
|
+
|
|
66
|
+
---
|
|
67
|
+
|
|
68
|
+
## Example
|
|
69
|
+
|
|
70
|
+
```koskript
|
|
71
|
+
// Student grade checker
|
|
72
|
+
local students = {
|
|
73
|
+
"Aleix": {
|
|
74
|
+
"age": 17,
|
|
75
|
+
"grade": 95
|
|
76
|
+
},
|
|
77
|
+
"Maria": {
|
|
78
|
+
"age": 15,
|
|
79
|
+
"grade": 72
|
|
80
|
+
},
|
|
81
|
+
"Juan": {
|
|
82
|
+
"age": 18,
|
|
83
|
+
"grade": 88
|
|
84
|
+
}
|
|
85
|
+
}
|
|
86
|
+
|
|
87
|
+
local passing_grade = 75
|
|
88
|
+
|
|
89
|
+
foreach (name, data in students) {
|
|
90
|
+
local grade = data.grade
|
|
91
|
+
|
|
92
|
+
if (grade >= passing_grade) {
|
|
93
|
+
print("PASS:", name, "->", grade) // Depends on how you implement it.
|
|
94
|
+
} elseif (grade >= 60) {
|
|
95
|
+
print("NEAR PASS:", name, "->", grade)
|
|
96
|
+
} else {
|
|
97
|
+
print("FAIL:", name, "->", grade)
|
|
98
|
+
}
|
|
99
|
+
}
|
|
100
|
+
```
|
|
101
|
+
|
|
102
|
+
Output:
|
|
103
|
+
```
|
|
104
|
+
PASS: Aleix -> 95
|
|
105
|
+
NEAR PASS: Maria -> 72
|
|
106
|
+
PASS: Juan -> 88
|
|
107
|
+
```
|
|
108
|
+
|
|
109
|
+
---
|
|
110
|
+
|
|
111
|
+
## Language Reference
|
|
112
|
+
|
|
113
|
+
### Types
|
|
114
|
+
|
|
115
|
+
| Type | Description |
|
|
116
|
+
|------|-------------|
|
|
117
|
+
| `int` | Integer number |
|
|
118
|
+
| `float` | Floating-point number |
|
|
119
|
+
| `string` | Text string |
|
|
120
|
+
| `bool` | `true` or `false` |
|
|
121
|
+
| `null` | The absence of a value |
|
|
122
|
+
| `array` | Ordered list |
|
|
123
|
+
| `map` | Key-value store |
|
|
124
|
+
|
|
125
|
+
### Variables
|
|
126
|
+
|
|
127
|
+
```koskript
|
|
128
|
+
local x = 10
|
|
129
|
+
local pi = 3.14
|
|
130
|
+
local name = "Koskript"
|
|
131
|
+
local active = true
|
|
132
|
+
local missing = null
|
|
133
|
+
local items = [1, 2, 3]
|
|
134
|
+
local empty = []
|
|
135
|
+
local config = { "debug": true, "version": 1 }
|
|
136
|
+
```
|
|
137
|
+
|
|
138
|
+
Variables declared with `local` are scoped to the block they are declared in — including `if`, `while`, `for` and `foreach` bodies.
|
|
139
|
+
|
|
140
|
+
### Functions
|
|
141
|
+
|
|
142
|
+
```koskript
|
|
143
|
+
fn add(a, b) {
|
|
144
|
+
return a + b
|
|
145
|
+
}
|
|
146
|
+
|
|
147
|
+
local result = add(10, 20)
|
|
148
|
+
```
|
|
149
|
+
|
|
150
|
+
### Operators
|
|
151
|
+
|
|
152
|
+
```koskript
|
|
153
|
+
local a = 2 + 3 * 4 // 14 (precedence: * / % before + -)
|
|
154
|
+
local b = 10 % 3 // 1
|
|
155
|
+
local c = -a // unary minus
|
|
156
|
+
local d = (a + b) * 2 // grouping with parentheses
|
|
157
|
+
|
|
158
|
+
if (x >= 10 and not done or retry) {
|
|
159
|
+
// ...
|
|
160
|
+
}
|
|
161
|
+
```
|
|
162
|
+
|
|
163
|
+
| Operators | Description |
|
|
164
|
+
|-----------|-------------|
|
|
165
|
+
| `+` `-` `*` `/` `%` | Arithmetic |
|
|
166
|
+
| `-x` | Unary minus |
|
|
167
|
+
| `==` `!=` `>` `<` `>=` `<=` | Comparison |
|
|
168
|
+
| `and` `or` `not` | Logical (short-circuiting) |
|
|
169
|
+
| `( )` | Grouping |
|
|
170
|
+
|
|
171
|
+
### Control Flow
|
|
172
|
+
|
|
173
|
+
```koskript
|
|
174
|
+
if (x > 10) {
|
|
175
|
+
print("big")
|
|
176
|
+
} elseif (x == 10) {
|
|
177
|
+
print("exact")
|
|
178
|
+
} else {
|
|
179
|
+
print("small")
|
|
180
|
+
}
|
|
181
|
+
```
|
|
182
|
+
|
|
183
|
+
### Lambda Functions
|
|
184
|
+
|
|
185
|
+
```koskript
|
|
186
|
+
local greet = () {
|
|
187
|
+
print("Hello world")
|
|
188
|
+
}
|
|
189
|
+
|
|
190
|
+
greet()
|
|
191
|
+
|
|
192
|
+
// lambdas can take parameters and return values
|
|
193
|
+
local add = (a, b) {
|
|
194
|
+
return a + b
|
|
195
|
+
}
|
|
196
|
+
|
|
197
|
+
print(add(1, 2))
|
|
198
|
+
|
|
199
|
+
// invoke a lambda literal directly
|
|
200
|
+
print((() { return 42 })())
|
|
201
|
+
```
|
|
202
|
+
|
|
203
|
+
### Loops
|
|
204
|
+
|
|
205
|
+
```koskript
|
|
206
|
+
// while
|
|
207
|
+
while (x > 0) {
|
|
208
|
+
x = x - 1
|
|
209
|
+
}
|
|
210
|
+
|
|
211
|
+
// for — iterate array
|
|
212
|
+
for (item in items) {
|
|
213
|
+
print(item)
|
|
214
|
+
}
|
|
215
|
+
|
|
216
|
+
// foreach — iterate map
|
|
217
|
+
foreach (key, value in config) {
|
|
218
|
+
print(key, value)
|
|
219
|
+
}
|
|
220
|
+
```
|
|
221
|
+
|
|
222
|
+
`break` exits the nearest loop and `continue` skips to the next iteration:
|
|
223
|
+
|
|
224
|
+
```koskript
|
|
225
|
+
for (item in items) {
|
|
226
|
+
if (item == 2) {
|
|
227
|
+
continue // skip this item
|
|
228
|
+
}
|
|
229
|
+
if (item == 5) {
|
|
230
|
+
break // stop looping
|
|
231
|
+
}
|
|
232
|
+
print(item)
|
|
233
|
+
}
|
|
234
|
+
```
|
|
235
|
+
|
|
236
|
+
### Member Access
|
|
237
|
+
|
|
238
|
+
```koskript
|
|
239
|
+
local user = { "name": "Aleix", "age": 17 }
|
|
240
|
+
print(user.name)
|
|
241
|
+
print(user.age)
|
|
242
|
+
```
|
|
243
|
+
|
|
244
|
+
### Index Access
|
|
245
|
+
|
|
246
|
+
```koskript
|
|
247
|
+
local items = [10, 20, 30]
|
|
248
|
+
print(items[0]) // 10
|
|
249
|
+
print(items[-1]) // 30
|
|
250
|
+
|
|
251
|
+
local config = { "debug": true }
|
|
252
|
+
print(config["debug"]) // true
|
|
253
|
+
|
|
254
|
+
// member access and indexing can be chained
|
|
255
|
+
local data = { "nums": [1, 2, 3] }
|
|
256
|
+
print(data.nums[1]) // 2
|
|
257
|
+
```
|
|
258
|
+
|
|
259
|
+
### Strings
|
|
260
|
+
|
|
261
|
+
Strings support single or double quotes and the escapes `\n`, `\t`, `\r`, `\0`, `\\`, `\"` and `\'`:
|
|
262
|
+
|
|
263
|
+
```koskript
|
|
264
|
+
print("line one\nline two")
|
|
265
|
+
```
|
|
266
|
+
|
|
267
|
+
### Comments
|
|
268
|
+
|
|
269
|
+
```koskript
|
|
270
|
+
// line comments start with two slashes
|
|
271
|
+
```
|
|
272
|
+
|
|
273
|
+
### Reserved Keywords
|
|
274
|
+
|
|
275
|
+
The following words cannot be used as identifiers:
|
|
276
|
+
|
|
277
|
+
`if` `elseif` `else` `while` `for` `foreach` `fn` `return` `local` `true` `false` `null` `and` `or` `not` `in` `break` `continue`
|
|
278
|
+
|
|
279
|
+
### Python Interop
|
|
280
|
+
|
|
281
|
+
Any Python value or callable can be exposed to Koskript. They are wrapped in a `KoskriptObject` automatically:
|
|
282
|
+
|
|
283
|
+
```python
|
|
284
|
+
from koskript import KoskriptRuntime
|
|
285
|
+
|
|
286
|
+
runtime = KoskriptRuntime({
|
|
287
|
+
"print": print,
|
|
288
|
+
"len": len,
|
|
289
|
+
})
|
|
290
|
+
|
|
291
|
+
# add more later — register() is chainable
|
|
292
|
+
runtime.register("sqrt", math.sqrt)
|
|
293
|
+
runtime["now"] = time.time
|
|
294
|
+
```
|
|
295
|
+
|
|
296
|
+
### Embedding API
|
|
297
|
+
|
|
298
|
+
`execute()` returns the value of the last evaluated expression, or the value of a top-level `return`:
|
|
299
|
+
|
|
300
|
+
```python
|
|
301
|
+
runtime = KoskriptRuntime({"print": print})
|
|
302
|
+
result = runtime.execute("local x = 10\nx * 2") # 20
|
|
303
|
+
result = runtime.execute("return 1 + 2") # 3
|
|
304
|
+
```
|
|
305
|
+
|
|
306
|
+
For a quick one-off script, use the `run()` helper:
|
|
307
|
+
|
|
308
|
+
```python
|
|
309
|
+
from koskript import run
|
|
310
|
+
|
|
311
|
+
run("print(1 + 2)", print=print) # 3
|
|
312
|
+
```
|
|
313
|
+
|
|
314
|
+
Errors raised by scripts are available under `koskript.Errors`:
|
|
315
|
+
|
|
316
|
+
```python
|
|
317
|
+
from koskript import Errors
|
|
318
|
+
|
|
319
|
+
try:
|
|
320
|
+
runtime.execute("local a = [1]\nprint(a[5])")
|
|
321
|
+
except Errors.RuntimeError as e:
|
|
322
|
+
print(e)
|
|
323
|
+
```
|
|
324
|
+
|
|
325
|
+
---
|
|
326
|
+
|
|
327
|
+
## Roadmap
|
|
328
|
+
|
|
329
|
+
- [x] Index access (`array[0]`, `map["key"]`)
|
|
330
|
+
- [x] `float` type
|
|
331
|
+
- [x] `null` type
|
|
332
|
+
- [x] `break` / `continue` statements
|
|
333
|
+
- [ ] Module imports (`import "mymodule"`)
|
|
334
|
+
- [ ] Performance improvements
|
|
335
|
+
- [ ] Standard library
|
|
336
|
+
- [ ] PyPI package
|
|
337
|
+
- [ ] Custom parser (remove Lark dependency)
|
|
338
|
+
- [ ] VM-based execution
|
|
339
|
+
|
|
340
|
+
---
|
|
341
|
+
|
|
342
|
+
## License
|
|
343
|
+
|
|
344
|
+
Koskript is licensed under the Mozilla Public License 2.0 (MPL-2.0).
|
|
345
|
+
|
|
346
|
+
You are free to use, modify, and redistribute Koskript. However, you may not redistribute this project under a different name or claim authorship of the Koskript language.
|
|
347
|
+
|
|
348
|
+
**Koskript is a trademark of Alesis.**
|
|
349
|
+
|
|
350
|
+
---
|
|
351
|
+
|
|
352
|
+
*Built with ❤️ by Alesis*
|
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
koskript/__init__.py,sha256=15DmS6nvri29BNpXwZRnsTWcE0A0xE6tJJz2TnynoYI,2622
|
|
2
|
+
koskript/lang/astgen.py,sha256=_GCX8gnX0vMFYg_vH5G-7T-qVB8FAcYndjZSeZPli3I,5709
|
|
3
|
+
koskript/lang/emtypes.py,sha256=bfc9-k9zjv_haA4a_EzFWoqRj2GUDbI_JT9ltrEINpo,3296
|
|
4
|
+
koskript/lang/errors.py,sha256=LtCDLXFsV-ZYqAJTbmQvzgXgl-xO7BcHbZTQ0JerUBw,351
|
|
5
|
+
koskript/lang/interpreter.py,sha256=TDYojeSTMSryBit32sjd0zwRd2xuhBvNbm_761bMsPY,11752
|
|
6
|
+
koskript-1.2.0.dist-info/licenses/LICENSE,sha256=kiHC-TYVm4RG0ykkn7TA8lvlEPRHODoPEzNqx5hWaKM,17099
|
|
7
|
+
koskript-1.2.0.dist-info/METADATA,sha256=kr1_9khWeCheCQTxf626pUuCX6-6ZujX3CwqPfu_XIs,7397
|
|
8
|
+
koskript-1.2.0.dist-info/WHEEL,sha256=YVMoNqKzERt-wjUZwJ33xBGAwnFl-4cqbYkTtWa4itE,91
|
|
9
|
+
koskript-1.2.0.dist-info/top_level.txt,sha256=cls7RsuQwlrfwJUYwcSkzqQcdE-KfdLlFu-6vuae6gQ,9
|
|
10
|
+
koskript-1.2.0.dist-info/RECORD,,
|