MARLA
Messages And Registers: Language with Actors. Eine kleine Programmiersprache für Mikrocontroller. Sie fühlt sich beim Schreiben an wie Python und verhält sich wie C. Der Compiler übersetzt MARLA nach lesbarem C99; das baut dann avr-gcc für den Arduino Uno oder der normale C-Compiler für einen Testlauf auf dem PC.
So sieht MARLA aus
extern fn hal_led_write(on: bool)
extern fn hal_delay_ms(ms: u16)
const BLINK_MS: u16 := 500
fn main():
let mut counter: u16 := 0
loop:
hal_led_write(true)
hal_delay_ms(BLINK_MS)
hal_led_write(false)
hal_delay_ms(BLINK_MS)
counter := counter + 1
if counter = 100:
counter := 0
Einrückung statt Klammern wie in Python, := für die Zuweisung, = für den Vergleich wie in
Pascal, feste Integer-Typen wie u16. Ein Ausschnitt aus dem C, das der Compiler daraus macht:
#define ml_BLINK_MS ((uint16_t)(500))
#line 7 "blink.mar"
static void ml_main(void) {
#line 8 "blink.mar"
uint16_t ml_counter = ((uint16_t)0);
#line 9 "blink.mar"
for (;;) {
#line 10 "blink.mar"
hal_led_write(true);
...
Die #line-Zeilen sorgen dafür, dass Fehlermeldungen von C-Compiler und Debugger auf die Zeile im
MARLA-Quelltext zeigen, nicht auf das erzeugte C.
Aktoren statt Threads
Auf einem Mikrocontroller passiert vieles gleichzeitig: Ein Timer läuft ab, ein Byte kommt über die
serielle Schnittstelle, eine Taste wird gedrückt. In C landet das in Interrupt-Routinen, die sich
mit der Hauptschleife über globale volatile-Variablen absprechen. Das ist eine klassische
Fehlerquelle.
MARLA macht daraus Nachrichten. Ein Aktor hat einen privaten Zustand und einen Briefkasten
(Mailbox) fester Größe. Der Timer schickt ihm ein Tick, er reagiert darauf:
extern fn hal_led_write(on: bool)
actor Blinker(mailbox: 4, full: DropOldest):
an: bool := false
fn receive(msg: TimerMsg):
match msg:
Tick:
self.an := not self.an
hal_led_write(self.an)
fn main():
return
Welcher Timer wie oft tickt, steht in einer kleinen Board-Datei, nicht im Programm. (Dieses
Beispiel läuft heute auf dem PC; auf dem Uno fehlt dafür noch ein Baustein, siehe
Stand & Plan.) Was passiert,
wenn der Briefkasten voll ist, muss jeder Aktor selbst festlegen: hier DropOldest, also die älteste
Nachricht überschreiben. Alle Aktoren stehen schon beim Übersetzen fest; es gibt keinen Heap und
kein dynamisches Erzeugen.
Fehler beim Übersetzen, nicht auf dem Board
Was auf dem Mikrocontroller schiefgeht, ist schwer zu finden. Deshalb prüft MARLA möglichst viel schon beim Übersetzen und sagt verständlich, was falsch ist:
typmix.mar:5:16: Fehler: Typkonflikt bei '+': 'u8' und 'u16' — keine impliziten Umwandlungen, expliziter Cast mit 'as' nötig
let c := a + b
^
nicht_mut.mar:4:5: Fehler: 'x' ist nicht veränderlich — deklariere 'let mut x'
x := 2
^
doppelgleich.mar:4:10: Fehler: '==' gibt es nicht — meintest du '='?
if x == 1:
^
Die Leitideen
- Keine versteckten Kosten. Jedes Sprachmittel hat eine offensichtliche, billige Übersetzung nach C. Kein Heap, kein Garbage Collector, keine Laufzeit-Magie.
- Fehler beim Übersetzen statt zur Laufzeit. Statische Typen, keine stillen Umwandlungen.
- Python-Gefühl, C-Semantik. Speicher liegt auf dem Stack oder global, Typen stehen beim Übersetzen fest.
- Nur portables C99. Alles Hardwarenahe geht über eine kleine HAL-Schicht pro Zielsystem.
- Lesbares C. Wer wissen will, was passiert, schaut einfach in die erzeugte Datei.
Weiter
- Einstieg: Installieren, erstes Programm, auf dem PC und auf dem Arduino Uno laufen lassen
- Die Sprache: Syntax, Typen, Operatoren und Aktoren im Überblick
- Stand & Plan: was fertig ist und was noch kommt
- Quelltext und vollständige Sprachreferenz: github.com/blueberrye-io/marla