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 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': lambda s: s.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.1 is running!")
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
- if __name__ == '__main__':
1692
- import sys
1693
- if len(sys.argv) < 2:
1694
- print("Usage: python -m src.interpreter <file.shl>")
1695
- sys.exit(1)
1696
- filename = sys.argv[1]
1697
- try:
1698
- with open(filename, 'r', encoding='utf-8') as f:
1699
- code = f.read()
1700
- lexer = Lexer(code)
1701
- tokens = lexer.tokenize()
1702
- parser = Parser(tokens)
1703
- ast = parser.parse()
1704
- interpreter = Interpreter()
1705
- for stmt in ast:
1706
- interpreter.visit(stmt)
1707
- except Exception as e:
1708
- print(f"Error: {e}")
1709
- import traceback
1710
- traceback.print_exc()
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': 'FOR',
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': 'ID',
227
- 'column': 'ID', 'row': 'ID',
228
- 'button': 'ID', 'heading': 'HEADING', 'text': 'ID',
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.2 | Made by Shrey Naithani")
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.1) is installed!")
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
- return self.parse_add_to()
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) -> Make:
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 = self.consume('TO')
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('SERVER')
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
- node = If(condition, body, else_body)
1019
- node.line = token.line
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', 'TEXT'):
1225
- widget_type = self.consume().value
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 == 'NUMBER':
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' or token.type == 'ASK':
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.consume('FILE')
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
- op_val = '=='
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, op_token.value, right)
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, op_token.value, right)
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
+
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.1
2
2
  Name: shell-lite
3
- Version: 0.4.2
3
+ Version: 0.4.4
4
4
  Summary: A lightweight, English-like scripting language.
5
5
  Author-email: Shrey Naithani <contact@shelllite.tech>
6
6
  License: MIT
@@ -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,,