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,1680 @@
|
|
|
1
|
+
from __future__ import annotations
|
|
2
|
+
|
|
3
|
+
import re
|
|
4
|
+
from dataclasses import dataclass, fields, is_dataclass
|
|
5
|
+
from typing import Any
|
|
6
|
+
|
|
7
|
+
from ..errors import InternalCompilerError
|
|
8
|
+
from ..flow import StorageClass
|
|
9
|
+
from ..ir import (
|
|
10
|
+
IRAugStore, IRBinary, IRBinaryOp, IRBool, IRBoolOp, IRBreak, IRCall, IRCallKind,
|
|
11
|
+
IRCompareChain, IRCompareOp, IRConditional, IRConstant, IRContinue, IREffect,
|
|
12
|
+
IRExpr, IRExprStmt, IRFixedListLoop, IRFormatString, IRFormattedValue, IRFunction,
|
|
13
|
+
IRIf, IRIndex, IRIndexStore, IRListLiteral, IRLoad, IRLoopControlTarget, IRModule,
|
|
14
|
+
IRPrint, IRRangeLoop, IRReturn, IRStmt, IRStore, IRSymbolRef,
|
|
15
|
+
IRTempRef, IRUnary, IRUnaryOp, IRWhile, validate_ir,
|
|
16
|
+
)
|
|
17
|
+
from ..source import SourceSpan
|
|
18
|
+
from ..symbols import SymbolKind
|
|
19
|
+
from ..target import TargetDiagnostic, TargetPlan
|
|
20
|
+
from ..typesys import TypeKind
|
|
21
|
+
|
|
22
|
+
|
|
23
|
+
@dataclass(frozen=True)
|
|
24
|
+
class SourceMapEntry:
|
|
25
|
+
cpp_line: int
|
|
26
|
+
source_name: str
|
|
27
|
+
span: SourceSpan
|
|
28
|
+
|
|
29
|
+
|
|
30
|
+
@dataclass(frozen=True)
|
|
31
|
+
class GeneratedSketch:
|
|
32
|
+
cpp: str
|
|
33
|
+
source_map: tuple[SourceMapEntry, ...]
|
|
34
|
+
dependency_ids: tuple[str, ...]
|
|
35
|
+
resolved_serial_baud: int | None
|
|
36
|
+
diagnostics: tuple[TargetDiagnostic, ...]
|
|
37
|
+
|
|
38
|
+
|
|
39
|
+
@dataclass(frozen=True)
|
|
40
|
+
class _OutputPart:
|
|
41
|
+
literal: str | None = None
|
|
42
|
+
expression: str | None = None
|
|
43
|
+
type_kind: TypeKind | None = None
|
|
44
|
+
precision: int | None = None
|
|
45
|
+
|
|
46
|
+
|
|
47
|
+
# _bj_numeric_fail() halts the Uno. Its loop MUST contain a side effect and stay out of
|
|
48
|
+
# line: with the pinned avr-gcc 7.3.0 + -Os -flto, an empty `for (;;) {}` let GCC's
|
|
49
|
+
# control-dependent DCE delete the non-failing path after an inlined check (WA-26).
|
|
50
|
+
_NUMERIC_HELPER_BLOB ='static void __attribute__((noinline, noreturn)) _bj_numeric_fail() {\n for (;;) { __asm__ __volatile__(""); }\n}\nstatic int32_t _bj_list_index(int32_t index, int32_t length) {\n if (index < 0 || index >= length) _bj_numeric_fail();\n return index;\n}\nstatic int32_t _bj_i32_add(int32_t a, int32_t b) {\n int64_t r = (int64_t)a + (int64_t)b;\n if (r < (-2147483647LL - 1LL) || r > 2147483647LL) _bj_numeric_fail();\n return (int32_t)r;\n}\nstatic int32_t _bj_i32_sub(int32_t a, int32_t b) {\n int64_t r = (int64_t)a - (int64_t)b;\n if (r < (-2147483647LL - 1LL) || r > 2147483647LL) _bj_numeric_fail();\n return (int32_t)r;\n}\nstatic int32_t _bj_i32_mul(int32_t a, int32_t b) {\n int64_t r = (int64_t)a * (int64_t)b;\n if (r < (-2147483647LL - 1LL) || r > 2147483647LL) _bj_numeric_fail();\n return (int32_t)r;\n}\nstatic int32_t _bj_i32_neg(int32_t value) {\n if (value == (-2147483647L - 1L)) _bj_numeric_fail();\n return -value;\n}\nstatic float _bj_true_div_f32(float a, float b) {\n if (b == 0.0f) _bj_numeric_fail();\n return a / b;\n}\nstatic int32_t _bj_mod_i32(int32_t a, int32_t b) {\n if (b == 0) _bj_numeric_fail();\n if (a == (-2147483647L - 1L) && b == -1) return 0;\n int32_t r = a % b;\n if (r != 0 && ((r < 0) != (b < 0))) r += b;\n return r;\n}\nstatic float _bj_mod_f32(float a, float b) {\n if (isnan(a) || isnan(b) || isinf(a) || isinf(b) || b == 0.0f) _bj_numeric_fail();\n float r = fmodf(a, b);\n if (r != 0.0f && ((r < 0.0f) != (b < 0.0f))) r += b;\n if (r == 0.0f) r = copysignf(0.0f, b);\n return r;\n}\nstatic int32_t _bj_digital_pin(int32_t pin) {\n if (pin < 0 || pin > 19) _bj_numeric_fail();\n return pin;\n}\nstatic int32_t _bj_pwm_pin(int32_t pin) {\n if (!(pin == 3 || pin == 5 || pin == 6 || pin == 9 || pin == 10 || pin == 11)) _bj_numeric_fail();\n return pin;\n}\nstatic int32_t _bj_analog_pin(int32_t pin) {\n if (!((pin >= 0 && pin <= 5) || (pin >= 14 && pin <= 19))) _bj_numeric_fail();\n return pin;\n}\nstatic int32_t _bj_servo_angle(int32_t value) {\n if (value < 0 || value > 180) _bj_numeric_fail();\n return value;\n}\nstatic int32_t _bj_servo_pulse(int32_t value) {\n if (value < 0 || value > 3000) _bj_numeric_fail();\n return value;\n}\nstatic void _bj_servo_attach_bounds(int32_t min_us, int32_t max_us) {\n int64_t dmin = 544LL - (int64_t)min_us; if (dmin < 0) dmin = -dmin;\n int64_t dmax = 2400LL - (int64_t)max_us; if (dmax < 0) dmax = -dmax;\n if (min_us <= 0 || min_us >= max_us || dmin / 4LL >= 128LL || dmax / 4LL >= 128LL) _bj_numeric_fail();\n}\nstatic int32_t _bj_pwm_value(int32_t value) {\n if (value < 0 || value > 255) _bj_numeric_fail();\n return value;\n}\nstatic int32_t _bj_byte_value(int32_t value) {\n if (value < 0 || value > 255) _bj_numeric_fail();\n return value;\n}\nstatic int32_t _bj_digital_level(int32_t value) {\n if (value < 0 || value > 1) _bj_numeric_fail();\n return value;\n}\nstatic int32_t _bj_pin_mode(int32_t value) {\n if (!(value == 0 || value == 1 || value == 2)) _bj_numeric_fail();\n return value;\n}\nstatic int32_t _bj_i2c_address(int32_t value) {\n if (value < 0 || value > 127) _bj_numeric_fail();\n return value;\n}\nstatic int32_t _bj_arduino_format(int32_t value) {\n if (value < 0 || value > 255) _bj_numeric_fail();\n return value;\n}\nstatic int32_t _bj_wire_count(int32_t value) {\n if (value < 0 || value > 255) _bj_numeric_fail();\n return value;\n}\nstatic int32_t _bj_nonnegative_int(int32_t value) {\n if (value < 0) _bj_numeric_fail();\n return value;\n}\nstatic int32_t _bj_lcd_dimension(int32_t value) {\n if (value < 1 || value > 255) _bj_numeric_fail();\n return value;\n}\nstatic int32_t _bj_lcd_charsize(int32_t value) {\n if (!(value == 0 || value == 4)) _bj_numeric_fail();\n return value;\n}\nstatic int32_t _bj_lcd_cursor_column(int32_t value) {\n if (value < 0 || value > 255) _bj_numeric_fail();\n return value;\n}\nstatic int32_t _bj_lcd_cursor_row(int32_t value) {\n if (value < 0 || value > 3) _bj_numeric_fail();\n return value;\n}\nstatic int32_t _bj_positive_int(int32_t value) {\n if (value <= 0) _bj_numeric_fail();\n return value;\n}\nstatic uint32_t _bj_time_threshold(int32_t value) {\n if (value < 0) _bj_numeric_fail();\n return (uint32_t)value;\n}\nstatic int32_t _bj_float_to_i32(float value) {\n if (isnan(value) || isinf(value) || value < -2147483648.0f || value >= 2147483648.0f) _bj_numeric_fail();\n return (int32_t)value;\n}\nstatic float _bj_round_even_f32(float value) {\n float base = floorf(value);\n float frac = value - base;\n if (frac > 0.5f) return base + 1.0f;\n if (frac < 0.5f) return base;\n return (fmodf(fabsf(base), 2.0f) == 0.0f) ? base : (base + 1.0f);\n}\nstatic int32_t _bj_round_i32(float value) {\n if (isnan(value) || isinf(value) || value < -2147483648.0f || value >= 2147483648.0f) _bj_numeric_fail();\n float rounded = _bj_round_even_f32(value);\n if (rounded < -2147483648.0f || rounded >= 2147483648.0f) _bj_numeric_fail();\n return (int32_t)rounded;\n}\nstatic __attribute__((noinline)) uint64_t _bj_round_ratio_even_u64(uint64_t num, uint64_t den) {\n uint64_t q = num / den;\n uint64_t r = num % den;\n uint64_t twice = r * 2ULL;\n if (twice > den || (twice == den && (q & 1ULL))) ++q;\n return q;\n}\nstatic __attribute__((noinline)) uint64_t _bj_round_pow2_even_u64(uint64_t num, uint8_t shift) {\n if (shift == 0) return num;\n if (shift >= 64) return 0;\n uint64_t q = num >> shift;\n uint64_t mask = (1ULL << shift) - 1ULL;\n uint64_t r = num & mask;\n uint64_t half = 1ULL << (shift - 1);\n if (r > half || (r == half && (q & 1ULL))) ++q;\n return q;\n}\nstatic __attribute__((noinline)) float _bj_ratio_to_f32(bool negative, uint64_t num, uint64_t den) {\n union { uint32_t u; float f; } out;\n if (num == 0) { out.u = negative ? 0x80000000UL : 0UL; return out.f; }\n int16_t exponent = 0;\n if (num >= den) {\n uint64_t scaled_den = den;\n while (num >= (scaled_den << 1)) { scaled_den <<= 1; ++exponent; }\n } else {\n uint64_t scaled_num = num;\n while (scaled_num < den) { scaled_num <<= 1; --exponent; }\n }\n int16_t shift = 23 - exponent;\n uint64_t scaled_num = num;\n uint64_t scaled_den = den;\n if (shift >= 0) scaled_num <<= shift; else scaled_den <<= -shift;\n uint64_t significand = _bj_round_ratio_even_u64(scaled_num, scaled_den);\n if (significand == (1ULL << 24)) { significand >>= 1; ++exponent; }\n uint32_t exponent_bits = (uint32_t)(exponent + 127);\n if (exponent_bits == 0 || exponent_bits >= 255) _bj_numeric_fail();\n out.u = (negative ? 0x80000000UL : 0UL) | (exponent_bits << 23) | ((uint32_t)significand & 0x7FFFFFUL);\n return out.f;\n}\nstatic float _bj_round_f32_digits(float value, int8_t ndigits) {\n if (isnan(value) || isinf(value) || value == 0.0f) return value;\n union { float f; uint32_t u; } bits; bits.f = value;\n bool negative = (bits.u & 0x80000000UL) != 0;\n uint32_t exp_bits = (bits.u >> 23) & 0xFFUL;\n uint32_t fraction = bits.u & 0x7FFFFFUL;\n uint64_t mantissa; int16_t exponent2; int16_t unbiased;\n if (exp_bits == 0) { mantissa = fraction; exponent2 = -149; unbiased = -126; }\n else { mantissa = (1UL << 23) | fraction; unbiased = (int16_t)exp_bits - 127; exponent2 = unbiased - 23; }\n static const uint32_t p5[7] = {1UL, 5UL, 25UL, 125UL, 625UL, 3125UL, 15625UL};\n static const uint32_t p10[7] = {1UL, 10UL, 100UL, 1000UL, 10000UL, 100000UL, 1000000UL};\n if (ndigits >= 0) {\n if (unbiased >= 23) return value;\n uint8_t n = (uint8_t)ndigits;\n uint64_t mag = mantissa * (uint64_t)p5[n];\n int16_t shift2 = exponent2 + n;\n uint64_t rounded;\n if (shift2 >= 0) rounded = mag << shift2;\n else rounded = _bj_round_pow2_even_u64(mag, (uint8_t)(-shift2));\n return _bj_ratio_to_f32(negative, rounded, p10[n]);\n }\n uint8_t n = (uint8_t)(-ndigits);\n if (unbiased >= 43) return value;\n int16_t shift2 = exponent2 - n;\n uint64_t rounded;\n if (shift2 >= 0) {\n uint64_t numerator = mantissa << shift2;\n rounded = _bj_round_ratio_even_u64(numerator, p5[n]);\n } else {\n uint8_t down = (uint8_t)(-shift2);\n if (down >= 24) rounded = 0;\n else rounded = _bj_round_ratio_even_u64(mantissa, ((uint64_t)p5[n]) << down);\n }\n return _bj_ratio_to_f32(negative, rounded * (uint64_t)p10[n], 1UL);\n}\nstatic int32_t _bj_round_i32_digits(int32_t value, int8_t ndigits) {\n if (ndigits >= 0) return value;\n static const int32_t p10[7] = {1L, 10L, 100L, 1000L, 10000L, 100000L, 1000000L};\n int64_t scale = p10[-ndigits];\n int64_t q = ((int64_t)value) / scale;\n int64_t r = ((int64_t)value) % scale;\n int64_t ar = r < 0 ? -r : r;\n int64_t twice = ar * 2LL;\n if (twice > scale || (twice == scale && (q % 2LL) != 0LL)) q += value < 0 ? -1LL : 1LL;\n int64_t result = q * scale;\n if (result < (-2147483647LL - 1LL) || result > 2147483647LL) _bj_numeric_fail();\n return (int32_t)result;\n}\nstatic int32_t _bj_i32_abs(int32_t value) {\n if (value == (-2147483647L - 1L)) _bj_numeric_fail();\n return value < 0 ? -value : value;\n}\nstatic bool _bj_str_eq(const char* a, const char* b) {\n while (*a && *b && *a == *b) { ++a; ++b; }\n return *a == *b;\n}\n'
|
|
51
|
+
_OUTPUT_HELPER_PREAMBLE = 'typedef struct { uint16_t limb[12]; } _bj_big192;'
|
|
52
|
+
_OUTPUT_HELPER_BLOB = r'''static __attribute__((noinline)) uint64_t _bj_pow5_u64(uint8_t n) {
|
|
53
|
+
uint64_t r = 1ULL;
|
|
54
|
+
while (n--) r *= 5ULL;
|
|
55
|
+
return r;
|
|
56
|
+
}
|
|
57
|
+
static __attribute__((noinline)) float _bj_f32_scale2(float value, int16_t shift) {
|
|
58
|
+
// Exact multiplication by 2^shift for normal results (replaces ldexp).
|
|
59
|
+
union { float f; uint32_t u; } bits; bits.f = value;
|
|
60
|
+
int16_t e = (int16_t)((bits.u >> 23) & 0xFFUL) + shift;
|
|
61
|
+
if (e <= 0 || e >= 255) return value * 0.0f + (shift > 0 ? INFINITY : 0.0f);
|
|
62
|
+
bits.u = (bits.u & 0x807FFFFFUL) | ((uint32_t)e << 23);
|
|
63
|
+
return bits.f;
|
|
64
|
+
}
|
|
65
|
+
static __attribute__((noinline)) bool _bj_f32_ge_pow10(uint64_t mantissa, int16_t exponent2, int16_t k) {
|
|
66
|
+
// Exact test of mantissa * 2^exponent2 >= 10^k for -9 <= k <= 16.
|
|
67
|
+
if (k >= 0) {
|
|
68
|
+
if (k > 16) return false;
|
|
69
|
+
uint64_t p5 = _bj_pow5_u64((uint8_t)k);
|
|
70
|
+
int16_t t = exponent2 - k; /* mantissa * 2^t >= 5^k */
|
|
71
|
+
if (t >= 0) return t >= 40 || (mantissa << t) >= p5;
|
|
72
|
+
if (-t > 25) return false;
|
|
73
|
+
return mantissa >= (p5 << (-t));
|
|
74
|
+
}
|
|
75
|
+
int16_t u = -k;
|
|
76
|
+
if (u > 16) return true;
|
|
77
|
+
uint64_t lhs = mantissa * _bj_pow5_u64((uint8_t)u); /* mantissa * 5^u * 2^(exponent2+u) >= 1 */
|
|
78
|
+
int16_t t = exponent2 + u;
|
|
79
|
+
if (t >= 0) return true;
|
|
80
|
+
if (-t >= 63) return false;
|
|
81
|
+
return lhs >= (1ULL << (-t));
|
|
82
|
+
}
|
|
83
|
+
static bool __attribute__((noinline)) _bj_f32_shortest(float value, char* digits, uint8_t* count, int16_t* exp10) {
|
|
84
|
+
// Exact shortest round-trip decimal for float32 in 1e-7 <= |value| < 1e16.
|
|
85
|
+
union { float f; uint32_t u; } bits; bits.f = value;
|
|
86
|
+
bool negative = (bits.u & 0x80000000UL) != 0;
|
|
87
|
+
uint32_t exp_bits = (bits.u >> 23) & 0xFFUL;
|
|
88
|
+
if (exp_bits == 0 || exp_bits == 0xFFUL) return false;
|
|
89
|
+
float magnitude = fabsf(value);
|
|
90
|
+
if (!(magnitude >= 1e-7f && magnitude < 1e16f)) return false;
|
|
91
|
+
uint64_t mantissa = (1ULL << 23) | (bits.u & 0x7FFFFFUL);
|
|
92
|
+
int16_t exponent2 = (int16_t)exp_bits - 150;
|
|
93
|
+
// Exact decimal exponent: 10^k <= |value| < 10^(k+1); integer log10(2) estimate then correction.
|
|
94
|
+
int16_t k_estimate = (int16_t)(((int32_t)(exponent2 + 23) * 1233L) >> 12);
|
|
95
|
+
for (uint8_t fix = 0; fix < 6; ++fix) {
|
|
96
|
+
if (!_bj_f32_ge_pow10(mantissa, exponent2, k_estimate)) { --k_estimate; continue; }
|
|
97
|
+
if (_bj_f32_ge_pow10(mantissa, exponent2, (int16_t)(k_estimate + 1))) { ++k_estimate; continue; }
|
|
98
|
+
break;
|
|
99
|
+
}
|
|
100
|
+
uint32_t low = 1UL; /* 10^(precision-1) */
|
|
101
|
+
for (uint8_t precision = 1; precision <= 9; ++precision, low *= 10UL) {
|
|
102
|
+
uint64_t high = (uint64_t)low * 10ULL; /* 10^precision */
|
|
103
|
+
int16_t k = k_estimate;
|
|
104
|
+
uint64_t n = 0;
|
|
105
|
+
bool ok = false;
|
|
106
|
+
for (uint8_t attempt = 0; attempt < 4; ++attempt) {
|
|
107
|
+
int16_t s = (int16_t)precision - 1 - k;
|
|
108
|
+
if (s > 16 || s < -16) break;
|
|
109
|
+
if (s >= 0) {
|
|
110
|
+
uint64_t scaled = mantissa * _bj_pow5_u64((uint8_t)s);
|
|
111
|
+
int16_t t = exponent2 + s;
|
|
112
|
+
if (t >= 0) { if (t > 30) break; n = scaled << t; }
|
|
113
|
+
else n = _bj_round_pow2_even_u64(scaled, (uint8_t)((-t) > 64 ? 64 : (-t)));
|
|
114
|
+
} else {
|
|
115
|
+
uint8_t u = (uint8_t)(-s);
|
|
116
|
+
int16_t t = exponent2 - u;
|
|
117
|
+
if (t >= 0) { if (t > 30) break; n = _bj_round_ratio_even_u64(mantissa << t, _bj_pow5_u64(u)); }
|
|
118
|
+
else { int16_t shift = -t; if (shift > 25) break; n = _bj_round_ratio_even_u64(mantissa, _bj_pow5_u64(u) << shift); }
|
|
119
|
+
}
|
|
120
|
+
if (n >= high) { ++k; continue; }
|
|
121
|
+
if (n < low) { --k; continue; }
|
|
122
|
+
ok = true;
|
|
123
|
+
break;
|
|
124
|
+
}
|
|
125
|
+
if (!ok) continue;
|
|
126
|
+
int16_t s = (int16_t)precision - 1 - k;
|
|
127
|
+
float back = (s >= 0)
|
|
128
|
+
? _bj_f32_scale2(_bj_ratio_to_f32(negative, n, _bj_pow5_u64((uint8_t)s)), (int16_t)(-s))
|
|
129
|
+
: _bj_f32_scale2(_bj_ratio_to_f32(negative, n * _bj_pow5_u64((uint8_t)(-s)), 1ULL), (int16_t)(-s));
|
|
130
|
+
if (back == value) {
|
|
131
|
+
uint32_t x = (uint32_t)n;
|
|
132
|
+
for (int8_t i = (int8_t)precision - 1; i >= 0; --i) { digits[i] = (char)('0' + (x % 10UL)); x /= 10UL; }
|
|
133
|
+
*count = precision;
|
|
134
|
+
*exp10 = k;
|
|
135
|
+
return true;
|
|
136
|
+
}
|
|
137
|
+
}
|
|
138
|
+
return false;
|
|
139
|
+
}
|
|
140
|
+
// Full-range exact fallback for float32. The AVR libc decimal conversion
|
|
141
|
+
// routines are not correctly-rounded enough for shortest-repr decisions at
|
|
142
|
+
// the exponent extremes, so this path uses integer arithmetic and IEEE-754 interval
|
|
143
|
+
// boundaries. 12 base-2^16 limbs (192 bits) are sufficient for every
|
|
144
|
+
// binary32 value scaled by the powers of five needed for 1..9 decimal digits.
|
|
145
|
+
static void _bj_big_zero(_bj_big192* a) {
|
|
146
|
+
for (uint8_t i = 0; i < 12; ++i) a->limb[i] = 0;
|
|
147
|
+
}
|
|
148
|
+
static void _bj_big_from_u32(_bj_big192* a, uint32_t value) {
|
|
149
|
+
_bj_big_zero(a);
|
|
150
|
+
a->limb[0] = (uint16_t)(value & 0xFFFFUL);
|
|
151
|
+
a->limb[1] = (uint16_t)(value >> 16);
|
|
152
|
+
}
|
|
153
|
+
static void _bj_big_copy(_bj_big192* dst, const _bj_big192* src) {
|
|
154
|
+
for (uint8_t i = 0; i < 12; ++i) dst->limb[i] = src->limb[i];
|
|
155
|
+
}
|
|
156
|
+
static void _bj_big_mul5(_bj_big192* a, uint8_t count) {
|
|
157
|
+
while (count--) {
|
|
158
|
+
uint32_t carry = 0;
|
|
159
|
+
for (uint8_t i = 0; i < 12; ++i) {
|
|
160
|
+
uint32_t v = (uint32_t)a->limb[i] * 5UL + carry;
|
|
161
|
+
a->limb[i] = (uint16_t)(v & 0xFFFFUL);
|
|
162
|
+
carry = v >> 16;
|
|
163
|
+
}
|
|
164
|
+
}
|
|
165
|
+
}
|
|
166
|
+
static void _bj_big_shl1(_bj_big192* a) {
|
|
167
|
+
uint16_t carry = 0;
|
|
168
|
+
for (uint8_t i = 0; i < 12; ++i) {
|
|
169
|
+
uint16_t next = (uint16_t)(a->limb[i] >> 15);
|
|
170
|
+
a->limb[i] = (uint16_t)((a->limb[i] << 1) | carry);
|
|
171
|
+
carry = next;
|
|
172
|
+
}
|
|
173
|
+
}
|
|
174
|
+
static void _bj_big_shr1(_bj_big192* a) {
|
|
175
|
+
uint16_t carry = 0;
|
|
176
|
+
for (int8_t i = 11; i >= 0; --i) {
|
|
177
|
+
uint16_t next = (uint16_t)(a->limb[i] & 1U);
|
|
178
|
+
a->limb[i] = (uint16_t)((a->limb[i] >> 1) | (uint16_t)(carry << 15));
|
|
179
|
+
carry = next;
|
|
180
|
+
}
|
|
181
|
+
}
|
|
182
|
+
static void _bj_big_shl(_bj_big192* a, uint16_t bits) {
|
|
183
|
+
while (bits--) _bj_big_shl1(a);
|
|
184
|
+
}
|
|
185
|
+
static void _bj_big_shr(_bj_big192* a, uint16_t bits) {
|
|
186
|
+
while (bits--) _bj_big_shr1(a);
|
|
187
|
+
}
|
|
188
|
+
static int8_t _bj_big_cmp(const _bj_big192* a, const _bj_big192* b) {
|
|
189
|
+
for (int8_t i = 11; i >= 0; --i) {
|
|
190
|
+
if (a->limb[i] < b->limb[i]) return -1;
|
|
191
|
+
if (a->limb[i] > b->limb[i]) return 1;
|
|
192
|
+
}
|
|
193
|
+
return 0;
|
|
194
|
+
}
|
|
195
|
+
static void _bj_big_sub(_bj_big192* a, const _bj_big192* b) {
|
|
196
|
+
uint32_t borrow = 0;
|
|
197
|
+
for (uint8_t i = 0; i < 12; ++i) {
|
|
198
|
+
uint32_t av = a->limb[i];
|
|
199
|
+
uint32_t bv = (uint32_t)b->limb[i] + borrow;
|
|
200
|
+
if (av >= bv) { a->limb[i] = (uint16_t)(av - bv); borrow = 0; }
|
|
201
|
+
else { a->limb[i] = (uint16_t)(0x10000UL + av - bv); borrow = 1; }
|
|
202
|
+
}
|
|
203
|
+
}
|
|
204
|
+
static uint16_t _bj_big_bitlen(const _bj_big192* a) {
|
|
205
|
+
for (int8_t i = 11; i >= 0; --i) {
|
|
206
|
+
uint16_t x = a->limb[i];
|
|
207
|
+
if (x) {
|
|
208
|
+
uint8_t n = 0;
|
|
209
|
+
while (x) { x >>= 1; ++n; }
|
|
210
|
+
return (uint16_t)((uint16_t)i * 16U + n);
|
|
211
|
+
}
|
|
212
|
+
}
|
|
213
|
+
return 0;
|
|
214
|
+
}
|
|
215
|
+
static bool _bj_big_to_u32(const _bj_big192* a, uint32_t* out) {
|
|
216
|
+
for (uint8_t i = 2; i < 12; ++i) if (a->limb[i]) return false;
|
|
217
|
+
*out = (uint32_t)a->limb[0] | ((uint32_t)a->limb[1] << 16);
|
|
218
|
+
return true;
|
|
219
|
+
}
|
|
220
|
+
static bool _bj_big_bit(const _bj_big192* a, uint16_t bit) {
|
|
221
|
+
if (bit >= 192) return false;
|
|
222
|
+
return ((a->limb[bit >> 4] >> (bit & 15)) & 1U) != 0;
|
|
223
|
+
}
|
|
224
|
+
static bool _bj_big_any_low_bits(const _bj_big192* a, uint16_t count) {
|
|
225
|
+
if (count > 192) count = 192;
|
|
226
|
+
for (uint16_t bit = 0; bit < count; ++bit) if (_bj_big_bit(a, bit)) return true;
|
|
227
|
+
return false;
|
|
228
|
+
}
|
|
229
|
+
static bool _bj_big_round_pow2_u32(const _bj_big192* input, uint16_t shift, uint32_t* out) {
|
|
230
|
+
if (shift >= 193) { *out = 0; return true; }
|
|
231
|
+
bool half = shift ? _bj_big_bit(input, (uint16_t)(shift - 1)) : false;
|
|
232
|
+
bool sticky = shift > 1 ? _bj_big_any_low_bits(input, (uint16_t)(shift - 1)) : false;
|
|
233
|
+
_bj_big192 q; _bj_big_copy(&q, input);
|
|
234
|
+
_bj_big_shr(&q, shift);
|
|
235
|
+
uint32_t value;
|
|
236
|
+
if (!_bj_big_to_u32(&q, &value)) return false;
|
|
237
|
+
if (half && (sticky || (value & 1UL))) ++value;
|
|
238
|
+
*out = value;
|
|
239
|
+
return true;
|
|
240
|
+
}
|
|
241
|
+
static bool _bj_big_div_round_u32(const _bj_big192* numerator, const _bj_big192* denominator, uint32_t* out) {
|
|
242
|
+
uint16_t den_bits = _bj_big_bitlen(denominator);
|
|
243
|
+
if (den_bits == 0) return false;
|
|
244
|
+
_bj_big192 rem; _bj_big_copy(&rem, numerator);
|
|
245
|
+
uint32_t q = 0;
|
|
246
|
+
uint16_t num_bits = _bj_big_bitlen(&rem);
|
|
247
|
+
if (num_bits >= den_bits) {
|
|
248
|
+
uint16_t shift = (uint16_t)(num_bits - den_bits);
|
|
249
|
+
_bj_big192 d; _bj_big_copy(&d, denominator); _bj_big_shl(&d, shift);
|
|
250
|
+
for (int16_t i = (int16_t)shift; i >= 0; --i) {
|
|
251
|
+
if (_bj_big_cmp(&rem, &d) >= 0) {
|
|
252
|
+
_bj_big_sub(&rem, &d);
|
|
253
|
+
if (i >= 32) return false;
|
|
254
|
+
q |= (1UL << i);
|
|
255
|
+
}
|
|
256
|
+
_bj_big_shr1(&d);
|
|
257
|
+
}
|
|
258
|
+
}
|
|
259
|
+
_bj_big192 twice; _bj_big_copy(&twice, &rem); _bj_big_shl1(&twice);
|
|
260
|
+
int8_t cmp = _bj_big_cmp(&twice, denominator);
|
|
261
|
+
if (cmp > 0 || (cmp == 0 && (q & 1UL))) ++q;
|
|
262
|
+
*out = q;
|
|
263
|
+
return true;
|
|
264
|
+
}
|
|
265
|
+
static int8_t _bj_dec_vs_binary(uint32_t n, int16_t scale10, uint32_t mantissa, int16_t exponent2) {
|
|
266
|
+
// Compare n * 10^(-scale10) with mantissa * 2^exponent2 exactly.
|
|
267
|
+
_bj_big192 a, b;
|
|
268
|
+
_bj_big_from_u32(&a, n);
|
|
269
|
+
_bj_big_from_u32(&b, mantissa);
|
|
270
|
+
if (scale10 >= 0) {
|
|
271
|
+
_bj_big_mul5(&b, (uint8_t)scale10);
|
|
272
|
+
int16_t q = (int16_t)(exponent2 + scale10);
|
|
273
|
+
if (q >= 0) _bj_big_shl(&b, (uint16_t)q);
|
|
274
|
+
else _bj_big_shl(&a, (uint16_t)(-q));
|
|
275
|
+
} else {
|
|
276
|
+
uint8_t t = (uint8_t)(-scale10);
|
|
277
|
+
_bj_big_mul5(&a, t);
|
|
278
|
+
int16_t q = (int16_t)(exponent2 - t);
|
|
279
|
+
if (q >= 0) _bj_big_shl(&b, (uint16_t)q);
|
|
280
|
+
else _bj_big_shl(&a, (uint16_t)(-q));
|
|
281
|
+
}
|
|
282
|
+
return _bj_big_cmp(&a, &b);
|
|
283
|
+
}
|
|
284
|
+
static bool _bj_scaled_decimal_round(uint32_t mantissa, int16_t exponent2, int16_t scale10, uint32_t* out) {
|
|
285
|
+
// Round mantissa * 2^exponent2 * 10^scale10 to the nearest integer, ties-to-even.
|
|
286
|
+
if (scale10 >= 0) {
|
|
287
|
+
_bj_big192 a; _bj_big_from_u32(&a, mantissa); _bj_big_mul5(&a, (uint8_t)scale10);
|
|
288
|
+
int16_t q = (int16_t)(exponent2 + scale10);
|
|
289
|
+
if (q >= 0) { _bj_big_shl(&a, (uint16_t)q); return _bj_big_to_u32(&a, out); }
|
|
290
|
+
return _bj_big_round_pow2_u32(&a, (uint16_t)(-q), out);
|
|
291
|
+
}
|
|
292
|
+
uint8_t t = (uint8_t)(-scale10);
|
|
293
|
+
_bj_big192 num, den;
|
|
294
|
+
_bj_big_from_u32(&num, mantissa);
|
|
295
|
+
_bj_big_from_u32(&den, 1UL); _bj_big_mul5(&den, t);
|
|
296
|
+
int16_t q = (int16_t)(exponent2 - t);
|
|
297
|
+
if (q >= 0) _bj_big_shl(&num, (uint16_t)q);
|
|
298
|
+
else _bj_big_shl(&den, (uint16_t)(-q));
|
|
299
|
+
return _bj_big_div_round_u32(&num, &den, out);
|
|
300
|
+
}
|
|
301
|
+
static void __attribute__((noinline)) _bj_f32_shortest_extreme(float value, char* digits, uint8_t* count, int16_t* exp10) {
|
|
302
|
+
union { float f; uint32_t u; } bits; bits.f = value;
|
|
303
|
+
uint32_t raw = bits.u & 0x7FFFFFFFUL;
|
|
304
|
+
uint32_t exp_bits = (raw >> 23) & 0xFFUL;
|
|
305
|
+
uint32_t fraction = raw & 0x7FFFFFUL;
|
|
306
|
+
uint32_t mantissa;
|
|
307
|
+
int16_t exponent2;
|
|
308
|
+
if (exp_bits == 0) { mantissa = fraction; exponent2 = -149; }
|
|
309
|
+
else { mantissa = (1UL << 23) | fraction; exponent2 = (int16_t)exp_bits - 150; }
|
|
310
|
+
|
|
311
|
+
// Exact lower/upper rounding boundaries, expressed as boundaryMantissa * 2^(exponent2-2).
|
|
312
|
+
// Powers of two have the closer lower neighbour; all other values use symmetric spacing.
|
|
313
|
+
uint32_t mm_shift = (fraction != 0 || exp_bits <= 1) ? 1UL : 0UL;
|
|
314
|
+
uint32_t lower_m = 4UL * mantissa - 1UL - mm_shift;
|
|
315
|
+
uint32_t upper_m = 4UL * mantissa + 2UL;
|
|
316
|
+
int16_t boundary_e2 = (int16_t)(exponent2 - 2);
|
|
317
|
+
bool accept_boundary = (mantissa & 1UL) == 0; // IEEE ties-to-even.
|
|
318
|
+
|
|
319
|
+
uint8_t mbits = 0; for (uint32_t t = mantissa; t; t >>= 1) ++mbits;
|
|
320
|
+
int16_t floor_log2 = (int16_t)(exponent2 + (int16_t)mbits - 1);
|
|
321
|
+
int32_t prod = (int32_t)floor_log2 * 1233L; // log10(2) approximation.
|
|
322
|
+
int16_t k = prod >= 0 ? (int16_t)(prod / 4096L)
|
|
323
|
+
: (int16_t)(-(((-prod) + 4095L) / 4096L));
|
|
324
|
+
for (uint8_t fix = 0; fix < 6; ++fix) {
|
|
325
|
+
// Compare 10^k to the exact binary32 value.
|
|
326
|
+
if (_bj_dec_vs_binary(1UL, (int16_t)(-k), mantissa, exponent2) > 0) { --k; continue; }
|
|
327
|
+
if (_bj_dec_vs_binary(1UL, (int16_t)(-(k + 1)), mantissa, exponent2) <= 0) { ++k; continue; }
|
|
328
|
+
break;
|
|
329
|
+
}
|
|
330
|
+
|
|
331
|
+
uint32_t low = 1UL;
|
|
332
|
+
for (uint8_t precision = 1; precision <= 9; ++precision, low *= 10UL) {
|
|
333
|
+
uint32_t high = low * 10UL;
|
|
334
|
+
int16_t trial_k = k;
|
|
335
|
+
for (uint8_t adjust = 0; adjust < 4; ++adjust) {
|
|
336
|
+
int16_t scale10 = (int16_t)precision - 1 - trial_k;
|
|
337
|
+
uint32_t n = 0;
|
|
338
|
+
if (!_bj_scaled_decimal_round(mantissa, exponent2, scale10, &n)) break;
|
|
339
|
+
if (n >= high) { ++trial_k; continue; }
|
|
340
|
+
if (n < low) { --trial_k; continue; }
|
|
341
|
+
int8_t lo_cmp = _bj_dec_vs_binary(n, scale10, lower_m, boundary_e2);
|
|
342
|
+
int8_t hi_cmp = _bj_dec_vs_binary(n, scale10, upper_m, boundary_e2);
|
|
343
|
+
bool inside = accept_boundary ? (lo_cmp >= 0 && hi_cmp <= 0)
|
|
344
|
+
: (lo_cmp > 0 && hi_cmp < 0);
|
|
345
|
+
if (!inside) break;
|
|
346
|
+
uint32_t x = n;
|
|
347
|
+
for (int8_t i = (int8_t)precision - 1; i >= 0; --i) {
|
|
348
|
+
digits[i] = (char)('0' + (x % 10UL)); x /= 10UL;
|
|
349
|
+
}
|
|
350
|
+
*count = precision;
|
|
351
|
+
*exp10 = trial_k;
|
|
352
|
+
return;
|
|
353
|
+
}
|
|
354
|
+
}
|
|
355
|
+
|
|
356
|
+
// IEEE-754 binary32 always has a 9-significant-digit round-trip decimal.
|
|
357
|
+
// This defensive path remains non-halting even if a future compiler change
|
|
358
|
+
// violates an assumption in the shortest search.
|
|
359
|
+
int16_t scale10 = (int16_t)(8 - k);
|
|
360
|
+
uint32_t n = 0;
|
|
361
|
+
if (!_bj_scaled_decimal_round(mantissa, exponent2, scale10, &n)) n = mantissa;
|
|
362
|
+
if (n >= 1000000000UL) { n /= 10UL; ++k; }
|
|
363
|
+
while (n < 100000000UL) { n *= 10UL; --k; }
|
|
364
|
+
for (int8_t i = 8; i >= 0; --i) { digits[i] = (char)('0' + (n % 10UL)); n /= 10UL; }
|
|
365
|
+
*count = 9;
|
|
366
|
+
*exp10 = k;
|
|
367
|
+
}
|
|
368
|
+
static void __attribute__((noinline)) _bj_print_py_float(Print& out, float value) {
|
|
369
|
+
if (isnan(value)) { out.print(F("nan")); return; }
|
|
370
|
+
if (isinf(value)) { out.print(value < 0.0f ? F("-inf") : F("inf")); return; }
|
|
371
|
+
if (value == 0.0f) {
|
|
372
|
+
union { float f; uint32_t u; } bits; bits.f = value;
|
|
373
|
+
out.print((bits.u & 0x80000000UL) ? F("-0.0") : F("0.0"));
|
|
374
|
+
return;
|
|
375
|
+
}
|
|
376
|
+
char result[32]; char* w = result;
|
|
377
|
+
char digits[10]; uint8_t count = 0;
|
|
378
|
+
int16_t exponent = 0;
|
|
379
|
+
if (value < 0.0f) *w++ = '-';
|
|
380
|
+
if (!_bj_f32_shortest(value, digits, &count, &exponent)) {
|
|
381
|
+
_bj_f32_shortest_extreme(value, digits, &count, &exponent);
|
|
382
|
+
}
|
|
383
|
+
while (count > 1 && digits[count - 1] == '0') --count;
|
|
384
|
+
if (exponent >= -4 && exponent < 16) {
|
|
385
|
+
int16_t decimal_pos = exponent + 1;
|
|
386
|
+
if (decimal_pos <= 0) {
|
|
387
|
+
*w++ = '0'; *w++ = '.';
|
|
388
|
+
for (int16_t i = 0; i < -decimal_pos; ++i) *w++ = '0';
|
|
389
|
+
for (uint8_t i = 0; i < count; ++i) *w++ = digits[i];
|
|
390
|
+
} else {
|
|
391
|
+
for (int16_t i = 0; i < decimal_pos; ++i) *w++ = (i < count) ? digits[i] : '0';
|
|
392
|
+
*w++ = '.';
|
|
393
|
+
if (decimal_pos < count) {
|
|
394
|
+
for (uint8_t i = (uint8_t)decimal_pos; i < count; ++i) *w++ = digits[i];
|
|
395
|
+
} else {
|
|
396
|
+
*w++ = '0';
|
|
397
|
+
}
|
|
398
|
+
}
|
|
399
|
+
} else {
|
|
400
|
+
*w++ = digits[0];
|
|
401
|
+
if (count > 1) { *w++ = '.'; for (uint8_t i = 1; i < count; ++i) *w++ = digits[i]; }
|
|
402
|
+
*w++ = 'e';
|
|
403
|
+
int16_t e = exponent;
|
|
404
|
+
if (e < 0) { *w++ = '-'; e = -e; } else { *w++ = '+'; }
|
|
405
|
+
char expbuf[6]; uint8_t n = 0;
|
|
406
|
+
do { expbuf[n++] = (char)('0' + (e % 10)); e /= 10; } while (e && n < sizeof(expbuf));
|
|
407
|
+
if (n < 2) *w++ = '0'; // Python prints at least two exponent digits: 1e-05
|
|
408
|
+
while (n) *w++ = expbuf[--n];
|
|
409
|
+
}
|
|
410
|
+
*w = '\0';
|
|
411
|
+
out.print(result);
|
|
412
|
+
}
|
|
413
|
+
'''
|
|
414
|
+
|
|
415
|
+
# Exact Python format(value, '.Nf') for float32 (N = 0..6). Arduino's
|
|
416
|
+
# Print::print(float, N) rounds half-up in float arithmetic and prints 'ovf'
|
|
417
|
+
# above 4294967040, which is not Python f-string behaviour.
|
|
418
|
+
_FIXED_HELPER_BLOB = r'''static void __attribute__((noinline)) _bj_print_py_fixed(Print& out, float value, uint8_t digits) {
|
|
419
|
+
// Python format(value, '.Nf') for the exact float32 value: round-half-even of value*10^N.
|
|
420
|
+
if (isnan(value)) { out.print(F("nan")); return; }
|
|
421
|
+
if (isinf(value)) { out.print(value < 0.0f ? F("-inf") : F("inf")); return; }
|
|
422
|
+
union { float f; uint32_t u; } bits; bits.f = value;
|
|
423
|
+
if (bits.u & 0x80000000UL) out.print('-');
|
|
424
|
+
uint32_t exp_bits = (bits.u >> 23) & 0xFFUL;
|
|
425
|
+
uint32_t mant = bits.u & 0x7FFFFFUL;
|
|
426
|
+
int16_t e2 = -149;
|
|
427
|
+
if (exp_bits != 0) { mant |= 0x800000UL; e2 = (int16_t)exp_bits - 150; }
|
|
428
|
+
uint16_t limb[10]; // little-endian base-2^16; holds up to 2^160 > 2^128 * 10^6
|
|
429
|
+
for (uint8_t i = 0; i < 10; ++i) limb[i] = 0;
|
|
430
|
+
limb[0] = (uint16_t)(mant & 0xFFFFUL);
|
|
431
|
+
limb[1] = (uint16_t)(mant >> 16);
|
|
432
|
+
for (uint8_t d = 0; d < digits; ++d) {
|
|
433
|
+
uint32_t carry = 0;
|
|
434
|
+
for (uint8_t i = 0; i < 10; ++i) {
|
|
435
|
+
uint32_t t = (uint32_t)limb[i] * 10UL + carry;
|
|
436
|
+
limb[i] = (uint16_t)(t & 0xFFFFUL);
|
|
437
|
+
carry = t >> 16;
|
|
438
|
+
}
|
|
439
|
+
}
|
|
440
|
+
if (e2 > 0) {
|
|
441
|
+
uint8_t q = (uint8_t)(e2 >> 4), r = (uint8_t)(e2 & 15);
|
|
442
|
+
for (int8_t i = 9; i >= 0; --i) {
|
|
443
|
+
uint32_t hi = (i - (int8_t)q >= 0) ? (uint32_t)limb[i - q] : 0UL;
|
|
444
|
+
uint32_t lo = (i - (int8_t)q - 1 >= 0) ? (uint32_t)limb[i - q - 1] : 0UL;
|
|
445
|
+
limb[i] = (uint16_t)((((hi << 16) | lo) << r) >> 16);
|
|
446
|
+
}
|
|
447
|
+
} else if (e2 < 0) {
|
|
448
|
+
uint8_t k = (uint8_t)(-e2);
|
|
449
|
+
bool half = false, sticky = false;
|
|
450
|
+
if (k <= 160) {
|
|
451
|
+
uint8_t hb = (uint8_t)(k - 1);
|
|
452
|
+
half = ((uint32_t)limb[hb >> 4] >> (hb & 15)) & 1UL;
|
|
453
|
+
for (uint8_t b = 0; b < hb && !sticky; ++b) {
|
|
454
|
+
if (((uint32_t)limb[b >> 4] >> (b & 15)) & 1UL) sticky = true;
|
|
455
|
+
}
|
|
456
|
+
}
|
|
457
|
+
uint8_t q = (uint8_t)(k >> 4), r = (uint8_t)(k & 15);
|
|
458
|
+
for (uint8_t i = 0; i < 10; ++i) {
|
|
459
|
+
uint32_t lo = (i + q < 10) ? (uint32_t)limb[i + q] : 0UL;
|
|
460
|
+
uint32_t hi = (i + q + 1 < 10) ? (uint32_t)limb[i + q + 1] : 0UL;
|
|
461
|
+
limb[i] = (uint16_t)((((hi << 16) | lo) >> r) & 0xFFFFUL);
|
|
462
|
+
}
|
|
463
|
+
if (half && (sticky || (limb[0] & 1U))) {
|
|
464
|
+
for (uint8_t i = 0; i < 10; ++i) { if (++limb[i] != 0) break; }
|
|
465
|
+
}
|
|
466
|
+
}
|
|
467
|
+
char buf[50];
|
|
468
|
+
uint8_t n = 0;
|
|
469
|
+
for (;;) {
|
|
470
|
+
uint32_t rem = 0;
|
|
471
|
+
bool more = false;
|
|
472
|
+
for (int8_t i = 9; i >= 0; --i) {
|
|
473
|
+
uint32_t cur = (rem << 16) | (uint32_t)limb[i];
|
|
474
|
+
limb[i] = (uint16_t)(cur / 10UL);
|
|
475
|
+
rem = cur % 10UL;
|
|
476
|
+
if (limb[i]) more = true;
|
|
477
|
+
}
|
|
478
|
+
buf[n++] = (char)('0' + (char)rem);
|
|
479
|
+
if (!more) break;
|
|
480
|
+
}
|
|
481
|
+
while (n <= digits) buf[n++] = '0';
|
|
482
|
+
for (uint8_t i = n; i-- > digits;) out.print(buf[i]);
|
|
483
|
+
if (digits) {
|
|
484
|
+
out.print('.');
|
|
485
|
+
for (uint8_t i = digits; i-- > 0;) out.print(buf[i]);
|
|
486
|
+
}
|
|
487
|
+
}
|
|
488
|
+
'''
|
|
489
|
+
|
|
490
|
+
_HELPER_DEPS: dict[str, tuple[str, ...]] = {
|
|
491
|
+
"_bj_list_index": ("_bj_numeric_fail",),
|
|
492
|
+
"_bj_i32_add": ("_bj_numeric_fail",),
|
|
493
|
+
"_bj_i32_sub": ("_bj_numeric_fail",),
|
|
494
|
+
"_bj_i32_mul": ("_bj_numeric_fail",),
|
|
495
|
+
"_bj_i32_neg": ("_bj_numeric_fail",),
|
|
496
|
+
"_bj_true_div_f32": ("_bj_numeric_fail",),
|
|
497
|
+
"_bj_mod_i32": ("_bj_numeric_fail",),
|
|
498
|
+
"_bj_mod_f32": ("_bj_numeric_fail",),
|
|
499
|
+
"_bj_pwm_value": ("_bj_numeric_fail",),
|
|
500
|
+
"_bj_byte_value": ("_bj_numeric_fail",),
|
|
501
|
+
"_bj_digital_level": ("_bj_numeric_fail",),
|
|
502
|
+
"_bj_pin_mode": ("_bj_numeric_fail",),
|
|
503
|
+
"_bj_i2c_address": ("_bj_numeric_fail",),
|
|
504
|
+
"_bj_arduino_format": ("_bj_numeric_fail",),
|
|
505
|
+
"_bj_wire_count": ("_bj_numeric_fail",),
|
|
506
|
+
"_bj_nonnegative_int": ("_bj_numeric_fail",),
|
|
507
|
+
"_bj_lcd_dimension": ("_bj_numeric_fail",),
|
|
508
|
+
"_bj_lcd_charsize": ("_bj_numeric_fail",),
|
|
509
|
+
"_bj_lcd_cursor_column": ("_bj_numeric_fail",),
|
|
510
|
+
"_bj_lcd_cursor_row": ("_bj_numeric_fail",),
|
|
511
|
+
"_bj_positive_int": ("_bj_numeric_fail",),
|
|
512
|
+
"_bj_servo_angle": ("_bj_numeric_fail",),
|
|
513
|
+
"_bj_servo_pulse": ("_bj_numeric_fail",),
|
|
514
|
+
"_bj_servo_attach_bounds": ("_bj_numeric_fail",),
|
|
515
|
+
"_bj_digital_pin": ("_bj_numeric_fail",),
|
|
516
|
+
"_bj_pwm_pin": ("_bj_numeric_fail",),
|
|
517
|
+
"_bj_analog_pin": ("_bj_numeric_fail",),
|
|
518
|
+
"_bj_time_threshold": ("_bj_numeric_fail",),
|
|
519
|
+
"_bj_float_to_i32": ("_bj_numeric_fail",),
|
|
520
|
+
"_bj_round_i32": ("_bj_round_even_f32", "_bj_numeric_fail"),
|
|
521
|
+
"_bj_round_f32_digits": (
|
|
522
|
+
"_bj_round_ratio_even_u64", "_bj_round_pow2_even_u64", "_bj_ratio_to_f32", "_bj_numeric_fail",
|
|
523
|
+
),
|
|
524
|
+
"_bj_round_i32_digits": ("_bj_numeric_fail",),
|
|
525
|
+
"_bj_i32_abs": ("_bj_numeric_fail",),
|
|
526
|
+
}
|
|
527
|
+
|
|
528
|
+
|
|
529
|
+
def _package_version() -> str:
|
|
530
|
+
from .. import __version__
|
|
531
|
+
return __version__
|
|
532
|
+
|
|
533
|
+
|
|
534
|
+
def _helper_name(line: str) -> str:
|
|
535
|
+
"""Name of a `static ... _bj_name(` helper definition line (attributes allowed)."""
|
|
536
|
+
match = re.search(r"\b(_bj_\w+)\s*\(", line)
|
|
537
|
+
if match is None:
|
|
538
|
+
raise InternalCompilerError(f"Malformed backend helper definition: {line!r}")
|
|
539
|
+
return match.group(1)
|
|
540
|
+
|
|
541
|
+
|
|
542
|
+
def _split_helper_blob(blob: str) -> dict[str, str]:
|
|
543
|
+
lines = blob.splitlines()
|
|
544
|
+
result: dict[str, str] = {}
|
|
545
|
+
i = 0
|
|
546
|
+
while i < len(lines):
|
|
547
|
+
line = lines[i]
|
|
548
|
+
if not line.startswith("static "):
|
|
549
|
+
i += 1
|
|
550
|
+
continue
|
|
551
|
+
name = _helper_name(line)
|
|
552
|
+
depth = 0
|
|
553
|
+
block: list[str] = []
|
|
554
|
+
while i < len(lines):
|
|
555
|
+
current = lines[i]
|
|
556
|
+
block.append(current)
|
|
557
|
+
depth += current.count("{") - current.count("}")
|
|
558
|
+
i += 1
|
|
559
|
+
if depth == 0:
|
|
560
|
+
break
|
|
561
|
+
result[name] = "\n".join(block)
|
|
562
|
+
return result
|
|
563
|
+
|
|
564
|
+
|
|
565
|
+
# Helpers for the upload-only Arduino core additions in V1.1 Stage 1.
|
|
566
|
+
_NUMERIC_HELPER_BLOB += r'''
|
|
567
|
+
static uint16_t _bj_tone_frequency(int32_t hz) {
|
|
568
|
+
if (hz < 31 || hz > 65535) _bj_numeric_fail();
|
|
569
|
+
return (uint16_t)hz;
|
|
570
|
+
}
|
|
571
|
+
static uint16_t _bj_delay_us(int32_t microseconds) {
|
|
572
|
+
if (microseconds < 0 || microseconds > 16383) _bj_numeric_fail();
|
|
573
|
+
return (uint16_t)microseconds;
|
|
574
|
+
}
|
|
575
|
+
static int32_t _bj_constrain_int(int32_t value, int32_t low, int32_t high) {
|
|
576
|
+
if (low > high) _bj_numeric_fail();
|
|
577
|
+
return value < low ? low : (value > high ? high : value);
|
|
578
|
+
}
|
|
579
|
+
static int32_t _bj_arduino_map(int32_t value, int32_t in_min, int32_t in_max, int32_t out_min, int32_t out_max) {
|
|
580
|
+
int64_t den = (int64_t)in_max - (int64_t)in_min;
|
|
581
|
+
if (!den) _bj_numeric_fail();
|
|
582
|
+
int64_t left = (int64_t)value - (int64_t)in_min;
|
|
583
|
+
int64_t right = (int64_t)out_max - (int64_t)out_min;
|
|
584
|
+
// Arduino map() truncates division toward zero. Using 64-bit intermediates
|
|
585
|
+
// avoids the usual 32-bit overflow in classroom joystick mappings.
|
|
586
|
+
// Reject extreme inputs that cannot be represented safely as signed 64-bit.
|
|
587
|
+
uint64_t a = left < 0 ? (uint64_t)(-left) : (uint64_t)left;
|
|
588
|
+
uint64_t b = right < 0 ? (uint64_t)(-right) : (uint64_t)right;
|
|
589
|
+
if (b && a > 9223372036854775807ULL / b) _bj_numeric_fail();
|
|
590
|
+
int64_t num = left * right;
|
|
591
|
+
int64_t result = num / den + (int64_t)out_min;
|
|
592
|
+
if (result < -2147483648LL || result > 2147483647LL) _bj_numeric_fail();
|
|
593
|
+
return (int32_t)result;
|
|
594
|
+
}
|
|
595
|
+
'''
|
|
596
|
+
_HELPER_DEPS.update({
|
|
597
|
+
"_bj_tone_frequency": ("_bj_numeric_fail",),
|
|
598
|
+
"_bj_delay_us": ("_bj_numeric_fail",),
|
|
599
|
+
"_bj_constrain_int": ("_bj_numeric_fail",),
|
|
600
|
+
"_bj_arduino_map": ("_bj_numeric_fail",),
|
|
601
|
+
})
|
|
602
|
+
_HELPER_BLOCKS = _split_helper_blob(_NUMERIC_HELPER_BLOB)
|
|
603
|
+
|
|
604
|
+
|
|
605
|
+
_PIN_GUARDS = {
|
|
606
|
+
"pinMode": "_bj_digital_pin", "digitalWrite": "_bj_digital_pin", "digitalRead": "_bj_digital_pin",
|
|
607
|
+
"analogWrite": "_bj_pwm_pin", "analogRead": "_bj_analog_pin", "tone": "_bj_digital_pin", "noTone": "_bj_digital_pin",
|
|
608
|
+
}
|
|
609
|
+
_PIN_SETS = {
|
|
610
|
+
"_bj_digital_pin": frozenset(range(20)),
|
|
611
|
+
"_bj_pwm_pin": frozenset({3, 5, 6, 9, 10, 11}),
|
|
612
|
+
"_bj_analog_pin": frozenset(set(range(6)) | set(range(14, 20))),
|
|
613
|
+
}
|
|
614
|
+
|
|
615
|
+
|
|
616
|
+
def _helper_closure(names: set[str]) -> set[str]:
|
|
617
|
+
closure = set(names); changed = True
|
|
618
|
+
while changed:
|
|
619
|
+
changed = False
|
|
620
|
+
for name in tuple(closure):
|
|
621
|
+
for dep in _HELPER_DEPS.get(name, ()):
|
|
622
|
+
if dep not in closure: closure.add(dep); changed = True
|
|
623
|
+
return closure
|
|
624
|
+
|
|
625
|
+
|
|
626
|
+
def _walk(value: Any):
|
|
627
|
+
yield value
|
|
628
|
+
if is_dataclass(value):
|
|
629
|
+
for f in fields(value):
|
|
630
|
+
yield from _walk(getattr(value, f.name))
|
|
631
|
+
elif isinstance(value, (tuple, list, frozenset)):
|
|
632
|
+
for item in value:
|
|
633
|
+
yield from _walk(item)
|
|
634
|
+
elif isinstance(value, dict):
|
|
635
|
+
for key, item in value.items():
|
|
636
|
+
yield from _walk(key)
|
|
637
|
+
yield from _walk(item)
|
|
638
|
+
|
|
639
|
+
|
|
640
|
+
class UnoCppBackend:
|
|
641
|
+
"""Phase-H backend. Its only semantic inputs are typed IR + TargetPlan."""
|
|
642
|
+
|
|
643
|
+
def __init__(self, ir_module: IRModule, target_plan: TargetPlan) -> None:
|
|
644
|
+
validate_ir(ir_module)
|
|
645
|
+
if target_plan.ir_module is not ir_module and target_plan.ir_module != ir_module:
|
|
646
|
+
raise InternalCompilerError("TargetPlan does not belong to the supplied typed IR module.")
|
|
647
|
+
self.ir = ir_module
|
|
648
|
+
self.target = target_plan
|
|
649
|
+
self._temp_counter = 0
|
|
650
|
+
self._temp_names: dict[object, str] = {}
|
|
651
|
+
self._storage = {decl.symbol.symbol_id: decl for decl in ir_module.storages}
|
|
652
|
+
self._objects = {obj.symbol.symbol_id: obj for obj in ir_module.objects}
|
|
653
|
+
self._hoisted_stmt_ids: set[int] = set()
|
|
654
|
+
self._dynamic_helpers: set[str] = set()
|
|
655
|
+
self._constant_symbol_values: dict[object, int] = {}
|
|
656
|
+
# Range facts are derived only from typed IR. They let the backend
|
|
657
|
+
# omit redundant runtime guards without reaching back into numeric AST analysis.
|
|
658
|
+
self._active_int_ranges: dict[object, tuple[int, int]] = {}
|
|
659
|
+
self._safe_pwm_call_ids: set[int] = self._find_range_proven_pwm_calls()
|
|
660
|
+
|
|
661
|
+
def _find_range_proven_pwm_calls(self) -> set[int]:
|
|
662
|
+
safe: set[int] = set()
|
|
663
|
+
|
|
664
|
+
def visit_expr(expr: IRExpr, ranges: dict[object, tuple[int, int]]) -> None:
|
|
665
|
+
if isinstance(expr, IRCall):
|
|
666
|
+
if (expr.kind == IRCallKind.API_FUNCTION and expr.name == "analogWrite"
|
|
667
|
+
and len(expr.args) >= 2 and self._known_pwm_safe(expr.args[1], ranges)):
|
|
668
|
+
safe.add(id(expr))
|
|
669
|
+
if is_dataclass(expr):
|
|
670
|
+
for f in fields(expr):
|
|
671
|
+
value = getattr(expr, f.name)
|
|
672
|
+
if isinstance(value, IRExpr):
|
|
673
|
+
visit_expr(value, ranges)
|
|
674
|
+
elif isinstance(value, (tuple, list)):
|
|
675
|
+
for item in value:
|
|
676
|
+
if isinstance(item, IRExpr):
|
|
677
|
+
visit_expr(item, ranges)
|
|
678
|
+
elif isinstance(item, IRFormattedValue):
|
|
679
|
+
visit_expr(item.value, ranges)
|
|
680
|
+
|
|
681
|
+
def visit_stmt(stmt: IRStmt, ranges: dict[object, tuple[int, int]]) -> None:
|
|
682
|
+
local = ranges
|
|
683
|
+
if isinstance(stmt, IRRangeLoop):
|
|
684
|
+
facts = self._constant_range_bounds(stmt)
|
|
685
|
+
body_ranges = dict(ranges)
|
|
686
|
+
if facts is not None and not self._body_writes(stmt.body, stmt.target.symbol_id, stmt.target.kind != SymbolKind.LOCAL):
|
|
687
|
+
body_ranges[stmt.target.symbol_id] = facts
|
|
688
|
+
visit_expr(stmt.start, ranges); visit_expr(stmt.stop, ranges); visit_expr(stmt.step, ranges)
|
|
689
|
+
for child in stmt.body:
|
|
690
|
+
visit_stmt(child, body_ranges)
|
|
691
|
+
return
|
|
692
|
+
if isinstance(stmt, IRIf):
|
|
693
|
+
visit_expr(stmt.condition, ranges)
|
|
694
|
+
for child in stmt.body: visit_stmt(child, dict(ranges))
|
|
695
|
+
for child in stmt.otherwise: visit_stmt(child, dict(ranges))
|
|
696
|
+
return
|
|
697
|
+
if isinstance(stmt, IRWhile):
|
|
698
|
+
visit_expr(stmt.condition, ranges)
|
|
699
|
+
for child in stmt.body: visit_stmt(child, dict(ranges))
|
|
700
|
+
return
|
|
701
|
+
if isinstance(stmt, IRFixedListLoop):
|
|
702
|
+
if stmt.index_start is not None: visit_expr(stmt.index_start, ranges)
|
|
703
|
+
for child in stmt.body: visit_stmt(child, dict(ranges))
|
|
704
|
+
return
|
|
705
|
+
if is_dataclass(stmt):
|
|
706
|
+
for f in fields(stmt):
|
|
707
|
+
value = getattr(stmt, f.name)
|
|
708
|
+
if isinstance(value, IRExpr): visit_expr(value, local)
|
|
709
|
+
elif isinstance(value, (tuple, list)):
|
|
710
|
+
for item in value:
|
|
711
|
+
if isinstance(item, IRExpr): visit_expr(item, local)
|
|
712
|
+
|
|
713
|
+
for stmt in self.ir.setup: visit_stmt(stmt, {})
|
|
714
|
+
for stmt in self.ir.main_loop: visit_stmt(stmt, {})
|
|
715
|
+
for fn in self.ir.functions:
|
|
716
|
+
for stmt in fn.body: visit_stmt(stmt, {})
|
|
717
|
+
return safe
|
|
718
|
+
|
|
719
|
+
@staticmethod
|
|
720
|
+
def _body_writes(body, symbol_id, global_symbol: bool = True) -> bool:
|
|
721
|
+
for item in _walk(body):
|
|
722
|
+
if isinstance(item, (IRStore, IRAugStore)) and item.target.symbol_id == symbol_id:
|
|
723
|
+
return True
|
|
724
|
+
if isinstance(item, IRRangeLoop) and item.target.symbol_id == symbol_id:
|
|
725
|
+
return True
|
|
726
|
+
if isinstance(item, IRFixedListLoop) and (item.value_target.symbol_id == symbol_id or (item.index_target is not None and item.index_target.symbol_id == symbol_id)):
|
|
727
|
+
return True
|
|
728
|
+
if global_symbol and isinstance(item, IRCall) and item.kind == IRCallKind.USER_FUNCTION:
|
|
729
|
+
return True # a called function may write a module-level loop variable
|
|
730
|
+
return False
|
|
731
|
+
|
|
732
|
+
def _constant_range_bounds(self, stmt: IRRangeLoop) -> tuple[int, int] | None:
|
|
733
|
+
start, stop, step = self._const_int(stmt.start), self._const_int(stmt.stop), self._const_int(stmt.step)
|
|
734
|
+
if start is None or stop is None or step in (None, 0):
|
|
735
|
+
return None
|
|
736
|
+
if step > 0:
|
|
737
|
+
if start >= stop: return None
|
|
738
|
+
last = start + ((stop - 1 - start) // step) * step
|
|
739
|
+
return (start, last)
|
|
740
|
+
if start <= stop: return None
|
|
741
|
+
last = start + ((start - (stop + 1)) // (-step)) * step
|
|
742
|
+
return (last, start)
|
|
743
|
+
|
|
744
|
+
def generate(self) -> GeneratedSketch:
|
|
745
|
+
helper_names = self._required_numeric_helpers()
|
|
746
|
+
if self._needs_python_float_formatter():
|
|
747
|
+
# The exact float32 shortest-repr formatter reuses the exact rational helpers.
|
|
748
|
+
helper_names |= {"_bj_round_ratio_even_u64", "_bj_round_pow2_even_u64", "_bj_ratio_to_f32", "_bj_numeric_fail"}
|
|
749
|
+
parts: list[tuple[str, SourceSpan | None]] = []
|
|
750
|
+
add = parts.append
|
|
751
|
+
|
|
752
|
+
add((f"// Generated by Barmaja PyArduino {_package_version()}", None))
|
|
753
|
+
add(("// Edit the Python source, not this generated file.", None))
|
|
754
|
+
for header in self.target.includes:
|
|
755
|
+
add((f"#include <{header}>", None))
|
|
756
|
+
add(("", None))
|
|
757
|
+
|
|
758
|
+
# Arduino's .ino preprocessor synthesizes prototypes before the first function
|
|
759
|
+
# definition. Helper-owned types therefore MUST appear before every helper
|
|
760
|
+
# function; otherwise ctags can place prototypes above their typedefs (F7).
|
|
761
|
+
if self._needs_python_float_formatter():
|
|
762
|
+
add((_OUTPUT_HELPER_PREAMBLE, None))
|
|
763
|
+
add(("", None))
|
|
764
|
+
|
|
765
|
+
# Helpers are inserted here after the body is generated, so runtime guards
|
|
766
|
+
# chosen during generation (delay/servo/timeout) are emitted exactly when used.
|
|
767
|
+
helper_insert_at = len(parts)
|
|
768
|
+
|
|
769
|
+
hoisted = self._find_hoistable_setup_stores()
|
|
770
|
+
# Module constants initialized statically have exactly one known value.
|
|
771
|
+
for sid, store in hoisted.items():
|
|
772
|
+
decl = self._storage.get(sid)
|
|
773
|
+
value = self._const_int(store.value)
|
|
774
|
+
if decl is not None and decl.storage == StorageClass.MODULE_CONSTANT and value is not None:
|
|
775
|
+
self._constant_symbol_values[sid] = value
|
|
776
|
+
emitted_globals = False
|
|
777
|
+
for decl in self.ir.storages:
|
|
778
|
+
if decl.storage not in {StorageClass.MODULE_CONSTANT, StorageClass.PERSISTENT_MODULE}:
|
|
779
|
+
continue
|
|
780
|
+
emitted_globals = True
|
|
781
|
+
init_stmt = hoisted.get(decl.symbol.symbol_id)
|
|
782
|
+
if init_stmt is not None:
|
|
783
|
+
self._hoisted_stmt_ids.add(id(init_stmt))
|
|
784
|
+
value = init_stmt.value
|
|
785
|
+
if decl.symbol.type.kind == TypeKind.FIXED_LIST:
|
|
786
|
+
if not isinstance(value, IRListLiteral):
|
|
787
|
+
raise InternalCompilerError("Fixed-list static initializer is not an IR list literal.")
|
|
788
|
+
elem = decl.symbol.type.element_type
|
|
789
|
+
if elem is None or decl.symbol.type.length is None:
|
|
790
|
+
raise InternalCompilerError("Incomplete fixed-list type reached Uno backend.")
|
|
791
|
+
items = ", ".join(self._expr(x) for x in value.elements)
|
|
792
|
+
if decl.storage == StorageClass.MODULE_CONSTANT:
|
|
793
|
+
# `const char*` elements: make the pointers const (`const char* const`),
|
|
794
|
+
# never the ill-formed `const const char*`.
|
|
795
|
+
elem_decl = f"{elem.cpp_type} const" if elem.cpp_type.startswith("const ") else f"const {elem.cpp_type}"
|
|
796
|
+
else:
|
|
797
|
+
elem_decl = elem.cpp_type
|
|
798
|
+
add((f"{elem_decl} {decl.symbol.backend_name}[{decl.symbol.type.length}] = {{{items}}};", decl.span))
|
|
799
|
+
else:
|
|
800
|
+
typ = decl.symbol.type.cpp_type
|
|
801
|
+
dtype = ("const char* const" if typ == "const char*" else f"const {typ}") if decl.storage == StorageClass.MODULE_CONSTANT else typ
|
|
802
|
+
add((f"{dtype} {decl.symbol.backend_name} = {self._expr(value)};", decl.span))
|
|
803
|
+
else:
|
|
804
|
+
add((self._decl_line(decl.symbol), decl.span))
|
|
805
|
+
if emitted_globals:
|
|
806
|
+
add(("", None))
|
|
807
|
+
|
|
808
|
+
ctor_values = dict(self.target.object_arg_values)
|
|
809
|
+
for obj in self.ir.objects:
|
|
810
|
+
args = ", ".join(self._ctor_arg(a, v) for a, v in zip(obj.args, ctor_values.get(obj.symbol.symbol_id, (None,) * len(obj.args))))
|
|
811
|
+
# Arduino Ethernet.h declares EthernetW5500 as a global enum value.
|
|
812
|
+
# Keep the public Python hardware-class name, but emit our uniquely
|
|
813
|
+
# named C++ adapter type to avoid collision with the Arduino library.
|
|
814
|
+
cpp_class_name = "BarmajaEthernetW5500" if obj.class_name == "EthernetW5500" else obj.class_name
|
|
815
|
+
text = f"{cpp_class_name} {obj.symbol.backend_name}({args});" if args else f"{cpp_class_name} {obj.symbol.backend_name};"
|
|
816
|
+
add((text, obj.span))
|
|
817
|
+
if self.ir.objects:
|
|
818
|
+
add(("", None))
|
|
819
|
+
|
|
820
|
+
for fn in self.ir.functions:
|
|
821
|
+
params = ", ".join(f"{p.type.cpp_type} {p.backend_name}" for p in fn.parameters)
|
|
822
|
+
add((f"{fn.return_type.cpp_type} {fn.symbol.backend_name}({params});", fn.span))
|
|
823
|
+
if self.ir.functions:
|
|
824
|
+
add(("", None))
|
|
825
|
+
|
|
826
|
+
for fn in self.ir.functions:
|
|
827
|
+
parts.extend(self._function(fn))
|
|
828
|
+
add(("", None))
|
|
829
|
+
|
|
830
|
+
add(("void setup() {", None))
|
|
831
|
+
for decl in self.ir.storages:
|
|
832
|
+
if decl.storage == StorageClass.SETUP_LOCAL:
|
|
833
|
+
add((" " + self._decl_line(decl.symbol, local=True), decl.span))
|
|
834
|
+
for hook in self.target.setup_hooks:
|
|
835
|
+
add((" " + hook, None))
|
|
836
|
+
for stmt in self.ir.setup:
|
|
837
|
+
if id(stmt) in self._hoisted_stmt_ids:
|
|
838
|
+
continue
|
|
839
|
+
parts.extend(self._stmt(stmt, 1))
|
|
840
|
+
add(("}", None))
|
|
841
|
+
add(("", None))
|
|
842
|
+
|
|
843
|
+
add(("void loop() {", None))
|
|
844
|
+
for decl in self.ir.storages:
|
|
845
|
+
if decl.storage == StorageClass.LOOP_TEMPORARY:
|
|
846
|
+
add((" " + self._decl_line(decl.symbol, local=True), decl.span))
|
|
847
|
+
for stmt in self.ir.main_loop:
|
|
848
|
+
parts.extend(self._stmt(stmt, 1))
|
|
849
|
+
add(("}", None))
|
|
850
|
+
add(("", None))
|
|
851
|
+
|
|
852
|
+
helper_names = _helper_closure(helper_names | self._dynamic_helpers)
|
|
853
|
+
helper_lines: list[tuple[str, SourceSpan | None]] = []
|
|
854
|
+
if helper_names:
|
|
855
|
+
for name in self._helper_order(helper_names):
|
|
856
|
+
block = _HELPER_BLOCKS.get(name)
|
|
857
|
+
if block is None:
|
|
858
|
+
raise InternalCompilerError(f"Backend helper '{name}' is not defined.")
|
|
859
|
+
helper_lines.extend((line, None) for line in block.splitlines())
|
|
860
|
+
helper_lines.append(("", None))
|
|
861
|
+
if self._needs_python_float_formatter():
|
|
862
|
+
helper_lines.extend((line, None) for line in _OUTPUT_HELPER_BLOB.splitlines())
|
|
863
|
+
helper_lines.append(("", None))
|
|
864
|
+
if self._needs_fixed_float_formatter():
|
|
865
|
+
helper_lines.extend((line, None) for line in _FIXED_HELPER_BLOB.splitlines())
|
|
866
|
+
helper_lines.append(("", None))
|
|
867
|
+
parts[helper_insert_at:helper_insert_at] = helper_lines
|
|
868
|
+
|
|
869
|
+
cpp_lines = [text for text, _ in parts]
|
|
870
|
+
source_map = tuple(
|
|
871
|
+
SourceMapEntry(index + 1, self.ir.source_name, span)
|
|
872
|
+
for index, (_, span) in enumerate(parts) if span is not None
|
|
873
|
+
)
|
|
874
|
+
return GeneratedSketch(
|
|
875
|
+
cpp="\n".join(cpp_lines),
|
|
876
|
+
source_map=source_map,
|
|
877
|
+
dependency_ids=self.target.dependency_ids,
|
|
878
|
+
resolved_serial_baud=self.target.resolved_serial_baud,
|
|
879
|
+
diagnostics=self.target.diagnostics,
|
|
880
|
+
)
|
|
881
|
+
|
|
882
|
+
def _ctor_arg(self, arg: IRExpr, value: int | None) -> str:
|
|
883
|
+
"""Hardware objects are constructed before setup(); never reference setup-time storage."""
|
|
884
|
+
if isinstance(arg, IRConstant) or (isinstance(arg, IRLoad) and arg.symbol.kind in {SymbolKind.API, SymbolKind.BUILTIN}):
|
|
885
|
+
return self._expr(arg)
|
|
886
|
+
if value is None:
|
|
887
|
+
raise InternalCompilerError("Hardware constructor argument reached the backend without a proven compile-time value.")
|
|
888
|
+
return str(value)
|
|
889
|
+
|
|
890
|
+
def _find_hoistable_setup_stores(self) -> dict[object, IRStore]:
|
|
891
|
+
"""Find setup stores that may become file-scope static initializers.
|
|
892
|
+
|
|
893
|
+
A store is only hoisted when it is the first write of that symbol in
|
|
894
|
+
execution order. Any earlier setup statement that may write the symbol
|
|
895
|
+
(nested stores, loop targets, augmented/index stores, or a user-function
|
|
896
|
+
call that could write globals) blocks hoisting, because moving the store
|
|
897
|
+
before those writes would change the program's result.
|
|
898
|
+
"""
|
|
899
|
+
result: dict[object, IRStore] = {}
|
|
900
|
+
written: set[object] = set()
|
|
901
|
+
opaque_call_seen = False
|
|
902
|
+
for stmt in self.ir.setup:
|
|
903
|
+
if isinstance(stmt, IRStore):
|
|
904
|
+
sid = stmt.target.symbol_id
|
|
905
|
+
decl = self._storage.get(sid)
|
|
906
|
+
if (
|
|
907
|
+
decl is not None
|
|
908
|
+
and decl.storage in {StorageClass.MODULE_CONSTANT, StorageClass.PERSISTENT_MODULE}
|
|
909
|
+
and sid not in written
|
|
910
|
+
and sid not in result
|
|
911
|
+
and (decl.storage == StorageClass.MODULE_CONSTANT or not opaque_call_seen)
|
|
912
|
+
and self._is_static_initializer(stmt.value)
|
|
913
|
+
and not (stmt.target.type.kind == TypeKind.TIME_U32 and not self._known_nonnegative_int(stmt.value))
|
|
914
|
+
):
|
|
915
|
+
result[sid] = stmt
|
|
916
|
+
for item in _walk(stmt):
|
|
917
|
+
if isinstance(item, (IRStore, IRAugStore)):
|
|
918
|
+
written.add(item.target.symbol_id)
|
|
919
|
+
elif isinstance(item, IRIndexStore):
|
|
920
|
+
written.add(item.collection.symbol_id)
|
|
921
|
+
elif isinstance(item, IRRangeLoop):
|
|
922
|
+
written.add(item.target.symbol_id)
|
|
923
|
+
elif isinstance(item, IRFixedListLoop):
|
|
924
|
+
written.add(item.value_target.symbol_id)
|
|
925
|
+
if item.index_target is not None:
|
|
926
|
+
written.add(item.index_target.symbol_id)
|
|
927
|
+
elif isinstance(item, IRCall) and item.kind == IRCallKind.USER_FUNCTION:
|
|
928
|
+
opaque_call_seen = True
|
|
929
|
+
return result
|
|
930
|
+
|
|
931
|
+
def _is_static_initializer(self, expr: IRExpr) -> bool:
|
|
932
|
+
if isinstance(expr, IRConstant):
|
|
933
|
+
return True
|
|
934
|
+
if isinstance(expr, IRUnary) and expr.op == IRUnaryOp.NEGATE and isinstance(expr.operand, IRConstant):
|
|
935
|
+
return True
|
|
936
|
+
if isinstance(expr, IRListLiteral):
|
|
937
|
+
return all(self._is_static_initializer(x) for x in expr.elements)
|
|
938
|
+
return False
|
|
939
|
+
|
|
940
|
+
@staticmethod
|
|
941
|
+
def _decl_line(symbol: IRSymbolRef, *, local: bool = False) -> str:
|
|
942
|
+
typ = symbol.type
|
|
943
|
+
if typ.kind == TypeKind.FIXED_LIST:
|
|
944
|
+
if typ.element_type is None or typ.length is None:
|
|
945
|
+
raise InternalCompilerError("Incomplete fixed-list type reached declaration generation.")
|
|
946
|
+
# A fixed list inside setup()/loop()/a function would otherwise live on the
|
|
947
|
+
# 2 KB AVR stack, invisible to both the static SRAM estimate and the
|
|
948
|
+
# linker's "Global variables" figure. Static storage is safe here: V1
|
|
949
|
+
# forbids recursion and every local list is definitely assigned before use.
|
|
950
|
+
storage = "static " if local else ""
|
|
951
|
+
return f"{storage}{typ.element_type.cpp_type} {symbol.backend_name}[{typ.length}];"
|
|
952
|
+
return f"{typ.cpp_type} {symbol.backend_name};"
|
|
953
|
+
|
|
954
|
+
def _function(self, fn: IRFunction) -> list[tuple[str, SourceSpan | None]]:
|
|
955
|
+
params = ", ".join(f"{p.type.cpp_type} {p.backend_name}" for p in fn.parameters)
|
|
956
|
+
out: list[tuple[str, SourceSpan | None]] = [(f"{fn.return_type.cpp_type} {fn.symbol.backend_name}({params}) {{", fn.span)]
|
|
957
|
+
for symbol in self._function_locals(fn):
|
|
958
|
+
out.append((" " + self._decl_line(symbol, local=True), fn.span))
|
|
959
|
+
for stmt in fn.body:
|
|
960
|
+
out.extend(self._stmt(stmt, 1))
|
|
961
|
+
out.append(("}", fn.span))
|
|
962
|
+
return out
|
|
963
|
+
|
|
964
|
+
def _function_locals(self, fn: IRFunction) -> list[IRSymbolRef]:
|
|
965
|
+
params = {p.symbol_id for p in fn.parameters}
|
|
966
|
+
module_ids = set(self._storage) | set(self._objects)
|
|
967
|
+
found: dict[object, IRSymbolRef] = {}
|
|
968
|
+
for item in _walk(fn.body):
|
|
969
|
+
refs: list[IRSymbolRef] = []
|
|
970
|
+
if isinstance(item, (IRStore, IRAugStore)):
|
|
971
|
+
refs.append(item.target)
|
|
972
|
+
elif isinstance(item, IRFixedListLoop):
|
|
973
|
+
refs.append(item.value_target)
|
|
974
|
+
if item.index_target is not None:
|
|
975
|
+
refs.append(item.index_target)
|
|
976
|
+
elif isinstance(item, IRRangeLoop):
|
|
977
|
+
refs.append(item.target)
|
|
978
|
+
for ref in refs:
|
|
979
|
+
if ref.symbol_id not in params and ref.symbol_id not in module_ids and ref.kind == SymbolKind.LOCAL:
|
|
980
|
+
found.setdefault(ref.symbol_id, ref)
|
|
981
|
+
return sorted(found.values(), key=lambda s: s.symbol_id.value)
|
|
982
|
+
|
|
983
|
+
def _aug_store_lines(self, stmt: IRAugStore, pad: str, span, current: str, rhs: str) -> list[tuple[str, SourceSpan | None]]:
|
|
984
|
+
name = stmt.target.backend_name
|
|
985
|
+
kind = stmt.target.type.kind
|
|
986
|
+
if stmt.op == IRBinaryOp.TRUE_DIVIDE:
|
|
987
|
+
return [(f"{pad}{name} = _bj_true_div_f32((float)({current}), (float)({rhs}));", span)]
|
|
988
|
+
if stmt.op == IRBinaryOp.MODULO:
|
|
989
|
+
if kind == TypeKind.FLOAT:
|
|
990
|
+
return [(f"{pad}{name} = _bj_mod_f32((float)({current}), (float)({rhs}));", span)]
|
|
991
|
+
return [(f"{pad}{name} = _bj_mod_i32({current}, {rhs});", span)]
|
|
992
|
+
if kind == TypeKind.INT and stmt.op in {IRBinaryOp.ADD, IRBinaryOp.SUBTRACT, IRBinaryOp.MULTIPLY}:
|
|
993
|
+
helper = {IRBinaryOp.ADD:"_bj_i32_add", IRBinaryOp.SUBTRACT:"_bj_i32_sub", IRBinaryOp.MULTIPLY:"_bj_i32_mul"}[stmt.op]
|
|
994
|
+
return [(f"{pad}{name} = {helper}({current}, {rhs});", span)]
|
|
995
|
+
op = {IRBinaryOp.ADD:"+", IRBinaryOp.SUBTRACT:"-", IRBinaryOp.MULTIPLY:"*"}.get(stmt.op)
|
|
996
|
+
if op is None:
|
|
997
|
+
raise InternalCompilerError("Unsupported augmented IR operator reached Uno backend.")
|
|
998
|
+
return [(f"{pad}{name} = ({current} {op} {rhs});", span)]
|
|
999
|
+
|
|
1000
|
+
def _stmt(self, stmt: IRStmt, indent: int) -> list[tuple[str, SourceSpan | None]]:
|
|
1001
|
+
pad = " " * indent
|
|
1002
|
+
span = stmt.span
|
|
1003
|
+
if isinstance(stmt, IRStore):
|
|
1004
|
+
if stmt.target.type.kind == TypeKind.FIXED_LIST:
|
|
1005
|
+
if not isinstance(stmt.value, IRListLiteral):
|
|
1006
|
+
raise InternalCompilerError("Whole fixed-list store reached backend without IRListLiteral.")
|
|
1007
|
+
elements = stmt.value.elements
|
|
1008
|
+
if all(isinstance(v, IRConstant) for v in elements):
|
|
1009
|
+
return [(f"{pad}{stmt.target.backend_name}[{i}] = {self._expr(v)};", span) for i, v in enumerate(elements)]
|
|
1010
|
+
# Python evaluates the whole list literal (left to right) before
|
|
1011
|
+
# rebinding: `L = [x, L[0], L[1]]` must read the OLD elements.
|
|
1012
|
+
# Materialize every element first, then store.
|
|
1013
|
+
elem_type = stmt.target.type.element_type
|
|
1014
|
+
if elem_type is None:
|
|
1015
|
+
raise InternalCompilerError("Incomplete fixed-list type reached Uno backend.")
|
|
1016
|
+
temps = [self._new_temp("list_elem") for _ in elements]
|
|
1017
|
+
out = [(f"{pad}{elem_type.cpp_type} {t} = {self._expr(v)};", span) for t, v in zip(temps, elements)]
|
|
1018
|
+
out.extend((f"{pad}{stmt.target.backend_name}[{i}] = {t};", span) for i, t in enumerate(temps))
|
|
1019
|
+
return out
|
|
1020
|
+
value = self._expr(stmt.value)
|
|
1021
|
+
if stmt.target.type.kind == TypeKind.TIME_U32 and stmt.value.type.kind == TypeKind.INT and not self._known_nonnegative_int(stmt.value):
|
|
1022
|
+
value = f"_bj_time_threshold({value})"; self._dynamic_helpers.add("_bj_time_threshold")
|
|
1023
|
+
return [(f"{pad}{stmt.target.backend_name} = {value};", span)]
|
|
1024
|
+
if isinstance(stmt, IRIndexStore):
|
|
1025
|
+
if self._expr_has_effects(stmt.value):
|
|
1026
|
+
# Python evaluates the right-hand side before the subscript target.
|
|
1027
|
+
value_tmp = self._new_temp("store_value")
|
|
1028
|
+
value_line = f"{pad}{stmt.value.type.cpp_type} {value_tmp} = {self._expr(stmt.value)};"
|
|
1029
|
+
index = self._list_index_expr(stmt.index, stmt.collection.type)
|
|
1030
|
+
return [(value_line, span), (f"{pad}{stmt.collection.backend_name}[{index}] = {value_tmp};", span)]
|
|
1031
|
+
index = self._list_index_expr(stmt.index, stmt.collection.type)
|
|
1032
|
+
return [(f"{pad}{stmt.collection.backend_name}[{index}] = {self._expr(stmt.value)};", span)]
|
|
1033
|
+
if isinstance(stmt, IRAugStore):
|
|
1034
|
+
name = stmt.target.backend_name
|
|
1035
|
+
rhs = self._expr(stmt.value)
|
|
1036
|
+
prefix: list[tuple[str, SourceSpan | None]] = []
|
|
1037
|
+
if self._expr_has_effects(stmt.value):
|
|
1038
|
+
# Python reads the target before evaluating the right-hand side.
|
|
1039
|
+
old_tmp = self._new_temp("aug_target")
|
|
1040
|
+
rhs_tmp = self._new_temp("aug_value")
|
|
1041
|
+
prefix = [
|
|
1042
|
+
(f"{pad}{stmt.target.type.cpp_type} {old_tmp} = {name};", span),
|
|
1043
|
+
(f"{pad}{stmt.value.type.cpp_type} {rhs_tmp} = {rhs};", span),
|
|
1044
|
+
]
|
|
1045
|
+
return prefix + self._aug_store_lines(stmt, pad, span, old_tmp, rhs_tmp)
|
|
1046
|
+
return self._aug_store_lines(stmt, pad, span, name, rhs)
|
|
1047
|
+
if isinstance(stmt, IRExprStmt):
|
|
1048
|
+
special = self._explicit_output_stmt(stmt.expression, indent)
|
|
1049
|
+
if special is not None:
|
|
1050
|
+
return [(line, span) for line in special]
|
|
1051
|
+
return [(f"{pad}{self._expr(stmt.expression)};", span)]
|
|
1052
|
+
if isinstance(stmt, IRPrint):
|
|
1053
|
+
return [(line, span) for line in self._python_print(stmt, indent)]
|
|
1054
|
+
if isinstance(stmt, IRReturn):
|
|
1055
|
+
return [(pad + ("return;" if stmt.value is None else f"return {self._expr(stmt.value)};"), span)]
|
|
1056
|
+
if isinstance(stmt, IRBreak):
|
|
1057
|
+
if stmt.target == IRLoopControlTarget.MAIN_LOOP:
|
|
1058
|
+
raise InternalCompilerError("A main-loop break reached the Uno backend after validation.")
|
|
1059
|
+
return [(pad + "break;", span)]
|
|
1060
|
+
if isinstance(stmt, IRContinue):
|
|
1061
|
+
return [(pad + ("return;" if stmt.target == IRLoopControlTarget.MAIN_LOOP else "continue;"), span)]
|
|
1062
|
+
if isinstance(stmt, IRIf):
|
|
1063
|
+
out = [(f"{pad}if ({self._expr(stmt.condition)}) {{", span)]
|
|
1064
|
+
for child in stmt.body:
|
|
1065
|
+
out.extend(self._stmt(child, indent + 1))
|
|
1066
|
+
out.append((pad + "}", span))
|
|
1067
|
+
if stmt.otherwise:
|
|
1068
|
+
out[-1] = (out[-1][0] + " else {", span)
|
|
1069
|
+
for child in stmt.otherwise:
|
|
1070
|
+
out.extend(self._stmt(child, indent + 1))
|
|
1071
|
+
out.append((pad + "}", span))
|
|
1072
|
+
return out
|
|
1073
|
+
if isinstance(stmt, IRWhile):
|
|
1074
|
+
out = [(f"{pad}while ({self._expr(stmt.condition)}) {{", span)]
|
|
1075
|
+
for child in stmt.body:
|
|
1076
|
+
out.extend(self._stmt(child, indent + 1))
|
|
1077
|
+
out.append((pad + "}", span))
|
|
1078
|
+
return out
|
|
1079
|
+
if isinstance(stmt, IRRangeLoop):
|
|
1080
|
+
start = self._temp_name(stmt.start_temp, "range_start")
|
|
1081
|
+
stop = self._temp_name(stmt.stop_temp, "range_stop")
|
|
1082
|
+
step = self._temp_name(stmt.step_temp, "range_step")
|
|
1083
|
+
iterator = self._temp_name(stmt.iterator_temp, "range_iter")
|
|
1084
|
+
out = [
|
|
1085
|
+
(f"{pad}int32_t {start} = {self._expr(stmt.start)};", span),
|
|
1086
|
+
(f"{pad}int32_t {stop} = {self._expr(stmt.stop)};", span),
|
|
1087
|
+
(f"{pad}int32_t {step} = {self._expr(stmt.step)};", span),
|
|
1088
|
+
]
|
|
1089
|
+
step_value = self._const_int(stmt.step)
|
|
1090
|
+
if step_value == 0:
|
|
1091
|
+
raise InternalCompilerError("A zero range step reached the Uno backend after validation.")
|
|
1092
|
+
if step_value is None:
|
|
1093
|
+
out.append((f"{pad}if ({step} == 0) _bj_numeric_fail();", span))
|
|
1094
|
+
cond = f"({step} > 0 ? {iterator} < {stop} : {iterator} > {stop})"
|
|
1095
|
+
else:
|
|
1096
|
+
cond = f"{iterator} {'<' if step_value > 0 else '>'} {stop}"
|
|
1097
|
+
# Python range() never computes a value past the final element, but a C++
|
|
1098
|
+
# for-loop does evaluate `iterator + step` once more. Near INT32_MAX/MIN
|
|
1099
|
+
# that final increment must not be an Int32 overflow trap (or UB), so the
|
|
1100
|
+
# iterator is widened unless the whole progression is statically in range.
|
|
1101
|
+
bounds = self._constant_range_bounds(stmt)
|
|
1102
|
+
iterator_type = "int64_t"
|
|
1103
|
+
if bounds is not None and step_value is not None:
|
|
1104
|
+
after_last = (bounds[1] + step_value) if step_value > 0 else (bounds[0] + step_value)
|
|
1105
|
+
if -(2**31) <= after_last <= 2**31 - 1:
|
|
1106
|
+
iterator_type = "int32_t"
|
|
1107
|
+
elif (
|
|
1108
|
+
step_value is not None
|
|
1109
|
+
and self._const_int(stmt.start) is not None
|
|
1110
|
+
and self._const_int(stmt.stop) is not None
|
|
1111
|
+
):
|
|
1112
|
+
# Constant but empty progression: the body never runs.
|
|
1113
|
+
iterator_type = "int32_t"
|
|
1114
|
+
out.append((f"{pad}for ({iterator_type} {iterator} = {start}; {cond}; {iterator} += {step}) {{", span))
|
|
1115
|
+
narrow = "(int32_t)" if iterator_type == "int64_t" else ""
|
|
1116
|
+
out.append((f"{pad} {stmt.target.backend_name} = {narrow}{iterator};", span))
|
|
1117
|
+
previous = self._active_int_ranges.get(stmt.target.symbol_id)
|
|
1118
|
+
facts = self._constant_range_bounds(stmt)
|
|
1119
|
+
if facts is not None and not self._body_writes(stmt.body, stmt.target.symbol_id, stmt.target.kind != SymbolKind.LOCAL):
|
|
1120
|
+
self._active_int_ranges[stmt.target.symbol_id] = facts
|
|
1121
|
+
else:
|
|
1122
|
+
# The loop variable is reassigned in the body: its range is not proven.
|
|
1123
|
+
self._active_int_ranges.pop(stmt.target.symbol_id, None)
|
|
1124
|
+
try:
|
|
1125
|
+
for child in stmt.body:
|
|
1126
|
+
out.extend(self._stmt(child, indent + 1))
|
|
1127
|
+
finally:
|
|
1128
|
+
if previous is None:
|
|
1129
|
+
self._active_int_ranges.pop(stmt.target.symbol_id, None)
|
|
1130
|
+
else:
|
|
1131
|
+
self._active_int_ranges[stmt.target.symbol_id] = previous
|
|
1132
|
+
out.append((pad + "}", span))
|
|
1133
|
+
return out
|
|
1134
|
+
if isinstance(stmt, IRFixedListLoop):
|
|
1135
|
+
typ = stmt.collection.type
|
|
1136
|
+
if typ.kind != TypeKind.FIXED_LIST or typ.length is None:
|
|
1137
|
+
raise InternalCompilerError("Fixed-list loop reached Uno backend without fixed-list type.")
|
|
1138
|
+
iterator = self._temp_name(stmt.iterator_temp, "list_iter")
|
|
1139
|
+
out: list[tuple[str, SourceSpan | None]] = []
|
|
1140
|
+
start_name: str | None = None
|
|
1141
|
+
if stmt.index_start is not None:
|
|
1142
|
+
if stmt.index_start_temp is None:
|
|
1143
|
+
raise InternalCompilerError("enumerate(start) IR is missing its evaluate-once temporary.")
|
|
1144
|
+
start_name = self._temp_name(stmt.index_start_temp, "enumerate_start")
|
|
1145
|
+
out.append((f"{pad}int32_t {start_name} = {self._expr(stmt.index_start)};", span))
|
|
1146
|
+
out.append((f"{pad}for (int32_t {iterator} = 0; {iterator} < {typ.length}; ++{iterator}) {{", span))
|
|
1147
|
+
if stmt.index_target is not None:
|
|
1148
|
+
if start_name is None:
|
|
1149
|
+
out.append((f"{pad} {stmt.index_target.backend_name} = {iterator};", span))
|
|
1150
|
+
else:
|
|
1151
|
+
out.append((f"{pad} {stmt.index_target.backend_name} = _bj_i32_add({start_name}, {iterator});", span))
|
|
1152
|
+
out.append((f"{pad} {stmt.value_target.backend_name} = {stmt.collection.backend_name}[{iterator}];", span))
|
|
1153
|
+
for child in stmt.body:
|
|
1154
|
+
out.extend(self._stmt(child, indent + 1))
|
|
1155
|
+
out.append((pad + "}", span))
|
|
1156
|
+
return out
|
|
1157
|
+
raise InternalCompilerError(f"Unsupported typed IR statement {type(stmt).__name__} reached Uno backend.")
|
|
1158
|
+
|
|
1159
|
+
def _expr(self, expr: IRExpr) -> str:
|
|
1160
|
+
if isinstance(expr, IRConstant):
|
|
1161
|
+
if expr.value is True: return "true"
|
|
1162
|
+
if expr.value is False: return "false"
|
|
1163
|
+
if isinstance(expr.value, str): return self._cpp_string_literal(expr.value)
|
|
1164
|
+
return repr(expr.value)
|
|
1165
|
+
if isinstance(expr, IRLoad):
|
|
1166
|
+
if expr.symbol.kind in {SymbolKind.API, SymbolKind.BUILTIN}:
|
|
1167
|
+
return expr.symbol.source_name
|
|
1168
|
+
return expr.symbol.backend_name
|
|
1169
|
+
if isinstance(expr, IRListLiteral):
|
|
1170
|
+
return "{" + ", ".join(self._expr(x) for x in expr.elements) + "}"
|
|
1171
|
+
if isinstance(expr, IRIndex):
|
|
1172
|
+
if not isinstance(expr.collection, IRLoad):
|
|
1173
|
+
raise InternalCompilerError("Fixed-list IR index has no direct list load.")
|
|
1174
|
+
return f"{self._expr(expr.collection)}[{self._list_index_expr(expr.index, expr.collection.type)}]"
|
|
1175
|
+
if isinstance(expr, IRUnary):
|
|
1176
|
+
operand = self._expr(expr.operand)
|
|
1177
|
+
if expr.op == IRUnaryOp.NOT:
|
|
1178
|
+
return f"(!{operand})"
|
|
1179
|
+
if isinstance(expr.operand, IRConstant) and expr.operand.value == 2**31:
|
|
1180
|
+
return "(-2147483647L - 1L)"
|
|
1181
|
+
if expr.type.kind == TypeKind.INT and IREffect.MAY_FAIL in expr.effects:
|
|
1182
|
+
return f"_bj_i32_neg({operand})"
|
|
1183
|
+
return f"(-{operand})"
|
|
1184
|
+
if isinstance(expr, IRBinary):
|
|
1185
|
+
return self._binary_expr(expr)
|
|
1186
|
+
if isinstance(expr, IRCompareChain):
|
|
1187
|
+
return self._comparison_expr(expr)
|
|
1188
|
+
if isinstance(expr, IRBool):
|
|
1189
|
+
op = " && " if expr.op == IRBoolOp.AND else " || "
|
|
1190
|
+
return "(" + op.join(self._expr(x) for x in expr.values) + ")"
|
|
1191
|
+
if isinstance(expr, IRConditional):
|
|
1192
|
+
return f"({self._expr(expr.condition)} ? {self._expr(expr.when_true)} : {self._expr(expr.when_false)})"
|
|
1193
|
+
if isinstance(expr, IRCall):
|
|
1194
|
+
return self._call(expr)
|
|
1195
|
+
if isinstance(expr, IRFormatString):
|
|
1196
|
+
raise InternalCompilerError("Output-only f-string reached scalar expression generation.")
|
|
1197
|
+
raise InternalCompilerError(f"Unsupported typed IR expression {type(expr).__name__} reached Uno backend.")
|
|
1198
|
+
|
|
1199
|
+
def _binary_expr(self, expr: IRBinary) -> str:
|
|
1200
|
+
left, right = self._expr(expr.left), self._expr(expr.right)
|
|
1201
|
+
def operation(a: str, b: str) -> str:
|
|
1202
|
+
if expr.type.kind == TypeKind.TIME_U32 and expr.op == IRBinaryOp.SUBTRACT:
|
|
1203
|
+
return f"((uint32_t)({a}) - (uint32_t)({b}))"
|
|
1204
|
+
if expr.op == IRBinaryOp.TRUE_DIVIDE:
|
|
1205
|
+
return f"_bj_true_div_f32((float)({a}), (float)({b}))"
|
|
1206
|
+
if expr.op == IRBinaryOp.MODULO:
|
|
1207
|
+
return f"_bj_mod_f32((float)({a}), (float)({b}))" if expr.type.kind == TypeKind.FLOAT else f"_bj_mod_i32({a}, {b})"
|
|
1208
|
+
if expr.type.kind == TypeKind.INT and expr.checked:
|
|
1209
|
+
helper = {IRBinaryOp.ADD:"_bj_i32_add", IRBinaryOp.SUBTRACT:"_bj_i32_sub", IRBinaryOp.MULTIPLY:"_bj_i32_mul"}.get(expr.op)
|
|
1210
|
+
if helper is not None: return f"{helper}({a}, {b})"
|
|
1211
|
+
op = {IRBinaryOp.ADD:"+", IRBinaryOp.SUBTRACT:"-", IRBinaryOp.MULTIPLY:"*"}.get(expr.op)
|
|
1212
|
+
if op is None: raise InternalCompilerError("Unknown binary IR operation reached backend.")
|
|
1213
|
+
return f"({a} {op} {b})"
|
|
1214
|
+
if self._expr_has_effects(expr.left) or self._expr_has_effects(expr.right):
|
|
1215
|
+
ltmp = self._new_temp("lhs"); rtmp = self._new_temp("rhs")
|
|
1216
|
+
return f"([&]() -> {expr.type.cpp_type} {{ {expr.left.type.cpp_type} {ltmp} = {left}; {expr.right.type.cpp_type} {rtmp} = {right}; return {operation(ltmp, rtmp)}; }}())"
|
|
1217
|
+
if (
|
|
1218
|
+
expr.type.kind == TypeKind.INT
|
|
1219
|
+
and not expr.checked
|
|
1220
|
+
and expr.op in {IRBinaryOp.ADD, IRBinaryOp.SUBTRACT, IRBinaryOp.MULTIPLY}
|
|
1221
|
+
and not self._is_int32_storage_load(expr.left)
|
|
1222
|
+
and not self._is_int32_storage_load(expr.right)
|
|
1223
|
+
):
|
|
1224
|
+
# AVR `int` is 16 bits: `1000 * 60` or `32767 + 29` written with C++ int
|
|
1225
|
+
# literals/Arduino macros would be computed (and overflow) in 16 bits even
|
|
1226
|
+
# though Barmaja proved the Python result fits Int32. Force Int32 arithmetic.
|
|
1227
|
+
left = f"(int32_t)({left})"
|
|
1228
|
+
return operation(left, right)
|
|
1229
|
+
|
|
1230
|
+
@staticmethod
|
|
1231
|
+
def _is_int32_storage_load(expr: IRExpr) -> bool:
|
|
1232
|
+
"""True when the emitted C++ expression already has type int32_t."""
|
|
1233
|
+
if expr.type.kind != TypeKind.INT:
|
|
1234
|
+
return False
|
|
1235
|
+
if isinstance(expr, IRLoad):
|
|
1236
|
+
return expr.symbol.kind not in {SymbolKind.API, SymbolKind.BUILTIN}
|
|
1237
|
+
if isinstance(expr, IRBinary):
|
|
1238
|
+
# checked helpers return int32_t; unchecked ADD/SUB/MUL are widened here;
|
|
1239
|
+
# modulo uses _bj_mod_i32 (int32_t).
|
|
1240
|
+
return True
|
|
1241
|
+
if isinstance(expr, IRIndex):
|
|
1242
|
+
return True # element of an int32_t fixed-list array
|
|
1243
|
+
if isinstance(expr, IRCall) and expr.kind == IRCallKind.USER_FUNCTION:
|
|
1244
|
+
return True # user functions returning Int are declared int32_t
|
|
1245
|
+
return False
|
|
1246
|
+
|
|
1247
|
+
def _comparison_expr(self, expr: IRCompareChain) -> str:
|
|
1248
|
+
names = [self._temp_name(t, "cmp") for t in expr.operand_temps]
|
|
1249
|
+
pieces = [f"{expr.operands[0].type.cpp_type} {names[0]} = {self._expr(expr.operands[0])};"]
|
|
1250
|
+
for i, op in enumerate(expr.operators):
|
|
1251
|
+
right = expr.operands[i + 1]
|
|
1252
|
+
pieces.append(f"{right.type.cpp_type} {names[i+1]} = {self._expr(right)};")
|
|
1253
|
+
cmp_expr = self._comparison_operand(expr.operands[i], right, op, names[i], names[i+1])
|
|
1254
|
+
pieces.append(f"if (!({cmp_expr})) return false;")
|
|
1255
|
+
pieces.append("return true;")
|
|
1256
|
+
return "([&]() -> bool { " + " ".join(pieces) + " }())"
|
|
1257
|
+
|
|
1258
|
+
def _comparison_operand(self, left_expr: IRExpr, right_expr: IRExpr, op: IRCompareOp, left: str, right: str) -> str:
|
|
1259
|
+
lt, rt = left_expr.type.kind, right_expr.type.kind
|
|
1260
|
+
if lt == TypeKind.STRING and rt == TypeKind.STRING:
|
|
1261
|
+
eq = f"_bj_str_eq({left}, {right})"
|
|
1262
|
+
if op == IRCompareOp.EQ: return eq
|
|
1263
|
+
if op == IRCompareOp.NE: return f"(!{eq})"
|
|
1264
|
+
raise InternalCompilerError("Ordered string comparison reached backend after validation.")
|
|
1265
|
+
if lt == TypeKind.TIME_U32 and rt == TypeKind.INT:
|
|
1266
|
+
right = f"((uint32_t)({right}))" if self._known_nonnegative_int(right_expr) else f"_bj_time_threshold({right})"
|
|
1267
|
+
elif rt == TypeKind.TIME_U32 and lt == TypeKind.INT:
|
|
1268
|
+
left = f"((uint32_t)({left}))" if self._known_nonnegative_int(left_expr) else f"_bj_time_threshold({left})"
|
|
1269
|
+
token = {IRCompareOp.EQ:"==", IRCompareOp.NE:"!=", IRCompareOp.LT:"<", IRCompareOp.LE:"<=", IRCompareOp.GT:">", IRCompareOp.GE:">="}[op]
|
|
1270
|
+
return f"({left} {token} {right})"
|
|
1271
|
+
|
|
1272
|
+
def _call(self, call: IRCall) -> str:
|
|
1273
|
+
args = [self._expr(a) for a in call.args]
|
|
1274
|
+
if call.kind == IRCallKind.BUILTIN:
|
|
1275
|
+
name = call.name
|
|
1276
|
+
if name == "round":
|
|
1277
|
+
arg = call.args[0]
|
|
1278
|
+
if len(args) == 1:
|
|
1279
|
+
return args[0] if arg.type.kind == TypeKind.INT else f"_bj_round_i32((float)({args[0]}))"
|
|
1280
|
+
ndigits = self._const_int(call.args[1])
|
|
1281
|
+
if ndigits is None: raise InternalCompilerError("round() ndigits is not constant in typed IR.")
|
|
1282
|
+
return f"_bj_round_i32_digits({args[0]}, {ndigits})" if arg.type.kind == TypeKind.INT else f"_bj_round_f32_digits((float)({args[0]}), {ndigits})"
|
|
1283
|
+
if name in {"min", "max"}: return self._minmax_expr(call, want_min=name == "min")
|
|
1284
|
+
if name == "int": return f"_bj_float_to_i32((float)({args[0]}))" if call.args[0].type.kind == TypeKind.FLOAT else f"((int32_t)({args[0]}))"
|
|
1285
|
+
if name == "float": return f"((float)({args[0]}))"
|
|
1286
|
+
if name == "bool": return f"((bool)({args[0]}))"
|
|
1287
|
+
if name == "abs": return f"_bj_i32_abs({args[0]})" if call.args[0].type.kind == TypeKind.INT else f"fabsf((float)({args[0]}))"
|
|
1288
|
+
if name == "len":
|
|
1289
|
+
typ = call.args[0].type
|
|
1290
|
+
if typ.kind != TypeKind.FIXED_LIST or typ.length is None: raise InternalCompilerError("len() reached backend without fixed-list IR type.")
|
|
1291
|
+
return str(typ.length)
|
|
1292
|
+
if name == "sum":
|
|
1293
|
+
arg = call.args[0]
|
|
1294
|
+
if not isinstance(arg, IRLoad) or arg.type.kind != TypeKind.FIXED_LIST or arg.type.length is None or arg.type.element_type is None:
|
|
1295
|
+
raise InternalCompilerError("sum() reached backend without fixed-list load.")
|
|
1296
|
+
idx = self._new_temp("sum_i"); acc = self._new_temp("sum_acc")
|
|
1297
|
+
if arg.type.element_type.kind == TypeKind.INT:
|
|
1298
|
+
return f"([&]() -> int32_t {{ int32_t {acc} = 0; for (int32_t {idx} = 0; {idx} < {arg.type.length}; ++{idx}) {acc} = _bj_i32_add({acc}, {arg.symbol.backend_name}[{idx}]); return {acc}; }}())"
|
|
1299
|
+
return f"([&]() -> float {{ float {acc} = 0.0f; for (int32_t {idx} = 0; {idx} < {arg.type.length}; ++{idx}) {acc} += {arg.symbol.backend_name}[{idx}]; return {acc}; }}())"
|
|
1300
|
+
raise InternalCompilerError(f"Unsupported builtin '{name}' reached Uno backend.")
|
|
1301
|
+
|
|
1302
|
+
if call.kind == IRCallKind.USER_FUNCTION:
|
|
1303
|
+
if call.function_symbol is None: raise InternalCompilerError("User-function IR call is missing function symbol.")
|
|
1304
|
+
callee = call.function_symbol.backend_name
|
|
1305
|
+
elif call.kind == IRCallKind.API_FUNCTION:
|
|
1306
|
+
callee = call.name
|
|
1307
|
+
if call.name == "tone":
|
|
1308
|
+
if not self._known_int_within(call.args[1], 31, 65535):
|
|
1309
|
+
args[1] = f"_bj_tone_frequency({args[1]})"; self._dynamic_helpers.add("_bj_tone_frequency")
|
|
1310
|
+
if len(call.args) == 3 and self._needs_nonnegative_guard(call.args[2]):
|
|
1311
|
+
args[2] = f"_bj_time_threshold({args[2]})"; self._dynamic_helpers.add("_bj_time_threshold")
|
|
1312
|
+
elif call.name == "delayMicroseconds":
|
|
1313
|
+
if not self._known_int_within(call.args[0], 0, 16383):
|
|
1314
|
+
args[0] = f"_bj_delay_us({args[0]})"; self._dynamic_helpers.add("_bj_delay_us")
|
|
1315
|
+
elif call.name == "map":
|
|
1316
|
+
callee = "_bj_arduino_map"; self._dynamic_helpers.add("_bj_arduino_map")
|
|
1317
|
+
elif call.name == "constrain":
|
|
1318
|
+
callee = "_bj_constrain_int"; self._dynamic_helpers.add("_bj_constrain_int")
|
|
1319
|
+
if call.name == "analogWrite" and len(call.args) >= 2 and id(call) not in self._safe_pwm_call_ids and not self._known_pwm_safe(call.args[1], self._active_int_ranges):
|
|
1320
|
+
args[1] = f"_bj_pwm_value({args[1]})"; self._dynamic_helpers.add("_bj_pwm_value")
|
|
1321
|
+
if call.name == "pinMode" and len(call.args) >= 2 and not self._known_int_within(call.args[1], 0, 2):
|
|
1322
|
+
args[1] = f"_bj_pin_mode({args[1]})"; self._dynamic_helpers.add("_bj_pin_mode")
|
|
1323
|
+
if call.name == "digitalWrite" and len(call.args) >= 2 and not self._known_int_within(call.args[1], 0, 1):
|
|
1324
|
+
args[1] = f"_bj_digital_level({args[1]})"; self._dynamic_helpers.add("_bj_digital_level")
|
|
1325
|
+
pin_guard = _PIN_GUARDS.get(call.name)
|
|
1326
|
+
if pin_guard is not None and call.args and not self._pin_is_proven(call.args[0], _PIN_SETS[pin_guard]):
|
|
1327
|
+
args[0] = f"{pin_guard}({args[0]})"; self._dynamic_helpers.add(pin_guard)
|
|
1328
|
+
if call.name == "delay" and call.args and self._needs_nonnegative_guard(call.args[0]):
|
|
1329
|
+
args[0] = f"_bj_time_threshold({args[0]})"; self._dynamic_helpers.add("_bj_time_threshold")
|
|
1330
|
+
elif call.kind == IRCallKind.OBJECT_METHOD:
|
|
1331
|
+
if call.owner_symbol is None or call.method_name is None: raise InternalCompilerError("Object-method IR call is incomplete.")
|
|
1332
|
+
callee = f"{call.owner_symbol.backend_name}.{call.method_name}"
|
|
1333
|
+
guard = self._object_arg_guard(call)
|
|
1334
|
+
if guard is not None:
|
|
1335
|
+
args[0] = f"{guard}({args[0]})"; self._dynamic_helpers.add(guard)
|
|
1336
|
+
cls = call.owner_symbol.type.hardware_class
|
|
1337
|
+
if cls == "Servo" and call.method_name == "writeMicroseconds" and call.args:
|
|
1338
|
+
if not self._known_int_within(call.args[0], 0, 3000):
|
|
1339
|
+
args[0] = f"_bj_servo_pulse({args[0]})"; self._dynamic_helpers.add("_bj_servo_pulse")
|
|
1340
|
+
if cls == "Servo" and call.method_name == "attach" and len(call.args) == 3:
|
|
1341
|
+
return self._ordered_servo_attach(call, callee, args)
|
|
1342
|
+
if cls == "LiquidCrystal_I2C" and call.method_name == "begin" and len(call.args) >= 2:
|
|
1343
|
+
for index in (0, 1):
|
|
1344
|
+
if not self._known_int_within(call.args[index], 1, 255):
|
|
1345
|
+
args[index] = f"_bj_lcd_dimension({args[index]})"; self._dynamic_helpers.add("_bj_lcd_dimension")
|
|
1346
|
+
if len(call.args) >= 3 and not self._known_int_in_set(call.args[2], {0, 4}):
|
|
1347
|
+
args[2] = f"_bj_lcd_charsize({args[2]})"; self._dynamic_helpers.add("_bj_lcd_charsize")
|
|
1348
|
+
if cls == "LiquidCrystal_I2C" and call.method_name == "setCursor" and len(call.args) >= 2:
|
|
1349
|
+
if not self._known_int_within(call.args[0], 0, 255):
|
|
1350
|
+
args[0] = f"_bj_lcd_cursor_column({args[0]})"; self._dynamic_helpers.add("_bj_lcd_cursor_column")
|
|
1351
|
+
if not self._known_int_within(call.args[1], 0, 3):
|
|
1352
|
+
args[1] = f"_bj_lcd_cursor_row({args[1]})"; self._dynamic_helpers.add("_bj_lcd_cursor_row")
|
|
1353
|
+
elif call.kind == IRCallKind.SINGLETON_METHOD:
|
|
1354
|
+
if call.owner_name is None or call.method_name is None: raise InternalCompilerError("Singleton-method IR call is incomplete.")
|
|
1355
|
+
callee = f"{call.owner_name}.{call.method_name}"
|
|
1356
|
+
if call.owner_name == "Wire":
|
|
1357
|
+
if call.method_name in {"beginTransmission", "requestFrom"} and call.args and not self._known_int_within(call.args[0], 0, 127):
|
|
1358
|
+
args[0] = f"_bj_i2c_address({args[0]})"; self._dynamic_helpers.add("_bj_i2c_address")
|
|
1359
|
+
if call.method_name == "write" and call.args and not self._known_int_within(call.args[0], 0, 255):
|
|
1360
|
+
args[0] = f"_bj_byte_value({args[0]})"; self._dynamic_helpers.add("_bj_byte_value")
|
|
1361
|
+
if call.method_name == "requestFrom" and len(call.args) >= 2 and not self._known_int_within(call.args[1], 0, 255):
|
|
1362
|
+
args[1] = f"_bj_wire_count({args[1]})"; self._dynamic_helpers.add("_bj_wire_count")
|
|
1363
|
+
# Pinned AVR TwoWire overloads beginTransmission(uint8_t|int) and
|
|
1364
|
+
# requestFrom(uint8_t,uint8_t|int,int): an int32_t argument (any Python
|
|
1365
|
+
# variable) is ambiguous in C++. The values are already proven or guarded
|
|
1366
|
+
# to 0..127 / 0..255, so select the uint8_t overloads explicitly.
|
|
1367
|
+
# The cast is applied at the call itself (also to ordered argument
|
|
1368
|
+
# temporaries, which are declared int32_t).
|
|
1369
|
+
if call.method_name == "beginTransmission" and call.args:
|
|
1370
|
+
return self._ordered_call(call, callee, args, casts={0: "uint8_t"})
|
|
1371
|
+
if call.method_name == "requestFrom" and len(call.args) >= 2:
|
|
1372
|
+
return self._ordered_call(call, callee, args, casts={0: "uint8_t", 1: "uint8_t"})
|
|
1373
|
+
if call.owner_name == "Serial" and call.method_name in {"print", "println"} and len(call.args) == 2:
|
|
1374
|
+
if not self._known_int_within(call.args[1], 0, 255):
|
|
1375
|
+
args[1] = f"_bj_arduino_format({args[1]})"; self._dynamic_helpers.add("_bj_arduino_format")
|
|
1376
|
+
else:
|
|
1377
|
+
raise InternalCompilerError("Unknown IR call category reached Uno backend.")
|
|
1378
|
+
return self._ordered_call(call, callee, args)
|
|
1379
|
+
|
|
1380
|
+
def _pin_is_proven(self, expr: IRExpr, allowed: frozenset[int]) -> bool:
|
|
1381
|
+
"""True only when the pin value is fixed at compile time (or range-proven) and valid.
|
|
1382
|
+
|
|
1383
|
+
Target analysis rejects provably wrong pins; everything it cannot fix at
|
|
1384
|
+
compile time gets a runtime check so an invalid pin never reaches the core.
|
|
1385
|
+
"""
|
|
1386
|
+
value = self._const_int(expr)
|
|
1387
|
+
if value is not None:
|
|
1388
|
+
return value in allowed
|
|
1389
|
+
if isinstance(expr, IRLoad):
|
|
1390
|
+
if expr.symbol.kind in {SymbolKind.API, SymbolKind.BUILTIN}:
|
|
1391
|
+
from ..api_registry import CONSTANT_VALUES
|
|
1392
|
+
return CONSTANT_VALUES.get(expr.symbol.source_name) in allowed
|
|
1393
|
+
if expr.symbol.symbol_id in self._constant_symbol_values:
|
|
1394
|
+
return self._constant_symbol_values[expr.symbol.symbol_id] in allowed
|
|
1395
|
+
facts = self._active_int_ranges.get(expr.symbol.symbol_id)
|
|
1396
|
+
if facts is not None:
|
|
1397
|
+
return all(v in allowed for v in range(facts[0], facts[1] + 1))
|
|
1398
|
+
return False
|
|
1399
|
+
|
|
1400
|
+
def _needs_nonnegative_guard(self, expr: IRExpr) -> bool:
|
|
1401
|
+
value = self._const_int(expr)
|
|
1402
|
+
if value is not None:
|
|
1403
|
+
return value < 0
|
|
1404
|
+
if isinstance(expr, IRLoad):
|
|
1405
|
+
facts = self._active_int_ranges.get(expr.symbol.symbol_id)
|
|
1406
|
+
if facts is not None and facts[0] >= 0:
|
|
1407
|
+
return False
|
|
1408
|
+
return True
|
|
1409
|
+
|
|
1410
|
+
def _object_arg_guard(self, call: IRCall) -> str | None:
|
|
1411
|
+
"""Runtime argument policy for hardware methods (registry constraints)."""
|
|
1412
|
+
if not call.args or call.owner_symbol is None:
|
|
1413
|
+
return None
|
|
1414
|
+
cls = call.owner_symbol.type.hardware_class
|
|
1415
|
+
if cls == "Servo" and call.method_name == "write":
|
|
1416
|
+
value = self._const_int(call.args[0])
|
|
1417
|
+
if value is not None and 0 <= value <= 180:
|
|
1418
|
+
return None
|
|
1419
|
+
if isinstance(call.args[0], IRLoad):
|
|
1420
|
+
facts = self._active_int_ranges.get(call.args[0].symbol.symbol_id)
|
|
1421
|
+
if facts is not None and facts[0] >= 0 and facts[1] <= 180:
|
|
1422
|
+
return None
|
|
1423
|
+
return "_bj_servo_angle"
|
|
1424
|
+
if cls == "Ultrasonic" and call.method_name == "setTimeout":
|
|
1425
|
+
return "_bj_time_threshold" if self._needs_nonnegative_guard(call.args[0]) else None
|
|
1426
|
+
return None
|
|
1427
|
+
|
|
1428
|
+
def _ordered_servo_attach(self, call: IRCall, callee: str, args: list[str]) -> str:
|
|
1429
|
+
# Servo 1.3.0 stores min/max calibration offsets in int8_t. Materialize
|
|
1430
|
+
# all Python arguments in order, validate the pair once, then call the library.
|
|
1431
|
+
self._dynamic_helpers.add("_bj_servo_attach_bounds")
|
|
1432
|
+
decls: list[str] = []; names: list[str] = []
|
|
1433
|
+
for arg, text in zip(call.args, args):
|
|
1434
|
+
name = self._new_temp("servo_attach_arg"); names.append(name)
|
|
1435
|
+
decls.append(f"{arg.type.cpp_type} {name} = {text};")
|
|
1436
|
+
decls.append(f"_bj_servo_attach_bounds({names[1]}, {names[2]});")
|
|
1437
|
+
invocation = f"{callee}({', '.join(names)})"
|
|
1438
|
+
if call.type.kind == TypeKind.VOID:
|
|
1439
|
+
return "([&]() { " + " ".join(decls) + f" {invocation}; }}())"
|
|
1440
|
+
return f"([&]() -> {call.type.cpp_type} {{ " + " ".join(decls) + f" return {invocation}; }}())"
|
|
1441
|
+
|
|
1442
|
+
def _ordered_call(self, call: IRCall, callee: str, args: list[str], *, casts: dict[int, str] | None = None) -> str:
|
|
1443
|
+
casts = casts or {}
|
|
1444
|
+
def cast(index: int, text: str) -> str:
|
|
1445
|
+
return f"({casts[index]})({text})" if index in casts else text
|
|
1446
|
+
if len(call.args) <= 1 or not any(self._expr_has_effects(a) for a in call.args):
|
|
1447
|
+
return f"{callee}({', '.join(cast(i, a) for i, a in enumerate(args))})"
|
|
1448
|
+
decls: list[str] = []; names: list[str] = []
|
|
1449
|
+
for arg, text in zip(call.args, args):
|
|
1450
|
+
name = self._new_temp("arg"); names.append(name); decls.append(f"{arg.type.cpp_type} {name} = {text};")
|
|
1451
|
+
invocation = f"{callee}({', '.join(cast(i, n) for i, n in enumerate(names))})"
|
|
1452
|
+
if call.type.kind == TypeKind.VOID: return "([&]() { " + " ".join(decls) + f" {invocation}; }}())"
|
|
1453
|
+
return f"([&]() -> {call.type.cpp_type} {{ " + " ".join(decls) + f" return {invocation}; }}())"
|
|
1454
|
+
|
|
1455
|
+
def _minmax_expr(self, call: IRCall, *, want_min: bool) -> str:
|
|
1456
|
+
names: list[str] = []; parts: list[str] = []
|
|
1457
|
+
for arg in call.args:
|
|
1458
|
+
name = self._new_temp("minmax_arg"); names.append(name); parts.append(f"{arg.type.cpp_type} {name} = {self._expr(arg)};")
|
|
1459
|
+
result = self._new_temp("minmax"); parts.append(f"{call.type.cpp_type} {result} = {names[0]};")
|
|
1460
|
+
token = "<" if want_min else ">"
|
|
1461
|
+
for name in names[1:]: parts.append(f"if ({name} {token} {result}) {result} = {name};")
|
|
1462
|
+
parts.append(f"return {result};")
|
|
1463
|
+
return f"([&]() -> {call.type.cpp_type} {{ " + " ".join(parts) + " }())"
|
|
1464
|
+
|
|
1465
|
+
def _python_print(self, stmt: IRPrint, indent: int) -> list[str]:
|
|
1466
|
+
pad = " " * indent; preludes: list[str] = []; parts: list[_OutputPart] = []
|
|
1467
|
+
for i, arg in enumerate(stmt.values):
|
|
1468
|
+
if i: parts.append(_OutputPart(literal=" "))
|
|
1469
|
+
pre, argparts = self._prepare_output_arg(arg); preludes.extend(pre); parts.extend(argparts)
|
|
1470
|
+
out = [pad + x for x in preludes]
|
|
1471
|
+
for part in parts: out.extend(self._emit_output_part("Serial", part, indent, lcd_mode=False))
|
|
1472
|
+
if stmt.newline: out.append(pad + "Serial.println();")
|
|
1473
|
+
return out
|
|
1474
|
+
|
|
1475
|
+
def _explicit_output_stmt(self, expr: IRExpr, indent: int) -> list[str] | None:
|
|
1476
|
+
if not isinstance(expr, IRCall) or expr.kind not in {IRCallKind.OBJECT_METHOD, IRCallKind.SINGLETON_METHOD}: return None
|
|
1477
|
+
if expr.method_name not in {"print", "println"} or len(expr.args) != 1: return None
|
|
1478
|
+
owner = expr.owner_symbol.backend_name if expr.owner_symbol is not None else expr.owner_name
|
|
1479
|
+
if owner is None: return None
|
|
1480
|
+
lcd_mode = expr.owner_symbol is not None and expr.owner_symbol.type.hardware_class == "LiquidCrystal_I2C"
|
|
1481
|
+
arg = expr.args[0]; pad = " " * indent
|
|
1482
|
+
if isinstance(arg, IRConstant) and isinstance(arg.value, str): return [f"{pad}{owner}.{expr.method_name}({self._flash_literal(arg.value, lcd_mode=lcd_mode)});"]
|
|
1483
|
+
if isinstance(arg, IRFormatString):
|
|
1484
|
+
preludes, parts = self._prepare_output_arg(arg); out = [pad + x for x in preludes]
|
|
1485
|
+
for part in parts: out.extend(self._emit_output_part(owner, part, indent, lcd_mode=lcd_mode))
|
|
1486
|
+
if expr.method_name == "println": out.append(f"{pad}{owner}.println();")
|
|
1487
|
+
return out
|
|
1488
|
+
return None
|
|
1489
|
+
|
|
1490
|
+
def _prepare_output_arg(self, expr: IRExpr) -> tuple[list[str], list[_OutputPart]]:
|
|
1491
|
+
if isinstance(expr, IRFormatString):
|
|
1492
|
+
preludes: list[str] = []; parts: list[_OutputPart] = []
|
|
1493
|
+
for part in expr.parts:
|
|
1494
|
+
if isinstance(part, str):
|
|
1495
|
+
if part: parts.append(_OutputPart(literal=part))
|
|
1496
|
+
else:
|
|
1497
|
+
pre, out = self._prepare_output_value(part.value, part.precision); preludes.extend(pre); parts.append(out)
|
|
1498
|
+
return preludes, parts
|
|
1499
|
+
if isinstance(expr, IRConstant) and isinstance(expr.value, str): return [], [_OutputPart(literal=expr.value)]
|
|
1500
|
+
pre, part = self._prepare_output_value(expr, None); return pre, [part]
|
|
1501
|
+
|
|
1502
|
+
def _prepare_output_value(self, expr: IRExpr, precision: int | None) -> tuple[list[str], _OutputPart]:
|
|
1503
|
+
if expr.type.kind not in {TypeKind.BOOL, TypeKind.INT, TypeKind.TIME_U32, TypeKind.FLOAT, TypeKind.STRING}: raise InternalCompilerError(f"Non-printable IR type {expr.type.kind.value} reached output backend.")
|
|
1504
|
+
text = self._expr(expr)
|
|
1505
|
+
if isinstance(expr, IRConstant): return [], _OutputPart(expression=text, type_kind=expr.type.kind, precision=precision)
|
|
1506
|
+
temp = self._new_temp("print_arg")
|
|
1507
|
+
return [f"{expr.type.cpp_type} {temp} = {text};"], _OutputPart(expression=temp, type_kind=expr.type.kind, precision=precision)
|
|
1508
|
+
|
|
1509
|
+
def _emit_output_part(self, sink: str, part: _OutputPart, indent: int, *, lcd_mode: bool) -> list[str]:
|
|
1510
|
+
pad = " " * indent
|
|
1511
|
+
if part.literal is not None: return [f"{pad}{sink}.print({self._flash_literal(part.literal, lcd_mode=lcd_mode)});"]
|
|
1512
|
+
if part.expression is None or part.type_kind is None: raise InternalCompilerError("Malformed output part reached Uno backend.")
|
|
1513
|
+
e = part.expression
|
|
1514
|
+
if part.precision is not None: return [f"{pad}_bj_print_py_fixed({sink}, (float)({e}), {part.precision});"]
|
|
1515
|
+
if part.type_kind == TypeKind.BOOL: return [f"{pad}if ({e}) {sink}.print(F(\"True\")); else {sink}.print(F(\"False\"));"]
|
|
1516
|
+
if part.type_kind == TypeKind.INT: return [f"{pad}{sink}.print((int32_t)({e}));"]
|
|
1517
|
+
if part.type_kind == TypeKind.TIME_U32: return [f"{pad}{sink}.print((uint32_t)({e}));"]
|
|
1518
|
+
if part.type_kind == TypeKind.FLOAT: return [f"{pad}_bj_print_py_float({sink}, (float)({e}));"]
|
|
1519
|
+
if part.type_kind == TypeKind.STRING: return [f"{pad}{sink}.print({e});"]
|
|
1520
|
+
raise InternalCompilerError("Unsupported output type reached Uno backend.")
|
|
1521
|
+
|
|
1522
|
+
def _required_numeric_helpers(self) -> set[str]:
|
|
1523
|
+
need: set[str] = set()
|
|
1524
|
+
for item in _walk(self.ir):
|
|
1525
|
+
if isinstance(item, IRIndex) and self._const_int(item.index) is None: need.add("_bj_list_index")
|
|
1526
|
+
elif isinstance(item, IRIndexStore) and self._const_int(item.index) is None: need.add("_bj_list_index")
|
|
1527
|
+
elif isinstance(item, IRRangeLoop):
|
|
1528
|
+
# The range iterator no longer uses checked addition (see _stmt); only a
|
|
1529
|
+
# dynamic step needs the zero-step trap.
|
|
1530
|
+
if self._const_int(item.step) is None: need.add("_bj_numeric_fail")
|
|
1531
|
+
elif isinstance(item, IRFixedListLoop) and item.index_start is not None: need.add("_bj_i32_add")
|
|
1532
|
+
elif isinstance(item, IRBinary):
|
|
1533
|
+
if item.op == IRBinaryOp.TRUE_DIVIDE: need.add("_bj_true_div_f32")
|
|
1534
|
+
elif item.op == IRBinaryOp.MODULO: need.add("_bj_mod_f32" if item.type.kind == TypeKind.FLOAT else "_bj_mod_i32")
|
|
1535
|
+
elif item.checked and item.type.kind == TypeKind.INT:
|
|
1536
|
+
name = {IRBinaryOp.ADD:"_bj_i32_add", IRBinaryOp.SUBTRACT:"_bj_i32_sub", IRBinaryOp.MULTIPLY:"_bj_i32_mul"}.get(item.op)
|
|
1537
|
+
if name: need.add(name)
|
|
1538
|
+
elif isinstance(item, IRAugStore):
|
|
1539
|
+
if item.op == IRBinaryOp.TRUE_DIVIDE: need.add("_bj_true_div_f32")
|
|
1540
|
+
elif item.op == IRBinaryOp.MODULO: need.add("_bj_mod_f32" if item.target.type.kind == TypeKind.FLOAT else "_bj_mod_i32")
|
|
1541
|
+
elif item.target.type.kind == TypeKind.INT:
|
|
1542
|
+
name = {IRBinaryOp.ADD:"_bj_i32_add", IRBinaryOp.SUBTRACT:"_bj_i32_sub", IRBinaryOp.MULTIPLY:"_bj_i32_mul"}.get(item.op)
|
|
1543
|
+
if name: need.add(name)
|
|
1544
|
+
elif isinstance(item, IRUnary) and item.type.kind == TypeKind.INT and IREffect.MAY_FAIL in item.effects: need.add("_bj_i32_neg")
|
|
1545
|
+
elif isinstance(item, IRCompareChain):
|
|
1546
|
+
for a, b in zip(item.operands, item.operands[1:]):
|
|
1547
|
+
if a.type.kind == b.type.kind == TypeKind.STRING: need.add("_bj_str_eq")
|
|
1548
|
+
if (a.type.kind == TypeKind.TIME_U32 and b.type.kind == TypeKind.INT and not self._known_nonnegative_int(b)) or (b.type.kind == TypeKind.TIME_U32 and a.type.kind == TypeKind.INT and not self._known_nonnegative_int(a)): need.add("_bj_time_threshold")
|
|
1549
|
+
elif isinstance(item, IRCall):
|
|
1550
|
+
if item.kind == IRCallKind.API_FUNCTION and item.name == "analogWrite" and len(item.args) >= 2 and id(item) not in self._safe_pwm_call_ids and not self._known_pwm_safe(item.args[1]): need.add("_bj_pwm_value")
|
|
1551
|
+
if item.kind == IRCallKind.BUILTIN:
|
|
1552
|
+
if item.name == "int" and item.args and item.args[0].type.kind == TypeKind.FLOAT: need.add("_bj_float_to_i32")
|
|
1553
|
+
elif item.name == "round" and item.args:
|
|
1554
|
+
if len(item.args) == 1 and item.args[0].type.kind == TypeKind.FLOAT: need.add("_bj_round_i32")
|
|
1555
|
+
elif len(item.args) == 2 and item.args[0].type.kind == TypeKind.FLOAT: need.add("_bj_round_f32_digits")
|
|
1556
|
+
elif len(item.args) == 2 and item.args[0].type.kind == TypeKind.INT: need.add("_bj_round_i32_digits")
|
|
1557
|
+
elif item.name == "abs" and item.args and item.args[0].type.kind == TypeKind.INT: need.add("_bj_i32_abs")
|
|
1558
|
+
elif item.name == "sum" and item.args and item.args[0].type.kind == TypeKind.FIXED_LIST and item.args[0].type.element_type is not None and item.args[0].type.element_type.kind == TypeKind.INT: need.add("_bj_i32_add")
|
|
1559
|
+
return _helper_closure(need)
|
|
1560
|
+
|
|
1561
|
+
@staticmethod
|
|
1562
|
+
def _helper_order(names: set[str]) -> list[str]:
|
|
1563
|
+
order = [_helper_name(line) for line in _NUMERIC_HELPER_BLOB.splitlines() if line.startswith("static ")]
|
|
1564
|
+
return [name for name in order if name in names]
|
|
1565
|
+
|
|
1566
|
+
def _needs_python_float_formatter(self) -> bool:
|
|
1567
|
+
for item in _walk(self.ir):
|
|
1568
|
+
if isinstance(item, IRPrint):
|
|
1569
|
+
if any(self._output_arg_needs_float_formatter(arg) for arg in item.values): return True
|
|
1570
|
+
elif isinstance(item, IRExprStmt) and isinstance(item.expression, IRCall):
|
|
1571
|
+
call = item.expression
|
|
1572
|
+
if call.method_name in {"print", "println"} and len(call.args) == 1 and isinstance(call.args[0], IRFormatString) and self._output_arg_needs_float_formatter(call.args[0]): return True
|
|
1573
|
+
return False
|
|
1574
|
+
|
|
1575
|
+
def _needs_fixed_float_formatter(self) -> bool:
|
|
1576
|
+
return any(isinstance(item, IRFormattedValue) and item.precision is not None for item in _walk(self.ir))
|
|
1577
|
+
|
|
1578
|
+
@staticmethod
|
|
1579
|
+
def _output_arg_needs_float_formatter(expr: IRExpr) -> bool:
|
|
1580
|
+
if isinstance(expr, IRFormatString): return any(isinstance(p, IRFormattedValue) and p.precision is None and p.value.type.kind == TypeKind.FLOAT for p in expr.parts)
|
|
1581
|
+
return expr.type.kind == TypeKind.FLOAT
|
|
1582
|
+
|
|
1583
|
+
@staticmethod
|
|
1584
|
+
def _expr_has_effects(expr: IRExpr) -> bool: return bool(expr.effects)
|
|
1585
|
+
|
|
1586
|
+
@staticmethod
|
|
1587
|
+
def _const_int(expr: IRExpr) -> int | None:
|
|
1588
|
+
if isinstance(expr, IRConstant) and isinstance(expr.value, int) and not isinstance(expr.value, bool): return expr.value
|
|
1589
|
+
if isinstance(expr, IRUnary) and expr.op == IRUnaryOp.NEGATE and expr.type.kind == TypeKind.INT:
|
|
1590
|
+
# Nested literal negation (e.g. round(x, --2)) is folded exactly like the
|
|
1591
|
+
# type/numeric phases fold it, so accepted programs never reach an ICE.
|
|
1592
|
+
inner = UnoCppBackend._const_int(expr.operand)
|
|
1593
|
+
return -inner if inner is not None else None
|
|
1594
|
+
return None
|
|
1595
|
+
|
|
1596
|
+
def _known_nonnegative_int(self, expr: IRExpr) -> bool:
|
|
1597
|
+
value = self._const_int(expr); return value is not None and value >= 0
|
|
1598
|
+
|
|
1599
|
+
def _known_int_within(self, expr: IRExpr, low: int, high: int) -> bool:
|
|
1600
|
+
value = self._const_int(expr)
|
|
1601
|
+
if value is not None:
|
|
1602
|
+
return low <= value <= high
|
|
1603
|
+
if isinstance(expr, IRLoad):
|
|
1604
|
+
if expr.symbol.kind in {SymbolKind.API, SymbolKind.BUILTIN}:
|
|
1605
|
+
from ..api_registry import CONSTANT_VALUES
|
|
1606
|
+
value = CONSTANT_VALUES.get(expr.symbol.source_name)
|
|
1607
|
+
return value is not None and low <= value <= high
|
|
1608
|
+
if expr.symbol.symbol_id in self._constant_symbol_values:
|
|
1609
|
+
value = self._constant_symbol_values[expr.symbol.symbol_id]
|
|
1610
|
+
return low <= value <= high
|
|
1611
|
+
facts = self._active_int_ranges.get(expr.symbol.symbol_id)
|
|
1612
|
+
return facts is not None and low <= facts[0] and facts[1] <= high
|
|
1613
|
+
return False
|
|
1614
|
+
|
|
1615
|
+
def _known_int_in_set(self, expr: IRExpr, allowed: set[int]) -> bool:
|
|
1616
|
+
value = self._const_int(expr)
|
|
1617
|
+
if value is not None:
|
|
1618
|
+
return value in allowed
|
|
1619
|
+
if isinstance(expr, IRLoad):
|
|
1620
|
+
if expr.symbol.kind in {SymbolKind.API, SymbolKind.BUILTIN}:
|
|
1621
|
+
from ..api_registry import CONSTANT_VALUES
|
|
1622
|
+
value = CONSTANT_VALUES.get(expr.symbol.source_name)
|
|
1623
|
+
return value in allowed if value is not None else False
|
|
1624
|
+
if expr.symbol.symbol_id in self._constant_symbol_values:
|
|
1625
|
+
return self._constant_symbol_values[expr.symbol.symbol_id] in allowed
|
|
1626
|
+
facts = self._active_int_ranges.get(expr.symbol.symbol_id)
|
|
1627
|
+
if facts is not None and facts[1] - facts[0] <= 16:
|
|
1628
|
+
return all(v in allowed for v in range(facts[0], facts[1] + 1))
|
|
1629
|
+
return False
|
|
1630
|
+
|
|
1631
|
+
def _known_pwm_safe(self, expr: IRExpr, ranges: dict[object, tuple[int, int]] | None = None) -> bool:
|
|
1632
|
+
value = self._const_int(expr)
|
|
1633
|
+
if value is not None:
|
|
1634
|
+
return 0 <= value <= 255
|
|
1635
|
+
if isinstance(expr, IRLoad):
|
|
1636
|
+
facts = (ranges or {}).get(expr.symbol.symbol_id)
|
|
1637
|
+
return facts is not None and 0 <= facts[0] and facts[1] <= 255
|
|
1638
|
+
return False
|
|
1639
|
+
|
|
1640
|
+
def _list_index_expr(self, index: IRExpr, list_type) -> str:
|
|
1641
|
+
if list_type.kind != TypeKind.FIXED_LIST or list_type.length is None: raise InternalCompilerError("List indexing reached backend without fixed-list type.")
|
|
1642
|
+
literal = self._const_int(index)
|
|
1643
|
+
return str(literal) if literal is not None else f"_bj_list_index({self._expr(index)}, {list_type.length})"
|
|
1644
|
+
|
|
1645
|
+
def _new_temp(self, hint: str) -> str:
|
|
1646
|
+
self._temp_counter += 1; return f"_bj_tmp_{hint}_{self._temp_counter:04d}"
|
|
1647
|
+
|
|
1648
|
+
def _temp_name(self, temp: IRTempRef, hint: str) -> str:
|
|
1649
|
+
name = self._temp_names.get(temp.temp_id)
|
|
1650
|
+
if name is None:
|
|
1651
|
+
name = self._new_temp(hint); self._temp_names[temp.temp_id] = name
|
|
1652
|
+
return name
|
|
1653
|
+
|
|
1654
|
+
@staticmethod
|
|
1655
|
+
def _flash_literal(text: str, *, lcd_mode: bool = False) -> str: return f"F({UnoCppBackend._cpp_string_literal(text, lcd_mode=lcd_mode)})"
|
|
1656
|
+
|
|
1657
|
+
@staticmethod
|
|
1658
|
+
def _cpp_string_literal(text: str, *, lcd_mode: bool = False) -> str:
|
|
1659
|
+
data = bytearray()
|
|
1660
|
+
if lcd_mode:
|
|
1661
|
+
for ch in text:
|
|
1662
|
+
code = ord(ch)
|
|
1663
|
+
if 0x20 <= code <= 0x7E: data.append(code)
|
|
1664
|
+
elif ch == "°": data.append(0xDF)
|
|
1665
|
+
else: raise InternalCompilerError("Unsupported LCD character reached backend after target validation.")
|
|
1666
|
+
else: data.extend(text.encode("utf-8"))
|
|
1667
|
+
pieces: list[str] = []
|
|
1668
|
+
for byte in data:
|
|
1669
|
+
if byte == 0x5C: pieces.append("\\\\")
|
|
1670
|
+
elif byte == 0x22: pieces.append('\\"')
|
|
1671
|
+
elif byte == 0x0A: pieces.append("\\n")
|
|
1672
|
+
elif byte == 0x0D: pieces.append("\\r")
|
|
1673
|
+
elif byte == 0x09: pieces.append("\\t")
|
|
1674
|
+
elif 0x20 <= byte <= 0x7E: pieces.append(chr(byte))
|
|
1675
|
+
else: pieces.append(f"\\{byte:03o}")
|
|
1676
|
+
return '"' + "".join(pieces) + '"'
|
|
1677
|
+
|
|
1678
|
+
|
|
1679
|
+
def generate_uno_cpp(ir_module: IRModule, target_plan: TargetPlan) -> GeneratedSketch:
|
|
1680
|
+
return UnoCppBackend(ir_module, target_plan).generate()
|