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,1721 @@
|
|
|
1
|
+
from __future__ import annotations
|
|
2
|
+
|
|
3
|
+
import ast
|
|
4
|
+
from dataclasses import dataclass, field
|
|
5
|
+
from pathlib import Path
|
|
6
|
+
|
|
7
|
+
from .board import BoardProfile, UNO
|
|
8
|
+
from .errors import BarmajaError, CompileError, InternalCompilerError
|
|
9
|
+
from .frontend import FrontendModule, is_noop_stmt, validate_frontend
|
|
10
|
+
from .flow import FlowAnalyzedModule, StorageClass, analyze_flow
|
|
11
|
+
from .source import ParsedSource, load_source_file, parse_source_text
|
|
12
|
+
from .symbols import ResolvedModule, SymbolKind, resolve_symbols
|
|
13
|
+
from .typesys import TypedModule, TypeKind, analyze_types
|
|
14
|
+
from .numeric import NumericAnalysis, analyze_numeric_ranges
|
|
15
|
+
from .ir import IRModule
|
|
16
|
+
from .lowering import lower_to_ir
|
|
17
|
+
from .target import TargetPlan, TargetDiagnostic, analyze_target
|
|
18
|
+
from .backend import GeneratedSketch, generate_uno_cpp
|
|
19
|
+
from .api_registry import CORE_FUNCTIONS, HARDWARE_CLASSES as API_HARDWARE_CLASSES, HOST_COMMANDS, SINGLETONS
|
|
20
|
+
|
|
21
|
+
CORE_CALLS = frozenset(CORE_FUNCTIONS)
|
|
22
|
+
BUILTIN_CALLS = {"round", "min", "max", "abs", "int", "float", "bool", "len", "sum", "enumerate"}
|
|
23
|
+
HARDWARE_CLASSES = frozenset(API_HARDWARE_CLASSES)
|
|
24
|
+
OBJECT_METHODS = {name: frozenset(spec.methods) for name, spec in API_HARDWARE_CLASSES.items()}
|
|
25
|
+
SINGLETON_METHODS = {name: frozenset(spec.methods) for name, spec in SINGLETONS.items()}
|
|
26
|
+
CONSTANTS = {
|
|
27
|
+
"HIGH", "LOW", "INPUT", "OUTPUT", "INPUT_PULLUP",
|
|
28
|
+
"A0", "A1", "A2", "A3", "A4", "A5",
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
|
|
32
|
+
@dataclass
|
|
33
|
+
class Program:
|
|
34
|
+
globals: list[ast.Assign] = field(default_factory=list)
|
|
35
|
+
objects: list[ast.Assign] = field(default_factory=list)
|
|
36
|
+
functions: list[ast.FunctionDef] = field(default_factory=list)
|
|
37
|
+
setup: list[ast.stmt] = field(default_factory=list)
|
|
38
|
+
loop: list[ast.stmt] = field(default_factory=list)
|
|
39
|
+
imports: set[str] = field(default_factory=set)
|
|
40
|
+
uses_print: bool = False
|
|
41
|
+
used_singletons: set[str] = field(default_factory=set)
|
|
42
|
+
|
|
43
|
+
|
|
44
|
+
@dataclass
|
|
45
|
+
class CompileResult:
|
|
46
|
+
source_path: Path | None
|
|
47
|
+
cpp: str
|
|
48
|
+
program: Program
|
|
49
|
+
board: BoardProfile
|
|
50
|
+
parsed_source: ParsedSource | None = None
|
|
51
|
+
resolved_module: ResolvedModule | None = None
|
|
52
|
+
flow_analyzed_module: FlowAnalyzedModule | None = None
|
|
53
|
+
typed_module: TypedModule | None = None
|
|
54
|
+
numeric_analysis: NumericAnalysis | None = None
|
|
55
|
+
ir_module: IRModule | None = None
|
|
56
|
+
target_plan: TargetPlan | None = None
|
|
57
|
+
generated_sketch: GeneratedSketch | None = None
|
|
58
|
+
warnings: tuple[TargetDiagnostic, ...] = ()
|
|
59
|
+
|
|
60
|
+
|
|
61
|
+
@dataclass
|
|
62
|
+
class FunctionInfo:
|
|
63
|
+
return_type: str
|
|
64
|
+
param_types: tuple[str, ...]
|
|
65
|
+
|
|
66
|
+
|
|
67
|
+
@dataclass(frozen=True)
|
|
68
|
+
class _OutputPart:
|
|
69
|
+
literal: str | None = None
|
|
70
|
+
expression: str | None = None
|
|
71
|
+
type_kind: TypeKind | None = None
|
|
72
|
+
precision: int | None = None
|
|
73
|
+
|
|
74
|
+
|
|
75
|
+
def _is_host_command_stmt(stmt: ast.stmt) -> bool:
|
|
76
|
+
return (
|
|
77
|
+
isinstance(stmt, ast.Expr)
|
|
78
|
+
and isinstance(stmt.value, ast.Call)
|
|
79
|
+
and isinstance(stmt.value.func, ast.Name)
|
|
80
|
+
and stmt.value.func.id in HOST_COMMANDS
|
|
81
|
+
)
|
|
82
|
+
|
|
83
|
+
|
|
84
|
+
def _strip_host_commands(parsed: ParsedSource) -> ParsedSource:
|
|
85
|
+
"""Remove top-level host workflow lines such as `upload()` and `LiveRun()`.
|
|
86
|
+
|
|
87
|
+
They are PC-side workflow commands (PictoBlox/IDE "Run" button) and never
|
|
88
|
+
part of the Arduino program. Any other use is a clear student error.
|
|
89
|
+
"""
|
|
90
|
+
import dataclasses
|
|
91
|
+
body = [stmt for stmt in parsed.tree.body if not _is_host_command_stmt(stmt)]
|
|
92
|
+
for node in ast.walk(ast.Module(body=body, type_ignores=[])):
|
|
93
|
+
if isinstance(node, ast.Name) and node.id in HOST_COMMANDS:
|
|
94
|
+
raise CompileError(
|
|
95
|
+
f"{node.id}() must be written on its own line at the top level of the program (for example right after the import).",
|
|
96
|
+
getattr(node, "lineno", None),
|
|
97
|
+
(getattr(node, "col_offset", 0) or 0) + 1,
|
|
98
|
+
)
|
|
99
|
+
if len(body) == len(parsed.tree.body):
|
|
100
|
+
return parsed
|
|
101
|
+
tree = ast.Module(body=body, type_ignores=list(getattr(parsed.tree, "type_ignores", [])))
|
|
102
|
+
return dataclasses.replace(parsed, tree=tree)
|
|
103
|
+
|
|
104
|
+
|
|
105
|
+
class Transpiler:
|
|
106
|
+
def __init__(self, board: BoardProfile = UNO):
|
|
107
|
+
self.board = board
|
|
108
|
+
self._global_values: dict[str, object] = {}
|
|
109
|
+
self._functions: dict[str, FunctionInfo] = {}
|
|
110
|
+
self._objects: dict[str, str] = {}
|
|
111
|
+
self._resolved: ResolvedModule | None = None
|
|
112
|
+
self._flow: FlowAnalyzedModule | None = None
|
|
113
|
+
self._typed: TypedModule | None = None
|
|
114
|
+
self._numeric: NumericAnalysis | None = None
|
|
115
|
+
self._target_plan: TargetPlan | None = None
|
|
116
|
+
self._temp_counter = 0
|
|
117
|
+
|
|
118
|
+
def _reset_state(self) -> None:
|
|
119
|
+
# One Transpiler instance may compile multiple independent student files.
|
|
120
|
+
self._global_values = {}
|
|
121
|
+
self._functions = {}
|
|
122
|
+
self._objects = {}
|
|
123
|
+
self._resolved = None
|
|
124
|
+
self._flow = None
|
|
125
|
+
self._typed = None
|
|
126
|
+
self._numeric = None
|
|
127
|
+
self._target_plan = None
|
|
128
|
+
self._temp_counter = 0
|
|
129
|
+
|
|
130
|
+
def _compile_parsed(self, parsed: ParsedSource, source_path: Path | None = None) -> CompileResult:
|
|
131
|
+
self._reset_state()
|
|
132
|
+
try:
|
|
133
|
+
parsed = _strip_host_commands(parsed)
|
|
134
|
+
frontend = validate_frontend(parsed.tree)
|
|
135
|
+
resolved = resolve_symbols(frontend)
|
|
136
|
+
self._resolved = resolved
|
|
137
|
+
flow = analyze_flow(resolved)
|
|
138
|
+
self._flow = flow
|
|
139
|
+
# Program is retained only as a compatibility/introspection view. It is
|
|
140
|
+
# classified before type/IR phases so no production phase after typed
|
|
141
|
+
# lowering depends on raw Python AST semantics.
|
|
142
|
+
program = self._classify(frontend)
|
|
143
|
+
typed = analyze_types(flow)
|
|
144
|
+
self._typed = typed
|
|
145
|
+
numeric = analyze_numeric_ranges(typed)
|
|
146
|
+
self._numeric = numeric
|
|
147
|
+
ir_module = lower_to_ir(typed, numeric, parsed.filename)
|
|
148
|
+
target_plan = analyze_target(ir_module, numeric, self.board)
|
|
149
|
+
self._target_plan = target_plan
|
|
150
|
+
generated = generate_uno_cpp(ir_module, target_plan)
|
|
151
|
+
return CompileResult(
|
|
152
|
+
source_path=source_path,
|
|
153
|
+
cpp=generated.cpp,
|
|
154
|
+
program=program,
|
|
155
|
+
board=self.board,
|
|
156
|
+
parsed_source=parsed,
|
|
157
|
+
resolved_module=resolved,
|
|
158
|
+
flow_analyzed_module=flow,
|
|
159
|
+
typed_module=typed,
|
|
160
|
+
numeric_analysis=numeric,
|
|
161
|
+
ir_module=ir_module,
|
|
162
|
+
target_plan=target_plan,
|
|
163
|
+
generated_sketch=generated,
|
|
164
|
+
warnings=target_plan.warnings,
|
|
165
|
+
)
|
|
166
|
+
except RecursionError:
|
|
167
|
+
raise CompileError(
|
|
168
|
+
"This expression or statement is nested too deeply for Barmaja PyArduino. Split it into smaller steps.",
|
|
169
|
+
None,
|
|
170
|
+
) from None
|
|
171
|
+
except BarmajaError as exc:
|
|
172
|
+
# Attach the source identity without changing existing error wording.
|
|
173
|
+
if getattr(exc, "source", None) is None:
|
|
174
|
+
exc.source = parsed.filename
|
|
175
|
+
raise
|
|
176
|
+
except Exception as exc:
|
|
177
|
+
# Student code must never surface raw IndexError/KeyError/etc. The
|
|
178
|
+
# detailed root cause is retained for trainer/developer diagnostics.
|
|
179
|
+
raise InternalCompilerError(
|
|
180
|
+
"An internal compiler error occurred while processing this program.",
|
|
181
|
+
source=parsed.filename,
|
|
182
|
+
details=f"{type(exc).__name__}: {exc}",
|
|
183
|
+
) from None
|
|
184
|
+
|
|
185
|
+
def compile_text(self, source: str, source_path: Path | None = None) -> CompileResult:
|
|
186
|
+
parsed = parse_source_text(source, source_path)
|
|
187
|
+
return self._compile_parsed(parsed, source_path)
|
|
188
|
+
|
|
189
|
+
def compile_file(self, path: str | Path) -> CompileResult:
|
|
190
|
+
p = Path(path)
|
|
191
|
+
parsed = load_source_file(p)
|
|
192
|
+
return self._compile_parsed(parsed, p)
|
|
193
|
+
|
|
194
|
+
# ---------- classification / validation ----------
|
|
195
|
+
def _classify(self, frontend: FrontendModule) -> Program:
|
|
196
|
+
tree = frontend.tree
|
|
197
|
+
p = Program()
|
|
198
|
+
main_loop_seen = False
|
|
199
|
+
for node in tree.body:
|
|
200
|
+
if isinstance(node, ast.ImportFrom):
|
|
201
|
+
if node.module != "Barmaja_PyArduino":
|
|
202
|
+
raise CompileError(
|
|
203
|
+
"Arduino projects may only import supported Barmaja PyArduino modules.",
|
|
204
|
+
node.lineno, node.col_offset + 1,
|
|
205
|
+
)
|
|
206
|
+
p.imports.update(alias.name for alias in node.names)
|
|
207
|
+
continue
|
|
208
|
+
if isinstance(node, ast.Import):
|
|
209
|
+
raise CompileError("Use 'from Barmaja_PyArduino import ...' in Arduino projects.", node.lineno)
|
|
210
|
+
if is_noop_stmt(node, docstring_allowed=True) and not main_loop_seen:
|
|
211
|
+
continue
|
|
212
|
+
if isinstance(node, ast.FunctionDef) and not main_loop_seen:
|
|
213
|
+
p.functions.append(node)
|
|
214
|
+
continue
|
|
215
|
+
if isinstance(node, ast.Assign) and not main_loop_seen and self._is_hardware_object_assignment(node):
|
|
216
|
+
p.objects.append(node)
|
|
217
|
+
continue
|
|
218
|
+
if isinstance(node, ast.Assign) and not main_loop_seen and self._is_compile_time_assignment(node):
|
|
219
|
+
# Step 6: a literal assignment is file-scope only when flow
|
|
220
|
+
# analysis proves constant/persistent module storage. Mutable
|
|
221
|
+
# setup-only values remain setup locals.
|
|
222
|
+
storages = []
|
|
223
|
+
if self._flow is not None:
|
|
224
|
+
targets = [node.targets[0]]
|
|
225
|
+
if isinstance(node.targets[0], (ast.Tuple, ast.List)):
|
|
226
|
+
targets = list(node.targets[0].elts)
|
|
227
|
+
for target in targets:
|
|
228
|
+
if isinstance(target, ast.Name):
|
|
229
|
+
sym = self._resolved.symbol_for_name(target) if self._resolved is not None else None
|
|
230
|
+
if sym is not None:
|
|
231
|
+
storages.append(self._flow.storage_for(sym))
|
|
232
|
+
if storages and all(x in {StorageClass.MODULE_CONSTANT, StorageClass.PERSISTENT_MODULE} for x in storages):
|
|
233
|
+
p.globals.append(node)
|
|
234
|
+
else:
|
|
235
|
+
p.setup.append(node)
|
|
236
|
+
continue
|
|
237
|
+
if isinstance(node, ast.While) and self._is_true(node.test):
|
|
238
|
+
if main_loop_seen:
|
|
239
|
+
raise CompileError("Only one top-level 'while True:' main loop is allowed.", node.lineno)
|
|
240
|
+
if node.orelse:
|
|
241
|
+
raise CompileError("The main 'while True:' loop cannot have an else block.", node.lineno)
|
|
242
|
+
main_loop_seen = True
|
|
243
|
+
p.loop.extend(node.body)
|
|
244
|
+
continue
|
|
245
|
+
if main_loop_seen:
|
|
246
|
+
raise CompileError("Nothing may follow the main top-level 'while True:' loop.", getattr(node, "lineno", None))
|
|
247
|
+
p.setup.append(node)
|
|
248
|
+
return p
|
|
249
|
+
|
|
250
|
+
@staticmethod
|
|
251
|
+
def _is_true(node: ast.AST) -> bool:
|
|
252
|
+
return isinstance(node, ast.Constant) and node.value is True
|
|
253
|
+
|
|
254
|
+
|
|
255
|
+
@staticmethod
|
|
256
|
+
def _is_hardware_object_assignment(node: ast.Assign) -> bool:
|
|
257
|
+
return (
|
|
258
|
+
len(node.targets) == 1
|
|
259
|
+
and isinstance(node.targets[0], ast.Name)
|
|
260
|
+
and isinstance(node.value, ast.Call)
|
|
261
|
+
and isinstance(node.value.func, ast.Name)
|
|
262
|
+
and node.value.func.id in HARDWARE_CLASSES
|
|
263
|
+
)
|
|
264
|
+
|
|
265
|
+
@staticmethod
|
|
266
|
+
def _is_compile_time_assignment(node: ast.Assign) -> bool:
|
|
267
|
+
if len(node.targets) != 1:
|
|
268
|
+
return False
|
|
269
|
+
target = node.targets[0]
|
|
270
|
+
if isinstance(target, ast.Name):
|
|
271
|
+
if isinstance(node.value, (ast.Constant, ast.UnaryOp)):
|
|
272
|
+
return True
|
|
273
|
+
if isinstance(node.value, ast.List) and node.value.elts:
|
|
274
|
+
return all(isinstance(v, (ast.Constant, ast.UnaryOp)) for v in node.value.elts)
|
|
275
|
+
return False
|
|
276
|
+
if isinstance(target, (ast.Tuple, ast.List)) and isinstance(node.value, (ast.Tuple, ast.List)):
|
|
277
|
+
return len(target.elts) == len(node.value.elts) and all(isinstance(t, ast.Name) for t in target.elts) and all(
|
|
278
|
+
isinstance(v, (ast.Constant, ast.UnaryOp)) for v in node.value.elts
|
|
279
|
+
)
|
|
280
|
+
return False
|
|
281
|
+
|
|
282
|
+
# LEGACY REFERENCE BACKEND (Steps 1-14)
|
|
283
|
+
# ---------------------------------------
|
|
284
|
+
# Step 15 retired the methods below from the production compile pipeline.
|
|
285
|
+
# They are temporarily retained as a regression/reference implementation
|
|
286
|
+
# while Step 16+ stabilizes packaging/toolchain work. Production Phase H
|
|
287
|
+
# uses backend.uno_cpp.generate_uno_cpp(IRModule, TargetPlan) exclusively.
|
|
288
|
+
def _analyze(self, p: Program) -> None:
|
|
289
|
+
for node in p.globals:
|
|
290
|
+
self._infer_global(node)
|
|
291
|
+
for node in p.objects:
|
|
292
|
+
name = node.targets[0].id
|
|
293
|
+
class_name = node.value.func.id
|
|
294
|
+
self._objects[name] = class_name
|
|
295
|
+
for arg in node.value.args:
|
|
296
|
+
self._validate_expr(arg, p)
|
|
297
|
+
if self._typed is None:
|
|
298
|
+
raise InternalCompilerError("Typed analysis is unavailable during compiler validation.")
|
|
299
|
+
for fn in p.functions:
|
|
300
|
+
signature = self._typed.signature_for(fn.name)
|
|
301
|
+
self._functions[fn.name] = FunctionInfo(
|
|
302
|
+
signature.return_type.cpp_type,
|
|
303
|
+
tuple(t.cpp_type for t in signature.parameter_types),
|
|
304
|
+
)
|
|
305
|
+
for fn in p.functions:
|
|
306
|
+
self._validate_function(fn, p)
|
|
307
|
+
for stmt in [*p.setup, *p.loop]:
|
|
308
|
+
self._validate_stmt(stmt, p)
|
|
309
|
+
|
|
310
|
+
def _infer_global(self, node: ast.Assign) -> None:
|
|
311
|
+
target = node.targets[0]
|
|
312
|
+
pairs = [(target, node.value)] if isinstance(target, ast.Name) else list(zip(target.elts, node.value.elts))
|
|
313
|
+
for name_node, value_node in pairs:
|
|
314
|
+
self._global_values[name_node.id] = self._literal_python_value(value_node)
|
|
315
|
+
|
|
316
|
+
def _validate_function(self, fn: ast.FunctionDef, p: Program) -> None:
|
|
317
|
+
if fn.decorator_list:
|
|
318
|
+
raise CompileError("Decorators are not supported in Barmaja PyArduino V1.", fn.lineno)
|
|
319
|
+
if fn.args.vararg or fn.args.kwarg or fn.args.kwonlyargs:
|
|
320
|
+
raise CompileError("Use simple positional function parameters in Barmaja PyArduino V1.", fn.lineno)
|
|
321
|
+
for stmt in fn.body:
|
|
322
|
+
self._validate_stmt(stmt, p, in_function=True)
|
|
323
|
+
|
|
324
|
+
def _validate_stmt(self, node: ast.stmt, p: Program, in_function: bool = False) -> None:
|
|
325
|
+
if is_noop_stmt(node, docstring_allowed=True):
|
|
326
|
+
return
|
|
327
|
+
if isinstance(node, ast.Expr):
|
|
328
|
+
if not isinstance(node.value, ast.Call):
|
|
329
|
+
raise CompileError("Only supported function calls may be standalone statements here.", node.lineno)
|
|
330
|
+
self._validate_call(node.value, p)
|
|
331
|
+
return
|
|
332
|
+
if isinstance(node, ast.Assign):
|
|
333
|
+
if len(node.targets) != 1:
|
|
334
|
+
raise CompileError("Chained assignment is not supported in V1.", node.lineno)
|
|
335
|
+
target = node.targets[0]
|
|
336
|
+
if not isinstance(target, (ast.Name, ast.Subscript)):
|
|
337
|
+
raise CompileError("Use a variable name or fixed-list item assignment.", node.lineno)
|
|
338
|
+
if isinstance(target, ast.Subscript):
|
|
339
|
+
self._validate_expr(target, p)
|
|
340
|
+
self._validate_expr(node.value, p)
|
|
341
|
+
return
|
|
342
|
+
if isinstance(node, ast.AugAssign):
|
|
343
|
+
if not isinstance(node.target, ast.Name) or not isinstance(node.op, (ast.Add, ast.Sub, ast.Mult, ast.Div, ast.Mod)):
|
|
344
|
+
raise CompileError("This augmented assignment is not supported yet.", node.lineno)
|
|
345
|
+
self._validate_expr(node.value, p)
|
|
346
|
+
return
|
|
347
|
+
if isinstance(node, ast.If):
|
|
348
|
+
self._validate_expr(node.test, p)
|
|
349
|
+
for s in node.body + node.orelse:
|
|
350
|
+
self._validate_stmt(s, p, in_function)
|
|
351
|
+
return
|
|
352
|
+
if isinstance(node, ast.While):
|
|
353
|
+
self._validate_expr(node.test, p)
|
|
354
|
+
for s in node.body:
|
|
355
|
+
self._validate_stmt(s, p, in_function)
|
|
356
|
+
return
|
|
357
|
+
if isinstance(node, ast.For):
|
|
358
|
+
if isinstance(node.iter, ast.Call) and isinstance(node.iter.func, ast.Name) and node.iter.func.id == "range":
|
|
359
|
+
if not isinstance(node.target, ast.Name):
|
|
360
|
+
raise CompileError("range() loop target must be a simple name.", node.lineno)
|
|
361
|
+
self._parse_range(node.iter, node, p)
|
|
362
|
+
elif isinstance(node.iter, ast.Call) and isinstance(node.iter.func, ast.Name) and node.iter.func.id == "enumerate":
|
|
363
|
+
self._validate_call(node.iter, p)
|
|
364
|
+
else:
|
|
365
|
+
self._validate_expr(node.iter, p)
|
|
366
|
+
for s in node.body:
|
|
367
|
+
self._validate_stmt(s, p, in_function)
|
|
368
|
+
if node.orelse:
|
|
369
|
+
raise CompileError("for/else is not supported in Barmaja PyArduino V1.", node.lineno)
|
|
370
|
+
return
|
|
371
|
+
if isinstance(node, ast.Return) and in_function:
|
|
372
|
+
if node.value is not None:
|
|
373
|
+
self._validate_expr(node.value, p)
|
|
374
|
+
return
|
|
375
|
+
if isinstance(node, ast.Global) and in_function:
|
|
376
|
+
# Phase C has already validated and resolved restricted global names.
|
|
377
|
+
return
|
|
378
|
+
if isinstance(node, (ast.Break, ast.Continue)):
|
|
379
|
+
# Phase D has already resolved/validated the loop target.
|
|
380
|
+
return
|
|
381
|
+
raise CompileError(f"'{type(node).__name__}' is not supported yet.", getattr(node, "lineno", None))
|
|
382
|
+
|
|
383
|
+
def _validate_expr(self, node: ast.AST, p: Program) -> None:
|
|
384
|
+
if isinstance(node, (ast.Constant, ast.Name)):
|
|
385
|
+
return
|
|
386
|
+
if isinstance(node, ast.JoinedStr):
|
|
387
|
+
for v in node.values:
|
|
388
|
+
if isinstance(v, ast.FormattedValue):
|
|
389
|
+
self._validate_expr(v.value, p)
|
|
390
|
+
return
|
|
391
|
+
if isinstance(node, ast.Call):
|
|
392
|
+
self._validate_call(node, p)
|
|
393
|
+
return
|
|
394
|
+
if isinstance(node, ast.UnaryOp) and isinstance(node.op, (ast.USub, ast.Not)):
|
|
395
|
+
self._validate_expr(node.operand, p); return
|
|
396
|
+
if isinstance(node, ast.BinOp) and isinstance(node.op, (ast.Add, ast.Sub, ast.Mult, ast.Div, ast.Mod)):
|
|
397
|
+
self._validate_expr(node.left, p); self._validate_expr(node.right, p); return
|
|
398
|
+
if isinstance(node, ast.Compare):
|
|
399
|
+
self._validate_expr(node.left, p)
|
|
400
|
+
for c in node.comparators: self._validate_expr(c, p)
|
|
401
|
+
return
|
|
402
|
+
if isinstance(node, ast.BoolOp) and isinstance(node.op, (ast.And, ast.Or)):
|
|
403
|
+
for v in node.values: self._validate_expr(v, p)
|
|
404
|
+
return
|
|
405
|
+
if isinstance(node, ast.IfExp):
|
|
406
|
+
self._validate_expr(node.test, p); self._validate_expr(node.body, p); self._validate_expr(node.orelse, p); return
|
|
407
|
+
if isinstance(node, ast.List):
|
|
408
|
+
for item in node.elts: self._validate_expr(item, p)
|
|
409
|
+
return
|
|
410
|
+
if isinstance(node, ast.Subscript):
|
|
411
|
+
self._validate_expr(node.value, p); self._validate_expr(node.slice, p); return
|
|
412
|
+
raise CompileError(f"Expression '{type(node).__name__}' is not supported yet.", getattr(node, "lineno", None))
|
|
413
|
+
|
|
414
|
+
def _validate_call(self, node: ast.Call, p: Program) -> None:
|
|
415
|
+
if isinstance(node.func, ast.Attribute) and isinstance(node.func.value, ast.Name):
|
|
416
|
+
owner = node.func.value.id
|
|
417
|
+
method = node.func.attr
|
|
418
|
+
if owner in self._objects:
|
|
419
|
+
cls = self._objects[owner]
|
|
420
|
+
if method not in OBJECT_METHODS[cls]:
|
|
421
|
+
raise CompileError(f"{cls}.{method}() is not supported in Barmaja PyArduino V1.", node.lineno)
|
|
422
|
+
elif owner in SINGLETON_METHODS:
|
|
423
|
+
if method not in SINGLETON_METHODS[owner]:
|
|
424
|
+
raise CompileError(f"{owner}.{method}() is not supported in Barmaja PyArduino V1.", node.lineno)
|
|
425
|
+
p.used_singletons.add(owner)
|
|
426
|
+
else:
|
|
427
|
+
raise CompileError(f"Unknown Arduino object '{owner}'.", node.lineno)
|
|
428
|
+
for arg in node.args:
|
|
429
|
+
self._validate_expr(arg, p)
|
|
430
|
+
if node.keywords:
|
|
431
|
+
raise CompileError("Keyword arguments are not supported for Arduino library methods.", node.lineno)
|
|
432
|
+
return
|
|
433
|
+
if not isinstance(node.func, ast.Name):
|
|
434
|
+
raise CompileError("Unsupported call form.", node.lineno)
|
|
435
|
+
name = node.func.id
|
|
436
|
+
if name == "print":
|
|
437
|
+
p.uses_print = True
|
|
438
|
+
for arg in node.args: self._validate_expr(arg, p)
|
|
439
|
+
if node.keywords:
|
|
440
|
+
raise CompileError("Custom print() sep/end are not supported in V1.", node.lineno)
|
|
441
|
+
return
|
|
442
|
+
if name not in CORE_CALLS and name not in BUILTIN_CALLS and name not in self._functions:
|
|
443
|
+
raise CompileError(f"'{name}' is not a supported Barmaja PyArduino call yet.", node.lineno)
|
|
444
|
+
for arg in node.args: self._validate_expr(arg, p)
|
|
445
|
+
if node.keywords:
|
|
446
|
+
raise CompileError("Keyword arguments are not supported for this call.", node.lineno)
|
|
447
|
+
|
|
448
|
+
# ---------- resolved backend names ----------
|
|
449
|
+
def _resolved_name(self, node: ast.Name) -> str:
|
|
450
|
+
if self._resolved is None:
|
|
451
|
+
raise InternalCompilerError("Symbol resolution is unavailable during code generation.")
|
|
452
|
+
try:
|
|
453
|
+
return self._resolved.symbol_for_name(node).cpp_name
|
|
454
|
+
except KeyError:
|
|
455
|
+
raise InternalCompilerError(
|
|
456
|
+
"A source name reached code generation without a resolved binding.",
|
|
457
|
+
line=getattr(node, "lineno", None),
|
|
458
|
+
details=f"unresolved name: {node.id}",
|
|
459
|
+
) from None
|
|
460
|
+
|
|
461
|
+
def _resolved_function_name(self, fn: ast.FunctionDef) -> str:
|
|
462
|
+
if self._resolved is None:
|
|
463
|
+
raise InternalCompilerError("Symbol resolution is unavailable during code generation.")
|
|
464
|
+
try:
|
|
465
|
+
return self._resolved.symbol_for_function(fn).cpp_name
|
|
466
|
+
except KeyError:
|
|
467
|
+
raise InternalCompilerError(
|
|
468
|
+
"A function reached code generation without a resolved binding.",
|
|
469
|
+
line=getattr(fn, "lineno", None),
|
|
470
|
+
details=f"unresolved function: {fn.name}",
|
|
471
|
+
) from None
|
|
472
|
+
|
|
473
|
+
def _resolved_parameter_name(self, arg: ast.arg) -> str:
|
|
474
|
+
if self._resolved is None:
|
|
475
|
+
raise InternalCompilerError("Symbol resolution is unavailable during code generation.")
|
|
476
|
+
try:
|
|
477
|
+
return self._resolved.symbol_for_parameter(arg).cpp_name
|
|
478
|
+
except KeyError:
|
|
479
|
+
raise InternalCompilerError(
|
|
480
|
+
"A function parameter reached code generation without a resolved binding.",
|
|
481
|
+
line=getattr(arg, "lineno", None),
|
|
482
|
+
details=f"unresolved parameter: {arg.arg}",
|
|
483
|
+
) from None
|
|
484
|
+
|
|
485
|
+
# ---------- C++ generation ----------
|
|
486
|
+
def _generate(self, p: Program) -> str:
|
|
487
|
+
if self._resolved is None or self._flow is None:
|
|
488
|
+
raise InternalCompilerError("Flow/symbol analysis is unavailable during code generation.")
|
|
489
|
+
|
|
490
|
+
if self._target_plan is None:
|
|
491
|
+
raise InternalCompilerError("Target analysis is unavailable during code generation.")
|
|
492
|
+
includes = [f"#include <{header}>" for header in self._target_plan.includes]
|
|
493
|
+
lines = [
|
|
494
|
+
"// Generated by Barmaja PyArduino 0.6.0",
|
|
495
|
+
"// Edit the Python source, not this generated file.",
|
|
496
|
+
*includes, "",
|
|
497
|
+
]
|
|
498
|
+
helpers = self._numeric_helper_lines()
|
|
499
|
+
if helpers:
|
|
500
|
+
lines.extend(helpers)
|
|
501
|
+
lines.append("")
|
|
502
|
+
output_helpers = self._output_helper_lines()
|
|
503
|
+
if output_helpers:
|
|
504
|
+
lines.extend(output_helpers)
|
|
505
|
+
lines.append("")
|
|
506
|
+
|
|
507
|
+
initialized_global_ids = set()
|
|
508
|
+
for node in p.globals:
|
|
509
|
+
target = node.targets[0]
|
|
510
|
+
pairs = [(target, node.value)] if isinstance(target, ast.Name) else list(zip(target.elts, node.value.elts))
|
|
511
|
+
for t, v in pairs:
|
|
512
|
+
symbol = self._resolved.symbol_for_name(t)
|
|
513
|
+
initialized_global_ids.add(symbol.symbol_id)
|
|
514
|
+
typ_obj = self._typed.type_for_symbol(symbol) if self._typed is not None else None
|
|
515
|
+
if typ_obj is not None and typ_obj.kind == TypeKind.FIXED_LIST:
|
|
516
|
+
if not isinstance(v, ast.List):
|
|
517
|
+
raise InternalCompilerError("Fixed list reached global generation without a list initializer.")
|
|
518
|
+
const_prefix = "const " if self._flow.storage_for(symbol) == StorageClass.MODULE_CONSTANT else ""
|
|
519
|
+
elem = typ_obj.element_type.cpp_type if typ_obj.element_type is not None else "int32_t"
|
|
520
|
+
items = ", ".join(self._expr(x) for x in v.elts)
|
|
521
|
+
lines.append(f"{const_prefix}{elem} {symbol.cpp_name}[{typ_obj.length}] = {{{items}}};")
|
|
522
|
+
else:
|
|
523
|
+
typ = self._cpp_type_for_symbol(symbol)
|
|
524
|
+
if self._flow.storage_for(symbol) == StorageClass.MODULE_CONSTANT:
|
|
525
|
+
decl_type = "const char* const" if typ == "const char*" else f"const {typ}"
|
|
526
|
+
else:
|
|
527
|
+
decl_type = typ
|
|
528
|
+
lines.append(f"{decl_type} {symbol.cpp_name} = {self._expr(v)};")
|
|
529
|
+
|
|
530
|
+
# Persistent state whose initialization must execute in setup/loop is
|
|
531
|
+
# allocated exactly once at file scope, without moving the assignment.
|
|
532
|
+
persistent = [
|
|
533
|
+
fact.symbol for fact in self._flow.symbol_facts.values()
|
|
534
|
+
if fact.storage == StorageClass.PERSISTENT_MODULE
|
|
535
|
+
and fact.symbol.symbol_id not in initialized_global_ids
|
|
536
|
+
]
|
|
537
|
+
for symbol in sorted(persistent, key=lambda x: (x.declaration_line or 10**9, x.symbol_id.value)):
|
|
538
|
+
lines.append(self._declaration_line(symbol))
|
|
539
|
+
if p.globals or persistent:
|
|
540
|
+
lines.append("")
|
|
541
|
+
|
|
542
|
+
for node in p.objects:
|
|
543
|
+
target = node.targets[0]
|
|
544
|
+
name = self._resolved_name(target)
|
|
545
|
+
cls = node.value.func.id
|
|
546
|
+
args = ", ".join(self._expr(a) for a in node.value.args)
|
|
547
|
+
lines.append(f"{cls} {name}({args});" if args else f"{cls} {name};")
|
|
548
|
+
if p.objects:
|
|
549
|
+
lines.append("")
|
|
550
|
+
|
|
551
|
+
# Function prototypes make source-order irrelevant after Phase C/D.
|
|
552
|
+
for fn in p.functions:
|
|
553
|
+
info = self._functions[fn.name]
|
|
554
|
+
params = ", ".join(f"{typ} {self._resolved_parameter_name(a)}" for typ, a in zip(info.param_types, fn.args.args))
|
|
555
|
+
lines.append(f"{info.return_type} {self._resolved_function_name(fn)}({params});")
|
|
556
|
+
if p.functions:
|
|
557
|
+
lines.append("")
|
|
558
|
+
|
|
559
|
+
for fn in p.functions:
|
|
560
|
+
lines.extend(self._function(fn)); lines.append("")
|
|
561
|
+
|
|
562
|
+
lines.append("void setup() {")
|
|
563
|
+
setup_symbols = self._symbols_with_storage(StorageClass.SETUP_LOCAL)
|
|
564
|
+
setup_scope = self._module_predeclared_cpp_names(StorageClass.PERSISTENT_MODULE, StorageClass.MODULE_CONSTANT, StorageClass.HARDWARE_PERSISTENT)
|
|
565
|
+
for symbol in setup_symbols:
|
|
566
|
+
lines.append(" " + self._declaration_line(symbol))
|
|
567
|
+
setup_scope.add(symbol.cpp_name)
|
|
568
|
+
for hook in self._target_plan.setup_hooks:
|
|
569
|
+
lines.append(" " + hook)
|
|
570
|
+
for stmt in p.setup:
|
|
571
|
+
lines.extend(self._stmt(stmt, 1, setup_scope, p, loop_stack=()))
|
|
572
|
+
lines.append("}")
|
|
573
|
+
lines.append("")
|
|
574
|
+
|
|
575
|
+
lines.append("void loop() {")
|
|
576
|
+
loop_symbols = self._symbols_with_storage(StorageClass.LOOP_TEMPORARY)
|
|
577
|
+
loop_scope = self._module_predeclared_cpp_names(StorageClass.PERSISTENT_MODULE, StorageClass.MODULE_CONSTANT, StorageClass.HARDWARE_PERSISTENT)
|
|
578
|
+
for symbol in loop_symbols:
|
|
579
|
+
lines.append(" " + self._declaration_line(symbol))
|
|
580
|
+
loop_scope.add(symbol.cpp_name)
|
|
581
|
+
for stmt in p.loop:
|
|
582
|
+
lines.extend(self._stmt(stmt, 1, loop_scope, p, loop_stack=("main",)))
|
|
583
|
+
lines.append("}")
|
|
584
|
+
lines.append("")
|
|
585
|
+
return "\n".join(lines)
|
|
586
|
+
|
|
587
|
+
def _function(self, fn: ast.FunctionDef) -> list[str]:
|
|
588
|
+
if self._resolved is None or self._flow is None:
|
|
589
|
+
raise InternalCompilerError("Flow/symbol analysis is unavailable during function generation.")
|
|
590
|
+
info = self._functions[fn.name]
|
|
591
|
+
params = ", ".join(f"{typ} {self._resolved_parameter_name(a)}" for typ, a in zip(info.param_types, fn.args.args))
|
|
592
|
+
out = [f"{info.return_type} {self._resolved_function_name(fn)}({params}) {{"]
|
|
593
|
+
scope = {self._resolved_parameter_name(a) for a in fn.args.args}
|
|
594
|
+
fn_scope = self._resolved.function_scopes.get(fn.name)
|
|
595
|
+
if fn_scope is not None:
|
|
596
|
+
for global_name in fn_scope.global_names:
|
|
597
|
+
module_symbol = self._resolved.module_scope.symbols.get(global_name)
|
|
598
|
+
if module_symbol is not None:
|
|
599
|
+
scope.add(module_symbol.cpp_name)
|
|
600
|
+
locals_ = [
|
|
601
|
+
sym for sym in fn_scope.symbols.values()
|
|
602
|
+
if self._flow.storage_for(sym) == StorageClass.FUNCTION_LOCAL
|
|
603
|
+
]
|
|
604
|
+
for symbol in sorted(locals_, key=lambda x: (x.declaration_line or 10**9, x.symbol_id.value)):
|
|
605
|
+
out.append(" " + self._declaration_line(symbol))
|
|
606
|
+
scope.add(symbol.cpp_name)
|
|
607
|
+
dummy = Program() # statement generator only; analysis already completed
|
|
608
|
+
for stmt in fn.body:
|
|
609
|
+
out.extend(self._stmt(stmt, 1, scope, dummy, loop_stack=()))
|
|
610
|
+
out.append("}")
|
|
611
|
+
return out
|
|
612
|
+
|
|
613
|
+
def _stmt(
|
|
614
|
+
self,
|
|
615
|
+
node: ast.stmt,
|
|
616
|
+
indent: int,
|
|
617
|
+
scope: set[str],
|
|
618
|
+
p: Program,
|
|
619
|
+
*,
|
|
620
|
+
loop_stack: tuple[str, ...],
|
|
621
|
+
) -> list[str]:
|
|
622
|
+
pad = " " * indent
|
|
623
|
+
if is_noop_stmt(node, docstring_allowed=True):
|
|
624
|
+
return []
|
|
625
|
+
if isinstance(node, ast.Expr) and isinstance(node.value, ast.Call):
|
|
626
|
+
if isinstance(node.value.func, ast.Name) and node.value.func.id == "print":
|
|
627
|
+
return self._serial_print(node.value, indent)
|
|
628
|
+
if isinstance(node.value.func, ast.Attribute) and node.value.func.attr in {"print", "println"}:
|
|
629
|
+
owner_node = node.value.func.value
|
|
630
|
+
if isinstance(owner_node, ast.Name):
|
|
631
|
+
owner_source = owner_node.id
|
|
632
|
+
is_supported_sink = owner_source == "Serial" or self._objects.get(owner_source) == "LiquidCrystal_I2C"
|
|
633
|
+
if is_supported_sink:
|
|
634
|
+
special = self._explicit_output_stmt(node.value, indent)
|
|
635
|
+
if special is not None:
|
|
636
|
+
return special
|
|
637
|
+
return [pad + self._expr(node.value) + ";"]
|
|
638
|
+
if isinstance(node, ast.Assign):
|
|
639
|
+
target = node.targets[0]
|
|
640
|
+
if isinstance(target, ast.Subscript):
|
|
641
|
+
return [f"{pad}{self._subscript_lvalue(target)} = {self._expr(node.value)};"]
|
|
642
|
+
if not isinstance(target, ast.Name):
|
|
643
|
+
raise InternalCompilerError("Unsupported assignment target reached backend.", line=getattr(node, "lineno", None))
|
|
644
|
+
name = self._resolved_name(target)
|
|
645
|
+
symbol = self._resolved.symbol_for_name(target) if self._resolved is not None else None
|
|
646
|
+
typ_obj = self._typed.type_for_symbol(symbol) if (self._typed is not None and symbol is not None) else None
|
|
647
|
+
if typ_obj is not None and typ_obj.kind == TypeKind.FIXED_LIST:
|
|
648
|
+
if not isinstance(node.value, ast.List):
|
|
649
|
+
raise CompileError("Whole fixed-list reassignment is not supported in V1; update individual items instead.", node.lineno)
|
|
650
|
+
items = [self._expr(x) for x in node.value.elts]
|
|
651
|
+
if name in scope:
|
|
652
|
+
return [f"{pad}{name}[{i}] = {value};" for i, value in enumerate(items)]
|
|
653
|
+
scope.add(name)
|
|
654
|
+
elem = typ_obj.element_type.cpp_type if typ_obj.element_type is not None else "int32_t"
|
|
655
|
+
return [f"{pad}{elem} {name}[{typ_obj.length}] = {{{', '.join(items)}}};"]
|
|
656
|
+
expr = self._expr(node.value)
|
|
657
|
+
if name in scope:
|
|
658
|
+
return [f"{pad}{name} = {expr};"]
|
|
659
|
+
scope.add(name)
|
|
660
|
+
typ = self._infer_expr_type(node.value)
|
|
661
|
+
return [f"{pad}{typ} {name} = {expr};"]
|
|
662
|
+
if isinstance(node, ast.AugAssign):
|
|
663
|
+
target_name = self._resolved_name(node.target)
|
|
664
|
+
target_type = self._typed.type_for_expr(node.target) if self._typed is not None else None
|
|
665
|
+
rhs = self._expr(node.value)
|
|
666
|
+
if isinstance(node.op, ast.Div):
|
|
667
|
+
return [f"{pad}{target_name} = _bj_true_div_f32((float)({target_name}), (float)({rhs}));"]
|
|
668
|
+
if isinstance(node.op, ast.Mod):
|
|
669
|
+
if target_type is not None and target_type.kind == TypeKind.FLOAT:
|
|
670
|
+
return [f"{pad}{target_name} = _bj_mod_f32((float)({target_name}), (float)({rhs}));"]
|
|
671
|
+
return [f"{pad}{target_name} = _bj_mod_i32({target_name}, {rhs});"]
|
|
672
|
+
if target_type is not None and target_type.kind == TypeKind.INT and isinstance(node.op, (ast.Add, ast.Sub, ast.Mult)):
|
|
673
|
+
helper = {ast.Add: "_bj_i32_add", ast.Sub: "_bj_i32_sub", ast.Mult: "_bj_i32_mul"}[type(node.op)]
|
|
674
|
+
return [f"{pad}{target_name} = {helper}({target_name}, {rhs});"]
|
|
675
|
+
ops = {ast.Add: "+=", ast.Sub: "-=", ast.Mult: "*="}
|
|
676
|
+
return [f"{pad}{target_name} {ops[type(node.op)]} {rhs};"]
|
|
677
|
+
if isinstance(node, ast.Return):
|
|
678
|
+
return [pad + ("return;" if node.value is None else f"return {self._expr(node.value)};")]
|
|
679
|
+
if isinstance(node, ast.Global):
|
|
680
|
+
return []
|
|
681
|
+
if isinstance(node, ast.Break):
|
|
682
|
+
if not loop_stack:
|
|
683
|
+
raise InternalCompilerError("break reached the backend without a loop target.", line=getattr(node, "lineno", None))
|
|
684
|
+
if loop_stack[-1] == "main":
|
|
685
|
+
raise InternalCompilerError("A main-loop break passed flow validation.", line=getattr(node, "lineno", None))
|
|
686
|
+
return [pad + "break;"]
|
|
687
|
+
if isinstance(node, ast.Continue):
|
|
688
|
+
if not loop_stack:
|
|
689
|
+
raise InternalCompilerError("continue reached the backend without a loop target.", line=getattr(node, "lineno", None))
|
|
690
|
+
# The special Python main while is represented by repeated calls to
|
|
691
|
+
# Arduino loop(); finishing this call is Python's next iteration.
|
|
692
|
+
return [pad + ("return;" if loop_stack[-1] == "main" else "continue;")]
|
|
693
|
+
if isinstance(node, ast.If):
|
|
694
|
+
out = [f"{pad}if ({self._expr(node.test)}) {{"]
|
|
695
|
+
child = set(scope)
|
|
696
|
+
for s in node.body:
|
|
697
|
+
out.extend(self._stmt(s, indent + 1, child, p, loop_stack=loop_stack))
|
|
698
|
+
out.append(pad + "}")
|
|
699
|
+
if node.orelse:
|
|
700
|
+
out[-1] += " else {"
|
|
701
|
+
child = set(scope)
|
|
702
|
+
for s in node.orelse:
|
|
703
|
+
out.extend(self._stmt(s, indent + 1, child, p, loop_stack=loop_stack))
|
|
704
|
+
out.append(pad + "}")
|
|
705
|
+
return out
|
|
706
|
+
if isinstance(node, ast.While):
|
|
707
|
+
out = [f"{pad}while ({self._expr(node.test)}) {{"]
|
|
708
|
+
child = set(scope)
|
|
709
|
+
for s in node.body:
|
|
710
|
+
out.extend(self._stmt(s, indent + 1, child, p, loop_stack=(*loop_stack, "nested")))
|
|
711
|
+
out.append(pad + "}")
|
|
712
|
+
return out
|
|
713
|
+
if isinstance(node, ast.For):
|
|
714
|
+
if isinstance(node.iter, ast.Call) and isinstance(node.iter.func, ast.Name) and node.iter.func.id == "range":
|
|
715
|
+
var = self._resolved_name(node.target)
|
|
716
|
+
start, stop, step = self._parse_range(node.iter, node, p)
|
|
717
|
+
step_value = self._literal_int(step)
|
|
718
|
+
if step_value == 0:
|
|
719
|
+
raise CompileError("range() step cannot be zero.", node.lineno)
|
|
720
|
+
start_temp = self._new_temp("range_start")
|
|
721
|
+
stop_temp = self._new_temp("range_stop")
|
|
722
|
+
step_temp = self._new_temp("range_step")
|
|
723
|
+
iterator = self._new_temp("range_iter")
|
|
724
|
+
out = [
|
|
725
|
+
f"{pad}int32_t {start_temp} = {self._expr(start)};",
|
|
726
|
+
f"{pad}int32_t {stop_temp} = {self._expr(stop)};",
|
|
727
|
+
f"{pad}int32_t {step_temp} = {self._expr(step)};",
|
|
728
|
+
]
|
|
729
|
+
if step_value is None:
|
|
730
|
+
out.append(f"{pad}if ({step_temp} == 0) _bj_numeric_fail();")
|
|
731
|
+
cond = f"({step_temp} > 0 ? {iterator} < {stop_temp} : {iterator} > {stop_temp})"
|
|
732
|
+
else:
|
|
733
|
+
cond = f"{iterator} {'<' if step_value > 0 else '>'} {stop_temp}"
|
|
734
|
+
out.extend([
|
|
735
|
+
f"{pad}for (int32_t {iterator} = {start_temp}; {cond}; {iterator} = _bj_i32_add({iterator}, {step_temp})) {{",
|
|
736
|
+
f"{pad} {var} = {iterator};",
|
|
737
|
+
])
|
|
738
|
+
child = set(scope); child.add(var)
|
|
739
|
+
for stmt in node.body:
|
|
740
|
+
out.extend(self._stmt(stmt, indent + 1, child, p, loop_stack=(*loop_stack, "nested")))
|
|
741
|
+
out.append(pad + "}")
|
|
742
|
+
return out
|
|
743
|
+
|
|
744
|
+
enumerate_mode = isinstance(node.iter, ast.Call) and isinstance(node.iter.func, ast.Name) and node.iter.func.id == "enumerate"
|
|
745
|
+
list_node = node.iter.args[0] if enumerate_mode else node.iter
|
|
746
|
+
if not isinstance(list_node, ast.Name):
|
|
747
|
+
raise InternalCompilerError("Fixed-list loop reached backend without a list name.", line=node.lineno)
|
|
748
|
+
list_type = self._typed.type_for_expr(list_node) if self._typed is not None else None
|
|
749
|
+
if list_type is None or list_type.kind != TypeKind.FIXED_LIST or list_type.length is None:
|
|
750
|
+
raise InternalCompilerError("Fixed-list loop reached backend without a fixed-list type.", line=node.lineno)
|
|
751
|
+
list_name = self._resolved_name(list_node)
|
|
752
|
+
iterator = self._new_temp("list_iter")
|
|
753
|
+
out = [f"{pad}for (int32_t {iterator} = 0; {iterator} < {list_type.length}; ++{iterator}) {{"]
|
|
754
|
+
child = set(scope)
|
|
755
|
+
if enumerate_mode:
|
|
756
|
+
if not isinstance(node.target, (ast.Tuple, ast.List)) or len(node.target.elts) != 2:
|
|
757
|
+
raise InternalCompilerError("enumerate target shape changed after validation.", line=node.lineno)
|
|
758
|
+
idx_target, value_target = node.target.elts
|
|
759
|
+
idx_name = self._resolved_name(idx_target)
|
|
760
|
+
value_name = self._resolved_name(value_target)
|
|
761
|
+
if len(node.iter.args) == 2:
|
|
762
|
+
start_temp = self._new_temp("enumerate_start")
|
|
763
|
+
out.insert(0, f"{pad}int32_t {start_temp} = {self._expr(node.iter.args[1])};")
|
|
764
|
+
out.append(f"{pad} {idx_name} = _bj_i32_add({start_temp}, {iterator});")
|
|
765
|
+
else:
|
|
766
|
+
out.append(f"{pad} {idx_name} = {iterator};")
|
|
767
|
+
out.append(f"{pad} {value_name} = {list_name}[{iterator}];")
|
|
768
|
+
child.update({idx_name, value_name})
|
|
769
|
+
else:
|
|
770
|
+
if not isinstance(node.target, ast.Name):
|
|
771
|
+
raise InternalCompilerError("Fixed-list target shape changed after validation.", line=node.lineno)
|
|
772
|
+
value_name = self._resolved_name(node.target)
|
|
773
|
+
out.append(f"{pad} {value_name} = {list_name}[{iterator}];")
|
|
774
|
+
child.add(value_name)
|
|
775
|
+
for stmt in node.body:
|
|
776
|
+
out.extend(self._stmt(stmt, indent + 1, child, p, loop_stack=(*loop_stack, "nested")))
|
|
777
|
+
out.append(pad + "}")
|
|
778
|
+
return out
|
|
779
|
+
raise CompileError(f"Cannot generate statement {type(node).__name__}.", getattr(node, "lineno", None))
|
|
780
|
+
|
|
781
|
+
def _numeric_helper_lines(self) -> list[str]:
|
|
782
|
+
"""Emit checked numeric/Python-semantic helpers only when required."""
|
|
783
|
+
if self._numeric is None or self._typed is None:
|
|
784
|
+
return []
|
|
785
|
+
tree = self._resolved.frontend.tree if self._resolved is not None else None
|
|
786
|
+
need_add = need_sub = need_mul = need_neg = False
|
|
787
|
+
need_div = need_mod_i = need_mod_f = False
|
|
788
|
+
need_pwm = bool(self._numeric.checked_pwm_arguments)
|
|
789
|
+
need_time = bool(self._numeric.checked_time_thresholds)
|
|
790
|
+
need_float_to_int = False
|
|
791
|
+
need_round_i32 = need_round_f32_digits = need_round_i32_digits = False
|
|
792
|
+
need_abs_i = False
|
|
793
|
+
need_string_eq = False
|
|
794
|
+
need_list_index = False
|
|
795
|
+
need_runtime_fail = False
|
|
796
|
+
|
|
797
|
+
if tree is not None:
|
|
798
|
+
for node in ast.walk(tree):
|
|
799
|
+
if isinstance(node, ast.Subscript):
|
|
800
|
+
if self._literal_int(node.slice) is None:
|
|
801
|
+
need_list_index = True
|
|
802
|
+
if isinstance(node, ast.For):
|
|
803
|
+
if isinstance(node.iter, ast.Call) and isinstance(node.iter.func, ast.Name) and node.iter.func.id == "range":
|
|
804
|
+
need_add = True
|
|
805
|
+
if isinstance(node.iter, ast.Call) and len(node.iter.args) == 3 and self._literal_int(node.iter.args[2]) is None:
|
|
806
|
+
need_runtime_fail = True
|
|
807
|
+
elif isinstance(node, ast.AugAssign):
|
|
808
|
+
try:
|
|
809
|
+
target_type = self._typed.type_for_expr(node.target)
|
|
810
|
+
except KeyError:
|
|
811
|
+
target_type = None
|
|
812
|
+
if isinstance(node.op, ast.Div):
|
|
813
|
+
need_div = True
|
|
814
|
+
elif isinstance(node.op, ast.Mod):
|
|
815
|
+
if target_type is not None and target_type.kind == TypeKind.FLOAT:
|
|
816
|
+
need_mod_f = True
|
|
817
|
+
else:
|
|
818
|
+
need_mod_i = True
|
|
819
|
+
elif target_type is not None and target_type.kind == TypeKind.INT:
|
|
820
|
+
need_add |= isinstance(node.op, ast.Add)
|
|
821
|
+
need_sub |= isinstance(node.op, ast.Sub)
|
|
822
|
+
need_mul |= isinstance(node.op, ast.Mult)
|
|
823
|
+
elif isinstance(node, ast.BinOp):
|
|
824
|
+
if isinstance(node.op, ast.Div):
|
|
825
|
+
need_div = True
|
|
826
|
+
elif isinstance(node.op, ast.Mod):
|
|
827
|
+
try:
|
|
828
|
+
typ = self._typed.type_for_expr(node)
|
|
829
|
+
except KeyError:
|
|
830
|
+
typ = None
|
|
831
|
+
need_mod_f |= typ is not None and typ.kind == TypeKind.FLOAT
|
|
832
|
+
need_mod_i |= typ is None or typ.kind == TypeKind.INT
|
|
833
|
+
elif self._numeric.int_expr_needs_runtime_check(node):
|
|
834
|
+
need_add |= isinstance(node.op, ast.Add)
|
|
835
|
+
need_sub |= isinstance(node.op, ast.Sub)
|
|
836
|
+
need_mul |= isinstance(node.op, ast.Mult)
|
|
837
|
+
elif isinstance(node, ast.UnaryOp) and isinstance(node.op, ast.USub) and self._numeric.int_expr_needs_runtime_check(node):
|
|
838
|
+
need_neg = True
|
|
839
|
+
elif isinstance(node, ast.Compare):
|
|
840
|
+
values = [node.left, *node.comparators]
|
|
841
|
+
for a, b in zip(values, values[1:]):
|
|
842
|
+
try:
|
|
843
|
+
need_string_eq |= self._typed.type_for_expr(a).kind == TypeKind.STRING and self._typed.type_for_expr(b).kind == TypeKind.STRING
|
|
844
|
+
except KeyError:
|
|
845
|
+
pass
|
|
846
|
+
elif isinstance(node, ast.Call) and isinstance(node.func, ast.Name):
|
|
847
|
+
if node.func.id == "sum" and node.args:
|
|
848
|
+
try:
|
|
849
|
+
list_type = self._typed.type_for_expr(node.args[0])
|
|
850
|
+
need_add |= list_type.kind == TypeKind.FIXED_LIST and list_type.element_type is not None and list_type.element_type.kind == TypeKind.INT
|
|
851
|
+
except KeyError:
|
|
852
|
+
pass
|
|
853
|
+
if node.func.id == "enumerate" and len(node.args) == 2:
|
|
854
|
+
need_add = True
|
|
855
|
+
if node.func.id == "int" and node.args:
|
|
856
|
+
try:
|
|
857
|
+
need_float_to_int |= self._typed.type_for_expr(node.args[0]).kind == TypeKind.FLOAT
|
|
858
|
+
except KeyError:
|
|
859
|
+
pass
|
|
860
|
+
elif node.func.id == "round" and node.args:
|
|
861
|
+
try:
|
|
862
|
+
arg_kind = self._typed.type_for_expr(node.args[0]).kind
|
|
863
|
+
except KeyError:
|
|
864
|
+
arg_kind = None
|
|
865
|
+
if len(node.args) == 1 and arg_kind == TypeKind.FLOAT:
|
|
866
|
+
need_round_i32 = True
|
|
867
|
+
elif len(node.args) == 2 and arg_kind == TypeKind.FLOAT:
|
|
868
|
+
need_round_f32_digits = True
|
|
869
|
+
elif len(node.args) == 2 and arg_kind == TypeKind.INT:
|
|
870
|
+
need_round_i32_digits = True
|
|
871
|
+
elif node.func.id == "abs" and node.args:
|
|
872
|
+
try:
|
|
873
|
+
need_abs_i |= self._typed.type_for_expr(node.args[0]).kind == TypeKind.INT
|
|
874
|
+
except KeyError:
|
|
875
|
+
pass
|
|
876
|
+
|
|
877
|
+
need_runtime_fail |= any((need_add, need_sub, need_mul, need_neg, need_div, need_mod_i, need_mod_f,
|
|
878
|
+
need_pwm, need_time, need_float_to_int, need_round_i32,
|
|
879
|
+
need_round_f32_digits, need_round_i32_digits, need_abs_i, need_list_index))
|
|
880
|
+
if not any((need_runtime_fail, need_string_eq)):
|
|
881
|
+
return []
|
|
882
|
+
|
|
883
|
+
out: list[str] = []
|
|
884
|
+
if need_runtime_fail:
|
|
885
|
+
out += [
|
|
886
|
+
"static void __attribute__((noinline, noreturn)) _bj_numeric_fail() {",
|
|
887
|
+
" for (;;) { __asm__ __volatile__(\"\"); }",
|
|
888
|
+
"}",
|
|
889
|
+
]
|
|
890
|
+
if need_list_index:
|
|
891
|
+
out += [
|
|
892
|
+
"static int32_t _bj_list_index(int32_t index, int32_t length) {",
|
|
893
|
+
" if (index < 0 || index >= length) _bj_numeric_fail();",
|
|
894
|
+
" return index;",
|
|
895
|
+
"}",
|
|
896
|
+
]
|
|
897
|
+
if need_add:
|
|
898
|
+
out += [
|
|
899
|
+
"static int32_t _bj_i32_add(int32_t a, int32_t b) {",
|
|
900
|
+
" int64_t r = (int64_t)a + (int64_t)b;",
|
|
901
|
+
" if (r < (-2147483647LL - 1LL) || r > 2147483647LL) _bj_numeric_fail();",
|
|
902
|
+
" return (int32_t)r;",
|
|
903
|
+
"}",
|
|
904
|
+
]
|
|
905
|
+
if need_sub:
|
|
906
|
+
out += [
|
|
907
|
+
"static int32_t _bj_i32_sub(int32_t a, int32_t b) {",
|
|
908
|
+
" int64_t r = (int64_t)a - (int64_t)b;",
|
|
909
|
+
" if (r < (-2147483647LL - 1LL) || r > 2147483647LL) _bj_numeric_fail();",
|
|
910
|
+
" return (int32_t)r;",
|
|
911
|
+
"}",
|
|
912
|
+
]
|
|
913
|
+
if need_mul:
|
|
914
|
+
out += [
|
|
915
|
+
"static int32_t _bj_i32_mul(int32_t a, int32_t b) {",
|
|
916
|
+
" int64_t r = (int64_t)a * (int64_t)b;",
|
|
917
|
+
" if (r < (-2147483647LL - 1LL) || r > 2147483647LL) _bj_numeric_fail();",
|
|
918
|
+
" return (int32_t)r;",
|
|
919
|
+
"}",
|
|
920
|
+
]
|
|
921
|
+
if need_neg:
|
|
922
|
+
out += [
|
|
923
|
+
"static int32_t _bj_i32_neg(int32_t value) {",
|
|
924
|
+
" if (value == (-2147483647L - 1L)) _bj_numeric_fail();",
|
|
925
|
+
" return -value;",
|
|
926
|
+
"}",
|
|
927
|
+
]
|
|
928
|
+
if need_div:
|
|
929
|
+
out += [
|
|
930
|
+
"static float _bj_true_div_f32(float a, float b) {",
|
|
931
|
+
" if (b == 0.0f) _bj_numeric_fail();",
|
|
932
|
+
" return a / b;",
|
|
933
|
+
"}",
|
|
934
|
+
]
|
|
935
|
+
if need_mod_i:
|
|
936
|
+
out += [
|
|
937
|
+
"static int32_t _bj_mod_i32(int32_t a, int32_t b) {",
|
|
938
|
+
" if (b == 0) _bj_numeric_fail();",
|
|
939
|
+
" if (a == (-2147483647L - 1L) && b == -1) return 0;",
|
|
940
|
+
" int32_t r = a % b;",
|
|
941
|
+
" if (r != 0 && ((r < 0) != (b < 0))) r += b;",
|
|
942
|
+
" return r;",
|
|
943
|
+
"}",
|
|
944
|
+
]
|
|
945
|
+
if need_mod_f:
|
|
946
|
+
out += [
|
|
947
|
+
"static float _bj_mod_f32(float a, float b) {",
|
|
948
|
+
" if (isnan(a) || isnan(b) || isinf(a) || isinf(b) || b == 0.0f) _bj_numeric_fail();",
|
|
949
|
+
" float r = fmodf(a, b);",
|
|
950
|
+
" if (r != 0.0f && ((r < 0.0f) != (b < 0.0f))) r += b;",
|
|
951
|
+
" if (r == 0.0f) r = copysignf(0.0f, b);",
|
|
952
|
+
" return r;",
|
|
953
|
+
"}",
|
|
954
|
+
]
|
|
955
|
+
if need_pwm:
|
|
956
|
+
out += [
|
|
957
|
+
"static int32_t _bj_pwm_value(int32_t value) {",
|
|
958
|
+
" if (value < 0 || value > 255) _bj_numeric_fail();",
|
|
959
|
+
" return value;",
|
|
960
|
+
"}",
|
|
961
|
+
]
|
|
962
|
+
if need_time:
|
|
963
|
+
out += [
|
|
964
|
+
"static uint32_t _bj_time_threshold(int32_t value) {",
|
|
965
|
+
" if (value < 0) _bj_numeric_fail();",
|
|
966
|
+
" return (uint32_t)value;",
|
|
967
|
+
"}",
|
|
968
|
+
]
|
|
969
|
+
if need_float_to_int:
|
|
970
|
+
out += [
|
|
971
|
+
"static int32_t _bj_float_to_i32(float value) {",
|
|
972
|
+
" if (isnan(value) || isinf(value) || value < -2147483648.0f || value >= 2147483648.0f) _bj_numeric_fail();",
|
|
973
|
+
" return (int32_t)value;",
|
|
974
|
+
"}",
|
|
975
|
+
]
|
|
976
|
+
if need_round_i32 or need_round_f32_digits:
|
|
977
|
+
out += [
|
|
978
|
+
"static float _bj_round_even_f32(float value) {",
|
|
979
|
+
" float base = floorf(value);",
|
|
980
|
+
" float frac = value - base;",
|
|
981
|
+
" if (frac > 0.5f) return base + 1.0f;",
|
|
982
|
+
" if (frac < 0.5f) return base;",
|
|
983
|
+
" return (fmodf(fabsf(base), 2.0f) == 0.0f) ? base : (base + 1.0f);",
|
|
984
|
+
"}",
|
|
985
|
+
]
|
|
986
|
+
if need_round_i32:
|
|
987
|
+
out += [
|
|
988
|
+
"static int32_t _bj_round_i32(float value) {",
|
|
989
|
+
" if (isnan(value) || isinf(value) || value < -2147483648.0f || value >= 2147483648.0f) _bj_numeric_fail();",
|
|
990
|
+
" float rounded = _bj_round_even_f32(value);",
|
|
991
|
+
" if (rounded < -2147483648.0f || rounded >= 2147483648.0f) _bj_numeric_fail();",
|
|
992
|
+
" return (int32_t)rounded;",
|
|
993
|
+
"}",
|
|
994
|
+
]
|
|
995
|
+
if need_round_f32_digits:
|
|
996
|
+
out += [
|
|
997
|
+
"static uint64_t _bj_round_ratio_even_u64(uint64_t num, uint64_t den) {",
|
|
998
|
+
" uint64_t q = num / den;",
|
|
999
|
+
" uint64_t r = num % den;",
|
|
1000
|
+
" uint64_t twice = r * 2ULL;",
|
|
1001
|
+
" if (twice > den || (twice == den && (q & 1ULL))) ++q;",
|
|
1002
|
+
" return q;",
|
|
1003
|
+
"}",
|
|
1004
|
+
"static uint64_t _bj_round_pow2_even_u64(uint64_t num, uint8_t shift) {",
|
|
1005
|
+
" if (shift == 0) return num;",
|
|
1006
|
+
" if (shift >= 64) return 0;",
|
|
1007
|
+
" uint64_t q = num >> shift;",
|
|
1008
|
+
" uint64_t mask = (1ULL << shift) - 1ULL;",
|
|
1009
|
+
" uint64_t r = num & mask;",
|
|
1010
|
+
" uint64_t half = 1ULL << (shift - 1);",
|
|
1011
|
+
" if (r > half || (r == half && (q & 1ULL))) ++q;",
|
|
1012
|
+
" return q;",
|
|
1013
|
+
"}",
|
|
1014
|
+
"static float _bj_ratio_to_f32(bool negative, uint64_t num, uint32_t den) {",
|
|
1015
|
+
" union { uint32_t u; float f; } out;",
|
|
1016
|
+
" if (num == 0) { out.u = negative ? 0x80000000UL : 0UL; return out.f; }",
|
|
1017
|
+
" int16_t exponent = 0;",
|
|
1018
|
+
" if (num >= den) {",
|
|
1019
|
+
" uint64_t scaled_den = den;",
|
|
1020
|
+
" while (num >= (scaled_den << 1)) { scaled_den <<= 1; ++exponent; }",
|
|
1021
|
+
" } else {",
|
|
1022
|
+
" uint64_t scaled_num = num;",
|
|
1023
|
+
" while (scaled_num < den) { scaled_num <<= 1; --exponent; }",
|
|
1024
|
+
" }",
|
|
1025
|
+
" int16_t shift = 23 - exponent;",
|
|
1026
|
+
" uint64_t scaled_num = num;",
|
|
1027
|
+
" uint64_t scaled_den = den;",
|
|
1028
|
+
" if (shift >= 0) scaled_num <<= shift; else scaled_den <<= -shift;",
|
|
1029
|
+
" uint64_t significand = _bj_round_ratio_even_u64(scaled_num, scaled_den);",
|
|
1030
|
+
" if (significand == (1ULL << 24)) { significand >>= 1; ++exponent; }",
|
|
1031
|
+
" uint32_t exponent_bits = (uint32_t)(exponent + 127);",
|
|
1032
|
+
" if (exponent_bits == 0 || exponent_bits >= 255) _bj_numeric_fail();",
|
|
1033
|
+
" out.u = (negative ? 0x80000000UL : 0UL) | (exponent_bits << 23) | ((uint32_t)significand & 0x7FFFFFUL);",
|
|
1034
|
+
" return out.f;",
|
|
1035
|
+
"}",
|
|
1036
|
+
"static float _bj_round_f32_digits(float value, int8_t ndigits) {",
|
|
1037
|
+
" if (isnan(value) || isinf(value) || value == 0.0f) return value;",
|
|
1038
|
+
" union { float f; uint32_t u; } bits; bits.f = value;",
|
|
1039
|
+
" bool negative = (bits.u & 0x80000000UL) != 0;",
|
|
1040
|
+
" uint32_t exp_bits = (bits.u >> 23) & 0xFFUL;",
|
|
1041
|
+
" uint32_t fraction = bits.u & 0x7FFFFFUL;",
|
|
1042
|
+
" uint64_t mantissa; int16_t exponent2; int16_t unbiased;",
|
|
1043
|
+
" if (exp_bits == 0) { mantissa = fraction; exponent2 = -149; unbiased = -126; }",
|
|
1044
|
+
" else { mantissa = (1UL << 23) | fraction; unbiased = (int16_t)exp_bits - 127; exponent2 = unbiased - 23; }",
|
|
1045
|
+
" static const uint32_t p5[7] = {1UL, 5UL, 25UL, 125UL, 625UL, 3125UL, 15625UL};",
|
|
1046
|
+
" static const uint32_t p10[7] = {1UL, 10UL, 100UL, 1000UL, 10000UL, 100000UL, 1000000UL};",
|
|
1047
|
+
" if (ndigits >= 0) {",
|
|
1048
|
+
" if (unbiased >= 23) return value;",
|
|
1049
|
+
" uint8_t n = (uint8_t)ndigits;",
|
|
1050
|
+
" uint64_t mag = mantissa * (uint64_t)p5[n];",
|
|
1051
|
+
" int16_t shift2 = exponent2 + n;",
|
|
1052
|
+
" uint64_t rounded;",
|
|
1053
|
+
" if (shift2 >= 0) rounded = mag << shift2;",
|
|
1054
|
+
" else rounded = _bj_round_pow2_even_u64(mag, (uint8_t)(-shift2));",
|
|
1055
|
+
" return _bj_ratio_to_f32(negative, rounded, p10[n]);",
|
|
1056
|
+
" }",
|
|
1057
|
+
" uint8_t n = (uint8_t)(-ndigits);",
|
|
1058
|
+
" if (unbiased >= 43) return value;",
|
|
1059
|
+
" int16_t shift2 = exponent2 - n;",
|
|
1060
|
+
" uint64_t rounded;",
|
|
1061
|
+
" if (shift2 >= 0) {",
|
|
1062
|
+
" uint64_t numerator = mantissa << shift2;",
|
|
1063
|
+
" rounded = _bj_round_ratio_even_u64(numerator, p5[n]);",
|
|
1064
|
+
" } else {",
|
|
1065
|
+
" uint8_t down = (uint8_t)(-shift2);",
|
|
1066
|
+
" if (down >= 24) rounded = 0;",
|
|
1067
|
+
" else rounded = _bj_round_ratio_even_u64(mantissa, ((uint64_t)p5[n]) << down);",
|
|
1068
|
+
" }",
|
|
1069
|
+
" return _bj_ratio_to_f32(negative, rounded * (uint64_t)p10[n], 1UL);",
|
|
1070
|
+
"}",
|
|
1071
|
+
]
|
|
1072
|
+
if need_round_i32_digits:
|
|
1073
|
+
out += [
|
|
1074
|
+
"static int32_t _bj_round_i32_digits(int32_t value, int8_t ndigits) {",
|
|
1075
|
+
" if (ndigits >= 0) return value;",
|
|
1076
|
+
" static const int32_t p10[7] = {1L, 10L, 100L, 1000L, 10000L, 100000L, 1000000L};",
|
|
1077
|
+
" int64_t scale = p10[-ndigits];",
|
|
1078
|
+
" int64_t q = ((int64_t)value) / scale;",
|
|
1079
|
+
" int64_t r = ((int64_t)value) % scale;",
|
|
1080
|
+
" int64_t ar = r < 0 ? -r : r;",
|
|
1081
|
+
" int64_t twice = ar * 2LL;",
|
|
1082
|
+
" if (twice > scale || (twice == scale && (q % 2LL) != 0LL)) q += value < 0 ? -1LL : 1LL;",
|
|
1083
|
+
" int64_t result = q * scale;",
|
|
1084
|
+
" if (result < (-2147483647LL - 1LL) || result > 2147483647LL) _bj_numeric_fail();",
|
|
1085
|
+
" return (int32_t)result;",
|
|
1086
|
+
"}",
|
|
1087
|
+
]
|
|
1088
|
+
if need_abs_i:
|
|
1089
|
+
out += [
|
|
1090
|
+
"static int32_t _bj_i32_abs(int32_t value) {",
|
|
1091
|
+
" if (value == (-2147483647L - 1L)) _bj_numeric_fail();",
|
|
1092
|
+
" return value < 0 ? -value : value;",
|
|
1093
|
+
"}",
|
|
1094
|
+
]
|
|
1095
|
+
if need_string_eq:
|
|
1096
|
+
out += [
|
|
1097
|
+
"static bool _bj_str_eq(const char* a, const char* b) {",
|
|
1098
|
+
" while (*a && *b && *a == *b) { ++a; ++b; }",
|
|
1099
|
+
" return *a == *b;",
|
|
1100
|
+
"}",
|
|
1101
|
+
]
|
|
1102
|
+
return out
|
|
1103
|
+
|
|
1104
|
+
def _output_helper_lines(self) -> list[str]:
|
|
1105
|
+
"""Compiler-owned Python output helpers, emitted only when required."""
|
|
1106
|
+
if not self._needs_python_float_formatter():
|
|
1107
|
+
return []
|
|
1108
|
+
return [
|
|
1109
|
+
"static void _bj_print_py_float(Print& out, float value) {",
|
|
1110
|
+
" if (isnan(value)) { out.print(F(\"nan\")); return; }",
|
|
1111
|
+
" if (isinf(value)) { out.print(value < 0.0f ? F(\"-inf\") : F(\"inf\")); return; }",
|
|
1112
|
+
" if (value == 0.0f) {",
|
|
1113
|
+
" union { float f; uint32_t u; } bits; bits.f = value;",
|
|
1114
|
+
" out.print((bits.u & 0x80000000UL) ? F(\"-0.0\") : F(\"0.0\"));",
|
|
1115
|
+
" return;",
|
|
1116
|
+
" }",
|
|
1117
|
+
" char scientific[20];",
|
|
1118
|
+
" dtostre(value, scientific, 8, 0); // 9 significant digits: enough to round-trip float32.",
|
|
1119
|
+
" const char* p = scientific;",
|
|
1120
|
+
" char result[32]; char* w = result;",
|
|
1121
|
+
" if (*p == '-') *w++ = *p++;",
|
|
1122
|
+
" char digits[10]; uint8_t count = 0;",
|
|
1123
|
+
" if (*p) digits[count++] = *p++;",
|
|
1124
|
+
" if (*p == '.') ++p;",
|
|
1125
|
+
" while (*p && *p != 'e' && *p != 'E' && count < 9) digits[count++] = *p++;",
|
|
1126
|
+
" while (count > 1 && digits[count - 1] == '0') --count;",
|
|
1127
|
+
" if (*p == 'e' || *p == 'E') ++p;",
|
|
1128
|
+
" bool exp_negative = false;",
|
|
1129
|
+
" if (*p == '+' || *p == '-') { exp_negative = (*p == '-'); ++p; }",
|
|
1130
|
+
" int16_t exponent = 0;",
|
|
1131
|
+
" while (*p >= '0' && *p <= '9') { exponent = (int16_t)(exponent * 10 + (*p++ - '0')); }",
|
|
1132
|
+
" if (exp_negative) exponent = -exponent;",
|
|
1133
|
+
" if (exponent >= -4 && exponent < 16) {",
|
|
1134
|
+
" int16_t decimal_pos = exponent + 1;",
|
|
1135
|
+
" if (decimal_pos <= 0) {",
|
|
1136
|
+
" *w++ = '0'; *w++ = '.';",
|
|
1137
|
+
" for (int16_t i = 0; i < -decimal_pos; ++i) *w++ = '0';",
|
|
1138
|
+
" for (uint8_t i = 0; i < count; ++i) *w++ = digits[i];",
|
|
1139
|
+
" } else {",
|
|
1140
|
+
" for (int16_t i = 0; i < decimal_pos; ++i) *w++ = (i < count) ? digits[i] : '0';",
|
|
1141
|
+
" if (decimal_pos < count) {",
|
|
1142
|
+
" *w++ = '.';",
|
|
1143
|
+
" for (uint8_t i = (uint8_t)decimal_pos; i < count; ++i) *w++ = digits[i];",
|
|
1144
|
+
" } else {",
|
|
1145
|
+
" *w++ = '.'; *w++ = '0';",
|
|
1146
|
+
" }",
|
|
1147
|
+
" }",
|
|
1148
|
+
" } else {",
|
|
1149
|
+
" *w++ = digits[0];",
|
|
1150
|
+
" if (count > 1) { *w++ = '.'; for (uint8_t i = 1; i < count; ++i) *w++ = digits[i]; }",
|
|
1151
|
+
" *w++ = 'e';",
|
|
1152
|
+
" int16_t e = exponent;",
|
|
1153
|
+
" if (e < 0) { *w++ = '-'; e = -e; } else { *w++ = '+'; }",
|
|
1154
|
+
" char expbuf[6]; uint8_t n = 0;",
|
|
1155
|
+
" do { expbuf[n++] = (char)('0' + (e % 10)); e /= 10; } while (e && n < sizeof(expbuf));",
|
|
1156
|
+
" while (n) *w++ = expbuf[--n];",
|
|
1157
|
+
" }",
|
|
1158
|
+
" *w = '\\0';",
|
|
1159
|
+
" out.print(result);",
|
|
1160
|
+
"}",
|
|
1161
|
+
]
|
|
1162
|
+
|
|
1163
|
+
def _needs_python_float_formatter(self) -> bool:
|
|
1164
|
+
if self._resolved is None or self._typed is None:
|
|
1165
|
+
return False
|
|
1166
|
+
|
|
1167
|
+
def fstring_needs(node: ast.JoinedStr) -> bool:
|
|
1168
|
+
for part in node.values:
|
|
1169
|
+
if not isinstance(part, ast.FormattedValue):
|
|
1170
|
+
continue
|
|
1171
|
+
if part.format_spec is not None:
|
|
1172
|
+
continue
|
|
1173
|
+
try:
|
|
1174
|
+
if self._typed.type_for_expr(part.value).kind == TypeKind.FLOAT:
|
|
1175
|
+
return True
|
|
1176
|
+
except KeyError:
|
|
1177
|
+
pass
|
|
1178
|
+
return False
|
|
1179
|
+
|
|
1180
|
+
for node in ast.walk(self._resolved.frontend.tree):
|
|
1181
|
+
if not isinstance(node, ast.Call):
|
|
1182
|
+
continue
|
|
1183
|
+
is_python_print = isinstance(node.func, ast.Name) and node.func.id == "print"
|
|
1184
|
+
is_output_method = (
|
|
1185
|
+
isinstance(node.func, ast.Attribute)
|
|
1186
|
+
and node.func.attr in {"print", "println"}
|
|
1187
|
+
)
|
|
1188
|
+
if not (is_python_print or is_output_method):
|
|
1189
|
+
continue
|
|
1190
|
+
for arg in node.args:
|
|
1191
|
+
if isinstance(arg, ast.JoinedStr):
|
|
1192
|
+
if fstring_needs(arg):
|
|
1193
|
+
return True
|
|
1194
|
+
elif is_python_print:
|
|
1195
|
+
try:
|
|
1196
|
+
if self._typed.type_for_expr(arg).kind == TypeKind.FLOAT:
|
|
1197
|
+
return True
|
|
1198
|
+
except KeyError:
|
|
1199
|
+
pass
|
|
1200
|
+
return False
|
|
1201
|
+
|
|
1202
|
+
def _new_temp(self, hint: str) -> str:
|
|
1203
|
+
self._temp_counter += 1
|
|
1204
|
+
return f"_bj_tmp_{hint}_{self._temp_counter:04d}"
|
|
1205
|
+
|
|
1206
|
+
def _symbols_with_storage(self, storage: StorageClass) -> list:
|
|
1207
|
+
if self._flow is None:
|
|
1208
|
+
return []
|
|
1209
|
+
symbols = [fact.symbol for fact in self._flow.symbol_facts.values() if fact.storage == storage]
|
|
1210
|
+
return sorted(symbols, key=lambda x: (x.declaration_line or 10**9, x.symbol_id.value))
|
|
1211
|
+
|
|
1212
|
+
def _module_predeclared_cpp_names(self, *storages: StorageClass) -> set[str]:
|
|
1213
|
+
if self._flow is None:
|
|
1214
|
+
return set()
|
|
1215
|
+
accepted = set(storages)
|
|
1216
|
+
return {
|
|
1217
|
+
fact.symbol.cpp_name for fact in self._flow.symbol_facts.values()
|
|
1218
|
+
if fact.symbol.scope_name == "<module>" and fact.storage in accepted
|
|
1219
|
+
}
|
|
1220
|
+
|
|
1221
|
+
def _cpp_type_for_symbol(self, symbol) -> str:
|
|
1222
|
+
if self._typed is None:
|
|
1223
|
+
raise InternalCompilerError("Typed analysis is unavailable during code generation.")
|
|
1224
|
+
return self._typed.type_for_symbol(symbol).cpp_type
|
|
1225
|
+
|
|
1226
|
+
def _declaration_type(self, symbol) -> str:
|
|
1227
|
+
"""Formal Step-7 storage type supplied by the type phase."""
|
|
1228
|
+
return self._cpp_type_for_symbol(symbol)
|
|
1229
|
+
|
|
1230
|
+
def _first_assignment_expr(self, symbol):
|
|
1231
|
+
if self._resolved is None:
|
|
1232
|
+
return None
|
|
1233
|
+
found = None
|
|
1234
|
+
|
|
1235
|
+
class Finder(ast.NodeVisitor):
|
|
1236
|
+
def visit_FunctionDef(inner_self, node: ast.FunctionDef):
|
|
1237
|
+
nonlocal found
|
|
1238
|
+
# Search only the owning function for function-local symbols;
|
|
1239
|
+
# skip functions when looking for module storage.
|
|
1240
|
+
if symbol.scope_name == "<module>":
|
|
1241
|
+
return
|
|
1242
|
+
if node.name != symbol.scope_name:
|
|
1243
|
+
return
|
|
1244
|
+
inner_self.generic_visit(node)
|
|
1245
|
+
|
|
1246
|
+
def visit_Assign(inner_self, node: ast.Assign):
|
|
1247
|
+
nonlocal found
|
|
1248
|
+
if found is not None:
|
|
1249
|
+
return
|
|
1250
|
+
for target in node.targets:
|
|
1251
|
+
pairs = [(target, node.value)]
|
|
1252
|
+
if isinstance(target, (ast.Tuple, ast.List)) and isinstance(node.value, (ast.Tuple, ast.List)):
|
|
1253
|
+
pairs = list(zip(target.elts, node.value.elts))
|
|
1254
|
+
for t, value in pairs:
|
|
1255
|
+
if isinstance(t, ast.Name):
|
|
1256
|
+
try:
|
|
1257
|
+
sym = self._resolved.symbol_for_name(t)
|
|
1258
|
+
except KeyError:
|
|
1259
|
+
continue
|
|
1260
|
+
if sym.symbol_id == symbol.symbol_id:
|
|
1261
|
+
found = value
|
|
1262
|
+
return
|
|
1263
|
+
inner_self.generic_visit(node.value)
|
|
1264
|
+
|
|
1265
|
+
def visit_For(inner_self, node: ast.For):
|
|
1266
|
+
nonlocal found
|
|
1267
|
+
if found is None and isinstance(node.target, ast.Name):
|
|
1268
|
+
try:
|
|
1269
|
+
sym = self._resolved.symbol_for_name(node.target)
|
|
1270
|
+
except KeyError:
|
|
1271
|
+
sym = None
|
|
1272
|
+
if sym is not None and sym.symbol_id == symbol.symbol_id:
|
|
1273
|
+
found = ast.Constant(value=0)
|
|
1274
|
+
return
|
|
1275
|
+
inner_self.generic_visit(node)
|
|
1276
|
+
|
|
1277
|
+
Finder().visit(self._resolved.frontend.tree)
|
|
1278
|
+
return found
|
|
1279
|
+
|
|
1280
|
+
def _serial_print(self, call: ast.Call, indent: int) -> list[str]:
|
|
1281
|
+
"""Lower Python print() with Python evaluation/formatting semantics."""
|
|
1282
|
+
pad = " " * indent
|
|
1283
|
+
preludes: list[str] = []
|
|
1284
|
+
parts: list[_OutputPart] = []
|
|
1285
|
+
for index, arg in enumerate(call.args):
|
|
1286
|
+
if index:
|
|
1287
|
+
parts.append(_OutputPart(literal=" "))
|
|
1288
|
+
arg_prelude, arg_parts = self._prepare_python_output_arg(arg)
|
|
1289
|
+
preludes.extend(arg_prelude)
|
|
1290
|
+
parts.extend(arg_parts)
|
|
1291
|
+
|
|
1292
|
+
out = [pad + line for line in preludes]
|
|
1293
|
+
for part in parts:
|
|
1294
|
+
out.extend(self._emit_python_output_part("Serial", part, indent, lcd_mode=False))
|
|
1295
|
+
# Python print() always emits exactly one final newline, including print().
|
|
1296
|
+
out.append(pad + "Serial.println();")
|
|
1297
|
+
return out
|
|
1298
|
+
|
|
1299
|
+
def _explicit_output_stmt(self, call: ast.Call, indent: int) -> list[str] | None:
|
|
1300
|
+
"""Special output lowering for flash literals and Python f-strings.
|
|
1301
|
+
|
|
1302
|
+
Plain explicit Serial/LCD numeric calls remain Arduino-compatible and
|
|
1303
|
+
therefore fall back to the ordinary API emitter. Only f-strings need
|
|
1304
|
+
Python formatting, while direct string literals can safely use F().
|
|
1305
|
+
"""
|
|
1306
|
+
if not isinstance(call.func, ast.Attribute) or not isinstance(call.func.value, ast.Name):
|
|
1307
|
+
return None
|
|
1308
|
+
owner_source = call.func.value.id
|
|
1309
|
+
method = call.func.attr
|
|
1310
|
+
if method not in {"print", "println"}:
|
|
1311
|
+
return None
|
|
1312
|
+
owner = self._expr(call.func.value)
|
|
1313
|
+
lcd_mode = self._objects.get(owner_source) == "LiquidCrystal_I2C"
|
|
1314
|
+
pad = " " * indent
|
|
1315
|
+
|
|
1316
|
+
if len(call.args) == 1 and isinstance(call.args[0], ast.Constant) and isinstance(call.args[0].value, str):
|
|
1317
|
+
literal = self._flash_literal(call.args[0].value, lcd_mode=lcd_mode)
|
|
1318
|
+
return [f"{pad}{owner}.{method}({literal});"]
|
|
1319
|
+
|
|
1320
|
+
if len(call.args) == 1 and isinstance(call.args[0], ast.JoinedStr):
|
|
1321
|
+
preludes, parts = self._prepare_python_output_arg(call.args[0])
|
|
1322
|
+
out = [pad + line for line in preludes]
|
|
1323
|
+
for part in parts:
|
|
1324
|
+
out.extend(self._emit_python_output_part(owner, part, indent, lcd_mode=lcd_mode))
|
|
1325
|
+
if method == "println":
|
|
1326
|
+
out.append(f"{pad}{owner}.println();")
|
|
1327
|
+
return out
|
|
1328
|
+
return None
|
|
1329
|
+
|
|
1330
|
+
def _prepare_python_output_arg(self, node: ast.AST) -> tuple[list[str], list[_OutputPart]]:
|
|
1331
|
+
if self._typed is None:
|
|
1332
|
+
raise InternalCompilerError("Typed analysis is unavailable during output lowering.")
|
|
1333
|
+
if isinstance(node, ast.JoinedStr):
|
|
1334
|
+
preludes: list[str] = []
|
|
1335
|
+
parts: list[_OutputPart] = []
|
|
1336
|
+
for item in node.values:
|
|
1337
|
+
if isinstance(item, ast.Constant) and isinstance(item.value, str):
|
|
1338
|
+
if item.value:
|
|
1339
|
+
parts.append(_OutputPart(literal=item.value))
|
|
1340
|
+
continue
|
|
1341
|
+
if not isinstance(item, ast.FormattedValue):
|
|
1342
|
+
raise InternalCompilerError("Unexpected f-string part reached output lowering.", line=getattr(item, "lineno", None))
|
|
1343
|
+
precision = self._fstring_precision(item)
|
|
1344
|
+
prelude, part = self._prepare_python_output_value(item.value, precision=precision)
|
|
1345
|
+
preludes.extend(prelude)
|
|
1346
|
+
parts.append(part)
|
|
1347
|
+
return preludes, parts
|
|
1348
|
+
|
|
1349
|
+
if isinstance(node, ast.Constant) and isinstance(node.value, str):
|
|
1350
|
+
return [], [_OutputPart(literal=node.value)]
|
|
1351
|
+
prelude, part = self._prepare_python_output_value(node, precision=None)
|
|
1352
|
+
return prelude, [part]
|
|
1353
|
+
|
|
1354
|
+
def _prepare_python_output_value(self, node: ast.AST, *, precision: int | None) -> tuple[list[str], _OutputPart]:
|
|
1355
|
+
if self._typed is None:
|
|
1356
|
+
raise InternalCompilerError("Typed analysis is unavailable during output lowering.")
|
|
1357
|
+
typ = self._typed.type_for_expr(node)
|
|
1358
|
+
if typ.kind not in {TypeKind.BOOL, TypeKind.INT, TypeKind.TIME_U32, TypeKind.FLOAT, TypeKind.STRING}:
|
|
1359
|
+
raise InternalCompilerError(
|
|
1360
|
+
f"Non-printable type {typ.kind.value} reached output lowering.",
|
|
1361
|
+
line=getattr(node, "lineno", None),
|
|
1362
|
+
)
|
|
1363
|
+
expr = self._expr(node)
|
|
1364
|
+
# Constants are side-effect free and immutable, so they need no temp.
|
|
1365
|
+
# Everything else is captured before *any* output bytes are emitted.
|
|
1366
|
+
if isinstance(node, ast.Constant):
|
|
1367
|
+
return [], _OutputPart(expression=expr, type_kind=typ.kind, precision=precision)
|
|
1368
|
+
temp = self._new_temp("print_arg")
|
|
1369
|
+
return [f"{typ.cpp_type} {temp} = {expr};"], _OutputPart(
|
|
1370
|
+
expression=temp,
|
|
1371
|
+
type_kind=typ.kind,
|
|
1372
|
+
precision=precision,
|
|
1373
|
+
)
|
|
1374
|
+
|
|
1375
|
+
def _emit_python_output_part(self, sink: str, part: _OutputPart, indent: int, *, lcd_mode: bool) -> list[str]:
|
|
1376
|
+
pad = " " * indent
|
|
1377
|
+
if part.literal is not None:
|
|
1378
|
+
return [f"{pad}{sink}.print({self._flash_literal(part.literal, lcd_mode=lcd_mode)});"]
|
|
1379
|
+
if part.expression is None or part.type_kind is None:
|
|
1380
|
+
raise InternalCompilerError("Malformed Python output part reached backend.")
|
|
1381
|
+
expr = part.expression
|
|
1382
|
+
if part.precision is not None:
|
|
1383
|
+
# Deliberate float cast prevents Arduino Print::print(long, base)
|
|
1384
|
+
# from interpreting .Nf precision as an integer number base.
|
|
1385
|
+
return [f"{pad}{sink}.print((float)({expr}), {part.precision});"]
|
|
1386
|
+
if part.type_kind == TypeKind.BOOL:
|
|
1387
|
+
return [f"{pad}if ({expr}) {sink}.print(F(\"True\")); else {sink}.print(F(\"False\"));"]
|
|
1388
|
+
if part.type_kind == TypeKind.INT:
|
|
1389
|
+
return [f"{pad}{sink}.print((int32_t)({expr}));"]
|
|
1390
|
+
if part.type_kind == TypeKind.TIME_U32:
|
|
1391
|
+
return [f"{pad}{sink}.print((uint32_t)({expr}));"]
|
|
1392
|
+
if part.type_kind == TypeKind.FLOAT:
|
|
1393
|
+
return [f"{pad}_bj_print_py_float({sink}, (float)({expr}));"]
|
|
1394
|
+
if part.type_kind == TypeKind.STRING:
|
|
1395
|
+
return [f"{pad}{sink}.print({expr});"]
|
|
1396
|
+
raise InternalCompilerError("Unsupported Python output type reached backend.")
|
|
1397
|
+
|
|
1398
|
+
@staticmethod
|
|
1399
|
+
def _fstring_precision(node: ast.FormattedValue) -> int | None:
|
|
1400
|
+
if node.format_spec is None:
|
|
1401
|
+
return None
|
|
1402
|
+
if not isinstance(node.format_spec, ast.JoinedStr):
|
|
1403
|
+
raise InternalCompilerError("Validated f-string format changed before backend lowering.")
|
|
1404
|
+
text = "".join(
|
|
1405
|
+
item.value for item in node.format_spec.values
|
|
1406
|
+
if isinstance(item, ast.Constant) and isinstance(item.value, str)
|
|
1407
|
+
)
|
|
1408
|
+
if text.startswith(".") and text.endswith("f") and text[1:-1].isdigit():
|
|
1409
|
+
return int(text[1:-1])
|
|
1410
|
+
raise InternalCompilerError("Unsupported f-string format reached backend after validation.")
|
|
1411
|
+
|
|
1412
|
+
def _flash_literal(self, text: str, *, lcd_mode: bool = False) -> str:
|
|
1413
|
+
return f"F({self._cpp_string_literal(text, lcd_mode=lcd_mode)})"
|
|
1414
|
+
|
|
1415
|
+
@staticmethod
|
|
1416
|
+
def _cpp_string_literal(text: str, *, lcd_mode: bool = False) -> str:
|
|
1417
|
+
data = bytearray()
|
|
1418
|
+
if lcd_mode:
|
|
1419
|
+
for ch in text:
|
|
1420
|
+
code = ord(ch)
|
|
1421
|
+
if 0x20 <= code <= 0x7E:
|
|
1422
|
+
data.append(code)
|
|
1423
|
+
elif ch == "°":
|
|
1424
|
+
data.append(0xDF)
|
|
1425
|
+
else:
|
|
1426
|
+
raise InternalCompilerError("Unsupported LCD character reached backend after target validation.")
|
|
1427
|
+
else:
|
|
1428
|
+
data.extend(text.encode("utf-8"))
|
|
1429
|
+
|
|
1430
|
+
pieces: list[str] = []
|
|
1431
|
+
for byte in data:
|
|
1432
|
+
if byte == 0x5C:
|
|
1433
|
+
pieces.append("\\\\")
|
|
1434
|
+
elif byte == 0x22:
|
|
1435
|
+
pieces.append('\\"')
|
|
1436
|
+
elif byte == 0x0A:
|
|
1437
|
+
pieces.append("\\n")
|
|
1438
|
+
elif byte == 0x0D:
|
|
1439
|
+
pieces.append("\\r")
|
|
1440
|
+
elif byte == 0x09:
|
|
1441
|
+
pieces.append("\\t")
|
|
1442
|
+
elif 0x20 <= byte <= 0x7E:
|
|
1443
|
+
pieces.append(chr(byte))
|
|
1444
|
+
else:
|
|
1445
|
+
# Fixed-width octal escapes cannot consume following hex chars.
|
|
1446
|
+
pieces.append(f"\\{byte:03o}")
|
|
1447
|
+
return '"' + "".join(pieces) + '"'
|
|
1448
|
+
|
|
1449
|
+
# ---------- semantic expression helpers ----------
|
|
1450
|
+
@staticmethod
|
|
1451
|
+
def _expr_has_effects(node: ast.AST) -> bool:
|
|
1452
|
+
return any(isinstance(x, ast.Call) for x in ast.walk(node))
|
|
1453
|
+
|
|
1454
|
+
def _cpp_expr_type(self, node: ast.AST) -> str:
|
|
1455
|
+
if self._typed is None:
|
|
1456
|
+
raise InternalCompilerError("Typed analysis is unavailable during semantic lowering.")
|
|
1457
|
+
return self._typed.type_for_expr(node).cpp_type
|
|
1458
|
+
|
|
1459
|
+
def _ordered_call(self, node: ast.Call, callee: str, args: list[str]) -> str:
|
|
1460
|
+
"""Preserve Python left-to-right argument evaluation for effectful args."""
|
|
1461
|
+
if len(node.args) <= 1 or not any(self._expr_has_effects(a) for a in node.args):
|
|
1462
|
+
return f"{callee}({', '.join(args)})"
|
|
1463
|
+
decls: list[str] = []
|
|
1464
|
+
names: list[str] = []
|
|
1465
|
+
for arg_node, arg_expr in zip(node.args, args):
|
|
1466
|
+
name = self._new_temp("arg")
|
|
1467
|
+
names.append(name)
|
|
1468
|
+
decls.append(f"{self._cpp_expr_type(arg_node)} {name} = {arg_expr};")
|
|
1469
|
+
ret = self._typed.type_for_expr(node) if self._typed is not None else None
|
|
1470
|
+
call = f"{callee}({', '.join(names)})"
|
|
1471
|
+
if ret is not None and ret.kind == TypeKind.VOID:
|
|
1472
|
+
return "([&]() { " + " ".join(decls) + f" {call}; }}())"
|
|
1473
|
+
return "([&]() -> " + (ret.cpp_type if ret is not None else "auto") + " { " + " ".join(decls) + f" return {call}; }}())"
|
|
1474
|
+
|
|
1475
|
+
def _binary_expr(self, node: ast.BinOp) -> str:
|
|
1476
|
+
if self._typed is None:
|
|
1477
|
+
raise InternalCompilerError("Typed analysis is unavailable during binary lowering.")
|
|
1478
|
+
typ = self._typed.type_for_expr(node)
|
|
1479
|
+
left_expr = self._expr(node.left)
|
|
1480
|
+
right_expr = self._expr(node.right)
|
|
1481
|
+
|
|
1482
|
+
def operation(left: str, right: str) -> str:
|
|
1483
|
+
if typ.kind == TypeKind.TIME_U32 and isinstance(node.op, ast.Sub):
|
|
1484
|
+
return f"((uint32_t)({left}) - (uint32_t)({right}))"
|
|
1485
|
+
if isinstance(node.op, ast.Div):
|
|
1486
|
+
return f"_bj_true_div_f32((float)({left}), (float)({right}))"
|
|
1487
|
+
if isinstance(node.op, ast.Mod):
|
|
1488
|
+
if typ.kind == TypeKind.FLOAT:
|
|
1489
|
+
return f"_bj_mod_f32((float)({left}), (float)({right}))"
|
|
1490
|
+
return f"_bj_mod_i32({left}, {right})"
|
|
1491
|
+
if typ.kind == TypeKind.INT and self._numeric is not None and self._numeric.int_expr_needs_runtime_check(node):
|
|
1492
|
+
helper = {ast.Add: "_bj_i32_add", ast.Sub: "_bj_i32_sub", ast.Mult: "_bj_i32_mul"}.get(type(node.op))
|
|
1493
|
+
if helper is not None:
|
|
1494
|
+
return f"{helper}({left}, {right})"
|
|
1495
|
+
op = {ast.Add: "+", ast.Sub: "-", ast.Mult: "*"}[type(node.op)]
|
|
1496
|
+
return f"({left} {op} {right})"
|
|
1497
|
+
|
|
1498
|
+
if self._expr_has_effects(node.left) or self._expr_has_effects(node.right):
|
|
1499
|
+
ltmp = self._new_temp("lhs")
|
|
1500
|
+
rtmp = self._new_temp("rhs")
|
|
1501
|
+
ltype = self._cpp_expr_type(node.left)
|
|
1502
|
+
rtype = self._cpp_expr_type(node.right)
|
|
1503
|
+
return (
|
|
1504
|
+
f"([&]() -> {typ.cpp_type} {{ {ltype} {ltmp} = {left_expr}; "
|
|
1505
|
+
f"{rtype} {rtmp} = {right_expr}; return {operation(ltmp, rtmp)}; }}())"
|
|
1506
|
+
)
|
|
1507
|
+
return operation(left_expr, right_expr)
|
|
1508
|
+
|
|
1509
|
+
def _comparison_operand(self, left_node: ast.AST, right_node: ast.AST, opnode: ast.cmpop, left: str, right: str) -> str:
|
|
1510
|
+
if self._typed is None:
|
|
1511
|
+
raise InternalCompilerError("Typed analysis is unavailable during comparison lowering.")
|
|
1512
|
+
lt = self._typed.type_for_expr(left_node)
|
|
1513
|
+
rt = self._typed.type_for_expr(right_node)
|
|
1514
|
+
if lt.kind == TypeKind.STRING and rt.kind == TypeKind.STRING:
|
|
1515
|
+
eq = f"_bj_str_eq({left}, {right})"
|
|
1516
|
+
return eq if isinstance(opnode, ast.Eq) else f"(!{eq})"
|
|
1517
|
+
if self._numeric is not None:
|
|
1518
|
+
if lt.kind == TypeKind.TIME_U32 and rt.kind == TypeKind.INT:
|
|
1519
|
+
right = f"_bj_time_threshold({right})" if self._numeric.time_threshold_needs_runtime_check(right_node) else f"((uint32_t)({right}))"
|
|
1520
|
+
elif rt.kind == TypeKind.TIME_U32 and lt.kind == TypeKind.INT:
|
|
1521
|
+
left = f"_bj_time_threshold({left})" if self._numeric.time_threshold_needs_runtime_check(left_node) else f"((uint32_t)({left}))"
|
|
1522
|
+
op = {ast.Eq: "==", ast.NotEq: "!=", ast.Lt: "<", ast.LtE: "<=", ast.Gt: ">", ast.GtE: ">="}[type(opnode)]
|
|
1523
|
+
return f"({left} {op} {right})"
|
|
1524
|
+
|
|
1525
|
+
def _comparison_expr(self, node: ast.Compare) -> str:
|
|
1526
|
+
# C++ does not guarantee Python's comparison operand evaluation order.
|
|
1527
|
+
# Materialize operands sequentially and stop before evaluating later
|
|
1528
|
+
# operands when an earlier comparison is false.
|
|
1529
|
+
values = [node.left, *node.comparators]
|
|
1530
|
+
temp_names = [self._new_temp("cmp") for _ in values]
|
|
1531
|
+
pieces: list[str] = [f"{self._cpp_expr_type(values[0])} {temp_names[0]} = {self._expr(values[0])};"]
|
|
1532
|
+
for index, (opnode, right_node) in enumerate(zip(node.ops, node.comparators)):
|
|
1533
|
+
pieces.append(f"{self._cpp_expr_type(right_node)} {temp_names[index + 1]} = {self._expr(right_node)};")
|
|
1534
|
+
cmp_expr = self._comparison_operand(values[index], right_node, opnode, temp_names[index], temp_names[index + 1])
|
|
1535
|
+
pieces.append(f"if (!({cmp_expr})) return false;")
|
|
1536
|
+
pieces.append("return true;")
|
|
1537
|
+
return "([&]() -> bool { " + " ".join(pieces) + " }())"
|
|
1538
|
+
|
|
1539
|
+
def _minmax_expr(self, node: ast.Call, *, want_min: bool) -> str:
|
|
1540
|
+
if self._typed is None:
|
|
1541
|
+
raise InternalCompilerError("Typed analysis is unavailable during min/max lowering.")
|
|
1542
|
+
result_type = self._typed.type_for_expr(node).cpp_type
|
|
1543
|
+
temp_names: list[str] = []
|
|
1544
|
+
parts: list[str] = []
|
|
1545
|
+
for arg in node.args:
|
|
1546
|
+
name = self._new_temp("minmax_arg")
|
|
1547
|
+
temp_names.append(name)
|
|
1548
|
+
parts.append(f"{self._cpp_expr_type(arg)} {name} = {self._expr(arg)};")
|
|
1549
|
+
result = self._new_temp("minmax")
|
|
1550
|
+
parts.append(f"{result_type} {result} = {temp_names[0]};")
|
|
1551
|
+
op = "<" if want_min else ">"
|
|
1552
|
+
for name in temp_names[1:]:
|
|
1553
|
+
parts.append(f"if ({name} {op} {result}) {result} = {name};")
|
|
1554
|
+
parts.append(f"return {result};")
|
|
1555
|
+
return f"([&]() -> {result_type} {{ " + " ".join(parts) + " }())"
|
|
1556
|
+
|
|
1557
|
+
# ---------- expressions ----------
|
|
1558
|
+
def _expr(self, node: ast.AST) -> str:
|
|
1559
|
+
if isinstance(node, ast.Constant):
|
|
1560
|
+
if node.value is True: return "true"
|
|
1561
|
+
if node.value is False: return "false"
|
|
1562
|
+
if node.value is None: raise CompileError("None is not supported in Arduino expressions.", node.lineno)
|
|
1563
|
+
if isinstance(node.value, str):
|
|
1564
|
+
return self._cpp_string_literal(node.value)
|
|
1565
|
+
return repr(node.value)
|
|
1566
|
+
if isinstance(node, ast.Name):
|
|
1567
|
+
return self._resolved_name(node)
|
|
1568
|
+
if isinstance(node, ast.Call):
|
|
1569
|
+
if isinstance(node.func, ast.Attribute):
|
|
1570
|
+
owner = self._expr(node.func.value)
|
|
1571
|
+
arg_values = [self._expr(a) for a in node.args]
|
|
1572
|
+
return self._ordered_call(node, f"{owner}.{node.func.attr}", arg_values)
|
|
1573
|
+
if not isinstance(node.func, ast.Name):
|
|
1574
|
+
raise CompileError("Unsupported call form.", node.lineno)
|
|
1575
|
+
name = node.func.id
|
|
1576
|
+
args = [self._expr(a) for a in node.args]
|
|
1577
|
+
if name == "round":
|
|
1578
|
+
arg_type = self._typed.type_for_expr(node.args[0]) if self._typed is not None else None
|
|
1579
|
+
if len(args) == 1:
|
|
1580
|
+
if arg_type is not None and arg_type.kind == TypeKind.INT:
|
|
1581
|
+
return args[0]
|
|
1582
|
+
return f"_bj_round_i32((float)({args[0]}))"
|
|
1583
|
+
ndigits = self._literal_int(node.args[1])
|
|
1584
|
+
if ndigits is None:
|
|
1585
|
+
raise InternalCompilerError("round() ndigits reached code generation without a compile-time value.", line=node.lineno)
|
|
1586
|
+
if arg_type is not None and arg_type.kind == TypeKind.INT:
|
|
1587
|
+
return f"_bj_round_i32_digits({args[0]}, {ndigits})"
|
|
1588
|
+
return f"_bj_round_f32_digits((float)({args[0]}), {ndigits})"
|
|
1589
|
+
if name == "min":
|
|
1590
|
+
return self._minmax_expr(node, want_min=True)
|
|
1591
|
+
if name == "max":
|
|
1592
|
+
return self._minmax_expr(node, want_min=False)
|
|
1593
|
+
if name == "int":
|
|
1594
|
+
if self._typed is not None and node.args:
|
|
1595
|
+
arg_type = self._typed.type_for_expr(node.args[0])
|
|
1596
|
+
if arg_type.kind == TypeKind.FLOAT:
|
|
1597
|
+
return f"_bj_float_to_i32((float)({args[0]}))"
|
|
1598
|
+
return f"((int32_t)({args[0]}))"
|
|
1599
|
+
if name == "float": return f"((float)({args[0]}))"
|
|
1600
|
+
if name == "bool": return f"((bool)({args[0]}))"
|
|
1601
|
+
if name == "abs" and self._typed is not None and node.args:
|
|
1602
|
+
if self._typed.type_for_expr(node.args[0]).kind == TypeKind.INT:
|
|
1603
|
+
return f"_bj_i32_abs({args[0]})"
|
|
1604
|
+
return f"fabsf((float)({args[0]}))"
|
|
1605
|
+
if name == "len" and node.args and self._typed is not None:
|
|
1606
|
+
list_type = self._typed.type_for_expr(node.args[0])
|
|
1607
|
+
if list_type.kind != TypeKind.FIXED_LIST or list_type.length is None:
|
|
1608
|
+
raise InternalCompilerError("len() reached backend without fixed-list type.", line=node.lineno)
|
|
1609
|
+
return str(list_type.length)
|
|
1610
|
+
if name == "sum" and node.args and self._typed is not None:
|
|
1611
|
+
arg = node.args[0]
|
|
1612
|
+
if not isinstance(arg, ast.Name):
|
|
1613
|
+
raise InternalCompilerError("sum() reached backend without a fixed-list name.", line=node.lineno)
|
|
1614
|
+
list_type = self._typed.type_for_expr(arg)
|
|
1615
|
+
if list_type.kind != TypeKind.FIXED_LIST or list_type.length is None or list_type.element_type is None:
|
|
1616
|
+
raise InternalCompilerError("sum() reached backend without fixed-list type.", line=node.lineno)
|
|
1617
|
+
list_name = self._resolved_name(arg)
|
|
1618
|
+
idx = self._new_temp("sum_i")
|
|
1619
|
+
acc = self._new_temp("sum_acc")
|
|
1620
|
+
if list_type.element_type.kind == TypeKind.INT:
|
|
1621
|
+
return f"([&]() -> int32_t {{ int32_t {acc} = 0; for (int32_t {idx} = 0; {idx} < {list_type.length}; ++{idx}) {acc} = _bj_i32_add({acc}, {list_name}[{idx}]); return {acc}; }}())"
|
|
1622
|
+
return f"([&]() -> float {{ float {acc} = 0.0f; for (int32_t {idx} = 0; {idx} < {list_type.length}; ++{idx}) {acc} += {list_name}[{idx}]; return {acc}; }}())"
|
|
1623
|
+
if name == "analogWrite" and len(node.args) >= 2 and self._numeric is not None:
|
|
1624
|
+
if self._numeric.pwm_arg_needs_runtime_check(node.args[1]):
|
|
1625
|
+
args[1] = f"_bj_pwm_value({args[1]})"
|
|
1626
|
+
callee = self._resolved_name(node.func)
|
|
1627
|
+
return self._ordered_call(node, callee, args)
|
|
1628
|
+
if isinstance(node, ast.UnaryOp):
|
|
1629
|
+
operand = self._expr(node.operand)
|
|
1630
|
+
if isinstance(node.op, ast.USub):
|
|
1631
|
+
if isinstance(node.operand, ast.Constant) and node.operand.value == 2**31:
|
|
1632
|
+
return "(-2147483647L - 1L)"
|
|
1633
|
+
if self._numeric is not None and self._numeric.int_expr_needs_runtime_check(node):
|
|
1634
|
+
return f"_bj_i32_neg({operand})"
|
|
1635
|
+
return f"(-{operand})"
|
|
1636
|
+
return f"(!{operand})"
|
|
1637
|
+
if isinstance(node, ast.BinOp):
|
|
1638
|
+
return self._binary_expr(node)
|
|
1639
|
+
if isinstance(node, ast.Compare):
|
|
1640
|
+
return self._comparison_expr(node)
|
|
1641
|
+
if isinstance(node, ast.BoolOp):
|
|
1642
|
+
op = " && " if isinstance(node.op, ast.And) else " || "
|
|
1643
|
+
return "(" + op.join(self._expr(v) for v in node.values) + ")"
|
|
1644
|
+
if isinstance(node, ast.IfExp):
|
|
1645
|
+
return f"({self._expr(node.test)} ? {self._expr(node.body)} : {self._expr(node.orelse)})"
|
|
1646
|
+
if isinstance(node, ast.List):
|
|
1647
|
+
return "{" + ", ".join(self._expr(x) for x in node.elts) + "}"
|
|
1648
|
+
if isinstance(node, ast.Subscript):
|
|
1649
|
+
return self._subscript_lvalue(node)
|
|
1650
|
+
raise CompileError(f"Cannot generate expression {type(node).__name__}.", getattr(node, "lineno", None))
|
|
1651
|
+
|
|
1652
|
+
def _declaration_line(self, symbol) -> str:
|
|
1653
|
+
if self._typed is None:
|
|
1654
|
+
raise InternalCompilerError("Typed analysis is unavailable during declaration generation.")
|
|
1655
|
+
typ = self._typed.type_for_symbol(symbol)
|
|
1656
|
+
if typ.kind == TypeKind.FIXED_LIST:
|
|
1657
|
+
if typ.element_type is None or typ.length is None:
|
|
1658
|
+
raise InternalCompilerError("Incomplete fixed-list type reached declaration generation.")
|
|
1659
|
+
return f"{typ.element_type.cpp_type} {symbol.cpp_name}[{typ.length}];"
|
|
1660
|
+
return f"{typ.cpp_type} {symbol.cpp_name};"
|
|
1661
|
+
|
|
1662
|
+
def _subscript_lvalue(self, node: ast.Subscript) -> str:
|
|
1663
|
+
if self._typed is None:
|
|
1664
|
+
raise InternalCompilerError("Typed analysis is unavailable during list indexing.")
|
|
1665
|
+
if not isinstance(node.value, ast.Name):
|
|
1666
|
+
raise InternalCompilerError("V1 fixed-list indexing reached backend without a list name.", line=getattr(node, "lineno", None))
|
|
1667
|
+
list_type = self._typed.type_for_expr(node.value)
|
|
1668
|
+
if list_type.kind != TypeKind.FIXED_LIST or list_type.length is None:
|
|
1669
|
+
raise InternalCompilerError("Indexed value is not a fixed list.", line=getattr(node, "lineno", None))
|
|
1670
|
+
name = self._resolved_name(node.value)
|
|
1671
|
+
literal = self._literal_int(node.slice)
|
|
1672
|
+
if literal is not None:
|
|
1673
|
+
return f"{name}[{literal}]"
|
|
1674
|
+
return f"{name}[_bj_list_index({self._expr(node.slice)}, {list_type.length})]"
|
|
1675
|
+
|
|
1676
|
+
def _infer_expr_type(self, node: ast.AST) -> str:
|
|
1677
|
+
if self._typed is None:
|
|
1678
|
+
raise InternalCompilerError("Typed analysis is unavailable during expression generation.")
|
|
1679
|
+
try:
|
|
1680
|
+
return self._typed.type_for_expr(node).cpp_type
|
|
1681
|
+
except KeyError:
|
|
1682
|
+
raise InternalCompilerError(
|
|
1683
|
+
"An expression reached code generation without a resolved type.",
|
|
1684
|
+
line=getattr(node, "lineno", None),
|
|
1685
|
+
details=type(node).__name__,
|
|
1686
|
+
) from None
|
|
1687
|
+
|
|
1688
|
+
# ---------- helpers ----------
|
|
1689
|
+
@staticmethod
|
|
1690
|
+
def _literal_python_value(value: ast.AST):
|
|
1691
|
+
if isinstance(value, ast.Constant): return value.value
|
|
1692
|
+
if isinstance(value, ast.UnaryOp) and isinstance(value.op, ast.USub) and isinstance(value.operand, ast.Constant):
|
|
1693
|
+
return -value.operand.value
|
|
1694
|
+
return None
|
|
1695
|
+
|
|
1696
|
+
def _literal_pin(self, node: ast.AST) -> int | None:
|
|
1697
|
+
if isinstance(node, ast.Constant) and isinstance(node.value, int): return node.value
|
|
1698
|
+
if isinstance(node, ast.Name):
|
|
1699
|
+
if node.id in self.board.analog_aliases: return self.board.analog_aliases[node.id]
|
|
1700
|
+
v = self._global_values.get(node.id)
|
|
1701
|
+
return v if isinstance(v, int) else None
|
|
1702
|
+
return None
|
|
1703
|
+
|
|
1704
|
+
def _parse_range(self, node: ast.AST, owner: ast.AST, p: Program):
|
|
1705
|
+
if not isinstance(node, ast.Call) or not isinstance(node.func, ast.Name) or node.func.id != "range":
|
|
1706
|
+
raise CompileError("V1 for-loops use 'for x in range(...)'.", getattr(owner, "lineno", None))
|
|
1707
|
+
if node.keywords or not (1 <= len(node.args) <= 3):
|
|
1708
|
+
raise CompileError("range() accepts 1 to 3 positional values.", getattr(owner, "lineno", None))
|
|
1709
|
+
zero, one = ast.Constant(value=0), ast.Constant(value=1)
|
|
1710
|
+
if len(node.args) == 1: start, stop, step = zero, node.args[0], one
|
|
1711
|
+
elif len(node.args) == 2: start, stop, step = node.args[0], node.args[1], one
|
|
1712
|
+
else: start, stop, step = node.args
|
|
1713
|
+
for expr in (start, stop, step): self._validate_expr(expr, p)
|
|
1714
|
+
return start, stop, step
|
|
1715
|
+
|
|
1716
|
+
@staticmethod
|
|
1717
|
+
def _literal_int(node: ast.AST) -> int | None:
|
|
1718
|
+
if isinstance(node, ast.Constant) and isinstance(node.value, int) and not isinstance(node.value, bool): return node.value
|
|
1719
|
+
if isinstance(node, ast.UnaryOp) and isinstance(node.op, ast.USub) and isinstance(node.operand, ast.Constant) and isinstance(node.operand.value, int):
|
|
1720
|
+
return -node.operand.value
|
|
1721
|
+
return None
|