shell-lite 0.5.3__tar.gz → 0.5.3.1__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.
- {shell_lite-0.5.3/shell_lite.egg-info → shell_lite-0.5.3.1}/PKG-INFO +1 -1
- {shell_lite-0.5.3 → shell_lite-0.5.3.1}/pyproject.toml +1 -1
- {shell_lite-0.5.3 → shell_lite-0.5.3.1}/shell_lite/interpreter.py +4 -2
- {shell_lite-0.5.3 → shell_lite-0.5.3.1}/shell_lite/main.py +0 -1
- {shell_lite-0.5.3 → shell_lite-0.5.3.1}/shell_lite/parser_gbp.py +133 -10
- {shell_lite-0.5.3 → shell_lite-0.5.3.1/shell_lite.egg-info}/PKG-INFO +1 -1
- {shell_lite-0.5.3 → shell_lite-0.5.3.1}/LICENSE +0 -0
- {shell_lite-0.5.3 → shell_lite-0.5.3.1}/README.md +0 -0
- {shell_lite-0.5.3 → shell_lite-0.5.3.1}/setup.cfg +0 -0
- {shell_lite-0.5.3 → shell_lite-0.5.3.1}/setup.py +0 -0
- {shell_lite-0.5.3 → shell_lite-0.5.3.1}/shell_lite/__init__.py +0 -0
- {shell_lite-0.5.3 → shell_lite-0.5.3.1}/shell_lite/ast_nodes.py +0 -0
- {shell_lite-0.5.3 → shell_lite-0.5.3.1}/shell_lite/cli.py +0 -0
- {shell_lite-0.5.3 → shell_lite-0.5.3.1}/shell_lite/compiler.py +0 -0
- {shell_lite-0.5.3 → shell_lite-0.5.3.1}/shell_lite/js_compiler.py +0 -0
- {shell_lite-0.5.3 → shell_lite-0.5.3.1}/shell_lite/lexer.py +0 -0
- {shell_lite-0.5.3 → shell_lite-0.5.3.1}/shell_lite/llvm_backend/__init__.py +0 -0
- {shell_lite-0.5.3 → shell_lite-0.5.3.1}/shell_lite/llvm_backend/builder.py +0 -0
- {shell_lite-0.5.3 → shell_lite-0.5.3.1}/shell_lite/llvm_backend/codegen.py +0 -0
- {shell_lite-0.5.3 → shell_lite-0.5.3.1}/shell_lite/parser.py +0 -0
- {shell_lite-0.5.3 → shell_lite-0.5.3.1}/shell_lite/runtime.py +0 -0
- {shell_lite-0.5.3 → shell_lite-0.5.3.1}/shell_lite.egg-info/SOURCES.txt +0 -0
- {shell_lite-0.5.3 → shell_lite-0.5.3.1}/shell_lite.egg-info/dependency_links.txt +0 -0
- {shell_lite-0.5.3 → shell_lite-0.5.3.1}/shell_lite.egg-info/entry_points.txt +0 -0
- {shell_lite-0.5.3 → shell_lite-0.5.3.1}/shell_lite.egg-info/requires.txt +0 -0
- {shell_lite-0.5.3 → shell_lite-0.5.3.1}/shell_lite.egg-info/top_level.txt +0 -0
- {shell_lite-0.5.3 → shell_lite-0.5.3.1}/tests/__init__.py +0 -0
- {shell_lite-0.5.3 → shell_lite-0.5.3.1}/tests/benchmark_driver.py +0 -0
- {shell_lite-0.5.3 → shell_lite-0.5.3.1}/tests/compare_parsers.py +0 -0
- {shell_lite-0.5.3 → shell_lite-0.5.3.1}/tests/debug_jit.py +0 -0
- {shell_lite-0.5.3 → shell_lite-0.5.3.1}/tests/generate_actual_graph.py +0 -0
- {shell_lite-0.5.3 → shell_lite-0.5.3.1}/tests/generate_perf_graph.py +0 -0
- {shell_lite-0.5.3 → shell_lite-0.5.3.1}/tests/generate_runtime_graph.py +0 -0
- {shell_lite-0.5.3 → shell_lite-0.5.3.1}/tests/run_jit.py +0 -0
- {shell_lite-0.5.3 → shell_lite-0.5.3.1}/tests/test_gbp_standalone.py +0 -0
- {shell_lite-0.5.3 → shell_lite-0.5.3.1}/tests/test_interpreter.py +0 -0
- {shell_lite-0.5.3 → shell_lite-0.5.3.1}/tests/test_lexer.py +0 -0
- {shell_lite-0.5.3 → shell_lite-0.5.3.1}/tests/test_parser.py +0 -0
- {shell_lite-0.5.3 → shell_lite-0.5.3.1}/tests/test_phase1.py +0 -0
- {shell_lite-0.5.3 → shell_lite-0.5.3.1}/tests/test_stdlib.py +0 -0
|
@@ -240,8 +240,10 @@ class Interpreter:
|
|
|
240
240
|
for k, v in self.builtins.items():
|
|
241
241
|
self.global_env.set(k, v)
|
|
242
242
|
def _make_tag_fn(self, tag_name):
|
|
243
|
-
def tag_fn(*args):
|
|
243
|
+
def tag_fn(*args, **kwargs):
|
|
244
244
|
attrs = {}
|
|
245
|
+
# Add kwargs directly as attributes
|
|
246
|
+
attrs.update(kwargs)
|
|
245
247
|
content = []
|
|
246
248
|
for arg in args:
|
|
247
249
|
if isinstance(arg, dict):
|
|
@@ -1502,7 +1504,7 @@ class Interpreter:
|
|
|
1502
1504
|
self.wfile.write(str(e).encode())
|
|
1503
1505
|
except: pass
|
|
1504
1506
|
server = ReusableHTTPServer(('0.0.0.0', port_val), ShellLiteHandler)
|
|
1505
|
-
print(f"\n ShellLite Server v0.5.3 is running!")
|
|
1507
|
+
print(f"\n ShellLite Server v0.5.3.1 is running!")
|
|
1506
1508
|
print(f" \u001b[1;36m➜\u001b[0m Local: \u001b[1;4;36mhttp://localhost:{port_val}/\u001b[0m\n")
|
|
1507
1509
|
try: server.serve_forever()
|
|
1508
1510
|
except KeyboardInterrupt:
|
|
@@ -17,7 +17,6 @@ def execute_source(source: str, interpreter: Interpreter):
|
|
|
17
17
|
lexer = Lexer(source)
|
|
18
18
|
tokens = lexer.tokenize()
|
|
19
19
|
if os.environ.get('USE_LEGACY_PARSER') == '1':
|
|
20
|
-
from .parser import Parser
|
|
21
20
|
parser = Parser(tokens)
|
|
22
21
|
else:
|
|
23
22
|
from .parser_gbp import GeometricBindingParser
|
|
@@ -43,20 +43,24 @@ class GeometricBindingParser:
|
|
|
43
43
|
node_stack: List[GeoNode] = [] # The active parents
|
|
44
44
|
current_tokens_accumulator = []
|
|
45
45
|
current_node: Optional[GeoNode] = None
|
|
46
|
+
last_line_node: Optional[GeoNode] = None # Track the last completed line's node
|
|
46
47
|
block_stack: List[GeoNode] = []
|
|
47
48
|
for token in self.tokens:
|
|
48
49
|
if token.type == 'EOF':
|
|
49
50
|
break
|
|
50
51
|
if token.type == 'INDENT':
|
|
51
|
-
if current_node
|
|
52
|
-
|
|
53
|
-
|
|
52
|
+
# Use last_line_node as parent if current_node is None (NEWLINE came before INDENT)
|
|
53
|
+
parent_to_push = current_node if current_node else last_line_node
|
|
54
|
+
if parent_to_push:
|
|
55
|
+
block_stack.append(parent_to_push)
|
|
56
|
+
current_node = None
|
|
54
57
|
continue
|
|
55
58
|
if token.type == 'DEDENT':
|
|
56
59
|
if block_stack:
|
|
57
60
|
block_stack.pop()
|
|
58
61
|
continue
|
|
59
62
|
if token.type == 'NEWLINE':
|
|
63
|
+
last_line_node = current_node # Save before resetting
|
|
60
64
|
current_node = None
|
|
61
65
|
continue
|
|
62
66
|
if current_node is None:
|
|
@@ -83,7 +87,17 @@ class GeometricBindingParser:
|
|
|
83
87
|
return self.bind_while(node)
|
|
84
88
|
elif head_type == 'FOR' or head_type == 'LOOP':
|
|
85
89
|
return self.bind_for(node)
|
|
86
|
-
elif head_type == '
|
|
90
|
+
elif head_type == 'USE':
|
|
91
|
+
return self.bind_use(node)
|
|
92
|
+
elif head_type == 'SERVE':
|
|
93
|
+
return self.bind_serve(node)
|
|
94
|
+
elif head_type == 'DEFINE':
|
|
95
|
+
return self.bind_define(node)
|
|
96
|
+
elif head_type == 'WHEN':
|
|
97
|
+
return self.bind_when(node)
|
|
98
|
+
elif head_type == 'ON':
|
|
99
|
+
return self.bind_on(node)
|
|
100
|
+
elif head_type == 'FUNCTION' or head_type == 'TO':
|
|
87
101
|
return self.bind_func(node)
|
|
88
102
|
elif head_type == 'PRINT' or head_type == 'SAY':
|
|
89
103
|
return self.bind_print(node)
|
|
@@ -96,11 +110,13 @@ class GeometricBindingParser:
|
|
|
96
110
|
elif head_type == 'LISTEN':
|
|
97
111
|
return self.bind_listen(node)
|
|
98
112
|
elif head_type == 'ID':
|
|
99
|
-
|
|
113
|
+
# Assignment: ID ASSIGN ... (second token must be ASSIGN)
|
|
114
|
+
# Function call with kwargs: ID ID ASSIGN ... (ASSIGN comes later)
|
|
115
|
+
if len(node.tokens) >= 2 and node.tokens[1].type == 'ASSIGN':
|
|
100
116
|
return self.bind_assignment(node)
|
|
101
|
-
return self.
|
|
117
|
+
return self.bind_call_or_expr(node)
|
|
102
118
|
else:
|
|
103
|
-
return self.
|
|
119
|
+
return self.bind_call_or_expr(node)
|
|
104
120
|
def peek_type(self, node: GeoNode, offset: int) -> str:
|
|
105
121
|
if offset < len(node.tokens):
|
|
106
122
|
return node.tokens[offset].type
|
|
@@ -159,12 +175,119 @@ class GeometricBindingParser:
|
|
|
159
175
|
if node.tokens[0].type == 'DEFINE': start = 2
|
|
160
176
|
name = node.tokens[start].value
|
|
161
177
|
args = []
|
|
178
|
+
# Look for 'using' keyword (which is tokenized as ID)
|
|
179
|
+
collecting_args = False
|
|
162
180
|
for t in node.tokens[start+1:]:
|
|
163
|
-
|
|
164
|
-
|
|
165
|
-
|
|
181
|
+
if t.type == 'USING':
|
|
182
|
+
collecting_args = True
|
|
183
|
+
continue
|
|
184
|
+
if t.type == 'ID' and collecting_args:
|
|
185
|
+
args.append((t.value, None, None))
|
|
186
|
+
elif t.type == 'COLON': break
|
|
187
|
+
elif t.type == 'COMMA': continue
|
|
188
|
+
body = [self.bind_node(child) for child in node.children]
|
|
189
|
+
return FunctionDef(name, args, body)
|
|
190
|
+
def bind_use(self, node: GeoNode) -> Import:
|
|
191
|
+
# 'use "filename.shl"'
|
|
192
|
+
for t in node.tokens:
|
|
193
|
+
if t.type == 'STRING':
|
|
194
|
+
return Import(t.value)
|
|
195
|
+
return Import(node.tokens[1].value if len(node.tokens) > 1 else '')
|
|
196
|
+
def bind_serve(self, node: GeoNode) -> ServeStatic:
|
|
197
|
+
# 'serve files from "public" at "/static"'
|
|
198
|
+
folder = String('public')
|
|
199
|
+
url = String('/static')
|
|
200
|
+
tokens = node.tokens
|
|
201
|
+
for i, t in enumerate(tokens):
|
|
202
|
+
if t.type == 'FROM' and i+1 < len(tokens):
|
|
203
|
+
folder = self.parse_expr_iterative([tokens[i+1]])
|
|
204
|
+
if t.type == 'AT' and i+1 < len(tokens):
|
|
205
|
+
url = self.parse_expr_iterative([tokens[i+1]])
|
|
206
|
+
return ServeStatic(folder, url)
|
|
207
|
+
def bind_define(self, node: GeoNode) -> FunctionDef:
|
|
208
|
+
# 'define page Name using arg1, arg2'
|
|
209
|
+
tokens = node.tokens
|
|
210
|
+
name = ''
|
|
211
|
+
args = []
|
|
212
|
+
i = 1
|
|
213
|
+
# Skip 'page' or 'component' if present
|
|
214
|
+
if i < len(tokens) and tokens[i].type == 'PAGE':
|
|
215
|
+
i += 1
|
|
216
|
+
if i < len(tokens) and tokens[i].type == 'ID':
|
|
217
|
+
name = tokens[i].value
|
|
218
|
+
i += 1
|
|
219
|
+
# Parse 'using' args
|
|
220
|
+
if i < len(tokens) and tokens[i].type == 'USING':
|
|
221
|
+
i += 1
|
|
222
|
+
while i < len(tokens):
|
|
223
|
+
if tokens[i].type == 'ID':
|
|
224
|
+
args.append((tokens[i].value, None, None))
|
|
225
|
+
elif tokens[i].type == 'COMMA':
|
|
226
|
+
pass
|
|
227
|
+
else:
|
|
228
|
+
break
|
|
229
|
+
i += 1
|
|
166
230
|
body = [self.bind_node(child) for child in node.children]
|
|
167
231
|
return FunctionDef(name, args, body)
|
|
232
|
+
def bind_when(self, node: GeoNode) -> OnRequest:
|
|
233
|
+
# 'when someone visits "/path"'
|
|
234
|
+
tokens = node.tokens
|
|
235
|
+
path = String('/')
|
|
236
|
+
for i, t in enumerate(tokens):
|
|
237
|
+
if t.type == 'STRING':
|
|
238
|
+
path = String(t.value)
|
|
239
|
+
break
|
|
240
|
+
body = [self.bind_node(child) for child in node.children]
|
|
241
|
+
return OnRequest(path, body)
|
|
242
|
+
def bind_on(self, node: GeoNode) -> OnRequest:
|
|
243
|
+
# 'on request to "/path"'
|
|
244
|
+
tokens = node.tokens
|
|
245
|
+
path = String('/')
|
|
246
|
+
for t in tokens:
|
|
247
|
+
if t.type == 'STRING':
|
|
248
|
+
path = String(t.value)
|
|
249
|
+
break
|
|
250
|
+
body = [self.bind_node(child) for child in node.children]
|
|
251
|
+
return OnRequest(path, body)
|
|
252
|
+
def bind_call_or_expr(self, node: GeoNode) -> Any:
|
|
253
|
+
# Handle function calls like 'Head "title"' or 'Navbar'
|
|
254
|
+
tokens = node.tokens
|
|
255
|
+
if len(tokens) >= 1 and tokens[0].type == 'ID':
|
|
256
|
+
name = tokens[0].value
|
|
257
|
+
args = []
|
|
258
|
+
kwargs = []
|
|
259
|
+
# Parse remaining tokens as arguments, handling key=value for HTML attributes
|
|
260
|
+
i = 1
|
|
261
|
+
while i < len(tokens):
|
|
262
|
+
t = tokens[i]
|
|
263
|
+
# Check for key=value pattern (e.g. class="container", style="...", href="...")
|
|
264
|
+
# HTML attributes may be tokenized as various types: ID, STRUCTURE, HREF, REL, NAME, etc.
|
|
265
|
+
is_attr_key = t.type in ('ID', 'STRUCTURE', 'HREF', 'REL', 'NAME', 'STYLE', 'CONTENT', 'CHARSET', 'SRC', 'ALT', 'TYPE', 'VALUE', 'PLACEHOLDER', 'METHOD', 'ACTION')
|
|
266
|
+
is_kwarg = (is_attr_key and i + 2 < len(tokens) and tokens[i + 1].type == 'ASSIGN')
|
|
267
|
+
if is_kwarg:
|
|
268
|
+
key = t.value
|
|
269
|
+
val_token = tokens[i + 2]
|
|
270
|
+
if val_token.type == 'STRING':
|
|
271
|
+
kwargs.append((key, String(val_token.value)))
|
|
272
|
+
elif val_token.type == 'NUMBER':
|
|
273
|
+
kwargs.append((key, Number(int(val_token.value) if '.' not in val_token.value else float(val_token.value))))
|
|
274
|
+
else:
|
|
275
|
+
kwargs.append((key, VarAccess(val_token.value)))
|
|
276
|
+
i += 3
|
|
277
|
+
continue
|
|
278
|
+
elif t.type == 'STRING':
|
|
279
|
+
args.append(String(t.value))
|
|
280
|
+
elif t.type == 'NUMBER':
|
|
281
|
+
args.append(Number(int(t.value) if '.' not in t.value else float(t.value)))
|
|
282
|
+
elif t.type == 'ID':
|
|
283
|
+
args.append(VarAccess(t.value))
|
|
284
|
+
i += 1
|
|
285
|
+
# Check for children (indented body)
|
|
286
|
+
body = None
|
|
287
|
+
if node.children:
|
|
288
|
+
body = [self.bind_node(child) for child in node.children]
|
|
289
|
+
return Call(name, args, kwargs=kwargs, body=body)
|
|
290
|
+
return self.parse_expr_iterative(tokens)
|
|
168
291
|
def _extract_expr_tokens(self, tokens: List[Token], start: int = 0) -> List[Token]:
|
|
169
292
|
end = len(tokens)
|
|
170
293
|
if tokens[-1].type == 'COLON':
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|