shell-lite 0.4.2__py3-none-any.whl → 0.4.4__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.
- shell_lite/interpreter.py +94 -25
- shell_lite/lexer.py +22 -7
- shell_lite/main.py +13 -2
- shell_lite/parser.py +467 -40
- {shell_lite-0.4.2.dist-info → shell_lite-0.4.4.dist-info}/METADATA +1 -1
- shell_lite-0.4.4.dist-info/RECORD +15 -0
- shell_lite/formatter.py +0 -75
- shell_lite/js_compiler.py +0 -220
- shell_lite-0.4.2.dist-info/RECORD +0 -17
- {shell_lite-0.4.2.dist-info → shell_lite-0.4.4.dist-info}/LICENSE +0 -0
- {shell_lite-0.4.2.dist-info → shell_lite-0.4.4.dist-info}/WHEEL +0 -0
- {shell_lite-0.4.2.dist-info → shell_lite-0.4.4.dist-info}/entry_points.txt +0 -0
- {shell_lite-0.4.2.dist-info → shell_lite-0.4.4.dist-info}/top_level.txt +0 -0
shell_lite/interpreter.py
CHANGED
|
@@ -128,7 +128,23 @@ class WebBuilder:
|
|
|
128
128
|
pass
|
|
129
129
|
class Interpreter:
|
|
130
130
|
def __init__(self):
|
|
131
|
+
print('DEBUG: ShellLite v0.04.4')
|
|
131
132
|
self.global_env = Environment()
|
|
133
|
+
self.global_env.set('str', str)
|
|
134
|
+
self.global_env.set('int', int)
|
|
135
|
+
self.global_env.set('float', float)
|
|
136
|
+
self.global_env.set('list', list)
|
|
137
|
+
self.global_env.set('len', len)
|
|
138
|
+
self.global_env.set('input', input)
|
|
139
|
+
self.global_env.set('range', range)
|
|
140
|
+
|
|
141
|
+
# English-like helpers
|
|
142
|
+
self.global_env.set('wait', time.sleep)
|
|
143
|
+
self.global_env.set('append', lambda l, x: l.append(x))
|
|
144
|
+
self.global_env.set('remove', lambda l, x: l.remove(x))
|
|
145
|
+
self.global_env.set('empty', lambda l: len(l) == 0)
|
|
146
|
+
self.global_env.set('contains', lambda l, x: x in l)
|
|
147
|
+
|
|
132
148
|
self.current_env = self.global_env
|
|
133
149
|
self.functions: Dict[str, FunctionDef] = {}
|
|
134
150
|
self.classes: Dict[str, ClassDef] = {}
|
|
@@ -153,11 +169,13 @@ class Interpreter:
|
|
|
153
169
|
'split': lambda s, d=" ": s.split(d),
|
|
154
170
|
'join': lambda lst, d="": d.join(str(x) for x in lst),
|
|
155
171
|
'replace': lambda s, old, new: s.replace(old, new),
|
|
156
|
-
'upper':
|
|
172
|
+
'upper': self._builtin_upper,
|
|
157
173
|
'lower': lambda s: s.lower(),
|
|
158
174
|
'trim': lambda s: s.strip(),
|
|
159
175
|
'startswith': lambda s, p: s.startswith(p),
|
|
160
176
|
'endswith': lambda s, p: s.endswith(p),
|
|
177
|
+
'sum_range': self._builtin_sum_range,
|
|
178
|
+
'range_list': self._builtin_range_list,
|
|
161
179
|
'find': lambda s, sub: s.find(sub),
|
|
162
180
|
'char': chr, 'ord': ord,
|
|
163
181
|
'append': lambda l, x: (l.append(x), l)[1],
|
|
@@ -172,9 +190,6 @@ class Interpreter:
|
|
|
172
190
|
'slice': lambda l, start, end=None: l[start:end],
|
|
173
191
|
'contains': lambda l, x: x in l,
|
|
174
192
|
'index': lambda l, x: l.index(x) if x in l else -1,
|
|
175
|
-
'map': self._builtin_map,
|
|
176
|
-
'filter': self._builtin_filter,
|
|
177
|
-
'reduce': self._builtin_reduce,
|
|
178
193
|
'exists': os.path.exists,
|
|
179
194
|
'delete': os.remove,
|
|
180
195
|
'copy': shutil.copy,
|
|
@@ -263,6 +278,12 @@ class Interpreter:
|
|
|
263
278
|
def _builtin_push(self, lst, item):
|
|
264
279
|
lst.append(item)
|
|
265
280
|
return None
|
|
281
|
+
def _builtin_upper(self, s):
|
|
282
|
+
return str(s).upper()
|
|
283
|
+
def _builtin_sum_range(self, start, end):
|
|
284
|
+
return sum(range(int(start), int(end)))
|
|
285
|
+
def _builtin_range_list(self, start, end):
|
|
286
|
+
return list(range(int(start), int(end)))
|
|
266
287
|
def _init_std_modules(self):
|
|
267
288
|
self.std_modules = {
|
|
268
289
|
'math': {
|
|
@@ -1519,7 +1540,7 @@ class Interpreter:
|
|
|
1519
1540
|
self.wfile.write(str(e).encode())
|
|
1520
1541
|
except: pass
|
|
1521
1542
|
server = HTTPServer(('0.0.0.0', port_val), ShellLiteHandler)
|
|
1522
|
-
print(f"\n ShellLite Server v0.04.
|
|
1543
|
+
print(f"\n ShellLite Server v0.04.4 is running!")
|
|
1523
1544
|
print(f" \u001b[1;36m➜\u001b[0m Local: \u001b[1;4;36mhttp://localhost:{port_val}/\u001b[0m\n")
|
|
1524
1545
|
try: server.serve_forever()
|
|
1525
1546
|
except KeyboardInterrupt:
|
|
@@ -1688,23 +1709,71 @@ class Interpreter:
|
|
|
1688
1709
|
except FileNotFoundError:
|
|
1689
1710
|
raise FileNotFoundError(f"File '{path}' not found.")
|
|
1690
1711
|
raise RuntimeError(f"Read failed: {e}")
|
|
1691
|
-
|
|
1692
|
-
|
|
1693
|
-
|
|
1694
|
-
|
|
1695
|
-
|
|
1696
|
-
|
|
1697
|
-
|
|
1698
|
-
|
|
1699
|
-
|
|
1700
|
-
|
|
1701
|
-
|
|
1702
|
-
|
|
1703
|
-
|
|
1704
|
-
|
|
1705
|
-
|
|
1706
|
-
|
|
1707
|
-
|
|
1708
|
-
|
|
1709
|
-
|
|
1710
|
-
|
|
1712
|
+
def _builtin_upper(self, s, only_letters=False):
|
|
1713
|
+
if not only_letters:
|
|
1714
|
+
return s.upper()
|
|
1715
|
+
# "UPPER words ONLY LETTERS" -> Uppercase normal letters, leave others?
|
|
1716
|
+
# Or maybe it means "Only extract uppercase letters"?
|
|
1717
|
+
# User output shows: "HELLO WORLD 123" -> "HELLO WORLD 123" (normal)
|
|
1718
|
+
# Wait, user screenshot says:
|
|
1719
|
+
# text = "hello world 123"
|
|
1720
|
+
# words = split text
|
|
1721
|
+
# say upper words only letters
|
|
1722
|
+
# Error: Variable 'only' is not defined.
|
|
1723
|
+
# So maybe they want to UPPERCASE ONLY LETTERS? digits remain same? .upper() does that.
|
|
1724
|
+
# But maybe they mean "remove non-letters"?
|
|
1725
|
+
# "upper words only letters" -> "HELLO WORLD".
|
|
1726
|
+
# If "only letters" means filter?
|
|
1727
|
+
# Let's assume it means "uppercase only the letters" which is standard behavior?
|
|
1728
|
+
# Or maybe "uppercase, and keep only letters".
|
|
1729
|
+
# Let's look at user intent. "upper words only letters".
|
|
1730
|
+
# Likely: Uppercase and remove numbers/symbols?
|
|
1731
|
+
# If input is "hello world 123", output might be "HELLO WORLD".
|
|
1732
|
+
if only_letters:
|
|
1733
|
+
import re
|
|
1734
|
+
return re.sub(r'[^a-zA-Z\s]', '', s).upper()
|
|
1735
|
+
return s.upper()
|
|
1736
|
+
|
|
1737
|
+
def _builtin_sum_range(self, start, end, condition=None):
|
|
1738
|
+
# condition is a string, e.g. "even", "odd", "prime", "digits"
|
|
1739
|
+
total = 0
|
|
1740
|
+
s = int(start)
|
|
1741
|
+
e = int(end)
|
|
1742
|
+
for i in range(s, e + 1):
|
|
1743
|
+
include = True
|
|
1744
|
+
if condition == 'even' and i % 2 != 0: include = False
|
|
1745
|
+
elif condition == 'odd' and i % 2 == 0: include = False
|
|
1746
|
+
elif condition == 'prime':
|
|
1747
|
+
if i < 2: include = False
|
|
1748
|
+
else:
|
|
1749
|
+
for k in range(2, int(i ** 0.5) + 1):
|
|
1750
|
+
if i % k == 0:
|
|
1751
|
+
include = False; break
|
|
1752
|
+
elif condition == 'digits':
|
|
1753
|
+
# sum of digits? Or sum of numbers that are single digits?
|
|
1754
|
+
# "sum of numbers from 1 to 10 when digits" -> unclear.
|
|
1755
|
+
# Assuming "digits" meant specific property.
|
|
1756
|
+
pass
|
|
1757
|
+
|
|
1758
|
+
if include:
|
|
1759
|
+
total += i
|
|
1760
|
+
return total
|
|
1761
|
+
|
|
1762
|
+
def _builtin_range_list(self, start, end, condition=None):
|
|
1763
|
+
res = []
|
|
1764
|
+
s = int(start)
|
|
1765
|
+
e = int(end)
|
|
1766
|
+
for i in range(s, e + 1):
|
|
1767
|
+
include = True
|
|
1768
|
+
if condition == 'even' and i % 2 != 0: include = False
|
|
1769
|
+
elif condition == 'odd' and i % 2 == 0: include = False
|
|
1770
|
+
elif condition == 'prime':
|
|
1771
|
+
if i < 2: include = False
|
|
1772
|
+
else:
|
|
1773
|
+
for k in range(2, int(i ** 0.5) + 1):
|
|
1774
|
+
if i % k == 0:
|
|
1775
|
+
include = False; break
|
|
1776
|
+
|
|
1777
|
+
if include:
|
|
1778
|
+
res.append(i)
|
|
1779
|
+
return res
|
shell_lite/lexer.py
CHANGED
|
@@ -25,8 +25,8 @@ class Lexer:
|
|
|
25
25
|
continue
|
|
26
26
|
indent_level = len(line) - len(line.lstrip())
|
|
27
27
|
if stripped_line.startswith('#'):
|
|
28
|
-
self.tokens.append(Token('COMMENT', stripped_line, self.line_number, indent_level + 1))
|
|
29
|
-
self.tokens.append(Token('NEWLINE', '', self.line_number, len(line) + 1))
|
|
28
|
+
# self.tokens.append(Token('COMMENT', stripped_line, self.line_number, indent_level + 1))
|
|
29
|
+
# self.tokens.append(Token('NEWLINE', '', self.line_number, len(line) + 1))
|
|
30
30
|
continue
|
|
31
31
|
if indent_level > self.indent_stack[-1]:
|
|
32
32
|
self.indent_stack.append(indent_level)
|
|
@@ -165,7 +165,8 @@ class Lexer:
|
|
|
165
165
|
'while': 'WHILE', 'until': 'UNTIL',
|
|
166
166
|
'repeat': 'REPEAT', 'forever': 'FOREVER',
|
|
167
167
|
'stop': 'STOP', 'skip': 'SKIP', 'exit': 'EXIT',
|
|
168
|
-
'each': '
|
|
168
|
+
'each': 'EACH',
|
|
169
|
+
'check': 'CHECK',
|
|
169
170
|
'unless': 'UNLESS', 'when': 'WHEN', 'otherwise': 'OTHERWISE',
|
|
170
171
|
'then': 'THEN', 'do': 'DO',
|
|
171
172
|
'print': 'PRINT', 'say': 'SAY', 'show': 'SAY',
|
|
@@ -182,7 +183,6 @@ class Lexer:
|
|
|
182
183
|
'const': 'CONST',
|
|
183
184
|
'and': 'AND', 'or': 'OR', 'not': 'NOT',
|
|
184
185
|
'try': 'TRY', 'catch': 'CATCH', 'always': 'ALWAYS',
|
|
185
|
-
'error': 'ERROR',
|
|
186
186
|
'use': 'USE', 'as': 'AS', 'share': 'SHARE',
|
|
187
187
|
'execute': 'EXECUTE', 'run': 'EXECUTE',
|
|
188
188
|
'alert': 'ALERT', 'prompt': 'PROMPT', 'confirm': 'CONFIRM',
|
|
@@ -223,9 +223,24 @@ class Lexer:
|
|
|
223
223
|
'count': 'COUNT', 'many': 'MANY', 'how': 'HOW',
|
|
224
224
|
'field': 'FIELD', 'submit': 'SUBMIT', 'named': 'NAMED',
|
|
225
225
|
'placeholder': 'PLACEHOLDER',
|
|
226
|
-
'app': 'APP', 'title': 'ID', 'size': '
|
|
227
|
-
'column': '
|
|
228
|
-
'button': '
|
|
226
|
+
'app': 'APP', 'title': 'ID', 'size': 'SIZE',
|
|
227
|
+
'column': 'COLUMN', 'row': 'ROW',
|
|
228
|
+
'button': 'BUTTON', 'heading': 'HEADING',
|
|
229
|
+
'sum': 'SUM', 'upper': 'UPPER', 'lower': 'LOWER',
|
|
230
|
+
'increment': 'INCREMENT', 'decrement': 'DECREMENT',
|
|
231
|
+
'multiply': 'MULTIPLY', 'divide': 'DIVIDE',
|
|
232
|
+
'be': 'BE', 'by': 'BY',
|
|
233
|
+
'plus': 'PLUS', 'minus': 'MINUS', 'divided': 'DIV',
|
|
234
|
+
'greater': 'GREATER', 'less': 'LESS', 'equal': 'EQUAL',
|
|
235
|
+
'define': 'DEFINE', 'function': 'FUNCTION',
|
|
236
|
+
'contains': 'CONTAINS', 'empty': 'EMPTY',
|
|
237
|
+
'remove': 'REMOVE',
|
|
238
|
+
'than': 'THAN',
|
|
239
|
+
'doing': 'DOING',
|
|
240
|
+
'make': 'MAKE', 'be': 'BE',
|
|
241
|
+
'as': 'AS', 'long': 'LONG',
|
|
242
|
+
'otherwise': 'OTHERWISE',
|
|
243
|
+
'ask': 'ASK',
|
|
229
244
|
}
|
|
230
245
|
token_type = keywords.get(value, 'ID')
|
|
231
246
|
self.tokens.append(Token(token_type, value, self.line_number, current_col))
|
shell_lite/main.py
CHANGED
|
@@ -12,6 +12,11 @@ from .ast_nodes import *
|
|
|
12
12
|
import json
|
|
13
13
|
def execute_source(source: str, interpreter: Interpreter):
|
|
14
14
|
lines = source.split('\n')
|
|
15
|
+
import sys
|
|
16
|
+
if 'shell_lite.interpreter' in sys.modules:
|
|
17
|
+
print(f"DEBUG: Loaded interpreter from {sys.modules['shell_lite.interpreter'].__file__}")
|
|
18
|
+
else:
|
|
19
|
+
print("DEBUG: shell_lite.interpreter not in sys.modules yet?")
|
|
15
20
|
import difflib
|
|
16
21
|
try:
|
|
17
22
|
lexer = Lexer(source)
|
|
@@ -52,6 +57,12 @@ def run_file(filename: str):
|
|
|
52
57
|
if not os.path.exists(filename):
|
|
53
58
|
print(f"Error: File '{filename}' not found.")
|
|
54
59
|
return
|
|
60
|
+
import sys
|
|
61
|
+
if 'shell_lite.interpreter' in sys.modules:
|
|
62
|
+
print(f"DEBUG: shell_lite.interpreter file: {sys.modules['shell_lite.interpreter'].__file__}")
|
|
63
|
+
from .interpreter_final import Interpreter
|
|
64
|
+
print(f"DEBUG: Interpreter class: {Interpreter}")
|
|
65
|
+
|
|
55
66
|
with open(filename, 'r', encoding='utf-8') as f:
|
|
56
67
|
source = f.read()
|
|
57
68
|
interpreter = Interpreter()
|
|
@@ -61,7 +72,7 @@ def run_repl():
|
|
|
61
72
|
print("\n" + "="*40)
|
|
62
73
|
print(" ShellLite REPL - English Syntax")
|
|
63
74
|
print("="*40)
|
|
64
|
-
print("Version: v0.04.
|
|
75
|
+
print("Version: v0.04.4 | Made by Shrey Naithani")
|
|
65
76
|
print("Commands: Type 'exit' to quit, 'help' for examples.")
|
|
66
77
|
print("Note: Terminal commands (like 'shl install') must be run in CMD/PowerShell, not here.")
|
|
67
78
|
|
|
@@ -192,7 +203,7 @@ def install_globally():
|
|
|
192
203
|
ps_cmd = f'$oldPath = [Environment]::GetEnvironmentVariable("Path", "User"); if ($oldPath -notlike "*ShellLite*") {{ [Environment]::SetEnvironmentVariable("Path", "$oldPath;{install_dir}", "User") }}'
|
|
193
204
|
subprocess.run(["powershell", "-Command", ps_cmd], capture_output=True)
|
|
194
205
|
|
|
195
|
-
print(f"\n[SUCCESS] ShellLite (v0.04.
|
|
206
|
+
print(f"\n[SUCCESS] ShellLite (v0.04.4) is installed!")
|
|
196
207
|
print(f"Location: {install_dir}")
|
|
197
208
|
print("\nIMPORTANT STEP REQUIRED:")
|
|
198
209
|
print("1. Close ALL open terminal windows (CMD, PowerShell, VS Code).")
|
shell_lite/parser.py
CHANGED
|
@@ -84,6 +84,16 @@ class Parser:
|
|
|
84
84
|
input_token = self.consume()
|
|
85
85
|
return self.parse_id_start_statement(passed_name_token=input_token)
|
|
86
86
|
return self.parse_expression_stmt()
|
|
87
|
+
elif self.check('BUTTON'):
|
|
88
|
+
return self.parse_id_start_statement(passed_name_token=self.consume('BUTTON'))
|
|
89
|
+
elif self.check('COLUMN'):
|
|
90
|
+
return self.parse_id_start_statement(passed_name_token=self.consume('COLUMN'))
|
|
91
|
+
elif self.check('ROW'):
|
|
92
|
+
return self.parse_id_start_statement(passed_name_token=self.consume('ROW'))
|
|
93
|
+
elif self.check('IMAGE'):
|
|
94
|
+
return self.parse_id_start_statement(passed_name_token=self.consume('IMAGE'))
|
|
95
|
+
elif self.check('SIZE'):
|
|
96
|
+
return self.parse_id_start_statement(passed_name_token=self.consume('SIZE'))
|
|
87
97
|
elif self.check('ID'):
|
|
88
98
|
return self.parse_id_start_statement()
|
|
89
99
|
elif self.check('SPAWN'):
|
|
@@ -124,15 +134,60 @@ class Parser:
|
|
|
124
134
|
elif self.check('BEFORE'):
|
|
125
135
|
return self.parse_middleware()
|
|
126
136
|
elif self.check('DEFINE'):
|
|
137
|
+
if self.peek(1).type == 'FUNCTION':
|
|
138
|
+
return self.parse_function_def()
|
|
127
139
|
return self.parse_define_page()
|
|
128
140
|
elif self.check('ADD'):
|
|
129
|
-
|
|
141
|
+
if self.peek(1).type == 'ID' or self.peek(1).type == 'NUMBER' or self.peek(1).type == 'STRING':
|
|
142
|
+
# Heuristic: "Add X to Y" (List) vs "Add Component" (UI) maybe?
|
|
143
|
+
# Assuming UI "add" uses 'ADD' keyword?
|
|
144
|
+
# Lexer maps 'add' to 'ADD'.
|
|
145
|
+
# parse_add_to -> UI.
|
|
146
|
+
# parse_add_to_list -> List.
|
|
147
|
+
# Let's check parse_add_to signature.
|
|
148
|
+
pass
|
|
149
|
+
# For now prioritize List if 'TO' is present later?
|
|
150
|
+
# Or assume parse_add_to handles UI.
|
|
151
|
+
# Let's peek(1). If it's an expression -> List op. If it's a Component?
|
|
152
|
+
# Creating a unified parse_add dispatcher might be better.
|
|
153
|
+
return self.parse_add_distinguish()
|
|
154
|
+
|
|
130
155
|
elif self.check('START'):
|
|
131
156
|
return self.parse_start_server()
|
|
132
157
|
elif self.check('HEADING'):
|
|
133
158
|
return self.parse_heading()
|
|
134
159
|
elif self.check('PARAGRAPH'):
|
|
135
160
|
return self.parse_paragraph()
|
|
161
|
+
|
|
162
|
+
# English List/Time Ops
|
|
163
|
+
if self.check('ADD'):
|
|
164
|
+
return self.parse_add_to_list()
|
|
165
|
+
if self.check('REMOVE'):
|
|
166
|
+
return self.parse_remove_from_list()
|
|
167
|
+
if self.check('WAIT'):
|
|
168
|
+
return self.parse_wait()
|
|
169
|
+
|
|
170
|
+
if self.check('INCREMENT'):
|
|
171
|
+
return self.parse_increment()
|
|
172
|
+
elif self.check('DECREMENT'):
|
|
173
|
+
return self.parse_decrement()
|
|
174
|
+
elif self.check('MULTIPLY'):
|
|
175
|
+
return self.parse_multiply()
|
|
176
|
+
elif self.check('DIVIDE'):
|
|
177
|
+
return self.parse_divide()
|
|
178
|
+
elif self.check('MAKE'):
|
|
179
|
+
return self.parse_make_assignment()
|
|
180
|
+
elif self.check('AS'):
|
|
181
|
+
return self.parse_as_long_as()
|
|
182
|
+
elif self.check('ASK'):
|
|
183
|
+
# Standalone ask statement? e.g. ask "Questions?"
|
|
184
|
+
# Or ask is expression. If statement, maybe just expression statement.
|
|
185
|
+
return self.parse_expression_statement()
|
|
186
|
+
elif self.check('CHECK'):
|
|
187
|
+
self.consume('CHECK')
|
|
188
|
+
return self.parse_if()
|
|
189
|
+
elif self.check('SET'):
|
|
190
|
+
return self.parse_set()
|
|
136
191
|
else:
|
|
137
192
|
return self.parse_expression_stmt()
|
|
138
193
|
def parse_alert(self) -> Alert:
|
|
@@ -307,9 +362,17 @@ class Parser:
|
|
|
307
362
|
self.consume('NEWLINE')
|
|
308
363
|
return node
|
|
309
364
|
|
|
310
|
-
def parse_make_expr(self) ->
|
|
365
|
+
def parse_make_expr(self) -> Node:
|
|
311
366
|
token = self.consume('MAKE')
|
|
312
367
|
class_name = self.consume('ID').value
|
|
368
|
+
|
|
369
|
+
if self.check('BE'):
|
|
370
|
+
self.consume('BE')
|
|
371
|
+
value = self.parse_expression()
|
|
372
|
+
node = Assign(class_name, value) # class_name is actually variable name here
|
|
373
|
+
node.line = token.line
|
|
374
|
+
return node
|
|
375
|
+
|
|
313
376
|
args = []
|
|
314
377
|
if self.check('LPAREN'):
|
|
315
378
|
self.consume('LPAREN')
|
|
@@ -325,24 +388,6 @@ class Parser:
|
|
|
325
388
|
node = Make(class_name, args)
|
|
326
389
|
node.line = token.line
|
|
327
390
|
return node
|
|
328
|
-
def parse_repeat(self) -> Repeat:
|
|
329
|
-
token = self.consume('REPEAT')
|
|
330
|
-
if self.check('NEWLINE'):
|
|
331
|
-
raise SyntaxError(f"repeat requires a count on line {token.line}")
|
|
332
|
-
count = self.parse_expression()
|
|
333
|
-
if self.check('TIMES'):
|
|
334
|
-
self.consume('TIMES')
|
|
335
|
-
self.consume('NEWLINE')
|
|
336
|
-
self.consume('INDENT')
|
|
337
|
-
body = []
|
|
338
|
-
while not self.check('DEDENT') and not self.check('EOF'):
|
|
339
|
-
while self.check('NEWLINE'): self.consume()
|
|
340
|
-
if self.check('DEDENT'): break
|
|
341
|
-
body.append(self.parse_statement())
|
|
342
|
-
self.consume('DEDENT')
|
|
343
|
-
node = Repeat(count, body)
|
|
344
|
-
node.line = token.line
|
|
345
|
-
return node
|
|
346
391
|
return node
|
|
347
392
|
def parse_db_op(self) -> DatabaseOp:
|
|
348
393
|
token = self.consume('DB')
|
|
@@ -476,7 +521,13 @@ class Parser:
|
|
|
476
521
|
node.line = token.line
|
|
477
522
|
return node
|
|
478
523
|
def parse_function_def(self) -> FunctionDef:
|
|
479
|
-
start_token =
|
|
524
|
+
start_token = None
|
|
525
|
+
if self.check('DEFINE'):
|
|
526
|
+
start_token = self.consume('DEFINE')
|
|
527
|
+
self.consume('FUNCTION')
|
|
528
|
+
else:
|
|
529
|
+
start_token = self.consume('TO') # Fallback to existing 'TO' if not 'DEFINE'
|
|
530
|
+
|
|
480
531
|
name = self.consume('ID').value
|
|
481
532
|
args = []
|
|
482
533
|
while self.check('ID'):
|
|
@@ -499,6 +550,7 @@ class Parser:
|
|
|
499
550
|
self.consume('ASSIGN')
|
|
500
551
|
default_val = self.parse_expression()
|
|
501
552
|
args.append((arg_name, default_val, type_hint))
|
|
553
|
+
if self.check('DOING'): self.consume('DOING')
|
|
502
554
|
if self.check('COLON'):
|
|
503
555
|
self.consume('COLON')
|
|
504
556
|
self.consume('NEWLINE')
|
|
@@ -901,8 +953,8 @@ class Parser:
|
|
|
901
953
|
return node
|
|
902
954
|
def parse_start_server(self) -> Node:
|
|
903
955
|
token = self.consume('START')
|
|
904
|
-
if self.check('SERVER'):
|
|
905
|
-
self.consume(
|
|
956
|
+
if self.check('SERVER') or self.check('WEBSITE') or (self.check('ID') and self.peek().value == 'website'):
|
|
957
|
+
self.consume()
|
|
906
958
|
port = Number(8080)
|
|
907
959
|
if self.check('ON'):
|
|
908
960
|
self.consume('ON')
|
|
@@ -969,6 +1021,7 @@ class Parser:
|
|
|
969
1021
|
def parse_if(self) -> If:
|
|
970
1022
|
self.consume('IF')
|
|
971
1023
|
condition = self.parse_expression()
|
|
1024
|
+
if self.check('COLON'): self.consume('COLON')
|
|
972
1025
|
self.consume('NEWLINE')
|
|
973
1026
|
self.consume('INDENT')
|
|
974
1027
|
body = []
|
|
@@ -980,8 +1033,11 @@ class Parser:
|
|
|
980
1033
|
else_body = None
|
|
981
1034
|
if self.check('ELIF'):
|
|
982
1035
|
else_body = [self.parse_elif()]
|
|
983
|
-
elif self.check('ELSE'):
|
|
984
|
-
self.consume('ELSE')
|
|
1036
|
+
elif self.check('ELSE') or self.check('OTHERWISE'):
|
|
1037
|
+
if self.check('ELSE'): self.consume('ELSE')
|
|
1038
|
+
else: self.consume('OTHERWISE')
|
|
1039
|
+
|
|
1040
|
+
if self.check('COLON'): self.consume('COLON')
|
|
985
1041
|
self.consume('NEWLINE')
|
|
986
1042
|
self.consume('INDENT')
|
|
987
1043
|
else_body = []
|
|
@@ -991,9 +1047,11 @@ class Parser:
|
|
|
991
1047
|
else_body.append(self.parse_statement())
|
|
992
1048
|
self.consume('DEDENT')
|
|
993
1049
|
return If(condition, body, else_body)
|
|
1050
|
+
|
|
994
1051
|
def parse_elif(self) -> If:
|
|
995
1052
|
token = self.consume('ELIF')
|
|
996
1053
|
condition = self.parse_expression()
|
|
1054
|
+
if self.check('COLON'): self.consume('COLON')
|
|
997
1055
|
self.consume('NEWLINE')
|
|
998
1056
|
self.consume('INDENT')
|
|
999
1057
|
body = []
|
|
@@ -1007,6 +1065,7 @@ class Parser:
|
|
|
1007
1065
|
else_body = [self.parse_elif()]
|
|
1008
1066
|
elif self.check('ELSE'):
|
|
1009
1067
|
self.consume('ELSE')
|
|
1068
|
+
if self.check('COLON'): self.consume('COLON')
|
|
1010
1069
|
self.consume('NEWLINE')
|
|
1011
1070
|
self.consume('INDENT')
|
|
1012
1071
|
else_body = []
|
|
@@ -1015,14 +1074,12 @@ class Parser:
|
|
|
1015
1074
|
if self.check('DEDENT'): break
|
|
1016
1075
|
else_body.append(self.parse_statement())
|
|
1017
1076
|
self.consume('DEDENT')
|
|
1018
|
-
|
|
1019
|
-
|
|
1020
|
-
node = If(condition, body, else_body)
|
|
1021
|
-
node.line = token.line
|
|
1022
|
-
return node
|
|
1077
|
+
return If(condition, body, else_body)
|
|
1078
|
+
|
|
1023
1079
|
def parse_while(self) -> While:
|
|
1024
1080
|
start_token = self.consume('WHILE')
|
|
1025
1081
|
condition = self.parse_expression()
|
|
1082
|
+
if self.check('COLON'): self.consume('COLON')
|
|
1026
1083
|
self.consume('NEWLINE')
|
|
1027
1084
|
self.consume('INDENT')
|
|
1028
1085
|
body = []
|
|
@@ -1034,8 +1091,28 @@ class Parser:
|
|
|
1034
1091
|
node = While(condition, body)
|
|
1035
1092
|
node.line = start_token.line
|
|
1036
1093
|
return node
|
|
1094
|
+
|
|
1095
|
+
def parse_repeat(self) -> Repeat:
|
|
1096
|
+
start_token = self.consume('REPEAT')
|
|
1097
|
+
count = self.parse_expression()
|
|
1098
|
+
self.consume('TIMES')
|
|
1099
|
+
if self.check('COLON'): self.consume('COLON')
|
|
1100
|
+
self.consume('NEWLINE')
|
|
1101
|
+
self.consume('INDENT')
|
|
1102
|
+
body = []
|
|
1103
|
+
while not self.check('DEDENT') and not self.check('EOF'):
|
|
1104
|
+
while self.check('NEWLINE'): self.consume()
|
|
1105
|
+
if self.check('DEDENT'): break
|
|
1106
|
+
body.append(self.parse_statement())
|
|
1107
|
+
self.consume('DEDENT')
|
|
1108
|
+
node = Repeat(count, body)
|
|
1109
|
+
node.line = start_token.line
|
|
1110
|
+
return node
|
|
1111
|
+
|
|
1037
1112
|
def parse_try(self) -> Try:
|
|
1038
1113
|
start_token = self.consume('TRY')
|
|
1114
|
+
if self.check('COLON'):
|
|
1115
|
+
self.consume('COLON')
|
|
1039
1116
|
self.consume('NEWLINE')
|
|
1040
1117
|
self.consume('INDENT')
|
|
1041
1118
|
try_body = []
|
|
@@ -1046,6 +1123,8 @@ class Parser:
|
|
|
1046
1123
|
self.consume('DEDENT')
|
|
1047
1124
|
self.consume('CATCH')
|
|
1048
1125
|
catch_var = self.consume('ID').value
|
|
1126
|
+
if self.check('COLON'):
|
|
1127
|
+
self.consume('COLON')
|
|
1049
1128
|
self.consume('NEWLINE')
|
|
1050
1129
|
self.consume('INDENT')
|
|
1051
1130
|
catch_body = []
|
|
@@ -1087,6 +1166,16 @@ class Parser:
|
|
|
1087
1166
|
return node
|
|
1088
1167
|
first_expr = self.parse_expression()
|
|
1089
1168
|
skip_formatted()
|
|
1169
|
+
|
|
1170
|
+
if self.check('TO'):
|
|
1171
|
+
self.consume('TO')
|
|
1172
|
+
end_val = self.parse_expression()
|
|
1173
|
+
skip_formatted()
|
|
1174
|
+
self.consume('RBRACKET')
|
|
1175
|
+
node = Call('range', [first_expr, end_val])
|
|
1176
|
+
node.line = token.line
|
|
1177
|
+
return node
|
|
1178
|
+
|
|
1090
1179
|
if self.check('FOR'):
|
|
1091
1180
|
self.consume('FOR')
|
|
1092
1181
|
var_name = self.consume('ID').value
|
|
@@ -1221,8 +1310,13 @@ class Parser:
|
|
|
1221
1310
|
self.consume('DEDENT')
|
|
1222
1311
|
return Layout(layout_type, children)
|
|
1223
1312
|
|
|
1224
|
-
elif token.type in ('BUTTON', 'INPUT', 'HEADING'
|
|
1225
|
-
|
|
1313
|
+
elif (token.type in ('BUTTON', 'INPUT', 'HEADING') or
|
|
1314
|
+
(token.type == 'ID' and token.value == 'text')):
|
|
1315
|
+
if token.type == 'ID' and token.value == 'text':
|
|
1316
|
+
widget_type = 'TEXT'
|
|
1317
|
+
self.consume() # consume 'text' ID
|
|
1318
|
+
else:
|
|
1319
|
+
widget_type = self.consume().value
|
|
1226
1320
|
label = self.consume('STRING').value
|
|
1227
1321
|
|
|
1228
1322
|
var_name = None
|
|
@@ -1248,7 +1342,13 @@ class Parser:
|
|
|
1248
1342
|
|
|
1249
1343
|
def parse_factor_simple(self) -> Node:
|
|
1250
1344
|
token = self.peek()
|
|
1251
|
-
if token.type == '
|
|
1345
|
+
if token.type == 'ASK':
|
|
1346
|
+
self.consume('ASK')
|
|
1347
|
+
prompt = self.parse_expression()
|
|
1348
|
+
node = Call('input', [prompt]) # Alias to input
|
|
1349
|
+
node.line = token.line
|
|
1350
|
+
return node
|
|
1351
|
+
elif token.type == 'NUMBER':
|
|
1252
1352
|
self.consume()
|
|
1253
1353
|
val = token.value
|
|
1254
1354
|
if '.' in val:
|
|
@@ -1325,7 +1425,7 @@ class Parser:
|
|
|
1325
1425
|
expr = self.parse_expression()
|
|
1326
1426
|
self.consume('RPAREN')
|
|
1327
1427
|
return expr
|
|
1328
|
-
elif token.type == 'INPUT'
|
|
1428
|
+
elif token.type == 'INPUT':
|
|
1329
1429
|
is_tag = False
|
|
1330
1430
|
next_t = self.peek(1)
|
|
1331
1431
|
if next_t.type in ('ID', 'TYPE', 'STRING', 'NAME', 'VALUE', 'CLASS', 'STYLE', 'ONCLICK', 'SRC', 'HREF', 'ACTION', 'METHOD'):
|
|
@@ -1354,6 +1454,8 @@ class Parser:
|
|
|
1354
1454
|
node = UnaryOp('not', right)
|
|
1355
1455
|
node.line = op.line
|
|
1356
1456
|
return node
|
|
1457
|
+
elif token.type == 'ASK':
|
|
1458
|
+
return self.parse_factor_simple()
|
|
1357
1459
|
elif token.type == 'LPAREN':
|
|
1358
1460
|
self.consume('LPAREN')
|
|
1359
1461
|
node = self.parse_expression()
|
|
@@ -1407,11 +1509,16 @@ class Parser:
|
|
|
1407
1509
|
return node
|
|
1408
1510
|
elif token.type == 'READ':
|
|
1409
1511
|
token = self.consume('READ')
|
|
1410
|
-
self.
|
|
1512
|
+
if self.check('FILE'):
|
|
1513
|
+
self.consume('FILE')
|
|
1411
1514
|
path = self.parse_factor()
|
|
1412
1515
|
node = FileRead(path)
|
|
1413
1516
|
node.line = token.line
|
|
1414
1517
|
return node
|
|
1518
|
+
elif token.type == 'SUM':
|
|
1519
|
+
return self.parse_sum()
|
|
1520
|
+
elif token.type == 'UPPER':
|
|
1521
|
+
return self.parse_upper()
|
|
1415
1522
|
elif token.type == 'DATE':
|
|
1416
1523
|
token = self.consume('DATE')
|
|
1417
1524
|
s = self.consume('STRING').value
|
|
@@ -1459,6 +1566,8 @@ class Parser:
|
|
|
1459
1566
|
return self._parse_natural_list()
|
|
1460
1567
|
elif self.peek(1).type == 'UNIQUE' and self.peek(2).type == 'SET' and self.peek(3).type == 'OF':
|
|
1461
1568
|
return self._parse_natural_set()
|
|
1569
|
+
if token.value == 'numbers' and self.peek(1).type == 'FROM':
|
|
1570
|
+
return self.parse_numbers_range()
|
|
1462
1571
|
self.consume()
|
|
1463
1572
|
instance_name = token.value
|
|
1464
1573
|
method_name = None
|
|
@@ -1571,6 +1680,7 @@ class Parser:
|
|
|
1571
1680
|
node.line = start_token.line
|
|
1572
1681
|
return node
|
|
1573
1682
|
start_token = self.consume('FOR')
|
|
1683
|
+
if self.check('EACH'): self.consume('EACH')
|
|
1574
1684
|
if self.check('ID') and self.peek(1).type == 'IN':
|
|
1575
1685
|
var_name = self.consume('ID').value
|
|
1576
1686
|
self.consume('IN')
|
|
@@ -1607,6 +1717,8 @@ class Parser:
|
|
|
1607
1717
|
count_expr = self.parse_expression()
|
|
1608
1718
|
self.consume('IN')
|
|
1609
1719
|
self.consume('RANGE')
|
|
1720
|
+
if self.check('COLON'):
|
|
1721
|
+
self.consume('COLON')
|
|
1610
1722
|
self.consume('NEWLINE')
|
|
1611
1723
|
self.consume('INDENT')
|
|
1612
1724
|
body = []
|
|
@@ -1669,11 +1781,39 @@ class Parser:
|
|
|
1669
1781
|
return left
|
|
1670
1782
|
def parse_comparison(self) -> Node:
|
|
1671
1783
|
left = self.parse_arithmetic()
|
|
1672
|
-
if self.peek().type in ('EQ', 'NEQ', 'GT', 'LT', 'GE', 'LE', 'IS', 'MATCHES'):
|
|
1784
|
+
if self.peek().type in ('EQ', 'NEQ', 'GT', 'LT', 'GE', 'LE', 'IS', 'MATCHES', 'GREATER', 'LESS', 'EQUAL', 'CONTAINS', 'EMPTY'):
|
|
1673
1785
|
op_token = self.consume()
|
|
1674
1786
|
op_val = op_token.value
|
|
1787
|
+
|
|
1788
|
+
# Handle "is greater/less/equal"
|
|
1675
1789
|
if op_token.type == 'IS':
|
|
1676
|
-
|
|
1790
|
+
if self.check('GREATER'):
|
|
1791
|
+
self.consume('GREATER'); self.consume('THAN')
|
|
1792
|
+
op_val = '>'
|
|
1793
|
+
elif self.check('LESS'):
|
|
1794
|
+
self.consume('LESS'); self.consume('THAN')
|
|
1795
|
+
op_val = '<'
|
|
1796
|
+
elif self.check('EQUAL'):
|
|
1797
|
+
self.consume('EQUAL'); self.consume('TO')
|
|
1798
|
+
op_val = '=='
|
|
1799
|
+
elif self.check('NOT'):
|
|
1800
|
+
self.consume('NOT'); self.consume('EQUAL'); self.consume('TO')
|
|
1801
|
+
op_val = '!='
|
|
1802
|
+
elif self.check('EMPTY'):
|
|
1803
|
+
self.consume('EMPTY')
|
|
1804
|
+
# is empty -> Call('empty', [left])
|
|
1805
|
+
node = Call('empty', [left])
|
|
1806
|
+
node.line = op_token.line
|
|
1807
|
+
return node
|
|
1808
|
+
else:
|
|
1809
|
+
op_val = '=='
|
|
1810
|
+
elif op_token.type == 'CONTAINS':
|
|
1811
|
+
# list contains item -> Call('contains', [list, item])
|
|
1812
|
+
right = self.parse_arithmetic()
|
|
1813
|
+
node = Call('contains', [left, right])
|
|
1814
|
+
node.line = op_token.line
|
|
1815
|
+
return node
|
|
1816
|
+
|
|
1677
1817
|
right = self.parse_arithmetic()
|
|
1678
1818
|
node = BinOp(left, op_val, right)
|
|
1679
1819
|
node.line = op_token.line
|
|
@@ -1683,17 +1823,39 @@ class Parser:
|
|
|
1683
1823
|
left = self.parse_term()
|
|
1684
1824
|
while self.peek().type in ('PLUS', 'MINUS'):
|
|
1685
1825
|
op_token = self.consume()
|
|
1826
|
+
op_val = op_token.value
|
|
1827
|
+
# Normalize to symbols
|
|
1828
|
+
if op_token.type == 'PLUS': op_val = '+'
|
|
1829
|
+
if op_token.type == 'MINUS': op_val = '-'
|
|
1830
|
+
|
|
1686
1831
|
right = self.parse_term()
|
|
1687
|
-
new_node = BinOp(left,
|
|
1832
|
+
new_node = BinOp(left, op_val, right)
|
|
1688
1833
|
new_node.line = op_token.line
|
|
1689
1834
|
left = new_node
|
|
1690
1835
|
return left
|
|
1691
1836
|
def parse_term(self) -> Node:
|
|
1692
1837
|
left = self.parse_factor()
|
|
1693
|
-
while self.peek().type in ('MUL', 'DIV', 'MOD'):
|
|
1838
|
+
while self.peek().type in ('MUL', 'DIV', 'MOD', 'TIMES'):
|
|
1839
|
+
# Disambiguate "repeat 3 TIMES" vs "3 TIMES 4"
|
|
1840
|
+
if self.peek().type == 'TIMES':
|
|
1841
|
+
next_tok = self.peek(1)
|
|
1842
|
+
if next_tok.type in ('COLON', 'NEWLINE'):
|
|
1843
|
+
break
|
|
1844
|
+
|
|
1694
1845
|
op_token = self.consume()
|
|
1846
|
+
op_val = op_token.value
|
|
1847
|
+
|
|
1848
|
+
# Normalize
|
|
1849
|
+
if op_token.type == 'MUL': op_val = '*'
|
|
1850
|
+
if op_token.type == 'TIMES': op_val = '*'
|
|
1851
|
+
if op_token.type == 'DIV':
|
|
1852
|
+
op_val = '/'
|
|
1853
|
+
if self.check('BY'): self.consume('BY') # Handle "divided by"
|
|
1854
|
+
|
|
1855
|
+
if op_token.type == 'MOD': op_val = '%'
|
|
1856
|
+
|
|
1695
1857
|
right = self.parse_factor()
|
|
1696
|
-
new_node = BinOp(left,
|
|
1858
|
+
new_node = BinOp(left, op_val, right)
|
|
1697
1859
|
new_node.line = op_token.line
|
|
1698
1860
|
left = new_node
|
|
1699
1861
|
return left
|
|
@@ -1814,3 +1976,268 @@ class Parser:
|
|
|
1814
1976
|
node = FileWrite(path, content, 'a')
|
|
1815
1977
|
node.line = token.line
|
|
1816
1978
|
return node
|
|
1979
|
+
|
|
1980
|
+
|
|
1981
|
+
def parse_increment(self) -> Assign:
|
|
1982
|
+
|
|
1983
|
+
token = self.consume('INCREMENT')
|
|
1984
|
+
|
|
1985
|
+
name = self.consume('ID').value
|
|
1986
|
+
|
|
1987
|
+
amount = Number(1)
|
|
1988
|
+
|
|
1989
|
+
if self.check('BY'):
|
|
1990
|
+
|
|
1991
|
+
self.consume('BY')
|
|
1992
|
+
|
|
1993
|
+
amount = self.parse_expression()
|
|
1994
|
+
|
|
1995
|
+
self.consume('NEWLINE')
|
|
1996
|
+
|
|
1997
|
+
node = Assign(name, BinOp(VarAccess(name), '+', amount))
|
|
1998
|
+
|
|
1999
|
+
node.line = token.line
|
|
2000
|
+
|
|
2001
|
+
return node
|
|
2002
|
+
|
|
2003
|
+
|
|
2004
|
+
|
|
2005
|
+
def parse_decrement(self) -> Assign:
|
|
2006
|
+
|
|
2007
|
+
token = self.consume('DECREMENT')
|
|
2008
|
+
|
|
2009
|
+
name = self.consume('ID').value
|
|
2010
|
+
|
|
2011
|
+
amount = Number(1)
|
|
2012
|
+
|
|
2013
|
+
if self.check('BY'):
|
|
2014
|
+
|
|
2015
|
+
self.consume('BY')
|
|
2016
|
+
|
|
2017
|
+
amount = self.parse_expression()
|
|
2018
|
+
|
|
2019
|
+
self.consume('NEWLINE')
|
|
2020
|
+
|
|
2021
|
+
node = Assign(name, BinOp(VarAccess(name), '-', amount))
|
|
2022
|
+
|
|
2023
|
+
node.line = token.line
|
|
2024
|
+
|
|
2025
|
+
return node
|
|
2026
|
+
|
|
2027
|
+
|
|
2028
|
+
|
|
2029
|
+
def parse_multiply(self) -> Assign:
|
|
2030
|
+
|
|
2031
|
+
token = self.consume('MULTIPLY')
|
|
2032
|
+
|
|
2033
|
+
name = self.consume('ID').value
|
|
2034
|
+
|
|
2035
|
+
self.consume('BY')
|
|
2036
|
+
|
|
2037
|
+
amount = self.parse_expression()
|
|
2038
|
+
|
|
2039
|
+
self.consume('NEWLINE')
|
|
2040
|
+
|
|
2041
|
+
node = Assign(name, BinOp(VarAccess(name), '*', amount))
|
|
2042
|
+
|
|
2043
|
+
node.line = token.line
|
|
2044
|
+
|
|
2045
|
+
return node
|
|
2046
|
+
|
|
2047
|
+
|
|
2048
|
+
|
|
2049
|
+
def parse_divide(self) -> Assign:
|
|
2050
|
+
|
|
2051
|
+
token = self.consume('DIVIDE')
|
|
2052
|
+
|
|
2053
|
+
name = self.consume('ID').value
|
|
2054
|
+
|
|
2055
|
+
self.consume('BY')
|
|
2056
|
+
|
|
2057
|
+
amount = self.parse_expression()
|
|
2058
|
+
|
|
2059
|
+
self.consume('NEWLINE')
|
|
2060
|
+
|
|
2061
|
+
node = Assign(name, BinOp(VarAccess(name), '/', amount))
|
|
2062
|
+
|
|
2063
|
+
node.line = token.line
|
|
2064
|
+
|
|
2065
|
+
return node
|
|
2066
|
+
|
|
2067
|
+
|
|
2068
|
+
|
|
2069
|
+
def parse_set(self) -> Assign:
|
|
2070
|
+
|
|
2071
|
+
token = self.consume('SET')
|
|
2072
|
+
|
|
2073
|
+
name = self.consume('ID').value
|
|
2074
|
+
|
|
2075
|
+
self.consume('TO')
|
|
2076
|
+
|
|
2077
|
+
value = self.parse_expression()
|
|
2078
|
+
|
|
2079
|
+
self.consume('NEWLINE')
|
|
2080
|
+
|
|
2081
|
+
node = Assign(name, value)
|
|
2082
|
+
|
|
2083
|
+
node.line = token.line
|
|
2084
|
+
|
|
2085
|
+
return node
|
|
2086
|
+
|
|
2087
|
+
|
|
2088
|
+
|
|
2089
|
+
def parse_sum(self) -> Node:
|
|
2090
|
+
token = self.consume('SUM')
|
|
2091
|
+
self.consume('OF')
|
|
2092
|
+
|
|
2093
|
+
# Check for 'numbers from ...' (contextual keyword 'numbers')
|
|
2094
|
+
if self.check('ID') and self.peek().value == 'numbers':
|
|
2095
|
+
range_node = self.parse_numbers_range()
|
|
2096
|
+
# range_node is Call('range_list', ...)
|
|
2097
|
+
# We want Call('sum', [range_node])
|
|
2098
|
+
node = Call('sum', [range_node])
|
|
2099
|
+
node.line = token.line
|
|
2100
|
+
return node
|
|
2101
|
+
|
|
2102
|
+
expr = self.parse_expression()
|
|
2103
|
+
node = Call('sum', [expr])
|
|
2104
|
+
node.line = token.line
|
|
2105
|
+
return node
|
|
2106
|
+
|
|
2107
|
+
|
|
2108
|
+
|
|
2109
|
+
def parse_upper(self) -> Node:
|
|
2110
|
+
token = self.consume('UPPER')
|
|
2111
|
+
expr = self.parse_expression()
|
|
2112
|
+
only_letters = Boolean(False)
|
|
2113
|
+
|
|
2114
|
+
if self.check('ID') and self.peek().value == 'only':
|
|
2115
|
+
self.consume() # consume 'only'
|
|
2116
|
+
if self.check('ID') and self.peek().value == 'letters':
|
|
2117
|
+
self.consume() # consume 'letters'
|
|
2118
|
+
only_letters = Boolean(True)
|
|
2119
|
+
node = Call('upper', [expr, only_letters])
|
|
2120
|
+
node.line = token.line
|
|
2121
|
+
return node
|
|
2122
|
+
|
|
2123
|
+
|
|
2124
|
+
|
|
2125
|
+
def parse_numbers_range(self) -> Node:
|
|
2126
|
+
# Expect 'numbers' as ID
|
|
2127
|
+
token = self.peek()
|
|
2128
|
+
if self.check('ID') and self.peek().value == 'numbers':
|
|
2129
|
+
self.consume()
|
|
2130
|
+
else:
|
|
2131
|
+
# Should be 'numbers' but if called from parse_sum we assume check passed.
|
|
2132
|
+
# If called from Factor loop...
|
|
2133
|
+
pass
|
|
2134
|
+
|
|
2135
|
+
self.consume('FROM')
|
|
2136
|
+
start = self.parse_expression()
|
|
2137
|
+
self.consume('TO')
|
|
2138
|
+
end = self.parse_expression()
|
|
2139
|
+
|
|
2140
|
+
condition = None
|
|
2141
|
+
if self.check('ID') and self.peek().value == 'that':
|
|
2142
|
+
self.consume() # that
|
|
2143
|
+
if self.check('ID') and self.peek().value == 'are':
|
|
2144
|
+
self.consume() # are
|
|
2145
|
+
|
|
2146
|
+
if self.check('ID') and self.peek().value == 'prime':
|
|
2147
|
+
self.consume() # prime
|
|
2148
|
+
condition = String('prime')
|
|
2149
|
+
elif self.check('ID') and self.peek().value == 'digits':
|
|
2150
|
+
self.consume() # digits
|
|
2151
|
+
condition = String('digits')
|
|
2152
|
+
|
|
2153
|
+
elif self.check('WHEN'):
|
|
2154
|
+
self.consume('WHEN')
|
|
2155
|
+
# 'when even' -> check for ID 'even' or expression?
|
|
2156
|
+
# User example: 'when even'. Implicit variable?
|
|
2157
|
+
# Let's verify repro: 'when even'
|
|
2158
|
+
if self.check('ID') and self.peek().value == 'even':
|
|
2159
|
+
self.consume()
|
|
2160
|
+
condition = String('even')
|
|
2161
|
+
elif self.check('ID') and self.peek().value == 'odd':
|
|
2162
|
+
self.consume()
|
|
2163
|
+
condition = String('odd')
|
|
2164
|
+
else:
|
|
2165
|
+
# TODO: handle generic expression filter if needed
|
|
2166
|
+
pass
|
|
2167
|
+
|
|
2168
|
+
node = Call('range_list', [start, end, condition if condition else Boolean(False)])
|
|
2169
|
+
node.line = token.line
|
|
2170
|
+
return node
|
|
2171
|
+
def parse_add_to_list(self) -> Node:
|
|
2172
|
+
token = self.consume('ADD')
|
|
2173
|
+
item = self.parse_expression()
|
|
2174
|
+
self.consume('TO')
|
|
2175
|
+
list_expr = self.parse_expression()
|
|
2176
|
+
self.consume('NEWLINE')
|
|
2177
|
+
node = Call('append', [list_expr, item])
|
|
2178
|
+
node.line = token.line
|
|
2179
|
+
return node
|
|
2180
|
+
|
|
2181
|
+
def parse_remove_from_list(self) -> Node:
|
|
2182
|
+
token = self.consume('REMOVE')
|
|
2183
|
+
item = self.parse_expression()
|
|
2184
|
+
self.consume('FROM')
|
|
2185
|
+
list_expr = self.parse_expression()
|
|
2186
|
+
self.consume('NEWLINE')
|
|
2187
|
+
node = Call('remove', [list_expr, item])
|
|
2188
|
+
node.line = token.line
|
|
2189
|
+
return node
|
|
2190
|
+
|
|
2191
|
+
def parse_wait(self) -> Node:
|
|
2192
|
+
token = self.consume('WAIT')
|
|
2193
|
+
value = self.parse_expression()
|
|
2194
|
+
if self.check('SECOND'): self.consume('SECOND')
|
|
2195
|
+
elif self.check('SECONDS'): self.consume('SECONDS') # Assuming 'SECONDS' token maps to SECOND?
|
|
2196
|
+
# Actually I need to check lexer mapping for MINUTES/SECONDS.
|
|
2197
|
+
# Lexer has: 'minute': 'MINUTE', 'minutes': 'MINUTE', 'second': 'SECOND', 'seconds': 'SECOND'
|
|
2198
|
+
|
|
2199
|
+
elif self.check('MINUTE'):
|
|
2200
|
+
self.consume('MINUTE')
|
|
2201
|
+
value = BinOp(value, '*', Number(60))
|
|
2202
|
+
|
|
2203
|
+
self.consume('NEWLINE')
|
|
2204
|
+
node = Call('wait', [value])
|
|
2205
|
+
node.line = token.line
|
|
2206
|
+
return node
|
|
2207
|
+
|
|
2208
|
+
def parse_add_distinguish(self) -> Node:
|
|
2209
|
+
# Distinguish "ADD <expr> TO <list>" vs "ADD <component> ..."
|
|
2210
|
+
tok = self.peek(1)
|
|
2211
|
+
if tok.type in ('BUTTON', 'HEADING', 'PARAGRAPH', 'IMAGE', 'APP', 'PAGE', 'Use', 'INPUT', 'TEXT'):
|
|
2212
|
+
return self.parse_add_to()
|
|
2213
|
+
else:
|
|
2214
|
+
return self.parse_add_to_list()
|
|
2215
|
+
|
|
2216
|
+
def parse_make_assignment(self) -> Node:
|
|
2217
|
+
token = self.consume('MAKE')
|
|
2218
|
+
name = self.consume('ID').value
|
|
2219
|
+
if self.check('BE'): self.consume('BE')
|
|
2220
|
+
value = self.parse_expression()
|
|
2221
|
+
self.consume('NEWLINE')
|
|
2222
|
+
node = Assign(name, value)
|
|
2223
|
+
node.line = token.line
|
|
2224
|
+
return node
|
|
2225
|
+
|
|
2226
|
+
def parse_as_long_as(self) -> While:
|
|
2227
|
+
start_token = self.consume('AS')
|
|
2228
|
+
self.consume('LONG')
|
|
2229
|
+
self.consume('AS')
|
|
2230
|
+
condition = self.parse_expression()
|
|
2231
|
+
if self.check('COLON'): self.consume('COLON')
|
|
2232
|
+
self.consume('NEWLINE')
|
|
2233
|
+
self.consume('INDENT')
|
|
2234
|
+
body = []
|
|
2235
|
+
while not self.check('DEDENT') and not self.check('EOF'):
|
|
2236
|
+
while self.check('NEWLINE'): self.consume()
|
|
2237
|
+
if self.check('DEDENT'): break
|
|
2238
|
+
body.append(self.parse_statement())
|
|
2239
|
+
self.consume('DEDENT')
|
|
2240
|
+
node = While(condition, body)
|
|
2241
|
+
node.line = start_token.line
|
|
2242
|
+
return node
|
|
2243
|
+
|
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
shell_lite/__init__.py,sha256=frcCV1k9oG9oKj3dpUqdJg1PxRT2RSN_XKdLCPjaYaY,2
|
|
2
|
+
shell_lite/ast_nodes.py,sha256=KGFlZmP21MJXo6uRYRJfo_R3BEspfDD8xMBtAVfUDgU,5873
|
|
3
|
+
shell_lite/cli.py,sha256=14Kq1ohSXS3p-xdh0DPi7eXskUtSX81huSyGhktoOMA,250
|
|
4
|
+
shell_lite/compiler.py,sha256=FcwDLbrjnMnH0VMzEeYPSqD3nIeFAwKIdvEDAT1e3GE,24984
|
|
5
|
+
shell_lite/interpreter.py,sha256=ZSbaqTDFfLmgzVpADQSs3_YcJkYAouKTafYGxY7Me3I,77916
|
|
6
|
+
shell_lite/lexer.py,sha256=TdU2QIfxjZqnKDUz_SBECNC74k4ZeQBpX27xFjZImEo,13441
|
|
7
|
+
shell_lite/main.py,sha256=lvycBSUentB3PfYT0boH5uSejvoqFpeSZCvgnNLuvJU,22960
|
|
8
|
+
shell_lite/parser.py,sha256=ZskS-zclccxsx2_gQzFrWmSN1f43pgnS6vSi60WRlyg,88196
|
|
9
|
+
shell_lite/runtime.py,sha256=pSjBeA1dTQ-a94q3FLdv9lqZurdd6MJmfhFGHhOoQEM,16057
|
|
10
|
+
shell_lite-0.4.4.dist-info/LICENSE,sha256=33eziKLPxbqGCqdHtEHAFe1KSOgqc0-jWUQmdgKq85Q,1092
|
|
11
|
+
shell_lite-0.4.4.dist-info/METADATA,sha256=Z2Rcc3pNZfpRbM6WkuXwwk7dHJRNPRi-eFaULBzc5l8,2475
|
|
12
|
+
shell_lite-0.4.4.dist-info/WHEEL,sha256=tZoeGjtWxWRfdplE7E3d45VPlLNQnvbKiYnx7gwAy8A,92
|
|
13
|
+
shell_lite-0.4.4.dist-info/entry_points.txt,sha256=tglL8tjyPIh1W85j6zFpNZjMpQe_xC-k-7BOhHLWfxc,45
|
|
14
|
+
shell_lite-0.4.4.dist-info/top_level.txt,sha256=hIln5ltrok_Mn3ijlQeqMFF6hHBHCyhzqCO7KL358cg,11
|
|
15
|
+
shell_lite-0.4.4.dist-info/RECORD,,
|
shell_lite/formatter.py
DELETED
|
@@ -1,75 +0,0 @@
|
|
|
1
|
-
from typing import List
|
|
2
|
-
from .lexer import Lexer, Token
|
|
3
|
-
class Formatter:
|
|
4
|
-
def __init__(self, source_code: str):
|
|
5
|
-
self.source_code = source_code
|
|
6
|
-
self.indent_size = 4
|
|
7
|
-
def format(self) -> str:
|
|
8
|
-
lexer = Lexer(self.source_code)
|
|
9
|
-
try:
|
|
10
|
-
tokens = lexer.tokenize()
|
|
11
|
-
except Exception:
|
|
12
|
-
raise
|
|
13
|
-
formatted_lines = []
|
|
14
|
-
current_indent = 0
|
|
15
|
-
current_line_tokens: List[Token] = []
|
|
16
|
-
def flush_line():
|
|
17
|
-
nonlocal current_line_tokens
|
|
18
|
-
if not current_line_tokens:
|
|
19
|
-
pass
|
|
20
|
-
line_str = self._format_line_tokens(current_line_tokens, current_indent)
|
|
21
|
-
formatted_lines.append(line_str)
|
|
22
|
-
current_line_tokens.clear()
|
|
23
|
-
for token in tokens:
|
|
24
|
-
if token.type == 'EOF':
|
|
25
|
-
if current_line_tokens:
|
|
26
|
-
flush_line()
|
|
27
|
-
break
|
|
28
|
-
elif token.type == 'INDENT':
|
|
29
|
-
current_indent += 1
|
|
30
|
-
elif token.type == 'DEDENT':
|
|
31
|
-
current_indent -= 1
|
|
32
|
-
if current_indent < 0: current_indent = 0
|
|
33
|
-
elif token.type == 'NEWLINE':
|
|
34
|
-
flush_line()
|
|
35
|
-
pass
|
|
36
|
-
else:
|
|
37
|
-
current_line_tokens.append(token)
|
|
38
|
-
return '\n'.join(formatted_lines)
|
|
39
|
-
def _format_line_tokens(self, tokens: List[Token], indent_level: int) -> str:
|
|
40
|
-
if not tokens:
|
|
41
|
-
return ''
|
|
42
|
-
line_parts = []
|
|
43
|
-
line_parts.append(' ' * (indent_level * self.indent_size))
|
|
44
|
-
for i, token in enumerate(tokens):
|
|
45
|
-
val = token.value
|
|
46
|
-
type = token.type
|
|
47
|
-
if type == 'STRING':
|
|
48
|
-
if '"' in val and "'" not in val:
|
|
49
|
-
val = f"'{val}'"
|
|
50
|
-
else:
|
|
51
|
-
val = val.replace('"', '\\"')
|
|
52
|
-
val = f'"{val}"'
|
|
53
|
-
elif type == 'REGEX':
|
|
54
|
-
val = f"/{val}/"
|
|
55
|
-
if i > 0:
|
|
56
|
-
prev = tokens[i-1]
|
|
57
|
-
need_space = True
|
|
58
|
-
if prev.type in ('LPAREN', 'LBRACKET', 'LBRACE', 'DOT', 'AT'):
|
|
59
|
-
need_space = False
|
|
60
|
-
if type in ('RPAREN', 'RBRACKET', 'RBRACE', 'DOT', 'COMMA', 'COLON'):
|
|
61
|
-
need_space = False
|
|
62
|
-
if type == 'LPAREN':
|
|
63
|
-
if prev.type == 'ID':
|
|
64
|
-
need_space = False
|
|
65
|
-
elif prev.type in ('RPAREN', 'RBRACKET', 'STRING'):
|
|
66
|
-
need_space = False
|
|
67
|
-
else:
|
|
68
|
-
pass
|
|
69
|
-
if type == 'LBRACKET':
|
|
70
|
-
if prev.type in ('ID', 'STRING', 'RPAREN', 'RBRACKET'):
|
|
71
|
-
need_space = False
|
|
72
|
-
if need_space:
|
|
73
|
-
line_parts.append(' ')
|
|
74
|
-
line_parts.append(val)
|
|
75
|
-
return "".join(line_parts).rstrip()
|
shell_lite/js_compiler.py
DELETED
|
@@ -1,220 +0,0 @@
|
|
|
1
|
-
import random
|
|
2
|
-
from typing import List
|
|
3
|
-
from .ast_nodes import *
|
|
4
|
-
class JSCompiler:
|
|
5
|
-
def __init__(self):
|
|
6
|
-
self.indentation = 0
|
|
7
|
-
def indent(self):
|
|
8
|
-
return " " * self.indentation
|
|
9
|
-
def visit(self, node: Node) -> str:
|
|
10
|
-
method_name = f'visit_{type(node).__name__}'
|
|
11
|
-
visitor = getattr(self, method_name, self.generic_visit)
|
|
12
|
-
return visitor(node)
|
|
13
|
-
def generic_visit(self, node: Node):
|
|
14
|
-
raise Exception(f"JSCompiler does not support {type(node).__name__}")
|
|
15
|
-
def compile_block(self, statements: List[Node]) -> str:
|
|
16
|
-
if not statements:
|
|
17
|
-
return ""
|
|
18
|
-
code = ""
|
|
19
|
-
for stmt in statements:
|
|
20
|
-
stmt_code = self.visit(stmt)
|
|
21
|
-
if not stmt_code: continue
|
|
22
|
-
indented_stmt = "\n".join([f"{self.indent()}{line}" for line in stmt_code.split('\n')])
|
|
23
|
-
code += indented_stmt + "\n"
|
|
24
|
-
return code.rstrip()
|
|
25
|
-
def compile(self, statements: List[Node]) -> str:
|
|
26
|
-
code = [
|
|
27
|
-
"// ShellLite Runtime (JS)",
|
|
28
|
-
"const fs = require('fs');",
|
|
29
|
-
"const path = require('path');",
|
|
30
|
-
"const https = require('https');",
|
|
31
|
-
"const { execSync } = require('child_process');",
|
|
32
|
-
"",
|
|
33
|
-
"// Builtins",
|
|
34
|
-
"const say = console.log;",
|
|
35
|
-
"const print = console.log;",
|
|
36
|
-
"const range = (n) => [...Array(n).keys()];",
|
|
37
|
-
"const int = (x) => parseInt(x);",
|
|
38
|
-
"const str = (x) => String(x);",
|
|
39
|
-
"const float = (x) => parseFloat(x);",
|
|
40
|
-
"const len = (x) => x.length;",
|
|
41
|
-
"",
|
|
42
|
-
"// Utils",
|
|
43
|
-
"const _slang_download = (url) => { console.log('Download not impl in minimal JS runtime'); };",
|
|
44
|
-
"",
|
|
45
|
-
"// --- User Code ---",
|
|
46
|
-
""
|
|
47
|
-
]
|
|
48
|
-
code.append(self.compile_block(statements))
|
|
49
|
-
return "\n".join(code)
|
|
50
|
-
def visit_Number(self, node: Number):
|
|
51
|
-
return str(node.value)
|
|
52
|
-
def visit_String(self, node: String):
|
|
53
|
-
return repr(node.value)
|
|
54
|
-
def visit_Boolean(self, node: Boolean):
|
|
55
|
-
return "true" if node.value else "false"
|
|
56
|
-
def visit_Regex(self, node: Regex):
|
|
57
|
-
return f"/{node.pattern}/"
|
|
58
|
-
def visit_ListVal(self, node: ListVal):
|
|
59
|
-
elements = [self.visit(e) for e in node.elements]
|
|
60
|
-
return f"[{', '.join(elements)}]"
|
|
61
|
-
def visit_Dictionary(self, node: Dictionary):
|
|
62
|
-
pairs = [f"{self.visit(k)}: {self.visit(v)}" for k, v in node.pairs]
|
|
63
|
-
return f"{{{', '.join(pairs)}}}"
|
|
64
|
-
def visit_SetVal(self, node: SetVal):
|
|
65
|
-
elements = [self.visit(e) for e in node.elements]
|
|
66
|
-
return f"new Set([{', '.join(elements)}])"
|
|
67
|
-
def visit_VarAccess(self, node: VarAccess):
|
|
68
|
-
return node.name
|
|
69
|
-
def visit_Assign(self, node: Assign):
|
|
70
|
-
return f"var {node.name} = {self.visit(node.value)};"
|
|
71
|
-
def visit_ConstAssign(self, node: ConstAssign):
|
|
72
|
-
return f"const {node.name} = {self.visit(node.value)};"
|
|
73
|
-
def visit_PropertyAssign(self, node: PropertyAssign):
|
|
74
|
-
return f"{node.instance_name}.{node.property_name} = {self.visit(node.value)};"
|
|
75
|
-
def visit_BinOp(self, node: BinOp):
|
|
76
|
-
left = self.visit(node.left)
|
|
77
|
-
right = self.visit(node.right)
|
|
78
|
-
op = node.op
|
|
79
|
-
js_ops = {
|
|
80
|
-
'matches': None,
|
|
81
|
-
'and': '&&',
|
|
82
|
-
'or': '||',
|
|
83
|
-
'==': '==='
|
|
84
|
-
}
|
|
85
|
-
if op == 'matches':
|
|
86
|
-
return f"new RegExp({right}).test({left})"
|
|
87
|
-
real_op = js_ops.get(op, op)
|
|
88
|
-
return f"({left} {real_op} {right})"
|
|
89
|
-
def visit_UnaryOp(self, node: UnaryOp):
|
|
90
|
-
return f"({node.op} {self.visit(node.right)})"
|
|
91
|
-
def visit_Print(self, node: Print):
|
|
92
|
-
return f"console.log({self.visit(node.expression)});"
|
|
93
|
-
def visit_Input(self, node: Input):
|
|
94
|
-
return f"require('readline-sync').question({repr(node.prompt) if node.prompt else '\"\"'})"
|
|
95
|
-
def visit_If(self, node: If):
|
|
96
|
-
code = f"if ({self.visit(node.condition)}) {{\n"
|
|
97
|
-
self.indentation += 1
|
|
98
|
-
code += self.compile_block(node.body)
|
|
99
|
-
self.indentation -= 1
|
|
100
|
-
code += f"\n{self.indent()}}}"
|
|
101
|
-
if node.else_body:
|
|
102
|
-
code += f" else {{\n"
|
|
103
|
-
self.indentation += 1
|
|
104
|
-
code += self.compile_block(node.else_body)
|
|
105
|
-
self.indentation -= 1
|
|
106
|
-
code += f"\n{self.indent()}}}"
|
|
107
|
-
return code
|
|
108
|
-
def visit_While(self, node: While):
|
|
109
|
-
code = f"while ({self.visit(node.condition)}) {{\n"
|
|
110
|
-
self.indentation += 1
|
|
111
|
-
code += self.compile_block(node.body)
|
|
112
|
-
self.indentation -= 1
|
|
113
|
-
code += f"\n{self.indent()}}}"
|
|
114
|
-
return code
|
|
115
|
-
def visit_For(self, node: For):
|
|
116
|
-
count = self.visit(node.count)
|
|
117
|
-
var = f"_i_{random.randint(0,1000)}"
|
|
118
|
-
code = f"for (let {var} = 0; {var} < {count}; {var}++) {{\n"
|
|
119
|
-
self.indentation += 1
|
|
120
|
-
code += self.compile_block(node.body)
|
|
121
|
-
self.indentation -= 1
|
|
122
|
-
code += f"\n{self.indent()}}}"
|
|
123
|
-
return code
|
|
124
|
-
def visit_ForIn(self, node: ForIn):
|
|
125
|
-
code = f"for (let {node.var_name} of {self.visit(node.iterable)}) {{\n"
|
|
126
|
-
self.indentation += 1
|
|
127
|
-
code += self.compile_block(node.body)
|
|
128
|
-
self.indentation -= 1
|
|
129
|
-
code += f"\n{self.indent()}}}"
|
|
130
|
-
return code
|
|
131
|
-
def visit_Repeat(self, node: Repeat):
|
|
132
|
-
return self.visit_For(For(node.count, node.body))
|
|
133
|
-
def visit_FunctionDef(self, node: FunctionDef):
|
|
134
|
-
args = [arg[0] for arg in node.args]
|
|
135
|
-
code = f"function {node.name}({', '.join(args)}) {{\n"
|
|
136
|
-
self.indentation += 1
|
|
137
|
-
code += self.compile_block(node.body)
|
|
138
|
-
self.indentation -= 1
|
|
139
|
-
code += f"\n{self.indent()}}}"
|
|
140
|
-
return code
|
|
141
|
-
def visit_Return(self, node: Return):
|
|
142
|
-
return f"return {self.visit(node.value)};"
|
|
143
|
-
def visit_Call(self, node: Call):
|
|
144
|
-
args = [self.visit(a) for a in node.args]
|
|
145
|
-
return f"{node.name}({', '.join(args)})"
|
|
146
|
-
def visit_ClassDef(self, node: ClassDef):
|
|
147
|
-
parent = node.parent if node.parent else ""
|
|
148
|
-
extends = f" extends {parent}" if parent else ""
|
|
149
|
-
code = f"class {node.name}{extends} {{\n"
|
|
150
|
-
self.indentation += 1
|
|
151
|
-
if node.properties:
|
|
152
|
-
props = []
|
|
153
|
-
assigns = []
|
|
154
|
-
for p in node.properties:
|
|
155
|
-
if isinstance(p, tuple):
|
|
156
|
-
name, default = p
|
|
157
|
-
if default:
|
|
158
|
-
# JS 6 supports defaults in args
|
|
159
|
-
props.append(f"{name} = {self.visit(default)}")
|
|
160
|
-
else:
|
|
161
|
-
props.append(name)
|
|
162
|
-
assigns.append(f"self.{name} = {name};")
|
|
163
|
-
else:
|
|
164
|
-
props.append(p)
|
|
165
|
-
assigns.append(f"self.{p} = {p};")
|
|
166
|
-
|
|
167
|
-
code += f"{self.indent()}constructor({', '.join(props)}) {{\n"
|
|
168
|
-
self.indentation += 1
|
|
169
|
-
if parent: code += f"{self.indent()}super();\n"
|
|
170
|
-
for assign in assigns:
|
|
171
|
-
code += f"{self.indent()}{assign}\n"
|
|
172
|
-
self.indentation -= 1
|
|
173
|
-
code += f"{self.indent()}}}\n"
|
|
174
|
-
for m in node.methods:
|
|
175
|
-
args = [arg[0] for arg in m.args]
|
|
176
|
-
code += f"\n{self.indent()}{m.name}({', '.join(args)}) {{\n"
|
|
177
|
-
self.indentation += 1
|
|
178
|
-
code += self.compile_block(m.body)
|
|
179
|
-
self.indentation -= 1
|
|
180
|
-
code += f"\n{self.indent()}}}"
|
|
181
|
-
self.indentation -= 1
|
|
182
|
-
code += f"\n{self.indent()}}}"
|
|
183
|
-
return code
|
|
184
|
-
def visit_Instantiation(self, node: Instantiation):
|
|
185
|
-
args = [self.visit(a) for a in node.args]
|
|
186
|
-
return f"var {node.var_name} = new {node.class_name}({', '.join(args)});"
|
|
187
|
-
def visit_MethodCall(self, node: MethodCall):
|
|
188
|
-
args = [self.visit(a) for a in node.args]
|
|
189
|
-
return f"{node.instance_name}.{node.method_name}({', '.join(args)})"
|
|
190
|
-
def visit_PropertyAccess(self, node: PropertyAccess):
|
|
191
|
-
return f"{node.instance_name}.{node.property_name}"
|
|
192
|
-
def visit_Import(self, node: Import):
|
|
193
|
-
base = node.path
|
|
194
|
-
if base == 'vscode': return 'const vscode = require("vscode");'
|
|
195
|
-
return f"const {base} = require('./{base}');"
|
|
196
|
-
def visit_ImportAs(self, node: ImportAs):
|
|
197
|
-
path = node.path
|
|
198
|
-
if path == 'vscode': return f"const {node.alias} = require('vscode');"
|
|
199
|
-
return f"const {node.alias} = require('./{path}');"
|
|
200
|
-
def visit_Try(self, node: Try):
|
|
201
|
-
code = f"try {{\n"
|
|
202
|
-
self.indentation += 1
|
|
203
|
-
code += self.compile_block(node.try_body)
|
|
204
|
-
self.indentation -= 1
|
|
205
|
-
code += f"\n{self.indent()}}} catch ({node.catch_var}) {{\n"
|
|
206
|
-
self.indentation += 1
|
|
207
|
-
code += self.compile_block(node.catch_body)
|
|
208
|
-
self.indentation -= 1
|
|
209
|
-
code += f"\n{self.indent()}}}"
|
|
210
|
-
return code
|
|
211
|
-
def visit_Throw(self, node: Throw):
|
|
212
|
-
return f"throw new Error({self.visit(node.message)});"
|
|
213
|
-
def visit_Skip(self, node: Skip):
|
|
214
|
-
return "continue;"
|
|
215
|
-
def visit_Stop(self, node: Stop):
|
|
216
|
-
return "break;"
|
|
217
|
-
def visit_Lambda(self, node: Lambda):
|
|
218
|
-
return f"({', '.join(node.params)}) => {self.visit(node.body)}"
|
|
219
|
-
def visit_Execute(self, node: Execute):
|
|
220
|
-
pass
|
|
@@ -1,17 +0,0 @@
|
|
|
1
|
-
shell_lite/__init__.py,sha256=frcCV1k9oG9oKj3dpUqdJg1PxRT2RSN_XKdLCPjaYaY,2
|
|
2
|
-
shell_lite/ast_nodes.py,sha256=KGFlZmP21MJXo6uRYRJfo_R3BEspfDD8xMBtAVfUDgU,5873
|
|
3
|
-
shell_lite/cli.py,sha256=14Kq1ohSXS3p-xdh0DPi7eXskUtSX81huSyGhktoOMA,250
|
|
4
|
-
shell_lite/compiler.py,sha256=FcwDLbrjnMnH0VMzEeYPSqD3nIeFAwKIdvEDAT1e3GE,24984
|
|
5
|
-
shell_lite/formatter.py,sha256=590BfQmhsX466i5_xONXAhgVE97zfcV79q1wA3DT47A,2952
|
|
6
|
-
shell_lite/interpreter.py,sha256=az_8oe82bqQFYm8ytbNG5FgFCT8UG9IG49pHM54ewBM,74614
|
|
7
|
-
shell_lite/js_compiler.py,sha256=b2-XyGqm2BYUfwU0OsphpMxLNeho1ihL7htAcg3uWII,9438
|
|
8
|
-
shell_lite/lexer.py,sha256=UDQtFX7UW3HgMftQ3HsCu9tfImk9vsyMX5icsAI6NuE,12549
|
|
9
|
-
shell_lite/main.py,sha256=Fy6L2P7FEmfNIkUqNW5IcmLHcd2Ef8bqnh0PocUsPJk,22435
|
|
10
|
-
shell_lite/parser.py,sha256=Eh-LjIX1AdYyuhPaL-_GPlDoSzDM0RxDNQsBWog-06k,73377
|
|
11
|
-
shell_lite/runtime.py,sha256=pSjBeA1dTQ-a94q3FLdv9lqZurdd6MJmfhFGHhOoQEM,16057
|
|
12
|
-
shell_lite-0.4.2.dist-info/LICENSE,sha256=33eziKLPxbqGCqdHtEHAFe1KSOgqc0-jWUQmdgKq85Q,1092
|
|
13
|
-
shell_lite-0.4.2.dist-info/METADATA,sha256=Vmjcs4LuvQvthMSnDAx7nXA-q4WoLHELI_JuIRb_Y0Q,2475
|
|
14
|
-
shell_lite-0.4.2.dist-info/WHEEL,sha256=tZoeGjtWxWRfdplE7E3d45VPlLNQnvbKiYnx7gwAy8A,92
|
|
15
|
-
shell_lite-0.4.2.dist-info/entry_points.txt,sha256=tglL8tjyPIh1W85j6zFpNZjMpQe_xC-k-7BOhHLWfxc,45
|
|
16
|
-
shell_lite-0.4.2.dist-info/top_level.txt,sha256=hIln5ltrok_Mn3ijlQeqMFF6hHBHCyhzqCO7KL358cg,11
|
|
17
|
-
shell_lite-0.4.2.dist-info/RECORD,,
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|