Nur-Header-PEG-Bibliothek (Parsing Expression Grammars) für C++17. Sie können es sofort verwenden, indem Sie einfach peglib.h
in Ihr Projekt einbinden.
Da diese Bibliothek nur C++17-Compiler unterstützt, stellen Sie bitte sicher, dass die Compileroption -std=c++17
aktiviert ist. ( /std:c++17 /Zc:__cplusplus
für MSVC)
Sie können auch die Online-Version PEG Playground unter https://yhirose.github.io/cpp-peglib ausprobieren.
Die PEG-Syntax ist auf Seite 2 im Dokument von Bryan Ford ausführlich beschrieben. cpp-peglib unterstützt derzeit auch die folgende zusätzliche Syntax:
'...'i
(Literaloperator ohne Berücksichtigung der Groß-/Kleinschreibung)
[...]i
(Zeichenklassenoperator ohne Berücksichtigung der Groß-/Kleinschreibung)
[^...]
(Negierter Zeichenklassenoperator)
[^...]i
(Operator für negierte Zeichenklassen ohne Berücksichtigung der Groß-/Kleinschreibung)
{2,5}
(Regex-ähnlicher Wiederholungsoperator)
<
... >
(Token-Grenzoperator)
~
(Operator ignorieren)
x20
(Hex-Zahlenzeichen)
u10FFFF
(Unicode-Zeichen)
%whitespace
(Automatisches Überspringen von Leerzeichen)
%word
(Wortausdruck)
$name(
... )
(Capture-Scope-Operator)
$name<
... >
(Benannter Capture-Operator)
$name
(Rückreferenzoperator)
|
(Wörterbuchoperator)
↑
(Cut-Operator)
MACRO_NAME(
... )
(parametrisierte Regel oder Makro)
{ precedence L - + L / * }
(Infixausdruck analysieren)
%recovery(
... )
(Fehlerbehebungsoperator)
exp⇑label
oder exp^label
(Syntaxzucker für (exp / %recover(label))
)
label { error_message "..." }
(Fehlermeldungsanweisung)
{ no_ast_opt }
(Keine AST-Knoten-Optimierungsanweisung)
Die Prüfung „Ende der Eingabe“ wird standardmäßig durchgeführt. Um die Prüfung zu deaktivieren, rufen Sie bitte disable_eoi_check
auf.
Diese Bibliothek unterstützt das als Packrat -Parsing bekannte lineare Zeitparsing.
WICHTIGER HINWEIS für einige Linux-Distributionen wie Ubuntu und CentOS: Beim Verknüpfen ist die Option -pthread
erforderlich. Siehe Nr. 23, Nr. 46 und Nr. 62.
Ich bin sicher, dass Ihnen dieser ausgezeichnete Artikel „Praktisches Parsen mit PEG und cpp-peglib“ von Bert Hubert gefallen wird!
Dies ist ein einfaches Taschenrechnerbeispiel. Es zeigt, wie man Grammatik definiert, semantische Aktionen mit der Grammatik verknüpft und mit semantischen Werten umgeht.
// (1) Fügen Sie die Header-Datei ein#include <peglib.h>#include <assert.h>#include <iostream>using namespace peg;using namespace std;int main(void) { // (2) Erstellen Sie einen Parser parser parser(R"( # Grammatik für Rechner... Additiv <- Multiplikativ '+' Additiv / Multiplikativ Multiplikativ <- Primär '*' Multiplikativ / Primär Primär <- '(' Additiv ')' / Zahl Zahl <- < [ 0-9]+ > %whitespace <- [ t]* )"); behaupten(static_cast<bool>(parser) == true); // (3) Setup-Aktionen parser["Additive"] = [](const SemanticValues &vs) {switch (vs.choice()) {case 0: // "Multiplicative '+' Additive" return any_cast<int>(vs[0]) + any_cast< int>(vs[1]);default: // „Multiplikativ“ return any_cast<int>(vs[0]); } }; parser["Multiplicative"] = [](const SemanticValues &vs) {switch (vs.choice()) {case 0: // "Primary '*' Multiplicative" return any_cast<int>(vs[0]) * any_cast< int>(vs[1]);default: // „Primary“ return any_cast<int>(vs[0]); } }; parser["Number"] = [](const SemanticValues &vs) {return vs.token_to_number<int>(); }; // (4) Analysieren parser.enable_packrat_parsing(); // Packrat-Parsing aktivieren. int val; parser.parse(" (1 + 2) * 3", val); behaupten(Wert == 9); }
So zeigen Sie Syntaxfehler im Grammatiktext an:
auto grammar = R"( # Grammatik für Rechner... Additiv <- Multiplikativ '+' Additiv / Multiplikativ Multiplikativ <- Primär '*' Multiplikativ / Primär Primär <- '(' Additiv ')' / Zahl Zahl <- < [ 0-9]+ > %whitespace <- [ t]*)"; Parser-Parser; parser.set_logger([](size_t line, size_t col, const string& msg, const string &rule) { cerr << line << ":" << col << ": " << msg << "n"; });auto ok = parser.load_grammar(grammar);assert(ok);
Es stehen vier semantische Aktionen zur Verfügung:
[](const SemanticValues& vs, any& dt) [](const SemanticValues& vs) [](SemanticValues& vs, any& dt) [](SemanticValues& vs)
Der SemanticValues
Wert enthält die folgenden Informationen:
Semantische Werte
Übereinstimmende Zeichenfolgeninformationen
Token-Informationen, wenn die Regel literal ist oder einen Token-Grenzoperator verwendet
Auswahlnummer, wenn die Regel „priorisierte Auswahl“ ist
any& dt
handelt es sich um Kontextdaten mit Lese-/Schreibzugriff, die für beliebige Zwecke verwendet werden können. Die anfänglichen Kontextdaten werden in der Methode peg::parser::parse
festgelegt.
Eine semantische Aktion kann einen Wert eines beliebigen Datentyps zurückgeben, der von peg::any
umschlossen wird. Wenn ein Benutzer in einer semantischen Aktion nichts zurückgibt, wird der erste semantische Wert im Argument const SemanticValues& vs
zurückgegeben. (Der Yacc-Parser hat das gleiche Verhalten.)
Hier zeigt die SemanticValues
-Struktur:
struct SemanticValues : protected std::vector<any> { // Eingabetext const char* path; const char* ss; // Übereinstimmende Zeichenfolge std::string_view sv() const { return sv_; } // Zeilennummer und Spalte, in der sich die übereinstimmende Zeichenfolge befindet std::pair<size_t, size_t> line_info() const; // Token std::vector<std::string_view> tokens; std::string_view token(size_t id = 0) const; // Token-Konvertierung std::string token_to_string(size_t id = 0) const; template <typename T> T token_to_number() const; // Auswahlnummer (0-basierter Index) size_t choice() const; // Den semantischen Wertvektor in einen anderen Vektor umwandeln template <typename T> vector<T> transform(size_t beg = 0, size_t end = -1) const; }
Im folgenden Beispiel wird der Operator <
... >
verwendet, bei dem es sich um einen Token- Grenzoperator handelt.
peg::parser parser(R"( ROOT <- _ TOKEN (',' _ TOKEN)* TOKEN <- < [a-z0-9]+ > _ _ <- [ trn]*)"); parser["TOKEN"] = [](const SemanticValues& vs) { // 'token' enthält keine nachgestellten Leerzeichen auto token = vs.token(); };auto ret = parser.parse(" token1, token2 ");
Wir können unnötige semantische Werte aus der Liste ignorieren, indem wir den Operator ~
verwenden.
peg::parser parser(R"( ROOT <- _ ITEM (',' _ ITEM _)* ITEM <- ([a-z0-9])+ ~_ <- [ t]*)"); parser["ROOT"] = [&](const SemanticValues& vs) { affirm(vs.size() == 2); // sollte 2 statt 5 sein.};auto ret = parser.parse(" item1, item2 ");
Die folgende Grammatik ist die gleiche wie oben.
peg::parser parser(R"( ROOT <- ~_ ITEM (',' ~_ ITEM ~_)* ITEM <- ([a-z0-9])+ _ <- [ t]*)");
Semantische Prädikatunterstützung ist mit einer Prädikataktion verfügbar.
peg::parser parser("NUMBER <- [0-9]+"); parser["NUMBER"] = [](const SemanticValues &vs) { return vs.token_to_number<long>(); }; parser["NUMBER"].predicate = [](const SemanticValues &vs,const std::any & /*dt*/, std::string &msg) { if (vs.token_to_number<long>() != 100) { msg = "Wertfehler!!";return false; } return true; };long val;auto ret = parser.parse("100", val);assert(ret == true);assert(val == 100); ret = parser.parse("200", val);assert(ret == false);
Es sind auch Eingabe- und Austrittsaktionen verfügbar.
parser["RULE"].enter = [](const Context &c, const char* s, size_t n, any& dt) { std::cout << "enter" << std::endl; }; parser["RULE"] = [](const SemanticValues& vs, any& dt) { std::cout << "Aktion!" << std::endl; }; parser["RULE"].leave = [](const Context &c, const char* s, size_t n, size_t matchlen, any& value, any& dt) { std::cout << "leave" << std::endl; };
Sie können Fehlerinformationen über einen Logger erhalten:
parser.set_logger([](size_t line, size_t col, const string& msg) { ... }); parser.set_logger([](size_t line, size_t col, const string& msg, const string &rule) { ... });
Wie Sie im ersten Beispiel sehen können, können wir Leerzeichen zwischen Token mit %whitespace
-Regel automatisch ignorieren.
%whitespace
-Regel kann auf die folgenden drei Bedingungen angewendet werden:
Nachgestellte Leerzeichen auf Token
Führende Leerzeichen im Text
nachgestellte Leerzeichen bei Literalzeichenfolgen in Regeln
Dies sind gültige Token:
KEYWORD <- 'keyword' KEYWORDI <- 'case_insensitive_keyword' WORD <- < [a-zA-Z0-9] [a-zA-Z0-9-_]* > # token boundary operator is used. IDNET <- < IDENT_START_CHAR IDENT_CHAR* > # token boundary operator is used.
Die folgende Grammatik akzeptiert one, "two three", four
.
ROOT <- ITEM (',' ITEM)* ITEM <- WORD / PHRASE WORD <- < [a-z]+ > PHRASE <- < '"' (!'"' .)* '"' > %whitespace <- [ trn]*
peg::parser parser(R"( ROOT <- 'hello' 'world' %whitespace <- [ trn]* %word <- [az]+)"); parser.parse("Hallo Welt"); // OKparser.parse("helloworld"); // NG
peg::parser parser(R"( ROOT <- CONTENT CONTENT <- (ELEMENT / TEXT)* ELEMENT <- $(STAG CONTENT ETAG) STAG <- '<' $tag< TAG_NAME > '>' ETAG <- '< /' $tag '>' TAG_NAME <- 'b' / 'u' TEXT <- TEXT_DATA TEXT_DATA <- ![<] .)"); parser.parse("Dies ist <b>ein <u>Test</u>-Text</b>."); // OKparser.parse("Dies ist <b>ein <u>Test</b>-Text</u>."); // NGparser.parse("Dies ist <b>ein <u>Testtext</b>."); // NG
|
Mit dem Operator können wir ein Wortwörterbuch für eine schnelle Suche erstellen, indem wir intern die Trie-Struktur verwenden. Wir müssen uns keine Gedanken über die Reihenfolge der Wörter machen.
START <- 'This month is ' MONTH '.'
MONTH <- 'Jan' | 'January' | 'Feb' | 'February' | '...'
Wir können herausfinden, welches Element mit choice()
übereinstimmt.
parser["MONTH"] = [](const SemanticValues &vs) { auto id = vs.choice(); };
Es unterstützt den Modus ohne Berücksichtigung der Groß-/Kleinschreibung.
START <- 'This month is ' MONTH '.'
MONTH <- 'Jan'i | 'January'i | 'Feb'i | 'February'i | '...'i
↑
Der Operator könnte das Backtrack-Leistungsproblem abmildern, birgt jedoch das Risiko, die Bedeutung der Grammatik zu ändern.
S <- '(' ↑ P ')' / '"' ↑ P '"' / P
P <- 'a' / 'b' / 'c'
Wenn wir (z
mit der obigen Grammatik analysieren, müssen wir in S
nicht zurückgehen, nachdem (
abgeglichen wurde, da dort ein Cut-Operator eingefügt wird.
# Syntax
Start ← _ Expr
Expr ← Sum
Sum ← List(Product, SumOpe)
Product ← List(Value, ProOpe)
Value ← Number / T('(') Expr T(')')
# Token
SumOpe ← T('+' / '-')
ProOpe ← T('*' / '/')
Number ← T([0-9]+)
~_ ← [ trn]*
# Macro
List(I, D) ← I (D I)*
T(x) ← < x > _
Informationen zum Precedence-Climbing-Algorithmus finden Sie in diesem Artikel.
parser parser(R"( EXPRESSION <- INFIX_EXPRESSION(ATOM, OPERATOR) ATOM <- NUMBER / '(' EXPRESSION ')' OPERATOR <- < [-+/*] > NUMBER <- < '-'? [0-9 ]+ > %whitespace <- [ t]* # Rangfolge festlegen INFIX_EXPRESSION(A, O) <- A (O A)* { Vorrang L + - L * / })"); parser["INFIX_EXPRESSION"] = [](const SemanticValues& vs) -> long { auto result = any_cast<long>(vs[0]); if (vs.size() > 1) {auto ope = any_cast<char>(vs[1]);auto num = any_cast<long>(vs[2]);switch (ope) { case '+': result += num; brechen; case '-': result -= num; brechen; case '*': result *= num; brechen; case '/': result /= num; brechen; } } Ergebnis zurückgeben; }; parser["OPERATOR"] = [](const SemanticValues& vs) { return *vs.sv(); }; parser["NUMBER"] = [](const SemanticValues& vs) { return vs.token_to_number<long>(); };langer Wert; parser.parse(" -1 + (1 + 2) * 3 - -1", val);assert(val == 9);
Die Vorranganweisung kann nur auf die folgende Stilregel „Liste“ angewendet werden.
Rule <- Atom (Operator Atom)* { precedence L - + L / * R ^ }
Die Vorranganweisung enthält Vorranginformationseinträge. Jeder Eintrag beginnt mit der Assoziativität, die „L“ (links) oder „R“ (rechts) ist, dann folgen Operator- Literal- Token. Der erste Eintrag hat die höchste Bestellstufe.
cpp-peglib ist in der Lage, beim Parsen einen AST (Abstract Syntax Tree) zu generieren. Die Methode enable_ast
in der Klasse peg::parser
aktiviert die Funktion.
HINWEIS: Ein AST-Knoten hält ein entsprechendes Token als std::string_vew
für Leistung und geringere Speichernutzung. Es liegt in der Verantwortung des Benutzers, den ursprünglichen Quelltext zusammen mit dem generierten AST-Baum aufzubewahren.
peg::parser parser(R"( ... definition1 <- ... { no_ast_opt } definition2 <- ... { no_ast_opt } ... )"); parser.enable_ast(); shared_ptr<peg::Ast> ast; if (parser.parse("...", ast)) { cout << peg::ast_to_s(ast); ast = parser.optimize_ast(ast); cout << peg::ast_to_s(ast); }
optimize_ast
entfernt redundante Knoten, um einen AST einfacher zu machen. Wenn Sie dieses Verhalten für bestimmte Regeln deaktivieren möchten, kann die Anweisung no_ast_opt
verwendet werden.
Es ruft intern peg::AstOptimizer
auf, um die Aufgabe zu erledigen. Sie können Ihre eigenen AST-Optimierer entsprechend Ihren Anforderungen erstellen.
Sehen Sie sich die tatsächliche Verwendung im Beispiel des AST-Rechners und im Beispiel der PL/0-Sprache an.
Anstatt einen Parser durch Parsen von PEG-Syntaxtext zu erstellen, können wir einen Parser auch manuell mit Parser-Kombinatoren erstellen. Hier ist ein Beispiel:
Verwendung des Namensraums peg;Verwendung des Namensraums std;vector<string>-Tags; Definition ROOT, TAG_NAME, _; ROOT <= seq(_, zom(seq(chr('['), TAG_NAME, chr(']'), _))); TAG_NAME <= oom(seq(npd(chr(']')), dot())), [&](const SemanticValues& vs) { tags.push_back(vs.token_to_string()); }; _ <= zom(cls(" t"));auto ret = ROOT.parse(" [tag1] [tag:2] [tag-3] ");
Die folgenden Operatoren sind verfügbar:
Operator | Beschreibung | Operator | Beschreibung |
---|---|---|---|
seq | Sequenz | cho | Priorisierte Wahl |
zom | Null oder mehr | bumm | Einer oder mehrere |
opt | Optional | apd | Und Prädikat |
NPD | Kein Prädikat | lit | Literale Zeichenfolge |
liti | Literalzeichenfolge ohne Berücksichtigung der Groß- und Kleinschreibung | cls | Charakterklasse |
ncls | Negierte Zeichenklasse | chr | Charakter |
Punkt | Jeder Charakter | tok | Token-Grenze |
ign | Semantischen Wert ignorieren | csc | Umfang erfassen |
Kappe | Erfassen | bkr | Rückverweis |
dic | Wörterbuch | vor | Infix-Ausdruck |
empf | Infix-Ausdruck | usr | Benutzerdefinierter Parser |
rep | Wiederholung |
Es ist möglich, Definitionen hinzuzufügen/zu überschreiben.
auto syntax = R"( ROOT <- _ 'Hallo' _ NAME '!' _)"; Regeln Additional_rules = { {"NAME", usr([](const char* s, size_t n, SemanticValues& vs, any& dt) -> size_t { static vector<string> name = { "PEG", "BNF" }; for (const auto& name : Namen) {if (name.size() <= n && !name.compare(0, name.size(), s, name.size())) { return name.size(); // verarbeitete Länge} } return -1; // Parse-Fehler}) }, {"~_", zom(cls(" trn")) } };auto g = parser(syntax, zusätzliche_regeln);assert(g.parse(" Hallo BNF! "));
cpp-peglib akzeptiert UTF8-Text. .
entspricht einem Unicode-Codepunkt. Außerdem unterstützt es u????
.
cpp-peglib unterstützt den am weitesten entfernten Fehlerpositionsbericht, wie im Originaldokument von Bryan Ford beschrieben.
Für eine bessere Fehlerberichterstattung und Wiederherstellung unterstützt cpp-peglib den „Recovery“-Operator mit Label, der einem Wiederherstellungsausdruck und einer benutzerdefinierten Fehlermeldung zugeordnet werden kann. Diese Idee stammt aus dem fantastischen Artikel „Syntax Error Recovery in Parsing Expression Grammars“ von Sergio Medeiros und Fabio Mascarenhas.
Die benutzerdefinierte Nachricht unterstützt %t
einen Platzhalter für das unerwartete Token, und %c
für das unerwartete Unicode-Zeichen.
Hier ist ein Beispiel einer Java-ähnlichen Grammatik:
# java.peg
Prog ← 'public' 'class' NAME '{' 'public' 'static' 'void' 'main' '(' 'String' '[' ']' NAME ')' BlockStmt '}'
BlockStmt ← '{' (!'}' Stmt^stmtb)* '}' # Annotated with `stmtb`
Stmt ← IfStmt / WhileStmt / PrintStmt / DecStmt / AssignStmt / BlockStmt
IfStmt ← 'if' '(' Exp ')' Stmt ('else' Stmt)?
WhileStmt ← 'while' '(' Exp^condw ')' Stmt # Annotated with `condw`
DecStmt ← 'int' NAME ('=' Exp)? ';'
AssignStmt ← NAME '=' Exp ';'^semia # Annotated with `semi`
PrintStmt ← 'System.out.println' '(' Exp ')' ';'
Exp ← RelExp ('==' RelExp)*
RelExp ← AddExp ('<' AddExp)*
AddExp ← MulExp (('+' / '-') MulExp)*
MulExp ← AtomExp (('*' / '/') AtomExp)*
AtomExp ← '(' Exp ')' / NUMBER / NAME
NUMBER ← < [0-9]+ >
NAME ← < [a-zA-Z_][a-zA-Z_0-9]* >
%whitespace ← [ tn]*
%word ← NAME
# Recovery operator labels
semia ← '' { error_message "missing semicolon in assignment." }
stmtb ← (!(Stmt / 'else' / '}') .)* { error_message "invalid statement" }
condw ← &'==' ('==' RelExp)* / &'<' ('<' AddExp)* / (!')' .)*
Beispielsweise ist ';'^semi
ein syntaktischer Zucker für (';' / %recovery(semi))
. %recover
-Operator versucht, den Fehler bei „;“ zu beheben. durch Überspringen von Eingabetext mit dem Wiederherstellungsausdruck semi
. Außerdem ist semi
mit der benutzerdefinierten Meldung „Semikolon in der Zuweisung fehlt“ verbunden.
Hier ist das Ergebnis:
> cat sample.javapublic class Beispiel { public static void main(String[] args) {int n = 5;int f = 1;while( < n) { f = f * n; n = n - 1};System.out.println(f); } } > peglint java.peg sample.javasample.java:5:12: Syntaxfehler, unerwartetes '<', erwartet '(', <NUMMER>, <NAME>.sample.java:8:5: fehlendes Semikolon in „assignment.sample“. .java:8:6: ungültige Anweisung
Wie Sie sehen, kann es jetzt mehr als einen Fehler anzeigen und aussagekräftigere Fehlermeldungen als die Standardmeldungen bereitstellen.
Wir können benutzerdefinierte Fehlermeldungen mit Definitionen verknüpfen.
# custom_message.peg
START <- CODE (',' CODE)*
CODE <- < '0x' [a-fA-F0-9]+ > { error_message 'code format error...' }
%whitespace <- [ t]*
> cat custom_message.txt 0x1234,0x@@@@,0xABCD > peglint custom_message.peg custom_message.txt custom_message.txt:1:8: code format error...
HINWEIS: Wenn in einer priorisierten Auswahl mehr als ein Element mit einer Fehlermeldungsanweisung vorhanden ist, funktioniert diese Funktion möglicherweise nicht wie erwartet.
Wir können die Startdefinitionsregel wie folgt ändern.
auto grammar = R"( Start <- A A <- B (',' B)* B <- '[one]' / '[two]' %whitespace <- [ tn]*)"; peg::parser parser(grammar, "A"); // Startregel ist „A“ orpeg::parser parser; parser.load_grammar(grammar, „A“); // Startregel ist „A“parser.parse(“ [one] , [two] „); // OK
> cd lint > mkdir build > cd build > cmake .. > make > ./peglint usage: grammar_file_path [source_file_path] options: --source: source text --packrat: enable packrat memoise --ast: show AST tree --opt, --opt-all: optimize all AST nodes except nodes selected with `no_ast_opt` instruction --opt-only: optimize only AST nodes selected with `no_ast_opt` instruction --trace: show concise trace messages --profile: show profile report --verbose: verbose output for trace and profile
> cat a.peg Additive <- Multiplicative '+' Additive / Multiplicative Multiplicative <- Primary '*' Multiplicative / Primary Primary <- '(' Additive ')' / Number %whitespace <- [ trn]* > peglint a.peg [commandline]:3:35: 'Number' is not defined.
> cat a.peg Additive <- Multiplicative '+' Additive / Multiplicative Multiplicative <- Primary '*' Multiplicative / Primary Primary <- '(' Additive ')' / Number Number <- < [0-9]+ > %whitespace <- [ trn]* > peglint --source "1 + a * 3" a.peg [commandline]:1:3: syntax error
> cat a.txt 1 + 2 * 3 > peglint --ast a.peg a.txt + Additive + Multiplicative + Primary - Number (1) + Additive + Multiplicative + Primary - Number (2) + Multiplicative + Primary - Number (3)
> peglint --ast --opt --source "1 + 2 * 3" a.peg + Additive - Multiplicative[Number] (1) + Additive[Multiplicative] - Primary[Number] (2) - Multiplicative[Number] (3)
no_ast_opt
an > cat a.peg Additive <- Multiplicative '+' Additive / Multiplicative Multiplicative <- Primary '*' Multiplicative / Primary Primary <- '(' Additive ')' / Number { no_ast_opt } Number <- < [0-9]+ > %whitespace <- [ trn]* > peglint --ast --opt --source "1 + 2 * 3" a.peg + Additive/0 + Multiplicative/1[Primary] - Number (1) + Additive/1[Multiplicative] + Primary/1 - Number (2) + Multiplicative/1[Primary] - Number (3) > peglint --ast --opt-only --source "1 + 2 * 3" a.peg + Additive/0 + Multiplicative/1 - Primary/1[Number] (1) + Additive/1 + Multiplicative/0 - Primary/1[Number] (2) + Multiplicative/1 - Primary/1[Number] (3)
Kalkulator
Rechner (mit Parser-Operatoren)
Rechner (AST-Version)
Rechner (Parsing von Ausdrücken nach Prioritätserhöhung)
Rechner (AST-Version und Parsen von Ausdrücken nach Prioritätserhöhung)
Ein winziger PL/0-JIT-Compiler in weniger als 900 LOC mit LLVM und PEG-Parser
Eine Programmiersprache nur zum Schreiben von Fizz Buzz-Programmen. :) :)
MIT-Lizenz (© 2022 Yuji Hirose)