Barmaja-PyArduino 2.0.0__py3-none-any.whl

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (87) hide show
  1. Barmaja_PyArduino/BARMAJA_PyArduino.ico +0 -0
  2. Barmaja_PyArduino/__init__.py +528 -0
  3. Barmaja_PyArduino/__init__.pyi +331 -0
  4. Barmaja_PyArduino/__main__.py +2 -0
  5. Barmaja_PyArduino/adapters/__init__.py +35 -0
  6. Barmaja_PyArduino/api_registry.py +459 -0
  7. Barmaja_PyArduino/backend/__init__.py +3 -0
  8. Barmaja_PyArduino/backend/uno_cpp.py +1680 -0
  9. Barmaja_PyArduino/board.py +80 -0
  10. Barmaja_PyArduino/cli.py +223 -0
  11. Barmaja_PyArduino/compiler.py +1721 -0
  12. Barmaja_PyArduino/console.py +217 -0
  13. Barmaja_PyArduino/device_packs.py +367 -0
  14. Barmaja_PyArduino/device_packs_builtin/Button/arduino/Barmaja_Button/library.properties +9 -0
  15. Barmaja_PyArduino/device_packs_builtin/Button/arduino/Barmaja_Button/src/Barmaja_Button.h +18 -0
  16. Barmaja_PyArduino/device_packs_builtin/Button/example.py +10 -0
  17. Barmaja_PyArduino/device_packs_builtin/Button/pack.json +40 -0
  18. Barmaja_PyArduino/device_packs_builtin/ClockDS3231/arduino/Barmaja_ClockDS3231/library.properties +9 -0
  19. Barmaja_PyArduino/device_packs_builtin/ClockDS3231/arduino/Barmaja_ClockDS3231/src/Barmaja_ClockDS3231.h +20 -0
  20. Barmaja_PyArduino/device_packs_builtin/ClockDS3231/example.py +8 -0
  21. Barmaja_PyArduino/device_packs_builtin/ClockDS3231/pack.json +136 -0
  22. Barmaja_PyArduino/device_packs_builtin/DistanceVL53L0X/arduino/Barmaja_DistanceVL53L0X/library.properties +9 -0
  23. Barmaja_PyArduino/device_packs_builtin/DistanceVL53L0X/arduino/Barmaja_DistanceVL53L0X/src/Barmaja_DistanceVL53L0X.h +12 -0
  24. Barmaja_PyArduino/device_packs_builtin/DistanceVL53L0X/example.py +8 -0
  25. Barmaja_PyArduino/device_packs_builtin/DistanceVL53L0X/pack.json +48 -0
  26. Barmaja_PyArduino/device_packs_builtin/LCDParallel/arduino/Barmaja_LCDParallel/library.properties +9 -0
  27. Barmaja_PyArduino/device_packs_builtin/LCDParallel/arduino/Barmaja_LCDParallel/src/Barmaja_LCDParallel.h +17 -0
  28. Barmaja_PyArduino/device_packs_builtin/LCDParallel/example.py +10 -0
  29. Barmaja_PyArduino/device_packs_builtin/LCDParallel/pack.json +137 -0
  30. Barmaja_PyArduino/device_packs_builtin/LoadCellHX711/arduino/Barmaja_LoadCellHX711/library.properties +9 -0
  31. Barmaja_PyArduino/device_packs_builtin/LoadCellHX711/arduino/Barmaja_LoadCellHX711/src/Barmaja_LoadCellHX711.h +16 -0
  32. Barmaja_PyArduino/device_packs_builtin/LoadCellHX711/example.py +11 -0
  33. Barmaja_PyArduino/device_packs_builtin/LoadCellHX711/pack.json +74 -0
  34. Barmaja_PyArduino/device_packs_builtin/NeoPixelStrip/arduino/Barmaja_NeoPixelStrip/library.properties +9 -0
  35. Barmaja_PyArduino/device_packs_builtin/NeoPixelStrip/arduino/Barmaja_NeoPixelStrip/src/Barmaja_NeoPixelStrip.h +15 -0
  36. Barmaja_PyArduino/device_packs_builtin/NeoPixelStrip/example.py +13 -0
  37. Barmaja_PyArduino/device_packs_builtin/NeoPixelStrip/pack.json +114 -0
  38. Barmaja_PyArduino/device_packs_builtin/OLEDText/arduino/Barmaja_OLEDText/library.properties +9 -0
  39. Barmaja_PyArduino/device_packs_builtin/OLEDText/arduino/Barmaja_OLEDText/src/Barmaja_OLEDText.h +21 -0
  40. Barmaja_PyArduino/device_packs_builtin/OLEDText/example.py +9 -0
  41. Barmaja_PyArduino/device_packs_builtin/OLEDText/pack.json +103 -0
  42. Barmaja_PyArduino/device_packs_builtin/PressureBMP280/arduino/Barmaja_PressureBMP280/library.properties +9 -0
  43. Barmaja_PyArduino/device_packs_builtin/PressureBMP280/arduino/Barmaja_PressureBMP280/src/Barmaja_PressureBMP280.h +16 -0
  44. Barmaja_PyArduino/device_packs_builtin/PressureBMP280/example.py +8 -0
  45. Barmaja_PyArduino/device_packs_builtin/PressureBMP280/pack.json +92 -0
  46. Barmaja_PyArduino/device_packs_builtin/RotaryEncoder/arduino/Barmaja_RotaryEncoder/library.properties +9 -0
  47. Barmaja_PyArduino/device_packs_builtin/RotaryEncoder/arduino/Barmaja_RotaryEncoder/src/Barmaja_RotaryEncoder.h +18 -0
  48. Barmaja_PyArduino/device_packs_builtin/RotaryEncoder/example.py +10 -0
  49. Barmaja_PyArduino/device_packs_builtin/RotaryEncoder/pack.json +44 -0
  50. Barmaja_PyArduino/device_packs_builtin/ServoDriverPCA9685/arduino/Barmaja_ServoDriverPCA9685/library.properties +9 -0
  51. Barmaja_PyArduino/device_packs_builtin/ServoDriverPCA9685/arduino/Barmaja_ServoDriverPCA9685/src/Barmaja_ServoDriverPCA9685.h +15 -0
  52. Barmaja_PyArduino/device_packs_builtin/ServoDriverPCA9685/example.py +10 -0
  53. Barmaja_PyArduino/device_packs_builtin/ServoDriverPCA9685/pack.json +117 -0
  54. Barmaja_PyArduino/device_packs_builtin/ShiftRegister595/arduino/Barmaja_ShiftRegister595/library.properties +9 -0
  55. Barmaja_PyArduino/device_packs_builtin/ShiftRegister595/arduino/Barmaja_ShiftRegister595/src/Barmaja_ShiftRegister595.h +14 -0
  56. Barmaja_PyArduino/device_packs_builtin/ShiftRegister595/example.py +9 -0
  57. Barmaja_PyArduino/device_packs_builtin/ShiftRegister595/pack.json +83 -0
  58. Barmaja_PyArduino/device_packs_builtin/TemperatureDS18B20/arduino/Barmaja_TemperatureDS18B20/library.properties +9 -0
  59. Barmaja_PyArduino/device_packs_builtin/TemperatureDS18B20/arduino/Barmaja_TemperatureDS18B20/src/Barmaja_TemperatureDS18B20.h +16 -0
  60. Barmaja_PyArduino/device_packs_builtin/TemperatureDS18B20/example.py +8 -0
  61. Barmaja_PyArduino/device_packs_builtin/TemperatureDS18B20/pack.json +63 -0
  62. Barmaja_PyArduino/doctor.py +406 -0
  63. Barmaja_PyArduino/environment.py +595 -0
  64. Barmaja_PyArduino/errors.py +166 -0
  65. Barmaja_PyArduino/flow.py +754 -0
  66. Barmaja_PyArduino/frontend.py +611 -0
  67. Barmaja_PyArduino/installer.py +151 -0
  68. Barmaja_PyArduino/ir.py +660 -0
  69. Barmaja_PyArduino/library_adder.py +440 -0
  70. Barmaja_PyArduino/live.py +859 -0
  71. Barmaja_PyArduino/lowering.py +596 -0
  72. Barmaja_PyArduino/numeric.py +748 -0
  73. Barmaja_PyArduino/pack_builder.py +544 -0
  74. Barmaja_PyArduino/py.typed +0 -0
  75. Barmaja_PyArduino/serial_monitor.py +172 -0
  76. Barmaja_PyArduino/source.py +184 -0
  77. Barmaja_PyArduino/stubgen.py +148 -0
  78. Barmaja_PyArduino/symbols.py +646 -0
  79. Barmaja_PyArduino/target.py +1631 -0
  80. Barmaja_PyArduino/toolchain.py +506 -0
  81. Barmaja_PyArduino/typesys.py +917 -0
  82. Barmaja_PyArduino/workflow.py +213 -0
  83. barmaja_pyarduino-2.0.0.dist-info/METADATA +39 -0
  84. barmaja_pyarduino-2.0.0.dist-info/RECORD +87 -0
  85. barmaja_pyarduino-2.0.0.dist-info/WHEEL +5 -0
  86. barmaja_pyarduino-2.0.0.dist-info/licenses/LICENSE.txt +7 -0
  87. barmaja_pyarduino-2.0.0.dist-info/top_level.txt +1 -0
@@ -0,0 +1,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)