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,917 @@
|
|
|
1
|
+
from __future__ import annotations
|
|
2
|
+
|
|
3
|
+
import ast
|
|
4
|
+
from dataclasses import dataclass
|
|
5
|
+
from enum import Enum
|
|
6
|
+
|
|
7
|
+
from .errors import TypeCheckError, UnsupportedFeatureError
|
|
8
|
+
from .flow import FlowAnalyzedModule
|
|
9
|
+
from .symbols import ResolvedModule, Symbol, SymbolId, SymbolKind
|
|
10
|
+
from .api_registry import CORE_FUNCTIONS, HARDWARE_CLASSES, SINGLETONS, SignatureSpec, matching_signatures
|
|
11
|
+
|
|
12
|
+
|
|
13
|
+
class TypeKind(Enum):
|
|
14
|
+
UNKNOWN = "Unknown"
|
|
15
|
+
BOOL = "Bool"
|
|
16
|
+
INT = "Int"
|
|
17
|
+
FLOAT = "Float"
|
|
18
|
+
STRING = "String"
|
|
19
|
+
VOID = "Void"
|
|
20
|
+
TIME_U32 = "TimeU32"
|
|
21
|
+
HARDWARE = "Hardware"
|
|
22
|
+
FIXED_LIST = "FixedList"
|
|
23
|
+
|
|
24
|
+
|
|
25
|
+
@dataclass(frozen=True)
|
|
26
|
+
class BarmajaType:
|
|
27
|
+
kind: TypeKind
|
|
28
|
+
hardware_class: str | None = None
|
|
29
|
+
element_type: "BarmajaType | None" = None
|
|
30
|
+
length: int | None = None
|
|
31
|
+
|
|
32
|
+
@property
|
|
33
|
+
def display_name(self) -> str:
|
|
34
|
+
if self.kind == TypeKind.HARDWARE:
|
|
35
|
+
return f"Hardware[{self.hardware_class}]"
|
|
36
|
+
if self.kind == TypeKind.FIXED_LIST:
|
|
37
|
+
inner = self.element_type.display_name if self.element_type is not None else "?"
|
|
38
|
+
return f"FixedList[{inner},{self.length}]"
|
|
39
|
+
return self.kind.value
|
|
40
|
+
|
|
41
|
+
@property
|
|
42
|
+
def cpp_type(self) -> str:
|
|
43
|
+
mapping = {
|
|
44
|
+
TypeKind.BOOL: "bool",
|
|
45
|
+
TypeKind.INT: "int32_t",
|
|
46
|
+
TypeKind.FLOAT: "float",
|
|
47
|
+
TypeKind.STRING: "const char*",
|
|
48
|
+
TypeKind.VOID: "void",
|
|
49
|
+
TypeKind.TIME_U32: "uint32_t",
|
|
50
|
+
}
|
|
51
|
+
if self.kind == TypeKind.HARDWARE:
|
|
52
|
+
return self.hardware_class or "/*hardware*/"
|
|
53
|
+
if self.kind == TypeKind.FIXED_LIST:
|
|
54
|
+
if self.element_type is None or self.length is None:
|
|
55
|
+
raise ValueError("Incomplete FixedList type")
|
|
56
|
+
return f"{self.element_type.cpp_type}[{self.length}]"
|
|
57
|
+
if self.kind == TypeKind.UNKNOWN:
|
|
58
|
+
raise ValueError("Unknown type has no C++ representation")
|
|
59
|
+
return mapping[self.kind]
|
|
60
|
+
|
|
61
|
+
|
|
62
|
+
UNKNOWN = BarmajaType(TypeKind.UNKNOWN)
|
|
63
|
+
BOOL = BarmajaType(TypeKind.BOOL)
|
|
64
|
+
INT = BarmajaType(TypeKind.INT)
|
|
65
|
+
FLOAT = BarmajaType(TypeKind.FLOAT)
|
|
66
|
+
STRING = BarmajaType(TypeKind.STRING)
|
|
67
|
+
VOID = BarmajaType(TypeKind.VOID)
|
|
68
|
+
TIME_U32 = BarmajaType(TypeKind.TIME_U32)
|
|
69
|
+
|
|
70
|
+
|
|
71
|
+
def hardware_type(name: str) -> BarmajaType:
|
|
72
|
+
return BarmajaType(TypeKind.HARDWARE, name)
|
|
73
|
+
|
|
74
|
+
|
|
75
|
+
def fixed_list_type(element_type: BarmajaType, length: int) -> BarmajaType:
|
|
76
|
+
return BarmajaType(TypeKind.FIXED_LIST, element_type=element_type, length=length)
|
|
77
|
+
|
|
78
|
+
|
|
79
|
+
NUMERIC_KINDS = {TypeKind.INT, TypeKind.FLOAT}
|
|
80
|
+
SCALAR_KINDS = {TypeKind.BOOL, TypeKind.INT, TypeKind.FLOAT, TypeKind.STRING, TypeKind.TIME_U32}
|
|
81
|
+
|
|
82
|
+
|
|
83
|
+
@dataclass(frozen=True)
|
|
84
|
+
class FunctionSignature:
|
|
85
|
+
name: str
|
|
86
|
+
symbol: Symbol
|
|
87
|
+
parameters: tuple[Symbol, ...]
|
|
88
|
+
parameter_types: tuple[BarmajaType, ...]
|
|
89
|
+
return_type: BarmajaType
|
|
90
|
+
|
|
91
|
+
|
|
92
|
+
@dataclass(frozen=True)
|
|
93
|
+
class TypedModule:
|
|
94
|
+
flow: FlowAnalyzedModule
|
|
95
|
+
symbol_types: dict[SymbolId, BarmajaType]
|
|
96
|
+
expression_types: dict[int, BarmajaType]
|
|
97
|
+
function_signatures: dict[str, FunctionSignature]
|
|
98
|
+
|
|
99
|
+
@property
|
|
100
|
+
def resolved(self) -> ResolvedModule:
|
|
101
|
+
return self.flow.resolved
|
|
102
|
+
|
|
103
|
+
def type_for_symbol(self, symbol: Symbol) -> BarmajaType:
|
|
104
|
+
return self.symbol_types[symbol.symbol_id]
|
|
105
|
+
|
|
106
|
+
def type_for_expr(self, node: ast.AST) -> BarmajaType:
|
|
107
|
+
return self.expression_types[id(node)]
|
|
108
|
+
|
|
109
|
+
def signature_for(self, name: str) -> FunctionSignature:
|
|
110
|
+
return self.function_signatures[name]
|
|
111
|
+
|
|
112
|
+
|
|
113
|
+
class TypeAnalyzer:
|
|
114
|
+
"""Step-7 fixed-point scalar/function type inference.
|
|
115
|
+
|
|
116
|
+
This phase owns language types. The backend receives resolved Barmaja
|
|
117
|
+
types and must not infer C++ storage types from AST spelling/shape.
|
|
118
|
+
Numeric overflow/range proofs are intentionally Step 8.
|
|
119
|
+
"""
|
|
120
|
+
|
|
121
|
+
def __init__(self, flow: FlowAnalyzedModule) -> None:
|
|
122
|
+
self.flow = flow
|
|
123
|
+
self.resolved = flow.resolved
|
|
124
|
+
self.frontend = self.resolved.frontend
|
|
125
|
+
self.symbol_types: dict[SymbolId, BarmajaType] = {}
|
|
126
|
+
self.expression_types: dict[int, BarmajaType] = {}
|
|
127
|
+
self._function_nodes: dict[str, ast.FunctionDef] = {
|
|
128
|
+
node.name: node for node in self.frontend.tree.body if isinstance(node, ast.FunctionDef)
|
|
129
|
+
}
|
|
130
|
+
self._param_symbols: dict[str, list[Symbol]] = {}
|
|
131
|
+
self._return_types: dict[str, BarmajaType] = {}
|
|
132
|
+
self._hardware_classes: dict[SymbolId, str] = {}
|
|
133
|
+
self._changed = False
|
|
134
|
+
|
|
135
|
+
def analyze(self) -> TypedModule:
|
|
136
|
+
self._reject_recursion()
|
|
137
|
+
self._seed_symbols()
|
|
138
|
+
|
|
139
|
+
# Calls and return expressions can constrain one another across source
|
|
140
|
+
# order. Iterate to a fixed point instead of using function order.
|
|
141
|
+
for _ in range(64):
|
|
142
|
+
self._changed = False
|
|
143
|
+
self.expression_types.clear()
|
|
144
|
+
self._scan_module()
|
|
145
|
+
if not self._changed:
|
|
146
|
+
break
|
|
147
|
+
else:
|
|
148
|
+
raise TypeCheckError("Function/type inference did not converge.")
|
|
149
|
+
|
|
150
|
+
# One strict pass after convergence rejects unresolved/invalid forms.
|
|
151
|
+
self.expression_types.clear()
|
|
152
|
+
self._scan_module(strict=True)
|
|
153
|
+
self._require_resolved_user_symbols()
|
|
154
|
+
|
|
155
|
+
signatures: dict[str, FunctionSignature] = {}
|
|
156
|
+
for name, fn in self._function_nodes.items():
|
|
157
|
+
fn_symbol = self.resolved.symbol_for_function(fn)
|
|
158
|
+
params = tuple(self._param_symbols[name])
|
|
159
|
+
signatures[name] = FunctionSignature(
|
|
160
|
+
name=name,
|
|
161
|
+
symbol=fn_symbol,
|
|
162
|
+
parameters=params,
|
|
163
|
+
parameter_types=tuple(self.symbol_types[p.symbol_id] for p in params),
|
|
164
|
+
return_type=self._return_types[name],
|
|
165
|
+
)
|
|
166
|
+
|
|
167
|
+
return TypedModule(
|
|
168
|
+
flow=self.flow,
|
|
169
|
+
symbol_types=dict(self.symbol_types),
|
|
170
|
+
expression_types=dict(self.expression_types),
|
|
171
|
+
function_signatures=signatures,
|
|
172
|
+
)
|
|
173
|
+
|
|
174
|
+
def _reject_recursion(self) -> None:
|
|
175
|
+
names = set(self._function_nodes)
|
|
176
|
+
graph: dict[str, set[str]] = {name: set() for name in names}
|
|
177
|
+
for name, fn in self._function_nodes.items():
|
|
178
|
+
for node in ast.walk(fn):
|
|
179
|
+
if isinstance(node, ast.Call) and isinstance(node.func, ast.Name) and node.func.id in names:
|
|
180
|
+
graph[name].add(node.func.id)
|
|
181
|
+
|
|
182
|
+
visiting: set[str] = set()
|
|
183
|
+
visited: set[str] = set()
|
|
184
|
+
|
|
185
|
+
def visit(name: str, path: list[str]) -> None:
|
|
186
|
+
if name in visiting:
|
|
187
|
+
start = path.index(name) if name in path else 0
|
|
188
|
+
cycle = path[start:] + [name]
|
|
189
|
+
fn = self._function_nodes[name]
|
|
190
|
+
raise UnsupportedFeatureError(
|
|
191
|
+
"Recursive function calls are not supported on Arduino Uno in Barmaja PyArduino V1: "
|
|
192
|
+
+ " -> ".join(cycle),
|
|
193
|
+
line=getattr(fn, "lineno", None),
|
|
194
|
+
)
|
|
195
|
+
if name in visited:
|
|
196
|
+
return
|
|
197
|
+
visiting.add(name)
|
|
198
|
+
path.append(name)
|
|
199
|
+
for other in sorted(graph[name]):
|
|
200
|
+
visit(other, path)
|
|
201
|
+
path.pop()
|
|
202
|
+
visiting.remove(name)
|
|
203
|
+
visited.add(name)
|
|
204
|
+
|
|
205
|
+
for name in sorted(graph):
|
|
206
|
+
visit(name, [])
|
|
207
|
+
|
|
208
|
+
def _seed_symbols(self) -> None:
|
|
209
|
+
# Every user data symbol starts unknown; functions are represented by
|
|
210
|
+
# signatures rather than scalar symbol values.
|
|
211
|
+
for symbol in self.resolved.module_scope.symbols.values():
|
|
212
|
+
if symbol.kind in {SymbolKind.MODULE, SymbolKind.HARDWARE}:
|
|
213
|
+
self.symbol_types[symbol.symbol_id] = UNKNOWN
|
|
214
|
+
for name, scope in self.resolved.function_scopes.items():
|
|
215
|
+
params: list[Symbol] = []
|
|
216
|
+
for symbol in scope.symbols.values():
|
|
217
|
+
if symbol.kind in {SymbolKind.PARAMETER, SymbolKind.LOCAL}:
|
|
218
|
+
self.symbol_types[symbol.symbol_id] = UNKNOWN
|
|
219
|
+
if symbol.kind == SymbolKind.PARAMETER:
|
|
220
|
+
params.append(symbol)
|
|
221
|
+
# Preserve Python parameter order rather than dict assumptions.
|
|
222
|
+
fn = self._function_nodes[name]
|
|
223
|
+
ordered: list[Symbol] = []
|
|
224
|
+
for arg in fn.args.args:
|
|
225
|
+
ordered.append(self.resolved.symbol_for_parameter(arg))
|
|
226
|
+
self._param_symbols[name] = ordered
|
|
227
|
+
self._return_types[name] = VOID if not self.flow.function_facts[name].has_value_return else UNKNOWN
|
|
228
|
+
|
|
229
|
+
# Hardware object constructor assignments are a direct type source.
|
|
230
|
+
for stmt in self.frontend.tree.body:
|
|
231
|
+
if self._is_hardware_assignment(stmt):
|
|
232
|
+
target = stmt.targets[0]
|
|
233
|
+
sym = self.resolved.symbol_for_name(target)
|
|
234
|
+
cls = stmt.value.func.id
|
|
235
|
+
typ = hardware_type(cls)
|
|
236
|
+
self.symbol_types[sym.symbol_id] = typ
|
|
237
|
+
self._hardware_classes[sym.symbol_id] = cls
|
|
238
|
+
|
|
239
|
+
@staticmethod
|
|
240
|
+
def _is_hardware_assignment(node: ast.AST) -> bool:
|
|
241
|
+
return (
|
|
242
|
+
isinstance(node, ast.Assign)
|
|
243
|
+
and len(node.targets) == 1
|
|
244
|
+
and isinstance(node.targets[0], ast.Name)
|
|
245
|
+
and isinstance(node.value, ast.Call)
|
|
246
|
+
and isinstance(node.value.func, ast.Name)
|
|
247
|
+
and node.value.func.id in HARDWARE_CLASSES
|
|
248
|
+
)
|
|
249
|
+
|
|
250
|
+
def _scan_module(self, strict: bool = False) -> None:
|
|
251
|
+
for stmt in self.frontend.tree.body:
|
|
252
|
+
if isinstance(stmt, (ast.ImportFrom, ast.FunctionDef)):
|
|
253
|
+
continue
|
|
254
|
+
if self._is_hardware_assignment(stmt):
|
|
255
|
+
# Still type-check constructor arguments.
|
|
256
|
+
self._expr_type(stmt.value, strict=strict, value_required=True)
|
|
257
|
+
continue
|
|
258
|
+
if isinstance(stmt, ast.While) and isinstance(stmt.test, ast.Constant) and stmt.test.value is True:
|
|
259
|
+
self._scan_block(stmt.body, strict=strict, function_name=None)
|
|
260
|
+
continue
|
|
261
|
+
self._scan_stmt(stmt, strict=strict, function_name=None)
|
|
262
|
+
|
|
263
|
+
for name, fn in self._function_nodes.items():
|
|
264
|
+
self._scan_block(fn.body, strict=strict, function_name=name)
|
|
265
|
+
|
|
266
|
+
def _scan_block(self, body: list[ast.stmt], *, strict: bool, function_name: str | None) -> None:
|
|
267
|
+
for stmt in body:
|
|
268
|
+
self._scan_stmt(stmt, strict=strict, function_name=function_name)
|
|
269
|
+
|
|
270
|
+
def _scan_stmt(self, stmt: ast.stmt, *, strict: bool, function_name: str | None) -> None:
|
|
271
|
+
if isinstance(stmt, (ast.Pass, ast.Global)):
|
|
272
|
+
return
|
|
273
|
+
if isinstance(stmt, ast.Expr):
|
|
274
|
+
if isinstance(stmt.value, ast.Constant) and (stmt.value.value is Ellipsis or isinstance(stmt.value.value, str)):
|
|
275
|
+
return
|
|
276
|
+
self._expr_type(stmt.value, strict=strict, value_required=False)
|
|
277
|
+
return
|
|
278
|
+
if isinstance(stmt, ast.Assign):
|
|
279
|
+
# Supported tuple/list unpacking is pair-wise and fixed at compile
|
|
280
|
+
# time. Type each element directly instead of inventing a general
|
|
281
|
+
# Tuple runtime type.
|
|
282
|
+
if (
|
|
283
|
+
len(stmt.targets) == 1
|
|
284
|
+
and isinstance(stmt.targets[0], (ast.Tuple, ast.List))
|
|
285
|
+
and isinstance(stmt.value, (ast.Tuple, ast.List))
|
|
286
|
+
):
|
|
287
|
+
for target, value in zip(stmt.targets[0].elts, stmt.value.elts):
|
|
288
|
+
value_type = self._expr_type(value, strict=strict, value_required=True)
|
|
289
|
+
self._assign_target(target, value_type, stmt, strict)
|
|
290
|
+
return
|
|
291
|
+
value_type = self._expr_type(stmt.value, strict=strict, value_required=True)
|
|
292
|
+
if value_type.kind == TypeKind.VOID:
|
|
293
|
+
raise self._error("A Void call cannot be assigned to a variable.", stmt.value)
|
|
294
|
+
if strict and value_type.kind == TypeKind.FIXED_LIST and not isinstance(stmt.value, ast.List):
|
|
295
|
+
# Python would make both names share ONE list object; a fixed Uno array
|
|
296
|
+
# cannot alias another array, so a copy would silently change behaviour.
|
|
297
|
+
raise self._error(
|
|
298
|
+
"A list variable can only be given a list literal such as [1, 2, 3] in Barmaja PyArduino V1. "
|
|
299
|
+
"Assigning one list to another name (sharing or copying a list) is not supported; "
|
|
300
|
+
"copy the elements one by one instead.",
|
|
301
|
+
stmt.value,
|
|
302
|
+
)
|
|
303
|
+
for target in stmt.targets:
|
|
304
|
+
if isinstance(target, ast.Subscript):
|
|
305
|
+
list_type = self._expr_type(target.value, strict=strict, value_required=True)
|
|
306
|
+
index_type = self._expr_type(target.slice, strict=strict, value_required=True)
|
|
307
|
+
self._require_kind(index_type, {TypeKind.INT}, target.slice, "List index must be Int.", strict)
|
|
308
|
+
if strict and list_type.kind != TypeKind.FIXED_LIST:
|
|
309
|
+
raise self._error("Indexed assignment requires a fixed list.", target.value)
|
|
310
|
+
if list_type.kind == TypeKind.FIXED_LIST and list_type.element_type is not None:
|
|
311
|
+
joined = self._join(list_type.element_type, value_type, stmt.value, strict)
|
|
312
|
+
if (
|
|
313
|
+
strict
|
|
314
|
+
and value_type.kind != TypeKind.UNKNOWN
|
|
315
|
+
and joined != list_type.element_type
|
|
316
|
+
):
|
|
317
|
+
# A fixed list keeps one element type. The only implicit
|
|
318
|
+
# element conversion is the lossless Int -> Float store; a
|
|
319
|
+
# Float value must never be silently truncated into an Int list.
|
|
320
|
+
raise self._error(
|
|
321
|
+
f"Cannot store a {value_type.display_name} value in a list of "
|
|
322
|
+
f"{list_type.element_type.display_name} values; fixed lists keep one element type. "
|
|
323
|
+
"Use int(...) or round(...) explicitly, or make the list hold Float values such as [1.0, 2.0].",
|
|
324
|
+
stmt.value,
|
|
325
|
+
)
|
|
326
|
+
self._validate_static_index(target.slice, list_type.length or 0, strict)
|
|
327
|
+
continue
|
|
328
|
+
self._assign_target(target, value_type, stmt, strict)
|
|
329
|
+
return
|
|
330
|
+
if isinstance(stmt, ast.AugAssign):
|
|
331
|
+
left = self._expr_type(stmt.target, strict=strict, value_required=True)
|
|
332
|
+
right = self._expr_type(stmt.value, strict=strict, value_required=True)
|
|
333
|
+
result = self._numeric_binop_type(left, right, stmt, division=isinstance(stmt.op, ast.Div), strict=strict)
|
|
334
|
+
sym = self.resolved.symbol_for_name(stmt.target)
|
|
335
|
+
self._merge_symbol(sym, result, stmt, strict)
|
|
336
|
+
return
|
|
337
|
+
if isinstance(stmt, ast.If):
|
|
338
|
+
cond = self._expr_type(stmt.test, strict=strict, value_required=True)
|
|
339
|
+
self._require_bool(cond, stmt.test, strict)
|
|
340
|
+
self._scan_block(stmt.body, strict=strict, function_name=function_name)
|
|
341
|
+
self._scan_block(stmt.orelse, strict=strict, function_name=function_name)
|
|
342
|
+
return
|
|
343
|
+
if isinstance(stmt, ast.While):
|
|
344
|
+
cond = self._expr_type(stmt.test, strict=strict, value_required=True)
|
|
345
|
+
self._require_bool(cond, stmt.test, strict)
|
|
346
|
+
self._scan_block(stmt.body, strict=strict, function_name=function_name)
|
|
347
|
+
return
|
|
348
|
+
if isinstance(stmt, ast.For):
|
|
349
|
+
iterator_type = self._expr_type(stmt.iter, strict=strict, value_required=False)
|
|
350
|
+
if isinstance(stmt.iter, ast.Call) and isinstance(stmt.iter.func, ast.Name) and stmt.iter.func.id == "range":
|
|
351
|
+
if isinstance(stmt.target, ast.Name):
|
|
352
|
+
self._merge_symbol(self.resolved.symbol_for_name(stmt.target), INT, stmt.target, strict)
|
|
353
|
+
self.expression_types[id(stmt.target)] = INT
|
|
354
|
+
elif isinstance(stmt.iter, ast.Call) and isinstance(stmt.iter.func, ast.Name) and stmt.iter.func.id == "enumerate":
|
|
355
|
+
if stmt.iter.args:
|
|
356
|
+
list_type = self._expr_type(stmt.iter.args[0], strict=strict, value_required=True)
|
|
357
|
+
if strict and list_type.kind != TypeKind.FIXED_LIST:
|
|
358
|
+
raise self._error("enumerate() requires a fixed list.", stmt.iter.args[0])
|
|
359
|
+
if len(stmt.iter.args) == 2:
|
|
360
|
+
start_type = self._expr_type(stmt.iter.args[1], strict=strict, value_required=True)
|
|
361
|
+
self._require_kind(start_type, {TypeKind.INT}, stmt.iter.args[1], "enumerate() start must be Int.", strict)
|
|
362
|
+
if list_type.kind == TypeKind.FIXED_LIST and isinstance(stmt.target, (ast.Tuple, ast.List)) and len(stmt.target.elts) == 2:
|
|
363
|
+
first, second = stmt.target.elts
|
|
364
|
+
if isinstance(first, ast.Name):
|
|
365
|
+
self._merge_symbol(self.resolved.symbol_for_name(first), INT, first, strict)
|
|
366
|
+
self.expression_types[id(first)] = INT
|
|
367
|
+
if isinstance(second, ast.Name) and list_type.element_type is not None:
|
|
368
|
+
self._merge_symbol(self.resolved.symbol_for_name(second), list_type.element_type, second, strict)
|
|
369
|
+
self.expression_types[id(second)] = list_type.element_type
|
|
370
|
+
else:
|
|
371
|
+
if strict and iterator_type.kind != TypeKind.FIXED_LIST:
|
|
372
|
+
raise self._error("for iteration requires range() or a fixed list in V1.", stmt.iter)
|
|
373
|
+
if iterator_type.kind == TypeKind.FIXED_LIST and isinstance(stmt.target, ast.Name) and iterator_type.element_type is not None:
|
|
374
|
+
self._merge_symbol(self.resolved.symbol_for_name(stmt.target), iterator_type.element_type, stmt.target, strict)
|
|
375
|
+
self.expression_types[id(stmt.target)] = iterator_type.element_type
|
|
376
|
+
self._scan_block(stmt.body, strict=strict, function_name=function_name)
|
|
377
|
+
return
|
|
378
|
+
if isinstance(stmt, ast.Return):
|
|
379
|
+
if function_name is None:
|
|
380
|
+
return
|
|
381
|
+
typ = VOID if stmt.value is None else self._expr_type(stmt.value, strict=strict, value_required=True)
|
|
382
|
+
if typ.kind == TypeKind.VOID and stmt.value is not None:
|
|
383
|
+
raise self._error("A function cannot return a Void call as a value.", stmt.value)
|
|
384
|
+
self._merge_return(function_name, typ, stmt, strict)
|
|
385
|
+
return
|
|
386
|
+
if isinstance(stmt, (ast.Break, ast.Continue)):
|
|
387
|
+
return
|
|
388
|
+
|
|
389
|
+
def _assign_target(self, target: ast.AST, value_type: BarmajaType, owner: ast.AST, strict: bool) -> None:
|
|
390
|
+
if isinstance(target, ast.Name):
|
|
391
|
+
sym = self.resolved.symbol_for_name(target)
|
|
392
|
+
self._merge_symbol(sym, value_type, owner, strict)
|
|
393
|
+
self.expression_types[id(target)] = self.symbol_types[sym.symbol_id]
|
|
394
|
+
return
|
|
395
|
+
if isinstance(target, (ast.Tuple, ast.List)):
|
|
396
|
+
# The current closed frontend only reaches tuple/list unpacking for
|
|
397
|
+
# compile-time literal pairs. Keep pair typing exact.
|
|
398
|
+
if isinstance(owner, ast.Assign) and isinstance(owner.value, (ast.Tuple, ast.List)):
|
|
399
|
+
for t, v in zip(target.elts, owner.value.elts):
|
|
400
|
+
self._assign_target(t, self._expr_type(v, strict=strict, value_required=True), owner, strict)
|
|
401
|
+
return
|
|
402
|
+
if strict:
|
|
403
|
+
raise self._error("This assignment target cannot be typed in V1.", target)
|
|
404
|
+
|
|
405
|
+
def _expr_type(self, node: ast.AST, *, strict: bool, value_required: bool) -> BarmajaType:
|
|
406
|
+
typ = self._expr_type_uncached(node, strict=strict, value_required=value_required)
|
|
407
|
+
self.expression_types[id(node)] = typ
|
|
408
|
+
if value_required and typ.kind == TypeKind.VOID:
|
|
409
|
+
raise self._error("A Void call cannot be used where a value is required.", node)
|
|
410
|
+
return typ
|
|
411
|
+
|
|
412
|
+
def _expr_type_uncached(self, node: ast.AST, *, strict: bool, value_required: bool) -> BarmajaType:
|
|
413
|
+
if isinstance(node, ast.Constant):
|
|
414
|
+
if isinstance(node.value, bool):
|
|
415
|
+
return BOOL
|
|
416
|
+
if isinstance(node.value, int):
|
|
417
|
+
return INT
|
|
418
|
+
if isinstance(node.value, float):
|
|
419
|
+
return FLOAT
|
|
420
|
+
if isinstance(node.value, str):
|
|
421
|
+
return STRING
|
|
422
|
+
if node.value is Ellipsis:
|
|
423
|
+
return VOID
|
|
424
|
+
|
|
425
|
+
if isinstance(node, ast.Name):
|
|
426
|
+
sym = self.resolved.symbol_for_name(node)
|
|
427
|
+
if sym.kind == SymbolKind.API:
|
|
428
|
+
if node.id in {"HIGH", "LOW", "INPUT", "OUTPUT", "INPUT_PULLUP", "A0", "A1", "A2", "A3", "A4", "A5", "DHT11", "DHT22", "CM", "INC"}:
|
|
429
|
+
return INT
|
|
430
|
+
if strict:
|
|
431
|
+
raise self._error(f"API name '{node.id}' is not a value.", node)
|
|
432
|
+
return UNKNOWN
|
|
433
|
+
if sym.kind == SymbolKind.BUILTIN:
|
|
434
|
+
if strict:
|
|
435
|
+
raise self._error(f"Builtin '{node.id}' must be called, not used as a value.", node)
|
|
436
|
+
return UNKNOWN
|
|
437
|
+
if sym.kind == SymbolKind.FUNCTION:
|
|
438
|
+
if strict:
|
|
439
|
+
raise self._error("Function objects are not first-class values in V1.", node)
|
|
440
|
+
return UNKNOWN
|
|
441
|
+
typ = self.symbol_types.get(sym.symbol_id, UNKNOWN)
|
|
442
|
+
if strict and typ.kind == TypeKind.UNKNOWN:
|
|
443
|
+
raise self._error(f"Could not infer a type for '{sym.name}'.", node)
|
|
444
|
+
return typ
|
|
445
|
+
|
|
446
|
+
if isinstance(node, ast.List):
|
|
447
|
+
if not node.elts:
|
|
448
|
+
if strict:
|
|
449
|
+
raise self._error("Empty fixed lists are not supported in V1.", node)
|
|
450
|
+
return UNKNOWN
|
|
451
|
+
element = UNKNOWN
|
|
452
|
+
for item in node.elts:
|
|
453
|
+
item_type = self._expr_type(item, strict=strict, value_required=True)
|
|
454
|
+
if item_type.kind in {TypeKind.VOID, TypeKind.HARDWARE, TypeKind.FIXED_LIST, TypeKind.TIME_U32}:
|
|
455
|
+
if strict:
|
|
456
|
+
raise self._error("Fixed-list elements must be Bool, Int, Float, or String values.", item)
|
|
457
|
+
return UNKNOWN
|
|
458
|
+
element = self._join(element, item_type, item, strict)
|
|
459
|
+
return fixed_list_type(element, len(node.elts)) if element.kind != TypeKind.UNKNOWN else UNKNOWN
|
|
460
|
+
|
|
461
|
+
if isinstance(node, ast.Subscript):
|
|
462
|
+
container = self._expr_type(node.value, strict=strict, value_required=True)
|
|
463
|
+
index = self._expr_type(node.slice, strict=strict, value_required=True)
|
|
464
|
+
self._require_kind(index, {TypeKind.INT}, node.slice, "List index must be Int.", strict)
|
|
465
|
+
if container.kind == TypeKind.FIXED_LIST and container.element_type is not None:
|
|
466
|
+
self._validate_static_index(node.slice, container.length or 0, strict)
|
|
467
|
+
return container.element_type
|
|
468
|
+
if strict:
|
|
469
|
+
raise self._error("Indexing is supported only on fixed lists in V1.", node.value)
|
|
470
|
+
return UNKNOWN
|
|
471
|
+
|
|
472
|
+
if isinstance(node, ast.JoinedStr):
|
|
473
|
+
for part in node.values:
|
|
474
|
+
if isinstance(part, ast.FormattedValue):
|
|
475
|
+
inner = self._expr_type(part.value, strict=strict, value_required=True)
|
|
476
|
+
if strict and inner.kind not in SCALAR_KINDS:
|
|
477
|
+
raise self._error("f-string values must be supported scalar values.", part.value)
|
|
478
|
+
return STRING
|
|
479
|
+
|
|
480
|
+
if isinstance(node, ast.UnaryOp):
|
|
481
|
+
operand = self._expr_type(node.operand, strict=strict, value_required=True)
|
|
482
|
+
if isinstance(node.op, ast.Not):
|
|
483
|
+
self._require_bool(operand, node.operand, strict)
|
|
484
|
+
return BOOL
|
|
485
|
+
if isinstance(node.op, ast.USub):
|
|
486
|
+
self._require_kind(operand, NUMERIC_KINDS, node.operand, "Unary '-' requires Int or Float.", strict)
|
|
487
|
+
return operand
|
|
488
|
+
|
|
489
|
+
if isinstance(node, ast.BinOp):
|
|
490
|
+
left = self._expr_type(node.left, strict=strict, value_required=True)
|
|
491
|
+
right = self._expr_type(node.right, strict=strict, value_required=True)
|
|
492
|
+
return self._numeric_binop_type(left, right, node, division=isinstance(node.op, ast.Div), strict=strict)
|
|
493
|
+
|
|
494
|
+
if isinstance(node, ast.BoolOp):
|
|
495
|
+
for value in node.values:
|
|
496
|
+
self._require_bool(self._expr_type(value, strict=strict, value_required=True), value, strict)
|
|
497
|
+
return BOOL
|
|
498
|
+
|
|
499
|
+
if isinstance(node, ast.Compare):
|
|
500
|
+
values = [node.left, *node.comparators]
|
|
501
|
+
types = [self._expr_type(x, strict=strict, value_required=True) for x in values]
|
|
502
|
+
if strict and all(t.kind != TypeKind.UNKNOWN for t in types):
|
|
503
|
+
for op, a, b in zip(node.ops, types, types[1:]):
|
|
504
|
+
if not self._comparison_compatible(a, b):
|
|
505
|
+
raise self._error(f"Cannot compare {a.display_name} with {b.display_name}.", node)
|
|
506
|
+
# V1 intentionally supports String/Bool equality only; do
|
|
507
|
+
# not silently inherit C++ pointer ordering or bool-as-int.
|
|
508
|
+
if (a.kind == TypeKind.STRING or b.kind == TypeKind.STRING) and not isinstance(op, (ast.Eq, ast.NotEq)):
|
|
509
|
+
raise self._error("String ordering comparisons are not supported in Barmaja PyArduino V1.", node)
|
|
510
|
+
if a.kind == TypeKind.BOOL and b.kind == TypeKind.BOOL and not isinstance(op, (ast.Eq, ast.NotEq)):
|
|
511
|
+
raise self._error("Bool ordering comparisons are not supported in Barmaja PyArduino V1.", node)
|
|
512
|
+
return BOOL
|
|
513
|
+
|
|
514
|
+
if isinstance(node, ast.IfExp):
|
|
515
|
+
self._require_bool(self._expr_type(node.test, strict=strict, value_required=True), node.test, strict)
|
|
516
|
+
a = self._expr_type(node.body, strict=strict, value_required=True)
|
|
517
|
+
b = self._expr_type(node.orelse, strict=strict, value_required=True)
|
|
518
|
+
return self._join(a, b, node, strict)
|
|
519
|
+
|
|
520
|
+
if isinstance(node, ast.Call):
|
|
521
|
+
return self._call_type(node, strict=strict)
|
|
522
|
+
|
|
523
|
+
if strict:
|
|
524
|
+
raise self._error(f"Expression '{type(node).__name__}' has no V1 type rule.", node)
|
|
525
|
+
return UNKNOWN
|
|
526
|
+
|
|
527
|
+
def _call_type(self, node: ast.Call, *, strict: bool) -> BarmajaType:
|
|
528
|
+
if isinstance(node.func, ast.Name):
|
|
529
|
+
name = node.func.id
|
|
530
|
+
args = [self._expr_type(arg, strict=strict, value_required=True) for arg in node.args]
|
|
531
|
+
|
|
532
|
+
if name in self._function_nodes:
|
|
533
|
+
params = self._param_symbols[name]
|
|
534
|
+
for param, arg_type, arg_node in zip(params, args, node.args):
|
|
535
|
+
self._merge_symbol(param, arg_type, arg_node, strict)
|
|
536
|
+
return self._return_types[name]
|
|
537
|
+
|
|
538
|
+
if name in HARDWARE_CLASSES:
|
|
539
|
+
return self._registry_constructor_type(name, args, node.args, strict)
|
|
540
|
+
if name == "print":
|
|
541
|
+
for typ, arg in zip(args, node.args):
|
|
542
|
+
if strict and typ.kind not in SCALAR_KINDS:
|
|
543
|
+
raise self._error("print() accepts supported scalar values in V1.", arg)
|
|
544
|
+
return VOID
|
|
545
|
+
if name in CORE_FUNCTIONS:
|
|
546
|
+
return self._registry_function_type(name, args, node.args, strict)
|
|
547
|
+
if name in {"int", "float", "bool"}:
|
|
548
|
+
return self._conversion_type(name, args[0] if args else UNKNOWN, node, strict)
|
|
549
|
+
if name == "round":
|
|
550
|
+
if not args:
|
|
551
|
+
return UNKNOWN
|
|
552
|
+
self._require_kind(args[0], NUMERIC_KINDS, node.args[0], "round() requires Int or Float.", strict)
|
|
553
|
+
if len(args) == 2:
|
|
554
|
+
self._require_kind(args[1], {TypeKind.INT}, node.args[1], "round() ndigits must be Int.", strict)
|
|
555
|
+
if strict:
|
|
556
|
+
ndigits = self._literal_int(node.args[1])
|
|
557
|
+
if ndigits is None or not (-6 <= ndigits <= 6):
|
|
558
|
+
raise self._error("round() ndigits must be a compile-time Int from -6 to 6 in V1.", node.args[1])
|
|
559
|
+
return args[0] if args[0].kind != TypeKind.UNKNOWN else UNKNOWN
|
|
560
|
+
return INT if args[0].kind == TypeKind.FLOAT else args[0]
|
|
561
|
+
if name == "abs":
|
|
562
|
+
self._require_kind(args[0], NUMERIC_KINDS, node.args[0], "abs() requires Int or Float.", strict)
|
|
563
|
+
return args[0]
|
|
564
|
+
if name in {"min", "max"}:
|
|
565
|
+
current = UNKNOWN
|
|
566
|
+
for typ, arg in zip(args, node.args):
|
|
567
|
+
self._require_kind(typ, NUMERIC_KINDS, arg, f"{name}() requires numeric arguments.", strict)
|
|
568
|
+
current = self._join(current, typ, node, strict)
|
|
569
|
+
return current
|
|
570
|
+
if name == "len":
|
|
571
|
+
if not args:
|
|
572
|
+
return UNKNOWN
|
|
573
|
+
if strict and args[0].kind != TypeKind.FIXED_LIST:
|
|
574
|
+
raise self._error("len() requires a fixed list in V1.", node.args[0])
|
|
575
|
+
return INT
|
|
576
|
+
if name == "sum":
|
|
577
|
+
if not args:
|
|
578
|
+
return UNKNOWN
|
|
579
|
+
if strict and args[0].kind != TypeKind.FIXED_LIST:
|
|
580
|
+
raise self._error("sum() requires a fixed list in V1.", node.args[0])
|
|
581
|
+
if args[0].kind == TypeKind.FIXED_LIST and args[0].element_type is not None:
|
|
582
|
+
self._require_kind(args[0].element_type, NUMERIC_KINDS, node.args[0], "sum() requires an Int or Float fixed list.", strict)
|
|
583
|
+
return args[0].element_type
|
|
584
|
+
return UNKNOWN
|
|
585
|
+
if name == "enumerate":
|
|
586
|
+
if args and strict and args[0].kind != TypeKind.FIXED_LIST:
|
|
587
|
+
raise self._error("enumerate() requires a fixed list in V1.", node.args[0])
|
|
588
|
+
if len(args) == 2:
|
|
589
|
+
self._require_kind(args[1], {TypeKind.INT}, node.args[1], "enumerate() start must be Int.", strict)
|
|
590
|
+
return VOID
|
|
591
|
+
if name == "range":
|
|
592
|
+
for typ, arg in zip(args, node.args):
|
|
593
|
+
self._require_kind(typ, {TypeKind.INT}, arg, "range() values must be Int.", strict)
|
|
594
|
+
return VOID # only valid as for-iterator, never a V1 value
|
|
595
|
+
|
|
596
|
+
if isinstance(node.func, ast.Attribute) and isinstance(node.func.value, ast.Name):
|
|
597
|
+
owner_node = node.func.value
|
|
598
|
+
owner_sym = self.resolved.symbol_for_name(owner_node)
|
|
599
|
+
args = [self._expr_type(arg, strict=strict, value_required=True) for arg in node.args]
|
|
600
|
+
if owner_node.id in SINGLETONS and owner_sym.kind == SymbolKind.API:
|
|
601
|
+
return self._registry_singleton_method_type(owner_node.id, node.func.attr, args, node.args, strict)
|
|
602
|
+
|
|
603
|
+
owner_type = self.symbol_types.get(owner_sym.symbol_id, UNKNOWN)
|
|
604
|
+
if owner_type.kind != TypeKind.HARDWARE:
|
|
605
|
+
if strict:
|
|
606
|
+
raise self._error(f"'{owner_node.id}' is not a hardware object.", owner_node)
|
|
607
|
+
return UNKNOWN
|
|
608
|
+
return self._registry_hardware_method_type(owner_type.hardware_class or "", node.func.attr, args, node.args, strict)
|
|
609
|
+
|
|
610
|
+
if strict:
|
|
611
|
+
raise self._error("Unsupported call form for type analysis.", node)
|
|
612
|
+
return UNKNOWN
|
|
613
|
+
|
|
614
|
+
def _conversion_type(self, name: str, arg: BarmajaType, node: ast.AST, strict: bool) -> BarmajaType:
|
|
615
|
+
allowed = {TypeKind.BOOL, TypeKind.INT, TypeKind.FLOAT}
|
|
616
|
+
if name == "float":
|
|
617
|
+
# Explicit, non-narrowing conversion of millis() time, e.g. float(millis()) / 1000.
|
|
618
|
+
allowed = allowed | {TypeKind.TIME_U32}
|
|
619
|
+
message = "float() accepts Bool, Int, Float, or millis() time in V1." if name == "float" else f"{name}() accepts Bool, Int, or Float in V1."
|
|
620
|
+
self._require_kind(arg, allowed, node, message, strict)
|
|
621
|
+
return {"int": INT, "float": FLOAT, "bool": BOOL}[name]
|
|
622
|
+
|
|
623
|
+
def _registry_constructor_type(self, name: str, args: list[BarmajaType], nodes: list[ast.AST], strict: bool) -> BarmajaType:
|
|
624
|
+
spec = HARDWARE_CLASSES[name]
|
|
625
|
+
sig = self._validate_registry_call(f"{name}()", spec.constructors, args, nodes, strict)
|
|
626
|
+
return self._type_from_registry_token(sig.returns, name)
|
|
627
|
+
|
|
628
|
+
def _registry_function_type(self, name: str, args: list[BarmajaType], nodes: list[ast.AST], strict: bool) -> BarmajaType:
|
|
629
|
+
spec = CORE_FUNCTIONS[name]
|
|
630
|
+
sig = self._validate_registry_call(f"{name}()", spec.signatures, args, nodes, strict)
|
|
631
|
+
return self._type_from_registry_token(sig.returns)
|
|
632
|
+
|
|
633
|
+
def _registry_hardware_method_type(self, cls: str, method: str, args: list[BarmajaType], nodes: list[ast.AST], strict: bool) -> BarmajaType:
|
|
634
|
+
try:
|
|
635
|
+
spec = HARDWARE_CLASSES[cls].methods[method]
|
|
636
|
+
except KeyError:
|
|
637
|
+
if strict:
|
|
638
|
+
node = nodes[0] if nodes else self._function_nodes[next(iter(self._function_nodes))] if self._function_nodes else self.frontend.tree
|
|
639
|
+
raise self._error(f"{cls}.{method}() is not registered in Barmaja PyArduino V1.", node)
|
|
640
|
+
return UNKNOWN
|
|
641
|
+
sig = self._validate_registry_call(f"{cls}.{method}()", spec.signatures, args, nodes, strict)
|
|
642
|
+
return self._type_from_registry_token(sig.returns)
|
|
643
|
+
|
|
644
|
+
def _registry_singleton_method_type(self, owner: str, method: str, args: list[BarmajaType], nodes: list[ast.AST], strict: bool) -> BarmajaType:
|
|
645
|
+
try:
|
|
646
|
+
spec = SINGLETONS[owner].methods[method]
|
|
647
|
+
except KeyError:
|
|
648
|
+
if strict:
|
|
649
|
+
node = nodes[0] if nodes else self.frontend.tree
|
|
650
|
+
raise self._error(f"{owner}.{method}() is not registered in Barmaja PyArduino V1.", node)
|
|
651
|
+
return UNKNOWN
|
|
652
|
+
sig = self._validate_registry_call(f"{owner}.{method}()", spec.signatures, args, nodes, strict)
|
|
653
|
+
return self._type_from_registry_token(sig.returns)
|
|
654
|
+
|
|
655
|
+
def _validate_registry_call(
|
|
656
|
+
self,
|
|
657
|
+
label: str,
|
|
658
|
+
signatures: tuple[SignatureSpec, ...],
|
|
659
|
+
args: list[BarmajaType],
|
|
660
|
+
nodes: list[ast.AST],
|
|
661
|
+
strict: bool,
|
|
662
|
+
) -> SignatureSpec:
|
|
663
|
+
candidates = matching_signatures(signatures, len(args))
|
|
664
|
+
if not candidates:
|
|
665
|
+
# Frontend owns arity diagnostics; retain a defensive type-phase guard.
|
|
666
|
+
node = nodes[0] if nodes else self.frontend.tree
|
|
667
|
+
if strict:
|
|
668
|
+
allowed = ", ".join(str(len(sig.args)) for sig in signatures)
|
|
669
|
+
raise self._error(f"{label} accepts registered argument counts: {allowed}.", node)
|
|
670
|
+
return signatures[0]
|
|
671
|
+
if not strict:
|
|
672
|
+
return candidates[0]
|
|
673
|
+
for sig in candidates:
|
|
674
|
+
if all(self._registry_token_accepts(token, typ, node, strict=False) for token, typ, node in zip(sig.args, args, nodes)):
|
|
675
|
+
# Strict pass gives specialized constant-token diagnostics.
|
|
676
|
+
for token, typ, node in zip(sig.args, args, nodes):
|
|
677
|
+
self._registry_token_accepts(token, typ, node, strict=True)
|
|
678
|
+
return sig
|
|
679
|
+
expected = " or ".join("(" + ", ".join(sig.args) + ")" for sig in candidates)
|
|
680
|
+
actual = "(" + ", ".join(t.display_name for t in args) + ")"
|
|
681
|
+
node = nodes[0] if nodes else self.frontend.tree
|
|
682
|
+
raise self._error(f"{label} expects {expected}, not {actual}.", node)
|
|
683
|
+
|
|
684
|
+
def _registry_token_accepts(self, token: str, typ: BarmajaType, node: ast.AST, *, strict: bool) -> bool:
|
|
685
|
+
if typ.kind == TypeKind.UNKNOWN:
|
|
686
|
+
return not strict
|
|
687
|
+
allowed: set[TypeKind] | None = {
|
|
688
|
+
"Int": {TypeKind.INT},
|
|
689
|
+
"Bool": {TypeKind.BOOL},
|
|
690
|
+
"Float": {TypeKind.FLOAT},
|
|
691
|
+
"String": {TypeKind.STRING},
|
|
692
|
+
"IntOrBool": {TypeKind.INT, TypeKind.BOOL},
|
|
693
|
+
"Scalar": SCALAR_KINDS,
|
|
694
|
+
"NumericOrBool": {TypeKind.INT, TypeKind.FLOAT, TypeKind.BOOL},
|
|
695
|
+
"UltrasonicUnit": {TypeKind.INT},
|
|
696
|
+
"DHTType": {TypeKind.INT},
|
|
697
|
+
}.get(token)
|
|
698
|
+
if allowed is None:
|
|
699
|
+
return True
|
|
700
|
+
if typ.kind not in allowed:
|
|
701
|
+
return False
|
|
702
|
+
if token == "UltrasonicUnit":
|
|
703
|
+
ok = isinstance(node, ast.Name) and node.id in {"CM", "INC"}
|
|
704
|
+
if strict and not ok:
|
|
705
|
+
raise self._error("Ultrasonic.read() unit must be CM or INC.", node)
|
|
706
|
+
return ok
|
|
707
|
+
if token == "DHTType":
|
|
708
|
+
ok = isinstance(node, ast.Name) and node.id in {"DHT11", "DHT22"}
|
|
709
|
+
if strict and not ok:
|
|
710
|
+
raise self._error("DHT() sensor type must be DHT11 or DHT22.", node)
|
|
711
|
+
return ok
|
|
712
|
+
return True
|
|
713
|
+
|
|
714
|
+
@staticmethod
|
|
715
|
+
def _type_from_registry_token(token: str, hardware_class: str | None = None) -> BarmajaType:
|
|
716
|
+
mapping = {
|
|
717
|
+
"Void": VOID,
|
|
718
|
+
"Bool": BOOL,
|
|
719
|
+
"Int": INT,
|
|
720
|
+
"Float": FLOAT,
|
|
721
|
+
"String": STRING,
|
|
722
|
+
"TimeU32": TIME_U32,
|
|
723
|
+
}
|
|
724
|
+
if token.startswith("Hardware["):
|
|
725
|
+
name = token[len("Hardware["):-1]
|
|
726
|
+
return hardware_type(name)
|
|
727
|
+
return mapping.get(token, hardware_type(hardware_class) if hardware_class else UNKNOWN)
|
|
728
|
+
|
|
729
|
+
def _require_all_int(self, args: list[BarmajaType], nodes: list[ast.AST], label: str, strict: bool) -> None:
|
|
730
|
+
for typ, node in zip(args, nodes):
|
|
731
|
+
self._require_kind(typ, {TypeKind.INT}, node, f"{label} arguments must be Int.", strict)
|
|
732
|
+
|
|
733
|
+
def _numeric_binop_type(self, left: BarmajaType, right: BarmajaType, node: ast.AST, *, division: bool, strict: bool) -> BarmajaType:
|
|
734
|
+
# Arduino wrap-safe elapsed time: TimeU32 - TimeU32 remains TimeU32.
|
|
735
|
+
if isinstance(node, ast.BinOp) and isinstance(node.op, ast.Sub) and left.kind == TypeKind.TIME_U32 and right.kind == TypeKind.TIME_U32:
|
|
736
|
+
return TIME_U32
|
|
737
|
+
# True division never narrows: millis() / 1000 is a Float number of seconds.
|
|
738
|
+
if (
|
|
739
|
+
division and isinstance(node, ast.BinOp)
|
|
740
|
+
and TypeKind.TIME_U32 in {left.kind, right.kind}
|
|
741
|
+
and {left.kind, right.kind} <= {TypeKind.TIME_U32, TypeKind.INT, TypeKind.FLOAT}
|
|
742
|
+
):
|
|
743
|
+
return FLOAT
|
|
744
|
+
if left.kind == TypeKind.UNKNOWN or right.kind == TypeKind.UNKNOWN:
|
|
745
|
+
# If one side is a name and the other fixes the numeric family, use
|
|
746
|
+
# that as a constraint. A float counterpart widens the unknown.
|
|
747
|
+
known = right if left.kind == TypeKind.UNKNOWN else left
|
|
748
|
+
unknown_node = node.left if isinstance(node, ast.BinOp) and left.kind == TypeKind.UNKNOWN else (node.right if isinstance(node, ast.BinOp) else None)
|
|
749
|
+
if known.kind in NUMERIC_KINDS and isinstance(unknown_node, ast.Name):
|
|
750
|
+
sym = self.resolved.symbol_for_name(unknown_node)
|
|
751
|
+
self._merge_symbol(sym, known, unknown_node, strict=False)
|
|
752
|
+
left = self.symbol_types.get(sym.symbol_id, known) if unknown_node is getattr(node, "left", None) else left
|
|
753
|
+
right = self.symbol_types.get(sym.symbol_id, known) if unknown_node is getattr(node, "right", None) else right
|
|
754
|
+
if left.kind == TypeKind.UNKNOWN or right.kind == TypeKind.UNKNOWN:
|
|
755
|
+
return UNKNOWN
|
|
756
|
+
if left.kind not in NUMERIC_KINDS or right.kind not in NUMERIC_KINDS:
|
|
757
|
+
if strict:
|
|
758
|
+
raise self._error(f"Arithmetic requires Int/Float, not {left.display_name} and {right.display_name}.", node)
|
|
759
|
+
return UNKNOWN
|
|
760
|
+
if division:
|
|
761
|
+
return FLOAT
|
|
762
|
+
return FLOAT if TypeKind.FLOAT in {left.kind, right.kind} else INT
|
|
763
|
+
|
|
764
|
+
def _validate_static_index(self, node: ast.AST, length: int, strict: bool) -> None:
|
|
765
|
+
value = self._literal_int(node)
|
|
766
|
+
if value is None or not strict:
|
|
767
|
+
return
|
|
768
|
+
if value < 0:
|
|
769
|
+
raise self._error("Negative list indexing is not supported in Barmaja PyArduino V1.", node)
|
|
770
|
+
if value >= length:
|
|
771
|
+
raise self._error(f"List index {value} is out of range for fixed list of length {length}.", node)
|
|
772
|
+
|
|
773
|
+
@staticmethod
|
|
774
|
+
def _literal_int(node: ast.AST) -> int | None:
|
|
775
|
+
if isinstance(node, ast.Constant) and isinstance(node.value, int) and not isinstance(node.value, bool):
|
|
776
|
+
return int(node.value)
|
|
777
|
+
if isinstance(node, ast.UnaryOp) and isinstance(node.op, ast.USub):
|
|
778
|
+
value = TypeAnalyzer._literal_int(node.operand)
|
|
779
|
+
return -value if value is not None else None
|
|
780
|
+
return None
|
|
781
|
+
|
|
782
|
+
@staticmethod
|
|
783
|
+
def _comparison_compatible(a: BarmajaType, b: BarmajaType) -> bool:
|
|
784
|
+
if a.kind == b.kind:
|
|
785
|
+
return a.kind in SCALAR_KINDS
|
|
786
|
+
if a.kind in NUMERIC_KINDS and b.kind in NUMERIC_KINDS:
|
|
787
|
+
return True
|
|
788
|
+
if a.kind == TypeKind.TIME_U32 and b.kind == TypeKind.INT:
|
|
789
|
+
return True
|
|
790
|
+
if b.kind == TypeKind.TIME_U32 and a.kind == TypeKind.INT:
|
|
791
|
+
return True
|
|
792
|
+
return False
|
|
793
|
+
|
|
794
|
+
def _merge_symbol(self, symbol: Symbol, incoming: BarmajaType, node: ast.AST, strict: bool) -> None:
|
|
795
|
+
if incoming.kind in {TypeKind.UNKNOWN, TypeKind.VOID}:
|
|
796
|
+
if strict and incoming.kind == TypeKind.VOID:
|
|
797
|
+
raise self._error("Void cannot be stored in a variable.", node)
|
|
798
|
+
return
|
|
799
|
+
if incoming.kind == TypeKind.FIXED_LIST and symbol.kind == SymbolKind.PARAMETER:
|
|
800
|
+
# Python passes the list object itself (aliasing); a C++ array parameter
|
|
801
|
+
# is not expressible in the current backend, so reject it cleanly.
|
|
802
|
+
if strict:
|
|
803
|
+
raise self._error(
|
|
804
|
+
f"Lists cannot be passed to functions in Barmaja PyArduino V1 (parameter '{symbol.name}'). "
|
|
805
|
+
"Use the list's module-level name inside the function instead.",
|
|
806
|
+
node,
|
|
807
|
+
)
|
|
808
|
+
return
|
|
809
|
+
if incoming.kind == TypeKind.HARDWARE:
|
|
810
|
+
# Hardware objects are created exactly once by their top-level constructor
|
|
811
|
+
# assignment (seeded directly, not merged). Copying one into another
|
|
812
|
+
# variable/parameter would construct or copy a second C++ device object.
|
|
813
|
+
if strict:
|
|
814
|
+
raise self._error(
|
|
815
|
+
f"'{symbol.name}' cannot hold a {incoming.hardware_class} object. Hardware objects cannot be copied, "
|
|
816
|
+
"passed to functions, or assigned to other variables; use the original object name directly.",
|
|
817
|
+
node,
|
|
818
|
+
)
|
|
819
|
+
return
|
|
820
|
+
current = self.symbol_types.get(symbol.symbol_id, UNKNOWN)
|
|
821
|
+
if (
|
|
822
|
+
symbol.kind != SymbolKind.PARAMETER
|
|
823
|
+
and {current.kind, incoming.kind} == {TypeKind.INT, TypeKind.TIME_U32}
|
|
824
|
+
):
|
|
825
|
+
# Arduino timer idiom: `last = 0` ... `last = millis()`. The variable
|
|
826
|
+
# stores time; Int stores are checked non-negative by the backend.
|
|
827
|
+
joined = TIME_U32
|
|
828
|
+
else:
|
|
829
|
+
joined = self._join(current, incoming, node, strict)
|
|
830
|
+
if joined != current:
|
|
831
|
+
self.symbol_types[symbol.symbol_id] = joined
|
|
832
|
+
self._changed = True
|
|
833
|
+
|
|
834
|
+
def _merge_return(self, name: str, incoming: BarmajaType, node: ast.AST, strict: bool) -> None:
|
|
835
|
+
if incoming.kind == TypeKind.FIXED_LIST:
|
|
836
|
+
if strict:
|
|
837
|
+
raise self._error(
|
|
838
|
+
f"Function '{name}' cannot return a list in Barmaja PyArduino V1. "
|
|
839
|
+
"Change the module-level list inside the function (using its name) or return one element.",
|
|
840
|
+
node,
|
|
841
|
+
)
|
|
842
|
+
return
|
|
843
|
+
if incoming.kind == TypeKind.HARDWARE:
|
|
844
|
+
if strict:
|
|
845
|
+
raise self._error(
|
|
846
|
+
f"Function '{name}' cannot return a {incoming.hardware_class} object. Use the hardware object name directly.",
|
|
847
|
+
node,
|
|
848
|
+
)
|
|
849
|
+
return
|
|
850
|
+
current = self._return_types[name]
|
|
851
|
+
if current.kind == TypeKind.VOID and incoming.kind != TypeKind.VOID:
|
|
852
|
+
current = UNKNOWN
|
|
853
|
+
joined = self._join(current, incoming, node, strict)
|
|
854
|
+
if joined != self._return_types[name]:
|
|
855
|
+
self._return_types[name] = joined
|
|
856
|
+
self._changed = True
|
|
857
|
+
|
|
858
|
+
def _join(self, a: BarmajaType, b: BarmajaType, node: ast.AST, strict: bool) -> BarmajaType:
|
|
859
|
+
if a.kind == TypeKind.UNKNOWN:
|
|
860
|
+
return b
|
|
861
|
+
if b.kind == TypeKind.UNKNOWN:
|
|
862
|
+
return a
|
|
863
|
+
if a == b:
|
|
864
|
+
return a
|
|
865
|
+
if a.kind in NUMERIC_KINDS and b.kind in NUMERIC_KINDS:
|
|
866
|
+
return FLOAT
|
|
867
|
+
if strict:
|
|
868
|
+
raise self._error(f"Incompatible types: {a.display_name} and {b.display_name}.", node)
|
|
869
|
+
# Do not hide an incompatible constraint during inference. The strict
|
|
870
|
+
# pass will report it at the source location.
|
|
871
|
+
return a
|
|
872
|
+
|
|
873
|
+
def _require_bool(self, typ: BarmajaType, node: ast.AST, strict: bool) -> None:
|
|
874
|
+
self._require_kind(typ, {TypeKind.BOOL}, node, "Condition/logical value must be Bool in Barmaja PyArduino V1.", strict)
|
|
875
|
+
|
|
876
|
+
def _require_kind(self, typ: BarmajaType, kinds: set[TypeKind], node: ast.AST, message: str, strict: bool) -> None:
|
|
877
|
+
if typ.kind == TypeKind.UNKNOWN and not strict:
|
|
878
|
+
return
|
|
879
|
+
if typ.kind not in kinds:
|
|
880
|
+
raise self._error(message, node)
|
|
881
|
+
|
|
882
|
+
def _require_resolved_user_symbols(self) -> None:
|
|
883
|
+
for symbol_id, typ in self.symbol_types.items():
|
|
884
|
+
if typ.kind == TypeKind.UNKNOWN:
|
|
885
|
+
symbol = self._symbol_by_id(symbol_id)
|
|
886
|
+
raise TypeCheckError(
|
|
887
|
+
f"Could not infer a type for '{symbol.name}'.",
|
|
888
|
+
line=symbol.declaration_line,
|
|
889
|
+
)
|
|
890
|
+
for name, typ in self._return_types.items():
|
|
891
|
+
if typ.kind == TypeKind.UNKNOWN:
|
|
892
|
+
fn = self._function_nodes[name]
|
|
893
|
+
raise self._error(f"Could not infer the return type of function '{name}'.", fn)
|
|
894
|
+
|
|
895
|
+
def _symbol_by_id(self, symbol_id: SymbolId) -> Symbol:
|
|
896
|
+
for symbol in self.resolved.module_scope.symbols.values():
|
|
897
|
+
if symbol.symbol_id == symbol_id:
|
|
898
|
+
return symbol
|
|
899
|
+
for scope in self.resolved.function_scopes.values():
|
|
900
|
+
for symbol in scope.symbols.values():
|
|
901
|
+
if symbol.symbol_id == symbol_id:
|
|
902
|
+
return symbol
|
|
903
|
+
raise KeyError(symbol_id)
|
|
904
|
+
|
|
905
|
+
@staticmethod
|
|
906
|
+
def _error(message: str, node: ast.AST) -> TypeCheckError:
|
|
907
|
+
return TypeCheckError(
|
|
908
|
+
message,
|
|
909
|
+
line=getattr(node, "lineno", None),
|
|
910
|
+
column=(getattr(node, "col_offset", None) + 1) if getattr(node, "col_offset", None) is not None else None,
|
|
911
|
+
end_line=getattr(node, "end_lineno", None),
|
|
912
|
+
end_column=getattr(node, "end_col_offset", None),
|
|
913
|
+
)
|
|
914
|
+
|
|
915
|
+
|
|
916
|
+
def analyze_types(flow: FlowAnalyzedModule) -> TypedModule:
|
|
917
|
+
return TypeAnalyzer(flow).analyze()
|