shell-lite 0.4.3__py3-none-any.whl → 0.4.5__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/parser.py CHANGED
@@ -80,10 +80,20 @@ class Parser:
80
80
  return self.parse_make()
81
81
  elif self.check('INPUT'):
82
82
  next_t = self.peek(1)
83
- if next_t.type in ('ID', 'TYPE', 'STRING', 'NAME', 'VALUE', 'CLASS', 'STYLE', 'ONCLICK', 'SRC', 'HREF', 'ACTION', 'METHOD'):
83
+ if next_t.type in ('ID', 'TYPE', 'STRING', 'NAME', 'VALUE', 'CLASS', 'STYLE', 'ONCLICK', 'SRC', 'HREF', 'ACTION', 'METHOD', 'PLACEHOLDER'):
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,16 +134,40 @@ 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()
136
- elif self.check('INCREMENT'):
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'):
137
171
  return self.parse_increment()
138
172
  elif self.check('DECREMENT'):
139
173
  return self.parse_decrement()
@@ -141,6 +175,17 @@ class Parser:
141
175
  return self.parse_multiply()
142
176
  elif self.check('DIVIDE'):
143
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()
144
189
  elif self.check('SET'):
145
190
  return self.parse_set()
146
191
  else:
@@ -343,24 +388,6 @@ class Parser:
343
388
  node = Make(class_name, args)
344
389
  node.line = token.line
345
390
  return node
346
- def parse_repeat(self) -> Repeat:
347
- token = self.consume('REPEAT')
348
- if self.check('NEWLINE'):
349
- raise SyntaxError(f"repeat requires a count on line {token.line}")
350
- count = self.parse_expression()
351
- if self.check('TIMES'):
352
- self.consume('TIMES')
353
- self.consume('NEWLINE')
354
- self.consume('INDENT')
355
- body = []
356
- while not self.check('DEDENT') and not self.check('EOF'):
357
- while self.check('NEWLINE'): self.consume()
358
- if self.check('DEDENT'): break
359
- body.append(self.parse_statement())
360
- self.consume('DEDENT')
361
- node = Repeat(count, body)
362
- node.line = token.line
363
- return node
364
391
  return node
365
392
  def parse_db_op(self) -> DatabaseOp:
366
393
  token = self.consume('DB')
@@ -494,7 +521,13 @@ class Parser:
494
521
  node.line = token.line
495
522
  return node
496
523
  def parse_function_def(self) -> FunctionDef:
497
- 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
+
498
531
  name = self.consume('ID').value
499
532
  args = []
500
533
  while self.check('ID'):
@@ -517,6 +550,7 @@ class Parser:
517
550
  self.consume('ASSIGN')
518
551
  default_val = self.parse_expression()
519
552
  args.append((arg_name, default_val, type_hint))
553
+ if self.check('DOING'): self.consume('DOING')
520
554
  if self.check('COLON'):
521
555
  self.consume('COLON')
522
556
  self.consume('NEWLINE')
@@ -919,8 +953,8 @@ class Parser:
919
953
  return node
920
954
  def parse_start_server(self) -> Node:
921
955
  token = self.consume('START')
922
- if self.check('SERVER'):
923
- self.consume('SERVER')
956
+ if self.check('SERVER') or self.check('WEBSITE') or (self.check('ID') and self.peek().value == 'website'):
957
+ self.consume()
924
958
  port = Number(8080)
925
959
  if self.check('ON'):
926
960
  self.consume('ON')
@@ -987,6 +1021,7 @@ class Parser:
987
1021
  def parse_if(self) -> If:
988
1022
  self.consume('IF')
989
1023
  condition = self.parse_expression()
1024
+ if self.check('COLON'): self.consume('COLON')
990
1025
  self.consume('NEWLINE')
991
1026
  self.consume('INDENT')
992
1027
  body = []
@@ -998,8 +1033,11 @@ class Parser:
998
1033
  else_body = None
999
1034
  if self.check('ELIF'):
1000
1035
  else_body = [self.parse_elif()]
1001
- elif self.check('ELSE'):
1002
- 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')
1003
1041
  self.consume('NEWLINE')
1004
1042
  self.consume('INDENT')
1005
1043
  else_body = []
@@ -1009,9 +1047,11 @@ class Parser:
1009
1047
  else_body.append(self.parse_statement())
1010
1048
  self.consume('DEDENT')
1011
1049
  return If(condition, body, else_body)
1050
+
1012
1051
  def parse_elif(self) -> If:
1013
1052
  token = self.consume('ELIF')
1014
1053
  condition = self.parse_expression()
1054
+ if self.check('COLON'): self.consume('COLON')
1015
1055
  self.consume('NEWLINE')
1016
1056
  self.consume('INDENT')
1017
1057
  body = []
@@ -1025,6 +1065,7 @@ class Parser:
1025
1065
  else_body = [self.parse_elif()]
1026
1066
  elif self.check('ELSE'):
1027
1067
  self.consume('ELSE')
1068
+ if self.check('COLON'): self.consume('COLON')
1028
1069
  self.consume('NEWLINE')
1029
1070
  self.consume('INDENT')
1030
1071
  else_body = []
@@ -1033,14 +1074,12 @@ class Parser:
1033
1074
  if self.check('DEDENT'): break
1034
1075
  else_body.append(self.parse_statement())
1035
1076
  self.consume('DEDENT')
1036
- node = If(condition, body, else_body)
1037
- node.line = token.line
1038
- node = If(condition, body, else_body)
1039
- node.line = token.line
1040
- return node
1077
+ return If(condition, body, else_body)
1078
+
1041
1079
  def parse_while(self) -> While:
1042
1080
  start_token = self.consume('WHILE')
1043
1081
  condition = self.parse_expression()
1082
+ if self.check('COLON'): self.consume('COLON')
1044
1083
  self.consume('NEWLINE')
1045
1084
  self.consume('INDENT')
1046
1085
  body = []
@@ -1052,8 +1091,28 @@ class Parser:
1052
1091
  node = While(condition, body)
1053
1092
  node.line = start_token.line
1054
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
+
1055
1112
  def parse_try(self) -> Try:
1056
1113
  start_token = self.consume('TRY')
1114
+ if self.check('COLON'):
1115
+ self.consume('COLON')
1057
1116
  self.consume('NEWLINE')
1058
1117
  self.consume('INDENT')
1059
1118
  try_body = []
@@ -1064,6 +1123,8 @@ class Parser:
1064
1123
  self.consume('DEDENT')
1065
1124
  self.consume('CATCH')
1066
1125
  catch_var = self.consume('ID').value
1126
+ if self.check('COLON'):
1127
+ self.consume('COLON')
1067
1128
  self.consume('NEWLINE')
1068
1129
  self.consume('INDENT')
1069
1130
  catch_body = []
@@ -1249,8 +1310,13 @@ class Parser:
1249
1310
  self.consume('DEDENT')
1250
1311
  return Layout(layout_type, children)
1251
1312
 
1252
- elif token.type in ('BUTTON', 'INPUT', 'HEADING', 'TEXT'):
1253
- 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
1254
1320
  label = self.consume('STRING').value
1255
1321
 
1256
1322
  var_name = None
@@ -1276,7 +1342,13 @@ class Parser:
1276
1342
 
1277
1343
  def parse_factor_simple(self) -> Node:
1278
1344
  token = self.peek()
1279
- 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':
1280
1352
  self.consume()
1281
1353
  val = token.value
1282
1354
  if '.' in val:
@@ -1353,7 +1425,7 @@ class Parser:
1353
1425
  expr = self.parse_expression()
1354
1426
  self.consume('RPAREN')
1355
1427
  return expr
1356
- elif token.type == 'INPUT' or token.type == 'ASK':
1428
+ elif token.type == 'INPUT':
1357
1429
  is_tag = False
1358
1430
  next_t = self.peek(1)
1359
1431
  if next_t.type in ('ID', 'TYPE', 'STRING', 'NAME', 'VALUE', 'CLASS', 'STYLE', 'ONCLICK', 'SRC', 'HREF', 'ACTION', 'METHOD'):
@@ -1382,6 +1454,8 @@ class Parser:
1382
1454
  node = UnaryOp('not', right)
1383
1455
  node.line = op.line
1384
1456
  return node
1457
+ elif token.type == 'ASK':
1458
+ return self.parse_factor_simple()
1385
1459
  elif token.type == 'LPAREN':
1386
1460
  self.consume('LPAREN')
1387
1461
  node = self.parse_expression()
@@ -1445,8 +1519,6 @@ class Parser:
1445
1519
  return self.parse_sum()
1446
1520
  elif token.type == 'UPPER':
1447
1521
  return self.parse_upper()
1448
- elif token.type == 'NUMBERS':
1449
- return self.parse_numbers_range()
1450
1522
  elif token.type == 'DATE':
1451
1523
  token = self.consume('DATE')
1452
1524
  s = self.consume('STRING').value
@@ -1494,6 +1566,8 @@ class Parser:
1494
1566
  return self._parse_natural_list()
1495
1567
  elif self.peek(1).type == 'UNIQUE' and self.peek(2).type == 'SET' and self.peek(3).type == 'OF':
1496
1568
  return self._parse_natural_set()
1569
+ if token.value == 'numbers' and self.peek(1).type == 'FROM':
1570
+ return self.parse_numbers_range()
1497
1571
  self.consume()
1498
1572
  instance_name = token.value
1499
1573
  method_name = None
@@ -1606,6 +1680,7 @@ class Parser:
1606
1680
  node.line = start_token.line
1607
1681
  return node
1608
1682
  start_token = self.consume('FOR')
1683
+ if self.check('EACH'): self.consume('EACH')
1609
1684
  if self.check('ID') and self.peek(1).type == 'IN':
1610
1685
  var_name = self.consume('ID').value
1611
1686
  self.consume('IN')
@@ -1642,6 +1717,8 @@ class Parser:
1642
1717
  count_expr = self.parse_expression()
1643
1718
  self.consume('IN')
1644
1719
  self.consume('RANGE')
1720
+ if self.check('COLON'):
1721
+ self.consume('COLON')
1645
1722
  self.consume('NEWLINE')
1646
1723
  self.consume('INDENT')
1647
1724
  body = []
@@ -1704,11 +1781,39 @@ class Parser:
1704
1781
  return left
1705
1782
  def parse_comparison(self) -> Node:
1706
1783
  left = self.parse_arithmetic()
1707
- 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'):
1708
1785
  op_token = self.consume()
1709
1786
  op_val = op_token.value
1787
+
1788
+ # Handle "is greater/less/equal"
1710
1789
  if op_token.type == 'IS':
1711
- 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
+
1712
1817
  right = self.parse_arithmetic()
1713
1818
  node = BinOp(left, op_val, right)
1714
1819
  node.line = op_token.line
@@ -1718,17 +1823,39 @@ class Parser:
1718
1823
  left = self.parse_term()
1719
1824
  while self.peek().type in ('PLUS', 'MINUS'):
1720
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
+
1721
1831
  right = self.parse_term()
1722
- new_node = BinOp(left, op_token.value, right)
1832
+ new_node = BinOp(left, op_val, right)
1723
1833
  new_node.line = op_token.line
1724
1834
  left = new_node
1725
1835
  return left
1726
1836
  def parse_term(self) -> Node:
1727
1837
  left = self.parse_factor()
1728
- 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
+
1729
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
+
1730
1857
  right = self.parse_factor()
1731
- new_node = BinOp(left, op_token.value, right)
1858
+ new_node = BinOp(left, op_val, right)
1732
1859
  new_node.line = op_token.line
1733
1860
  left = new_node
1734
1861
  return left
@@ -1960,134 +2087,157 @@ class Parser:
1960
2087
 
1961
2088
 
1962
2089
  def parse_sum(self) -> Node:
1963
-
1964
2090
  token = self.consume('SUM')
1965
-
1966
2091
  self.consume('OF')
1967
-
1968
2092
 
1969
-
1970
- # Check for 'numbers from ...'
1971
-
1972
- if self.check('NUMBERS'):
1973
-
2093
+ # Check for 'numbers from ...' (contextual keyword 'numbers')
2094
+ if self.check('ID') and self.peek().value == 'numbers':
1974
2095
  range_node = self.parse_numbers_range()
1975
-
1976
2096
  # range_node is Call('range_list', ...)
1977
-
1978
2097
  # We want Call('sum', [range_node])
1979
-
1980
2098
  node = Call('sum', [range_node])
1981
-
1982
2099
  node.line = token.line
1983
-
1984
2100
  return node
1985
-
1986
2101
 
1987
-
1988
2102
  expr = self.parse_expression()
1989
-
1990
2103
  node = Call('sum', [expr])
1991
-
1992
2104
  node.line = token.line
1993
-
1994
2105
  return node
1995
2106
 
1996
2107
 
1997
2108
 
1998
2109
  def parse_upper(self) -> Node:
1999
-
2000
2110
  token = self.consume('UPPER')
2001
-
2002
2111
  expr = self.parse_expression()
2003
-
2004
2112
  only_letters = Boolean(False)
2005
2113
 
2006
- if self.check('ONLY'):
2007
-
2008
- self.consume('ONLY')
2009
-
2010
- if self.check('LETTERS'):
2011
-
2012
- self.consume('LETTERS')
2013
-
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'
2014
2118
  only_letters = Boolean(True)
2015
-
2016
2119
  node = Call('upper', [expr, only_letters])
2017
-
2018
2120
  node.line = token.line
2019
-
2020
2121
  return node
2021
2122
 
2022
2123
 
2023
2124
 
2024
2125
  def parse_numbers_range(self) -> Node:
2025
-
2026
- token = self.consume('NUMBERS')
2027
-
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
+
2028
2135
  self.consume('FROM')
2029
-
2030
2136
  start = self.parse_expression()
2031
-
2032
2137
  self.consume('TO')
2033
-
2034
2138
  end = self.parse_expression()
2035
-
2036
2139
 
2037
-
2038
2140
  condition = None
2039
-
2040
- if self.check('THAT'):
2041
-
2042
- self.consume('THAT')
2043
-
2044
- self.consume('ARE')
2045
-
2046
- if self.check('PRIME'):
2047
-
2048
- self.consume('PRIME')
2049
-
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
2050
2148
  condition = String('prime')
2051
-
2052
- elif self.check('DIGITS'):
2053
-
2054
- self.consume('DIGITS')
2055
-
2149
+ elif self.check('ID') and self.peek().value == 'digits':
2150
+ self.consume() # digits
2056
2151
  condition = String('digits')
2057
-
2152
+
2058
2153
  elif self.check('WHEN'):
2059
-
2060
2154
  self.consume('WHEN')
2061
-
2062
2155
  # 'when even' -> check for ID 'even' or expression?
2063
-
2064
2156
  # User example: 'when even'. Implicit variable?
2065
-
2066
2157
  # Let's verify repro: 'when even'
2067
-
2068
2158
  if self.check('ID') and self.peek().value == 'even':
2069
-
2070
2159
  self.consume()
2071
-
2072
2160
  condition = String('even')
2073
-
2074
2161
  elif self.check('ID') and self.peek().value == 'odd':
2075
-
2076
2162
  self.consume()
2077
-
2078
2163
  condition = String('odd')
2079
-
2080
2164
  else:
2081
-
2082
2165
  # TODO: handle generic expression filter if needed
2083
-
2084
2166
  pass
2085
-
2086
2167
 
2087
-
2088
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
2089
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])
2090
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
2091
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
2092
2242
  return node
2093
2243
 
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.1
2
2
  Name: shell-lite
3
- Version: 0.4.3
3
+ Version: 0.4.5
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=iyRlaCgLTpGQjeyn5hDm1_4BpztmBjH45QYwaDoOtBg,78408
6
+ shell_lite/lexer.py,sha256=TdU2QIfxjZqnKDUz_SBECNC74k4ZeQBpX27xFjZImEo,13441
7
+ shell_lite/main.py,sha256=xkUy2KHp8zpQ6yQ3TulwI86a-PX63-kqVGeaOf6lp8s,22791
8
+ shell_lite/parser.py,sha256=IznHJxopliPX3MsI-P9SzB5UOkBCJlxwatw9wxMzDK0,88211
9
+ shell_lite/runtime.py,sha256=pSjBeA1dTQ-a94q3FLdv9lqZurdd6MJmfhFGHhOoQEM,16057
10
+ shell_lite-0.4.5.dist-info/LICENSE,sha256=33eziKLPxbqGCqdHtEHAFe1KSOgqc0-jWUQmdgKq85Q,1092
11
+ shell_lite-0.4.5.dist-info/METADATA,sha256=FV-ta6IiRo6YdJ46ufi0yrF4Nec_9f13k2cMDj4aY4Q,2475
12
+ shell_lite-0.4.5.dist-info/WHEEL,sha256=tZoeGjtWxWRfdplE7E3d45VPlLNQnvbKiYnx7gwAy8A,92
13
+ shell_lite-0.4.5.dist-info/entry_points.txt,sha256=tglL8tjyPIh1W85j6zFpNZjMpQe_xC-k-7BOhHLWfxc,45
14
+ shell_lite-0.4.5.dist-info/top_level.txt,sha256=hIln5ltrok_Mn3ijlQeqMFF6hHBHCyhzqCO7KL358cg,11
15
+ shell_lite-0.4.5.dist-info/RECORD,,
shell_lite/fix_nulls.py DELETED
@@ -1,29 +0,0 @@
1
-
2
- import sys
3
- import glob
4
- import os
5
-
6
- files = [
7
- r'c:\Users\shrey\OneDrive\Desktop\oka\shell-lite\shell_lite\parser.py',
8
- r'c:\Users\shrey\OneDrive\Desktop\oka\shell-lite\shell_lite\lexer.py',
9
- r'c:\Users\shrey\OneDrive\Desktop\oka\shell-lite\shell_lite\interpreter.py',
10
- r'c:\Users\shrey\OneDrive\Desktop\oka\shell-lite\shell_lite\main.py',
11
- r'c:\Users\shrey\OneDrive\Desktop\oka\shell-lite\shell_lite\ast_nodes.py',
12
- r'c:\Users\shrey\OneDrive\Desktop\oka\tests_suite\repro_issues.shl'
13
- ]
14
-
15
- for path in files:
16
- try:
17
- with open(path, 'rb') as f:
18
- content = f.read()
19
-
20
- if b'\x00' in content:
21
- print(f"Null bytes found in {path}! Fixing...")
22
- new_content = content.replace(b'\x00', b'')
23
- with open(path, 'wb') as f:
24
- f.write(new_content)
25
- print(f"Fixed {path}.")
26
- else:
27
- print(f"No null bytes in {path}.")
28
- except Exception as e:
29
- print(f"Error checking {path}: {e}")