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.
- Barmaja_PyArduino/BARMAJA_PyArduino.ico +0 -0
- Barmaja_PyArduino/__init__.py +528 -0
- Barmaja_PyArduino/__init__.pyi +331 -0
- Barmaja_PyArduino/__main__.py +2 -0
- Barmaja_PyArduino/adapters/__init__.py +35 -0
- Barmaja_PyArduino/api_registry.py +459 -0
- Barmaja_PyArduino/backend/__init__.py +3 -0
- Barmaja_PyArduino/backend/uno_cpp.py +1680 -0
- Barmaja_PyArduino/board.py +80 -0
- Barmaja_PyArduino/cli.py +223 -0
- Barmaja_PyArduino/compiler.py +1721 -0
- Barmaja_PyArduino/console.py +217 -0
- Barmaja_PyArduino/device_packs.py +367 -0
- Barmaja_PyArduino/device_packs_builtin/Button/arduino/Barmaja_Button/library.properties +9 -0
- Barmaja_PyArduino/device_packs_builtin/Button/arduino/Barmaja_Button/src/Barmaja_Button.h +18 -0
- Barmaja_PyArduino/device_packs_builtin/Button/example.py +10 -0
- Barmaja_PyArduino/device_packs_builtin/Button/pack.json +40 -0
- Barmaja_PyArduino/device_packs_builtin/ClockDS3231/arduino/Barmaja_ClockDS3231/library.properties +9 -0
- Barmaja_PyArduino/device_packs_builtin/ClockDS3231/arduino/Barmaja_ClockDS3231/src/Barmaja_ClockDS3231.h +20 -0
- Barmaja_PyArduino/device_packs_builtin/ClockDS3231/example.py +8 -0
- Barmaja_PyArduino/device_packs_builtin/ClockDS3231/pack.json +136 -0
- Barmaja_PyArduino/device_packs_builtin/DistanceVL53L0X/arduino/Barmaja_DistanceVL53L0X/library.properties +9 -0
- Barmaja_PyArduino/device_packs_builtin/DistanceVL53L0X/arduino/Barmaja_DistanceVL53L0X/src/Barmaja_DistanceVL53L0X.h +12 -0
- Barmaja_PyArduino/device_packs_builtin/DistanceVL53L0X/example.py +8 -0
- Barmaja_PyArduino/device_packs_builtin/DistanceVL53L0X/pack.json +48 -0
- Barmaja_PyArduino/device_packs_builtin/LCDParallel/arduino/Barmaja_LCDParallel/library.properties +9 -0
- Barmaja_PyArduino/device_packs_builtin/LCDParallel/arduino/Barmaja_LCDParallel/src/Barmaja_LCDParallel.h +17 -0
- Barmaja_PyArduino/device_packs_builtin/LCDParallel/example.py +10 -0
- Barmaja_PyArduino/device_packs_builtin/LCDParallel/pack.json +137 -0
- Barmaja_PyArduino/device_packs_builtin/LoadCellHX711/arduino/Barmaja_LoadCellHX711/library.properties +9 -0
- Barmaja_PyArduino/device_packs_builtin/LoadCellHX711/arduino/Barmaja_LoadCellHX711/src/Barmaja_LoadCellHX711.h +16 -0
- Barmaja_PyArduino/device_packs_builtin/LoadCellHX711/example.py +11 -0
- Barmaja_PyArduino/device_packs_builtin/LoadCellHX711/pack.json +74 -0
- Barmaja_PyArduino/device_packs_builtin/NeoPixelStrip/arduino/Barmaja_NeoPixelStrip/library.properties +9 -0
- Barmaja_PyArduino/device_packs_builtin/NeoPixelStrip/arduino/Barmaja_NeoPixelStrip/src/Barmaja_NeoPixelStrip.h +15 -0
- Barmaja_PyArduino/device_packs_builtin/NeoPixelStrip/example.py +13 -0
- Barmaja_PyArduino/device_packs_builtin/NeoPixelStrip/pack.json +114 -0
- Barmaja_PyArduino/device_packs_builtin/OLEDText/arduino/Barmaja_OLEDText/library.properties +9 -0
- Barmaja_PyArduino/device_packs_builtin/OLEDText/arduino/Barmaja_OLEDText/src/Barmaja_OLEDText.h +21 -0
- Barmaja_PyArduino/device_packs_builtin/OLEDText/example.py +9 -0
- Barmaja_PyArduino/device_packs_builtin/OLEDText/pack.json +103 -0
- Barmaja_PyArduino/device_packs_builtin/PressureBMP280/arduino/Barmaja_PressureBMP280/library.properties +9 -0
- Barmaja_PyArduino/device_packs_builtin/PressureBMP280/arduino/Barmaja_PressureBMP280/src/Barmaja_PressureBMP280.h +16 -0
- Barmaja_PyArduino/device_packs_builtin/PressureBMP280/example.py +8 -0
- Barmaja_PyArduino/device_packs_builtin/PressureBMP280/pack.json +92 -0
- Barmaja_PyArduino/device_packs_builtin/RotaryEncoder/arduino/Barmaja_RotaryEncoder/library.properties +9 -0
- Barmaja_PyArduino/device_packs_builtin/RotaryEncoder/arduino/Barmaja_RotaryEncoder/src/Barmaja_RotaryEncoder.h +18 -0
- Barmaja_PyArduino/device_packs_builtin/RotaryEncoder/example.py +10 -0
- Barmaja_PyArduino/device_packs_builtin/RotaryEncoder/pack.json +44 -0
- Barmaja_PyArduino/device_packs_builtin/ServoDriverPCA9685/arduino/Barmaja_ServoDriverPCA9685/library.properties +9 -0
- Barmaja_PyArduino/device_packs_builtin/ServoDriverPCA9685/arduino/Barmaja_ServoDriverPCA9685/src/Barmaja_ServoDriverPCA9685.h +15 -0
- Barmaja_PyArduino/device_packs_builtin/ServoDriverPCA9685/example.py +10 -0
- Barmaja_PyArduino/device_packs_builtin/ServoDriverPCA9685/pack.json +117 -0
- Barmaja_PyArduino/device_packs_builtin/ShiftRegister595/arduino/Barmaja_ShiftRegister595/library.properties +9 -0
- Barmaja_PyArduino/device_packs_builtin/ShiftRegister595/arduino/Barmaja_ShiftRegister595/src/Barmaja_ShiftRegister595.h +14 -0
- Barmaja_PyArduino/device_packs_builtin/ShiftRegister595/example.py +9 -0
- Barmaja_PyArduino/device_packs_builtin/ShiftRegister595/pack.json +83 -0
- Barmaja_PyArduino/device_packs_builtin/TemperatureDS18B20/arduino/Barmaja_TemperatureDS18B20/library.properties +9 -0
- Barmaja_PyArduino/device_packs_builtin/TemperatureDS18B20/arduino/Barmaja_TemperatureDS18B20/src/Barmaja_TemperatureDS18B20.h +16 -0
- Barmaja_PyArduino/device_packs_builtin/TemperatureDS18B20/example.py +8 -0
- Barmaja_PyArduino/device_packs_builtin/TemperatureDS18B20/pack.json +63 -0
- Barmaja_PyArduino/doctor.py +406 -0
- Barmaja_PyArduino/environment.py +595 -0
- Barmaja_PyArduino/errors.py +166 -0
- Barmaja_PyArduino/flow.py +754 -0
- Barmaja_PyArduino/frontend.py +611 -0
- Barmaja_PyArduino/installer.py +151 -0
- Barmaja_PyArduino/ir.py +660 -0
- Barmaja_PyArduino/library_adder.py +440 -0
- Barmaja_PyArduino/live.py +859 -0
- Barmaja_PyArduino/lowering.py +596 -0
- Barmaja_PyArduino/numeric.py +748 -0
- Barmaja_PyArduino/pack_builder.py +544 -0
- Barmaja_PyArduino/py.typed +0 -0
- Barmaja_PyArduino/serial_monitor.py +172 -0
- Barmaja_PyArduino/source.py +184 -0
- Barmaja_PyArduino/stubgen.py +148 -0
- Barmaja_PyArduino/symbols.py +646 -0
- Barmaja_PyArduino/target.py +1631 -0
- Barmaja_PyArduino/toolchain.py +506 -0
- Barmaja_PyArduino/typesys.py +917 -0
- Barmaja_PyArduino/workflow.py +213 -0
- barmaja_pyarduino-2.0.0.dist-info/METADATA +39 -0
- barmaja_pyarduino-2.0.0.dist-info/RECORD +87 -0
- barmaja_pyarduino-2.0.0.dist-info/WHEEL +5 -0
- barmaja_pyarduino-2.0.0.dist-info/licenses/LICENSE.txt +7 -0
- 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()
|