Berry ist eine ultraleichte, dynamisch typisierte Embedded-Skriptsprache
(berry-lang.github.io)- Berry ist eine dynamisch typisierte Skriptsprache für leistungsschwache Embedded-Geräte; auf einem ARM Cortex M4 ist der Interpreter-Core kleiner als 40 KiB und kann auch mit weniger als 4 KiB Heap ausgeführt werden
- Die Runtime besteht aus einem One-Pass-Compiler und einer registerbasierten VM; der gesamte Code ist in ANSI C99 geschrieben und lässt sich daher leicht in bestehenden Embedded-Code integrieren
- Aus Performance-Gründen werden int, real, boolean und string nicht als Klassenobjekte umgesetzt; nur list, map und range werden als Klassenobjekte behandelt
- Die Sprachfunktionen unterstützen imperative, objektorientierte und funktionale Programmierung und umfassen GC, FFI, Exception Handling, Bytecode-Dateien und Erweiterungsmodule
- Durch Objektkonstruktion zur Compile-Zeit und Fixierung im Flash lässt sich die RAM-Nutzung von Code, Klassen und Modulen reduzieren, was gut zu stark speicherbeschränkten Umgebungen passt
Runtime für Embedded-Umgebungen
- Berry ist eine dynamisch typisierte Skriptsprache für Embedded-Geräte mit geringer Leistung, etwa Mikroprozessoren
- Auf Basis einer ARM Cortex M4 CPU, Thumb ISA und des ARMCC-Compilers liegt die Codegröße des Interpreter-Cores unter 40 KiB
- Unter denselben Bedingungen ist die Ausführung mit weniger als 4 KiB Heap möglich
- Der Interpreter ist in ANSI C99 geschrieben und enthält einen One-Pass-Bytecode-Compiler sowie eine registerbasierte virtuelle Maschine
- Das Typdesign priorisiert Laufzeit-Performance und unterscheidet zwischen Werttypen und Objekttypen
- Typen, die keine Klassenobjekte sind: int, real, boolean, string
- Typen, die Klassenobjekte sind: list, map, range
- Die Objektkonstruktion zur Compile-Zeit speichert die meisten konstanten Objekte im schreibgeschützten Code-Datensegment und senkt so beim Start des Interpreters die RAM-Nutzung
Sprachfunktionen und Ausführungsmodell
- Die Basistypen umfassen nil, boolean, integer, real, string, class, instance, module, list, map und range
- Ausdrücke und Operatoren unterstützen Zuweisung, relationale, logische, arithmetische und bitweise Operationen, Felder, Indizes, String-Verkettung, den bedingten Operator, Klammern und bytes buffer
- Kontrollstrukturen sind
if elif else end,while,for,breakundcontinue - Die Funktionsfunktionen umfassen lokale Variablen und Block-Scope, Rückgabeanweisungen, verschachtelte Funktionsdefinitionen, Upvalue-basierte Closures, anonyme Funktionen und Lambda-Ausdrücke
- Klassen unterstützen nur öffentliche Einfachvererbung und bieten Methoden, Operator-Overloading, Konstruktormethoden und Destruktormethoden
- Der GC verwendet Mark-Sweep GC
- Exception Handling kann mit der
raise-Anweisung beliebige Exception-Werte werfen und unterstützt mehrere catch-Modi - Mit der Bytecode-Dateifunktion können Funktionen in Bytecode-Dateien exportiert und anschließend wieder geladen und ausgeführt werden
Module, C-Integration und Beispiele
- Die Modulverwaltung unterstützt sowohl eingebaute Module als auch Erweiterungsmodule
- Erweiterungsmodule umfassen Skriptmodule, Bytecode-Dateimodule und Shared-Library-Module wie
*.sound*.dll - Code, Klassen und Module können im Flash fixiert werden, um die RAM-Nutzung zu reduzieren
- Optional werden Regex-Unterstützung und optionales LVGL mapping angeboten
- Erweiterungsmodule umfassen Skriptmodule, Bytecode-Dateimodule und Shared-Library-Module wie
- Über das Native C Interface lässt es sich wie eine Bibliothek in bestehenden Code einbetten; Code wie Tasmota dient als Beispiel
- Optionales easy mapping wird bereitgestellt, um aus Berry heraus C-Code aufzurufen
- Die Syntaxbeispiele zeigen das Durchlaufen von Listen, das Erstellen von maps und Durchlaufen von Schlüsseln,
class-Deklarationen sowie Einfachvererbung über den Operator: - Weitere Beispielquellen sind auf GitHub verfügbar
1 Kommentare
Hacker-News-Kommentare
Für eine 40-KB-Runtime ist der Funktionsumfang erstaunlich groß: Auf einer VM mit Garbage Collection läuft eine Python/Ruby-ähnliche Sprache, und sie unterstützt prozedurale, objektorientierte und funktionale Stile.
Schon auf den ersten Blick wirkt sie ziemlich angenehm zu schreiben. Besonders auffällig ist die Möglichkeit, konstante Objekte vorab zu erzeugen und größtenteils im ROM abzulegen, während RAM nur für tatsächlich veränderbare Daten genutzt wird. Soweit ich weiß, gibt es so etwas weder in MicroPython noch in Lua; bei MCUs mit reichlich ROM/Flash, aber knappem RAM macht das einen großen Unterschied.
Wenn ein Modul gespeicherte Daten in Form unveränderlicher Objekte wie Strings oder
bytes()enthält, werden sie direkt aus dem Flash gelesen, ohne vorher in den RAM kopiert zu werden. Allerdings muss man zum Freezen den Code einmal auf einem Desktop-Computer ausführen.https://docs.micropython.org/en/latest/reference/constrained...
Ein angenehmer Workflow ist daher, den Code zur Laufzeit auszuliefern, bis die Codebasis stabil ist oder sich der Produktphase nähert, und den Code dann in die Firmware zu freezen. Als noch komfortableres Modell gibt es einen Vorschlag für
mapfs-Unterstützung: Ein Teil des Flash wird für Code-Speicher reserviert, Python wird zu Bytecode kompiliert (mpy_cross), in diesen Bereich hochgeladen und kann zur Laufzeit ausgeführt werden. Ein Proof of Concept existiert bereits, aber vor der Aufnahme in den Mainline-Zweig sind noch Details zu bereinigen.https://github.com/micropython/micropython/pull/8381
Siehe die rotables unter https://eluaproject.net/doc/v0.9/en_arch_ltr.html
Berry wird in Tasmota verwendet: https://tasmota.github.io/docs/Berry/
Sieht wirklich gut aus. Persönlich halte ich die Dokumentation für nahezu erstklassig, verglichen mit vielem, was ich bisher gesehen habe.
Besonders gut ist das „short manual“, das erfahrenen Entwicklern schnell ein Gefühl für die Sprache vermittelt. Tasmota kannte ich vorher nicht, aber ich werde wohl nach einem Vorwand suchen, es in einem Projekt auszuprobieren.
Am meisten interessiert mich, wie Performance und Speicherverbrauch im Vergleich zu Lua aussehen und wie gut Sandboxing möglich ist.
Ich frage mich auch, ob man nicht vertrauenswürdigen Code ausführen kann.
Wenn diese Sprache mit einfachen Structs und Standardkonstruktoren eine brauchbare Alternative ist, könnte ich sie empfehlen, um Python-Skripte und deren Abhängigkeiten loszuwerden.
Sandboxing muss die Anwendung übernehmen. Für Sandboxing ist WebAssembly eine gute Alternative.
async-Schlüsselwort hinzukäme, würde sie sich für Game-Engine-Einsätze klar von Lua abheben.Etwas fast Genaues in dieser Art, aber mit weniger Paradigmen und statischer Typisierung, wäre als Konfigurationssprache wirklich großartig.
In mehreren Projekten mit komplexer Konfiguration gab es die Möglichkeit, statt hunderter Zeilen YAML Lua oder Starlark/Python zu verwenden, und das fühlte sich deutlich besser an. Allerdings fehlten mir immer Autovervollständigung und Reflection. Für diesen Zweck sehe ich kaum passende Kandidaten, und fast alle kleinen einbettbaren Skriptsprachen sind dynamisch typisiert.
Allerdings zeigt https://dhall-lang.org/, dass sich viele Konfigurationen statisch typisiert behandeln lassen und dass das große Vorteile hat. Es scheint sich programmatisch in mehrere Sprachen einbetten zu lassen.
https://docs.dhall-lang.org/howtos/How-to-integrate-Dhall.ht...
Sieht gut aus. Trotzdem tendiere ich derzeit noch ziemlich zu uLisp (http://www.ulisp.com), aber es ist schön, dass so etwas erscheint.
Wenn man sich zuerst Codebeispiele ansieht, um ein Gefühl für eine Sprache zu bekommen, lohnt sich ein Blick in das Beispielverzeichnis: https://github.com/berry-lang/berry/tree/master/examples
Bindings für andere Sprachen wären großartig. Wir verwenden quickjs aus Rust heraus, und das funktioniert ziemlich gut.
Da man der VM nur das bereitstellen kann, was man möchte, lässt sich auch nicht vertrauenswürdiger Code ausführen.
Wirkt wie eine Sprache, die für Embedded-Einsätze optimiert ist. Design und Dokumentation sind gut, und sie scheint nichts Seltsames oder Unerwartetes zu tun.
Auch die Syntax ist angenehm minimalistisch und trifft meinen Geschmack. Ich werde sie bei meinem nächsten ESP32-Projekt definitiv im Hinterkopf behalten.
Für diese Nische scheint sie wirklich gut zu passen, vor allem weil sie Dinge tut, für die mir in anderen Sprachen keine direkten Entsprechungen einfallen.
Ich frage mich, ob es einen Standardansatz gibt, um in nativen Stacktraces bei Stackframes die Namen von Skriptfunktionen anzuzeigen.
Bei Abstürzen oder beim Einsatz eines CPU-Profilers wüsste ich gern, ob sich native Stacktraces und Skript-Stacktraces kombiniert darstellen lassen.