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,646 @@
1
+ from __future__ import annotations
2
+
3
+ import ast
4
+ from dataclasses import dataclass, field
5
+ from enum import Enum
6
+
7
+ from .errors import NameResolutionError
8
+ from .frontend import FrontendModule
9
+ from .api_registry import HARDWARE_CLASSES
10
+
11
+
12
+ LANGUAGE_BUILTINS = frozenset({
13
+ "print", "round", "min", "max", "abs", "int", "float", "bool", "len", "sum", "enumerate", "range",
14
+ })
15
+
16
+
17
+ class SymbolKind(Enum):
18
+ API = "api"
19
+ BUILTIN = "builtin"
20
+ MODULE = "module"
21
+ FUNCTION = "function"
22
+ HARDWARE = "hardware"
23
+ PARAMETER = "parameter"
24
+ LOCAL = "local"
25
+
26
+
27
+ @dataclass(frozen=True, order=True)
28
+ class SymbolId:
29
+ value: int
30
+
31
+ def __str__(self) -> str:
32
+ return f"S{self.value:04d}"
33
+
34
+
35
+ @dataclass
36
+ class Symbol:
37
+ symbol_id: SymbolId
38
+ name: str
39
+ kind: SymbolKind
40
+ scope_name: str
41
+ cpp_name: str
42
+ declaration_line: int | None = None
43
+ imported: bool = False
44
+
45
+ @property
46
+ def is_user_symbol(self) -> bool:
47
+ return self.kind not in {SymbolKind.API, SymbolKind.BUILTIN}
48
+
49
+
50
+ @dataclass
51
+ class Scope:
52
+ name: str
53
+ kind: str
54
+ symbols: dict[str, Symbol] = field(default_factory=dict)
55
+ global_names: set[str] = field(default_factory=set)
56
+
57
+
58
+ @dataclass(frozen=True)
59
+ class ResolvedModule:
60
+ frontend: FrontendModule
61
+ module_scope: Scope
62
+ function_scopes: dict[str, Scope]
63
+ bindings: dict[int, Symbol]
64
+ function_bindings: dict[int, Symbol]
65
+ parameter_bindings: dict[int, Symbol]
66
+
67
+ def symbol_for_name(self, node: ast.Name) -> Symbol:
68
+ return self.bindings[id(node)]
69
+
70
+ def symbol_for_function(self, node: ast.FunctionDef) -> Symbol:
71
+ return self.function_bindings[id(node)]
72
+
73
+ def symbol_for_parameter(self, node: ast.arg) -> Symbol:
74
+ return self.parameter_bindings[id(node)]
75
+
76
+
77
+ class _Mangler:
78
+ """Deterministic backend identifiers independent of Python spelling.
79
+
80
+ User spelling is intentionally absent from the generated identifier. This
81
+ makes C++ keywords/macros, AVR register names, Arduino names and non-ASCII
82
+ Python identifiers harmless to the backend.
83
+ """
84
+
85
+ def __init__(self) -> None:
86
+ self._next = 1
87
+
88
+ def user_symbol(self) -> tuple[SymbolId, str]:
89
+ symbol_id = SymbolId(self._next)
90
+ self._next += 1
91
+ return symbol_id, f"_bj_s{symbol_id.value:04d}"
92
+
93
+ @staticmethod
94
+ def synthetic(symbol_id: int, prefix: str) -> tuple[SymbolId, str]:
95
+ sid = SymbolId(symbol_id)
96
+ return sid, prefix
97
+
98
+
99
+ class SymbolResolver:
100
+ def __init__(self, frontend: FrontendModule) -> None:
101
+ self.frontend = frontend
102
+ self.module = Scope("<module>", "module")
103
+ self.function_scopes: dict[str, Scope] = {}
104
+ self.bindings: dict[int, Symbol] = {}
105
+ self.function_bindings: dict[int, Symbol] = {}
106
+ self.parameter_bindings: dict[int, Symbol] = {}
107
+ self._mangler = _Mangler()
108
+ self._function_nodes: dict[str, ast.FunctionDef] = {}
109
+ self._function_call_graph: dict[str, set[str]] = {}
110
+ self._function_module_dependencies: dict[str, set[str]] = {}
111
+
112
+ def resolve(self) -> ResolvedModule:
113
+ self._install_predefined_bindings()
114
+ self._collect_module_bindings()
115
+ self._collect_function_scopes()
116
+ self._resolve_module_body()
117
+ self._resolve_functions()
118
+ self._validate_immediate_top_level_function_calls()
119
+ return ResolvedModule(
120
+ frontend=self.frontend,
121
+ module_scope=self.module,
122
+ function_scopes=self.function_scopes,
123
+ bindings=self.bindings,
124
+ function_bindings=self.function_bindings,
125
+ parameter_bindings=self.parameter_bindings,
126
+ )
127
+
128
+ # ---------- collection ----------
129
+ def _install_predefined_bindings(self) -> None:
130
+ # Negative IDs are compiler-predefined and are never emitted as user
131
+ # storage. Keep ordering deterministic for debugging/tests.
132
+ next_predefined = -1
133
+ for name in sorted(self.frontend.api_names):
134
+ sid, cpp = self._mangler.synthetic(next_predefined, name)
135
+ next_predefined -= 1
136
+ self.module.symbols[name] = Symbol(
137
+ sid, name, SymbolKind.API, self.module.name, cpp, imported=True,
138
+ )
139
+ for name in sorted(LANGUAGE_BUILTINS):
140
+ # An explicitly imported API always wins if a future public surface
141
+ # deliberately uses the same spelling.
142
+ if name in self.module.symbols:
143
+ continue
144
+ sid, cpp = self._mangler.synthetic(next_predefined, name)
145
+ next_predefined -= 1
146
+ self.module.symbols[name] = Symbol(
147
+ sid, name, SymbolKind.BUILTIN, self.module.name, cpp,
148
+ )
149
+
150
+ def _new_user_symbol(
151
+ self,
152
+ name: str,
153
+ kind: SymbolKind,
154
+ scope: Scope,
155
+ node: ast.AST,
156
+ ) -> Symbol:
157
+ self._check_protected_binding(name, node)
158
+ sid, cpp_name = self._mangler.user_symbol()
159
+ symbol = Symbol(
160
+ sid,
161
+ name,
162
+ kind,
163
+ scope.name,
164
+ cpp_name,
165
+ declaration_line=getattr(node, "lineno", None),
166
+ )
167
+ scope.symbols[name] = symbol
168
+ return symbol
169
+
170
+ def _check_protected_binding(self, name: str, node: ast.AST) -> None:
171
+ existing = self.module.symbols.get(name)
172
+ if existing and existing.kind in {SymbolKind.API, SymbolKind.BUILTIN}:
173
+ raise self._name_error(
174
+ f"'{name}' is a protected Barmaja API/builtin name and cannot be rebound.",
175
+ node,
176
+ )
177
+
178
+ def _collect_module_bindings(self) -> None:
179
+ for stmt in self.frontend.tree.body:
180
+ if isinstance(stmt, ast.ImportFrom):
181
+ continue
182
+ if isinstance(stmt, ast.FunctionDef):
183
+ if stmt.name in self._function_nodes:
184
+ raise self._name_error(f"Function '{stmt.name}' is defined more than once.", stmt)
185
+ if stmt.name in self.module.symbols:
186
+ # API/builtin conflict or an earlier variable/object conflict.
187
+ existing = self.module.symbols[stmt.name]
188
+ if existing.kind in {SymbolKind.API, SymbolKind.BUILTIN}:
189
+ self._check_protected_binding(stmt.name, stmt)
190
+ raise self._name_error(
191
+ f"'{stmt.name}' is already bound at module scope and cannot also be a function.",
192
+ stmt,
193
+ )
194
+ sym = self._new_user_symbol(stmt.name, SymbolKind.FUNCTION, self.module, stmt)
195
+ self._function_nodes[stmt.name] = stmt
196
+ self.function_bindings[id(stmt)] = sym
197
+ continue
198
+ self._collect_module_stmt_bindings(stmt)
199
+
200
+ def _collect_module_stmt_bindings(self, stmt: ast.stmt) -> None:
201
+ if isinstance(stmt, ast.Assign):
202
+ for target in stmt.targets:
203
+ self._collect_module_target(target, stmt, self._assignment_kind(stmt))
204
+ return
205
+ if isinstance(stmt, ast.AugAssign):
206
+ self._collect_module_target(stmt.target, stmt, SymbolKind.MODULE)
207
+ return
208
+ if isinstance(stmt, ast.For):
209
+ self._collect_module_target(stmt.target, stmt, SymbolKind.MODULE)
210
+ for child in stmt.body:
211
+ self._collect_module_stmt_bindings(child)
212
+ for child in stmt.orelse:
213
+ self._collect_module_stmt_bindings(child)
214
+ return
215
+ if isinstance(stmt, ast.If):
216
+ for child in stmt.body + stmt.orelse:
217
+ self._collect_module_stmt_bindings(child)
218
+ return
219
+ if isinstance(stmt, ast.While):
220
+ for child in stmt.body + stmt.orelse:
221
+ self._collect_module_stmt_bindings(child)
222
+ return
223
+ # FunctionDef is intentionally not descended into here.
224
+
225
+ def _assignment_kind(self, stmt: ast.Assign) -> SymbolKind:
226
+ if (
227
+ len(stmt.targets) == 1
228
+ and isinstance(stmt.targets[0], ast.Name)
229
+ and isinstance(stmt.value, ast.Call)
230
+ and isinstance(stmt.value.func, ast.Name)
231
+ and stmt.value.func.id in HARDWARE_CLASSES
232
+ ):
233
+ return SymbolKind.HARDWARE
234
+ return SymbolKind.MODULE
235
+
236
+ def _collect_module_target(self, target: ast.AST, owner: ast.AST, kind: SymbolKind) -> None:
237
+ if isinstance(target, ast.Name):
238
+ existing = self.module.symbols.get(target.id)
239
+ if existing is None:
240
+ self._new_user_symbol(target.id, kind, self.module, owner)
241
+ return
242
+ if existing.kind in {SymbolKind.API, SymbolKind.BUILTIN}:
243
+ self._check_protected_binding(target.id, owner)
244
+ if kind == SymbolKind.HARDWARE and existing.kind != SymbolKind.HARDWARE:
245
+ raise self._name_error(
246
+ f"'{target.id}' is already a normal module variable and cannot be rebound as a hardware object.",
247
+ owner,
248
+ )
249
+ if existing.kind == SymbolKind.FUNCTION:
250
+ raise self._name_error(
251
+ f"'{target.id}' is a function and cannot be rebound as a module variable.",
252
+ owner,
253
+ )
254
+ return
255
+ if isinstance(target, (ast.Tuple, ast.List)):
256
+ for elt in target.elts:
257
+ self._collect_module_target(elt, owner, SymbolKind.MODULE)
258
+
259
+ def _collect_function_scopes(self) -> None:
260
+ for fn in self.frontend.tree.body:
261
+ if not isinstance(fn, ast.FunctionDef):
262
+ continue
263
+ scope = Scope(fn.name, "function")
264
+ self.function_scopes[fn.name] = scope
265
+ globals_by_name = self._collect_global_declarations(fn)
266
+ scope.global_names.update(globals_by_name)
267
+
268
+ parameter_names = {arg.arg for arg in fn.args.args}
269
+ overlap = parameter_names & scope.global_names
270
+ if overlap:
271
+ name = sorted(overlap)[0]
272
+ raise self._name_error(
273
+ f"Function parameter '{name}' cannot also be declared global.",
274
+ fn,
275
+ )
276
+
277
+ for arg in fn.args.args:
278
+ self._check_protected_binding(arg.arg, arg)
279
+ sym = self._new_user_symbol(arg.arg, SymbolKind.PARAMETER, scope, arg)
280
+ self.parameter_bindings[id(arg)] = sym
281
+
282
+ local_targets: list[tuple[str, ast.AST]] = []
283
+ self._collect_function_local_targets(fn.body, local_targets)
284
+ for name, node in local_targets:
285
+ if name in scope.global_names or name in scope.symbols:
286
+ continue
287
+ self._check_protected_binding(name, node)
288
+ self._new_user_symbol(name, SymbolKind.LOCAL, scope, node)
289
+
290
+ # Restricted global requires an already-known module symbol and may
291
+ # not target an imported API/builtin/function-only name.
292
+ for name, global_node in globals_by_name.items():
293
+ target = self.module.symbols.get(name)
294
+ if target is None or target.kind in {SymbolKind.API, SymbolKind.BUILTIN, SymbolKind.FUNCTION}:
295
+ raise self._name_error(
296
+ f"global '{name}' must refer to an existing module variable or hardware object.",
297
+ global_node,
298
+ )
299
+ self._validate_global_order(fn, name, global_node)
300
+
301
+ def _collect_global_declarations(self, fn: ast.FunctionDef) -> dict[str, ast.Global]:
302
+ found: dict[str, ast.Global] = {}
303
+ for node in ast.walk(fn):
304
+ if isinstance(node, (ast.FunctionDef, ast.Lambda)) and node is not fn:
305
+ continue
306
+ if isinstance(node, ast.Global):
307
+ for name in node.names:
308
+ if name in found:
309
+ continue
310
+ found[name] = node
311
+ return found
312
+
313
+ def _collect_function_local_targets(
314
+ self,
315
+ body: list[ast.stmt],
316
+ out: list[tuple[str, ast.AST]],
317
+ ) -> None:
318
+ for stmt in body:
319
+ if isinstance(stmt, ast.Assign):
320
+ for target in stmt.targets:
321
+ self._append_simple_targets(target, stmt, out)
322
+ elif isinstance(stmt, ast.AugAssign):
323
+ self._append_simple_targets(stmt.target, stmt, out)
324
+ elif isinstance(stmt, ast.For):
325
+ self._append_simple_targets(stmt.target, stmt, out)
326
+ self._collect_function_local_targets(stmt.body, out)
327
+ self._collect_function_local_targets(stmt.orelse, out)
328
+ elif isinstance(stmt, ast.If):
329
+ self._collect_function_local_targets(stmt.body, out)
330
+ self._collect_function_local_targets(stmt.orelse, out)
331
+ elif isinstance(stmt, ast.While):
332
+ self._collect_function_local_targets(stmt.body, out)
333
+ self._collect_function_local_targets(stmt.orelse, out)
334
+ # Nested defs are rejected by Phase B and not traversed.
335
+
336
+ @staticmethod
337
+ def _append_simple_targets(target: ast.AST, owner: ast.AST, out: list[tuple[str, ast.AST]]) -> None:
338
+ if isinstance(target, ast.Name):
339
+ out.append((target.id, owner))
340
+ elif isinstance(target, (ast.Tuple, ast.List)):
341
+ for elt in target.elts:
342
+ SymbolResolver._append_simple_targets(elt, owner, out)
343
+
344
+ def _validate_global_order(self, fn: ast.FunctionDef, name: str, global_node: ast.Global) -> None:
345
+ global_line = getattr(global_node, "lineno", 10**9)
346
+ for node in ast.walk(fn):
347
+ if isinstance(node, ast.Name) and node.id == name and getattr(node, "lineno", 10**9) < global_line:
348
+ raise self._name_error(
349
+ f"global '{name}' must appear before that name is used or assigned in the function.",
350
+ global_node,
351
+ )
352
+
353
+ # ---------- resolution ----------
354
+ def _resolve_module_body(self) -> None:
355
+ for stmt in self.frontend.tree.body:
356
+ if isinstance(stmt, ast.FunctionDef):
357
+ continue
358
+ self._resolve_stmt(stmt, self.module, in_function=False)
359
+
360
+ def _resolve_functions(self) -> None:
361
+ for fn in self.frontend.tree.body:
362
+ if not isinstance(fn, ast.FunctionDef):
363
+ continue
364
+ scope = self.function_scopes[fn.name]
365
+ self._function_call_graph[fn.name] = set()
366
+ self._function_module_dependencies[fn.name] = set()
367
+ for stmt in fn.body:
368
+ self._resolve_stmt(stmt, scope, in_function=True, current_function=fn.name)
369
+ for node in ast.walk(fn):
370
+ if not isinstance(node, ast.Name):
371
+ continue
372
+ sym = self.bindings.get(id(node))
373
+ if sym and sym.scope_name == self.module.name and sym.kind in {SymbolKind.MODULE, SymbolKind.HARDWARE}:
374
+ self._function_module_dependencies[fn.name].add(sym.name)
375
+
376
+ def _resolve_stmt(
377
+ self,
378
+ stmt: ast.stmt,
379
+ scope: Scope,
380
+ *,
381
+ in_function: bool,
382
+ current_function: str | None = None,
383
+ ) -> None:
384
+ if isinstance(stmt, ast.ImportFrom):
385
+ return
386
+ if isinstance(stmt, ast.Global):
387
+ return
388
+ if isinstance(stmt, ast.Expr):
389
+ self._resolve_expr(stmt.value, scope, current_function=current_function)
390
+ return
391
+ if isinstance(stmt, ast.Assign):
392
+ self._resolve_expr(stmt.value, scope, current_function=current_function)
393
+ for target in stmt.targets:
394
+ self._resolve_store_target(target, scope, in_function=in_function)
395
+ return
396
+ if isinstance(stmt, ast.AugAssign):
397
+ # AugAssign reads and writes the same binding/value.
398
+ if isinstance(stmt.target, ast.Name):
399
+ self._bind_name(stmt.target, scope, ast.Load(), current_function=current_function)
400
+ self._resolve_store_target(stmt.target, scope, in_function=in_function)
401
+ elif isinstance(stmt.target, ast.Subscript):
402
+ self._resolve_expr(stmt.target, scope, current_function=current_function)
403
+ self._resolve_expr(stmt.value, scope, current_function=current_function)
404
+ return
405
+ if isinstance(stmt, ast.If):
406
+ self._resolve_expr(stmt.test, scope, current_function=current_function)
407
+ for child in stmt.body + stmt.orelse:
408
+ self._resolve_stmt(child, scope, in_function=in_function, current_function=current_function)
409
+ return
410
+ if isinstance(stmt, ast.While):
411
+ self._resolve_expr(stmt.test, scope, current_function=current_function)
412
+ for child in stmt.body + stmt.orelse:
413
+ self._resolve_stmt(child, scope, in_function=in_function, current_function=current_function)
414
+ return
415
+ if isinstance(stmt, ast.For):
416
+ self._resolve_expr(stmt.iter, scope, current_function=current_function)
417
+ self._resolve_store_target(stmt.target, scope, in_function=in_function)
418
+ for child in stmt.body + stmt.orelse:
419
+ self._resolve_stmt(child, scope, in_function=in_function, current_function=current_function)
420
+ return
421
+ if isinstance(stmt, ast.Return):
422
+ if stmt.value is not None:
423
+ self._resolve_expr(stmt.value, scope, current_function=current_function)
424
+ return
425
+ # no-op statements were structurally validated already.
426
+
427
+ def _resolve_store_target(self, target: ast.AST, scope: Scope, *, in_function: bool) -> None:
428
+ if isinstance(target, ast.Name):
429
+ self._bind_name(target, scope, ast.Store())
430
+ return
431
+ if isinstance(target, (ast.Tuple, ast.List)):
432
+ for elt in target.elts:
433
+ self._resolve_store_target(elt, scope, in_function=in_function)
434
+ return
435
+ if isinstance(target, ast.Subscript):
436
+ self._resolve_expr(target.value, scope)
437
+ self._resolve_expr(target.slice, scope)
438
+
439
+ def _resolve_expr(self, node: ast.AST, scope: Scope, *, current_function: str | None = None) -> None:
440
+ if isinstance(node, ast.Name):
441
+ sym = self._bind_name(node, scope, node.ctx, current_function=current_function)
442
+ if current_function and sym.kind == SymbolKind.FUNCTION:
443
+ self._function_call_graph[current_function].add(sym.name)
444
+ return
445
+ if isinstance(node, ast.Constant):
446
+ return
447
+ if isinstance(node, ast.JoinedStr):
448
+ for value in node.values:
449
+ if isinstance(value, ast.FormattedValue):
450
+ self._resolve_expr(value.value, scope, current_function=current_function)
451
+ return
452
+ if isinstance(node, ast.FormattedValue):
453
+ self._resolve_expr(node.value, scope, current_function=current_function)
454
+ return
455
+ if isinstance(node, ast.Call):
456
+ self._resolve_expr(node.func, scope, current_function=current_function)
457
+ for arg in node.args:
458
+ self._resolve_expr(arg, scope, current_function=current_function)
459
+ for kw in node.keywords:
460
+ self._resolve_expr(kw.value, scope, current_function=current_function)
461
+ return
462
+ if isinstance(node, ast.Attribute):
463
+ self._resolve_expr(node.value, scope, current_function=current_function)
464
+ return
465
+ if isinstance(node, ast.UnaryOp):
466
+ self._resolve_expr(node.operand, scope, current_function=current_function)
467
+ return
468
+ if isinstance(node, ast.BinOp):
469
+ self._resolve_expr(node.left, scope, current_function=current_function)
470
+ self._resolve_expr(node.right, scope, current_function=current_function)
471
+ return
472
+ if isinstance(node, ast.Compare):
473
+ self._resolve_expr(node.left, scope, current_function=current_function)
474
+ for value in node.comparators:
475
+ self._resolve_expr(value, scope, current_function=current_function)
476
+ return
477
+ if isinstance(node, ast.BoolOp):
478
+ for value in node.values:
479
+ self._resolve_expr(value, scope, current_function=current_function)
480
+ return
481
+ if isinstance(node, ast.IfExp):
482
+ self._resolve_expr(node.test, scope, current_function=current_function)
483
+ self._resolve_expr(node.body, scope, current_function=current_function)
484
+ self._resolve_expr(node.orelse, scope, current_function=current_function)
485
+ return
486
+ if isinstance(node, ast.List):
487
+ for item in node.elts:
488
+ self._resolve_expr(item, scope, current_function=current_function)
489
+ return
490
+ if isinstance(node, ast.Subscript):
491
+ self._resolve_expr(node.value, scope, current_function=current_function)
492
+ self._resolve_expr(node.slice, scope, current_function=current_function)
493
+ return
494
+
495
+ def _bind_name(
496
+ self,
497
+ node: ast.Name,
498
+ scope: Scope,
499
+ ctx: ast.expr_context,
500
+ *,
501
+ current_function: str | None = None,
502
+ ) -> Symbol:
503
+ name = node.id
504
+ if scope.kind == "function":
505
+ if name in scope.global_names:
506
+ sym = self.module.symbols.get(name)
507
+ elif name in scope.symbols:
508
+ sym = scope.symbols[name]
509
+ else:
510
+ sym = self.module.symbols.get(name)
511
+ else:
512
+ sym = self.module.symbols.get(name)
513
+
514
+ if sym is None:
515
+ raise self._name_error(f"Name '{name}' is not defined.", node)
516
+
517
+ self.bindings[id(node)] = sym
518
+ return sym
519
+
520
+ # ---------- module execution-order check ----------
521
+ def _validate_immediate_top_level_function_calls(self) -> None:
522
+ def_lines = {
523
+ name: (getattr(node, "lineno", 0) or 0)
524
+ for name, node in self._function_nodes.items()
525
+ }
526
+
527
+ for stmt in self.frontend.tree.body:
528
+ if isinstance(stmt, (ast.FunctionDef, ast.ImportFrom)):
529
+ continue
530
+ # The top-level while body executes only after reaching the main
531
+ # loop; every supported setup assignment/definition is before that point.
532
+ if isinstance(stmt, ast.While) and stmt is self.frontend.main_loop:
533
+ continue
534
+ call_line = getattr(stmt, "lineno", 0) or 0
535
+
536
+ # A direct setup-time read cannot see a module variable/object whose
537
+ # first binding statement has not executed yet. Conditional/branch
538
+ # availability after an earlier binding is Step 6 definite assignment.
539
+ for child in ast.walk(stmt):
540
+ if not isinstance(child, ast.Name) or not isinstance(child.ctx, ast.Load):
541
+ continue
542
+ sym = self.bindings.get(id(child))
543
+ if not sym or sym.scope_name != self.module.name:
544
+ continue
545
+ if sym.kind not in {SymbolKind.MODULE, SymbolKind.HARDWARE}:
546
+ continue
547
+ decl_line = sym.declaration_line or 0
548
+ # Compare with the reading node itself, not with the enclosing
549
+ # top-level statement: a name first bound earlier inside the same
550
+ # if/for/while body is available here. Branch/loop availability is
551
+ # decided by the definite-assignment flow phase.
552
+ read_line = getattr(child, "lineno", call_line) or call_line
553
+ if decl_line > read_line:
554
+ raise self._name_error(
555
+ f"Name '{sym.name}' is used before its module definition has executed.",
556
+ child,
557
+ )
558
+
559
+ for called, site_line in self._user_function_call_sites(stmt, call_line):
560
+ unavailable = self._reachable_functions_defined_after(called, site_line, def_lines)
561
+ if unavailable:
562
+ missing = sorted(unavailable, key=lambda n: def_lines[n])[0]
563
+ raise self._name_error(
564
+ f"Function '{missing}' is used before its definition has executed.",
565
+ stmt,
566
+ )
567
+ unavailable_module = self._reachable_module_dependencies_defined_after(called, site_line)
568
+ if unavailable_module:
569
+ missing = sorted(
570
+ unavailable_module,
571
+ key=lambda n: self.module.symbols[n].declaration_line or 0,
572
+ )[0]
573
+ raise self._name_error(
574
+ f"Name '{missing}' is used by this function before its module definition has executed.",
575
+ stmt,
576
+ )
577
+
578
+ def _reachable_functions_defined_after(
579
+ self,
580
+ start: str,
581
+ call_line: int,
582
+ def_lines: dict[str, int],
583
+ ) -> set[str]:
584
+ pending = [start]
585
+ seen: set[str] = set()
586
+ bad: set[str] = set()
587
+ while pending:
588
+ name = pending.pop()
589
+ if name in seen:
590
+ continue
591
+ seen.add(name)
592
+ if name in def_lines and def_lines[name] > call_line:
593
+ bad.add(name)
594
+ pending.extend(self._function_call_graph.get(name, ()))
595
+ return bad
596
+
597
+ def _reachable_module_dependencies_defined_after(self, start: str, call_line: int) -> set[str]:
598
+ pending = [start]
599
+ seen: set[str] = set()
600
+ bad: set[str] = set()
601
+ while pending:
602
+ fn_name = pending.pop()
603
+ if fn_name in seen:
604
+ continue
605
+ seen.add(fn_name)
606
+ for dep_name in self._function_module_dependencies.get(fn_name, ()):
607
+ symbol = self.module.symbols.get(dep_name)
608
+ if symbol is not None and (symbol.declaration_line or 0) > call_line:
609
+ bad.add(dep_name)
610
+ pending.extend(self._function_call_graph.get(fn_name, ()))
611
+ return bad
612
+
613
+ def _user_function_call_sites(self, node: ast.AST, default_line: int) -> list[tuple[str, int]]:
614
+ sites: dict[str, int] = {}
615
+ for child in ast.walk(node):
616
+ if isinstance(child, ast.Call) and isinstance(child.func, ast.Name):
617
+ sym = self.bindings.get(id(child.func))
618
+ if sym and sym.kind == SymbolKind.FUNCTION:
619
+ line = getattr(child, "lineno", default_line) or default_line
620
+ # The earliest call site is the strictest availability point.
621
+ sites[sym.name] = min(line, sites.get(sym.name, line))
622
+ return sorted(sites.items(), key=lambda item: (item[1], item[0]))
623
+
624
+ def _user_function_calls(self, node: ast.AST) -> set[str]:
625
+ result: set[str] = set()
626
+ for child in ast.walk(node):
627
+ if isinstance(child, ast.Call) and isinstance(child.func, ast.Name):
628
+ sym = self.bindings.get(id(child.func))
629
+ if sym and sym.kind == SymbolKind.FUNCTION:
630
+ result.add(sym.name)
631
+ return result
632
+
633
+ @staticmethod
634
+ def _name_error(message: str, node: ast.AST) -> NameResolutionError:
635
+ col = getattr(node, "col_offset", None)
636
+ return NameResolutionError(
637
+ message,
638
+ line=getattr(node, "lineno", None),
639
+ column=(col + 1) if col is not None else None,
640
+ end_line=getattr(node, "end_lineno", None),
641
+ end_column=getattr(node, "end_col_offset", None),
642
+ )
643
+
644
+
645
+ def resolve_symbols(frontend: FrontendModule) -> ResolvedModule:
646
+ return SymbolResolver(frontend).resolve()