Barmaja-PyArduino 2.0.0__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.
Files changed (87) hide show
  1. Barmaja_PyArduino/BARMAJA_PyArduino.ico +0 -0
  2. Barmaja_PyArduino/__init__.py +528 -0
  3. Barmaja_PyArduino/__init__.pyi +331 -0
  4. Barmaja_PyArduino/__main__.py +2 -0
  5. Barmaja_PyArduino/adapters/__init__.py +35 -0
  6. Barmaja_PyArduino/api_registry.py +459 -0
  7. Barmaja_PyArduino/backend/__init__.py +3 -0
  8. Barmaja_PyArduino/backend/uno_cpp.py +1680 -0
  9. Barmaja_PyArduino/board.py +80 -0
  10. Barmaja_PyArduino/cli.py +223 -0
  11. Barmaja_PyArduino/compiler.py +1721 -0
  12. Barmaja_PyArduino/console.py +217 -0
  13. Barmaja_PyArduino/device_packs.py +367 -0
  14. Barmaja_PyArduino/device_packs_builtin/Button/arduino/Barmaja_Button/library.properties +9 -0
  15. Barmaja_PyArduino/device_packs_builtin/Button/arduino/Barmaja_Button/src/Barmaja_Button.h +18 -0
  16. Barmaja_PyArduino/device_packs_builtin/Button/example.py +10 -0
  17. Barmaja_PyArduino/device_packs_builtin/Button/pack.json +40 -0
  18. Barmaja_PyArduino/device_packs_builtin/ClockDS3231/arduino/Barmaja_ClockDS3231/library.properties +9 -0
  19. Barmaja_PyArduino/device_packs_builtin/ClockDS3231/arduino/Barmaja_ClockDS3231/src/Barmaja_ClockDS3231.h +20 -0
  20. Barmaja_PyArduino/device_packs_builtin/ClockDS3231/example.py +8 -0
  21. Barmaja_PyArduino/device_packs_builtin/ClockDS3231/pack.json +136 -0
  22. Barmaja_PyArduino/device_packs_builtin/DistanceVL53L0X/arduino/Barmaja_DistanceVL53L0X/library.properties +9 -0
  23. Barmaja_PyArduino/device_packs_builtin/DistanceVL53L0X/arduino/Barmaja_DistanceVL53L0X/src/Barmaja_DistanceVL53L0X.h +12 -0
  24. Barmaja_PyArduino/device_packs_builtin/DistanceVL53L0X/example.py +8 -0
  25. Barmaja_PyArduino/device_packs_builtin/DistanceVL53L0X/pack.json +48 -0
  26. Barmaja_PyArduino/device_packs_builtin/LCDParallel/arduino/Barmaja_LCDParallel/library.properties +9 -0
  27. Barmaja_PyArduino/device_packs_builtin/LCDParallel/arduino/Barmaja_LCDParallel/src/Barmaja_LCDParallel.h +17 -0
  28. Barmaja_PyArduino/device_packs_builtin/LCDParallel/example.py +10 -0
  29. Barmaja_PyArduino/device_packs_builtin/LCDParallel/pack.json +137 -0
  30. Barmaja_PyArduino/device_packs_builtin/LoadCellHX711/arduino/Barmaja_LoadCellHX711/library.properties +9 -0
  31. Barmaja_PyArduino/device_packs_builtin/LoadCellHX711/arduino/Barmaja_LoadCellHX711/src/Barmaja_LoadCellHX711.h +16 -0
  32. Barmaja_PyArduino/device_packs_builtin/LoadCellHX711/example.py +11 -0
  33. Barmaja_PyArduino/device_packs_builtin/LoadCellHX711/pack.json +74 -0
  34. Barmaja_PyArduino/device_packs_builtin/NeoPixelStrip/arduino/Barmaja_NeoPixelStrip/library.properties +9 -0
  35. Barmaja_PyArduino/device_packs_builtin/NeoPixelStrip/arduino/Barmaja_NeoPixelStrip/src/Barmaja_NeoPixelStrip.h +15 -0
  36. Barmaja_PyArduino/device_packs_builtin/NeoPixelStrip/example.py +13 -0
  37. Barmaja_PyArduino/device_packs_builtin/NeoPixelStrip/pack.json +114 -0
  38. Barmaja_PyArduino/device_packs_builtin/OLEDText/arduino/Barmaja_OLEDText/library.properties +9 -0
  39. Barmaja_PyArduino/device_packs_builtin/OLEDText/arduino/Barmaja_OLEDText/src/Barmaja_OLEDText.h +21 -0
  40. Barmaja_PyArduino/device_packs_builtin/OLEDText/example.py +9 -0
  41. Barmaja_PyArduino/device_packs_builtin/OLEDText/pack.json +103 -0
  42. Barmaja_PyArduino/device_packs_builtin/PressureBMP280/arduino/Barmaja_PressureBMP280/library.properties +9 -0
  43. Barmaja_PyArduino/device_packs_builtin/PressureBMP280/arduino/Barmaja_PressureBMP280/src/Barmaja_PressureBMP280.h +16 -0
  44. Barmaja_PyArduino/device_packs_builtin/PressureBMP280/example.py +8 -0
  45. Barmaja_PyArduino/device_packs_builtin/PressureBMP280/pack.json +92 -0
  46. Barmaja_PyArduino/device_packs_builtin/RotaryEncoder/arduino/Barmaja_RotaryEncoder/library.properties +9 -0
  47. Barmaja_PyArduino/device_packs_builtin/RotaryEncoder/arduino/Barmaja_RotaryEncoder/src/Barmaja_RotaryEncoder.h +18 -0
  48. Barmaja_PyArduino/device_packs_builtin/RotaryEncoder/example.py +10 -0
  49. Barmaja_PyArduino/device_packs_builtin/RotaryEncoder/pack.json +44 -0
  50. Barmaja_PyArduino/device_packs_builtin/ServoDriverPCA9685/arduino/Barmaja_ServoDriverPCA9685/library.properties +9 -0
  51. Barmaja_PyArduino/device_packs_builtin/ServoDriverPCA9685/arduino/Barmaja_ServoDriverPCA9685/src/Barmaja_ServoDriverPCA9685.h +15 -0
  52. Barmaja_PyArduino/device_packs_builtin/ServoDriverPCA9685/example.py +10 -0
  53. Barmaja_PyArduino/device_packs_builtin/ServoDriverPCA9685/pack.json +117 -0
  54. Barmaja_PyArduino/device_packs_builtin/ShiftRegister595/arduino/Barmaja_ShiftRegister595/library.properties +9 -0
  55. Barmaja_PyArduino/device_packs_builtin/ShiftRegister595/arduino/Barmaja_ShiftRegister595/src/Barmaja_ShiftRegister595.h +14 -0
  56. Barmaja_PyArduino/device_packs_builtin/ShiftRegister595/example.py +9 -0
  57. Barmaja_PyArduino/device_packs_builtin/ShiftRegister595/pack.json +83 -0
  58. Barmaja_PyArduino/device_packs_builtin/TemperatureDS18B20/arduino/Barmaja_TemperatureDS18B20/library.properties +9 -0
  59. Barmaja_PyArduino/device_packs_builtin/TemperatureDS18B20/arduino/Barmaja_TemperatureDS18B20/src/Barmaja_TemperatureDS18B20.h +16 -0
  60. Barmaja_PyArduino/device_packs_builtin/TemperatureDS18B20/example.py +8 -0
  61. Barmaja_PyArduino/device_packs_builtin/TemperatureDS18B20/pack.json +63 -0
  62. Barmaja_PyArduino/doctor.py +406 -0
  63. Barmaja_PyArduino/environment.py +595 -0
  64. Barmaja_PyArduino/errors.py +166 -0
  65. Barmaja_PyArduino/flow.py +754 -0
  66. Barmaja_PyArduino/frontend.py +611 -0
  67. Barmaja_PyArduino/installer.py +151 -0
  68. Barmaja_PyArduino/ir.py +660 -0
  69. Barmaja_PyArduino/library_adder.py +440 -0
  70. Barmaja_PyArduino/live.py +859 -0
  71. Barmaja_PyArduino/lowering.py +596 -0
  72. Barmaja_PyArduino/numeric.py +748 -0
  73. Barmaja_PyArduino/pack_builder.py +544 -0
  74. Barmaja_PyArduino/py.typed +0 -0
  75. Barmaja_PyArduino/serial_monitor.py +172 -0
  76. Barmaja_PyArduino/source.py +184 -0
  77. Barmaja_PyArduino/stubgen.py +148 -0
  78. Barmaja_PyArduino/symbols.py +646 -0
  79. Barmaja_PyArduino/target.py +1631 -0
  80. Barmaja_PyArduino/toolchain.py +506 -0
  81. Barmaja_PyArduino/typesys.py +917 -0
  82. Barmaja_PyArduino/workflow.py +213 -0
  83. barmaja_pyarduino-2.0.0.dist-info/METADATA +39 -0
  84. barmaja_pyarduino-2.0.0.dist-info/RECORD +87 -0
  85. barmaja_pyarduino-2.0.0.dist-info/WHEEL +5 -0
  86. barmaja_pyarduino-2.0.0.dist-info/licenses/LICENSE.txt +7 -0
  87. barmaja_pyarduino-2.0.0.dist-info/top_level.txt +1 -0
@@ -0,0 +1,596 @@
1
+ from __future__ import annotations
2
+
3
+ import ast
4
+
5
+ from .errors import InternalCompilerError
6
+ from .frontend import is_noop_stmt
7
+ from .flow import StorageClass
8
+ from .ir import (
9
+ IRAugStore,
10
+ IRBinary,
11
+ IRBinaryOp,
12
+ IRBool,
13
+ IRBoolOp,
14
+ IRBreak,
15
+ IRCall,
16
+ IRCallKind,
17
+ IRCompareChain,
18
+ IRCompareOp,
19
+ IRConditional,
20
+ IRConstant,
21
+ IRContinue,
22
+ IREffect,
23
+ IRExpr,
24
+ IRExprStmt,
25
+ IRFormatString,
26
+ IRFormattedValue,
27
+ IRFunction,
28
+ IRFunctionRef,
29
+ IRIf,
30
+ IRIndex,
31
+ IRIndexStore,
32
+ IRListLiteral,
33
+ IRFixedListLoop,
34
+ IRLoad,
35
+ IRLoopControlTarget,
36
+ IRModule,
37
+ IRObjectDecl,
38
+ IRPrint,
39
+ IRRangeLoop,
40
+ IRReturn,
41
+ IRStorageDecl,
42
+ IRStore,
43
+ IRSymbolRef,
44
+ IRTempId,
45
+ IRTempRef,
46
+ IRUnary,
47
+ IRUnaryOp,
48
+ IRWhile,
49
+ validate_ir,
50
+ )
51
+ from .numeric import NumericAnalysis
52
+ from .source import SourceSpan
53
+ from .symbols import Symbol, SymbolKind
54
+ from .typesys import BOOL, INT, STRING, BarmajaType, TypedModule, TypeKind
55
+ from .api_registry import HARDWARE_CLASSES
56
+
57
+
58
+ _BINARY_OPS = {
59
+ ast.Add: IRBinaryOp.ADD,
60
+ ast.Sub: IRBinaryOp.SUBTRACT,
61
+ ast.Mult: IRBinaryOp.MULTIPLY,
62
+ ast.Div: IRBinaryOp.TRUE_DIVIDE,
63
+ ast.Mod: IRBinaryOp.MODULO,
64
+ }
65
+ _AUG_OPS = {
66
+ ast.Add: IRBinaryOp.ADD,
67
+ ast.Sub: IRBinaryOp.SUBTRACT,
68
+ ast.Mult: IRBinaryOp.MULTIPLY,
69
+ ast.Div: IRBinaryOp.TRUE_DIVIDE,
70
+ ast.Mod: IRBinaryOp.MODULO,
71
+ }
72
+ _COMPARE_OPS = {
73
+ ast.Eq: IRCompareOp.EQ,
74
+ ast.NotEq: IRCompareOp.NE,
75
+ ast.Lt: IRCompareOp.LT,
76
+ ast.LtE: IRCompareOp.LE,
77
+ ast.Gt: IRCompareOp.GT,
78
+ ast.GtE: IRCompareOp.GE,
79
+ }
80
+
81
+
82
+ class IRLowerer:
83
+ """Lower the fully resolved/typed frontend into target-independent IR.
84
+
85
+ This phase may inspect Python AST only while lowering. The returned IR owns
86
+ no AST objects and carries stable SymbolIds, types, spans, and conservative
87
+ effects. C++ generation remains on the legacy AST path until Step 15.
88
+ """
89
+
90
+ def __init__(self, typed: TypedModule, numeric: NumericAnalysis, source_name: str) -> None:
91
+ self.typed = typed
92
+ self.numeric = numeric
93
+ self.resolved = typed.resolved
94
+ self.flow = typed.flow
95
+ self.frontend = self.resolved.frontend
96
+ self.source_name = source_name
97
+ self._temp_counter = 0
98
+ self._temps: list[IRTempRef] = []
99
+
100
+ def lower(self) -> IRModule:
101
+ storages = self._lower_storage_declarations()
102
+ objects: list[IRObjectDecl] = []
103
+ setup: list = []
104
+ functions: list[IRFunction] = []
105
+
106
+ main_loop_node = self.frontend.main_loop
107
+ for stmt in self.frontend.tree.body:
108
+ if isinstance(stmt, ast.ImportFrom):
109
+ continue
110
+ if isinstance(stmt, ast.FunctionDef):
111
+ functions.append(self._lower_function(stmt))
112
+ continue
113
+ if stmt is main_loop_node:
114
+ continue
115
+ if is_noop_stmt(stmt, docstring_allowed=True):
116
+ continue
117
+ if self._is_hardware_object_assignment(stmt):
118
+ objects.append(self._lower_object_decl(stmt))
119
+ continue
120
+ setup.extend(self._lower_stmt(stmt, in_main=False, nested_loop_depth=0))
121
+
122
+ main_loop = tuple(
123
+ item
124
+ for stmt in main_loop_node.body
125
+ for item in self._lower_stmt(stmt, in_main=True, nested_loop_depth=0)
126
+ )
127
+
128
+ module = IRModule(
129
+ source_name=self.source_name,
130
+ storages=tuple(storages),
131
+ objects=tuple(objects),
132
+ setup=tuple(setup),
133
+ main_loop=main_loop,
134
+ functions=tuple(functions),
135
+ generated_temporaries=tuple(self._temps),
136
+ )
137
+ validate_ir(module)
138
+ return module
139
+
140
+ # ---------- declarations ----------
141
+ def _lower_storage_declarations(self) -> list[IRStorageDecl]:
142
+ result: list[IRStorageDecl] = []
143
+ symbols = sorted(
144
+ (
145
+ s for s in self.resolved.module_scope.symbols.values()
146
+ if s.kind == SymbolKind.MODULE
147
+ ),
148
+ key=lambda s: s.symbol_id.value,
149
+ )
150
+ for symbol in symbols:
151
+ typ = self.typed.symbol_types.get(symbol.symbol_id)
152
+ if typ is None or typ.kind == TypeKind.UNKNOWN:
153
+ raise InternalCompilerError(f"No resolved type is available for module symbol '{symbol.name}'.")
154
+ facts = self.flow.symbol_facts.get(symbol.symbol_id)
155
+ if facts is None:
156
+ raise InternalCompilerError(f"No flow/storage facts are available for module symbol '{symbol.name}'.")
157
+ result.append(
158
+ IRStorageDecl(
159
+ symbol=self._symbol_ref(symbol),
160
+ storage=facts.storage,
161
+ initializer=None,
162
+ span=self._declaration_span(symbol),
163
+ )
164
+ )
165
+ return result
166
+
167
+ def _declaration_span(self, symbol: Symbol) -> SourceSpan | None:
168
+ if symbol.declaration_line is None:
169
+ return None
170
+ return SourceSpan(symbol.declaration_line, 0, symbol.declaration_line, 0)
171
+
172
+ def _lower_function(self, node: ast.FunctionDef) -> IRFunction:
173
+ signature = self.typed.signature_for(node.name)
174
+ function_symbol = self.resolved.symbol_for_function(node)
175
+ params = tuple(self._symbol_ref(s) for s in signature.parameters)
176
+ body = tuple(
177
+ item
178
+ for stmt in node.body
179
+ for item in self._lower_stmt(stmt, in_main=False, nested_loop_depth=0)
180
+ )
181
+ return IRFunction(
182
+ symbol=IRFunctionRef(function_symbol.symbol_id, function_symbol.name, function_symbol.cpp_name),
183
+ parameters=params,
184
+ return_type=signature.return_type,
185
+ body=body,
186
+ span=self._span(node),
187
+ )
188
+
189
+ @staticmethod
190
+ def _is_hardware_object_assignment(node: ast.AST) -> bool:
191
+ return (
192
+ isinstance(node, ast.Assign)
193
+ and len(node.targets) == 1
194
+ and isinstance(node.targets[0], ast.Name)
195
+ and isinstance(node.value, ast.Call)
196
+ and isinstance(node.value.func, ast.Name)
197
+ and node.value.func.id in HARDWARE_CLASSES
198
+ )
199
+
200
+ def _lower_object_decl(self, node: ast.Assign) -> IRObjectDecl:
201
+ target = node.targets[0]
202
+ assert isinstance(target, ast.Name)
203
+ call = node.value
204
+ assert isinstance(call, ast.Call) and isinstance(call.func, ast.Name)
205
+ symbol = self.resolved.symbol_for_name(target)
206
+ return IRObjectDecl(
207
+ symbol=self._symbol_ref(symbol),
208
+ class_name=call.func.id,
209
+ args=tuple(self._expr(a) for a in call.args),
210
+ span=self._span(node),
211
+ )
212
+
213
+ # ---------- statements ----------
214
+ def _lower_stmt(self, node: ast.stmt, *, in_main: bool, nested_loop_depth: int) -> tuple:
215
+ if is_noop_stmt(node, docstring_allowed=True) or isinstance(node, ast.Global):
216
+ return ()
217
+
218
+ if isinstance(node, ast.Assign):
219
+ if len(node.targets) == 1 and isinstance(node.targets[0], ast.Subscript):
220
+ target = node.targets[0]
221
+ if not isinstance(target.value, ast.Name):
222
+ raise InternalCompilerError("Validated fixed-list store has no list-name target.")
223
+ return (
224
+ IRIndexStore(
225
+ span=self._span(node),
226
+ collection=self._symbol_ref(self.resolved.symbol_for_name(target.value)),
227
+ index=self._expr(target.slice),
228
+ value=self._expr(node.value),
229
+ ),
230
+ )
231
+ if (
232
+ len(node.targets) == 1
233
+ and isinstance(node.targets[0], (ast.Tuple, ast.List))
234
+ and isinstance(node.value, (ast.Tuple, ast.List))
235
+ ):
236
+ return tuple(
237
+ IRStore(self._span(node), self._symbol_ref(self.resolved.symbol_for_name(t)), self._expr(v))
238
+ for t, v in zip(node.targets[0].elts, node.value.elts)
239
+ if isinstance(t, ast.Name)
240
+ )
241
+ target = node.targets[0]
242
+ if not isinstance(target, ast.Name):
243
+ raise InternalCompilerError("Lowering received an unsupported assignment target.")
244
+ return (
245
+ IRStore(
246
+ span=self._span(node),
247
+ target=self._symbol_ref(self.resolved.symbol_for_name(target)),
248
+ value=self._expr(node.value),
249
+ ),
250
+ )
251
+
252
+ if isinstance(node, ast.AugAssign):
253
+ if not isinstance(node.target, ast.Name):
254
+ raise InternalCompilerError("Lowering received an unsupported augmented-assignment target.")
255
+ op = _AUG_OPS.get(type(node.op))
256
+ if op is None:
257
+ raise InternalCompilerError("Lowering received an unsupported augmented-assignment operator.")
258
+ checked = self.numeric.int_expr_needs_runtime_check(node)
259
+ return (
260
+ IRAugStore(
261
+ span=self._span(node),
262
+ target=self._symbol_ref(self.resolved.symbol_for_name(node.target)),
263
+ op=op,
264
+ value=self._expr(node.value),
265
+ checked=checked,
266
+ ),
267
+ )
268
+
269
+ if isinstance(node, ast.Expr):
270
+ if isinstance(node.value, ast.Call) and isinstance(node.value.func, ast.Name) and node.value.func.id == "print":
271
+ return (
272
+ IRPrint(
273
+ span=self._span(node),
274
+ values=tuple(self._expr(arg) for arg in node.value.args),
275
+ newline=True,
276
+ ),
277
+ )
278
+ return (IRExprStmt(self._span(node), self._expr(node.value)),)
279
+
280
+ if isinstance(node, ast.If):
281
+ return (
282
+ IRIf(
283
+ span=self._span(node),
284
+ condition=self._expr(node.test),
285
+ body=tuple(
286
+ item for stmt in node.body
287
+ for item in self._lower_stmt(stmt, in_main=in_main, nested_loop_depth=nested_loop_depth)
288
+ ),
289
+ otherwise=tuple(
290
+ item for stmt in node.orelse
291
+ for item in self._lower_stmt(stmt, in_main=in_main, nested_loop_depth=nested_loop_depth)
292
+ ),
293
+ ),
294
+ )
295
+
296
+ if isinstance(node, ast.While):
297
+ return (
298
+ IRWhile(
299
+ span=self._span(node),
300
+ condition=self._expr(node.test),
301
+ body=tuple(
302
+ item for stmt in node.body
303
+ for item in self._lower_stmt(stmt, in_main=in_main, nested_loop_depth=nested_loop_depth + 1)
304
+ ),
305
+ ),
306
+ )
307
+
308
+ if isinstance(node, ast.For):
309
+ if isinstance(node.iter, ast.Call) and isinstance(node.iter.func, ast.Name) and node.iter.func.id == "range":
310
+ if not isinstance(node.target, ast.Name):
311
+ raise InternalCompilerError("Validated range loop has no simple target.")
312
+ args = list(node.iter.args)
313
+ if len(args) == 1:
314
+ start_expr = self._generated_int_constant(0, node.iter)
315
+ stop_expr = self._expr(args[0])
316
+ step_expr = self._generated_int_constant(1, node.iter)
317
+ elif len(args) == 2:
318
+ start_expr, stop_expr = self._expr(args[0]), self._expr(args[1])
319
+ step_expr = self._generated_int_constant(1, node.iter)
320
+ elif len(args) == 3:
321
+ start_expr, stop_expr, step_expr = (self._expr(a) for a in args)
322
+ else:
323
+ raise InternalCompilerError("Validated range() reached lowering with invalid arity.")
324
+ return (
325
+ IRRangeLoop(
326
+ span=self._span(node),
327
+ target=self._symbol_ref(self.resolved.symbol_for_name(node.target)),
328
+ iterator_temp=self._new_temp(INT, "range_iterator", node),
329
+ start_temp=self._new_temp(INT, "range_start_once", node.iter),
330
+ stop_temp=self._new_temp(INT, "range_stop_once", node.iter),
331
+ step_temp=self._new_temp(INT, "range_step_once", node.iter),
332
+ start=start_expr,
333
+ stop=stop_expr,
334
+ step=step_expr,
335
+ body=tuple(
336
+ item for stmt in node.body
337
+ for item in self._lower_stmt(stmt, in_main=in_main, nested_loop_depth=nested_loop_depth + 1)
338
+ ),
339
+ ),
340
+ )
341
+
342
+ enumerate_mode = isinstance(node.iter, ast.Call) and isinstance(node.iter.func, ast.Name) and node.iter.func.id == "enumerate"
343
+ collection_node = node.iter.args[0] if enumerate_mode else node.iter
344
+ if not isinstance(collection_node, ast.Name):
345
+ raise InternalCompilerError("Validated fixed-list loop has no list-name iterator.")
346
+ collection_ref = self._symbol_ref(self.resolved.symbol_for_name(collection_node))
347
+ if enumerate_mode:
348
+ if not isinstance(node.target, (ast.Tuple, ast.List)) or len(node.target.elts) != 2:
349
+ raise InternalCompilerError("Validated enumerate target changed before lowering.")
350
+ index_node, value_node = node.target.elts
351
+ if not isinstance(index_node, ast.Name) or not isinstance(value_node, ast.Name):
352
+ raise InternalCompilerError("Validated enumerate target is not two simple names.")
353
+ index_ref = self._symbol_ref(self.resolved.symbol_for_name(index_node))
354
+ value_ref = self._symbol_ref(self.resolved.symbol_for_name(value_node))
355
+ else:
356
+ if not isinstance(node.target, ast.Name):
357
+ raise InternalCompilerError("Validated fixed-list loop target is not a simple name.")
358
+ index_ref = None
359
+ value_ref = self._symbol_ref(self.resolved.symbol_for_name(node.target))
360
+ index_start = None
361
+ index_start_temp = None
362
+ if enumerate_mode and len(node.iter.args) == 2:
363
+ index_start = self._expr(node.iter.args[1])
364
+ index_start_temp = self._new_temp(INT, "enumerate_start_once", node.iter.args[1])
365
+ return (
366
+ IRFixedListLoop(
367
+ span=self._span(node),
368
+ collection=collection_ref,
369
+ value_target=value_ref,
370
+ index_target=index_ref,
371
+ iterator_temp=self._new_temp(INT, "fixed_list_iterator", node),
372
+ index_start=index_start,
373
+ index_start_temp=index_start_temp,
374
+ body=tuple(
375
+ item for stmt in node.body
376
+ for item in self._lower_stmt(stmt, in_main=in_main, nested_loop_depth=nested_loop_depth + 1)
377
+ ),
378
+ ),
379
+ )
380
+
381
+ if isinstance(node, ast.Break):
382
+ target = IRLoopControlTarget.NESTED_LOOP
383
+ if in_main and nested_loop_depth == 0:
384
+ target = IRLoopControlTarget.MAIN_LOOP
385
+ return (IRBreak(self._span(node), target),)
386
+
387
+ if isinstance(node, ast.Continue):
388
+ target = IRLoopControlTarget.MAIN_LOOP if in_main and nested_loop_depth == 0 else IRLoopControlTarget.NESTED_LOOP
389
+ return (IRContinue(self._span(node), target),)
390
+
391
+ if isinstance(node, ast.Return):
392
+ return (IRReturn(self._span(node), None if node.value is None else self._expr(node.value)),)
393
+
394
+ raise InternalCompilerError(f"Validated statement {type(node).__name__} has no typed IR lowering path.")
395
+
396
+ # ---------- expressions ----------
397
+ def _expr(self, node: ast.AST) -> IRExpr:
398
+ typ = self.typed.type_for_expr(node)
399
+ span = self._span(node)
400
+
401
+ if isinstance(node, ast.Constant):
402
+ if not isinstance(node.value, (bool, int, float, str)):
403
+ raise InternalCompilerError("Validated constant has no typed IR representation.")
404
+ return IRConstant(typ, span, frozenset(), node.value)
405
+
406
+ if isinstance(node, ast.Name):
407
+ return IRLoad(typ, span, frozenset(), self._symbol_ref(self.resolved.symbol_for_name(node)))
408
+
409
+ if isinstance(node, ast.List):
410
+ elements = tuple(self._expr(item) for item in node.elts)
411
+ return IRListLiteral(typ, span, frozenset(self._union_effects(*elements)), elements)
412
+
413
+ if isinstance(node, ast.Subscript):
414
+ collection = self._expr(node.value)
415
+ index = self._expr(node.slice)
416
+ effects = self._union_effects(collection, index)
417
+ if not (isinstance(node.slice, ast.Constant) and isinstance(node.slice.value, int)):
418
+ effects.add(IREffect.MAY_FAIL)
419
+ return IRIndex(typ, span, frozenset(effects), collection, index)
420
+
421
+ if isinstance(node, ast.UnaryOp):
422
+ operand = self._expr(node.operand)
423
+ if isinstance(node.op, ast.USub):
424
+ op = IRUnaryOp.NEGATE
425
+ elif isinstance(node.op, ast.Not):
426
+ op = IRUnaryOp.NOT
427
+ else:
428
+ raise InternalCompilerError("Validated unary operator has no typed IR representation.")
429
+ effects = self._union_effects(operand)
430
+ if self.numeric.int_expr_needs_runtime_check(node):
431
+ effects = effects | {IREffect.MAY_FAIL}
432
+ return IRUnary(typ, span, frozenset(effects), op, operand)
433
+
434
+ if isinstance(node, ast.BinOp):
435
+ left, right = self._expr(node.left), self._expr(node.right)
436
+ op = _BINARY_OPS.get(type(node.op))
437
+ if op is None:
438
+ raise InternalCompilerError("Validated binary operator has no typed IR representation.")
439
+ checked = self.numeric.int_expr_needs_runtime_check(node)
440
+ effects = self._union_effects(left, right)
441
+ if checked or isinstance(node.op, (ast.Div, ast.Mod)):
442
+ effects.add(IREffect.MAY_FAIL)
443
+ return IRBinary(typ, span, frozenset(effects), op, left, right, checked)
444
+
445
+ if isinstance(node, ast.Compare):
446
+ operands = (self._expr(node.left),) + tuple(self._expr(c) for c in node.comparators)
447
+ operators = tuple(_COMPARE_OPS[type(op)] for op in node.ops)
448
+ effects = self._union_effects(*operands)
449
+ if any(self.numeric.time_threshold_needs_runtime_check(c) for c in node.comparators):
450
+ effects.add(IREffect.MAY_FAIL)
451
+ temps = tuple(self._new_temp(expr.type, "compare_operand_once", source) for expr, source in zip(operands, [node.left, *node.comparators]))
452
+ return IRCompareChain(typ, span, frozenset(effects), operands, operators, temps)
453
+
454
+ if isinstance(node, ast.BoolOp):
455
+ values = tuple(self._expr(v) for v in node.values)
456
+ op = IRBoolOp.AND if isinstance(node.op, ast.And) else IRBoolOp.OR
457
+ return IRBool(typ, span, frozenset(self._union_effects(*values)), op, values)
458
+
459
+ if isinstance(node, ast.IfExp):
460
+ cond, yes, no = self._expr(node.test), self._expr(node.body), self._expr(node.orelse)
461
+ return IRConditional(typ, span, frozenset(self._union_effects(cond, yes, no)), cond, yes, no)
462
+
463
+ if isinstance(node, ast.JoinedStr):
464
+ parts: list[str | IRFormattedValue] = []
465
+ child_exprs: list[IRExpr] = []
466
+ for part in node.values:
467
+ if isinstance(part, ast.Constant) and isinstance(part.value, str):
468
+ parts.append(part.value)
469
+ continue
470
+ if not isinstance(part, ast.FormattedValue):
471
+ raise InternalCompilerError("Validated f-string part has no typed IR representation.")
472
+ value = self._expr(part.value)
473
+ child_exprs.append(value)
474
+ parts.append(IRFormattedValue(value, self._fstring_precision(part), self._span(part)))
475
+ return IRFormatString(STRING, span, frozenset(self._union_effects(*child_exprs)), tuple(parts))
476
+
477
+ if isinstance(node, ast.Call):
478
+ return self._call(node, typ)
479
+
480
+ raise InternalCompilerError(f"Validated expression {type(node).__name__} has no typed IR lowering path.")
481
+
482
+ def _call(self, node: ast.Call, typ: BarmajaType) -> IRCall:
483
+ args = tuple(self._expr(a) for a in node.args)
484
+ effects = self._union_effects(*args)
485
+ span = self._span(node)
486
+
487
+ if isinstance(node.func, ast.Name):
488
+ name = node.func.id
489
+ symbol = self.resolved.symbol_for_name(node.func)
490
+ if symbol.kind == SymbolKind.FUNCTION:
491
+ effects.add(IREffect.USER_CALL)
492
+ function_ref = IRFunctionRef(symbol.symbol_id, symbol.name, symbol.cpp_name)
493
+ return IRCall(
494
+ typ, span, frozenset(effects), IRCallKind.USER_FUNCTION, name, args,
495
+ function_symbol=function_ref,
496
+ )
497
+ if symbol.kind == SymbolKind.BUILTIN:
498
+ # Checked int conversion and later semantic helpers may fail.
499
+ if name == "int" or self.numeric.int_expr_needs_runtime_check(node):
500
+ effects.add(IREffect.MAY_FAIL)
501
+ return IRCall(typ, span, frozenset(effects), IRCallKind.BUILTIN, name, args)
502
+ if symbol.kind == SymbolKind.API:
503
+ effects.add(IREffect.HARDWARE_IO)
504
+ if name == "print":
505
+ effects.add(IREffect.OUTPUT)
506
+ if name == "analogWrite" and len(node.args) > 1 and self.numeric.pwm_arg_needs_runtime_check(node.args[1]):
507
+ effects.add(IREffect.MAY_FAIL)
508
+ return IRCall(typ, span, frozenset(effects), IRCallKind.API_FUNCTION, name, args)
509
+ raise InternalCompilerError(f"Resolved direct call '{name}' has no IR call category.")
510
+
511
+ if isinstance(node.func, ast.Attribute) and isinstance(node.func.value, ast.Name):
512
+ owner_node = node.func.value
513
+ owner_name = owner_node.id
514
+ owner_symbol = self.resolved.symbol_for_name(owner_node)
515
+ owner_ref = None
516
+ kind = IRCallKind.SINGLETON_METHOD
517
+ if owner_symbol.kind == SymbolKind.HARDWARE:
518
+ owner_ref = self._symbol_ref(owner_symbol)
519
+ kind = IRCallKind.OBJECT_METHOD
520
+ effects.add(IREffect.HARDWARE_IO)
521
+ if (owner_name == "Serial" and node.func.attr in {"print", "println"}) or node.func.attr == "print":
522
+ effects.add(IREffect.OUTPUT)
523
+ return IRCall(
524
+ typ, span, frozenset(effects), kind,
525
+ f"{owner_name}.{node.func.attr}", args,
526
+ owner_symbol=owner_ref,
527
+ owner_name=owner_name,
528
+ method_name=node.func.attr,
529
+ )
530
+
531
+ raise InternalCompilerError("Validated call has no typed IR lowering path.")
532
+
533
+ # ---------- helpers ----------
534
+ def _symbol_ref(self, symbol: Symbol) -> IRSymbolRef:
535
+ typ = self.typed.symbol_types.get(symbol.symbol_id)
536
+ if typ is None:
537
+ # Constants/API singleton spellings are represented as API value
538
+ # loads when used as values. Their language value type is Int.
539
+ if symbol.kind == SymbolKind.API and symbol.name in {
540
+ "HIGH", "LOW", "INPUT", "OUTPUT", "INPUT_PULLUP",
541
+ "A0", "A1", "A2", "A3", "A4", "A5",
542
+ "DHT11", "DHT22", "CM", "INC",
543
+ }:
544
+ typ = INT
545
+ else:
546
+ raise InternalCompilerError(f"No resolved type is available for symbol '{symbol.name}'.")
547
+ if typ.kind == TypeKind.UNKNOWN:
548
+ raise InternalCompilerError(f"Symbol '{symbol.name}' reached IR lowering with Unknown type.")
549
+ return IRSymbolRef(symbol.symbol_id, symbol.name, symbol.cpp_name, symbol.kind, typ)
550
+
551
+ def _new_temp(self, typ: BarmajaType, purpose: str, owner: ast.AST) -> IRTempRef:
552
+ self._temp_counter += 1
553
+ temp = IRTempRef(IRTempId(self._temp_counter), f"_bj_t{self._temp_counter:04d}", typ, purpose, self._span(owner))
554
+ self._temps.append(temp)
555
+ return temp
556
+
557
+ def _generated_int_constant(self, value: int, owner: ast.AST) -> IRConstant:
558
+ return IRConstant(INT, self._span(owner), frozenset(), value)
559
+
560
+ @staticmethod
561
+ def _union_effects(*expressions: IRExpr) -> set[IREffect]:
562
+ result: set[IREffect] = set()
563
+ for expr in expressions:
564
+ result.update(expr.effects)
565
+ return result
566
+
567
+ @staticmethod
568
+ def _span(node: ast.AST) -> SourceSpan | None:
569
+ line = getattr(node, "lineno", None)
570
+ column = getattr(node, "col_offset", None)
571
+ if line is None or column is None:
572
+ return None
573
+ return SourceSpan(
574
+ line,
575
+ column,
576
+ getattr(node, "end_lineno", None) or line,
577
+ getattr(node, "end_col_offset", None) if getattr(node, "end_col_offset", None) is not None else column,
578
+ )
579
+
580
+ @staticmethod
581
+ def _fstring_precision(node: ast.FormattedValue) -> int | None:
582
+ if node.format_spec is None:
583
+ return None
584
+ if not isinstance(node.format_spec, ast.JoinedStr):
585
+ raise InternalCompilerError("Validated f-string format has no typed IR representation.")
586
+ text = "".join(
587
+ part.value for part in node.format_spec.values
588
+ if isinstance(part, ast.Constant) and isinstance(part.value, str)
589
+ )
590
+ if len(text) >= 3 and text.startswith(".") and text.endswith("f") and text[1:-1].isdigit():
591
+ return int(text[1:-1])
592
+ raise InternalCompilerError("Validated f-string precision has no typed IR representation.")
593
+
594
+
595
+ def lower_to_ir(typed: TypedModule, numeric: NumericAnalysis, source_name: str = "<student.py>") -> IRModule:
596
+ return IRLowerer(typed, numeric, source_name).lower()