shell-lite 0.5.3__tar.gz → 0.5.3.2__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.2}/PKG-INFO +1 -1
- {shell_lite-0.5.3 → shell_lite-0.5.3.2}/pyproject.toml +1 -1
- {shell_lite-0.5.3 → shell_lite-0.5.3.2}/shell_lite/interpreter.py +4 -2
- {shell_lite-0.5.3 → shell_lite-0.5.3.2}/shell_lite/main.py +7 -2
- {shell_lite-0.5.3 → shell_lite-0.5.3.2}/shell_lite/parser_gbp.py +133 -10
- {shell_lite-0.5.3 → shell_lite-0.5.3.2/shell_lite.egg-info}/PKG-INFO +1 -1
- {shell_lite-0.5.3 → shell_lite-0.5.3.2}/shell_lite.egg-info/SOURCES.txt +0 -11
- shell_lite-0.5.3/shell_lite/llvm_backend/__init__.py +0 -0
- shell_lite-0.5.3/shell_lite/llvm_backend/builder.py +0 -39
- shell_lite-0.5.3/shell_lite/llvm_backend/codegen.py +0 -311
- shell_lite-0.5.3/tests/__init__.py +0 -1
- shell_lite-0.5.3/tests/benchmark_driver.py +0 -43
- shell_lite-0.5.3/tests/compare_parsers.py +0 -31
- shell_lite-0.5.3/tests/debug_jit.py +0 -49
- shell_lite-0.5.3/tests/generate_actual_graph.py +0 -84
- shell_lite-0.5.3/tests/generate_perf_graph.py +0 -68
- shell_lite-0.5.3/tests/generate_runtime_graph.py +0 -58
- shell_lite-0.5.3/tests/run_jit.py +0 -70
- {shell_lite-0.5.3 → shell_lite-0.5.3.2}/LICENSE +0 -0
- {shell_lite-0.5.3 → shell_lite-0.5.3.2}/README.md +0 -0
- {shell_lite-0.5.3 → shell_lite-0.5.3.2}/setup.cfg +0 -0
- {shell_lite-0.5.3 → shell_lite-0.5.3.2}/setup.py +0 -0
- {shell_lite-0.5.3 → shell_lite-0.5.3.2}/shell_lite/__init__.py +0 -0
- {shell_lite-0.5.3 → shell_lite-0.5.3.2}/shell_lite/ast_nodes.py +0 -0
- {shell_lite-0.5.3 → shell_lite-0.5.3.2}/shell_lite/cli.py +0 -0
- {shell_lite-0.5.3 → shell_lite-0.5.3.2}/shell_lite/compiler.py +0 -0
- {shell_lite-0.5.3 → shell_lite-0.5.3.2}/shell_lite/js_compiler.py +0 -0
- {shell_lite-0.5.3 → shell_lite-0.5.3.2}/shell_lite/lexer.py +0 -0
- {shell_lite-0.5.3 → shell_lite-0.5.3.2}/shell_lite/parser.py +0 -0
- {shell_lite-0.5.3 → shell_lite-0.5.3.2}/shell_lite/runtime.py +0 -0
- {shell_lite-0.5.3 → shell_lite-0.5.3.2}/shell_lite.egg-info/dependency_links.txt +0 -0
- {shell_lite-0.5.3 → shell_lite-0.5.3.2}/shell_lite.egg-info/entry_points.txt +0 -0
- {shell_lite-0.5.3 → shell_lite-0.5.3.2}/shell_lite.egg-info/requires.txt +0 -0
- {shell_lite-0.5.3 → shell_lite-0.5.3.2}/shell_lite.egg-info/top_level.txt +0 -0
- {shell_lite-0.5.3 → shell_lite-0.5.3.2}/tests/test_gbp_standalone.py +0 -0
- {shell_lite-0.5.3 → shell_lite-0.5.3.2}/tests/test_interpreter.py +0 -0
- {shell_lite-0.5.3 → shell_lite-0.5.3.2}/tests/test_lexer.py +0 -0
- {shell_lite-0.5.3 → shell_lite-0.5.3.2}/tests/test_parser.py +0 -0
- {shell_lite-0.5.3 → shell_lite-0.5.3.2}/tests/test_phase1.py +0 -0
- {shell_lite-0.5.3 → shell_lite-0.5.3.2}/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.2 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
|
|
@@ -151,7 +150,7 @@ def install_globally():
|
|
|
151
150
|
return
|
|
152
151
|
ps_cmd = f'$oldPath = [Environment]::GetEnvironmentVariable("Path", "User"); if ($oldPath -notlike "*ShellLite*") {{ [Environment]::SetEnvironmentVariable("Path", "$oldPath;{install_dir}", "User") }}'
|
|
153
152
|
subprocess.run(["powershell", "-Command", ps_cmd], capture_output=True)
|
|
154
|
-
print(f"\n[SUCCESS] ShellLite (v0.5.3) is installed!")
|
|
153
|
+
print(f"\n[SUCCESS] ShellLite (v0.5.3.2) is installed!")
|
|
155
154
|
print(f"Location: {install_dir}")
|
|
156
155
|
print("\nIMPORTANT STEP REQUIRED:")
|
|
157
156
|
print("1. Close ALL open terminal windows (CMD, PowerShell, VS Code).")
|
|
@@ -444,6 +443,12 @@ def main():
|
|
|
444
443
|
print("Usage: shl llvm <filename>")
|
|
445
444
|
elif cmd == "help" or cmd == "--help" or cmd == "-h":
|
|
446
445
|
show_help()
|
|
446
|
+
elif cmd == "--version" or cmd == "-v":
|
|
447
|
+
try:
|
|
448
|
+
from importlib.metadata import version
|
|
449
|
+
print(f"ShellLite v{version('shell-lite')}")
|
|
450
|
+
except Exception:
|
|
451
|
+
print("ShellLite v0.5.3.1")
|
|
447
452
|
elif cmd == "get":
|
|
448
453
|
if len(sys.argv) > 2:
|
|
449
454
|
package_name = sys.argv[2]
|
|
@@ -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':
|
|
@@ -19,17 +19,6 @@ shell_lite.egg-info/dependency_links.txt
|
|
|
19
19
|
shell_lite.egg-info/entry_points.txt
|
|
20
20
|
shell_lite.egg-info/requires.txt
|
|
21
21
|
shell_lite.egg-info/top_level.txt
|
|
22
|
-
shell_lite/llvm_backend/__init__.py
|
|
23
|
-
shell_lite/llvm_backend/builder.py
|
|
24
|
-
shell_lite/llvm_backend/codegen.py
|
|
25
|
-
tests/__init__.py
|
|
26
|
-
tests/benchmark_driver.py
|
|
27
|
-
tests/compare_parsers.py
|
|
28
|
-
tests/debug_jit.py
|
|
29
|
-
tests/generate_actual_graph.py
|
|
30
|
-
tests/generate_perf_graph.py
|
|
31
|
-
tests/generate_runtime_graph.py
|
|
32
|
-
tests/run_jit.py
|
|
33
22
|
tests/test_gbp_standalone.py
|
|
34
23
|
tests/test_interpreter.py
|
|
35
24
|
tests/test_lexer.py
|
|
File without changes
|
|
@@ -1,39 +0,0 @@
|
|
|
1
|
-
import llvmlite.binding as llvm
|
|
2
|
-
from .codegen import LLVMCompiler
|
|
3
|
-
from ..lexer import Lexer
|
|
4
|
-
from ..parser import Parser
|
|
5
|
-
import os
|
|
6
|
-
def build_llvm(filename: str):
|
|
7
|
-
print(f"Compiling {filename} with LLVM Backend...")
|
|
8
|
-
with open(filename, 'r', encoding='utf-8') as f:
|
|
9
|
-
source = f.read()
|
|
10
|
-
lexer = Lexer(source)
|
|
11
|
-
tokens = lexer.tokenize()
|
|
12
|
-
parser = Parser(tokens)
|
|
13
|
-
statements = parser.parse()
|
|
14
|
-
compiler = LLVMCompiler()
|
|
15
|
-
module = compiler.compile(statements)
|
|
16
|
-
llvm_ir = str(module)
|
|
17
|
-
print("\n--- Generated LLVM IR ---")
|
|
18
|
-
print(llvm_ir)
|
|
19
|
-
print("-------------------------\n")
|
|
20
|
-
ll_filename = os.path.splitext(filename)[0] + ".ll"
|
|
21
|
-
with open(ll_filename, 'w') as f:
|
|
22
|
-
f.write(llvm_ir)
|
|
23
|
-
print(f"[SUCCESS] Generated LLVM IR: {ll_filename}")
|
|
24
|
-
print("\nTo compile to executable, you can use Clang:")
|
|
25
|
-
print(f" clang {ll_filename} -o {os.path.splitext(filename)[0]}.exe")
|
|
26
|
-
"""
|
|
27
|
-
try:
|
|
28
|
-
print("Initializing LLVM...")
|
|
29
|
-
llvm.initialize()
|
|
30
|
-
llvm.initialize_native_target()
|
|
31
|
-
llvm.initialize_native_asmprinter()
|
|
32
|
-
print("LLVM Initialized.")
|
|
33
|
-
print("Creating Target...")
|
|
34
|
-
target = llvm.Target.from_default_triple()
|
|
35
|
-
print(f"Target Triple: {target}")
|
|
36
|
-
target_machine = target.create_target_machine()
|
|
37
|
-
except Exception as e:
|
|
38
|
-
print(f"Native binding skipped due to: {e}")
|
|
39
|
-
"""
|
|
@@ -1,311 +0,0 @@
|
|
|
1
|
-
from llvmlite import ir
|
|
2
|
-
from ..ast_nodes import *
|
|
3
|
-
class LLVMCompiler:
|
|
4
|
-
def __init__(self):
|
|
5
|
-
self.module = ir.Module(name="shell_lite_module")
|
|
6
|
-
self.module.triple = "x86_64-pc-windows-msvc" # Assume Windows for now based on user OS
|
|
7
|
-
self.int32 = ir.IntType(32)
|
|
8
|
-
self.char_ptr = ir.IntType(8).as_pointer()
|
|
9
|
-
voidptr_ty = ir.IntType(8).as_pointer()
|
|
10
|
-
printf_ty = ir.FunctionType(self.int32, [voidptr_ty], var_arg=True)
|
|
11
|
-
self.printf = ir.Function(self.module, printf_ty, name="printf")
|
|
12
|
-
malloc_ty = ir.FunctionType(voidptr_ty, [self.int32])
|
|
13
|
-
self.malloc = ir.Function(self.module, malloc_ty, name="malloc")
|
|
14
|
-
free_ty = ir.FunctionType(ir.VoidType(), [voidptr_ty])
|
|
15
|
-
self.free = ir.Function(self.module, free_ty, name="free")
|
|
16
|
-
strlen_ty = ir.FunctionType(self.int32, [voidptr_ty])
|
|
17
|
-
self.strlen = ir.Function(self.module, strlen_ty, name="strlen")
|
|
18
|
-
strcpy_ty = ir.FunctionType(voidptr_ty, [voidptr_ty, voidptr_ty])
|
|
19
|
-
self.strcpy = ir.Function(self.module, strcpy_ty, name="strcpy")
|
|
20
|
-
strcat_ty = ir.FunctionType(voidptr_ty, [voidptr_ty, voidptr_ty])
|
|
21
|
-
self.strcat = ir.Function(self.module, strcat_ty, name="strcat")
|
|
22
|
-
fopen_ty = ir.FunctionType(voidptr_ty, [voidptr_ty, voidptr_ty])
|
|
23
|
-
self.fopen = ir.Function(self.module, fopen_ty, name="fopen")
|
|
24
|
-
fclose_ty = ir.FunctionType(self.int32, [voidptr_ty])
|
|
25
|
-
self.fclose = ir.Function(self.module, fclose_ty, name="fclose")
|
|
26
|
-
fwrite_ty = ir.FunctionType(self.int32, [voidptr_ty, self.int32, self.int32, voidptr_ty])
|
|
27
|
-
self.fwrite = ir.Function(self.module, fwrite_ty, name="fwrite")
|
|
28
|
-
fread_ty = ir.FunctionType(self.int32, [voidptr_ty, self.int32, self.int32, voidptr_ty])
|
|
29
|
-
self.fread = ir.Function(self.module, fread_ty, name="fread")
|
|
30
|
-
fgets_ty = ir.FunctionType(voidptr_ty, [voidptr_ty, self.int32, voidptr_ty])
|
|
31
|
-
self.fgets = ir.Function(self.module, fgets_ty, name="fgets")
|
|
32
|
-
fseek_ty = ir.FunctionType(self.int32, [voidptr_ty, self.int32, self.int32])
|
|
33
|
-
self.fseek = ir.Function(self.module, fseek_ty, name="fseek")
|
|
34
|
-
ftell_ty = ir.FunctionType(self.int32, [voidptr_ty])
|
|
35
|
-
self.ftell = ir.Function(self.module, ftell_ty, name="ftell")
|
|
36
|
-
rewind_ty = ir.FunctionType(ir.VoidType(), [voidptr_ty])
|
|
37
|
-
self.rewind = ir.Function(self.module, rewind_ty, name="rewind")
|
|
38
|
-
get_stdin_ty = ir.FunctionType(voidptr_ty, [self.int32])
|
|
39
|
-
self.get_stdin = ir.Function(self.module, get_stdin_ty, name="__acrt_iob_func")
|
|
40
|
-
system_ty = ir.FunctionType(self.int32, [voidptr_ty])
|
|
41
|
-
self.system = ir.Function(self.module, system_ty, name="system")
|
|
42
|
-
func_type = ir.FunctionType(self.int32, [], var_arg=False)
|
|
43
|
-
self.main_func = ir.Function(self.module, func_type, name="main")
|
|
44
|
-
block = self.main_func.append_basic_block(name="entry")
|
|
45
|
-
self.builder = ir.IRBuilder(block)
|
|
46
|
-
self.scopes = [{}]
|
|
47
|
-
self.loop_stack = []
|
|
48
|
-
self.str_constants = {}
|
|
49
|
-
def _get_scope(self):
|
|
50
|
-
return self.scopes[-1]
|
|
51
|
-
def _alloca(self, name, typ=None):
|
|
52
|
-
if typ is None: typ = self.int32
|
|
53
|
-
with self.builder.goto_entry_block():
|
|
54
|
-
ptr = self.builder.alloca(typ, size=None, name=name)
|
|
55
|
-
return ptr
|
|
56
|
-
def compile(self, statements):
|
|
57
|
-
for stmt in statements:
|
|
58
|
-
self.visit(stmt)
|
|
59
|
-
self.builder.ret(ir.Constant(self.int32, 0))
|
|
60
|
-
return self.module
|
|
61
|
-
def visit(self, node):
|
|
62
|
-
method_name = f'visit_{type(node).__name__}'
|
|
63
|
-
visitor = getattr(self, method_name, self.generic_visit)
|
|
64
|
-
return visitor(node)
|
|
65
|
-
def generic_visit(self, node):
|
|
66
|
-
print(f"Warning: LLVM Backend does not support {type(node).__name__} yet.")
|
|
67
|
-
return None
|
|
68
|
-
def visit_Number(self, node: Number):
|
|
69
|
-
return ir.Constant(self.int32, int(node.value))
|
|
70
|
-
def visit_String(self, node: String):
|
|
71
|
-
return self._get_string_constant(node.value)
|
|
72
|
-
def visit_BinOp(self, node: BinOp):
|
|
73
|
-
left = self.visit(node.left)
|
|
74
|
-
right = self.visit(node.right)
|
|
75
|
-
op = node.op
|
|
76
|
-
if op == '+':
|
|
77
|
-
is_str_op = False
|
|
78
|
-
if left.type == self.char_ptr or right.type == self.char_ptr:
|
|
79
|
-
is_str_op = True
|
|
80
|
-
if is_str_op:
|
|
81
|
-
if left.type == self.int32:
|
|
82
|
-
left = self.builder.inttoptr(left, self.char_ptr, name="cast_l")
|
|
83
|
-
if right.type == self.int32:
|
|
84
|
-
right = self.builder.inttoptr(right, self.char_ptr, name="cast_r")
|
|
85
|
-
if is_str_op:
|
|
86
|
-
len1 = self.builder.call(self.strlen, [left], name="len1")
|
|
87
|
-
len2 = self.builder.call(self.strlen, [right], name="len2")
|
|
88
|
-
total_len = self.builder.add(len1, len2, name="total_len")
|
|
89
|
-
total_len_null = self.builder.add(total_len, ir.Constant(self.int32, 1), name="alloc_len")
|
|
90
|
-
new_str = self.builder.call(self.malloc, [total_len_null], name="new_str")
|
|
91
|
-
self.builder.call(self.strcpy, [new_str, left])
|
|
92
|
-
self.builder.call(self.strcat, [new_str, right])
|
|
93
|
-
return new_str
|
|
94
|
-
return self.builder.add(left, right, name="addtmp")
|
|
95
|
-
elif op == '-':
|
|
96
|
-
return self.builder.sub(left, right, name="subtmp")
|
|
97
|
-
elif op == '*':
|
|
98
|
-
return self.builder.mul(left, right, name="multmp")
|
|
99
|
-
elif op == '/':
|
|
100
|
-
return self.builder.sdiv(left, right, name="divtmp")
|
|
101
|
-
elif op == '==' or op == 'is':
|
|
102
|
-
return self.builder.icmp_signed('==', left, right, name="eqtmp")
|
|
103
|
-
elif op == '!=' or op == 'is not':
|
|
104
|
-
return self.builder.icmp_signed('!=', left, right, name="netmp")
|
|
105
|
-
elif op == '<':
|
|
106
|
-
return self.builder.icmp_signed('<', left, right, name="lttmp")
|
|
107
|
-
elif op == '<=':
|
|
108
|
-
return self.builder.icmp_signed('<=', left, right, name="letmp")
|
|
109
|
-
elif op == '>':
|
|
110
|
-
return self.builder.icmp_signed('>', left, right, name="gttmp")
|
|
111
|
-
elif op == '>=':
|
|
112
|
-
return self.builder.icmp_signed('>=', left, right, name="getmp")
|
|
113
|
-
else:
|
|
114
|
-
raise Exception(f"Unknown operator: {op}")
|
|
115
|
-
def visit_If(self, node: If):
|
|
116
|
-
cond_val = self.visit(node.condition)
|
|
117
|
-
if cond_val.type != ir.IntType(1):
|
|
118
|
-
cond_val = self.builder.icmp_signed('!=', cond_val, ir.Constant(self.int32, 0), name="ifcond")
|
|
119
|
-
then_bb = self.builder.append_basic_block(name="then")
|
|
120
|
-
else_bb = self.builder.append_basic_block(name="else")
|
|
121
|
-
merge_bb = self.builder.append_basic_block(name="ifcont")
|
|
122
|
-
self.builder.cbranch(cond_val, then_bb, else_bb)
|
|
123
|
-
self.builder.position_at_end(then_bb)
|
|
124
|
-
for stmt in node.body:
|
|
125
|
-
self.visit(stmt)
|
|
126
|
-
if not self.builder.block.is_terminated:
|
|
127
|
-
self.builder.branch(merge_bb)
|
|
128
|
-
self.builder.position_at_end(else_bb)
|
|
129
|
-
if node.else_body:
|
|
130
|
-
for stmt in node.else_body:
|
|
131
|
-
self.visit(stmt)
|
|
132
|
-
if not self.builder.block.is_terminated:
|
|
133
|
-
self.builder.branch(merge_bb)
|
|
134
|
-
self.builder.position_at_end(merge_bb)
|
|
135
|
-
def visit_While(self, node: While):
|
|
136
|
-
cond_bb = self.builder.append_basic_block(name="loop.cond")
|
|
137
|
-
body_bb = self.builder.append_basic_block(name="loop.body")
|
|
138
|
-
after_bb = self.builder.append_basic_block(name="loop.after")
|
|
139
|
-
self.loop_stack.append((cond_bb, after_bb))
|
|
140
|
-
self.builder.branch(cond_bb)
|
|
141
|
-
self.builder.position_at_end(cond_bb)
|
|
142
|
-
cond_val = self.visit(node.condition)
|
|
143
|
-
if cond_val.type != ir.IntType(1):
|
|
144
|
-
cond_val = self.builder.icmp_signed('!=', cond_val, ir.Constant(self.int32, 0), name="loopcond")
|
|
145
|
-
self.builder.cbranch(cond_val, body_bb, after_bb)
|
|
146
|
-
self.builder.position_at_end(body_bb)
|
|
147
|
-
for stmt in node.body:
|
|
148
|
-
self.visit(stmt)
|
|
149
|
-
self.builder.branch(cond_bb) # Loop back
|
|
150
|
-
self.builder.position_at_end(after_bb)
|
|
151
|
-
self.loop_stack.pop()
|
|
152
|
-
def visit_Repeat(self, node: Repeat):
|
|
153
|
-
count_val = self.visit(node.count)
|
|
154
|
-
import random
|
|
155
|
-
uid = random.randint(0, 10000)
|
|
156
|
-
i_ptr = self._alloca(f"_loop_i_{uid}")
|
|
157
|
-
self.builder.store(ir.Constant(self.int32, 0), i_ptr)
|
|
158
|
-
cond_bb = self.builder.append_basic_block(name="repeat.cond")
|
|
159
|
-
body_bb = self.builder.append_basic_block(name="repeat.body")
|
|
160
|
-
after_bb = self.builder.append_basic_block(name="repeat.after")
|
|
161
|
-
self.loop_stack.append((cond_bb, after_bb))
|
|
162
|
-
self.builder.branch(cond_bb)
|
|
163
|
-
self.builder.position_at_end(cond_bb)
|
|
164
|
-
curr_i = self.builder.load(i_ptr, name="i_load")
|
|
165
|
-
cmp = self.builder.icmp_signed('<', curr_i, count_val, name="loopcheck")
|
|
166
|
-
self.builder.cbranch(cmp, body_bb, after_bb)
|
|
167
|
-
self.builder.position_at_end(body_bb)
|
|
168
|
-
for stmt in node.body:
|
|
169
|
-
self.visit(stmt)
|
|
170
|
-
curr_i_Body = self.builder.load(i_ptr)
|
|
171
|
-
next_i = self.builder.add(curr_i_Body, ir.Constant(self.int32, 1), name="inc_i")
|
|
172
|
-
self.builder.store(next_i, i_ptr)
|
|
173
|
-
self.builder.branch(cond_bb)
|
|
174
|
-
self.builder.position_at_end(after_bb)
|
|
175
|
-
self.loop_stack.pop()
|
|
176
|
-
def visit_FunctionDef(self, node: FunctionDef):
|
|
177
|
-
arg_types = [self.int32] * len(node.args)
|
|
178
|
-
arg_types = []
|
|
179
|
-
for arg in node.args:
|
|
180
|
-
name, _, hint = arg # arg is tuple
|
|
181
|
-
if True: # FORCE ALL ARGS TO STRINGS FOR SHELL LITE (Phase 7 Fix)
|
|
182
|
-
arg_types.append(self.char_ptr)
|
|
183
|
-
else:
|
|
184
|
-
arg_types.append(self.int32)
|
|
185
|
-
func_ty = ir.FunctionType(self.int32, arg_types) # Return int32 (or pointer casted)
|
|
186
|
-
func = ir.Function(self.module, func_ty, name=node.name)
|
|
187
|
-
old_builder = self.builder
|
|
188
|
-
block = func.append_basic_block(name="entry")
|
|
189
|
-
self.builder = ir.IRBuilder(block)
|
|
190
|
-
self.scopes.append({}) # New scope
|
|
191
|
-
for i, arg in enumerate(func.args):
|
|
192
|
-
arg_name = node.args[i][0]
|
|
193
|
-
arg.name = arg_name
|
|
194
|
-
ptr = self.builder.alloca(arg.type, name=arg_name)
|
|
195
|
-
self.builder.store(arg, ptr)
|
|
196
|
-
self.scopes[-1][arg_name] = ptr
|
|
197
|
-
for stmt in node.body:
|
|
198
|
-
self.visit(stmt)
|
|
199
|
-
if not self.builder.block.is_terminated:
|
|
200
|
-
self.builder.ret(ir.Constant(self.int32, 0))
|
|
201
|
-
self.scopes.pop()
|
|
202
|
-
self.builder = old_builder
|
|
203
|
-
def visit_Return(self, node: Return):
|
|
204
|
-
val = self.visit(node.value)
|
|
205
|
-
if val.type == self.char_ptr:
|
|
206
|
-
val = self.builder.ptrtoint(val, self.int32)
|
|
207
|
-
self.builder.ret(val)
|
|
208
|
-
def visit_Call(self, node: Call):
|
|
209
|
-
if node.name in self.module.globals:
|
|
210
|
-
func = self.module.globals[node.name]
|
|
211
|
-
elif node.name == 'read':
|
|
212
|
-
if len(node.args) > 0:
|
|
213
|
-
return self.visit_FileRead(FileRead(node.args[0]))
|
|
214
|
-
else:
|
|
215
|
-
return ir.Constant(self.int32, 0)
|
|
216
|
-
else:
|
|
217
|
-
print(f"Warning: Function {node.name} not found")
|
|
218
|
-
return ir.Constant(self.int32, 0)
|
|
219
|
-
args = [self.visit(a) for a in node.args]
|
|
220
|
-
return self.builder.call(func, args, name="calltmp")
|
|
221
|
-
def visit_Assign(self, node: Assign):
|
|
222
|
-
value = self.visit(node.value)
|
|
223
|
-
scope = self._get_scope()
|
|
224
|
-
if node.name not in scope:
|
|
225
|
-
ptr = self._alloca(node.name, typ=value.type)
|
|
226
|
-
scope[node.name] = ptr
|
|
227
|
-
else:
|
|
228
|
-
ptr = scope[node.name]
|
|
229
|
-
self.builder.store(value, ptr)
|
|
230
|
-
return value
|
|
231
|
-
def visit_Execute(self, node: Execute):
|
|
232
|
-
cmd = self.visit(node.code)
|
|
233
|
-
self.builder.call(self.system, [cmd])
|
|
234
|
-
return ir.Constant(self.int32, 0)
|
|
235
|
-
def visit_Stop(self, node: Stop):
|
|
236
|
-
if not self.loop_stack:
|
|
237
|
-
print("Error: stop used outside of loop")
|
|
238
|
-
return
|
|
239
|
-
after_bb = self.loop_stack[-1][1]
|
|
240
|
-
self.builder.branch(after_bb)
|
|
241
|
-
dead_bb = self.builder.append_basic_block(name="dead")
|
|
242
|
-
self.builder.position_at_end(dead_bb)
|
|
243
|
-
def visit_Skip(self, node: Skip):
|
|
244
|
-
if not self.loop_stack:
|
|
245
|
-
print("Error: skip used outside of loop")
|
|
246
|
-
return
|
|
247
|
-
cond_bb = self.loop_stack[-1][0]
|
|
248
|
-
self.builder.branch(cond_bb)
|
|
249
|
-
dead_bb = self.builder.append_basic_block(name="dead")
|
|
250
|
-
self.builder.position_at_end(dead_bb)
|
|
251
|
-
def _get_stdin_handle(self):
|
|
252
|
-
return self.builder.call(self.get_stdin, [ir.Constant(self.int32, 0)])
|
|
253
|
-
def visit_Input(self, node: Input):
|
|
254
|
-
buffer_len = ir.Constant(self.int32, 256)
|
|
255
|
-
buffer = self.builder.call(self.malloc, [buffer_len], name="input_buf")
|
|
256
|
-
stdin = self._get_stdin_handle()
|
|
257
|
-
self.builder.call(self.fgets, [buffer, buffer_len, stdin])
|
|
258
|
-
return buffer
|
|
259
|
-
def visit_FileWrite(self, node: FileWrite):
|
|
260
|
-
path = self.visit(node.path)
|
|
261
|
-
content = self.visit(node.content)
|
|
262
|
-
mode = self._get_string_constant("w")
|
|
263
|
-
fp = self.builder.call(self.fopen, [path, mode], name="fp")
|
|
264
|
-
length = self.builder.call(self.strlen, [content])
|
|
265
|
-
self.builder.call(self.fwrite, [content, ir.Constant(self.int32, 1), length, fp])
|
|
266
|
-
self.builder.call(self.fclose, [fp])
|
|
267
|
-
return ir.Constant(self.int32, 0)
|
|
268
|
-
def visit_FileRead(self, node: FileRead):
|
|
269
|
-
path = self.visit(node.path)
|
|
270
|
-
mode = self._get_string_constant("rb") # Binary to avoid text translation issues? Or "r"
|
|
271
|
-
fp = self.builder.call(self.fopen, [path, mode], name="fp")
|
|
272
|
-
self.builder.call(self.fseek, [fp, ir.Constant(self.int32, 0), ir.Constant(self.int32, 2)])
|
|
273
|
-
size = self.builder.call(self.ftell, [fp], name="fsize")
|
|
274
|
-
self.builder.call(self.rewind, [fp])
|
|
275
|
-
alloc_size = self.builder.add(size, ir.Constant(self.int32, 1))
|
|
276
|
-
buffer = self.builder.call(self.malloc, [alloc_size], name="fbuf")
|
|
277
|
-
self.builder.call(self.fread, [buffer, ir.Constant(self.int32, 1), size, fp])
|
|
278
|
-
null_term_ptr = self.builder.gep(buffer, [size])
|
|
279
|
-
self.builder.store(ir.Constant(ir.IntType(8), 0), null_term_ptr)
|
|
280
|
-
self.builder.call(self.fclose, [fp])
|
|
281
|
-
return buffer
|
|
282
|
-
def visit_VarAccess(self, node: VarAccess):
|
|
283
|
-
scope = self._get_scope()
|
|
284
|
-
if node.name in scope:
|
|
285
|
-
ptr = scope[node.name]
|
|
286
|
-
return self.builder.load(ptr, name=node.name)
|
|
287
|
-
else:
|
|
288
|
-
raise Exception(f"Variable '{node.name}' not defined")
|
|
289
|
-
def _get_string_constant(self, text):
|
|
290
|
-
text += '\0'
|
|
291
|
-
if text in self.str_constants:
|
|
292
|
-
return self.str_constants[text]
|
|
293
|
-
byte_arr = bytearray(text.encode("utf8"))
|
|
294
|
-
c_str_ty = ir.ArrayType(ir.IntType(8), len(byte_arr))
|
|
295
|
-
global_var = ir.GlobalVariable(self.module, c_str_ty, name=f".str_{len(self.str_constants)}")
|
|
296
|
-
global_var.linkage = 'internal'
|
|
297
|
-
global_var.global_constant = True
|
|
298
|
-
global_var.initializer = ir.Constant(c_str_ty, byte_arr)
|
|
299
|
-
ptr = self.builder.bitcast(global_var, self.char_ptr)
|
|
300
|
-
self.str_constants[text] = ptr
|
|
301
|
-
return ptr
|
|
302
|
-
def visit_Print(self, node: Print):
|
|
303
|
-
value = self.visit(node.expression)
|
|
304
|
-
if value.type == self.char_ptr:
|
|
305
|
-
fmt_str = self._get_string_constant("%s\n")
|
|
306
|
-
self.builder.call(self.printf, [fmt_str, value])
|
|
307
|
-
else:
|
|
308
|
-
if value.type != self.int32:
|
|
309
|
-
pass
|
|
310
|
-
fmt_str = self._get_string_constant("%d\n")
|
|
311
|
-
self.builder.call(self.printf, [fmt_str, value])
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
|
|
@@ -1,43 +0,0 @@
|
|
|
1
|
-
import os
|
|
2
|
-
import time
|
|
3
|
-
import subprocess
|
|
4
|
-
import sys
|
|
5
|
-
SHL_CMD = ["python", "shell_lite/main.py"]
|
|
6
|
-
def run_benchmark():
|
|
7
|
-
print("Generating LLVM IR...")
|
|
8
|
-
subprocess.run(SHL_CMD + ["llvm", "tests/runtime_benchmark.shl"], check=True)
|
|
9
|
-
print("Compiling with Clang...")
|
|
10
|
-
subprocess.run(["clang", "tests/runtime_benchmark.ll", "-o", "tests/runtime_benchmark.exe", "-O3"], check=True)
|
|
11
|
-
print("\n[Running Native Executable...]")
|
|
12
|
-
start = time.perf_counter()
|
|
13
|
-
subprocess.run(["tests/runtime_benchmark.exe"], check=True)
|
|
14
|
-
native_time = time.perf_counter() - start
|
|
15
|
-
print(f"Native Time: {native_time:.4f}s")
|
|
16
|
-
print("\n[Running Interpreter (GBP)...]")
|
|
17
|
-
env = os.environ.copy()
|
|
18
|
-
env["USE_GBP"] = "1"
|
|
19
|
-
start = time.perf_counter()
|
|
20
|
-
subprocess.run(SHL_CMD + ["run", "tests/runtime_benchmark.shl"], env=env, check=True)
|
|
21
|
-
interp_time = time.perf_counter() - start
|
|
22
|
-
print(f"Interpreter Time: {interp_time:.4f}s")
|
|
23
|
-
print("\n[Running Python Equivalent...]")
|
|
24
|
-
py_code = """
|
|
25
|
-
i = 0
|
|
26
|
-
sum = 0
|
|
27
|
-
count = 100000000
|
|
28
|
-
while i < count:
|
|
29
|
-
sum = sum + 1
|
|
30
|
-
i = i + 1
|
|
31
|
-
print("Done")
|
|
32
|
-
print(sum)
|
|
33
|
-
"""
|
|
34
|
-
start = time.perf_counter()
|
|
35
|
-
exec(py_code)
|
|
36
|
-
py_time = time.perf_counter() - start
|
|
37
|
-
print(f"Python Time: {py_time:.4f}s")
|
|
38
|
-
print("\n" + "="*30)
|
|
39
|
-
print(f"Native Speedup vs Interpreter: {interp_time/native_time:.2f}x")
|
|
40
|
-
print(f"Native Speedup vs Python: {py_time/native_time:.2f}x")
|
|
41
|
-
print("="*30)
|
|
42
|
-
if __name__ == "__main__":
|
|
43
|
-
run_benchmark()
|
|
@@ -1,31 +0,0 @@
|
|
|
1
|
-
import sys
|
|
2
|
-
import os
|
|
3
|
-
import time
|
|
4
|
-
sys.path.append(os.path.join(os.path.dirname(__file__), '..'))
|
|
5
|
-
from shell_lite.lexer import Lexer
|
|
6
|
-
from shell_lite.parser import Parser
|
|
7
|
-
from shell_lite.parser_gbp import GeometricBindingParser
|
|
8
|
-
def benchmark(filename):
|
|
9
|
-
with open(filename, 'r') as f:
|
|
10
|
-
source = f.read()
|
|
11
|
-
long_source = source * 500
|
|
12
|
-
print(f"Benchmarking on {len(long_source)} chars of code...")
|
|
13
|
-
lexer = Lexer(long_source)
|
|
14
|
-
tokens = lexer.tokenize()
|
|
15
|
-
tokens_copy = list(tokens)
|
|
16
|
-
start = time.perf_counter()
|
|
17
|
-
p_old = Parser(list(tokens)) # fresh copy? Parser consumes? yes
|
|
18
|
-
ast_old = p_old.parse()
|
|
19
|
-
end = time.perf_counter()
|
|
20
|
-
t_old = end - start
|
|
21
|
-
print(f"Recursive Descent: {t_old:.4f}s")
|
|
22
|
-
start = time.perf_counter()
|
|
23
|
-
p_new = GeometricBindingParser(list(tokens))
|
|
24
|
-
ast_new = p_new.parse()
|
|
25
|
-
end = time.perf_counter()
|
|
26
|
-
t_new = end - start
|
|
27
|
-
print(f"Geometric-Binding: {t_new:.4f}s")
|
|
28
|
-
diff = t_old / t_new if t_new > 0 else 0
|
|
29
|
-
print(f"Speedup: {diff:.2f}x")
|
|
30
|
-
if __name__ == "__main__":
|
|
31
|
-
benchmark("tests/benchmark.shl")
|
|
@@ -1,49 +0,0 @@
|
|
|
1
|
-
import llvmlite.binding as llvm
|
|
2
|
-
import sys
|
|
3
|
-
def debug():
|
|
4
|
-
try:
|
|
5
|
-
llvm.initialize()
|
|
6
|
-
llvm.initialize_native_target()
|
|
7
|
-
llvm.initialize_native_asmprinter()
|
|
8
|
-
print("LLVM Initialized.")
|
|
9
|
-
except Exception as e:
|
|
10
|
-
print(f"Init Failed: {e}")
|
|
11
|
-
return
|
|
12
|
-
print("Available in llvm.binding:")
|
|
13
|
-
ops = [x for x in dir(llvm) if 'create' in x or 'jit' in x.lower() or 'engine' in x.lower()]
|
|
14
|
-
print(ops)
|
|
15
|
-
try:
|
|
16
|
-
mod = llvm.parse_assembly('define i32 @answer() { ret i32 42 }')
|
|
17
|
-
mod.verify()
|
|
18
|
-
print("Module parsed.")
|
|
19
|
-
target = llvm.Target.from_default_triple()
|
|
20
|
-
target_machine = target.create_target_machine()
|
|
21
|
-
engine = None
|
|
22
|
-
if hasattr(llvm, 'create_mcjit_compiler'):
|
|
23
|
-
print("Attempting MCJIT...")
|
|
24
|
-
try:
|
|
25
|
-
engine = llvm.create_mcjit_compiler(mod, target_machine)
|
|
26
|
-
print("MCJIT Created!")
|
|
27
|
-
except Exception as e:
|
|
28
|
-
print(f"MCJIT Failed: {e}")
|
|
29
|
-
if not engine and hasattr(llvm, 'create_execution_engine'):
|
|
30
|
-
print("Attempting create_execution_engine...")
|
|
31
|
-
try:
|
|
32
|
-
engine = llvm.create_execution_engine()
|
|
33
|
-
engine.add_module(mod)
|
|
34
|
-
print("ExecEngine Created!")
|
|
35
|
-
except Exception as e:
|
|
36
|
-
print(f"ExecEngine Failed: {e}")
|
|
37
|
-
if engine:
|
|
38
|
-
engine.finalize_object()
|
|
39
|
-
print("Engine Finalized.")
|
|
40
|
-
addr = engine.get_function_address("answer")
|
|
41
|
-
print(f"Function Address: {addr}")
|
|
42
|
-
import ctypes
|
|
43
|
-
cfunc = ctypes.CFUNCTYPE(ctypes.c_int)(addr)
|
|
44
|
-
res = cfunc()
|
|
45
|
-
print(f"Result: {res}")
|
|
46
|
-
except Exception as e:
|
|
47
|
-
print(f"Module/Engine Error: {e}")
|
|
48
|
-
if __name__ == "__main__":
|
|
49
|
-
debug()
|
|
@@ -1,84 +0,0 @@
|
|
|
1
|
-
import sys
|
|
2
|
-
import os
|
|
3
|
-
import time
|
|
4
|
-
import matplotlib.pyplot as plt
|
|
5
|
-
import subprocess
|
|
6
|
-
import llvmlite.binding as llvm
|
|
7
|
-
sys.path.append(os.path.join(os.path.dirname(__file__), '..'))
|
|
8
|
-
try:
|
|
9
|
-
from tests.run_jit import compile_and_run_jit
|
|
10
|
-
except ImportError:
|
|
11
|
-
sys.path.append(os.path.join(os.path.dirname(__file__)))
|
|
12
|
-
from run_jit import compile_and_run_jit
|
|
13
|
-
def run_benchmark():
|
|
14
|
-
counts = [1000000, 10000000, 50000000, 100000000, 200000000]
|
|
15
|
-
t_interp = []
|
|
16
|
-
t_python = []
|
|
17
|
-
t_llvm = []
|
|
18
|
-
print("Running Massive Runtime Benchmark...")
|
|
19
|
-
for n in counts:
|
|
20
|
-
print(f"\n--- n = {n} ---")
|
|
21
|
-
if n <= 100000: # Reduced limit for massive scale run
|
|
22
|
-
shl_code = f"i = 0\nwhile i < {n}:\n i = i + 1\n"
|
|
23
|
-
shl_file = f"tests/temp_{n}.shl"
|
|
24
|
-
with open(shl_file, "w") as f: f.write(shl_code)
|
|
25
|
-
env = os.environ.copy()
|
|
26
|
-
env["USE_GBP"] = "1"
|
|
27
|
-
start = time.perf_counter()
|
|
28
|
-
try:
|
|
29
|
-
subprocess.run(["python", "shell_lite/main.py", "run", shl_file], env=env, check=True, stdout=subprocess.DEVNULL, stderr=subprocess.PIPE)
|
|
30
|
-
dur = time.perf_counter() - start
|
|
31
|
-
except subprocess.CalledProcessError as e:
|
|
32
|
-
print(f"Interp failed: {e.stderr.decode()}")
|
|
33
|
-
dur = None
|
|
34
|
-
except Exception as e:
|
|
35
|
-
print(f"Interp failed: {e}")
|
|
36
|
-
dur = None
|
|
37
|
-
t_interp.append(dur)
|
|
38
|
-
if os.path.exists(shl_file): os.remove(shl_file)
|
|
39
|
-
if dur is not None:
|
|
40
|
-
print(f"Interpreter: {dur:.4f}s")
|
|
41
|
-
else:
|
|
42
|
-
print("Interpreter: Failed")
|
|
43
|
-
else:
|
|
44
|
-
t_interp.append(None)
|
|
45
|
-
print("Interpreter: Skipped (Too Slow)")
|
|
46
|
-
start = time.perf_counter()
|
|
47
|
-
i = 0
|
|
48
|
-
while i < n:
|
|
49
|
-
i += 1
|
|
50
|
-
dur = time.perf_counter() - start
|
|
51
|
-
t_python.append(dur)
|
|
52
|
-
print(f"Python: {dur:.6f}s")
|
|
53
|
-
jit_code = f"""
|
|
54
|
-
i = 0
|
|
55
|
-
count = {n}
|
|
56
|
-
while i < count:
|
|
57
|
-
i = i + 1
|
|
58
|
-
"""
|
|
59
|
-
try:
|
|
60
|
-
_, dur, _ = compile_and_run_jit(jit_code)
|
|
61
|
-
if dur < 1e-7: dur = 1e-7
|
|
62
|
-
t_llvm.append(dur)
|
|
63
|
-
print(f"LLVM JIT: {dur:.8f}s")
|
|
64
|
-
except Exception as e:
|
|
65
|
-
print(f"JIT Failed: {e}")
|
|
66
|
-
t_llvm.append(None)
|
|
67
|
-
plt.figure(figsize=(10, 6))
|
|
68
|
-
x_interp = [x for x, y in zip(counts, t_interp) if y is not None]
|
|
69
|
-
y_interp = [y for y in t_interp if y is not None]
|
|
70
|
-
if x_interp:
|
|
71
|
-
plt.plot(x_interp, y_interp, label='ShellLite Interpreter', marker='o', color='orange')
|
|
72
|
-
plt.plot(counts, t_python, label='Python Native', marker='s', color='green')
|
|
73
|
-
plt.plot(counts, t_llvm, label='LLVM JIT', marker='^', color='purple', linestyle='-')
|
|
74
|
-
plt.xlabel('Iterations')
|
|
75
|
-
plt.ylabel('Time (seconds)')
|
|
76
|
-
plt.title('GBP+LLVM Runtime Performance (Massive Scale)')
|
|
77
|
-
plt.yscale('log')
|
|
78
|
-
plt.legend()
|
|
79
|
-
plt.grid(True, which="both", ls="-", alpha=0.2)
|
|
80
|
-
output_path = os.path.abspath(os.path.join(os.path.dirname(__file__), '..', '..', 'benchmark_final.png'))
|
|
81
|
-
plt.savefig(output_path)
|
|
82
|
-
print(f"Graph saved to {output_path}")
|
|
83
|
-
if __name__ == "__main__":
|
|
84
|
-
run_benchmark()
|
|
@@ -1,68 +0,0 @@
|
|
|
1
|
-
import sys
|
|
2
|
-
import os
|
|
3
|
-
import time
|
|
4
|
-
import matplotlib.pyplot as plt
|
|
5
|
-
sys.path.append(os.path.join(os.path.dirname(__file__), '..'))
|
|
6
|
-
from shell_lite.lexer import Lexer
|
|
7
|
-
from shell_lite.parser import Parser
|
|
8
|
-
from shell_lite.parser_gbp import GeometricBindingParser
|
|
9
|
-
HAS_LLVM = False
|
|
10
|
-
try:
|
|
11
|
-
from shell_lite.llvm_backend.codegen import LLVMCompiler
|
|
12
|
-
HAS_LLVM = True
|
|
13
|
-
except ImportError:
|
|
14
|
-
print("LLVM backend (llvmlite) not found. Skipping LLVM benchmark.")
|
|
15
|
-
def generate_large_file(lines=1000):
|
|
16
|
-
code = "x = 0\n"
|
|
17
|
-
for i in range(lines):
|
|
18
|
-
code += f"if x < {i}:\n"
|
|
19
|
-
code += f" x = x + 1\n"
|
|
20
|
-
return code
|
|
21
|
-
def run_benchmark():
|
|
22
|
-
sizes = [1000, 5000, 10000, 20000, 50000]
|
|
23
|
-
times_old = []
|
|
24
|
-
times_gbp = []
|
|
25
|
-
times_py = []
|
|
26
|
-
times_llvm = []
|
|
27
|
-
for size in sizes:
|
|
28
|
-
print(f"Benchmarking size: {size} lines...")
|
|
29
|
-
source_shl = generate_large_file(size)
|
|
30
|
-
source_py = source_shl.replace("x = x + 1", "x += 1")
|
|
31
|
-
start = time.perf_counter()
|
|
32
|
-
l = Lexer(source_shl)
|
|
33
|
-
toks = l.tokenize()
|
|
34
|
-
Parser(toks).parse()
|
|
35
|
-
times_old.append(time.perf_counter() - start)
|
|
36
|
-
start = time.perf_counter()
|
|
37
|
-
l = Lexer(source_shl)
|
|
38
|
-
toks = l.tokenize()
|
|
39
|
-
GeometricBindingParser(toks).parse()
|
|
40
|
-
times_gbp.append(time.perf_counter() - start)
|
|
41
|
-
start = time.perf_counter()
|
|
42
|
-
compile(source_py, '<string>', 'exec')
|
|
43
|
-
times_py.append(time.perf_counter() - start)
|
|
44
|
-
start = time.perf_counter()
|
|
45
|
-
if HAS_LLVM:
|
|
46
|
-
l = Lexer(source_shl)
|
|
47
|
-
toks = l.tokenize()
|
|
48
|
-
ast = GeometricBindingParser(toks).parse()
|
|
49
|
-
LLVMCompiler().compile(ast)
|
|
50
|
-
times_llvm.append(time.perf_counter() - start)
|
|
51
|
-
else:
|
|
52
|
-
times_llvm.append(0)
|
|
53
|
-
plt.figure(figsize=(10, 6))
|
|
54
|
-
plt.plot(sizes, times_old, label='Old Parser', marker='o', color='red')
|
|
55
|
-
plt.plot(sizes, times_gbp, label='GBP Parser', marker='s', color='blue')
|
|
56
|
-
plt.plot(sizes, times_py, label='Python Native', marker='^', color='green')
|
|
57
|
-
if HAS_LLVM and any(times_llvm):
|
|
58
|
-
plt.plot(sizes, times_llvm, label='LLVM Compile (via GBP)', marker='x', color='purple', linestyle='--')
|
|
59
|
-
plt.xlabel('Lines of Code')
|
|
60
|
-
plt.ylabel('Time (seconds)')
|
|
61
|
-
plt.title('Parsing/Compilation Speed vs Code Size')
|
|
62
|
-
plt.legend()
|
|
63
|
-
plt.grid(True)
|
|
64
|
-
output_path = os.path.join(os.path.dirname(__file__), 'benchmark_scaling.png')
|
|
65
|
-
plt.savefig(output_path)
|
|
66
|
-
print(f"Graph saved to {output_path}")
|
|
67
|
-
if __name__ == "__main__":
|
|
68
|
-
run_benchmark()
|
|
@@ -1,58 +0,0 @@
|
|
|
1
|
-
import sys
|
|
2
|
-
import os
|
|
3
|
-
import time
|
|
4
|
-
import matplotlib.pyplot as plt
|
|
5
|
-
import subprocess
|
|
6
|
-
sys.path.append(os.path.join(os.path.dirname(__file__), '..'))
|
|
7
|
-
def run_benchmark():
|
|
8
|
-
iterations = 1000000 # 1 Million
|
|
9
|
-
print(f"Benchmarking Runtime (Count: {iterations})...")
|
|
10
|
-
shl_code = f"""
|
|
11
|
-
i = 0
|
|
12
|
-
while i < {iterations}:
|
|
13
|
-
i = i + 1
|
|
14
|
-
"""
|
|
15
|
-
shl_file = "tests/temp_loop.shl"
|
|
16
|
-
with open(shl_file, "w") as f:
|
|
17
|
-
f.write(shl_code)
|
|
18
|
-
env = os.environ.copy()
|
|
19
|
-
env["USE_GBP"] = "1"
|
|
20
|
-
start = time.perf_counter()
|
|
21
|
-
try:
|
|
22
|
-
subprocess.run(["python", "shell_lite/main.py", "run", shl_file], env=env, check=True, stdout=subprocess.DEVNULL, stderr=subprocess.PIPE)
|
|
23
|
-
except subprocess.CalledProcessError as e:
|
|
24
|
-
print(f"Interpreter Crash:\n{e.stderr.decode()}")
|
|
25
|
-
t_interp = 10.0 # Dummy
|
|
26
|
-
else:
|
|
27
|
-
t_interp = time.perf_counter() - start
|
|
28
|
-
print(f"Interpreter: {t_interp:.4f}s")
|
|
29
|
-
start = time.perf_counter()
|
|
30
|
-
i = 0
|
|
31
|
-
while i < iterations:
|
|
32
|
-
i = i + 1
|
|
33
|
-
t_python = time.perf_counter() - start
|
|
34
|
-
print(f"Python: {t_python:.4f}s")
|
|
35
|
-
t_llvm_est = t_python / 20.0
|
|
36
|
-
print(f"LLVM (Est): {t_llvm_est:.4f}s")
|
|
37
|
-
labels = ['ShellLite Interpreter', 'Python Native', 'LLVM Native (Projected)']
|
|
38
|
-
times = [t_interp, t_python, t_llvm_est]
|
|
39
|
-
colors = ['orange', 'green', 'purple']
|
|
40
|
-
plt.figure(figsize=(10, 6))
|
|
41
|
-
bars = plt.bar(labels, times, color=colors)
|
|
42
|
-
plt.ylabel('Execution Time (seconds)')
|
|
43
|
-
plt.title(f'Runtime Speed Comparison ({iterations} Iterations)')
|
|
44
|
-
plt.yscale('log') # Log scale because difference is massive
|
|
45
|
-
for bar in bars:
|
|
46
|
-
height = bar.get_height()
|
|
47
|
-
plt.text(bar.get_x() + bar.get_width()/2., height,
|
|
48
|
-
f'{height:.4f}s',
|
|
49
|
-
ha='center', va='bottom')
|
|
50
|
-
plt.figtext(0.5, 0.01,
|
|
51
|
-
"Note: Logarithmic Scale used due to massive speed difference.",
|
|
52
|
-
ha="center", fontsize=10)
|
|
53
|
-
output_path = os.path.join(os.path.dirname(__file__), 'benchmark_runtime.png')
|
|
54
|
-
plt.savefig(output_path)
|
|
55
|
-
print(f"Graph saved to {output_path}")
|
|
56
|
-
if os.path.exists(shl_file): os.remove(shl_file)
|
|
57
|
-
if __name__ == "__main__":
|
|
58
|
-
run_benchmark()
|
|
@@ -1,70 +0,0 @@
|
|
|
1
|
-
import sys
|
|
2
|
-
import os
|
|
3
|
-
import time
|
|
4
|
-
import ctypes
|
|
5
|
-
import llvmlite.binding as llvm
|
|
6
|
-
try:
|
|
7
|
-
from llvmlite.binding.orcjit import create_lljit_compiler, JITLibraryBuilder
|
|
8
|
-
except ImportError:
|
|
9
|
-
import llvmlite.binding.orcjit as orc
|
|
10
|
-
create_lljit_compiler = orc.create_lljit_compiler
|
|
11
|
-
JITLibraryBuilder = orc.JITLibraryBuilder
|
|
12
|
-
sys.path.append(os.path.join(os.path.dirname(__file__), '..'))
|
|
13
|
-
from shell_lite.lexer import Lexer
|
|
14
|
-
from shell_lite.parser_gbp import GeometricBindingParser
|
|
15
|
-
from shell_lite.llvm_backend.codegen import LLVMCompiler
|
|
16
|
-
def compile_and_run_jit(source_code):
|
|
17
|
-
llvm.initialize_native_target()
|
|
18
|
-
llvm.initialize_native_asmprinter()
|
|
19
|
-
start_compile = time.perf_counter()
|
|
20
|
-
lexer = Lexer(source_code)
|
|
21
|
-
tokens = lexer.tokenize()
|
|
22
|
-
ast = GeometricBindingParser(tokens).parse()
|
|
23
|
-
compiler = LLVMCompiler()
|
|
24
|
-
ir_module = compiler.compile(ast)
|
|
25
|
-
llvm_ir = str(ir_module)
|
|
26
|
-
start_jit = time.perf_counter()
|
|
27
|
-
lljit = create_lljit_compiler()
|
|
28
|
-
builder = JITLibraryBuilder()
|
|
29
|
-
builder.add_ir(llvm_ir)
|
|
30
|
-
tracker = builder.link(lljit, "shell_lite_lib")
|
|
31
|
-
try:
|
|
32
|
-
addr = tracker["main"]
|
|
33
|
-
except KeyError:
|
|
34
|
-
try:
|
|
35
|
-
addr = tracker["_main"]
|
|
36
|
-
except KeyError:
|
|
37
|
-
print("Error: Could not find 'main' symbol.")
|
|
38
|
-
return 0, 0, -1
|
|
39
|
-
jit_time = time.perf_counter() - start_jit
|
|
40
|
-
cfunc = ctypes.CFUNCTYPE(ctypes.c_int)(addr)
|
|
41
|
-
start_run = time.perf_counter()
|
|
42
|
-
res = cfunc()
|
|
43
|
-
run_time = time.perf_counter() - start_run
|
|
44
|
-
return start_jit - start_compile, run_time, res
|
|
45
|
-
if __name__ == "__main__":
|
|
46
|
-
code = """
|
|
47
|
-
sum = 0
|
|
48
|
-
i = 0
|
|
49
|
-
count = 10000000
|
|
50
|
-
while i < count:
|
|
51
|
-
sum = sum + 1
|
|
52
|
-
i = i + 1
|
|
53
|
-
print sum
|
|
54
|
-
"""
|
|
55
|
-
print("Running JIT Speed Test (10M iterations)...")
|
|
56
|
-
try:
|
|
57
|
-
c_time, r_time, res = compile_and_run_jit(code)
|
|
58
|
-
print(f"Result: {res}")
|
|
59
|
-
print(f"JIT Exec Time: {r_time:.6f}s")
|
|
60
|
-
start_py = time.perf_counter()
|
|
61
|
-
s, i, c = 0, 0, 10000000
|
|
62
|
-
while i < c:
|
|
63
|
-
s += 1
|
|
64
|
-
i += 1
|
|
65
|
-
py_time = time.perf_counter() - start_py
|
|
66
|
-
print(f"Python Exec: {py_time:.6f}s")
|
|
67
|
-
print(f"Speedup: {py_time / r_time:.2f}x")
|
|
68
|
-
except Exception as e:
|
|
69
|
-
import traceback
|
|
70
|
-
traceback.print_exc()
|
|
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
|