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,611 @@
|
|
|
1
|
+
from __future__ import annotations
|
|
2
|
+
|
|
3
|
+
import ast
|
|
4
|
+
from dataclasses import dataclass
|
|
5
|
+
|
|
6
|
+
from .errors import CompileError, UnsupportedFeatureError
|
|
7
|
+
|
|
8
|
+
from . import device_packs as _device_packs
|
|
9
|
+
from .api_registry import (
|
|
10
|
+
CORE_FUNCTIONS, HARDWARE_CLASSES, PUBLIC_API_NAMES, SINGLETONS, arity_range,
|
|
11
|
+
)
|
|
12
|
+
|
|
13
|
+
# Phase-B keeps structural validation, but all Arduino API arity/public-surface
|
|
14
|
+
# facts now come from the formal Step-12 registry. Language builtins remain
|
|
15
|
+
# compiler-language rules rather than Arduino API entries.
|
|
16
|
+
CORE_CALL_ARITY: dict[str, tuple[int, int | None]] = {
|
|
17
|
+
name: arity_range(spec.arities) for name, spec in CORE_FUNCTIONS.items()
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
BUILTIN_CALL_ARITY: dict[str, tuple[int, int | None]] = {
|
|
21
|
+
"round": (1, 2),
|
|
22
|
+
"min": (2, None),
|
|
23
|
+
"max": (2, None),
|
|
24
|
+
"abs": (1, 1),
|
|
25
|
+
"int": (1, 1),
|
|
26
|
+
"float": (1, 1),
|
|
27
|
+
"bool": (1, 1),
|
|
28
|
+
"len": (1, 1),
|
|
29
|
+
"sum": (1, 1),
|
|
30
|
+
"enumerate": (1, 2),
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
CONSTRUCTOR_ARITY: dict[str, tuple[int, int | None]] = {
|
|
34
|
+
name: arity_range(spec.constructor_arities) for name, spec in HARDWARE_CLASSES.items()
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
METHOD_ARITY: dict[str, dict[str, tuple[int, int | None]]] = {
|
|
38
|
+
**{
|
|
39
|
+
cls: {name: arity_range(method.arities) for name, method in spec.methods.items()}
|
|
40
|
+
for cls, spec in HARDWARE_CLASSES.items()
|
|
41
|
+
},
|
|
42
|
+
**{
|
|
43
|
+
owner: {name: arity_range(method.arities) for name, method in spec.methods.items()}
|
|
44
|
+
for owner, spec in SINGLETONS.items()
|
|
45
|
+
},
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
SUPPORTED_COMPARE_OPS = (ast.Eq, ast.NotEq, ast.Lt, ast.LtE, ast.Gt, ast.GtE)
|
|
49
|
+
SUPPORTED_BIN_OPS = (ast.Add, ast.Sub, ast.Mult, ast.Div, ast.Mod)
|
|
50
|
+
SUPPORTED_AUG_OPS = (ast.Add, ast.Sub, ast.Mult, ast.Div, ast.Mod)
|
|
51
|
+
SUPPORTED_UNARY_OPS = (ast.USub, ast.Not)
|
|
52
|
+
|
|
53
|
+
|
|
54
|
+
@dataclass(frozen=True)
|
|
55
|
+
class FrontendModule:
|
|
56
|
+
"""Phase-B result: structurally closed AST plus normalized import surface."""
|
|
57
|
+
|
|
58
|
+
tree: ast.Module
|
|
59
|
+
api_names: frozenset[str]
|
|
60
|
+
source_imports: tuple[str, ...]
|
|
61
|
+
main_loop: ast.While
|
|
62
|
+
|
|
63
|
+
|
|
64
|
+
def _loc(node: ast.AST) -> dict[str, int | None]:
|
|
65
|
+
return {
|
|
66
|
+
"line": getattr(node, "lineno", None),
|
|
67
|
+
"column": (getattr(node, "col_offset", None) + 1) if getattr(node, "col_offset", None) is not None else None,
|
|
68
|
+
"end_line": getattr(node, "end_lineno", None),
|
|
69
|
+
"end_column": getattr(node, "end_col_offset", None),
|
|
70
|
+
}
|
|
71
|
+
|
|
72
|
+
|
|
73
|
+
def _unsupported(message: str, node: ast.AST) -> UnsupportedFeatureError:
|
|
74
|
+
return UnsupportedFeatureError(message, **_loc(node))
|
|
75
|
+
|
|
76
|
+
|
|
77
|
+
def _compile_error(message: str, node: ast.AST) -> CompileError:
|
|
78
|
+
return CompileError(message, **_loc(node))
|
|
79
|
+
|
|
80
|
+
|
|
81
|
+
def _is_true(node: ast.AST) -> bool:
|
|
82
|
+
return isinstance(node, ast.Constant) and node.value is True
|
|
83
|
+
|
|
84
|
+
|
|
85
|
+
def _is_ellipsis_stmt(node: ast.stmt) -> bool:
|
|
86
|
+
return isinstance(node, ast.Expr) and isinstance(node.value, ast.Constant) and node.value.value is Ellipsis
|
|
87
|
+
|
|
88
|
+
|
|
89
|
+
def _is_docstring_stmt(node: ast.stmt) -> bool:
|
|
90
|
+
return isinstance(node, ast.Expr) and isinstance(node.value, ast.Constant) and isinstance(node.value.value, str)
|
|
91
|
+
|
|
92
|
+
|
|
93
|
+
def _is_literal_scalar(node: ast.AST) -> bool:
|
|
94
|
+
if isinstance(node, ast.UnaryOp) and isinstance(node.op, ast.USub):
|
|
95
|
+
node = node.operand
|
|
96
|
+
return isinstance(node, ast.Constant) and isinstance(node.value, (int, float)) and not isinstance(node.value, bool)
|
|
97
|
+
return isinstance(node, ast.Constant) and isinstance(node.value, (bool, int, float, str))
|
|
98
|
+
|
|
99
|
+
|
|
100
|
+
def is_noop_stmt(node: ast.stmt, *, docstring_allowed: bool = False) -> bool:
|
|
101
|
+
return isinstance(node, ast.Pass) or _is_ellipsis_stmt(node) or (docstring_allowed and _is_docstring_stmt(node))
|
|
102
|
+
|
|
103
|
+
|
|
104
|
+
def _arity_text(min_args: int, max_args: int | None) -> str:
|
|
105
|
+
if max_args is None:
|
|
106
|
+
return f"at least {min_args} positional arguments"
|
|
107
|
+
if min_args == max_args:
|
|
108
|
+
return f"{min_args} positional argument" + ("" if min_args == 1 else "s")
|
|
109
|
+
return f"{min_args} to {max_args} positional arguments"
|
|
110
|
+
|
|
111
|
+
|
|
112
|
+
|
|
113
|
+
|
|
114
|
+
def _check_registered_arities(name: str, node: ast.Call, arities: frozenset[int]) -> None:
|
|
115
|
+
if len(node.args) in arities:
|
|
116
|
+
return
|
|
117
|
+
allowed = sorted(arities)
|
|
118
|
+
if len(allowed) == 1:
|
|
119
|
+
text = f"{allowed[0]} positional argument" + ("" if allowed[0] == 1 else "s")
|
|
120
|
+
else:
|
|
121
|
+
text = "one of " + ", ".join(str(x) for x in allowed[:-1]) + f" or {allowed[-1]} positional arguments"
|
|
122
|
+
raise _compile_error(f"{name}() accepts {text}.", node)
|
|
123
|
+
|
|
124
|
+
def _check_arity(name: str, node: ast.Call, rule: tuple[int, int | None]) -> None:
|
|
125
|
+
min_args, max_args = rule
|
|
126
|
+
count = len(node.args)
|
|
127
|
+
if count < min_args or (max_args is not None and count > max_args):
|
|
128
|
+
raise _compile_error(f"{name}() accepts {_arity_text(min_args, max_args)} in Barmaja PyArduino V1.", node)
|
|
129
|
+
|
|
130
|
+
|
|
131
|
+
class FrontendValidator:
|
|
132
|
+
"""Closed structural validator for the 0.6.0 frontend subset.
|
|
133
|
+
|
|
134
|
+
This phase deliberately does not resolve ordinary student names or infer
|
|
135
|
+
types. Those belong to Steps 5+. Its job is to guarantee that every AST
|
|
136
|
+
shape passed onward has an explicit later-phase path and cannot surprise the
|
|
137
|
+
current analyzer/backend with an unknown node/operator/call shape.
|
|
138
|
+
"""
|
|
139
|
+
|
|
140
|
+
def __init__(self) -> None:
|
|
141
|
+
self.api_names: set[str] = set()
|
|
142
|
+
self.source_imports: list[str] = []
|
|
143
|
+
self.function_names: set[str] = set()
|
|
144
|
+
self.object_classes: dict[str, str] = {}
|
|
145
|
+
self._created_objects: dict[str, str] = {}
|
|
146
|
+
|
|
147
|
+
def validate(self, tree: ast.Module) -> FrontendModule:
|
|
148
|
+
main_loop: ast.While | None = None
|
|
149
|
+
main_loop_seen = False
|
|
150
|
+
|
|
151
|
+
# Pre-collect declaration shapes so a function body may structurally
|
|
152
|
+
# reference a hardware object/function declared later at module level.
|
|
153
|
+
# Execution-order/name availability is resolved in Step 5.
|
|
154
|
+
for node in tree.body:
|
|
155
|
+
if isinstance(node, ast.FunctionDef):
|
|
156
|
+
self.function_names.add(node.name)
|
|
157
|
+
if (
|
|
158
|
+
isinstance(node, ast.Assign)
|
|
159
|
+
and len(node.targets) == 1
|
|
160
|
+
and isinstance(node.targets[0], ast.Name)
|
|
161
|
+
and isinstance(node.value, ast.Call)
|
|
162
|
+
and isinstance(node.value.func, ast.Name)
|
|
163
|
+
and node.value.func.id in CONSTRUCTOR_ARITY
|
|
164
|
+
):
|
|
165
|
+
self.object_classes[node.targets[0].id] = node.value.func.id
|
|
166
|
+
|
|
167
|
+
for index, node in enumerate(tree.body):
|
|
168
|
+
if isinstance(node, ast.ImportFrom):
|
|
169
|
+
if main_loop_seen:
|
|
170
|
+
raise _compile_error("Nothing may follow the main top-level 'while True:' loop.", node)
|
|
171
|
+
self._validate_import_from(node)
|
|
172
|
+
continue
|
|
173
|
+
if isinstance(node, ast.Import):
|
|
174
|
+
raise _unsupported(
|
|
175
|
+
"Desktop/module imports are not supported in Arduino projects. Use 'from Barmaja_PyArduino import ...'.",
|
|
176
|
+
node,
|
|
177
|
+
)
|
|
178
|
+
if isinstance(node, (ast.AsyncFunctionDef, ast.ClassDef)):
|
|
179
|
+
if isinstance(node, ast.ClassDef):
|
|
180
|
+
raise _unsupported("Classes are not supported in Barmaja PyArduino V1. Use simple functions instead.", node)
|
|
181
|
+
raise _unsupported("Async functions are not supported in Barmaja PyArduino V1.", node)
|
|
182
|
+
if isinstance(node, ast.FunctionDef):
|
|
183
|
+
if main_loop_seen:
|
|
184
|
+
raise _compile_error("Nothing may follow the main top-level 'while True:' loop.", node)
|
|
185
|
+
self._validate_function(node)
|
|
186
|
+
continue
|
|
187
|
+
if isinstance(node, ast.While) and _is_true(node.test):
|
|
188
|
+
if main_loop_seen:
|
|
189
|
+
raise _compile_error("Only one top-level 'while True:' main loop is allowed.", node)
|
|
190
|
+
if node.orelse:
|
|
191
|
+
raise _unsupported("The main 'while True:' loop cannot use an else block.", node)
|
|
192
|
+
main_loop = node
|
|
193
|
+
main_loop_seen = True
|
|
194
|
+
self._validate_block(node.body, context="main", docstring_allowed=False)
|
|
195
|
+
continue
|
|
196
|
+
if main_loop_seen:
|
|
197
|
+
raise _compile_error("Nothing may follow the main top-level 'while True:' loop.", node)
|
|
198
|
+
|
|
199
|
+
# Module docstring is accepted only in the actual docstring slot.
|
|
200
|
+
if is_noop_stmt(node, docstring_allowed=True):
|
|
201
|
+
continue
|
|
202
|
+
|
|
203
|
+
# Recognize hardware-object construction before generic assignment
|
|
204
|
+
# validation so the object method surface can be checked later.
|
|
205
|
+
if isinstance(node, ast.Assign) and self._register_hardware_object(node):
|
|
206
|
+
continue
|
|
207
|
+
|
|
208
|
+
self._validate_stmt(node, context="setup", docstring_allowed=False)
|
|
209
|
+
|
|
210
|
+
if main_loop is None:
|
|
211
|
+
raise CompileError("Arduino projects require one top-level 'while True:' main loop.")
|
|
212
|
+
|
|
213
|
+
return FrontendModule(
|
|
214
|
+
tree=tree,
|
|
215
|
+
api_names=frozenset(self.api_names),
|
|
216
|
+
source_imports=tuple(self.source_imports),
|
|
217
|
+
main_loop=main_loop,
|
|
218
|
+
)
|
|
219
|
+
|
|
220
|
+
def _validate_import_from(self, node: ast.ImportFrom) -> None:
|
|
221
|
+
if node.level != 0 or node.module != "Barmaja_PyArduino":
|
|
222
|
+
raise _unsupported(
|
|
223
|
+
"Arduino projects may only import from 'Barmaja_PyArduino'.",
|
|
224
|
+
node,
|
|
225
|
+
)
|
|
226
|
+
for alias in node.names:
|
|
227
|
+
if alias.asname is not None:
|
|
228
|
+
raise _unsupported("Import aliases using 'as' are not supported in Barmaja PyArduino V1.", node)
|
|
229
|
+
if alias.name == "*":
|
|
230
|
+
self.api_names.update(PUBLIC_API_NAMES)
|
|
231
|
+
self.source_imports.append("*")
|
|
232
|
+
continue
|
|
233
|
+
if alias.name not in PUBLIC_API_NAMES:
|
|
234
|
+
raise _compile_error(f"'{alias.name}' is not a public Barmaja PyArduino API name.", node)
|
|
235
|
+
self.api_names.add(alias.name)
|
|
236
|
+
self.source_imports.append(alias.name)
|
|
237
|
+
|
|
238
|
+
def _validate_function(self, fn: ast.FunctionDef) -> None:
|
|
239
|
+
if fn.decorator_list:
|
|
240
|
+
raise _unsupported("Decorators are not supported in Barmaja PyArduino V1.", fn)
|
|
241
|
+
args = fn.args
|
|
242
|
+
if args.defaults or any(d is not None for d in args.kw_defaults):
|
|
243
|
+
raise _unsupported("Default function parameters are not supported in Barmaja PyArduino V1.", fn)
|
|
244
|
+
if args.vararg or args.kwarg or args.kwonlyargs or args.posonlyargs:
|
|
245
|
+
raise _unsupported("Use simple positional function parameters in Barmaja PyArduino V1.", fn)
|
|
246
|
+
if fn.returns is not None or any(a.annotation is not None for a in args.args):
|
|
247
|
+
raise _unsupported("Python type annotations are not supported in Barmaja PyArduino V1 source yet.", fn)
|
|
248
|
+
self._validate_block(fn.body, context="function", docstring_allowed=True)
|
|
249
|
+
|
|
250
|
+
def _register_hardware_object(self, node: ast.Assign) -> bool:
|
|
251
|
+
if (
|
|
252
|
+
len(node.targets) == 1
|
|
253
|
+
and isinstance(node.targets[0], ast.Name)
|
|
254
|
+
and isinstance(node.value, ast.Call)
|
|
255
|
+
and isinstance(node.value.func, ast.Name)
|
|
256
|
+
and node.value.func.id in CONSTRUCTOR_ARITY
|
|
257
|
+
):
|
|
258
|
+
name = node.targets[0].id
|
|
259
|
+
cls = node.value.func.id
|
|
260
|
+
self._validate_call(node.value, allow_fstring=False, constructor_allowed=True)
|
|
261
|
+
if name in self._created_objects:
|
|
262
|
+
raise _compile_error(
|
|
263
|
+
f"'{name}' already holds a hardware object ({self._created_objects[name]}). Create each hardware object once, "
|
|
264
|
+
"with its own variable name.",
|
|
265
|
+
node,
|
|
266
|
+
)
|
|
267
|
+
self._created_objects[name] = cls
|
|
268
|
+
self.object_classes[name] = cls
|
|
269
|
+
return True
|
|
270
|
+
return False
|
|
271
|
+
|
|
272
|
+
def _validate_block(self, body: list[ast.stmt], *, context: str, docstring_allowed: bool) -> None:
|
|
273
|
+
for index, stmt in enumerate(body):
|
|
274
|
+
self._validate_stmt(stmt, context=context, docstring_allowed=(docstring_allowed and index == 0))
|
|
275
|
+
|
|
276
|
+
def _validate_stmt(self, node: ast.stmt, *, context: str, docstring_allowed: bool = False) -> None:
|
|
277
|
+
if is_noop_stmt(node, docstring_allowed=docstring_allowed):
|
|
278
|
+
return
|
|
279
|
+
if _is_docstring_stmt(node):
|
|
280
|
+
raise _unsupported("Standalone string expressions are not supported here.", node)
|
|
281
|
+
|
|
282
|
+
if isinstance(node, ast.Expr):
|
|
283
|
+
if not isinstance(node.value, ast.Call):
|
|
284
|
+
raise _unsupported("Only supported function or Arduino method calls may be standalone statements here.", node)
|
|
285
|
+
self._validate_call(node.value, allow_fstring=False)
|
|
286
|
+
return
|
|
287
|
+
|
|
288
|
+
if isinstance(node, ast.Assign):
|
|
289
|
+
if len(node.targets) != 1:
|
|
290
|
+
raise _unsupported("Chained assignment is not supported in Barmaja PyArduino V1.", node)
|
|
291
|
+
target = node.targets[0]
|
|
292
|
+
if isinstance(target, ast.Name):
|
|
293
|
+
self._validate_expr(node.value)
|
|
294
|
+
return
|
|
295
|
+
if isinstance(target, ast.Subscript):
|
|
296
|
+
self._validate_expr(target.value)
|
|
297
|
+
if isinstance(target.slice, ast.Slice):
|
|
298
|
+
raise _unsupported("List slicing is not supported in Barmaja PyArduino V1.", target)
|
|
299
|
+
self._validate_expr(target.slice)
|
|
300
|
+
self._validate_expr(node.value)
|
|
301
|
+
return
|
|
302
|
+
if context == "setup" and isinstance(target, (ast.Tuple, ast.List)) and isinstance(node.value, (ast.Tuple, ast.List)):
|
|
303
|
+
# Preserve the existing top-level literal tuple-unpack form only.
|
|
304
|
+
if len(target.elts) != len(node.value.elts) or not all(isinstance(t, ast.Name) for t in target.elts):
|
|
305
|
+
raise _unsupported("Tuple assignment must use the same number of simple variable names and values.", node)
|
|
306
|
+
for value in node.value.elts:
|
|
307
|
+
self._validate_expr(value)
|
|
308
|
+
if not _is_literal_scalar(value):
|
|
309
|
+
# Python evaluates the whole right side before binding
|
|
310
|
+
# (`a, b = b, a` swaps). V1 only lowers literal values here.
|
|
311
|
+
raise _unsupported(
|
|
312
|
+
"Tuple assignment in Barmaja PyArduino V1 supports literal values only, for example "
|
|
313
|
+
"R, G, B = 9, 10, 11. Use one assignment per line (with a temporary variable to swap values).",
|
|
314
|
+
node,
|
|
315
|
+
)
|
|
316
|
+
return
|
|
317
|
+
raise _unsupported("Assignment targets must be simple variable names in Barmaja PyArduino V1.", node)
|
|
318
|
+
|
|
319
|
+
if isinstance(node, ast.AnnAssign):
|
|
320
|
+
raise _unsupported("Annotated assignments such as 'x: int = ...' are not supported in Barmaja PyArduino V1.", node)
|
|
321
|
+
|
|
322
|
+
if isinstance(node, ast.AugAssign):
|
|
323
|
+
if not isinstance(node.target, ast.Name):
|
|
324
|
+
raise _unsupported("Augmented assignment requires a simple variable name.", node)
|
|
325
|
+
if not isinstance(node.op, SUPPORTED_AUG_OPS):
|
|
326
|
+
raise _unsupported("This augmented-assignment operator is not supported in Barmaja PyArduino V1.", node)
|
|
327
|
+
self._validate_expr(node.value)
|
|
328
|
+
return
|
|
329
|
+
|
|
330
|
+
if isinstance(node, ast.If):
|
|
331
|
+
self._validate_expr(node.test)
|
|
332
|
+
self._validate_block(node.body, context=context, docstring_allowed=False)
|
|
333
|
+
self._validate_block(node.orelse, context=context, docstring_allowed=False)
|
|
334
|
+
return
|
|
335
|
+
|
|
336
|
+
if isinstance(node, ast.While):
|
|
337
|
+
if node.orelse:
|
|
338
|
+
raise _unsupported("'while ... else' is not supported in Barmaja PyArduino V1.", node)
|
|
339
|
+
self._validate_expr(node.test)
|
|
340
|
+
self._validate_block(node.body, context=context, docstring_allowed=False)
|
|
341
|
+
return
|
|
342
|
+
|
|
343
|
+
if isinstance(node, ast.For):
|
|
344
|
+
if node.orelse:
|
|
345
|
+
raise _unsupported("'for ... else' is not supported in Barmaja PyArduino V1.", node)
|
|
346
|
+
if isinstance(node.iter, ast.Call) and isinstance(node.iter.func, ast.Name) and node.iter.func.id == "range":
|
|
347
|
+
if not isinstance(node.target, ast.Name):
|
|
348
|
+
raise _unsupported("range() loop target must be a simple name.", node)
|
|
349
|
+
self._validate_range(node.iter, node)
|
|
350
|
+
elif isinstance(node.iter, ast.Call) and isinstance(node.iter.func, ast.Name) and node.iter.func.id == "enumerate":
|
|
351
|
+
if not (isinstance(node.target, (ast.Tuple, ast.List)) and len(node.target.elts) == 2 and all(isinstance(x, ast.Name) for x in node.target.elts)):
|
|
352
|
+
raise _unsupported("enumerate() loop target must be exactly two simple names: 'for index, value in enumerate(items)'.", node)
|
|
353
|
+
if not (1 <= len(node.iter.args) <= 2) or not isinstance(node.iter.args[0], ast.Name):
|
|
354
|
+
raise _unsupported("enumerate() in V1 requires a fixed-list variable name and optional Int start.", node.iter)
|
|
355
|
+
self._validate_expr(node.iter.args[0])
|
|
356
|
+
if len(node.iter.args) == 2:
|
|
357
|
+
self._validate_expr(node.iter.args[1])
|
|
358
|
+
else:
|
|
359
|
+
if not isinstance(node.target, ast.Name):
|
|
360
|
+
raise _unsupported("Fixed-list loop target must be a simple name.", node)
|
|
361
|
+
if not isinstance(node.iter, ast.Name):
|
|
362
|
+
raise _unsupported("Fixed-list iteration in V1 requires a list variable name.", node.iter)
|
|
363
|
+
self._validate_expr(node.iter)
|
|
364
|
+
self._validate_block(node.body, context=context, docstring_allowed=False)
|
|
365
|
+
return
|
|
366
|
+
|
|
367
|
+
if isinstance(node, ast.Return):
|
|
368
|
+
if context != "function":
|
|
369
|
+
raise _unsupported("return may only be used inside a function.", node)
|
|
370
|
+
if node.value is not None:
|
|
371
|
+
self._validate_expr(node.value)
|
|
372
|
+
return
|
|
373
|
+
|
|
374
|
+
if isinstance(node, ast.Global):
|
|
375
|
+
if context != "function":
|
|
376
|
+
raise _unsupported("The 'global' statement is supported only inside user functions.", node)
|
|
377
|
+
if not node.names:
|
|
378
|
+
raise _unsupported("global requires at least one module variable name.", node)
|
|
379
|
+
# Binding, existence, ordering and protected-name rules are Phase C.
|
|
380
|
+
return
|
|
381
|
+
if isinstance(node, ast.Nonlocal):
|
|
382
|
+
raise _unsupported("'nonlocal' is not supported because nested functions are outside Barmaja PyArduino V1.", node)
|
|
383
|
+
if isinstance(node, (ast.Break, ast.Continue)):
|
|
384
|
+
# Step 6 flow analysis resolves the actual target. In particular,
|
|
385
|
+
# continue may target the special Arduino main loop, while break
|
|
386
|
+
# may not terminate that special loop.
|
|
387
|
+
return
|
|
388
|
+
|
|
389
|
+
messages: tuple[tuple[type[ast.AST], str], ...] = (
|
|
390
|
+
(ast.ClassDef, "Classes are not supported in Barmaja PyArduino V1."),
|
|
391
|
+
(ast.Try, "try/except/finally is not supported in Barmaja PyArduino V1."),
|
|
392
|
+
(ast.With, "with-statements are not supported in Barmaja PyArduino V1."),
|
|
393
|
+
(ast.Raise, "raise is not supported in Barmaja PyArduino V1."),
|
|
394
|
+
(ast.Assert, "assert is not supported in Barmaja PyArduino V1."),
|
|
395
|
+
(ast.Delete, "del is not supported in Barmaja PyArduino V1."),
|
|
396
|
+
(ast.Match, "match/case is not supported in Barmaja PyArduino V1."),
|
|
397
|
+
(ast.AsyncFor, "async programming is not supported in Barmaja PyArduino V1."),
|
|
398
|
+
(ast.AsyncWith, "async programming is not supported in Barmaja PyArduino V1."),
|
|
399
|
+
)
|
|
400
|
+
for cls, message in messages:
|
|
401
|
+
if isinstance(node, cls):
|
|
402
|
+
raise _unsupported(message, node)
|
|
403
|
+
raise _unsupported("This Python statement is not supported in Barmaja PyArduino V1.", node)
|
|
404
|
+
|
|
405
|
+
def _validate_expr(self, node: ast.AST, *, allow_fstring: bool = False) -> None:
|
|
406
|
+
if isinstance(node, ast.Constant):
|
|
407
|
+
if node.value is None:
|
|
408
|
+
raise _unsupported("None is not supported as an Arduino value in Barmaja PyArduino V1.", node)
|
|
409
|
+
if node.value is Ellipsis:
|
|
410
|
+
raise _unsupported("Ellipsis (...) is only supported as a no-op statement.", node)
|
|
411
|
+
if not isinstance(node.value, (bool, int, float, str)):
|
|
412
|
+
raise _unsupported("This literal value type is not supported in Barmaja PyArduino V1.", node)
|
|
413
|
+
if isinstance(node.value, str):
|
|
414
|
+
try:
|
|
415
|
+
node.value.encode("utf-8")
|
|
416
|
+
except UnicodeEncodeError:
|
|
417
|
+
raise _unsupported(
|
|
418
|
+
"This string contains a character that cannot be sent to the Arduino (an unpaired surrogate such as '\\ud800').",
|
|
419
|
+
node,
|
|
420
|
+
) from None
|
|
421
|
+
return
|
|
422
|
+
if isinstance(node, ast.Name):
|
|
423
|
+
return
|
|
424
|
+
if isinstance(node, ast.JoinedStr):
|
|
425
|
+
if not allow_fstring:
|
|
426
|
+
raise _unsupported("f-strings are supported only in print output in Barmaja PyArduino V1.", node)
|
|
427
|
+
self._validate_fstring(node)
|
|
428
|
+
return
|
|
429
|
+
if isinstance(node, ast.FormattedValue):
|
|
430
|
+
raise _unsupported("Formatted values are supported only as part of a print f-string.", node)
|
|
431
|
+
if isinstance(node, ast.Call):
|
|
432
|
+
self._validate_call(node, allow_fstring=False)
|
|
433
|
+
return
|
|
434
|
+
if isinstance(node, ast.UnaryOp):
|
|
435
|
+
if not isinstance(node.op, SUPPORTED_UNARY_OPS):
|
|
436
|
+
raise _unsupported("This unary operator is not supported in Barmaja PyArduino V1.", node)
|
|
437
|
+
self._validate_expr(node.operand)
|
|
438
|
+
return
|
|
439
|
+
if isinstance(node, ast.BinOp):
|
|
440
|
+
if not isinstance(node.op, SUPPORTED_BIN_OPS):
|
|
441
|
+
friendly = "Floor division (//), powers (**), shifts, and bitwise operators are not supported in Barmaja PyArduino V1."
|
|
442
|
+
raise _unsupported(friendly, node)
|
|
443
|
+
self._validate_expr(node.left)
|
|
444
|
+
self._validate_expr(node.right)
|
|
445
|
+
return
|
|
446
|
+
if isinstance(node, ast.Compare):
|
|
447
|
+
for op in node.ops:
|
|
448
|
+
if not isinstance(op, SUPPORTED_COMPARE_OPS):
|
|
449
|
+
raise _unsupported("Only ==, !=, <, <=, >, and >= comparisons are supported in Barmaja PyArduino V1.", node)
|
|
450
|
+
self._validate_expr(node.left)
|
|
451
|
+
for comparator in node.comparators:
|
|
452
|
+
self._validate_expr(comparator)
|
|
453
|
+
return
|
|
454
|
+
if isinstance(node, ast.BoolOp):
|
|
455
|
+
if not isinstance(node.op, (ast.And, ast.Or)):
|
|
456
|
+
raise _unsupported("This boolean operator is not supported in Barmaja PyArduino V1.", node)
|
|
457
|
+
for value in node.values:
|
|
458
|
+
self._validate_expr(value)
|
|
459
|
+
return
|
|
460
|
+
if isinstance(node, ast.IfExp):
|
|
461
|
+
self._validate_expr(node.test)
|
|
462
|
+
self._validate_expr(node.body)
|
|
463
|
+
self._validate_expr(node.orelse)
|
|
464
|
+
return
|
|
465
|
+
|
|
466
|
+
if isinstance(node, ast.List):
|
|
467
|
+
if not node.elts:
|
|
468
|
+
raise _unsupported("Empty lists are not supported because a fixed element type cannot be inferred.", node)
|
|
469
|
+
for item in node.elts:
|
|
470
|
+
self._validate_expr(item)
|
|
471
|
+
return
|
|
472
|
+
if isinstance(node, ast.Tuple):
|
|
473
|
+
raise _unsupported("Tuple values are not supported in Barmaja PyArduino V1 except for supported unpacking/enumerate targets.", node)
|
|
474
|
+
if isinstance(node, ast.Subscript):
|
|
475
|
+
if isinstance(node.slice, ast.Slice):
|
|
476
|
+
raise _unsupported("List slicing is not supported in Barmaja PyArduino V1.", node)
|
|
477
|
+
if not isinstance(node.value, ast.Name):
|
|
478
|
+
raise _unsupported("V1 indexing requires a fixed-list variable name.", node)
|
|
479
|
+
self._validate_expr(node.value)
|
|
480
|
+
self._validate_expr(node.slice)
|
|
481
|
+
return
|
|
482
|
+
if isinstance(node, (ast.Dict, ast.Set)):
|
|
483
|
+
raise _unsupported("Dictionaries and sets are not supported in Barmaja PyArduino V1.", node)
|
|
484
|
+
if isinstance(node, (ast.ListComp, ast.SetComp, ast.DictComp, ast.GeneratorExp)):
|
|
485
|
+
raise _unsupported("Comprehensions and generator expressions are not supported in Barmaja PyArduino V1.", node)
|
|
486
|
+
if isinstance(node, ast.Lambda):
|
|
487
|
+
raise _unsupported("lambda functions are not supported in Barmaja PyArduino V1. Use a normal def function.", node)
|
|
488
|
+
if isinstance(node, ast.NamedExpr):
|
|
489
|
+
raise _unsupported("The ':=' assignment expression is not supported in Barmaja PyArduino V1.", node)
|
|
490
|
+
if isinstance(node, (ast.Yield, ast.YieldFrom, ast.Await)):
|
|
491
|
+
raise _unsupported("Generators and async expressions are not supported in Barmaja PyArduino V1.", node)
|
|
492
|
+
if isinstance(node, ast.Attribute):
|
|
493
|
+
raise _unsupported("Attribute access is supported only for registered Arduino method calls.", node)
|
|
494
|
+
raise _unsupported("This Python expression is not supported in Barmaja PyArduino V1.", node)
|
|
495
|
+
|
|
496
|
+
def _validate_fstring(self, node: ast.JoinedStr) -> None:
|
|
497
|
+
for part in node.values:
|
|
498
|
+
if isinstance(part, ast.Constant) and isinstance(part.value, str):
|
|
499
|
+
self._validate_expr(part) # same literal checks as ordinary strings
|
|
500
|
+
continue
|
|
501
|
+
if not isinstance(part, ast.FormattedValue):
|
|
502
|
+
raise _unsupported("This f-string form is not supported in Barmaja PyArduino V1.", part)
|
|
503
|
+
if part.conversion != -1:
|
|
504
|
+
raise _unsupported("f-string conversions such as !r, !s, and !a are not supported in Barmaja PyArduino V1.", part)
|
|
505
|
+
self._validate_expr(part.value)
|
|
506
|
+
if part.format_spec is None:
|
|
507
|
+
continue
|
|
508
|
+
if not isinstance(part.format_spec, ast.JoinedStr):
|
|
509
|
+
raise _unsupported("Only fixed '.Nf' f-string precision is supported in Barmaja PyArduino V1.", part)
|
|
510
|
+
text_parts: list[str] = []
|
|
511
|
+
for spec_part in part.format_spec.values:
|
|
512
|
+
if not isinstance(spec_part, ast.Constant) or not isinstance(spec_part.value, str):
|
|
513
|
+
raise _unsupported("Dynamic f-string format specifications are not supported in Barmaja PyArduino V1.", part)
|
|
514
|
+
text_parts.append(spec_part.value)
|
|
515
|
+
text = "".join(text_parts)
|
|
516
|
+
if not (len(text) >= 3 and text[0] == "." and text[-1] == "f" and text[1:-1].isdigit()):
|
|
517
|
+
raise _unsupported("Only fixed '.Nf' f-string precision, such as {value:.1f}, is supported in Barmaja PyArduino V1.", part)
|
|
518
|
+
if not (text[1:-1].isascii() and 0 <= int(text[1:-1]) <= 6):
|
|
519
|
+
raise _unsupported("f-string precision '.Nf' supports N from 0 to 6 in Barmaja PyArduino V1, for example {value:.2f}.", part)
|
|
520
|
+
|
|
521
|
+
def _validate_call(self, node: ast.Call, *, allow_fstring: bool, constructor_allowed: bool = False) -> None:
|
|
522
|
+
if any(isinstance(arg, ast.Starred) for arg in node.args):
|
|
523
|
+
raise _unsupported("Starred call arguments (*args) are not supported in Barmaja PyArduino V1.", node)
|
|
524
|
+
if node.keywords:
|
|
525
|
+
raise _unsupported("Keyword arguments are not supported for this call in Barmaja PyArduino V1.", node)
|
|
526
|
+
|
|
527
|
+
if isinstance(node.func, ast.Name):
|
|
528
|
+
name = node.func.id
|
|
529
|
+
if name in CONSTRUCTOR_ARITY:
|
|
530
|
+
if not constructor_allowed:
|
|
531
|
+
raise _unsupported(f"{name}() hardware objects must be created once as a top-level variable.", node)
|
|
532
|
+
_check_registered_arities(name, node, HARDWARE_CLASSES[name].constructor_arities)
|
|
533
|
+
elif name == "print":
|
|
534
|
+
for arg in node.args:
|
|
535
|
+
self._validate_expr(arg, allow_fstring=True)
|
|
536
|
+
return
|
|
537
|
+
elif name in CORE_CALL_ARITY:
|
|
538
|
+
_check_registered_arities(name, node, CORE_FUNCTIONS[name].arities)
|
|
539
|
+
elif name == "enumerate":
|
|
540
|
+
raise _unsupported("enumerate() is supported only directly in a fixed-list for-loop.", node)
|
|
541
|
+
elif name in BUILTIN_CALL_ARITY:
|
|
542
|
+
_check_arity(name, node, BUILTIN_CALL_ARITY[name])
|
|
543
|
+
if name in {"len", "sum"} and node.args and not isinstance(node.args[0], ast.Name):
|
|
544
|
+
raise _unsupported(f"{name}() in V1 requires a fixed-list variable name.", node)
|
|
545
|
+
elif name in self.function_names:
|
|
546
|
+
# Function-call arity is resolved after all definitions are
|
|
547
|
+
# collected in the symbol/type phases. The shape is safe here.
|
|
548
|
+
pass
|
|
549
|
+
elif name == "range":
|
|
550
|
+
raise _unsupported("range() is supported only as a for-loop iterator.", node)
|
|
551
|
+
elif name == "enumerate":
|
|
552
|
+
raise _unsupported("enumerate() is supported only as a fixed-list for-loop iterator.", node)
|
|
553
|
+
else:
|
|
554
|
+
# Step 5 decides whether an ordinary name is a function/API or
|
|
555
|
+
# undefined. Keep this node structurally valid for that phase.
|
|
556
|
+
pass
|
|
557
|
+
for arg in node.args:
|
|
558
|
+
self._validate_expr(arg, allow_fstring=allow_fstring)
|
|
559
|
+
return
|
|
560
|
+
|
|
561
|
+
if isinstance(node.func, ast.Attribute) and isinstance(node.func.value, ast.Name):
|
|
562
|
+
owner = node.func.value.id
|
|
563
|
+
method = node.func.attr
|
|
564
|
+
cls = self.object_classes.get(owner)
|
|
565
|
+
if cls is None and owner in SINGLETONS:
|
|
566
|
+
cls = owner
|
|
567
|
+
if cls is None:
|
|
568
|
+
raise _unsupported("Method calls are supported only on registered Barmaja Arduino objects.", node)
|
|
569
|
+
methods = METHOD_ARITY[cls]
|
|
570
|
+
if method not in methods:
|
|
571
|
+
raise _unsupported(f"{cls}.{method}() is not supported in Barmaja PyArduino V1.", node)
|
|
572
|
+
_check_registered_arities(
|
|
573
|
+
f"{owner}.{method}", node,
|
|
574
|
+
(HARDWARE_CLASSES[cls].methods[method].arities if cls in HARDWARE_CLASSES else SINGLETONS[cls].methods[method].arities),
|
|
575
|
+
)
|
|
576
|
+
method_allows_fstring = (
|
|
577
|
+
(cls == "LiquidCrystal_I2C" and method == "print")
|
|
578
|
+
or (cls == "Serial" and method in {"print", "println"})
|
|
579
|
+
or (method in {"print", "println"} and cls in _device_packs.LOADED)
|
|
580
|
+
)
|
|
581
|
+
for arg in node.args:
|
|
582
|
+
self._validate_expr(arg, allow_fstring=method_allows_fstring)
|
|
583
|
+
return
|
|
584
|
+
|
|
585
|
+
raise _unsupported("Only direct function calls and registered Arduino method calls are supported.", node)
|
|
586
|
+
|
|
587
|
+
def _validate_range(self, node: ast.AST, owner: ast.For) -> None:
|
|
588
|
+
if not isinstance(node, ast.Call) or not isinstance(node.func, ast.Name) or node.func.id != "range":
|
|
589
|
+
raise _unsupported("V1 for-loops use 'for name in range(...)'.", owner)
|
|
590
|
+
if node.keywords or any(isinstance(arg, ast.Starred) for arg in node.args):
|
|
591
|
+
raise _unsupported("range() uses positional values only in Barmaja PyArduino V1.", node)
|
|
592
|
+
if not (1 <= len(node.args) <= 3):
|
|
593
|
+
raise _compile_error("range() accepts 1 to 3 positional values.", node)
|
|
594
|
+
for arg in node.args:
|
|
595
|
+
self._validate_expr(arg)
|
|
596
|
+
if len(node.args) == 3:
|
|
597
|
+
step = node.args[2]
|
|
598
|
+
literal_step = None
|
|
599
|
+
node_step = step
|
|
600
|
+
sign = 1
|
|
601
|
+
while isinstance(node_step, ast.UnaryOp) and isinstance(node_step.op, ast.USub):
|
|
602
|
+
sign = -sign
|
|
603
|
+
node_step = node_step.operand
|
|
604
|
+
if isinstance(node_step, ast.Constant) and isinstance(node_step.value, int) and not isinstance(node_step.value, bool):
|
|
605
|
+
literal_step = sign * node_step.value
|
|
606
|
+
if literal_step == 0:
|
|
607
|
+
raise _compile_error("range() step cannot be zero.", step)
|
|
608
|
+
|
|
609
|
+
|
|
610
|
+
def validate_frontend(tree: ast.Module) -> FrontendModule:
|
|
611
|
+
return FrontendValidator().validate(tree)
|