-
Notifications
You must be signed in to change notification settings - Fork 0
Adding Backends
A backend is a subclass of CodegenBackend that overrides ~10 methods. The base class handles all IR tree walking — you only define target-specific output.
Create scratch2c/codegen/arduino.py (or whatever your target is):
from __future__ import annotations
from .base import CodegenBackend, _escape_c
class ArduinoBackend(CodegenBackend):
def file_header(self) -> list[str]:
return [
"/* Generated by scratch2c */",
'#include "scratch_runtime.h"',
"",
]
def file_footer(self) -> list[str]:
return []
def emit_say(self, expr_code: str, is_string: bool) -> str:
if is_string:
return f'Serial.println({expr_code});'
return f'Serial.println((long){expr_code});'
def main_function_name(self) -> str:
return "setup"
def main_signature_open(self) -> str:
return "void setup() {\n Serial.begin(9600);"
def main_signature_close(self) -> str:
return ""
def exit_function_name(self) -> str:
return "loop"
def exit_signature_open(self) -> str:
return "void loop() {"
def exit_signature_close(self) -> str:
return ""
def declare_long_variable(self, c_name: str, initial: str) -> str:
return f"long {c_name} = {initial};"
def declare_string_variable(self, c_name: str, initial: str) -> str:
escaped = _escape_c(initial)
return f'char {c_name}[256] = "{escaped}";'
def wait_comment(self, duration_code: str) -> str:
return f"delay({duration_code} * 1000);"In scratch2c/codegen/__init__.py:
from .arduino import ArduinoBackend
BACKENDS: dict[str, type[CodegenBackend]] = {
"userspace": UserspaceBackend,
"kernel": KernelBackend,
"arduino": ArduinoBackend,
}In scratch2c/cli.py, add "arduino" to the choices list:
parser.add_argument(
"-b", "--backend",
default="userspace",
choices=["userspace", "kernel", "arduino"],
)Create tests/test_codegen_arduino.py. Test the properties that matter for your target — headers, output function, variable declarations:
def _generate_arduino(project_json: dict) -> str:
project = build_ir(project_json)
infer_types(project)
return ArduinoBackend().generate(project)
class TestArduinoCodegen:
def test_serial_println(self, simple_say_json):
code = _generate_arduino(simple_say_json)
assert "Serial.println" in code
def test_setup_function(self, simple_say_json):
code = _generate_arduino(simple_say_json)
assert "void setup()" in codedef emit_stop(self) -> str:
"""Default is 'return;'. Override if main returns non-void."""
return "return;"
def _needs_exit(self) -> bool:
"""Default is False. Override if target always needs an exit function."""
return FalseThe kernel backend overrides both: emit_stop() returns "return 0;" (because scratch_init returns int) and _needs_exit() returns True (kbuild requires module_exit).
The runtime header uses #ifdef __KERNEL__ to switch between userspace and kernel implementations. If your target has its own constraints (no snprintf, no strlen), you may need to add a third #ifdef branch or create a separate runtime header.