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.
@@ -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
+ ![Koskript](https://raw.githubusercontent.com/alesisce/koskript/refs/heads/main/banner.png)
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,,
@@ -0,0 +1,5 @@
1
+ Wheel-Version: 1.0
2
+ Generator: setuptools (84.0.0)
3
+ Root-Is-Purelib: true
4
+ Tag: py3-none-any
5
+