diff --git a/CMakeLists.txt b/CMakeLists.txt index b2fed34d8..4856608c6 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -18,75 +18,8 @@ if (CONFIG_ARDUINO_API) zephyr_include_directories(${variant_dir}) if (CONFIG_USE_ARDUINO_API_RUST_IMPLEMENTATION) - # quote from https://github.com/zephyrproject-rtos/zephyr-lang-rust/blob/main/CMakeLists.txt - function (rust_target_arch RUST_TARGET) - # Map Zephyr targets to LLVM targets. - if(CONFIG_CPU_CORTEX_M) - if(CONFIG_CPU_CORTEX_M0 OR CONFIG_CPU_CORTEX_M0PLUS OR CONFIG_CPU_CORTEX_M1) - set(${RUST_TARGET} "thumbv6m-none-eabi" PARENT_SCOPE) - elseif(CONFIG_CPU_CORTEX_M3) - set(${RUST_TARGET} "thumbv7m-none-eabi" PARENT_SCOPE) - elseif(CONFIG_CPU_CORTEX_M4 OR CONFIG_CPU_CORTEX_M7) - if(CONFIG_FP_HARDABI OR FORCE_FP_HARDABI) - set(${RUST_TARGET} "thumbv7em-none-eabihf" PARENT_SCOPE) - else() - set(${RUST_TARGET} "thumbv7em-none-eabi" PARENT_SCOPE) - endif() - elseif(CONFIG_CPU_CORTEX_M23) - set(${RUST_TARGET} "thumbv8m.base-none-eabi" PARENT_SCOPE) - elseif(CONFIG_CPU_CORTEX_M33 OR CONFIG_CPU_CORTEX_M55) - # Not a typo, Zephyr, uses ARMV7_M_ARMV8_M_FP to select the FP even on v8m. - if(CONFIG_FP_HARDABI OR FORCE_FP_HARDABI) - set(${RUST_TARGET} "thumbv8m.main-none-eabihf" PARENT_SCOPE) - else() - set(${RUST_TARGET} "thumbv8m.main-none-eabi" PARENT_SCOPE) - endif() - - # Todo: The M55 is thumbv8.1m.main-none-eabi, which can be added when Rust - # gain support for this target. - else() - message(FATAL_ERROR "Unknown Cortex-M target.") - endif() - elseif(CONFIG_RISCV) - if(CONFIG_RISCV_ISA_RV64I) - # TODO: Should fail if the extensions don't match. - set(${RUST_TARGET} "riscv64imac-unknown-none-elf" PARENT_SCOPE) - elseif(CONFIG_RISCV_ISA_RV32I) - # TODO: We have multiple choices, try to pick the best. - set(${RUST_TARGET} "riscv32i-unknown-none-elf" PARENT_SCOPE) - else() - message(FATAL_ERROR "Rust: Unsupported riscv ISA") - endif() - elseif(CONFIG_ARCH_POSIX AND CONFIG_64BIT AND (${CMAKE_HOST_SYSTEM_PROCESSOR} MATCHES "x86_64")) - set(${RUST_TARGET} "x86_64-unknown-none" PARENT_SCOPE) - elseif(CONFIG_ARCH_POSIX AND CONFIG_64BIT AND (${CMAKE_HOST_SYSTEM_PROCESSOR} MATCHES "aarch64")) - set(${RUST_TARGET} "aarch64-unknown-none" PARENT_SCOPE) - else() - message(FATAL_ERROR "Rust: Add support for other target") - endif() - endfunction() - - rust_target_arch(RUST_TARGET_TRIPLE) - - set(RUST_CRATE_DIR ${CMAKE_CURRENT_SOURCE_DIR}/rust) - set(RUST_OUT_DIR ${CMAKE_CURRENT_BINARY_DIR}/rust) - set(RUST_LIB ${RUST_OUT_DIR}/target/${RUST_TARGET_TRIPLE}/release/libarduinocore_api_rust.a) - - add_custom_command( - OUTPUT ${RUST_LIB} - COMMAND ${CMAKE_COMMAND} -E env CARGO_TARGET_DIR=${RUST_OUT_DIR}/target - cargo build --manifest-path ${RUST_CRATE_DIR}/Cargo.toml - --target ${RUST_TARGET_TRIPLE} --release - WORKING_DIRECTORY ${RUST_CRATE_DIR} - COMMENT "Building Rust staticlib for ${RUST_TARGET}" - VERBATIM - ) - - add_custom_target(arduinocore_api_rust_build ALL DEPENDS ${RUST_LIB}) - add_library(arduinocore_api_rust STATIC IMPORTED GLOBAL) - set_target_properties(arduinocore_api_rust PROPERTIES IMPORTED_LOCATION ${RUST_LIB}) - zephyr_link_libraries(arduinocore_api_rust) - zephyr_include_directories(${CMAKE_CURRENT_SOURCE_DIR}/zephyr/blobs/ArduinoCore-API/) + zephyr_include_directories(${CMAKE_CURRENT_SOURCE_DIR}/alt_arduinocore_api) + add_subdirectory(alt_arduinocore_api) else() zephyr_include_directories(${CMAKE_CURRENT_SOURCE_DIR}/zephyr/blobs/ArduinoCore-API/) zephyr_sources(${CMAKE_CURRENT_SOURCE_DIR}/zephyr/blobs/ArduinoCore-API/api/CanMsg.cpp) diff --git a/Kconfig b/Kconfig index 4d4215905..9ea63b69b 100644 --- a/Kconfig +++ b/Kconfig @@ -20,6 +20,7 @@ if ARDUINO_API config USE_ARDUINO_API_RUST_IMPLEMENTATION bool "Use Rust implementation API core" select RUST + select NANOPB config QEMU_ICOUNT bool "QEMU icount mode" diff --git a/README.md b/README.md index b703227b8..05ceb913a 100644 --- a/README.md +++ b/README.md @@ -6,6 +6,7 @@ The **Arduino Core API** module for zephyr leverages the power of Zephyr under a * [Using external Arduino Libraries](/documentation/arduino_libs.md) * [Adding custom boards/ variants](/documentation/variants.md) +* [Arduino IDL code generation](/documentation/idl_codegen.md) ## Adding Arduino Core API to Zephyr diff --git a/alt_arduinocore_api/CMakeLists.txt b/alt_arduinocore_api/CMakeLists.txt new file mode 100644 index 000000000..ac6af67bb --- /dev/null +++ b/alt_arduinocore_api/CMakeLists.txt @@ -0,0 +1,83 @@ +# SPDX-License-Identifier: Apache-2.0 + +function(rust_target_arch RUST_TARGET) + # Map Zephyr targets to LLVM targets. + if(CONFIG_CPU_CORTEX_M) + if(CONFIG_CPU_CORTEX_M0 OR CONFIG_CPU_CORTEX_M0PLUS OR CONFIG_CPU_CORTEX_M1) + set(${RUST_TARGET} "thumbv6m-none-eabi" PARENT_SCOPE) + elseif(CONFIG_CPU_CORTEX_M3) + set(${RUST_TARGET} "thumbv7m-none-eabi" PARENT_SCOPE) + elseif(CONFIG_CPU_CORTEX_M4 OR CONFIG_CPU_CORTEX_M7) + if(CONFIG_FP_HARDABI OR FORCE_FP_HARDABI) + set(${RUST_TARGET} "thumbv7em-none-eabihf" PARENT_SCOPE) + else() + set(${RUST_TARGET} "thumbv7em-none-eabi" PARENT_SCOPE) + endif() + elseif(CONFIG_CPU_CORTEX_M23) + set(${RUST_TARGET} "thumbv8m.base-none-eabi" PARENT_SCOPE) + elseif(CONFIG_CPU_CORTEX_M33 OR CONFIG_CPU_CORTEX_M55) + if(CONFIG_FP_HARDABI OR FORCE_FP_HARDABI) + set(${RUST_TARGET} "thumbv8m.main-none-eabihf" PARENT_SCOPE) + else() + set(${RUST_TARGET} "thumbv8m.main-none-eabi" PARENT_SCOPE) + endif() + else() + message(FATAL_ERROR "Unknown Cortex-M target.") + endif() + elseif(CONFIG_RISCV) + if(CONFIG_RISCV_ISA_RV64I) + set(${RUST_TARGET} "riscv64imac-unknown-none-elf" PARENT_SCOPE) + elseif(CONFIG_RISCV_ISA_RV32I) + set(${RUST_TARGET} "riscv32i-unknown-none-elf" PARENT_SCOPE) + else() + message(FATAL_ERROR "Rust: Unsupported riscv ISA") + endif() + elseif(CONFIG_ARCH_POSIX AND CONFIG_64BIT AND (${CMAKE_HOST_SYSTEM_PROCESSOR} MATCHES "x86_64")) + set(${RUST_TARGET} "x86_64-unknown-none" PARENT_SCOPE) + elseif(CONFIG_ARCH_POSIX AND CONFIG_64BIT AND (${CMAKE_HOST_SYSTEM_PROCESSOR} MATCHES "aarch64")) + set(${RUST_TARGET} "aarch64-unknown-none" PARENT_SCOPE) + else() + message(FATAL_ERROR "Rust: Add support for other target") + endif() +endfunction() + +rust_target_arch(RUST_TARGET_TRIPLE) + +if (EXISTS ${CMAKE_CURRENT_SOURCE_DIR}/rust/Cargo.toml) + set(RUST_CRATE_DIR ${CMAKE_CURRENT_SOURCE_DIR}/rust) +else() + set(RUST_CRATE_DIR ${CMAKE_CURRENT_SOURCE_DIR}/../rust) +endif() +set(RUST_OUT_DIR ${CMAKE_CURRENT_BINARY_DIR}/rust) +set(RUST_LIB ${RUST_OUT_DIR}/target/${RUST_TARGET_TRIPLE}/release/libarduinocore_api_rust.a) +set(RUST_SRCS + ${RUST_CRATE_DIR}/src/lib.rs + ${RUST_CRATE_DIR}/src/common.rs + ${RUST_CRATE_DIR}/src/string.rs +) + +add_custom_command( + OUTPUT ${RUST_LIB} + COMMAND ${CMAKE_COMMAND} -E env CARGO_TARGET_DIR=${RUST_OUT_DIR}/target + cargo build --manifest-path ${RUST_CRATE_DIR}/Cargo.toml + --target ${RUST_TARGET_TRIPLE} --release + WORKING_DIRECTORY ${RUST_CRATE_DIR} + DEPENDS ${RUST_CRATE_DIR}/Cargo.toml ${RUST_SRCS} + COMMENT "Building Rust staticlib for ${RUST_TARGET_TRIPLE}" + VERBATIM +) + +add_custom_target(arduinocore_api_rust_build ALL DEPENDS ${RUST_LIB}) +add_library(arduinocore_api_rust STATIC IMPORTED GLOBAL) +set_target_properties(arduinocore_api_rust PROPERTIES IMPORTED_LOCATION ${RUST_LIB}) +zephyr_link_libraries(arduinocore_api_rust) + +if (EXISTS ${CMAKE_CURRENT_SOURCE_DIR}/idl/CMakeLists.txt) + add_subdirectory(idl) +elseif (EXISTS ${CMAKE_CURRENT_SOURCE_DIR}/../idl/CMakeLists.txt) + add_subdirectory(${CMAKE_CURRENT_SOURCE_DIR}/../idl ${CMAKE_CURRENT_BINARY_DIR}/legacy_idl) +endif() + +if(TARGET arduinocore_idl_generate_interfaces) + add_dependencies(app arduinocore_idl_generate_interfaces) +endif() diff --git a/alt_arduinocore_api/api/ArduinoAPI.h b/alt_arduinocore_api/api/ArduinoAPI.h new file mode 100644 index 000000000..990301557 --- /dev/null +++ b/alt_arduinocore_api/api/ArduinoAPI.h @@ -0,0 +1,130 @@ +#pragma once + +#ifdef __cplusplus +#include +#include +#include +#else +#include +#include +#include +#endif +#include + +#include "api/HardwareSerial.h" +#include "common_types.h" + +#define lowByte(w) ((uint8_t) ((w) & 0xff)) +#define highByte(w) ((uint8_t) ((w) >> 8)) + +#define bit(b) BIT(b) +#define bitRead(value, bit) FIELD_GET(BIT(bit), value) +#define bitSet(value, bit) ((value) |= BIT(bit)) +#define bitClear(value, bit) ((value) &= ~BIT(bit)) +#define bitToggle(value, bit) ((value) ^= BIT(bit)) +#define bitWrite(value, bit, bitvalue) WRITE_BIT(value, bit, bitvalue) + + +#ifdef __cplusplus +extern "C" { +#endif + +typedef gpio_port_pins_t pin_size_t; + +typedef void (*voidFuncPtr)(void); +typedef void (*voidFuncPtrParam)(void*); + +void yield(void); + +void init(void); +void initVariant(void); + +int main() __attribute__((weak)); + +void pinMode(pin_size_t pinNumber, PinMode pinMode); +void digitalWrite(pin_size_t pinNumber, PinStatus status); +PinStatus digitalRead(pin_size_t pinNumber); +int analogRead(pin_size_t pinNumber); +void analogReference(uint8_t mode); +void analogWrite(pin_size_t pinNumber, int value); + +unsigned long millis(void); +unsigned long micros(void); +void delay(unsigned long); +void delayMicroseconds(unsigned int us); +unsigned long pulseIn(pin_size_t pin, uint8_t state, unsigned long timeout); +unsigned long pulseInLong(pin_size_t pin, uint8_t state, unsigned long timeout); + +void shiftOut(pin_size_t dataPin, pin_size_t clockPin, BitOrder bitOrder, uint8_t val); +uint8_t shiftIn(pin_size_t dataPin, pin_size_t clockPin, BitOrder bitOrder); + +void attachInterrupt(pin_size_t interruptNumber, voidFuncPtr callback, PinStatus mode); +void attachInterruptParam(pin_size_t interruptNumber, voidFuncPtrParam callback, PinStatus mode, void* param); +void detachInterrupt(pin_size_t interruptNumber); + +void setup(void); +void loop(void); + +#ifdef __cplusplus +} // extern "C" +#endif + +unsigned long pulseIn(uint8_t pin, uint8_t state, unsigned long timeout = 1000000L); +unsigned long pulseInLong(uint8_t pin, uint8_t state, unsigned long timeout = 1000000L); + +void tone(uint8_t _pin, unsigned int frequency, unsigned long duration = 0); +void noTone(uint8_t _pin); + +// WMath prototypes +long random(long); +long random(long, long); +void randomSeed(unsigned long); + + +extern "C" { + int32_t map_i32(int32_t x, int32_t in_min, int32_t in_max, int32_t out_min, int32_t out_max); + uint16_t makeWord_w(uint16_t w); + uint16_t makeWord_hl(uint8_t h, uint8_t l); +} + +inline long map(long x, long in_min, long in_max, long out_min, long out_max) +{ + return map_i32(x, in_min, in_max, out_min, out_max); +} + +inline uint16_t makeWord(uint16_t w) { + return makeWord_w(w); +} + +inline uint16_t makeWord(uint8_t h, uint8_t l) { + return makeWord_hl(h, l); +} + +#define word(...) makeWord(__VA_ARGS__) + +#ifdef __cplusplus + template + auto min(const T& a, const L& b) -> decltype((b < a) ? b : a) + { + return (b < a) ? b : a; + } + + template + auto max(const T& a, const L& b) -> decltype((b < a) ? b : a) + { + return (a < b) ? b : a; + } +#else +#ifndef min +#define min(a,b) \ + ({ __typeof__ (a) _a = (a); \ + __typeof__ (b) _b = (b); \ + _a < _b ? _a : _b; }) +#endif +#ifndef max +#define max(a,b) \ + ({ __typeof__ (a) _a = (a); \ + __typeof__ (b) _b = (b); \ + _a > _b ? _a : _b; }) +#endif +#endif diff --git a/alt_arduinocore_api/api/Binary.h b/alt_arduinocore_api/api/Binary.h new file mode 100644 index 000000000..e6bf58f68 --- /dev/null +++ b/alt_arduinocore_api/api/Binary.h @@ -0,0 +1,7 @@ +/* + * Copyright (c) 2026 TOKITA Hiroshi + * + * SPDX-License-Identifier: Apache-2.0 + */ + +/* stub file for compat */ diff --git a/alt_arduinocore_api/api/Client.h b/alt_arduinocore_api/api/Client.h new file mode 100644 index 000000000..49618b01f --- /dev/null +++ b/alt_arduinocore_api/api/Client.h @@ -0,0 +1,13 @@ +#pragma once + +#include "Stream.h" +#include + +namespace arduino { + +class Client : virtual public Stream, virtual public ClientInterface { +protected: + uint8_t* rawIPAddress(IPAddress& addr); //TODO +}; + +}; diff --git a/alt_arduinocore_api/api/Common.h b/alt_arduinocore_api/api/Common.h new file mode 100644 index 000000000..e6bf58f68 --- /dev/null +++ b/alt_arduinocore_api/api/Common.h @@ -0,0 +1,7 @@ +/* + * Copyright (c) 2026 TOKITA Hiroshi + * + * SPDX-License-Identifier: Apache-2.0 + */ + +/* stub file for compat */ diff --git a/alt_arduinocore_api/api/HardwareI2C.h b/alt_arduinocore_api/api/HardwareI2C.h new file mode 100644 index 000000000..604d0d879 --- /dev/null +++ b/alt_arduinocore_api/api/HardwareI2C.h @@ -0,0 +1,17 @@ +/* + * Copyright (c) 2026 TOKITA Hiroshi + * + * SPDX-License-Identifier: Apache-2.0 + */ + +#pragma once + +#include "Stream.h" +#include + +namespace arduino { + +class HardwareI2C : virtual public Stream, virtual public HardwareI2CInterface { +}; + +}; diff --git a/alt_arduinocore_api/api/HardwareSPI.h b/alt_arduinocore_api/api/HardwareSPI.h new file mode 100644 index 000000000..3e3793536 --- /dev/null +++ b/alt_arduinocore_api/api/HardwareSPI.h @@ -0,0 +1,18 @@ +/* + * Copyright (c) 2026 TOKITA Hiroshi + * + * SPDX-License-Identifier: Apache-2.0 + */ + +#pragma once + +#include "Stream.h" +#include + +namespace arduino { + +class HardwareSPI : virtual public HardwareSPIInterface { +}; + +typedef HardwareSPI SPIClass; +}; diff --git a/alt_arduinocore_api/api/HardwareSerial.h b/alt_arduinocore_api/api/HardwareSerial.h new file mode 100644 index 000000000..e63c4b722 --- /dev/null +++ b/alt_arduinocore_api/api/HardwareSerial.h @@ -0,0 +1,64 @@ +/* + * Copyright (c) 2026 TOKITA Hiroshi + * + * SPDX-License-Identifier: Apache-2.0 + */ + +#pragma once + +#include "Stream.h" +#include + +namespace arduino { + +enum SerialConfig { + SERIAL_5E1 = (SERIAL_DATA_5 | SERIAL_PARITY_EVEN | SERIAL_STOP_BIT_1), + SERIAL_5E2 = (SERIAL_DATA_5 | SERIAL_PARITY_EVEN | SERIAL_STOP_BIT_2), + SERIAL_5O1 = (SERIAL_DATA_5 | SERIAL_PARITY_ODD | SERIAL_STOP_BIT_1), + SERIAL_5O2 = (SERIAL_DATA_5 | SERIAL_PARITY_ODD | SERIAL_STOP_BIT_2), + SERIAL_5N1 = (SERIAL_DATA_5 | SERIAL_PARITY_NONE | SERIAL_STOP_BIT_1), + SERIAL_5N2 = (SERIAL_DATA_5 | SERIAL_PARITY_NONE | SERIAL_STOP_BIT_2), + SERIAL_5M1 = (SERIAL_DATA_5 | SERIAL_PARITY_MARK | SERIAL_STOP_BIT_1), + SERIAL_5M2 = (SERIAL_DATA_5 | SERIAL_PARITY_MARK | SERIAL_STOP_BIT_2), + SERIAL_5S1 = (SERIAL_DATA_5 | SERIAL_PARITY_SPACE | SERIAL_STOP_BIT_1), + SERIAL_5S2 = (SERIAL_DATA_5 | SERIAL_PARITY_SPACE | SERIAL_STOP_BIT_2), + SERIAL_6E1 = (SERIAL_DATA_6 | SERIAL_PARITY_EVEN | SERIAL_STOP_BIT_1), + SERIAL_6E2 = (SERIAL_DATA_6 | SERIAL_PARITY_EVEN | SERIAL_STOP_BIT_2), + SERIAL_6O1 = (SERIAL_DATA_6 | SERIAL_PARITY_ODD | SERIAL_STOP_BIT_1), + SERIAL_6O2 = (SERIAL_DATA_6 | SERIAL_PARITY_ODD | SERIAL_STOP_BIT_2), + SERIAL_6N1 = (SERIAL_DATA_6 | SERIAL_PARITY_NONE | SERIAL_STOP_BIT_1), + SERIAL_6N2 = (SERIAL_DATA_6 | SERIAL_PARITY_NONE | SERIAL_STOP_BIT_2), + SERIAL_6M1 = (SERIAL_DATA_6 | SERIAL_PARITY_MARK | SERIAL_STOP_BIT_1), + SERIAL_6M2 = (SERIAL_DATA_6 | SERIAL_PARITY_MARK | SERIAL_STOP_BIT_2), + SERIAL_6S1 = (SERIAL_DATA_6 | SERIAL_PARITY_SPACE | SERIAL_STOP_BIT_1), + SERIAL_6S2 = (SERIAL_DATA_6 | SERIAL_PARITY_SPACE | SERIAL_STOP_BIT_2), + SERIAL_7E1 = (SERIAL_DATA_7 | SERIAL_PARITY_EVEN | SERIAL_STOP_BIT_1), + SERIAL_7E2 = (SERIAL_DATA_7 | SERIAL_PARITY_EVEN | SERIAL_STOP_BIT_2), + SERIAL_7O1 = (SERIAL_DATA_7 | SERIAL_PARITY_ODD | SERIAL_STOP_BIT_1), + SERIAL_7O2 = (SERIAL_DATA_7 | SERIAL_PARITY_ODD | SERIAL_STOP_BIT_2), + SERIAL_7N1 = (SERIAL_DATA_7 | SERIAL_PARITY_NONE | SERIAL_STOP_BIT_1), + SERIAL_7N2 = (SERIAL_DATA_7 | SERIAL_PARITY_NONE | SERIAL_STOP_BIT_2), + SERIAL_7M1 = (SERIAL_DATA_7 | SERIAL_PARITY_MARK | SERIAL_STOP_BIT_1), + SERIAL_7M2 = (SERIAL_DATA_7 | SERIAL_PARITY_MARK | SERIAL_STOP_BIT_2), + SERIAL_7S1 = (SERIAL_DATA_7 | SERIAL_PARITY_SPACE | SERIAL_STOP_BIT_1), + SERIAL_7S2 = (SERIAL_DATA_7 | SERIAL_PARITY_SPACE | SERIAL_STOP_BIT_2), + SERIAL_8E1 = (SERIAL_DATA_8 | SERIAL_PARITY_EVEN | SERIAL_STOP_BIT_1), + SERIAL_8E2 = (SERIAL_DATA_8 | SERIAL_PARITY_EVEN | SERIAL_STOP_BIT_2), + SERIAL_8O1 = (SERIAL_DATA_8 | SERIAL_PARITY_ODD | SERIAL_STOP_BIT_1), + SERIAL_8O2 = (SERIAL_DATA_8 | SERIAL_PARITY_ODD | SERIAL_STOP_BIT_2), + SERIAL_8N1 = (SERIAL_DATA_8 | SERIAL_PARITY_NONE | SERIAL_STOP_BIT_1), + SERIAL_8N2 = (SERIAL_DATA_8 | SERIAL_PARITY_NONE | SERIAL_STOP_BIT_2), + SERIAL_8M1 = (SERIAL_DATA_8 | SERIAL_PARITY_MARK | SERIAL_STOP_BIT_1), + SERIAL_8M2 = (SERIAL_DATA_8 | SERIAL_PARITY_MARK | SERIAL_STOP_BIT_2), + SERIAL_8S1 = (SERIAL_DATA_8 | SERIAL_PARITY_SPACE | SERIAL_STOP_BIT_1), + SERIAL_8S2 = (SERIAL_DATA_8 | SERIAL_PARITY_SPACE | SERIAL_STOP_BIT_2), +}; + +class HardwareSerial : virtual public Stream, virtual public HardwareSerialInterface { +public: + using Print::write; +}; + +extern void __weak serialEventRun(void); + +} // namespace arduino diff --git a/alt_arduinocore_api/api/IPAddress.h b/alt_arduinocore_api/api/IPAddress.h new file mode 100644 index 000000000..deea80f37 --- /dev/null +++ b/alt_arduinocore_api/api/IPAddress.h @@ -0,0 +1,16 @@ +/* + * Copyright (c) 2026 TOKITA Hiroshi + * + * SPDX-License-Identifier: Apache-2.0 + */ + +#pragma once + +#include "Printable.h" + +namespace arduino { + +class IPAddress : public Printable { +}; + +} diff --git a/alt_arduinocore_api/api/Print.h b/alt_arduinocore_api/api/Print.h new file mode 100644 index 000000000..99f60be3c --- /dev/null +++ b/alt_arduinocore_api/api/Print.h @@ -0,0 +1,254 @@ +/* + * Copyright (c) 2026 TOKITA Hiroshi + * + * SPDX-License-Identifier: Apache-2.0 + */ + +#pragma once + +#include +#include +#include + +#include + +#include "Printable.h" +#include "String.h" + +namespace arduino { + +class Print : virtual public PrintInterface { + class ErrorCode { + int code; + + void setError(int err) { + code = err; + } + + int getError() { + return code; + } + + void clearError() { + setError(0); + } + + friend Print; + } errcode; + +protected: + void setWriteError(int err = 1) { + errcode.setError(err); + } + +public: + using PrintInterface::write; + + int getWriteError() { + return errcode.getError(); + } + + void clearWriteError() { + errcode.clearError(); + } + + virtual size_t write(const uint8_t *buffer, size_t size); + + size_t write(const char *buffer, size_t size) { + if (buffer != nullptr && size != 0) { + return write(reinterpret_cast(buffer), size); + } else { + return 0; + } + } + + size_t write(const char *str) { + return write(str, strlen(str)); + } + + int availableForWrite() { + return 0; + } + + void flush() { + } + + size_t print(const String &); + size_t print(const char[]); + size_t print(char); + size_t print(unsigned char, int = 10); + size_t print(int, int = 10); + size_t print(unsigned int, int = 10); + size_t print(long, int = 10); + size_t print(unsigned long, int = 10); + size_t print(long long, int = 10); + size_t print(unsigned long long, int = 10); + size_t print(double, int = 2); + size_t print(const Printable &); + + size_t println(const String &s); + size_t println(const char[]); + size_t println(char); + size_t println(unsigned char, int = 10); + size_t println(int, int = 10); + size_t println(unsigned int, int = 10); + size_t println(long, int = 10); + size_t println(unsigned long, int = 10); + size_t println(long long, int = 10); + size_t println(unsigned long long, int = 10); + size_t println(double, int = 2); + size_t println(const Printable &); + size_t println(void); +}; + +namespace zephyr { + +int cbprintf_callback(int c, void *ctx); +size_t wrap_cbprintf(void *ctx, const char *format, ...); +size_t print_number_base_any(void *ctx, unsigned long long ull, int base); +size_t print_number_base_pow2(void *ctx, unsigned long long ull, unsigned bits); + +template +size_t print_number(void *ctx, Number n, const int base, const char *decfmt) { + if (base == 0) { + return reinterpret_cast(ctx)->write(static_cast(n)); + } else if (base == 2) { + return zephyr::print_number_base_pow2(ctx, n, 1); + } else if (base == 4) { + return zephyr::print_number_base_pow2(ctx, n, 2); + } else if (base == 8) { + return zephyr::print_number_base_pow2(ctx, n, 3); + } else if (base == 10) { + return zephyr::wrap_cbprintf(ctx, decfmt, n); + } else if (base == 16) { + return zephyr::print_number_base_pow2(ctx, n, 4); + } else if (base == 32) { + return zephyr::print_number_base_pow2(ctx, n, 5); + } else { + return zephyr::print_number_base_any(ctx, n, base); + } +} + +} // namespace zephyr + +inline size_t Print::print(const String &s) { + return write(s.c_str(), s.length()); +} + +inline size_t Print::print(const char str[]) { + return write(str); +} + +inline size_t Print::print(char c) { + return write(c); +} + +inline size_t Print::print(unsigned char n, int base) { + return zephyr::print_number(this, n, base, "%hhu"); +} + +inline size_t Print::print(int n, int base) { + return zephyr::print_number(this, n, base, "%d"); +} + +inline size_t Print::print(unsigned int n, int base) { + return zephyr::print_number(this, n, base, "%u"); +} + +inline size_t Print::print(long n, int base) { + return zephyr::print_number(this, n, base, "%ld"); +} + +inline size_t Print::print(unsigned long n, int base) { + return zephyr::print_number(this, n, base, "%lu"); +} + +inline size_t Print::print(long long n, int base) { + return zephyr::print_number(this, n, base, "%lld"); +} + +inline size_t Print::print(unsigned long long n, int base) { + return zephyr::print_number(this, n, base, "%llu"); +} + +inline size_t Print::print(double n, int perception) { + if (perception < 10) { + const char ch_perception = static_cast('0' + perception); + const char format[] = {'%', '.', ch_perception, 'f', '\0'}; + return zephyr::wrap_cbprintf(this, format, n); + } else { + const char ch_perception = static_cast('0' + (perception % 10)); + const char format[] = {'%', '.', '1', ch_perception, 'f', '\0'}; + return zephyr::wrap_cbprintf(this, format, n); + } +} + +inline size_t Print::print(const Printable &printable) { + return printable.printTo(*this); +} + +inline size_t Print::println(const String &s) { + return print(s) + println(); +} + +inline size_t Print::println(const char str[]) { + return print(str) + println(); +} + +inline size_t Print::println(char c) { + return print(c) + println(); +} + +inline size_t Print::println(unsigned char uc, int base) { + return print(uc, base) + println(); +} + +inline size_t Print::println(int i, int base) { + return print(i, base) + println(); +} + +inline size_t Print::println(unsigned int ui, int base) { + return print(ui, base) + println(); +} + +inline size_t Print::println(long l, int base) { + return print(l, base) + println(); +} + +inline size_t Print::println(unsigned long ul, int base) { + return print(ul, base) + println(); +} + +inline size_t Print::println(long long ll, int base) { + return print(ll, base) + println(); +} + +inline size_t Print::println(unsigned long long ull, int base) { + return print(ull, base) + println(); +} + +inline size_t Print::println(double d, int perception) { + return print(d, perception) + println(); +} + +inline size_t Print::println(const Printable &printable) { + return print(printable) + println(); +} + +inline size_t Print::println(void) { + return write("\r\n", 2); +} + +/* + * This is the default implementation. + * It will be overridden by subclasses. + */ +inline size_t arduino::Print::write(const uint8_t *buffer, size_t size) { + size_t i; + + for (i = 0; i < size && write(buffer[i]); i++) { + } + + return i; +} +} // namespace arduino diff --git a/alt_arduinocore_api/api/Printable.h b/alt_arduinocore_api/api/Printable.h new file mode 100644 index 000000000..823e04494 --- /dev/null +++ b/alt_arduinocore_api/api/Printable.h @@ -0,0 +1,20 @@ +/* + * Copyright (c) 2026 TOKITA Hiroshi + * + * SPDX-License-Identifier: Apache-2.0 + */ + +#pragma once + +namespace arduino { + class Print; +}; + +#include + +namespace arduino { + +class Printable : virtual public PrintableInterface { +}; + +} diff --git a/alt_arduinocore_api/api/Server.h b/alt_arduinocore_api/api/Server.h new file mode 100644 index 000000000..1496228fa --- /dev/null +++ b/alt_arduinocore_api/api/Server.h @@ -0,0 +1,17 @@ +/* + * Copyright (c) 2026 TOKITA Hiroshi + * + * SPDX-License-Identifier: Apache-2.0 + */ + +#pragma once + +#include "Stream.h" +#include + +namespace arduino { + +class Server : virtual public Print, virtual public ServerInterface { +}; + +}; diff --git a/alt_arduinocore_api/api/Stream.h b/alt_arduinocore_api/api/Stream.h new file mode 100644 index 000000000..7687c0c57 --- /dev/null +++ b/alt_arduinocore_api/api/Stream.h @@ -0,0 +1,17 @@ +/* + * Copyright (c) 2026 TOKITA Hiroshi + * + * SPDX-License-Identifier: Apache-2.0 + */ + +#pragma once + +#include "Print.h" +#include + +namespace arduino { + +class Stream : virtual public Print, virtual public StreamInterface { +}; + +}; // namespace arduino diff --git a/alt_arduinocore_api/api/String.h b/alt_arduinocore_api/api/String.h new file mode 100644 index 000000000..683280e5d --- /dev/null +++ b/alt_arduinocore_api/api/String.h @@ -0,0 +1,407 @@ +/* + String library for Wiring & Arduino + ...mostly rewritten by Paul Stoffregen... + Copyright (c) 2009-10 Hernando Barragan. All right reserved. + Copyright 2011, Paul Stoffregen, paul@pjrc.com + + This library is free software; you can redistribute it and/or + modify it under the terms of the GNU Lesser General Public + License as published by the Free Software Foundation; either + version 2.1 of the License, or (at your option) any later version. + + This library is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + Lesser General Public License for more details. + + You should have received a copy of the GNU Lesser General Public + License along with this library; if not, write to the Free Software + Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA +*/ + +#ifdef __cplusplus + +#ifndef __ARDUINO_STRINGS__ +#define __ARDUINO_STRINGS__ + +#include +#include +#include +#include +#if defined(__AVR__) +#include "avr/pgmspace.h" +#else +#ifndef PSTR +#define PSTR(str) (str) +#endif +#ifndef strcpy_P +#define strcpy_P(dest, src) strcpy((dest), (src)) +#endif +#endif + +extern "C" { + +void arduino_string_free(char **buffer, unsigned int *capacity, unsigned int *len); +bool arduino_string_change_buffer( + char **buffer, + unsigned int *capacity, + unsigned int *len, + unsigned int max_str_len); +bool arduino_string_copy_bytes( + char **buffer, + unsigned int *capacity, + unsigned int *len, + const char *src, + unsigned int length); +bool arduino_string_copy_char( + char **buffer, + unsigned int *capacity, + unsigned int *len, + char value); +bool arduino_string_copy_unsigned_char_base( + char **buffer, + unsigned int *capacity, + unsigned int *len, + unsigned char value, + unsigned char base); +bool arduino_string_copy_int_base( + char **buffer, + unsigned int *capacity, + unsigned int *len, + int value, + unsigned char base); +bool arduino_string_copy_unsigned_int_base( + char **buffer, + unsigned int *capacity, + unsigned int *len, + unsigned int value, + unsigned char base); +bool arduino_string_copy_long_base( + char **buffer, + unsigned int *capacity, + unsigned int *len, + long value, + unsigned char base); +bool arduino_string_copy_unsigned_long_base( + char **buffer, + unsigned int *capacity, + unsigned int *len, + unsigned long value, + unsigned char base); +bool arduino_string_copy_float_precision( + char **buffer, + unsigned int *capacity, + unsigned int *len, + float value, + unsigned char decimal_places); +bool arduino_string_copy_double_precision( + char **buffer, + unsigned int *capacity, + unsigned int *len, + double value, + unsigned char decimal_places); +bool arduino_string_concat_bytes( + char **buffer, + unsigned int *capacity, + unsigned int *len, + const char *src, + unsigned int length); +bool arduino_string_concat_unsigned_char( + char **buffer, + unsigned int *capacity, + unsigned int *len, + unsigned char value); +bool arduino_string_concat_int( + char **buffer, + unsigned int *capacity, + unsigned int *len, + int value); +bool arduino_string_concat_unsigned_int( + char **buffer, + unsigned int *capacity, + unsigned int *len, + unsigned int value); +bool arduino_string_concat_long( + char **buffer, + unsigned int *capacity, + unsigned int *len, + long value); +bool arduino_string_concat_unsigned_long( + char **buffer, + unsigned int *capacity, + unsigned int *len, + unsigned long value); +bool arduino_string_concat_float( + char **buffer, + unsigned int *capacity, + unsigned int *len, + float value); +bool arduino_string_concat_double( + char **buffer, + unsigned int *capacity, + unsigned int *len, + double value); + +int arduino_string_compare_cstr(const char *buffer, unsigned int len, const char *other); +bool arduino_string_equals_cstr(const char *buffer, unsigned int len, const char *other); +bool arduino_string_equals_bytes( + const char *buffer, + unsigned int len, + const char *other, + unsigned int other_len); +bool arduino_string_equals_ignore_case( + const char *buffer, + unsigned int len, + const char *other, + unsigned int other_len); +bool arduino_string_starts_with( + const char *buffer, + unsigned int len, + const char *prefix, + unsigned int prefix_len, + unsigned int offset); +bool arduino_string_ends_with( + const char *buffer, + unsigned int len, + const char *suffix, + unsigned int suffix_len); +int arduino_string_index_of_char(const char *buffer, unsigned int len, char ch, unsigned int from_index); +int arduino_string_index_of_bytes( + const char *buffer, + unsigned int len, + const char *pattern, + unsigned int pattern_len, + unsigned int from_index); +int arduino_string_last_index_of_char( + const char *buffer, + unsigned int len, + char ch, + unsigned int from_index); +int arduino_string_last_index_of_bytes( + const char *buffer, + unsigned int len, + const char *pattern, + unsigned int pattern_len, + unsigned int from_index); +void arduino_string_replace_char(char *buffer, unsigned int len, char find_ch, char replace_ch); +bool arduino_string_replace_bytes( + char **buffer, + unsigned int *capacity, + unsigned int *len, + const char *find, + unsigned int find_len, + const char *replace, + unsigned int replace_len); +unsigned int arduino_string_remove(char *buffer, unsigned int len, unsigned int index, unsigned int count); +void arduino_string_reverse(char *buffer, unsigned int len); +void arduino_string_get_bytes( + const char *buffer, + unsigned int len, + unsigned char *out, + unsigned int out_size, + unsigned int index); +bool arduino_string_copy_substring( + const char *src_buffer, + unsigned int src_len, + unsigned int left, + unsigned int right, + char **out_buffer, + unsigned int *out_capacity, + unsigned int *out_len); + +} + +namespace arduino { + +// When compiling programs with this class, the following gcc parameters +// dramatically increase performance and memory (RAM) efficiency, typically +// with little or no increase in code size. +// -felide-constructors +// -std=c++0x + +class __FlashStringHelper; +#define F(string_literal) (reinterpret_cast(PSTR(string_literal))) + +// An inherited class for holding the result of a concatenation. These +// result objects are assumed to be writable by subsequent concatenations. +class StringSumHelper; + +class String +{ + friend class StringSumHelper; + typedef void (String::*StringIfHelperType)() const; + void StringIfHelper() const; + + static size_t const FLT_MAX_DECIMAL_PLACES = 10; + static size_t const DBL_MAX_DECIMAL_PLACES = FLT_MAX_DECIMAL_PLACES; + +public: + String(const char *cstr = ""); + String(const char *cstr, unsigned int length); + String(const uint8_t *cstr, unsigned int length); + String(const String &str); + String(const __FlashStringHelper *str); + String(String &&rval); + explicit String(char c); + explicit String(unsigned char, unsigned char base=10); + explicit String(int, unsigned char base=10); + explicit String(unsigned int, unsigned char base=10); + explicit String(long, unsigned char base=10); + explicit String(unsigned long, unsigned char base=10); + explicit String(float, unsigned char decimalPlaces=2); + explicit String(double, unsigned char decimalPlaces=2); + ~String(void); + + bool reserve(unsigned int size); + unsigned int length(void) const; + bool isEmpty(void) const; + + String & operator = (const String &rhs); + String & operator = (const char *cstr); + String & operator = (const __FlashStringHelper *str); + String & operator = (String &&rval); + + bool concat(const String &str); + bool concat(const char *cstr); + bool concat(const char *cstr, unsigned int length); + bool concat(const uint8_t *cstr, unsigned int length); + bool concat(char c); + bool concat(unsigned char num); + bool concat(int num); + bool concat(unsigned int num); + bool concat(long num); + bool concat(unsigned long num); + bool concat(float num); + bool concat(double num); + bool concat(const __FlashStringHelper * str); + + String & operator += (const String &rhs); + String & operator += (const char *cstr); + String & operator += (char c); + String & operator += (unsigned char num); + String & operator += (int num); + String & operator += (unsigned int num); + String & operator += (long num); + String & operator += (unsigned long num); + String & operator += (float num); + String & operator += (double num); + String & operator += (const __FlashStringHelper *str); + + friend StringSumHelper & operator + (const StringSumHelper &lhs, const String &rhs); + friend StringSumHelper & operator + (const StringSumHelper &lhs, const char *cstr); + friend StringSumHelper & operator + (const StringSumHelper &lhs, char c); + friend StringSumHelper & operator + (const StringSumHelper &lhs, unsigned char num); + friend StringSumHelper & operator + (const StringSumHelper &lhs, int num); + friend StringSumHelper & operator + (const StringSumHelper &lhs, unsigned int num); + friend StringSumHelper & operator + (const StringSumHelper &lhs, long num); + friend StringSumHelper & operator + (const StringSumHelper &lhs, unsigned long num); + friend StringSumHelper & operator + (const StringSumHelper &lhs, float num); + friend StringSumHelper & operator + (const StringSumHelper &lhs, double num); + friend StringSumHelper & operator + (const StringSumHelper &lhs, const __FlashStringHelper *rhs); + + operator StringIfHelperType() const; + int compareTo(const String &s) const; + int compareTo(const char *cstr) const; + bool equals(const String &s) const; + bool equals(const char *cstr) const; + + friend bool operator == (const String &a, const String &b); + friend bool operator == (const String &a, const char *b); + friend bool operator == (const char *a, const String &b); + friend bool operator < (const String &a, const String &b); + friend bool operator < (const String &a, const char *b); + friend bool operator < (const char *a, const String &b); + + friend bool operator != (const String &a, const String &b); + friend bool operator != (const String &a, const char *b); + friend bool operator != (const char *a, const String &b); + friend bool operator > (const String &a, const String &b); + friend bool operator > (const String &a, const char *b); + friend bool operator > (const char *a, const String &b); + friend bool operator <= (const String &a, const String &b); + friend bool operator <= (const String &a, const char *b); + friend bool operator <= (const char *a, const String &b); + friend bool operator >= (const String &a, const String &b); + friend bool operator >= (const String &a, const char *b); + friend bool operator >= (const char *a, const String &b); + + bool equalsIgnoreCase(const String &s) const; + bool startsWith(const String &prefix) const; + bool startsWith(const String &prefix, unsigned int offset) const; + bool endsWith(const String &suffix) const; + + char charAt(unsigned int index) const; + void setCharAt(unsigned int index, char c); + char operator [] (unsigned int index) const; + char& operator [] (unsigned int index); + void getBytes(unsigned char *buf, unsigned int bufsize, unsigned int index=0) const; + void toCharArray(char *buf, unsigned int bufsize, unsigned int index=0) const; + const char* c_str() const; + char* begin(); + char* end(); + const char* begin() const; + const char* end() const; + + int indexOf(char ch) const; + int indexOf(char ch, unsigned int fromIndex) const; + int indexOf(const String &str) const; + int indexOf(const String &str, unsigned int fromIndex) const; + int lastIndexOf(char ch) const; + int lastIndexOf(char ch, unsigned int fromIndex) const; + int lastIndexOf(const String &str) const; + int lastIndexOf(const String &str, unsigned int fromIndex) const; + String substring(unsigned int beginIndex) const; + String substring(unsigned int beginIndex, unsigned int endIndex) const; + + void replace(char find, char replace); + void replace(const String& find, const String& replace); + void remove(unsigned int index); + void remove(unsigned int index, unsigned int count); + void toLowerCase(void); + void toUpperCase(void); + void trim(void); + void reverse(void); + + long toInt(void) const; + float toFloat(void) const; + double toDouble(void) const; + +protected: + char *buffer; + unsigned int capacity; + unsigned int len; + +protected: + void invalidate(void); + void invalidateOnFailure(bool success); + bool changeBuffer(unsigned int maxStrLen); + + String & copy(const char *cstr, unsigned int length); + String & copy(const __FlashStringHelper *pstr, unsigned int length); + void move(String &rhs); +}; + +class StringSumHelper : public String +{ +public: + StringSumHelper(const String &s); + StringSumHelper(const char *p); + StringSumHelper(char c); + StringSumHelper(unsigned char num); + StringSumHelper(int num); + StringSumHelper(unsigned int num); + StringSumHelper(long num); + StringSumHelper(unsigned long num); + StringSumHelper(float num); + StringSumHelper(double num); +}; + +} // namespace arduino + +using arduino::__FlashStringHelper; +using arduino::String; + +#include "String.inl" + +#endif // __cplusplus +#endif // __ARDUINO_STRINGS__ diff --git a/alt_arduinocore_api/api/String.inl b/alt_arduinocore_api/api/String.inl new file mode 100644 index 000000000..a594a1ecc --- /dev/null +++ b/alt_arduinocore_api/api/String.inl @@ -0,0 +1,690 @@ +/* + * Copyright (c) 2026 TOKITA Hiroshi + * + * SPDX-License-Identifier: Apache-2.0 + */ + +namespace arduino { + +inline void String::StringIfHelper() const { +} + +inline String::String(const char *cstr) + : buffer(NULL), capacity(0), len(0) { + if (cstr) { + copy(cstr, static_cast(strlen(cstr))); + } +} + +inline String::String(const char *cstr, unsigned int length) + : buffer(NULL), capacity(0), len(0) { + if (cstr) { + copy(cstr, length); + } +} + +inline String::String(const uint8_t *cstr, unsigned int length) + : String((const char *)cstr, length) { +} + +inline String::String(const String &str) + : buffer(NULL), capacity(0), len(0) { + *this = str; +} + +inline String::String(const __FlashStringHelper *str) + : buffer(NULL), capacity(0), len(0) { + *this = str; +} + +inline String::String(String &&rval) + : buffer(NULL), capacity(0), len(0) { + move(rval); +} + +inline String::String(char c) + : buffer(NULL), capacity(0), len(0) { + invalidateOnFailure(arduino_string_copy_char(&buffer, &capacity, &len, c)); +} + +inline String::String(unsigned char value, unsigned char base) + : buffer(NULL), capacity(0), len(0) { + invalidateOnFailure(arduino_string_copy_unsigned_char_base(&buffer, &capacity, &len, value, base)); +} + +inline String::String(int value, unsigned char base) + : buffer(NULL), capacity(0), len(0) { + invalidateOnFailure(arduino_string_copy_int_base(&buffer, &capacity, &len, value, base)); +} + +inline String::String(unsigned int value, unsigned char base) + : buffer(NULL), capacity(0), len(0) { + invalidateOnFailure(arduino_string_copy_unsigned_int_base(&buffer, &capacity, &len, value, base)); +} + +inline String::String(long value, unsigned char base) + : buffer(NULL), capacity(0), len(0) { + invalidateOnFailure(arduino_string_copy_long_base(&buffer, &capacity, &len, value, base)); +} + +inline String::String(unsigned long value, unsigned char base) + : buffer(NULL), capacity(0), len(0) { + invalidateOnFailure(arduino_string_copy_unsigned_long_base(&buffer, &capacity, &len, value, base)); +} + +inline String::String(float value, unsigned char decimalPlaces) + : buffer(NULL), capacity(0), len(0) { + invalidateOnFailure(arduino_string_copy_float_precision(&buffer, &capacity, &len, value, decimalPlaces)); +} + +inline String::String(double value, unsigned char decimalPlaces) + : buffer(NULL), capacity(0), len(0) { + invalidateOnFailure(arduino_string_copy_double_precision(&buffer, &capacity, &len, value, decimalPlaces)); +} + +inline String::~String(void) { + arduino_string_free(&buffer, &capacity, &len); +} + +inline bool String::reserve(unsigned int size) { + if (buffer && capacity >= size) { + return true; + } + if (changeBuffer(size)) { + if (len == 0) { + buffer[0] = 0; + } + return true; + } + return false; +} + +inline unsigned int String::length(void) const { + return len; +} + +inline bool String::isEmpty(void) const { + return length() == 0; +} + +inline String &String::operator=(const String &rhs) { + if (this == &rhs) { + return *this; + } + if (rhs.buffer) { + copy(rhs.buffer, rhs.len); + } else { + invalidate(); + } + return *this; +} + +inline String &String::operator=(const char *cstr) { + if (cstr) { + copy(cstr, static_cast(strlen(cstr))); + } else { + invalidate(); + } + return *this; +} + +inline String &String::operator=(const __FlashStringHelper *str) { + if (str) { + copy(str, static_cast(strlen((const char *)str))); + } else { + invalidate(); + } + return *this; +} + +inline String &String::operator=(String &&rval) { + move(rval); + return *this; +} + +inline bool String::concat(const String &str) { + return arduino_string_concat_bytes(&buffer, &capacity, &len, str.buffer, str.len); +} + +inline bool String::concat(const char *cstr) { + return arduino_string_concat_bytes( + &buffer, &capacity, &len, cstr, cstr ? static_cast(strlen(cstr)) : 0); +} + +inline bool String::concat(const char *cstr, unsigned int length) { + return arduino_string_concat_bytes(&buffer, &capacity, &len, cstr, length); +} + +inline bool String::concat(const uint8_t *cstr, unsigned int length) { + return arduino_string_concat_bytes(&buffer, &capacity, &len, reinterpret_cast(cstr), length); +} + +inline bool String::concat(char c) { + return arduino_string_concat_bytes(&buffer, &capacity, &len, reinterpret_cast(&c), 1); +} + +inline bool String::concat(unsigned char num) { + return arduino_string_concat_unsigned_char(&buffer, &capacity, &len, num); +} + +inline bool String::concat(int num) { + return arduino_string_concat_int(&buffer, &capacity, &len, num); +} + +inline bool String::concat(unsigned int num) { + return arduino_string_concat_unsigned_int(&buffer, &capacity, &len, num); +} + +inline bool String::concat(long num) { + return arduino_string_concat_long(&buffer, &capacity, &len, num); +} + +inline bool String::concat(unsigned long num) { + return arduino_string_concat_unsigned_long(&buffer, &capacity, &len, num); +} + +inline bool String::concat(float num) { + return arduino_string_concat_float(&buffer, &capacity, &len, num); +} + +inline bool String::concat(double num) { + return arduino_string_concat_double(&buffer, &capacity, &len, num); +} + +inline bool String::concat(const __FlashStringHelper *str) { + if (!str) { + return false; + } + const unsigned int length = static_cast(strlen((const char *)str)); + if (length == 0) { + return true; + } + return arduino_string_concat_bytes(&buffer, &capacity, &len, (const char *)str, length); +} + +inline String &String::operator+=(const String &str) { + (void)arduino_string_concat_bytes(&buffer, &capacity, &len, str.buffer, str.len); + return *this; +} + +inline String &String::operator+=(const char *cstr) { + (void)arduino_string_concat_bytes( + &buffer, &capacity, &len, cstr, cstr ? static_cast(strlen(cstr)) : 0); + return *this; +} + +inline String &String::operator+=(char c) { + (void)arduino_string_concat_bytes(&buffer, &capacity, &len, reinterpret_cast(&c), 1); + return *this; +} + +inline String &String::operator+=(unsigned char num) { + (void)arduino_string_concat_unsigned_char(&buffer, &capacity, &len, num); + return *this; +} + +inline String &String::operator+=(int num) { + (void)arduino_string_concat_int(&buffer, &capacity, &len, num); + return *this; +} + +inline String &String::operator+=(unsigned int num) { + (void)arduino_string_concat_unsigned_int(&buffer, &capacity, &len, num); + return *this; +} + +inline String &String::operator+=(long num) { + (void)arduino_string_concat_long(&buffer, &capacity, &len, num); + return *this; +} + +inline String &String::operator+=(unsigned long num) { + (void)arduino_string_concat_unsigned_long(&buffer, &capacity, &len, num); + return *this; +} + +inline String &String::operator+=(float num) { + (void)arduino_string_concat_float(&buffer, &capacity, &len, num); + return *this; +} + +inline String &String::operator+=(double num) { + (void)arduino_string_concat_double(&buffer, &capacity, &len, num); + return *this; +} + +inline String &String::operator+=(const __FlashStringHelper *str) { + concat(str); + return *this; +} + +inline String::operator StringIfHelperType() const { + return buffer ? &String::StringIfHelper : 0; +} + +inline int String::compareTo(const String &s) const { + return compareTo(s.buffer); +} + +inline int String::compareTo(const char *cstr) const { + return arduino_string_compare_cstr(buffer, len, cstr); +} + +inline bool String::equals(const String &s) const { + return arduino_string_equals_bytes(buffer, len, s.buffer, s.len); +} + +inline bool String::equals(const char *cstr) const { + return arduino_string_equals_cstr(buffer, len, cstr); +} + +inline bool String::equalsIgnoreCase(const String &s) const { + if (this == &s) { + return true; + } + return arduino_string_equals_ignore_case(buffer, len, s.buffer, s.len); +} + +inline bool String::startsWith(const String &prefix) const { + return arduino_string_starts_with(buffer, len, prefix.buffer, prefix.len, 0); +} + +inline bool String::startsWith(const String &prefix, unsigned int offset) const { + return arduino_string_starts_with(buffer, len, prefix.buffer, prefix.len, offset); +} + +inline bool String::endsWith(const String &suffix) const { + return arduino_string_ends_with(buffer, len, suffix.buffer, suffix.len); +} + +inline char String::charAt(unsigned int index) const { + return operator[](index); +} + +inline void String::setCharAt(unsigned int index, char c) { + if (index < len && buffer) { + buffer[index] = c; + } +} + +inline char String::operator[](unsigned int index) const { + if (index >= len || !buffer) { + return 0; + } + return buffer[index]; +} + +inline char &String::operator[](unsigned int index) { + static char dummy_writable_char; + if (index >= len || !buffer) { + dummy_writable_char = 0; + return dummy_writable_char; + } + return buffer[index]; +} + +inline void String::getBytes(unsigned char *buf, unsigned int bufsize, unsigned int index) const { + arduino_string_get_bytes(buffer, len, buf, bufsize, index); +} + +inline void String::toCharArray(char *buf, unsigned int bufsize, unsigned int index) const { + getBytes(reinterpret_cast(buf), bufsize, index); +} + +inline const char *String::c_str() const { + return buffer; +} + +inline char *String::begin() { + return buffer; +} + +inline char *String::end() { + return buffer + length(); +} + +inline const char *String::begin() const { + return c_str(); +} + +inline const char *String::end() const { + return c_str() + length(); +} + +inline int String::indexOf(char ch) const { + return arduino_string_index_of_char(buffer, len, ch, 0); +} + +inline int String::indexOf(char ch, unsigned int fromIndex) const { + return arduino_string_index_of_char(buffer, len, ch, fromIndex); +} + +inline int String::indexOf(const String &str) const { + return arduino_string_index_of_bytes(buffer, len, str.buffer, str.len, 0); +} + +inline int String::indexOf(const String &str, unsigned int fromIndex) const { + return arduino_string_index_of_bytes(buffer, len, str.buffer, str.len, fromIndex); +} + +inline int String::lastIndexOf(char ch) const { + return lastIndexOf(ch, len - 1); +} + +inline int String::lastIndexOf(char ch, unsigned int fromIndex) const { + return arduino_string_last_index_of_char(buffer, len, ch, fromIndex); +} + +inline int String::lastIndexOf(const String &str) const { + return lastIndexOf(str, len - str.len); +} + +inline int String::lastIndexOf(const String &str, unsigned int fromIndex) const { + return arduino_string_last_index_of_bytes(buffer, len, str.buffer, str.len, fromIndex); +} + +inline String String::substring(unsigned int beginIndex) const { + return substring(beginIndex, len); +} + +inline String String::substring(unsigned int beginIndex, unsigned int endIndex) const { + String out(""); + if (!arduino_string_copy_substring(buffer, len, beginIndex, endIndex, &out.buffer, + &out.capacity, &out.len)) { + out.invalidate(); + } + return out; +} + +inline void String::replace(char find, char replace) { + arduino_string_replace_char(buffer, len, find, replace); +} + +inline void String::replace(const String &find, const String &replace) { + (void)arduino_string_replace_bytes(&buffer, &capacity, &len, find.buffer, find.len, + replace.buffer, replace.len); +} + +inline void String::remove(unsigned int index) { + len = arduino_string_remove(buffer, len, index, static_cast(-1)); +} + +inline void String::remove(unsigned int index, unsigned int count) { + len = arduino_string_remove(buffer, len, index, count); +} + +inline void String::toLowerCase(void) { + if (!buffer) { + return; + } + for (char *p = buffer; *p; ++p) { + *p = static_cast(tolower(static_cast(*p))); + } +} + +inline void String::toUpperCase(void) { + if (!buffer) { + return; + } + for (char *p = buffer; *p; ++p) { + *p = static_cast(toupper(static_cast(*p))); + } +} + +inline void String::trim(void) { + if (!buffer || len == 0) { + return; + } + + char *begin = buffer; + while (begin < buffer + len && isspace(static_cast(*begin))) { + ++begin; + } + + if (begin >= buffer + len) { + len = 0; + buffer[0] = 0; + return; + } + + char *end = buffer + len - 1; + while (end > begin && isspace(static_cast(*end))) { + --end; + } + + len = static_cast(end + 1 - begin); + if (begin > buffer) { + memmove(buffer, begin, len); + } + buffer[len] = 0; +} + +inline void String::reverse(void) { + arduino_string_reverse(buffer, len); +} + +inline long String::toInt(void) const { + return buffer ? strtol(buffer, NULL, 10) : 0; +} + +inline float String::toFloat(void) const { + return static_cast(toDouble()); +} + +inline double String::toDouble(void) const { + return buffer ? strtod(buffer, NULL) : 0.0; +} + +inline void String::invalidate(void) { + arduino_string_free(&buffer, &capacity, &len); +} + +inline void String::invalidateOnFailure(bool success) { + if (!success) { + invalidate(); + } +} + +inline bool String::changeBuffer(unsigned int maxStrLen) { + return arduino_string_change_buffer(&buffer, &capacity, &len, maxStrLen); +} + +inline String &String::copy(const char *cstr, unsigned int length) { + invalidateOnFailure(cstr && arduino_string_copy_bytes(&buffer, &capacity, &len, cstr, length)); + return *this; +} + +inline String &String::copy(const __FlashStringHelper *pstr, unsigned int length) { + invalidateOnFailure( + pstr && arduino_string_copy_bytes(&buffer, &capacity, &len, (const char *)pstr, length)); + return *this; +} + +inline void String::move(String &rhs) { + if (this != &rhs) { + arduino_string_free(&buffer, &capacity, &len); + buffer = rhs.buffer; + len = rhs.len; + capacity = rhs.capacity; + rhs.buffer = NULL; + rhs.len = 0; + rhs.capacity = 0; + } +} + +inline StringSumHelper::StringSumHelper(const String &s) : String(s) { +} + +inline StringSumHelper::StringSumHelper(const char *p) : String(p) { +} + +inline StringSumHelper::StringSumHelper(char c) : String(c) { +} + +inline StringSumHelper::StringSumHelper(unsigned char num) : String(num) { +} + +inline StringSumHelper::StringSumHelper(int num) : String(num) { +} + +inline StringSumHelper::StringSumHelper(unsigned int num) : String(num) { +} + +inline StringSumHelper::StringSumHelper(long num) : String(num) { +} + +inline StringSumHelper::StringSumHelper(unsigned long num) : String(num) { +} + +inline StringSumHelper::StringSumHelper(float num) : String(num) { +} + +inline StringSumHelper::StringSumHelper(double num) : String(num) { +} + +inline StringSumHelper &operator+(const StringSumHelper &lhs, const String &rhs) { + StringSumHelper &a = const_cast(lhs); + a.invalidateOnFailure(arduino_string_concat_bytes(&a.buffer, &a.capacity, &a.len, rhs.buffer, rhs.len)); + return a; +} + +inline StringSumHelper &operator+(const StringSumHelper &lhs, const char *cstr) { + StringSumHelper &a = const_cast(lhs); + a.invalidateOnFailure(arduino_string_concat_bytes( + &a.buffer, &a.capacity, &a.len, cstr, cstr ? static_cast(strlen(cstr)) : 0)); + return a; +} + +inline StringSumHelper &operator+(const StringSumHelper &lhs, char c) { + StringSumHelper &a = const_cast(lhs); + a.invalidateOnFailure(arduino_string_concat_bytes( + &a.buffer, &a.capacity, &a.len, reinterpret_cast(&c), 1)); + return a; +} + +inline StringSumHelper &operator+(const StringSumHelper &lhs, unsigned char num) { + StringSumHelper &a = const_cast(lhs); + a.invalidateOnFailure(arduino_string_concat_unsigned_char(&a.buffer, &a.capacity, &a.len, num)); + return a; +} + +inline StringSumHelper &operator+(const StringSumHelper &lhs, int num) { + StringSumHelper &a = const_cast(lhs); + a.invalidateOnFailure(arduino_string_concat_int(&a.buffer, &a.capacity, &a.len, num)); + return a; +} + +inline StringSumHelper &operator+(const StringSumHelper &lhs, unsigned int num) { + StringSumHelper &a = const_cast(lhs); + a.invalidateOnFailure(arduino_string_concat_unsigned_int(&a.buffer, &a.capacity, &a.len, num)); + return a; +} + +inline StringSumHelper &operator+(const StringSumHelper &lhs, long num) { + StringSumHelper &a = const_cast(lhs); + a.invalidateOnFailure(arduino_string_concat_long(&a.buffer, &a.capacity, &a.len, num)); + return a; +} + +inline StringSumHelper &operator+(const StringSumHelper &lhs, unsigned long num) { + StringSumHelper &a = const_cast(lhs); + a.invalidateOnFailure(arduino_string_concat_unsigned_long(&a.buffer, &a.capacity, &a.len, num)); + return a; +} + +inline StringSumHelper &operator+(const StringSumHelper &lhs, float num) { + StringSumHelper &a = const_cast(lhs); + a.invalidateOnFailure(arduino_string_concat_float(&a.buffer, &a.capacity, &a.len, num)); + return a; +} + +inline StringSumHelper &operator+(const StringSumHelper &lhs, double num) { + StringSumHelper &a = const_cast(lhs); + a.invalidateOnFailure(arduino_string_concat_double(&a.buffer, &a.capacity, &a.len, num)); + return a; +} + +inline StringSumHelper &operator+(const StringSumHelper &lhs, const __FlashStringHelper *rhs) { + StringSumHelper &a = const_cast(lhs); + a.invalidateOnFailure(arduino_string_concat_bytes( + &a.buffer, + &a.capacity, + &a.len, + (const char *)rhs, + rhs ? static_cast(strlen((const char *)rhs)) : 0)); + return a; +} + +inline bool operator==(const String &a, const String &b) { + return arduino_string_equals_bytes(a.buffer, a.len, b.buffer, b.len); +} + +inline bool operator==(const String &a, const char *b) { + return arduino_string_equals_cstr(a.buffer, a.len, b); +} + +inline bool operator==(const char *a, const String &b) { + return arduino_string_equals_cstr(b.buffer, b.len, a); +} + +inline bool operator<(const String &a, const String &b) { + return arduino_string_compare_cstr(a.buffer, a.len, b.buffer) < 0; +} + +inline bool operator<(const String &a, const char *b) { + return arduino_string_compare_cstr(a.buffer, a.len, b) < 0; +} + +inline bool operator<(const char *a, const String &b) { + return arduino_string_compare_cstr(b.buffer, b.len, a) > 0; +} + +inline bool operator!=(const String &a, const String &b) { + return !arduino_string_equals_bytes(a.buffer, a.len, b.buffer, b.len); +} + +inline bool operator!=(const String &a, const char *b) { + return !arduino_string_equals_cstr(a.buffer, a.len, b); +} + +inline bool operator!=(const char *a, const String &b) { + return !arduino_string_equals_cstr(b.buffer, b.len, a); +} + +inline bool operator>(const String &a, const String &b) { + return arduino_string_compare_cstr(a.buffer, a.len, b.buffer) > 0; +} + +inline bool operator>(const String &a, const char *b) { + return arduino_string_compare_cstr(a.buffer, a.len, b) > 0; +} + +inline bool operator>(const char *a, const String &b) { + return arduino_string_compare_cstr(b.buffer, b.len, a) < 0; +} + +inline bool operator<=(const String &a, const String &b) { + return arduino_string_compare_cstr(a.buffer, a.len, b.buffer) <= 0; +} + +inline bool operator<=(const String &a, const char *b) { + return arduino_string_compare_cstr(a.buffer, a.len, b) <= 0; +} + +inline bool operator<=(const char *a, const String &b) { + return arduino_string_compare_cstr(b.buffer, b.len, a) >= 0; +} + +inline bool operator>=(const String &a, const String &b) { + return arduino_string_compare_cstr(a.buffer, a.len, b.buffer) >= 0; +} + +inline bool operator>=(const String &a, const char *b) { + return arduino_string_compare_cstr(a.buffer, a.len, b) >= 0; +} + +inline bool operator>=(const char *a, const String &b) { + return arduino_string_compare_cstr(b.buffer, b.len, a) <= 0; +} + +} // namespace arduino diff --git a/alt_arduinocore_api/api/Udp.h b/alt_arduinocore_api/api/Udp.h new file mode 100644 index 000000000..31a0da722 --- /dev/null +++ b/alt_arduinocore_api/api/Udp.h @@ -0,0 +1,19 @@ +/* + * Copyright (c) 2026 TOKITA Hiroshi + * + * SPDX-License-Identifier: Apache-2.0 + */ + +#pragma once + +#include "Stream.h" +#include + +namespace arduino { + +class Udp : virtual public Stream, virtual public UDPInterface { +protected: + uint8_t* rawIPAddress(IPAddress& addr); //TODO +} + +}; diff --git a/alt_arduinocore_api/api/WCharacter.h b/alt_arduinocore_api/api/WCharacter.h new file mode 100644 index 000000000..f12306fb0 --- /dev/null +++ b/alt_arduinocore_api/api/WCharacter.h @@ -0,0 +1,94 @@ +/* + * Copyright (c) 2026 TOKITA Hiroshi + * + * SPDX-License-Identifier: Apache-2.0 + */ + +#pragma once + +extern "C" { +bool arduino_wchar_is_alnum(int c); +bool arduino_wchar_is_alpha(int c); +bool arduino_wchar_is_ascii(int c); +bool arduino_wchar_is_blank(int c); +bool arduino_wchar_is_cntrl(int c); +bool arduino_wchar_is_digit(int c); +bool arduino_wchar_is_graph(int c); +bool arduino_wchar_is_lower(int c); +bool arduino_wchar_is_print(int c); +bool arduino_wchar_is_punct(int c); +bool arduino_wchar_is_space(int c); +bool arduino_wchar_is_upper(int c); +bool arduino_wchar_is_xdigit(int c); +int arduino_wchar_to_ascii(int c); +int arduino_wchar_to_lower(int c); +int arduino_wchar_to_upper(int c); +} + +namespace arduino { + +ALWAYS_INLINE bool isAlphaNumeric(int c) { + return arduino_wchar_is_alnum(c); +} + +ALWAYS_INLINE bool isAlpha(int c) { + return arduino_wchar_is_alpha(c); +} + +ALWAYS_INLINE bool isAscii(int c) { + return arduino_wchar_is_ascii(c); +} + +ALWAYS_INLINE bool isWhitespace(int c) { + return arduino_wchar_is_blank(c); +} + +ALWAYS_INLINE bool isControl(int c) { + return arduino_wchar_is_cntrl(c); +} + +ALWAYS_INLINE bool isDigit(int c) { + return arduino_wchar_is_digit(c); +} + +ALWAYS_INLINE bool isGraph(int c) { + return arduino_wchar_is_graph(c); +} + +ALWAYS_INLINE bool isLowerCase(int c) { + return arduino_wchar_is_lower(c); +} + +ALWAYS_INLINE bool isPrintable(int c) { + return arduino_wchar_is_print(c); +} + +ALWAYS_INLINE bool isPunct(int c) { + return arduino_wchar_is_punct(c); +} + +ALWAYS_INLINE bool isSpace(int c) { + return arduino_wchar_is_space(c); +} + +ALWAYS_INLINE bool isUpperCase(int c) { + return arduino_wchar_is_upper(c); +} + +ALWAYS_INLINE bool isHexadecimalDigit(int c) { + return arduino_wchar_is_xdigit(c); +} + +ALWAYS_INLINE int toAscii(int c) { + return arduino_wchar_to_ascii(c); +} + +ALWAYS_INLINE int toLowerCase(int c) { + return arduino_wchar_to_lower(c); +} + +ALWAYS_INLINE int toUpperCase(int c) { + return arduino_wchar_to_upper(c); +} + +} diff --git a/alt_arduinocore_api/api/cstdlib.c b/alt_arduinocore_api/api/cstdlib.c new file mode 100755 index 000000000..e0d18e411 --- /dev/null +++ b/alt_arduinocore_api/api/cstdlib.c @@ -0,0 +1,199 @@ +/* + * Copyright (c) 2006-2022, RT-Thread Development Team + * + * SPDX-License-Identifier: Apache-2.0 + * + * Change Logs: + * Date Author Notes + * 2021-02-15 Meco Man first version + */ + +#if 0 +#include +#include + +#define DBG_TAG "stdlib" +#define DBG_LVL DBG_INFO +#include + +/** + * @brief This function is called when a thread exits. It can detach the thread and perform cleanup. + * + * @param status is the exit status of the thread. + */ +void __rt_libc_exit(int status) +{ + rt_thread_t self = rt_thread_self(); + + if (self != RT_NULL) + { + LOG_W("thread:%s exit:%d!", self->parent.name, status); +#ifdef RT_USING_PTHREADS + if (self->pthread_data != RT_NULL) + { + extern void pthread_exit(void *value); + pthread_exit((void *)status); + } + else +#endif + { + rt_thread_control(self, RT_THREAD_CTRL_CLOSE, RT_NULL); + } + } +} + +#ifdef RT_USING_MSH +/** + * @brief Execute a command using the Micro-Shell (MSH) subsystem. + * + * @param command is the command string to execute. + * + * @return Returns 0 after executing the command. + */ +int system(const char *command) +{ + extern int msh_exec(char *cmd, rt_size_t length); + + if (command) + { + msh_exec((char *)command, rt_strlen(command)); + } + + return 0; +} +RTM_EXPORT(system); +#endif /* RT_USING_MSH */ + +/** + * @brief Convert a long integer to a string representation with a specified radix. + * + * @param value is the long integer to convert. + * @param string is the destination string where the result will be stored. + * @param radix is the base of the number system to be used for conversion. + * + * @return Returns a pointer to the destination string. + */ +char *ltoa(long value, char *string, int radix) +{ + char tmp[33]; + char *tp = tmp; + long i; + unsigned long v; + int sign; + char *sp; + + if (string == NULL) + { + return 0; + } + + if (radix > 36 || radix <= 1) + { + return 0; + } + + sign = (radix == 10 && value < 0); + if (sign) + { + v = -value; + } + else + { + v = (unsigned long)value; + } + + while (v || tp == tmp) + { + i = v % radix; + v = v / radix; + if (i < 10) + *tp++ = (char)(i + '0'); + else + *tp++ = (char)(i + 'a' - 10); + } + + sp = string; + + if (sign) + *sp++ = '-'; + while (tp > tmp) + *sp++ = *--tp; + *sp = 0; + + return string; +} + +/** + * @brief Convert an integer to a string representation with a specified radix. + * + * @param value is the integer to convert. + * @param string is the destination string where the result will be stored. + * @param radix is the base of the number system to be used for conversion. + * + * @return Returns a pointer to the destination string. + */ +char *itoa(int value, char *string, int radix) +{ + return ltoa(value, string, radix); +} +#endif + +/** + * @brief Convert an unsigned long integer to a string representation with a specified radix. + * + * @param value is the unsigned long integer to convert. + * @param string is the destination string where the result will be stored. + * @param radix is the base of the number system to be used for conversion. + * + * @return Returns a pointer to the destination string. + */ +char *ultoa(unsigned long value, char *string, int radix) +{ + char tmp[33]; + char *tp = tmp; + long i; + unsigned long v = value; + char *sp; + + if (string == NULL) + { + return 0; + } + + if (radix > 36 || radix <= 1) + { + return 0; + } + + while (v || tp == tmp) + { + i = v % radix; + v = v / radix; + if (i < 10) + *tp++ = (char)(i + '0'); + else + *tp++ = (char)(i + 'a' - 10); + } + + sp = string; + + while (tp > tmp) + *sp++ = *--tp; + *sp = 0; + + return string; +} + +/** + * @brief Convert an unsigned integer to a string representation with a specified radix. + * + * @param value is the unsigned integer to convert. + * @param string is the destination string where the result will be stored. + * @param radix is the base of the number system to be used for conversion. + * + * @return Returns a pointer to the destination string. + */ +char *utoa(unsigned value, char *string, int radix) +{ + return ultoa(value, string, radix); +} diff --git a/alt_arduinocore_api/api/itoa.h b/alt_arduinocore_api/api/itoa.h new file mode 100644 index 000000000..45c780725 --- /dev/null +++ b/alt_arduinocore_api/api/itoa.h @@ -0,0 +1,13 @@ +/* + * Copyright (c) 2026 TOKITA Hiroshi + * + * SPDX-License-Identifier: Apache-2.0 + */ + +#pragma once + +#include + +char* ltoa(long value, char *string, int radix); //TODO +char* ultoa(unsigned long value, char *string, int radix); //TODO + diff --git a/alt_arduinocore_api/idl/CMakeLists.txt b/alt_arduinocore_api/idl/CMakeLists.txt new file mode 100644 index 000000000..71017633f --- /dev/null +++ b/alt_arduinocore_api/idl/CMakeLists.txt @@ -0,0 +1,82 @@ +# SPDX-License-Identifier: Apache-2.0 + +list(APPEND CMAKE_MODULE_PATH ${ZEPHYR_BASE}/modules/nanopb) +include(nanopb) + +set(ARDUINO_IDL_PROTO_DIR ${CMAKE_CURRENT_SOURCE_DIR}/proto) +set(ARDUINO_IDL_CLASS_PROTO_FILES + print.proto + stream.proto + client.proto + server.proto + hardware_serial.proto + hardware_i2c.proto + hardware_spi.proto + hardware_can.proto + udp.proto + pluggable_usb_module.proto + printable.proto +) +set(ARDUINO_IDL_OPTS_PROTO ${ARDUINO_IDL_PROTO_DIR}/arduino_opts.proto) +set(ARDUINO_IDL_SHARED_PROTO_FILES + common.proto +) +set(ARDUINO_IDL_PLUGIN ${CMAKE_CURRENT_SOURCE_DIR}/../../tools/protoc-gen-arduinoif) +set(ARDUINO_IDL_NANOPB_PROTO_DIR ${NANOPB_SRC_ROOT_FOLDER}/generator/proto) +set(ARDUINO_IDL_IF_OUTPUT_DIR ${CMAKE_CURRENT_BINARY_DIR}/generated/arduinoif) +set(ARDUINO_IDL_TYPE_HEADERS) +foreach(proto_file IN LISTS ARDUINO_IDL_SHARED_PROTO_FILES) + get_filename_component(proto_stem ${proto_file} NAME_WE) + list(APPEND ARDUINO_IDL_TYPE_HEADERS + ${ARDUINO_IDL_IF_OUTPUT_DIR}/${proto_stem}_types.h) +endforeach() + +set(ARDUINO_IDL_CLASS_PROTOS) +set(ARDUINO_IDL_CLASS_PROTO_REL_PATHS) +set(ARDUINO_IDL_IF_HEADERS) +foreach(proto_file IN LISTS ARDUINO_IDL_CLASS_PROTO_FILES) + get_filename_component(proto_stem ${proto_file} NAME_WE) + list(APPEND ARDUINO_IDL_CLASS_PROTOS + ${ARDUINO_IDL_PROTO_DIR}/${proto_file}) + list(APPEND ARDUINO_IDL_CLASS_PROTO_REL_PATHS + proto/${proto_file}) + list(APPEND ARDUINO_IDL_IF_HEADERS + ${ARDUINO_IDL_IF_OUTPUT_DIR}/${proto_stem}_interface.hpp) +endforeach() +set(ARDUINO_IDL_PLUGIN_INPUT_PROTOS + ${ARDUINO_IDL_CLASS_PROTOS} +) +foreach(proto_file IN LISTS ARDUINO_IDL_SHARED_PROTO_FILES) + list(APPEND ARDUINO_IDL_PLUGIN_INPUT_PROTOS + ${ARDUINO_IDL_PROTO_DIR}/${proto_file}) +endforeach() + +zephyr_library_named(arduinocore_idl) +zephyr_nanopb_sources(arduinocore_idl + proto/google/protobuf/empty.proto + proto/google/protobuf/wrappers.proto + proto/arduino_opts.proto + ${ARDUINO_IDL_CLASS_PROTO_REL_PATHS} +) + +add_custom_command( + OUTPUT ${ARDUINO_IDL_IF_HEADERS} ${ARDUINO_IDL_TYPE_HEADERS} + COMMAND ${CMAKE_COMMAND} -E make_directory ${ARDUINO_IDL_IF_OUTPUT_DIR} + COMMAND ${PROTOBUF_PROTOC_EXECUTABLE} + --plugin=protoc-gen-arduinoif=${ARDUINO_IDL_PLUGIN} + --arduinoif_out=${ARDUINO_IDL_IF_OUTPUT_DIR} + --proto_path=${ARDUINO_IDL_PROTO_DIR} + --proto_path=${ARDUINO_IDL_NANOPB_PROTO_DIR} + ${ARDUINO_IDL_PLUGIN_INPUT_PROTOS} + DEPENDS + ${ARDUINO_IDL_PLUGIN_INPUT_PROTOS} + ${ARDUINO_IDL_OPTS_PROTO} + ${ARDUINO_IDL_PLUGIN} + COMMENT "Generating C++ interface headers from Arduino IDL proto files" + VERBATIM +) + +add_custom_target(arduinocore_idl_generate_interfaces ALL DEPENDS ${ARDUINO_IDL_IF_HEADERS} ${ARDUINO_IDL_TYPE_HEADERS}) +add_dependencies(arduinocore_idl arduinocore_idl_generate_interfaces) + +zephyr_include_directories(${ARDUINO_IDL_IF_OUTPUT_DIR}) diff --git a/alt_arduinocore_api/idl/proto/arduino_opts.proto b/alt_arduinocore_api/idl/proto/arduino_opts.proto new file mode 100644 index 000000000..3dcace7cb --- /dev/null +++ b/alt_arduinocore_api/idl/proto/arduino_opts.proto @@ -0,0 +1,40 @@ +/* + * Copyright (c) 2026 TOKITA Hiroshi + * + * SPDX-License-Identifier: Apache-2.0 + */ + +syntax = "proto3"; + +package arduino; + +import "google/protobuf/descriptor.proto"; + +extend google.protobuf.FieldOptions { + string cpp_type = 50001; + string field_cpp_name = 50002; +} + +extend google.protobuf.MethodOptions { + string cpp_name = 50101; + string cpp_return = 50102; + string cpp_decl = 50103; + bool source_virtual = 50111; + bool emit_api = 50112; + bool emit_service = 50113; + string method_visibility = 50114; +} + +extend google.protobuf.ServiceOptions { + bool generate_api_class = 50201; + bool generate_service_class = 50202; + bool generate_service_impl_class = 50203; + string ifc_class_name = 50211; + string api_class_name = 50212; + string service_class_name = 50213; + string service_impl_class_name = 50214; + string api_member_name = 50215; + repeated string base_services = 50216; + string ifc_header_name = 50217; + repeated string extra_includes = 50218; +} diff --git a/alt_arduinocore_api/idl/proto/client.proto b/alt_arduinocore_api/idl/proto/client.proto new file mode 100644 index 000000000..0f949ab6a --- /dev/null +++ b/alt_arduinocore_api/idl/proto/client.proto @@ -0,0 +1,36 @@ +/* + * Copyright (c) 2026 TOKITA Hiroshi + * + * SPDX-License-Identifier: Apache-2.0 + */ + +syntax = "proto3"; + +package arduino; + +import "arduino_opts.proto"; +import "google/protobuf/empty.proto"; +import "google/protobuf/wrappers.proto"; + +service Client { + option (arduino.extra_includes) = "api/IPAddress.h"; + + rpc ConnectAddress(google.protobuf.Empty) returns (google.protobuf.Empty) { + option (arduino.cpp_decl) = "int connect(IPAddress ip, uint16_t port)"; + } + rpc ConnectHost(google.protobuf.Empty) returns (google.protobuf.Empty) { + option (arduino.cpp_decl) = "int connect(const char *host, uint16_t port)"; + } + rpc ReadBuffer(google.protobuf.Empty) returns (google.protobuf.Empty) { + option (arduino.cpp_decl) = "int read(uint8_t *buf, size_t size)"; + } + rpc Stop(google.protobuf.Empty) returns (google.protobuf.Empty) { + option (arduino.cpp_decl) = "void stop()"; + } + rpc Connected(google.protobuf.Empty) returns (google.protobuf.UInt32Value) { + option (arduino.cpp_decl) = "uint8_t connected()"; + } + rpc IsValid(google.protobuf.Empty) returns (google.protobuf.BoolValue) { + option (arduino.cpp_decl) = "operator bool()"; + } +} diff --git a/alt_arduinocore_api/idl/proto/common.proto b/alt_arduinocore_api/idl/proto/common.proto new file mode 100644 index 000000000..6f47aba78 --- /dev/null +++ b/alt_arduinocore_api/idl/proto/common.proto @@ -0,0 +1,35 @@ +/* + * Copyright (c) 2026 TOKITA Hiroshi + * + * SPDX-License-Identifier: Apache-2.0 + */ + +syntax = "proto3"; + +package arduino; + +enum PinStatus { + LOW = 0; + HIGH = 1; + CHANGE = 2; + FALLING = 3; + RISING = 4; +} + +enum PinMode { + INPUT = 0; + OUTPUT = 1; + INPUT_PULLUP = 2; + INPUT_PULLDOWN = 3; + OUTPUT_OPENDRAIN = 4; +} + +enum BitOrder { + LSBFIRST = 0; + MSBFIRST = 1; +} + +enum IPType { + IPv4 = 0; + IPv6 = 1; +} diff --git a/alt_arduinocore_api/idl/proto/google/protobuf/empty.proto b/alt_arduinocore_api/idl/proto/google/protobuf/empty.proto new file mode 100644 index 000000000..778d17bc3 --- /dev/null +++ b/alt_arduinocore_api/idl/proto/google/protobuf/empty.proto @@ -0,0 +1,5 @@ +syntax = "proto3"; + +package google.protobuf; + +message Empty {} diff --git a/alt_arduinocore_api/idl/proto/google/protobuf/wrappers.proto b/alt_arduinocore_api/idl/proto/google/protobuf/wrappers.proto new file mode 100644 index 000000000..3e80f94f0 --- /dev/null +++ b/alt_arduinocore_api/idl/proto/google/protobuf/wrappers.proto @@ -0,0 +1,39 @@ +syntax = "proto3"; + +package google.protobuf; + +message DoubleValue { + double value = 1; +} + +message FloatValue { + float value = 1; +} + +message Int64Value { + int64 value = 1; +} + +message UInt64Value { + uint64 value = 1; +} + +message Int32Value { + int32 value = 1; +} + +message UInt32Value { + uint32 value = 1; +} + +message BoolValue { + bool value = 1; +} + +message StringValue { + string value = 1; +} + +message BytesValue { + bytes value = 1; +} diff --git a/alt_arduinocore_api/idl/proto/hardware_can.proto b/alt_arduinocore_api/idl/proto/hardware_can.proto new file mode 100644 index 000000000..a4c32c8ab --- /dev/null +++ b/alt_arduinocore_api/idl/proto/hardware_can.proto @@ -0,0 +1,41 @@ +/* + * Copyright (c) 2026 TOKITA Hiroshi + * + * SPDX-License-Identifier: Apache-2.0 + */ + +syntax = "proto3"; + +package arduino; + +import "arduino_opts.proto"; +import "google/protobuf/empty.proto"; +import "google/protobuf/wrappers.proto"; + +enum CanBitRate { + BR_UNSPECIFIED = 0; + BR_125k = 125000; + BR_250k = 250000; + BR_500k = 500000; + BR_1000k = 1000000; +} + +service HardwareCAN { + option (arduino.extra_includes) = "api/HardwareCAN.h"; + + rpc Begin(google.protobuf.Empty) returns (google.protobuf.BoolValue) { + option (arduino.cpp_decl) = "bool begin(CanBitRate const can_bitrate)"; + } + rpc End(google.protobuf.Empty) returns (google.protobuf.Empty) { + option (arduino.cpp_decl) = "void end()"; + } + rpc Write(google.protobuf.Empty) returns (google.protobuf.Empty) { + option (arduino.cpp_decl) = "int write(CanMsg const &msg)"; + } + rpc Available(google.protobuf.Empty) returns (google.protobuf.UInt64Value) { + option (arduino.cpp_decl) = "size_t available()"; + } + rpc Read(google.protobuf.Empty) returns (google.protobuf.Empty) { + option (arduino.cpp_decl) = "CanMsg read()"; + } +} diff --git a/alt_arduinocore_api/idl/proto/hardware_i2c.proto b/alt_arduinocore_api/idl/proto/hardware_i2c.proto new file mode 100644 index 000000000..6986ed37a --- /dev/null +++ b/alt_arduinocore_api/idl/proto/hardware_i2c.proto @@ -0,0 +1,45 @@ +syntax = "proto3"; + +package arduino; + +import "arduino_opts.proto"; +import "google/protobuf/empty.proto"; +import "google/protobuf/wrappers.proto"; + +service HardwareI2C { + option (arduino.ifc_header_name) = "hardware_i2c_interface.hpp"; + + rpc Begin(google.protobuf.Empty) returns (google.protobuf.Empty) { + option (arduino.cpp_decl) = "void begin()"; + } + rpc BeginAddress(google.protobuf.UInt32Value) returns (google.protobuf.Empty) { + option (arduino.cpp_decl) = "void begin(uint8_t address)"; + } + rpc End(google.protobuf.Empty) returns (google.protobuf.Empty) { + option (arduino.cpp_decl) = "void end()"; + } + rpc SetClock(google.protobuf.UInt32Value) returns (google.protobuf.Empty) { + option (arduino.cpp_decl) = "void setClock(uint32_t freq)"; + } + rpc BeginTransmission(google.protobuf.UInt32Value) returns (google.protobuf.Empty) { + option (arduino.cpp_decl) = "void beginTransmission(uint8_t address)"; + } + rpc EndTransmissionStop(google.protobuf.BoolValue) returns (google.protobuf.UInt32Value) { + option (arduino.cpp_decl) = "uint8_t endTransmission(bool stopBit)"; + } + rpc EndTransmission(google.protobuf.Empty) returns (google.protobuf.UInt32Value) { + option (arduino.cpp_decl) = "uint8_t endTransmission(void)"; + } + rpc RequestFromStop(google.protobuf.Empty) returns (google.protobuf.Empty) { + option (arduino.cpp_decl) = "size_t requestFrom(uint8_t address, size_t len, bool stopBit)"; + } + rpc RequestFrom(google.protobuf.Empty) returns (google.protobuf.Empty) { + option (arduino.cpp_decl) = "size_t requestFrom(uint8_t address, size_t len)"; + } + rpc OnReceive(google.protobuf.Empty) returns (google.protobuf.Empty) { + option (arduino.cpp_decl) = "void onReceive(void (*cb)(int))"; + } + rpc OnRequest(google.protobuf.Empty) returns (google.protobuf.Empty) { + option (arduino.cpp_decl) = "void onRequest(void (*cb)(void))"; + } +} diff --git a/alt_arduinocore_api/idl/proto/hardware_serial.proto b/alt_arduinocore_api/idl/proto/hardware_serial.proto new file mode 100644 index 000000000..c1e685cf0 --- /dev/null +++ b/alt_arduinocore_api/idl/proto/hardware_serial.proto @@ -0,0 +1,61 @@ +/* + * Copyright (c) 2026 TOKITA Hiroshi + * + * SPDX-License-Identifier: Apache-2.0 + */ + +syntax = "proto3"; + +package arduino; + +import "arduino_opts.proto"; +import "google/protobuf/empty.proto"; +import "google/protobuf/wrappers.proto"; + +enum SerialParity { + SERIAL_PARITY_UNSPECIFIED = 0; + SERIAL_PARITY_EVEN = 0x1; + SERIAL_PARITY_ODD = 0x2; + SERIAL_PARITY_NONE = 0x3; + SERIAL_PARITY_MARK = 0x4; + SERIAL_PARITY_SPACE = 0x5; + SERIAL_PARITY_MASK = 0xF; +} + +enum SerialStopBit { + SERIAL_STOP_BIT_UNSPECIFIED = 0; + SERIAL_STOP_BIT_1 = 0x10; + SERIAL_STOP_BIT_1_5 = 0x20; + SERIAL_STOP_BIT_2 = 0x30; + SERIAL_STOP_BIT_MASK = 0xF0; +} + +enum SerialDataLen { + SERIAL_DATA_UNSPECIFIED = 0; + SERIAL_DATA_5 = 0x100; + SERIAL_DATA_6 = 0x200; + SERIAL_DATA_7 = 0x300; + SERIAL_DATA_8 = 0x400; + SERIAL_DATA_MASK = 0xF00; +} + +service HardwareSerial { + option (arduino.base_services) = "Stream"; + option (arduino.generate_api_class) = true; + option (arduino.generate_service_class) = true; + option (arduino.generate_service_impl_class) = true; + option (arduino.api_class_name) = "arduino::HardwareSerial"; + + rpc BeginBaud(google.protobuf.UInt64Value) returns (google.protobuf.Empty) { + option (arduino.cpp_decl) = "void begin(unsigned long baudrate)"; + } + rpc BeginBaudConfig(google.protobuf.Empty) returns (google.protobuf.Empty) { + option (arduino.cpp_decl) = "void begin(unsigned long baudrate, uint16_t config)"; + } + rpc End(google.protobuf.Empty) returns (google.protobuf.Empty) { + option (arduino.cpp_decl) = "void end()"; + } + rpc IsValid(google.protobuf.Empty) returns (google.protobuf.BoolValue) { + option (arduino.cpp_decl) = "operator bool()"; + } +} diff --git a/alt_arduinocore_api/idl/proto/hardware_spi.proto b/alt_arduinocore_api/idl/proto/hardware_spi.proto new file mode 100644 index 000000000..0d249f401 --- /dev/null +++ b/alt_arduinocore_api/idl/proto/hardware_spi.proto @@ -0,0 +1,63 @@ +/* + * Copyright (c) 2026 TOKITA Hiroshi + * + * SPDX-License-Identifier: Apache-2.0 + */ + +syntax = "proto3"; + +package arduino; + +import "arduino_opts.proto"; +import "google/protobuf/empty.proto"; +import "google/protobuf/wrappers.proto"; + +enum SPIMode { + SPI_MODE0 = 0; + SPI_MODE1 = 1; + SPI_MODE2 = 2; + SPI_MODE3 = 3; +} + +enum SPIBusMode { + SPI_CONTROLLER = 0; + SPI_PERIPHERAL = 1; +} + +service HardwareSPI { + option (arduino.extra_includes) = "api/HardwareSPI.h"; + + rpc Transfer(google.protobuf.UInt32Value) returns (google.protobuf.UInt32Value) { + option (arduino.cpp_decl) = "uint8_t transfer(uint8_t data)"; + } + rpc Transfer16(google.protobuf.UInt32Value) returns (google.protobuf.UInt32Value) { + option (arduino.cpp_decl) = "uint16_t transfer16(uint16_t data)"; + } + rpc TransferBuffer(google.protobuf.Empty) returns (google.protobuf.Empty) { + option (arduino.cpp_decl) = "void transfer(void *buf, size_t count)"; + } + rpc UsingInterrupt(google.protobuf.Int32Value) returns (google.protobuf.Empty) { + option (arduino.cpp_decl) = "void usingInterrupt(int interruptNumber)"; + } + rpc NotUsingInterrupt(google.protobuf.Int32Value) returns (google.protobuf.Empty) { + option (arduino.cpp_decl) = "void notUsingInterrupt(int interruptNumber)"; + } + rpc BeginTransaction(google.protobuf.Empty) returns (google.protobuf.Empty) { + option (arduino.cpp_decl) = "void beginTransaction(SPISettings settings)"; + } + rpc EndTransaction(google.protobuf.Empty) returns (google.protobuf.Empty) { + option (arduino.cpp_decl) = "void endTransaction(void)"; + } + rpc AttachInterrupt(google.protobuf.Empty) returns (google.protobuf.Empty) { + option (arduino.cpp_decl) = "void attachInterrupt()"; + } + rpc DetachInterrupt(google.protobuf.Empty) returns (google.protobuf.Empty) { + option (arduino.cpp_decl) = "void detachInterrupt()"; + } + rpc Begin(google.protobuf.Empty) returns (google.protobuf.Empty) { + option (arduino.cpp_decl) = "void begin()"; + } + rpc End(google.protobuf.Empty) returns (google.protobuf.Empty) { + option (arduino.cpp_decl) = "void end()"; + } +} diff --git a/alt_arduinocore_api/idl/proto/pluggable_usb_module.proto b/alt_arduinocore_api/idl/proto/pluggable_usb_module.proto new file mode 100644 index 000000000..0b41ac4bf --- /dev/null +++ b/alt_arduinocore_api/idl/proto/pluggable_usb_module.proto @@ -0,0 +1,27 @@ +/* + * Copyright (c) 2026 TOKITA Hiroshi + * + * SPDX-License-Identifier: Apache-2.0 + */ + +syntax = "proto3"; + +package arduino; + +import "arduino_opts.proto"; +import "google/protobuf/empty.proto"; +import "google/protobuf/wrappers.proto"; + +service PluggableUSBModule { + option (arduino.extra_includes) = "api/PluggableUSB.h"; + + rpc Setup(google.protobuf.Empty) returns (google.protobuf.BoolValue) { + option (arduino.cpp_decl) = "bool setup(USBSetup& setup)"; + } + rpc GetInterface(google.protobuf.Empty) returns (google.protobuf.Int32Value) { + option (arduino.cpp_decl) = "int getInterface(uint8_t* interfaceCount)"; + } + rpc GetDescriptor(google.protobuf.Empty) returns (google.protobuf.Int32Value) { + option (arduino.cpp_decl) = "int getDescriptor(USBSetup& setup)"; + } +} diff --git a/alt_arduinocore_api/idl/proto/print.proto b/alt_arduinocore_api/idl/proto/print.proto new file mode 100644 index 000000000..46b8972b8 --- /dev/null +++ b/alt_arduinocore_api/idl/proto/print.proto @@ -0,0 +1,28 @@ +/* + * Copyright (c) 2026 TOKITA Hiroshi + * + * SPDX-License-Identifier: Apache-2.0 + */ + +syntax = "proto3"; + +package arduino; + +import "arduino_opts.proto"; +import "google/protobuf/empty.proto"; +import "google/protobuf/wrappers.proto"; + +service Print { + rpc WriteByte(google.protobuf.UInt32Value) returns (google.protobuf.UInt32Value) { + option (arduino.cpp_decl) = "size_t write(uint8_t value)"; + } + rpc WriteBuffer(google.protobuf.BytesValue) returns (google.protobuf.UInt32Value) { + option (arduino.cpp_decl) = "size_t write(const uint8_t *buffer, size_t size)"; + } + rpc AvailableForWrite(google.protobuf.Empty) returns (google.protobuf.Int32Value) { + option (arduino.cpp_decl) = "int availableForWrite()"; + } + rpc Flush(google.protobuf.Empty) returns (google.protobuf.Empty) { + option (arduino.cpp_decl) = "void flush()"; + } +} diff --git a/alt_arduinocore_api/idl/proto/printable.proto b/alt_arduinocore_api/idl/proto/printable.proto new file mode 100644 index 000000000..31c3f9363 --- /dev/null +++ b/alt_arduinocore_api/idl/proto/printable.proto @@ -0,0 +1,21 @@ +/* + * Copyright (c) 2026 TOKITA Hiroshi + * + * SPDX-License-Identifier: Apache-2.0 + */ + +syntax = "proto3"; + +package arduino; + +import "arduino_opts.proto"; +import "google/protobuf/empty.proto"; +import "google/protobuf/wrappers.proto"; + +service Printable { + option (arduino.extra_includes) = "api/Print.h"; + + rpc PrintTo(google.protobuf.Empty) returns (google.protobuf.UInt64Value) { + option (arduino.cpp_decl) = "size_t printTo(Print& p) const"; + } +} diff --git a/alt_arduinocore_api/idl/proto/server.proto b/alt_arduinocore_api/idl/proto/server.proto new file mode 100644 index 000000000..93bc8007f --- /dev/null +++ b/alt_arduinocore_api/idl/proto/server.proto @@ -0,0 +1,18 @@ +/* + * Copyright (c) 2026 TOKITA Hiroshi + * + * SPDX-License-Identifier: Apache-2.0 + */ + +syntax = "proto3"; + +package arduino; + +import "arduino_opts.proto"; +import "google/protobuf/empty.proto"; + +service Server { + rpc Begin(google.protobuf.Empty) returns (google.protobuf.Empty) { + option (arduino.cpp_decl) = "void begin()"; + } +} diff --git a/alt_arduinocore_api/idl/proto/stream.proto b/alt_arduinocore_api/idl/proto/stream.proto new file mode 100644 index 000000000..a8c1cffbe --- /dev/null +++ b/alt_arduinocore_api/idl/proto/stream.proto @@ -0,0 +1,33 @@ +/* + * Copyright (c) 2026 TOKITA Hiroshi + * + * SPDX-License-Identifier: Apache-2.0 + */ + +syntax = "proto3"; + +package arduino; + +import "arduino_opts.proto"; +import "google/protobuf/empty.proto"; +import "google/protobuf/wrappers.proto"; + +enum LookaheadMode { + SKIP_ALL = 0; + SKIP_NONE = 1; + SKIP_WHITESPACE = 2; +} + +service Stream { + option (arduino.base_services) = "Print"; + + rpc Available(google.protobuf.Empty) returns (google.protobuf.Int32Value) { + option (arduino.cpp_decl) = "int available()"; + } + rpc Read(google.protobuf.Empty) returns (google.protobuf.Int32Value) { + option (arduino.cpp_decl) = "int read()"; + } + rpc Peek(google.protobuf.Empty) returns (google.protobuf.Int32Value) { + option (arduino.cpp_decl) = "int peek()"; + } +} diff --git a/alt_arduinocore_api/idl/proto/udp.proto b/alt_arduinocore_api/idl/proto/udp.proto new file mode 100644 index 000000000..017310d50 --- /dev/null +++ b/alt_arduinocore_api/idl/proto/udp.proto @@ -0,0 +1,48 @@ +/* + * Copyright (c) 2026 TOKITA Hiroshi + * + * SPDX-License-Identifier: Apache-2.0 + */ + +syntax = "proto3"; + +package arduino; + +import "arduino_opts.proto"; +import "google/protobuf/empty.proto"; +import "google/protobuf/wrappers.proto"; + +service UDP { + option (arduino.extra_includes) = "api/IPAddress.h"; + + rpc Begin(google.protobuf.UInt32Value) returns (google.protobuf.UInt32Value) { + option (arduino.cpp_decl) = "uint8_t begin(uint16_t port)"; + } + rpc Stop(google.protobuf.Empty) returns (google.protobuf.Empty) { + option (arduino.cpp_decl) = "void stop()"; + } + rpc BeginPacketIp(google.protobuf.Empty) returns (google.protobuf.Empty) { + option (arduino.cpp_decl) = "int beginPacket(IPAddress ip, uint16_t port)"; + } + rpc BeginPacketHost(google.protobuf.Empty) returns (google.protobuf.Empty) { + option (arduino.cpp_decl) = "int beginPacket(const char *host, uint16_t port)"; + } + rpc EndPacket(google.protobuf.Empty) returns (google.protobuf.Int32Value) { + option (arduino.cpp_decl) = "int endPacket()"; + } + rpc ParsePacket(google.protobuf.Empty) returns (google.protobuf.Int32Value) { + option (arduino.cpp_decl) = "int parsePacket()"; + } + rpc ReadUnsigned(google.protobuf.Empty) returns (google.protobuf.Empty) { + option (arduino.cpp_decl) = "int read(unsigned char *buffer, size_t len)"; + } + rpc ReadChar(google.protobuf.Empty) returns (google.protobuf.Empty) { + option (arduino.cpp_decl) = "int read(char *buffer, size_t len)"; + } + rpc RemoteIP(google.protobuf.Empty) returns (google.protobuf.Empty) { + option (arduino.cpp_decl) = "IPAddress remoteIP()"; + } + rpc RemotePort(google.protobuf.Empty) returns (google.protobuf.UInt32Value) { + option (arduino.cpp_decl) = "uint16_t remotePort()"; + } +} diff --git a/rust/Cargo.toml b/alt_arduinocore_api/rust/Cargo.toml similarity index 100% rename from rust/Cargo.toml rename to alt_arduinocore_api/rust/Cargo.toml diff --git a/alt_arduinocore_api/rust/src/common.rs b/alt_arduinocore_api/rust/src/common.rs new file mode 100644 index 000000000..eebb9de94 --- /dev/null +++ b/alt_arduinocore_api/rust/src/common.rs @@ -0,0 +1,149 @@ +// Copyright (c) 2025 TOKITA Hiroshi +// SPDX-License-Identifier: Apache-2.0 + +use core::ffi::c_int; + +const CTYPE_EOF: c_int = -1; + +unsafe extern "C" { + fn isalnum(c: c_int) -> c_int; + fn isalpha(c: c_int) -> c_int; + fn isblank(c: c_int) -> c_int; + fn iscntrl(c: c_int) -> c_int; + fn isdigit(c: c_int) -> c_int; + fn isgraph(c: c_int) -> c_int; + fn islower(c: c_int) -> c_int; + fn isprint(c: c_int) -> c_int; + fn ispunct(c: c_int) -> c_int; + fn isspace(c: c_int) -> c_int; + fn isupper(c: c_int) -> c_int; + fn isxdigit(c: c_int) -> c_int; + fn tolower(c: c_int) -> c_int; + fn toupper(c: c_int) -> c_int; +} + +#[unsafe(no_mangle)] +pub extern "C" fn map_i32( + x: i32, in_min: i32, in_max: i32, out_min: i32, out_max: i32 +) -> i32 { + let num = x.wrapping_sub(in_min).wrapping_mul(out_max.wrapping_sub(out_min)); + let den = in_max.wrapping_sub(in_min); + // Note: To keep compatibility, the panic when den=0 is left as is. + num / den.wrapping_add(out_min) +} + +#[unsafe(no_mangle)] +pub extern "C" fn makeWord_w(w: u16) -> u16 { + w +} + +#[unsafe(no_mangle)] +pub extern "C" fn makeWord_hl(h: u8, l: u8) -> u16 { + ((h as u16) << 8) | (l as u16) +} + +#[inline] +fn ctype_arg(c: c_int) -> Option { + if c == CTYPE_EOF || (0..=255).contains(&c) { + Some(c) + } else { + None + } +} + +#[inline] +fn ctype_predicate(c: c_int, pred: unsafe extern "C" fn(c_int) -> c_int) -> bool { + match ctype_arg(c) { + // SAFETY: ctype_arg ensures an argument value accepted by C ctype (EOF or unsigned char range). + Some(arg) => unsafe { pred(arg) != 0 }, + None => false, + } +} + +#[unsafe(no_mangle)] +pub extern "C" fn arduino_wchar_is_alnum(c: c_int) -> bool { + ctype_predicate(c, isalnum) +} + +#[unsafe(no_mangle)] +pub extern "C" fn arduino_wchar_is_alpha(c: c_int) -> bool { + ctype_predicate(c, isalpha) +} + +#[unsafe(no_mangle)] +pub extern "C" fn arduino_wchar_is_ascii(c: c_int) -> bool { + (c & !0x7f) == 0 +} + +#[unsafe(no_mangle)] +pub extern "C" fn arduino_wchar_is_blank(c: c_int) -> bool { + ctype_predicate(c, isblank) +} + +#[unsafe(no_mangle)] +pub extern "C" fn arduino_wchar_is_cntrl(c: c_int) -> bool { + ctype_predicate(c, iscntrl) +} + +#[unsafe(no_mangle)] +pub extern "C" fn arduino_wchar_is_digit(c: c_int) -> bool { + ctype_predicate(c, isdigit) +} + +#[unsafe(no_mangle)] +pub extern "C" fn arduino_wchar_is_graph(c: c_int) -> bool { + ctype_predicate(c, isgraph) +} + +#[unsafe(no_mangle)] +pub extern "C" fn arduino_wchar_is_lower(c: c_int) -> bool { + ctype_predicate(c, islower) +} + +#[unsafe(no_mangle)] +pub extern "C" fn arduino_wchar_is_print(c: c_int) -> bool { + ctype_predicate(c, isprint) +} + +#[unsafe(no_mangle)] +pub extern "C" fn arduino_wchar_is_punct(c: c_int) -> bool { + ctype_predicate(c, ispunct) +} + +#[unsafe(no_mangle)] +pub extern "C" fn arduino_wchar_is_space(c: c_int) -> bool { + ctype_predicate(c, isspace) +} + +#[unsafe(no_mangle)] +pub extern "C" fn arduino_wchar_is_upper(c: c_int) -> bool { + ctype_predicate(c, isupper) +} + +#[unsafe(no_mangle)] +pub extern "C" fn arduino_wchar_is_xdigit(c: c_int) -> bool { + ctype_predicate(c, isxdigit) +} + +#[unsafe(no_mangle)] +pub extern "C" fn arduino_wchar_to_ascii(c: c_int) -> c_int { + c & 0x7f +} + +#[unsafe(no_mangle)] +pub extern "C" fn arduino_wchar_to_lower(c: c_int) -> c_int { + match ctype_arg(c) { + // SAFETY: ctype_arg ensures an argument value accepted by C tolower (EOF or unsigned char range). + Some(arg) => unsafe { tolower(arg) }, + None => c, + } +} + +#[unsafe(no_mangle)] +pub extern "C" fn arduino_wchar_to_upper(c: c_int) -> c_int { + match ctype_arg(c) { + // SAFETY: ctype_arg ensures an argument value accepted by C toupper (EOF or unsigned char range). + Some(arg) => unsafe { toupper(arg) }, + None => c, + } +} diff --git a/rust/src/lib.rs b/alt_arduinocore_api/rust/src/lib.rs similarity index 94% rename from rust/src/lib.rs rename to alt_arduinocore_api/rust/src/lib.rs index 2f3e5e00a..cd632fd0b 100644 --- a/rust/src/lib.rs +++ b/alt_arduinocore_api/rust/src/lib.rs @@ -4,6 +4,7 @@ #![no_std] mod common; +mod string; pub use common::*; use core::panic::PanicInfo; diff --git a/alt_arduinocore_api/rust/src/string.rs b/alt_arduinocore_api/rust/src/string.rs new file mode 100644 index 000000000..970732666 --- /dev/null +++ b/alt_arduinocore_api/rust/src/string.rs @@ -0,0 +1,1334 @@ +// Copyright (c) 2026 TOKITA Hiroshi +// SPDX-License-Identifier: Apache-2.0 + +use core::ffi::{c_char, c_double, c_int, c_long, c_uint, c_ulong, c_void, CStr}; +use core::mem::size_of; +use core::ptr; + +unsafe extern "C" { + fn malloc(size: usize) -> *mut c_void; + fn realloc(ptr: *mut c_void, size: usize) -> *mut c_void; + fn free(ptr: *mut c_void); + fn snprintf(s: *mut c_char, n: usize, format: *const c_char, ...) -> c_int; +} + +#[inline] +fn state_mut<'a>( + buffer: *mut *mut c_char, + capacity: *mut c_uint, + len: *mut c_uint, +) -> Option<(&'a mut *mut c_char, &'a mut c_uint, &'a mut c_uint)> { + if buffer.is_null() || capacity.is_null() || len.is_null() { + None + } else { + // SAFETY: null is checked above and callers pass valid pointers. + Some(unsafe { (&mut *buffer, &mut *capacity, &mut *len) }) + } +} + +#[inline] +unsafe fn c_strlen(s: *const c_char) -> usize { + if s.is_null() { + return 0; + } + // SAFETY: caller ensures `s` points to a valid NUL-terminated C string. + unsafe { CStr::from_ptr(s).to_bytes().len() } +} + +#[inline] +fn first_byte(s: *const c_char) -> Option { + if s.is_null() { + None + } else { + // SAFETY: null is checked above. + Some(unsafe { *s as u8 }) + } +} + +#[inline] +unsafe fn bytes_match( + base: *const c_char, + offset: usize, + pat: *const c_char, + pat_len: usize, +) -> bool { + // SAFETY: caller validates both ranges are readable. + unsafe { + let lhs = core::slice::from_raw_parts(base.add(offset).cast::(), pat_len); + let rhs = core::slice::from_raw_parts(pat.cast::(), pat_len); + lhs == rhs + } +} + +#[inline] +unsafe fn index_of_char_from( + buffer: *const c_char, + len: usize, + target: u8, + from: usize, +) -> Option { + if from >= len { + return None; + } + // SAFETY: caller guarantees [from, len) are readable. + unsafe { + let tail = core::slice::from_raw_parts(buffer.add(from).cast::(), len - from); + tail.iter().position(|&b| b == target).map(|idx| idx + from) + } +} + +#[inline] +unsafe fn index_of_bytes_from( + buffer: *const c_char, + hay_len: usize, + pat: *const c_char, + pat_len: usize, + from: usize, +) -> Option { + if pat_len == 0 { + return Some(from); + } + if pat_len > hay_len.saturating_sub(from) { + return None; + } + + // SAFETY: caller guarantees ranges are readable. + unsafe { + let hay = core::slice::from_raw_parts(buffer.cast::(), hay_len); + let needle = core::slice::from_raw_parts(pat.cast::(), pat_len); + let max_start = hay_len - pat_len; + for i in from..=max_start { + if &hay[i..i + pat_len] == needle { + return Some(i); + } + } + None + } +} + +#[inline] +fn index_or_minus_one(index: Option) -> c_int { + index.map(|idx| idx as c_int).unwrap_or(-1) +} + +#[inline] +fn checked_forward_search( + buffer: *const c_char, + len: c_uint, + from_index: c_uint, +) -> Option<(usize, usize)> { + if buffer.is_null() || from_index >= len { + return None; + } + Some((len as usize, from_index as usize)) +} + +#[inline] +fn checked_match_window( + buffer: *const c_char, + len: c_uint, + pat: *const c_char, + pat_len: c_uint, + offset: c_uint, +) -> Option<(usize, usize)> { + if len < pat_len { + return None; + } + if offset > len.saturating_sub(pat_len) { + return None; + } + if buffer.is_null() || pat.is_null() { + return None; + } + Some((offset as usize, pat_len as usize)) +} + +#[inline] +unsafe fn compare_bytes( + lhs: *const c_char, + rhs: *const c_char, + len: usize, +) -> c_int { + // SAFETY: caller validates both ranges are readable. + unsafe { + let lhs_bytes = core::slice::from_raw_parts(lhs.cast::(), len); + let rhs_bytes = core::slice::from_raw_parts(rhs.cast::(), len); + for (&a, &b) in lhs_bytes.iter().zip(rhs_bytes.iter()) { + let (x, y) = if IGNORE_CASE { + (a.to_ascii_lowercase(), b.to_ascii_lowercase()) + } else { + (a, b) + }; + if x != y { + return x as c_int - y as c_int; + } + } + 0 + } +} + +#[inline] +unsafe fn bytes_equal( + lhs: *const c_char, + rhs: *const c_char, + len: usize, +) -> bool { + // SAFETY: caller validates both ranges are readable. + unsafe { compare_bytes::(lhs, rhs, len) == 0 } +} + +#[inline] +unsafe fn last_index_of_bytes_from( + buffer: *const c_char, + hay_len: usize, + pat: *const c_char, + pat_len: usize, + from_index: usize, +) -> Option { + if hay_len == 0 || pat_len == 0 || pat_len > hay_len { + return None; + } + + let from = core::cmp::min(from_index, hay_len - 1); + if from + 1 < pat_len { + return None; + } + + // SAFETY: caller validates both ranges are readable. + unsafe { + let hay = core::slice::from_raw_parts(buffer.cast::(), hay_len); + let needle = core::slice::from_raw_parts(pat.cast::(), pat_len); + let max_start = core::cmp::min(from, hay_len - pat_len); + for i in (0..=max_start).rev() { + if &hay[i..i + pat_len] == needle { + return Some(i); + } + } + None + } +} + +#[inline] +unsafe fn count_non_overlapping_matches( + buffer: *const c_char, + hay_len: usize, + pat: *const c_char, + pat_len: usize, +) -> usize { + // SAFETY: caller validates both ranges are readable. + unsafe { + let hay = core::slice::from_raw_parts(buffer.cast::(), hay_len); + let needle = core::slice::from_raw_parts(pat.cast::(), pat_len); + let mut count = 0usize; + let mut pos = 0usize; + while pos + pat_len <= hay_len { + if &hay[pos..pos + pat_len] == needle { + count += 1; + pos += pat_len; + } else { + pos += 1; + } + } + count + } +} + +#[inline] +fn compute_replaced_len( + current_len: c_uint, + find_len: c_uint, + replace_len: c_uint, + match_count: usize, +) -> Option { + let match_count_i64 = i64::try_from(match_count).ok()?; + let len_diff = replace_len as i64 - find_len as i64; + let delta = len_diff.checked_mul(match_count_i64)?; + let new_len_i64 = (current_len as i64).checked_add(delta)?; + if !(0..=(c_uint::MAX as i64)).contains(&new_len_i64) { + return None; + } + Some(new_len_i64 as c_uint) +} + +#[inline] +unsafe fn write_replaced_bytes( + src: *const c_char, + src_len: usize, + find: *const c_char, + find_len: usize, + replace: *const c_char, + replace_len: usize, + dst: *mut c_char, + dst_capacity: usize, +) -> usize { + // SAFETY: caller validates source ranges and destination capacity. + unsafe { + let src_bytes = core::slice::from_raw_parts(src.cast::(), src_len); + let find_bytes = core::slice::from_raw_parts(find.cast::(), find_len); + let replace_bytes: &[u8] = if replace_len == 0 { + &[] + } else { + core::slice::from_raw_parts(replace.cast::(), replace_len) + }; + let dst_bytes = core::slice::from_raw_parts_mut(dst.cast::(), dst_capacity); + + let mut src_pos = 0usize; + let mut dst_pos = 0usize; + while src_pos < src_len { + if src_pos + find_len <= src_len + && &src_bytes[src_pos..src_pos + find_len] == find_bytes + { + if replace_len > 0 { + dst_bytes[dst_pos..dst_pos + replace_len].copy_from_slice(replace_bytes); + } + src_pos += find_len; + dst_pos += replace_len; + } else { + dst_bytes[dst_pos] = src_bytes[src_pos]; + src_pos += 1; + dst_pos += 1; + } + } + + dst_bytes[dst_pos] = 0; + dst_pos + } +} + +enum ReplaceBytesFlow { + Return(bool), + Proceed { + current_len: c_uint, + hay_len: usize, + needle_len: usize, + replace_len: usize, + }, +} + +#[inline] +fn validate_replace_bytes( + buffer: *mut c_char, + current_len: c_uint, + find: *const c_char, + find_len: c_uint, + replace: *const c_char, + replace_len: c_uint, +) -> ReplaceBytesFlow { + if current_len == 0 || find_len == 0 { + return ReplaceBytesFlow::Return(true); + } + if buffer.is_null() || find.is_null() { + return ReplaceBytesFlow::Return(false); + } + if replace.is_null() && replace_len != 0 { + return ReplaceBytesFlow::Return(false); + } + + let hay_len = current_len as usize; + let needle_len = find_len as usize; + if needle_len > hay_len { + return ReplaceBytesFlow::Return(true); + } + + ReplaceBytesFlow::Proceed { + current_len, + hay_len, + needle_len, + replace_len: replace_len as usize, + } +} + +#[inline] +fn execute_replace_bytes( + buffer: &mut *mut c_char, + capacity: &mut c_uint, + len: &mut c_uint, + find: *const c_char, + replace: *const c_char, + flow: &ReplaceBytesFlow, +) -> bool { + let ReplaceBytesFlow::Proceed { + current_len, + hay_len, + needle_len, + replace_len, + } = flow + else { + return false; + }; + + // SAFETY: `flow` is validated by `validate_replace_bytes`, and pointers/lengths here are coherent. + unsafe { + let match_count = count_non_overlapping_matches(*buffer, *hay_len, find, *needle_len); + if match_count == 0 { + return true; + } + + let Some(new_len) = compute_replaced_len( + *current_len, + *needle_len as c_uint, + *replace_len as c_uint, + match_count, + ) else { + return false; + }; + + let scratch_size = (new_len as usize).saturating_add(1); + let scratch_ptr = malloc(scratch_size) as *mut c_char; + if scratch_ptr.is_null() { + return false; + } + + let written = write_replaced_bytes( + *buffer, + *hay_len, + find, + *needle_len, + replace, + *replace_len, + scratch_ptr, + scratch_size, + ); + + if !ensure_capacity(buffer, capacity, len, new_len) { + free(scratch_ptr.cast()); + return false; + } + + ptr::copy_nonoverlapping( + scratch_ptr.cast::(), + (*buffer).cast::(), + written + 1, + ); + free(scratch_ptr.cast()); + *len = new_len; + true + } +} + +#[inline] +fn ensure_capacity( + buffer: &mut *mut c_char, + capacity: &mut c_uint, + len: &mut c_uint, + needed_len: c_uint, +) -> bool { + if !(*buffer).is_null() && *capacity >= needed_len { + return true; + } + + let alloc_size = (needed_len as usize).saturating_add(1); + // SAFETY: realloc supports a null source pointer. + let new_buffer = unsafe { realloc((*buffer).cast(), alloc_size) as *mut c_char }; + if new_buffer.is_null() { + return false; + } + + *buffer = new_buffer; + *capacity = needed_len; + + if *len > *capacity { + *len = *capacity; + } + + // SAFETY: buffer has capacity+1 bytes and len <= capacity. + unsafe { + *(*buffer).add(*len as usize) = 0; + } + + true +} + +#[unsafe(no_mangle)] +pub extern "C" fn arduino_string_free( + buffer: *mut *mut c_char, + capacity: *mut c_uint, + len: *mut c_uint, +) { + let Some((buffer, capacity, len)) = state_mut(buffer, capacity, len) else { + return; + }; + + if !(*buffer).is_null() { + // SAFETY: buffer pointer comes from malloc/realloc. + unsafe { + free((*buffer).cast()); + } + } + + *buffer = ptr::null_mut(); + *capacity = 0; + *len = 0; +} + +#[unsafe(no_mangle)] +pub extern "C" fn arduino_string_change_buffer( + buffer: *mut *mut c_char, + capacity: *mut c_uint, + len: *mut c_uint, + max_str_len: c_uint, +) -> bool { + let Some((buffer, capacity, len)) = state_mut(buffer, capacity, len) else { + return false; + }; + + ensure_capacity(buffer, capacity, len, max_str_len) +} + +#[unsafe(no_mangle)] +pub extern "C" fn arduino_string_copy_bytes( + buffer: *mut *mut c_char, + capacity: *mut c_uint, + len: *mut c_uint, + src: *const c_char, + length: c_uint, +) -> bool { + let Some((buffer, capacity, len)) = state_mut(buffer, capacity, len) else { + return false; + }; + + if src.is_null() { + return false; + } + + if !ensure_capacity(buffer, capacity, len, length) { + return false; + } + + // SAFETY: destination has enough space. Copy ranges do not overlap when `length > 0`. + // Writing the terminating NUL at `length` is in-bounds. + unsafe { + if length > 0 { + ptr::copy_nonoverlapping(src.cast::(), (*buffer).cast::(), length as usize); + } + *len = length; + *(*buffer).add(length as usize) = 0; + } + + true +} + +#[inline] +fn normalized_base(base: u8) -> u32 { + if (2..=36).contains(&base) { + base as u32 + } else { + 10 + } +} + +#[inline] +fn write_unsigned_base(mut value: u64, base: u32, out: &mut [c_char]) -> Option { + const DIGITS: &[u8; 36] = b"0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZ"; + let mut scratch = [0u8; 66]; + let mut count = 0usize; + + loop { + let digit = (value % (base as u64)) as usize; + scratch[count] = DIGITS[digit]; + count += 1; + value /= base as u64; + if value == 0 { + break; + } + } + + if count + 1 > out.len() { + return None; + } + + for i in 0..count { + out[i] = scratch[count - 1 - i] as c_char; + } + out[count] = 0; + Some(count) +} + +#[inline] +fn write_signed_base(value: i64, base: u32, out: &mut [c_char]) -> Option { + if base == 10 && value < 0 { + if out.len() < 2 { + return None; + } + out[0] = b'-' as c_char; + let magnitude = ((-(value + 1)) as u64) + 1; + let used = write_unsigned_base(magnitude, base, &mut out[1..])?; + Some(1 + used) + } else { + write_unsigned_base(value as u64, base, out) + } +} + +#[inline] +fn apply_snprintf_result( + buffer: *mut *mut c_char, + capacity: *mut c_uint, + len: *mut c_uint, + tmp: *const c_char, + tmp_cap: usize, + written: c_int, + apply: extern "C" fn(*mut *mut c_char, *mut c_uint, *mut c_uint, *const c_char, c_uint) -> bool, +) -> bool { + if written < 0 { + return false; + } + + let text_len = if (written as usize) < tmp_cap { + written as c_uint + } else { + // Match C++ behavior when local buffer truncates. + unsafe { c_strlen(tmp) as c_uint } + }; + + apply(buffer, capacity, len, tmp, text_len) +} + +macro_rules! snprintf_to_copy { + ($buffer:expr, $capacity:expr, $len:expr, $tmp_size:expr, $fmt:expr, $($arg:expr),+ $(,)?) => {{ + let mut tmp = [0 as c_char; $tmp_size]; + // SAFETY: tmp points to writable memory and format string is valid. + let written = unsafe { + snprintf( + tmp.as_mut_ptr(), + tmp.len(), + $fmt.as_ptr().cast::(), + $($arg),+ + ) + }; + apply_snprintf_result( + $buffer, + $capacity, + $len, + tmp.as_ptr(), + tmp.len(), + written, + arduino_string_copy_bytes, + ) + }}; +} + +macro_rules! snprintf_to_concat { + ($buffer:expr, $capacity:expr, $len:expr, $tmp_size:expr, $fmt:expr, $($arg:expr),+ $(,)?) => {{ + let mut tmp = [0 as c_char; $tmp_size]; + // SAFETY: tmp points to writable memory and format string is valid. + let written = unsafe { + snprintf( + tmp.as_mut_ptr(), + tmp.len(), + $fmt.as_ptr().cast::(), + $($arg),+ + ) + }; + apply_snprintf_result( + $buffer, + $capacity, + $len, + tmp.as_ptr(), + tmp.len(), + written, + arduino_string_concat_bytes, + ) + }}; +} + +#[inline] +fn copy_unsigned_base_value( + buffer: *mut *mut c_char, + capacity: *mut c_uint, + len: *mut c_uint, + value: u64, + base: u8, + tmp: &mut [c_char], +) -> bool { + let Some(text_len) = write_unsigned_base(value, normalized_base(base), tmp) else { + return false; + }; + arduino_string_copy_bytes(buffer, capacity, len, tmp.as_ptr(), text_len as c_uint) +} + +#[inline] +fn copy_signed_base_value( + buffer: *mut *mut c_char, + capacity: *mut c_uint, + len: *mut c_uint, + value: i64, + base: u8, + tmp: &mut [c_char], +) -> bool { + let Some(text_len) = write_signed_base(value, normalized_base(base), tmp) else { + return false; + }; + arduino_string_copy_bytes(buffer, capacity, len, tmp.as_ptr(), text_len as c_uint) +} + +#[inline] +fn copy_single_char( + buffer: *mut *mut c_char, + capacity: *mut c_uint, + len: *mut c_uint, + value: c_char, +) -> bool { + let tmp = [value, 0]; + arduino_string_copy_bytes(buffer, capacity, len, tmp.as_ptr(), 1) +} + +macro_rules! define_copy_base_unsigned { + ($fn_name:ident, $value_ty:ty, $tmp_size:expr) => { + #[unsafe(no_mangle)] + pub extern "C" fn $fn_name( + buffer: *mut *mut c_char, + capacity: *mut c_uint, + len: *mut c_uint, + value: $value_ty, + base: u8, + ) -> bool { + let mut tmp = [0 as c_char; $tmp_size]; + copy_unsigned_base_value(buffer, capacity, len, value as u64, base, &mut tmp) + } + }; +} + +macro_rules! define_copy_base_signed { + ($fn_name:ident, $value_ty:ty, $tmp_size:expr) => { + #[unsafe(no_mangle)] + pub extern "C" fn $fn_name( + buffer: *mut *mut c_char, + capacity: *mut c_uint, + len: *mut c_uint, + value: $value_ty, + base: u8, + ) -> bool { + let mut tmp = [0 as c_char; $tmp_size]; + copy_signed_base_value(buffer, capacity, len, value as i64, base, &mut tmp) + } + }; +} + +#[unsafe(no_mangle)] +pub extern "C" fn arduino_string_copy_char( + buffer: *mut *mut c_char, + capacity: *mut c_uint, + len: *mut c_uint, + value: c_char, +) -> bool { + copy_single_char(buffer, capacity, len, value) +} + +define_copy_base_unsigned!( + arduino_string_copy_unsigned_char_base, + u8, + 1 + 8 * size_of::() +); +define_copy_base_signed!( + arduino_string_copy_int_base, + c_int, + 2 + 8 * size_of::() +); +define_copy_base_unsigned!( + arduino_string_copy_unsigned_int_base, + c_uint, + 1 + 8 * size_of::() +); +define_copy_base_signed!( + arduino_string_copy_long_base, + c_long, + 2 + 8 * size_of::() +); +define_copy_base_unsigned!( + arduino_string_copy_unsigned_long_base, + c_ulong, + 1 + 8 * size_of::() +); + +#[unsafe(no_mangle)] +pub extern "C" fn arduino_string_copy_float_precision( + buffer: *mut *mut c_char, + capacity: *mut c_uint, + len: *mut c_uint, + value: f32, + decimal_places: u8, +) -> bool { + copy_float_precision_value(buffer, capacity, len, value as c_double, decimal_places) +} + +#[unsafe(no_mangle)] +pub extern "C" fn arduino_string_copy_double_precision( + buffer: *mut *mut c_char, + capacity: *mut c_uint, + len: *mut c_uint, + value: c_double, + decimal_places: u8, +) -> bool { + copy_float_precision_value(buffer, capacity, len, value, decimal_places) +} + +#[inline] +fn copy_float_precision_value( + buffer: *mut *mut c_char, + capacity: *mut c_uint, + len: *mut c_uint, + value: c_double, + decimal_places: u8, +) -> bool { + let precision = core::cmp::min(decimal_places as c_int, 10); + // SAFETY: tmp points to writable memory, format string is valid. + snprintf_to_copy!(buffer, capacity, len, 64, b"%.*f\0", precision, value) +} + +#[unsafe(no_mangle)] +pub extern "C" fn arduino_string_copy_substring( + src_buffer: *const c_char, + src_len: c_uint, + left: c_uint, + right: c_uint, + out_buffer: *mut *mut c_char, + out_capacity: *mut c_uint, + out_len: *mut c_uint, +) -> bool { + const EMPTY: [u8; 1] = [0]; + + if src_buffer.is_null() { + if src_len != 0 { + return false; + } + return arduino_string_copy_bytes( + out_buffer, + out_capacity, + out_len, + EMPTY.as_ptr().cast::(), + 0, + ); + } + + let mut begin = left; + let mut end = right; + if begin > end { + core::mem::swap(&mut begin, &mut end); + } + if begin >= src_len { + return arduino_string_copy_bytes( + out_buffer, + out_capacity, + out_len, + EMPTY.as_ptr().cast::(), + 0, + ); + } + if end > src_len { + end = src_len; + } + + let copy_len = end - begin; + if copy_len == 0 { + return arduino_string_copy_bytes( + out_buffer, + out_capacity, + out_len, + EMPTY.as_ptr().cast::(), + 0, + ); + } + + let src = src_buffer.wrapping_add(begin as usize); + arduino_string_copy_bytes(out_buffer, out_capacity, out_len, src, copy_len) +} + +#[unsafe(no_mangle)] +pub extern "C" fn arduino_string_concat_bytes( + buffer: *mut *mut c_char, + capacity: *mut c_uint, + len: *mut c_uint, + src: *const c_char, + length: c_uint, +) -> bool { + let Some((buffer, capacity, len)) = state_mut(buffer, capacity, len) else { + return false; + }; + + if src.is_null() { + return false; + } + + if length == 0 { + return true; + } + + let Some(new_len) = (*len).checked_add(length) else { + return false; + }; + + if !ensure_capacity(buffer, capacity, len, new_len) { + return false; + } + + // SAFETY: destination has enough space and copy ranges do not overlap. + // Writing terminating NUL at `new_len` is in-bounds. + unsafe { + ptr::copy_nonoverlapping( + src.cast::(), + (*buffer).add(*len as usize).cast::(), + length as usize, + ); + *len = new_len; + *(*buffer).add(new_len as usize) = 0; + } + + true +} + +macro_rules! define_concat_base10_unsigned { + ($fn_name:ident, $value_ty:ty, $tmp_size:expr) => { + #[unsafe(no_mangle)] + pub extern "C" fn $fn_name( + buffer: *mut *mut c_char, + capacity: *mut c_uint, + len: *mut c_uint, + value: $value_ty, + ) -> bool { + let mut tmp = [0 as c_char; $tmp_size]; + concat_unsigned_base10_value(buffer, capacity, len, value as u64, &mut tmp) + } + }; +} + +macro_rules! define_concat_base10_signed { + ($fn_name:ident, $value_ty:ty, $tmp_size:expr) => { + #[unsafe(no_mangle)] + pub extern "C" fn $fn_name( + buffer: *mut *mut c_char, + capacity: *mut c_uint, + len: *mut c_uint, + value: $value_ty, + ) -> bool { + let mut tmp = [0 as c_char; $tmp_size]; + concat_signed_base10_value(buffer, capacity, len, value as i64, &mut tmp) + } + }; +} + +define_concat_base10_unsigned!( + arduino_string_concat_unsigned_char, + u8, + 1 + 8 * size_of::() +); +define_concat_base10_signed!(arduino_string_concat_int, c_int, 2 + 8 * size_of::()); +define_concat_base10_unsigned!( + arduino_string_concat_unsigned_int, + c_uint, + 1 + 8 * size_of::() +); +define_concat_base10_signed!( + arduino_string_concat_long, + c_long, + 2 + 8 * size_of::() +); +define_concat_base10_unsigned!( + arduino_string_concat_unsigned_long, + c_ulong, + 1 + 8 * size_of::() +); + +#[unsafe(no_mangle)] +pub extern "C" fn arduino_string_concat_float( + buffer: *mut *mut c_char, + capacity: *mut c_uint, + len: *mut c_uint, + value: f32, +) -> bool { + concat_float_value(buffer, capacity, len, value as c_double) +} + +#[unsafe(no_mangle)] +pub extern "C" fn arduino_string_concat_double( + buffer: *mut *mut c_char, + capacity: *mut c_uint, + len: *mut c_uint, + value: c_double, +) -> bool { + concat_float_value(buffer, capacity, len, value) +} + +#[inline] +fn concat_unsigned_base10_value( + buffer: *mut *mut c_char, + capacity: *mut c_uint, + len: *mut c_uint, + value: u64, + tmp: &mut [c_char], +) -> bool { + let Some(text_len) = write_unsigned_base(value, 10, tmp) else { + return false; + }; + arduino_string_concat_bytes(buffer, capacity, len, tmp.as_ptr(), text_len as c_uint) +} + +#[inline] +fn concat_signed_base10_value( + buffer: *mut *mut c_char, + capacity: *mut c_uint, + len: *mut c_uint, + value: i64, + tmp: &mut [c_char], +) -> bool { + let Some(text_len) = write_signed_base(value, 10, tmp) else { + return false; + }; + arduino_string_concat_bytes(buffer, capacity, len, tmp.as_ptr(), text_len as c_uint) +} + +#[inline] +fn concat_float_value( + buffer: *mut *mut c_char, + capacity: *mut c_uint, + len: *mut c_uint, + value: c_double, +) -> bool { + // SAFETY: tmp points to writable memory, format string is valid. + snprintf_to_concat!(buffer, capacity, len, 32, b"%.2f\0", value) +} + +#[unsafe(no_mangle)] +pub extern "C" fn arduino_string_compare_cstr( + buffer: *const c_char, + len: c_uint, + other: *const c_char, +) -> c_int { + if buffer.is_null() || other.is_null() { + if let Some(first) = first_byte(other) { + if first != 0 { + return -(first as c_int); + } + } + + if len > 0 { + if let Some(first) = first_byte(buffer) { + return first as c_int; + } + } + + return 0; + } + + // SAFETY: non-null checked above, and `other` is assumed to be a valid C string. + unsafe { + let other_len = c_strlen(other); + let lhs_len = len as usize; + let shared_len = core::cmp::min(lhs_len, other_len); + + if shared_len > 0 { + let diff = compare_bytes::(buffer, other, shared_len); + if diff != 0 { + return diff; + } + } + + if lhs_len == other_len { + return 0; + } + + if lhs_len < other_len { + let rhs = *other.add(lhs_len) as u8; + -(rhs as c_int) + } else { + let lhs = *buffer.add(other_len) as u8; + lhs as c_int + } + } +} + +#[unsafe(no_mangle)] +pub extern "C" fn arduino_string_equals_cstr( + buffer: *const c_char, + len: c_uint, + other: *const c_char, +) -> bool { + if len == 0 { + return first_byte(other).is_none_or(|b| b == 0); + } + + if other.is_null() { + return first_byte(buffer).is_some_and(|b| b == 0); + } + + arduino_string_compare_cstr(buffer, len, other) == 0 +} + +#[unsafe(no_mangle)] +pub extern "C" fn arduino_string_equals_bytes( + buffer: *const c_char, + len: c_uint, + other: *const c_char, + other_len: c_uint, +) -> bool { + if len != other_len { + return false; + } + if len == 0 { + return true; + } + if buffer.is_null() || other.is_null() { + return false; + } + + // SAFETY: pointers are non-null and `len` bytes are readable. + unsafe { bytes_equal::(buffer, other, len as usize) } +} + +#[unsafe(no_mangle)] +pub extern "C" fn arduino_string_equals_ignore_case( + buffer: *const c_char, + len: c_uint, + other: *const c_char, + other_len: c_uint, +) -> bool { + if len != other_len { + return false; + } + if len == 0 { + return true; + } + if buffer.is_null() || other.is_null() { + return false; + } + + // SAFETY: pointers are non-null and `len` bytes are readable. + unsafe { bytes_equal::(buffer, other, len as usize) } +} + +#[unsafe(no_mangle)] +pub extern "C" fn arduino_string_starts_with( + buffer: *const c_char, + len: c_uint, + prefix: *const c_char, + prefix_len: c_uint, + offset: c_uint, +) -> bool { + let Some((start, pat_len)) = checked_match_window(buffer, len, prefix, prefix_len, offset) + else { + return false; + }; + // SAFETY: ranges validated above. + unsafe { bytes_match(buffer, start, prefix, pat_len) } +} + +#[unsafe(no_mangle)] +pub extern "C" fn arduino_string_ends_with( + buffer: *const c_char, + len: c_uint, + suffix: *const c_char, + suffix_len: c_uint, +) -> bool { + let offset = len.saturating_sub(suffix_len); + let Some((start, pat_len)) = checked_match_window(buffer, len, suffix, suffix_len, offset) + else { + return false; + }; + // SAFETY: ranges validated above. + unsafe { bytes_match(buffer, start, suffix, pat_len) } +} + +#[unsafe(no_mangle)] +pub extern "C" fn arduino_string_index_of_char( + buffer: *const c_char, + len: c_uint, + ch: c_char, + from_index: c_uint, +) -> c_int { + let Some((hay_len, from)) = checked_forward_search(buffer, len, from_index) else { + return -1; + }; + + // SAFETY: buffer is non-null and [from_index, len) is readable. + index_or_minus_one(unsafe { index_of_char_from(buffer, hay_len, ch as u8, from) }) +} + +#[unsafe(no_mangle)] +pub extern "C" fn arduino_string_index_of_bytes( + buffer: *const c_char, + len: c_uint, + pat: *const c_char, + pat_len: c_uint, + from_index: c_uint, +) -> c_int { + let Some((hay_len, from)) = checked_forward_search(buffer, len, from_index) else { + return -1; + }; + if pat.is_null() { + return -1; + } + if pat_len == 0 { + return from_index as c_int; + } + + // SAFETY: buffer/pat are non-null and ranges are readable. + index_or_minus_one(unsafe { index_of_bytes_from(buffer, hay_len, pat, pat_len as usize, from) }) +} + +#[unsafe(no_mangle)] +pub extern "C" fn arduino_string_last_index_of_char( + buffer: *const c_char, + len: c_uint, + ch: c_char, + from_index: c_uint, +) -> c_int { + let Some((hay_len, from)) = checked_forward_search(buffer, len, from_index) else { + return -1; + }; + + let needle = [ch]; + // SAFETY: buffer is non-null and from_index < len. + index_or_minus_one(unsafe { + last_index_of_bytes_from(buffer, hay_len, needle.as_ptr(), needle.len(), from) + }) +} + +#[unsafe(no_mangle)] +pub extern "C" fn arduino_string_last_index_of_bytes( + buffer: *const c_char, + len: c_uint, + pat: *const c_char, + pat_len: c_uint, + from_index: c_uint, +) -> c_int { + if buffer.is_null() || pat.is_null() { + return -1; + } + + // SAFETY: buffer/pat are non-null and caller provides readable ranges. + index_or_minus_one(unsafe { + last_index_of_bytes_from( + buffer, + len as usize, + pat, + pat_len as usize, + from_index as usize, + ) + }) +} + +#[unsafe(no_mangle)] +pub extern "C" fn arduino_string_replace_char( + buffer: *mut c_char, + len: c_uint, + find_ch: c_char, + replace_ch: c_char, +) { + if buffer.is_null() { + return; + } + + let find_b = find_ch as u8; + let replace_b = replace_ch as u8; + // SAFETY: caller owns writable storage of at least `len` bytes. + unsafe { + let bytes = core::slice::from_raw_parts_mut(buffer.cast::(), len as usize); + for b in bytes.iter_mut() { + if *b == find_b { + *b = replace_b; + } + } + } +} + +#[unsafe(no_mangle)] +pub extern "C" fn arduino_string_replace_bytes( + buffer: *mut *mut c_char, + capacity: *mut c_uint, + len: *mut c_uint, + find: *const c_char, + find_len: c_uint, + replace: *const c_char, + replace_len: c_uint, +) -> bool { + let Some((buffer, capacity, len)) = state_mut(buffer, capacity, len) else { + return false; + }; + + let flow = validate_replace_bytes(*buffer, *len, find, find_len, replace, replace_len); + if let ReplaceBytesFlow::Return(ok) = &flow { + return *ok; + } + + execute_replace_bytes(buffer, capacity, len, find, replace, &flow) +} + +#[unsafe(no_mangle)] +pub extern "C" fn arduino_string_remove( + buffer: *mut c_char, + len: c_uint, + index: c_uint, + count: c_uint, +) -> c_uint { + if buffer.is_null() || count == 0 { + return len; + } + + let current_len = len; + if index >= current_len { + return current_len; + } + + let remaining = current_len - index; + let remove_count = core::cmp::min(count, remaining); + + let dst = index as usize; + let src = (index + remove_count) as usize; + let tail = (current_len - index - remove_count) as usize; + let new_len = current_len - remove_count; + + // SAFETY: `buffer` is non-null. Copy ranges are within the same allocation and may overlap. + // Writing the terminating NUL at `new_len` is within bounds. + unsafe { + ptr::copy(buffer.add(src), buffer.add(dst), tail); + *buffer.add(new_len as usize) = 0; + } + + new_len +} + +#[unsafe(no_mangle)] +pub extern "C" fn arduino_string_reverse(buffer: *mut c_char, len: c_uint) { + if buffer.is_null() || len <= 1 { + return; + } + + // SAFETY: caller owns writable string storage with at least `len` bytes. + let bytes = unsafe { core::slice::from_raw_parts_mut(buffer.cast::(), len as usize) }; + bytes.reverse(); +} + +#[unsafe(no_mangle)] +pub extern "C" fn arduino_string_get_bytes( + buffer: *const c_char, + len: c_uint, + out: *mut u8, + out_size: c_uint, + index: c_uint, +) { + if out.is_null() || out_size == 0 { + return; + } + + // SAFETY: `out` is writable for `out_size` bytes. If source copy is performed, source range + // is validated by `buffer`/`index`/`len` checks below. + unsafe { + let out_slice = core::slice::from_raw_parts_mut(out, out_size as usize); + if buffer.is_null() || index >= len { + out_slice[0] = 0; + return; + } + + let mut copy_len = (out_size - 1) as usize; + let max_len = (len - index) as usize; + if copy_len > max_len { + copy_len = max_len; + } + + if copy_len > 0 { + let src = + core::slice::from_raw_parts(buffer.add(index as usize).cast::(), copy_len); + out_slice[..copy_len].copy_from_slice(src); + } + out_slice[copy_len] = 0; + } +} diff --git a/cores/arduino/CMakeLists.txt b/cores/arduino/CMakeLists.txt index 36e337b90..4d81791c2 100644 --- a/cores/arduino/CMakeLists.txt +++ b/cores/arduino/CMakeLists.txt @@ -16,7 +16,7 @@ zephyr_sources(zephyrCommon.cpp) if(CONFIG_USE_ARDUINO_API_RUST_IMPLEMENTATION) zephyr_sources(zephyrPrint.cpp) - zephyr_sources(apiCommon.cpp) + zephyr_sources(zephyrString.cpp) endif() if(DEFINED CONFIG_ARDUINO_ENTRY) @@ -24,4 +24,3 @@ zephyr_sources(main.cpp) endif() endif() - diff --git a/cores/arduino/apiCommon.cpp b/cores/arduino/apiCommon.cpp deleted file mode 100644 index cfb6d919f..000000000 --- a/cores/arduino/apiCommon.cpp +++ /dev/null @@ -1,26 +0,0 @@ -/* - * Copyright (c) 2025 TOKITA Hiroshi - * - * SPDX-License-Identifier: Apache-2.0 - */ - -#include -#include "zephyrInternal.h" - -extern "C" { - int32_t map_i32(int32_t x, int32_t in_min, int32_t in_max, int32_t out_min, int32_t out_max); - uint16_t makeWord_w(uint16_t w); - uint16_t makeWord_hl(byte h, byte l); -} - -long map(long x, long in_min, long in_max, long out_min, long out_max) -{ - return map_i32(x, in_min, in_max, out_min, out_max); -} - -uint16_t makeWord(uint16_t w) { - return makeWord_w(w); -} -uint16_t makeWord(byte h, byte l) { - return makeWord_hl(h, l); -} diff --git a/cores/arduino/zephyrPrint.cpp b/cores/arduino/zephyrPrint.cpp index 2e07c2329..bf250532d 100644 --- a/cores/arduino/zephyrPrint.cpp +++ b/cores/arduino/zephyrPrint.cpp @@ -87,16 +87,3 @@ size_t print_number_base_pow2(void *ctx, unsigned long long ull, unsigned bits) } // namespace zephyr } // namespace arduino - -/* - * This is the default implementation. - * It will be overridden by subclassese. - */ -size_t arduino::Print::write(const uint8_t *buffer, size_t size) -{ - size_t i; - for (i=0; i - * - * SPDX-License-Identifier: Apache-2.0 - */ - -#pragma once - -#include - -#include -#include - -namespace arduino -{ -namespace zephyr -{ - -int cbprintf_callback(int c, void *ctx); -size_t wrap_cbprintf(void *ctx, const char *format, ...); -size_t print_number_base_any(void *ctx, unsigned long long ull, int base); -size_t print_number_base_pow2(void *ctx, unsigned long long ull, unsigned bits); - -template size_t print_number(void *ctx, Number n, const int base, const char *decfmt) -{ - if (base == 0) { - return reinterpret_cast(ctx)->write((char)n); - } else if (base == 2) { - return arduino::zephyr::print_number_base_pow2(ctx, n, 1); - } else if (base == 4) { - return arduino::zephyr::print_number_base_pow2(ctx, n, 2); - } else if (base == 8) { - return arduino::zephyr::print_number_base_pow2(ctx, n, 3); - } else if (base == 10) { - return arduino::zephyr::wrap_cbprintf(ctx, decfmt, n); - } else if (base == 16) { - return arduino::zephyr::print_number_base_pow2(ctx, n, 4); - } else if (base == 32) { - return arduino::zephyr::print_number_base_pow2(ctx, n, 5); - } else { - return arduino::zephyr::print_number_base_any(ctx, n, base); - } -} - -} // namespace zephyr - -} // namespace arduino - -inline size_t arduino::Print::print(const __FlashStringHelper *fsh) -{ - return write(reinterpret_cast(fsh)); -} - -inline size_t arduino::Print::print(const String &s) -{ - return write(s.c_str(), s.length()); -} - -inline size_t arduino::Print::print(const char str[]) -{ - return write(str); -} - -inline size_t arduino::Print::print(char c) -{ - return write(c); -} - -inline size_t arduino::Print::print(unsigned char n, int base) -{ - return arduino::zephyr::print_number(this, n, base, "%hhu"); -} - -inline size_t arduino::Print::print(int n, int base) -{ - return arduino::zephyr::print_number(this, n, base, "%d"); -} - -inline size_t arduino::Print::print(unsigned int n, int base) -{ - return arduino::zephyr::print_number(this, n, base, "%u"); -} - -inline size_t arduino::Print::print(long n, int base) -{ - return arduino::zephyr::print_number(this, n, base, "%ld"); -} - -inline size_t arduino::Print::print(unsigned long n, int base) -{ - return arduino::zephyr::print_number(this, n, base, "%lu"); -} - -inline size_t arduino::Print::print(long long n, int base) -{ - return arduino::zephyr::print_number(this, n, base, "%lld"); -} - -inline size_t arduino::Print::print(unsigned long long n, int base) -{ - return arduino::zephyr::print_number(this, n, base, "%llu"); -} - -inline size_t arduino::Print::print(double n, int perception) -{ - if (perception < 10) { - const char ch_perception = static_cast('0' + perception); - const char format[] = {'%', '.', ch_perception, 'f', '\0'}; - return arduino::zephyr::wrap_cbprintf(this, format, n); - } else { - const char ch_perception = static_cast('0' + (perception % 10)); - const char format[] = {'%', '.', '1', ch_perception, 'f', '\0'}; - return arduino::zephyr::wrap_cbprintf(this, format, n); - } -} - -inline size_t arduino::Print::print(const Printable &printable) -{ - return printable.printTo(*this); -} - -inline size_t arduino::Print::println(const __FlashStringHelper *fsh) -{ - return print(fsh) + println(); -} - -inline size_t arduino::Print::println(const String &s) -{ - return print(s) + println(); -} - -inline size_t arduino::Print::println(const char str[]) -{ - return print(str) + println(); -} - -inline size_t arduino::Print::println(char c) -{ - return print(c) + println(); -} - -inline size_t arduino::Print::println(unsigned char uc, int base) -{ - return print(uc, base) + println(); -} - -inline size_t arduino::Print::println(int i, int base) -{ - return print(i, base) + println(); -} - -inline size_t arduino::Print::println(unsigned int ui, int base) -{ - return print(ui, base) + println(); -} - -inline size_t arduino::Print::println(long l, int base) -{ - return print(l, base) + println(); -} - -inline size_t arduino::Print::println(unsigned long ul, int base) -{ - return print(ul, base) + println(); -} - -inline size_t arduino::Print::println(long long ll, int base) -{ - return print(ll, base) + println(); -} - -inline size_t arduino::Print::println(unsigned long long ull, int base) -{ - return print(ull, base) + println(); -} - -inline size_t arduino::Print::println(double d, int perception) -{ - return print(d, perception) + println(); -} - -inline size_t arduino::Print::println(const Printable &printable) -{ - return print(printable) + println(); -} - -inline size_t arduino::Print::println(void) -{ - return write("\r\n", 2); -} diff --git a/cores/arduino/zephyrSerial.cpp b/cores/arduino/zephyrSerial.cpp index b3047ac1c..9afac9de6 100644 --- a/cores/arduino/zephyrSerial.cpp +++ b/cores/arduino/zephyrSerial.cpp @@ -10,6 +10,8 @@ #include #include +using namespace arduino; + namespace { diff --git a/cores/arduino/zephyrString.cpp b/cores/arduino/zephyrString.cpp new file mode 100644 index 000000000..0f4b09b13 --- /dev/null +++ b/cores/arduino/zephyrString.cpp @@ -0,0 +1,8 @@ +#include + +namespace arduino { + +size_t const String::FLT_MAX_DECIMAL_PLACES; +size_t const String::DBL_MAX_DECIMAL_PLACES; + +} // namespace arduino diff --git a/documentation/idl_codegen.md b/documentation/idl_codegen.md new file mode 100644 index 000000000..573525898 --- /dev/null +++ b/documentation/idl_codegen.md @@ -0,0 +1,161 @@ +# Arduino IDL Codegen Manual + +This document explains how to use `protoc-gen-arduinoif` with the Arduino IDL `.proto` files in `idl/proto`. + +## Goal + +Generate C++ headers from proto service definitions with explicit control over: + +1. `Ifc` class (pure virtual, virtual-source methods only) +2. `Api` class (delegate wrapper) +3. `Service` class (pure virtual service contract) +4. `ServiceImpl` class (service implementation delegating to `Api`) + +The generator supports per-method and per-service options in `arduino_opts.proto`. + +## Files + +1. Generator: `tools/protoc-gen-arduinoif` +2. Options: `idl/proto/arduino_opts.proto` +3. IDL services: `idl/proto/*.proto` + +## Build Integration + +`idl/CMakeLists.txt` already invokes the plugin and emits headers into the build tree. + +Typical generated header names (per service): + +1. `*_ifc.hpp` +2. `*_api.hpp` +3. `*_service.hpp` +4. `*_service_impl.hpp` + +## Method Options + +Defined in `idl/proto/arduino_opts.proto` as `google.protobuf.MethodOptions` extensions: + +1. `cpp_decl` +2. `cpp_name` +3. `cpp_return` +4. `source_virtual` +5. `emit_api` +6. `emit_service` +7. `method_visibility` + +### Meaning + +1. `cpp_decl` + Full C++ declaration string, for example `size_t write(const uint8_t *buffer, size_t size)`. +2. `source_virtual` + Marks whether this method is considered virtual in the source Arduino class model. +3. `emit_api` + Whether this method is emitted to generated `Api`. +4. `emit_service` + Whether this method is emitted to generated `Service`. +5. `method_visibility` + Access specifier for generated methods. Supported values: `public`, `protected`, `private` (default: `public`). + +## Service Options + +Defined as `google.protobuf.ServiceOptions` extensions: + +1. `generate_ifc_class` +2. `generate_api_class` +3. `generate_service_class` +4. `generate_service_impl_class` +5. `ifc_class_name` +6. `api_class_name` +7. `service_class_name` +8. `service_impl_class_name` +9. `api_member_name` +10. `base_services` + +### Meaning + +1. `generate_ifc_class` + Emit `Ifc` (or overridden class name). +2. `generate_api_class` + Emit `Api` delegate wrapper. +3. `generate_service_class` + Emit `Service` pure virtual class. +4. `generate_service_impl_class` + Emit `ServiceImpl` delegating to `Api`. +5. `*_class_name` + Override generated class names. +6. `api_member_name` + Override delegate member name in `ServiceImpl` (default: `api_`). +7. `base_services` + Parent services to inherit from. You can specify a same-package short name (for example `Print`) or a fully-qualified name (for example `arduino.idl.Print` or `.arduino.idl.Print`). Generated `Service` class declarations inherit all ancestor `Ifc` classes (direct and indirect) in lineage order. + +## Recommended Mapping + +Use this mapping for mixed virtual/non-virtual Arduino APIs: + +1. `Ifc` + Include only methods with `source_virtual = true`. +2. `Api` + Wrapper for `Ifc` operations. +3. `Service` + Contract surface for service exposure. You can include additional methods via `emit_service = true`. +4. `ServiceImpl` + Delegates service calls into an `Api` object. + +## Validation Rules (Fail Fast) + +The generator intentionally fails when configuration is inconsistent: + +1. `generate_api_class = true` requires `generate_ifc_class = true` +2. `generate_service_impl_class = true` requires `generate_service_class = true` +3. `generate_api_class = true` rejects methods with `emit_api = true` and `source_virtual = false` +4. `generate_service_impl_class = true` with `generate_api_class = true` requires all `Service` methods to be callable on generated `Api` +5. Cyclic `base_services` references are rejected +6. If `base_services` is set and `generate_service_class = true`, each ancestor service must also set `generate_ifc_class = true` + +## Example + +```proto +syntax = "proto3"; +package arduino.idl; + +import "arduino_opts.proto"; +import "google/protobuf/empty.proto"; + +service Demo { + option (arduino.generate_ifc_class) = true; + option (arduino.generate_api_class) = true; + option (arduino.generate_service_class) = true; + option (arduino.generate_service_impl_class) = true; + + option (arduino.ifc_class_name) = "DemoIfcCustom"; + option (arduino.api_class_name) = "DemoApiCustom"; + option (arduino.service_class_name) = "DemoServiceCustom"; + option (arduino.service_impl_class_name) = "DemoServiceImplCustom"; + option (arduino.api_member_name) = "delegate_"; + option (arduino.base_services) = "Print"; + + rpc Read(google.protobuf.Empty) returns (google.protobuf.Empty) { + option (arduino.cpp_decl) = "int read()"; + option (arduino.source_virtual) = true; + option (arduino.emit_api) = true; + option (arduino.emit_service) = true; + option (arduino.method_visibility) = "public"; + } +} +``` + +## Command Line Example + +```sh +protoc \ + --plugin=protoc-gen-arduinoif=tools/protoc-gen-arduinoif \ + --arduinoif_out=/tmp/arduinoif-gen \ + --proto_path=idl/proto \ + --proto_path=idl/proto/google/protobuf \ + idl/proto/print.proto +``` + +## Notes + +1. This generator produces headers only. +2. Runtime transport (for example nanopb framing, dispatch loop, registry) is a separate layer. +3. Keep API compatibility decisions in proto options so codegen output remains deterministic. diff --git a/rust/src/common.rs b/rust/src/common.rs deleted file mode 100644 index 21fde6541..000000000 --- a/rust/src/common.rs +++ /dev/null @@ -1,22 +0,0 @@ -// Copyright (c) 2025 TOKITA Hiroshi -// SPDX-License-Identifier: Apache-2.0 - -#[unsafe(no_mangle)] -pub extern "C" fn map_i32( - x: i32, in_min: i32, in_max: i32, out_min: i32, out_max: i32 -) -> i32 { - let num = x.wrapping_sub(in_min).wrapping_mul(out_max.wrapping_sub(out_min)); - let den = in_max.wrapping_sub(in_min); - // Note: To keep compatibility, the panic when den=0 is left as is. - num / den.wrapping_add(out_min) -} - -#[unsafe(no_mangle)] -pub extern "C" fn makeWord_w(w: u16) -> u16 { - w -} - -#[unsafe(no_mangle)] -pub extern "C" fn makeWord_hl(h: u8, l: u8) -> u16 { - ((h as u16) << 8) | (l as u16) -} diff --git a/samples/blinky_arduino/prj.conf b/samples/blinky_arduino/prj.conf index 290d61a3c..509972c3a 100644 --- a/samples/blinky_arduino/prj.conf +++ b/samples/blinky_arduino/prj.conf @@ -1,2 +1,3 @@ CONFIG_GPIO=y CONFIG_ARDUINO_API=y +CONFIG_USE_ARDUINO_API_RUST_IMPLEMENTATION=y diff --git a/samples/hello_arduino/prj.conf b/samples/hello_arduino/prj.conf index f93fa3218..d56aaa2ce 100644 --- a/samples/hello_arduino/prj.conf +++ b/samples/hello_arduino/prj.conf @@ -1 +1,2 @@ CONFIG_ARDUINO_API=y +CONFIG_USE_ARDUINO_API_RUST_IMPLEMENTATION=y diff --git a/tools/protoc-gen-arduinoif b/tools/protoc-gen-arduinoif new file mode 100755 index 000000000..9585d7094 --- /dev/null +++ b/tools/protoc-gen-arduinoif @@ -0,0 +1,16 @@ +#!/usr/bin/env python3 +# SPDX-License-Identifier: Apache-2.0 + +from __future__ import annotations + +import sys +from pathlib import Path + +SCRIPT_DIR = Path(__file__).resolve().parent +sys.path.insert(0, str(SCRIPT_DIR)) + +from protoc_gen_arduinoif.core import main + + +if __name__ == "__main__": + raise SystemExit(main()) diff --git a/tools/protoc_gen_arduinoif/__init__.py b/tools/protoc_gen_arduinoif/__init__.py new file mode 100644 index 000000000..988131360 --- /dev/null +++ b/tools/protoc_gen_arduinoif/__init__.py @@ -0,0 +1 @@ +# SPDX-License-Identifier: Apache-2.0 diff --git a/tools/protoc_gen_arduinoif/constants.py b/tools/protoc_gen_arduinoif/constants.py new file mode 100644 index 000000000..8a5121f2e --- /dev/null +++ b/tools/protoc_gen_arduinoif/constants.py @@ -0,0 +1,29 @@ +# SPDX-License-Identifier: Apache-2.0 + +from __future__ import annotations + +from typing import Dict, Tuple + +FIELD_CPP_TYPE_TAG = 50001 +FIELD_CPP_NAME_TAG = 50002 +METHOD_CPP_NAME_TAG = 50101 +METHOD_CPP_RETURN_TAG = 50102 +METHOD_CPP_DECL_TAG = 50103 +METHOD_SOURCE_VIRTUAL_TAG = 50111 +METHOD_EMIT_API_TAG = 50112 +METHOD_EMIT_SERVICE_TAG = 50113 +METHOD_VISIBILITY_TAG = 50114 +SERVICE_GENERATE_API_CLASS_TAG = 50201 +SERVICE_GENERATE_SERVICE_CLASS_TAG = 50202 +SERVICE_GENERATE_SERVICE_IMPL_CLASS_TAG = 50203 +SERVICE_IFC_CLASS_NAME_TAG = 50211 +SERVICE_API_CLASS_NAME_TAG = 50212 +SERVICE_SERVICE_CLASS_NAME_TAG = 50213 +SERVICE_SERVICE_IMPL_CLASS_NAME_TAG = 50214 +SERVICE_API_MEMBER_NAME_TAG = 50215 +SERVICE_BASE_SERVICES_TAG = 50216 +SERVICE_IFC_HEADER_NAME_TAG = 50217 +SERVICE_EXTRA_INCLUDES_TAG = 50218 + +ServiceDescriptor = Tuple[object, str] +ServiceIndex = Dict[str, ServiceDescriptor] diff --git a/tools/protoc_gen_arduinoif/core.py b/tools/protoc_gen_arduinoif/core.py new file mode 100755 index 000000000..b5ade85ad --- /dev/null +++ b/tools/protoc_gen_arduinoif/core.py @@ -0,0 +1,69 @@ +# SPDX-License-Identifier: Apache-2.0 + +from __future__ import annotations + +import sys +from pathlib import PurePosixPath + +from google.protobuf.compiler import plugin_pb2 + +from .descriptors import ( + collect_message_types, + collect_service_descriptors, + full_service_name, + types_header_name_for_proto, +) +from .header_render import render_enum_header +from .service_codegen import generate_service_headers + + +def main() -> int: + request = plugin_pb2.CodeGeneratorRequest() + request.ParseFromString(sys.stdin.buffer.read()) + + response = plugin_pb2.CodeGeneratorResponse() + message_map = collect_message_types(request) + service_index = collect_service_descriptors(request) + lineage_cache: dict[str, list[str]] = {} + requested_files = set(request.file_to_generate) + requested_basenames = {PurePosixPath(name).name for name in requested_files} + + try: + for proto_file in request.proto_file: + proto_basename = PurePosixPath(proto_file.name).name + if ( + proto_file.name not in requested_files + and proto_basename not in requested_basenames + ): + continue + + if proto_file.enum_type and not proto_file.service: + output = response.file.add() + output.name = types_header_name_for_proto(proto_file.name) + output.content = render_enum_header(list(proto_file.enum_type)) + + for service in proto_file.service: + service_full_name = full_service_name(proto_file.package, service.name) + generated_headers = generate_service_headers( + service, + service_full_name, + service_index, + lineage_cache, + message_map, + proto_file.package, + list(proto_file.enum_type), + ) + for name, content in generated_headers.items(): + output = response.file.add() + output.name = name + output.content = content + except Exception as error: + response.error = str(error) + + if hasattr(plugin_pb2.CodeGeneratorResponse, "FEATURE_PROTO3_OPTIONAL"): + response.supported_features = ( + plugin_pb2.CodeGeneratorResponse.FEATURE_PROTO3_OPTIONAL + ) + + sys.stdout.buffer.write(response.SerializeToString()) + return 0 diff --git a/tools/protoc_gen_arduinoif/descriptors.py b/tools/protoc_gen_arduinoif/descriptors.py new file mode 100644 index 000000000..2f70eee66 --- /dev/null +++ b/tools/protoc_gen_arduinoif/descriptors.py @@ -0,0 +1,198 @@ +# SPDX-License-Identifier: Apache-2.0 + +from __future__ import annotations + +import re +from pathlib import PurePosixPath +from typing import Dict, List, Optional + +from google.protobuf.compiler import plugin_pb2 +from google.protobuf.descriptor_pb2 import DescriptorProto + +from .constants import ( + SERVICE_BASE_SERVICES_TAG, + SERVICE_EXTRA_INCLUDES_TAG, + SERVICE_IFC_CLASS_NAME_TAG, + SERVICE_IFC_HEADER_NAME_TAG, + SERVICE_SERVICE_CLASS_NAME_TAG, + ServiceIndex, +) +from .wire_options import get_string_list_option, get_string_option + + +def snake_case(name: str) -> str: + first_pass = re.sub(r"(.)([A-Z][a-z]+)", r"\1_\2", name) + second_pass = re.sub(r"([a-z0-9])([A-Z])", r"\1_\2", first_pass) + return second_pass.lower() + + +def collect_message_types( + request: plugin_pb2.CodeGeneratorRequest, +) -> Dict[str, DescriptorProto]: + messages: Dict[str, DescriptorProto] = {} + + def add_message( + package_prefix: str, parent_name: str, message: DescriptorProto + ) -> None: + if parent_name: + full_name = f"{package_prefix}.{parent_name}.{message.name}" + else: + full_name = f"{package_prefix}.{message.name}" + messages[full_name] = message + + child_parent = full_name[len(package_prefix) + 1 :] + for nested in message.nested_type: + add_message(package_prefix, child_parent, nested) + + for proto_file in request.proto_file: + prefix = f".{proto_file.package}" if proto_file.package else "" + for message in proto_file.message_type: + add_message(prefix, "", message) + + return messages + + +def full_service_name(package_name: str, service_name: str) -> str: + if package_name: + return f".{package_name}.{service_name}" + return f".{service_name}" + + +def collect_service_descriptors( + request: plugin_pb2.CodeGeneratorRequest, +) -> ServiceIndex: + services: ServiceIndex = {} + for proto_file in request.proto_file: + for service in proto_file.service: + full_name = full_service_name(proto_file.package, service.name) + services[full_name] = (service, proto_file.package) + return services + + +def dedupe_ordered(items: List[str]) -> List[str]: + seen = set() + result: List[str] = [] + for item in items: + if item in seen: + continue + seen.add(item) + result.append(item) + return result + + +def _resolve_service_reference( + reference: str, + current_package: str, + service_index: ServiceIndex, +) -> str: + if reference.startswith("."): + candidate = reference + elif "." in reference: + candidate = f".{reference}" + elif current_package: + candidate = f".{current_package}.{reference}" + else: + candidate = f".{reference}" + + if candidate in service_index: + return candidate + + raise ValueError( + f"service '{candidate}' not found for base_services entry '{reference}'" + ) + + +def collect_service_lineage( + service_full_name: str, + service_index: ServiceIndex, + lineage_cache: Dict[str, List[str]], + visiting: Optional[List[str]] = None, +) -> List[str]: + if service_full_name in lineage_cache: + return lineage_cache[service_full_name] + if visiting is None: + visiting = [] + if service_full_name in visiting: + cycle = " -> ".join([*visiting, service_full_name]) + raise ValueError(f"cyclic service inheritance detected: {cycle}") + + entry = service_index.get(service_full_name) + if entry is None: + raise ValueError(f"service '{service_full_name}' not found") + service, package_name = entry + base_refs = get_string_list_option(service.options, SERVICE_BASE_SERVICES_TAG) + + inherited: List[str] = [] + for base_ref in base_refs: + base_full_name = _resolve_service_reference( + base_ref, package_name, service_index + ) + inherited.extend( + collect_service_lineage( + base_full_name, + service_index, + lineage_cache, + [*visiting, service_full_name], + ) + ) + inherited.append(service_full_name) + lineage = dedupe_ordered(inherited) + lineage_cache[service_full_name] = lineage + return lineage + + +def collect_lineage_includes( + lineage: List[str], + service_index: ServiceIndex, +) -> List[str]: + includes: List[str] = [] + for service_full_name in lineage: + service, _ = service_index[service_full_name] + includes.extend( + get_string_list_option(service.options, SERVICE_EXTRA_INCLUDES_TAG) + ) + return dedupe_ordered(includes) + + +def service_class_name(service) -> str: + return ( + get_string_option(service.options, SERVICE_SERVICE_CLASS_NAME_TAG).strip() + or f"{service.name}Service" + ) + + +def ifc_class_name(service) -> str: + return ( + get_string_option(service.options, SERVICE_IFC_CLASS_NAME_TAG).strip() + or f"{service.name}Interface" + ) + + +def class_decl(class_name: str, base_classes: List[str]) -> str: + if not base_classes: + return f"class {class_name} {{" + bases = ", ".join(f"public {base_class}" for base_class in base_classes) + return f"class {class_name} : {bases} {{" + + +def service_header_name(service) -> str: + header_name = get_string_option(service.options, SERVICE_IFC_HEADER_NAME_TAG).strip() + if header_name: + return header_name + return f"{snake_case(service.name)}_interface.hpp" + + +def service_header_stem(service) -> str: + ifc_header = service_header_name(service) + suffix = "_interface.hpp" + if ifc_header.endswith(suffix): + return ifc_header[: -len(suffix)] + return PurePosixPath(ifc_header).stem + + +def cpp_namespace_from_package(package_name: str) -> str: + return "::".join(part for part in package_name.split(".") if part) + + +def types_header_name_for_proto(proto_file_name: str) -> str: + return f"{PurePosixPath(proto_file_name).stem}_types.h" diff --git a/tools/protoc_gen_arduinoif/header_render.py b/tools/protoc_gen_arduinoif/header_render.py new file mode 100644 index 000000000..839d0fed2 --- /dev/null +++ b/tools/protoc_gen_arduinoif/header_render.py @@ -0,0 +1,221 @@ +# SPDX-License-Identifier: Apache-2.0 + +from __future__ import annotations + +from typing import List + +from google.protobuf.descriptor_pb2 import EnumDescriptorProto + +from .descriptors import class_decl +from .model import MethodSpec + + +def render_enum_header(enums: List[EnumDescriptorProto]) -> str: + lines: List[str] = [] + lines.append("// Generated by protoc-gen-arduinoif. DO NOT EDIT.") + lines.append("") + lines.append("#pragma once") + lines.append("") + lines.append("#include ") + lines.append("") + + for enum_desc in enums: + lines.append(f"typedef enum {enum_desc.name} {{") + for value in enum_desc.value: + lines.append(f" {value.name} = {value.number},") + lines.append(f"}} {enum_desc.name};") + lines.append("") + + while lines and lines[-1] == "": + lines.pop() + lines.append("") + return "\n".join(lines) + + +def _append_enum_decls(body_lines: List[str], enums: List[EnumDescriptorProto]) -> None: + if not enums: + return + for enum_desc in enums: + body_lines.append(f"typedef enum {enum_desc.name} {{") + for value in enum_desc.value: + body_lines.append(f" {value.name} = {value.number},") + body_lines.append(f"}} {enum_desc.name};") + body_lines.append("") + + +def _render_header( + includes: List[str], + namespace_name: str, + body_lines: List[str], +) -> str: + lines: List[str] = [] + lines.append("// Generated by protoc-gen-arduinoif. DO NOT EDIT.") + lines.append("") + lines.append("#pragma once") + lines.append("") + lines.append("#include ") + lines.append("#include ") + for include in includes: + lines.append(f"#include <{include}>") + lines.append("") + if namespace_name: + lines.append(f"namespace {namespace_name} {{") + lines.append("") + lines.extend(body_lines) + if namespace_name: + lines.append("") + lines.append(f"}} // namespace {namespace_name}") + lines.append("") + + while lines and lines[-1] == "": + lines.pop() + lines.append("") + return "\n".join(lines) + + +def _append_grouped_methods( + body_lines: List[str], + methods: List[MethodSpec], + emit_method, +) -> None: + ordered_visibility = ("public", "protected", "private") + first_section = True + for visibility in ordered_visibility: + scoped = [spec for spec in methods if spec.visibility == visibility] + if not scoped: + continue + if not first_section: + body_lines.append("") + body_lines.append(f"{visibility}:") + for spec in scoped: + emit_method(spec) + first_section = False + + +def _append_forwarding_method( + body_lines: List[str], + spec: MethodSpec, + target_expr: str, + *, + mark_override: bool = False, +) -> None: + signature = f" {spec.decl}" + if spec.suffix: + signature += f" {spec.suffix}" + if mark_override: + signature += " override" + body_lines.append(f"{signature} {{") + + call_args = ", ".join(spec.arg_names) + call_expr = f"{target_expr}.{spec.call_name}({call_args})" + if spec.returns_void: + body_lines.append(f" {call_expr};") + else: + body_lines.append(f" return {call_expr};") + body_lines.append(" }") + body_lines.append("") + + +def render_ifc_header_content( + include_list: List[str], + namespace_name: str, + proto_enums: List[EnumDescriptorProto], + ifc_class_name: str, + ifc_methods: List[MethodSpec], +) -> str: + body_lines: List[str] = [] + _append_enum_decls(body_lines, proto_enums) + body_lines.append(f"class {ifc_class_name} {{") + body_lines.append("public:") + body_lines.append(f" virtual ~{ifc_class_name}() = default;") + body_lines.append("") + _append_grouped_methods( + body_lines, + ifc_methods, + lambda spec: body_lines.append(f" virtual {spec.decl} = 0;"), + ) + body_lines.append("};") + return _render_header(include_list, namespace_name, body_lines) + + +def render_api_header_content( + api_includes: List[str], + namespace_name: str, + api_class_name: str, + ifc_class_name: str, + api_methods: List[MethodSpec], +) -> str: + body_lines: List[str] = [] + body_lines.append(f"class {api_class_name} : public {ifc_class_name} {{") + body_lines.append("public:") + body_lines.append( + f" explicit {api_class_name}({ifc_class_name}& impl) : impl_(impl) {{}}" + ) + body_lines.append("") + + _append_grouped_methods( + body_lines, + api_methods, + lambda spec: _append_forwarding_method(body_lines, spec, "impl_"), + ) + body_lines.append("private:") + body_lines.append(f" {ifc_class_name}& impl_;") + body_lines.append("};") + return _render_header(api_includes, namespace_name, body_lines) + + +def render_service_header_content( + service_includes: List[str], + namespace_name: str, + service_class_name: str, + service_base_ifc_class_names: List[str], + service_methods: List[MethodSpec], +) -> str: + body_lines: List[str] = [] + body_lines.append(class_decl(service_class_name, service_base_ifc_class_names)) + body_lines.append("public:") + body_lines.append(f" virtual ~{service_class_name}() = default;") + body_lines.append("") + _append_grouped_methods( + body_lines, + service_methods, + lambda spec: body_lines.append(f" virtual {spec.decl} = 0;"), + ) + body_lines.append("};") + return _render_header(service_includes, namespace_name, body_lines) + + +def render_service_impl_header_content( + service_impl_includes: List[str], + namespace_name: str, + service_impl_class_name: str, + service_class_name: str, + api_class_name: str, + api_member_name: str, + service_impl_methods: List[MethodSpec], + generate_api_class: bool, +) -> str: + body_lines: List[str] = [] + if not generate_api_class: + body_lines.append(f"class {api_class_name};") + body_lines.append("") + + body_lines.append( + f"class {service_impl_class_name} : public {service_class_name} {{" + ) + body_lines.append("public:") + body_lines.append( + f" explicit {service_impl_class_name}({api_class_name}& api) : {api_member_name}(api) {{}}" + ) + body_lines.append("") + _append_grouped_methods( + body_lines, + service_impl_methods, + lambda spec: _append_forwarding_method( + body_lines, spec, api_member_name, mark_override=True + ), + ) + body_lines.append("private:") + body_lines.append(f" {api_class_name}& {api_member_name};") + body_lines.append("};") + return _render_header(service_impl_includes, namespace_name, body_lines) diff --git a/tools/protoc_gen_arduinoif/method_specs.py b/tools/protoc_gen_arduinoif/method_specs.py new file mode 100644 index 000000000..3b52d960d --- /dev/null +++ b/tools/protoc_gen_arduinoif/method_specs.py @@ -0,0 +1,275 @@ +# SPDX-License-Identifier: Apache-2.0 + +from __future__ import annotations + +import re +from typing import Dict, List + +from google.protobuf.descriptor_pb2 import DescriptorProto, FieldDescriptorProto + +from .constants import ( + FIELD_CPP_NAME_TAG, + FIELD_CPP_TYPE_TAG, + METHOD_CPP_DECL_TAG, + METHOD_CPP_NAME_TAG, + METHOD_CPP_RETURN_TAG, + METHOD_EMIT_API_TAG, + METHOD_EMIT_SERVICE_TAG, + METHOD_SOURCE_VIRTUAL_TAG, + METHOD_VISIBILITY_TAG, + ServiceIndex, +) +from .model import MethodSpec +from .wire_options import get_bool_option, get_string_option + + +def _field_type(field: FieldDescriptorProto) -> str: + default_types = { + FieldDescriptorProto.TYPE_BOOL: "bool", + FieldDescriptorProto.TYPE_INT32: "int32_t", + FieldDescriptorProto.TYPE_INT64: "int64_t", + FieldDescriptorProto.TYPE_UINT32: "uint32_t", + FieldDescriptorProto.TYPE_UINT64: "uint64_t", + FieldDescriptorProto.TYPE_SINT32: "int32_t", + FieldDescriptorProto.TYPE_SINT64: "int64_t", + FieldDescriptorProto.TYPE_FIXED32: "uint32_t", + FieldDescriptorProto.TYPE_FIXED64: "uint64_t", + FieldDescriptorProto.TYPE_SFIXED32: "int32_t", + FieldDescriptorProto.TYPE_SFIXED64: "int64_t", + FieldDescriptorProto.TYPE_FLOAT: "float", + FieldDescriptorProto.TYPE_DOUBLE: "double", + } + return default_types.get(field.type, "int32_t") + + +def _split_top_level_commas(text: str) -> List[str]: + items: List[str] = [] + start = 0 + paren = 0 + angle = 0 + bracket = 0 + brace = 0 + + for index, char in enumerate(text): + if char == "(": + paren += 1 + elif char == ")": + paren = max(paren - 1, 0) + elif char == "<": + angle += 1 + elif char == ">": + angle = max(angle - 1, 0) + elif char == "[": + bracket += 1 + elif char == "]": + bracket = max(bracket - 1, 0) + elif char == "{": + brace += 1 + elif char == "}": + brace = max(brace - 1, 0) + elif char == "," and paren == 0 and angle == 0 and bracket == 0 and brace == 0: + item = text[start:index].strip() + if item: + items.append(item) + start = index + 1 + + tail = text[start:].strip() + if tail: + items.append(tail) + return items + + +def _find_matching_paren(text: str, open_index: int) -> int: + depth = 0 + for index in range(open_index, len(text)): + if text[index] == "(": + depth += 1 + elif text[index] == ")": + depth -= 1 + if depth == 0: + return index + raise ValueError(f"unmatched parenthesis in declaration: {text}") + + +def _extract_param_name(param_decl: str, param_index: int) -> str: + decl = param_decl.split("=", 1)[0].strip() + if not decl or decl == "void": + return "" + + function_ptr = re.search(r"\(\s*[*&]\s*([A-Za-z_]\w*)\s*\)", decl) + if function_ptr: + return function_ptr.group(1) + + if decl.endswith("*") or decl.endswith("&"): + return f"arg{param_index}" + + trailing_name = re.search(r"([A-Za-z_]\w*)\s*(?:\[[^\]]*\])?\s*$", decl) + if trailing_name is None: + return f"arg{param_index}" + + candidate = trailing_name.group(1) + identifiers = re.findall(r"[A-Za-z_]\w*", decl) + if len(identifiers) <= 1 and " " not in decl: + return f"arg{param_index}" + if candidate in { + "void", + "const", + "volatile", + "signed", + "unsigned", + "long", + "short", + }: + return f"arg{param_index}" + return candidate + + +def _parse_cpp_decl(method_decl: str) -> MethodSpec: + signature = method_decl.strip().rstrip(";") + open_index = signature.find("(") + if open_index < 0: + raise ValueError(f"invalid cpp_decl (missing '('): {method_decl}") + + close_index = _find_matching_paren(signature, open_index) + prefix = signature[:open_index].rstrip() + suffix = signature[close_index + 1 :].strip() + + method_name = "" + returns_void = False + if prefix.startswith("operator "): + method_name = prefix + else: + name_match = re.search(r"([~A-Za-z_][A-Za-z0-9_:~]*)\s*$", prefix) + if name_match is None: + raise ValueError(f"invalid cpp_decl (method name): {method_decl}") + method_name = name_match.group(1).split("::")[-1] + return_type = prefix[: name_match.start()].strip() + if not return_type: + raise ValueError(f"invalid cpp_decl (return type): {method_decl}") + returns_void = return_type == "void" + + params_blob = signature[open_index + 1 : close_index].strip() + if not params_blob or params_blob == "void": + return MethodSpec( + signature, method_name, [], suffix, returns_void, True, True, True, "public" + ) + + raw_params = _split_top_level_commas(params_blob) + arg_names: List[str] = [] + for index, raw_param in enumerate(raw_params): + param_decl = raw_param.strip() + if not param_decl or param_decl == "void": + continue + + arg_name = _extract_param_name(param_decl, index) + if arg_name and not re.search(rf"\b{re.escape(arg_name)}\b", param_decl): + param_decl = f"{param_decl} {arg_name}" + + arg_names.append(arg_name if arg_name else f"arg{index}") + return MethodSpec( + signature, + method_name, + arg_names, + suffix, + returns_void, + True, + True, + True, + "public", + ) + + +def method_spec_from_descriptor( + method, message_map: Dict[str, DescriptorProto] +) -> MethodSpec: + source_virtual = get_bool_option(method.options, METHOD_SOURCE_VIRTUAL_TAG, True) + emit_api = get_bool_option(method.options, METHOD_EMIT_API_TAG, True) + emit_service = get_bool_option(method.options, METHOD_EMIT_SERVICE_TAG, True) + visibility = ( + get_string_option(method.options, METHOD_VISIBILITY_TAG).strip().lower() + or "public" + ) + if visibility not in {"public", "protected", "private"}: + raise ValueError( + f"{method.name}: unsupported method_visibility '{visibility}' " + "(expected: public, protected, private)" + ) + + method_decl = ( + get_string_option(method.options, METHOD_CPP_DECL_TAG).strip().rstrip(";") + ) + if method_decl: + parsed = _parse_cpp_decl(method_decl) + return MethodSpec( + parsed.decl, + parsed.call_name, + parsed.arg_names, + parsed.suffix, + parsed.returns_void, + source_virtual, + emit_api, + emit_service, + visibility, + ) + + method_name = get_string_option(method.options, METHOD_CPP_NAME_TAG).strip() + if not method_name: + method_name = method.name + + return_type = get_string_option(method.options, METHOD_CPP_RETURN_TAG).strip() + if not return_type: + output_message = message_map.get(method.output_type) + if output_message is not None and len(output_message.field) == 0: + return_type = "void" + else: + return_type = "void" + + arg_names: List[str] = [] + param_decls: List[str] = [] + input_message = message_map.get(method.input_type) + if input_message is not None: + ordered_fields = sorted(input_message.field, key=lambda field: field.number) + for field in ordered_fields: + param_type = get_string_option(field.options, FIELD_CPP_TYPE_TAG).strip() + if not param_type: + param_type = _field_type(field) + + param_name = get_string_option(field.options, FIELD_CPP_NAME_TAG).strip() + if not param_name: + param_name = field.name + + param_decls.append(f"{param_type} {param_name}") + arg_names.append(param_name) + + decl = f"{return_type} {method_name}({', '.join(param_decls)})" + return MethodSpec( + decl, + method_name, + arg_names, + "", + return_type == "void", + source_virtual, + emit_api, + emit_service, + visibility, + ) + + +def collect_lineage_methods( + lineage: List[str], + service_index: ServiceIndex, + message_map: Dict[str, DescriptorProto], +) -> List[MethodSpec]: + ordered_decls: List[str] = [] + by_decl: Dict[str, MethodSpec] = {} + + for service_full_name in lineage: + service, _ = service_index[service_full_name] + for method in service.method: + spec = method_spec_from_descriptor(method, message_map) + if spec.decl in by_decl: + ordered_decls.remove(spec.decl) + ordered_decls.append(spec.decl) + by_decl[spec.decl] = spec + + return [by_decl[decl] for decl in ordered_decls] diff --git a/tools/protoc_gen_arduinoif/model.py b/tools/protoc_gen_arduinoif/model.py new file mode 100644 index 000000000..ad5ec5bde --- /dev/null +++ b/tools/protoc_gen_arduinoif/model.py @@ -0,0 +1,17 @@ +# SPDX-License-Identifier: Apache-2.0 + +from __future__ import annotations + +from typing import List, NamedTuple + + +class MethodSpec(NamedTuple): + decl: str + call_name: str + arg_names: List[str] + suffix: str + returns_void: bool + source_virtual: bool + emit_api: bool + emit_service: bool + visibility: str diff --git a/tools/protoc_gen_arduinoif/service_codegen.py b/tools/protoc_gen_arduinoif/service_codegen.py new file mode 100644 index 000000000..f21584cf9 --- /dev/null +++ b/tools/protoc_gen_arduinoif/service_codegen.py @@ -0,0 +1,179 @@ +# SPDX-License-Identifier: Apache-2.0 + +from __future__ import annotations + +from typing import Dict, List + +from google.protobuf.descriptor_pb2 import DescriptorProto, EnumDescriptorProto + +from .constants import ( + SERVICE_API_CLASS_NAME_TAG, + SERVICE_API_MEMBER_NAME_TAG, + SERVICE_GENERATE_API_CLASS_TAG, + SERVICE_GENERATE_SERVICE_CLASS_TAG, + SERVICE_GENERATE_SERVICE_IMPL_CLASS_TAG, + SERVICE_IFC_CLASS_NAME_TAG, + SERVICE_SERVICE_IMPL_CLASS_NAME_TAG, + ServiceIndex, +) +from .descriptors import ( + collect_lineage_includes, + collect_service_lineage, + cpp_namespace_from_package, + dedupe_ordered, + ifc_class_name, + service_class_name, + service_header_name, + service_header_stem, +) +from .header_render import ( + render_api_header_content, + render_ifc_header_content, + render_service_header_content, + render_service_impl_header_content, +) +from .method_specs import collect_lineage_methods, method_spec_from_descriptor +from .wire_options import get_bool_option, get_string_option + + +def generate_service_headers( + service, + service_full_name: str, + service_index: ServiceIndex, + lineage_cache: Dict[str, List[str]], + message_map: Dict[str, DescriptorProto], + package_name: str, + proto_enums: List[EnumDescriptorProto], +) -> Dict[str, str]: + ifc_name = ( + get_string_option(service.options, SERVICE_IFC_CLASS_NAME_TAG).strip() + or f"{service.name}Interface" + ) + api_name = ( + get_string_option(service.options, SERVICE_API_CLASS_NAME_TAG).strip() + or f"{service.name}Api" + ) + service_name = service_class_name(service) + service_impl_name = ( + get_string_option(service.options, SERVICE_SERVICE_IMPL_CLASS_NAME_TAG).strip() + or f"{service.name}ServiceImpl" + ) + api_member_name = ( + get_string_option(service.options, SERVICE_API_MEMBER_NAME_TAG).strip() + or "api_" + ) + generate_api = get_bool_option(service.options, SERVICE_GENERATE_API_CLASS_TAG, False) + generate_service = get_bool_option( + service.options, SERVICE_GENERATE_SERVICE_CLASS_TAG, False + ) + generate_service_impl = get_bool_option( + service.options, SERVICE_GENERATE_SERVICE_IMPL_CLASS_TAG, False + ) + + if generate_service_impl and not generate_service: + raise ValueError( + f"{service.name}: generate_service_impl_class=true requires generate_service_class=true" + ) + + namespace_name = cpp_namespace_from_package(package_name) + own_method_specs = [ + method_spec_from_descriptor(method, message_map) for method in service.method + ] + lineage = collect_service_lineage(service_full_name, service_index, lineage_cache) + ancestor_services = lineage[:-1] + lineage_method_specs = collect_lineage_methods(lineage, service_index, message_map) + ifc_methods = [spec for spec in own_method_specs if spec.source_virtual] + api_methods = [ + spec for spec in lineage_method_specs if spec.emit_api and spec.source_virtual + ] + service_methods = [spec for spec in lineage_method_specs if spec.emit_service] + service_impl_methods = service_methods + + if generate_api: + invalid_api_methods = [ + spec.decl + for spec in lineage_method_specs + if spec.emit_api and not spec.source_virtual + ] + if invalid_api_methods: + joined = ", ".join(invalid_api_methods) + raise ValueError( + f"{service.name}: generate_api_class=true cannot emit non-virtual-source methods in API ({joined})" + ) + + if generate_service_impl and generate_api: + api_callable = {spec.call_name for spec in api_methods} + missing_api = [ + spec.decl for spec in service_methods if spec.call_name not in api_callable + ] + if missing_api: + joined = ", ".join(missing_api) + raise ValueError( + f"{service.name}: service impl delegates API, but API does not expose ({joined})" + ) + service_impl_methods = [ + spec for spec in service_methods if spec.call_name in api_callable + ] + + include_list = dedupe_ordered(collect_lineage_includes(lineage, service_index)) + service_base_ifc_class_names: List[str] = [] + service_base_ifc_header_names: List[str] = [] + for ancestor_full_name in ancestor_services: + ancestor_service, _ = service_index[ancestor_full_name] + service_base_ifc_class_names.append(ifc_class_name(ancestor_service)) + service_base_ifc_header_names.append(service_header_name(ancestor_service)) + service_base_ifc_class_names = dedupe_ordered(service_base_ifc_class_names) + service_base_ifc_header_names = dedupe_ordered(service_base_ifc_header_names) + + stem = service_header_stem(service) + ifc_header = service_header_name(service) + api_header = f"{stem}_api.hpp" + service_header = f"{stem}_service.hpp" + service_impl_header = f"{stem}_service_impl.hpp" + + outputs: Dict[str, str] = {} + outputs[ifc_header] = render_ifc_header_content( + include_list, + namespace_name, + proto_enums, + ifc_name, + ifc_methods, + ) + + if generate_api: + outputs[api_header] = render_api_header_content( + [ifc_header, *include_list], + namespace_name, + api_name, + ifc_name, + api_methods, + ) + + if generate_service: + service_includes = dedupe_ordered([*service_base_ifc_header_names, *include_list]) + if proto_enums: + service_includes = dedupe_ordered([ifc_header, *service_includes]) + outputs[service_header] = render_service_header_content( + service_includes, + namespace_name, + service_name, + service_base_ifc_class_names, + service_methods, + ) + + if generate_service_impl: + service_impl_includes = [service_header, *include_list] + if generate_api: + service_impl_includes.insert(1, api_header) + outputs[service_impl_header] = render_service_impl_header_content( + service_impl_includes, + namespace_name, + service_impl_name, + service_name, + api_name, + api_member_name, + service_impl_methods, + generate_api, + ) + + return outputs diff --git a/tools/protoc_gen_arduinoif/wire_options.py b/tools/protoc_gen_arduinoif/wire_options.py new file mode 100644 index 000000000..f107fc7a6 --- /dev/null +++ b/tools/protoc_gen_arduinoif/wire_options.py @@ -0,0 +1,114 @@ +# SPDX-License-Identifier: Apache-2.0 + +from __future__ import annotations + +from typing import Dict, List, Tuple + + +def _read_varint(data: bytes, index: int) -> Tuple[int, int]: + value = 0 + shift = 0 + while True: + if index >= len(data): + raise ValueError("unexpected end of varint") + byte = data[index] + index += 1 + value |= (byte & 0x7F) << shift + if not (byte & 0x80): + return value, index + shift += 7 + if shift > 63: + raise ValueError("varint is too large") + + +def _parse_wire_fields(data: bytes) -> Dict[int, List[Tuple[int, object]]]: + parsed: Dict[int, List[Tuple[int, object]]] = {} + index = 0 + + while index < len(data): + key, index = _read_varint(data, index) + field_number = key >> 3 + wire_type = key & 0x7 + + if wire_type == 0: + value, index = _read_varint(data, index) + elif wire_type == 1: + if index + 8 > len(data): + raise ValueError("invalid fixed64 field") + value = data[index : index + 8] + index += 8 + elif wire_type == 2: + length, index = _read_varint(data, index) + if index + length > len(data): + raise ValueError("invalid length-delimited field") + value = data[index : index + length] + index += length + elif wire_type == 5: + if index + 4 > len(data): + raise ValueError("invalid fixed32 field") + value = data[index : index + 4] + index += 4 + else: + raise ValueError(f"unsupported wire type {wire_type}") + + parsed.setdefault(field_number, []).append((wire_type, value)) + + return parsed + + +def get_string_option(options, field_number: int) -> str: + raw = options.SerializeToString() + if not raw: + return "" + + try: + fields = _parse_wire_fields(raw) + except ValueError: + return "" + + for wire_type, value in fields.get(field_number, []): + if wire_type == 2: + try: + return bytes(value).decode("utf-8") + except UnicodeDecodeError: + return "" + return "" + + +def get_bool_option(options, field_number: int, default: bool = False) -> bool: + raw = options.SerializeToString() + if not raw: + return default + + try: + fields = _parse_wire_fields(raw) + except ValueError: + return default + + for wire_type, value in fields.get(field_number, []): + if wire_type == 0: + return bool(value) + return default + + +def get_string_list_option(options, field_number: int) -> List[str]: + raw = options.SerializeToString() + if not raw: + return [] + + try: + fields = _parse_wire_fields(raw) + except ValueError: + return [] + + values: List[str] = [] + for wire_type, value in fields.get(field_number, []): + if wire_type != 2: + continue + try: + text = bytes(value).decode("utf-8").strip() + except UnicodeDecodeError: + continue + if text: + values.append(text) + return values