Die Sprache im Überblick
Diese Seite fasst die Sprache kurz zusammen. Alle Entscheidungen mit Begründung stehen in der Sprachreferenz auf GitHub.
Kurzformel: „Rust light mit Python-Gemüt“. Ein prozeduraler Kern, statische Typen mit Inferenz,
structs statt Klassen, Enums mit match, Ergebnis-Typen statt Exceptions und Aktoren, die beim
Übersetzen feststehen.
Syntax
- Blöcke durch Einrückung wie in Python. Tabs sind verboten, nur Leerzeichen.
- Kommentare beginnen mit
#. - Typen stehen hinter dem Namen:
name: Typ. - Zuweisung mit
:=ist eine Anweisung, kein Ausdruck.x := y := 5gibt es nicht. - Jedes Wort ist immer Schlüsselwort oder immer Name, nie abhängig vom Zusammenhang.
Sonderbedeutungen wie
mainoder der Aktor-Handlerreceivesind normale Funktionsnamen, die der Compiler erkennt.
Typen
u8 i8 u16 i16 u32 i32 bool
Es gibt keine stillen Umwandlungen. u8 + u16 ist ein Fehler; umgewandelt wird ausdrücklich mit
as. Das erspart die Integer-Promotion von C, die auf dem AVR (dort ist int nur 16 Bit breit)
besonders gern überrascht. Floats gibt es noch nicht; / ist für sie reserviert.
let x: u16 := 500 # unveränderlich, Typ angegeben
let y := 5 # unveränderlich, Typ abgeleitet
let mut zaehler: u16 := 0 # veränderlich
const LED_PIN: u8 := 13 # Konstante, wird beim Übersetzen ausgerechnet
Rechnen mit vorzeichenlosen Typen läuft auf der angegebenen Breite definiert über
(255 + 1 wird bei u8 zu 0).
Operatoren
| Zweck | MARLA | Anmerkung |
|---|---|---|
| Zuweisung | :=, +=, -= … |
nur als Anweisung; kein ++ |
| gleich / ungleich | = / <> |
== gibt es nicht, der Compiler fragt „meintest du =?“ |
| Vergleiche | < <= > >= |
|
| Logik | and or not |
Wörter statt Zeichen |
| Rechnen | + - * |
|
| Ganzzahl-Division, Rest | //, mod |
Rundung wie in C (zur Null hin) |
| Bits | & \| ^ << >> |
wie in C |
| Umwandlung | as |
x as u16 |
| Nachricht senden | ->> |
Tick ->> Blinker |
Kontrollfluss und Funktionen
fn quadrat(x: u16) -> u32:
return (x as u32) * (x as u32)
fn main():
let mut i: u8 := 0
while i < 10:
if i mod 2 = 0:
i += 1
continue
elif i = 7:
break
else:
i += 1
loop: # Endlosschleife
return
Der Compiler prüft, dass jede Funktion mit Rückgabetyp auf allen Wegen etwas zurückgibt.
Anbindung an C
Hardwarenahes bleibt in C. MARLA ruft es über extern fn auf:
extern fn hal_led_write(on: bool)
extern fn hal_delay_ms(ms: u16)
Die Funktionen liefert eine kleine HAL-Schicht pro Zielsystem: hal_avr.c für den Uno,
hal_host.c für den Testlauf auf dem PC.
Aktoren
Ein Aktor ist ein Programmteil mit eigenem Zustand und einem Briefkasten (Mailbox) fester Größe. Andere Programmteile schicken ihm Nachrichten; er arbeitet sie nacheinander ab. Niemand sonst kommt an seinen Zustand.
enum MotorMsg:
Stopp
SetSpeed(rpm: u16)
actor Motor(mailbox: 8, full: DropOldest):
current_rpm: u16 := 0
fn receive(msg: MotorMsg):
match msg:
Stopp:
self.current_rpm := 0
SetSpeed(rpm):
self.current_rpm := rpm
fn main():
SetSpeed(120) ->> Motor
Stopp ->> Motor
- Alle Aktoren stehen beim Übersetzen fest. Jeder wird ein globales C-struct, der Briefkasten ein Ringpuffer. Der Speicherbedarf ist damit vorher bekannt.
- Ein Nachrichten-Typ pro Aktor. Der Compiler prüft, dass nur passende Nachrichten geschickt werden.
- Voller Briefkasten ist kein Zufall, sondern pro Aktor festgelegt:
Drop(Neues verwerfen),DropOldest(Ältestes überschreiben) oderPanic(Programmfehler). Eine Vorgabe gibt es absichtlich nicht. - Senden blockiert nie. Auf dem AVR sperrt
->>kurz die Interrupts; deshalb dürfen auch Interrupt-Routinen sicher senden, ohne dass man sich umvolatilekümmern muss. - Timer und serielle Schnittstelle sind vordefinierte Aktoren. Sie schicken
Tickbzw.ByteReceived(b)an den Aktor, den die Board-Datei nennt.
Auf dem AVR fragt eine Hauptschleife die Briefkästen reihum ab. Ein Handler, der wartet, hält deshalb alle anderen auf; Handler müssen kurz sein.
Was es bewusst nicht gibt
| Nicht in MARLA | Warum |
|---|---|
| Heap, Garbage Collector | unberechenbarer Speicher und Zeitbedarf auf 2 KB RAM |
| Exceptions | brauchen Stack-Abwicklung, unvorhersehbares Timing; stattdessen Ergebnis-Typen |
| Vererbung, virtuelle Methoden | versteckte Kosten, in Embedded selten das richtige Werkzeug |
| Closures | brauchen Speicher für eingefangene Variablen |
| eigene Threads | auf dem AVR Interrupts, später auf Zephyr dessen Threads |
| globale veränderliche Variablen | veränderlicher Zustand gehört gekapselt in Aktoren |