Ich habe SLIC (System of Languages for Implementing Compilers) in sich selbst geschrieben. Dann von Hand zusammengestellt. SLIC hat viel zu bieten, da es sich um einen einzelnen Compiler aus fünf Untersprachen handelte:
- SYNTAX Parser Programmiersprache PPL
- GENERATOR LISP 2-basierte PSEUDO-Codegenerierungssprache zum Crawlen von Bäumen
- ISO In Sequence, PSEUDO-Code, Optimierungssprache
- PSEUDO Makro wie Assembler Code produzierende Sprache.
- MACHOP Assembly-Machine-Anweisung, die die Sprache definiert.
SLIC wurde von CWIC (Compiler zum Schreiben und Implementieren von Compilern) inspiriert. Im Gegensatz zu den meisten Compiler-Entwicklungspaketen haben SLIC und CWIC die Codegenerierung mit speziellen, domänenspezifischen Sprachen behandelt. SLIC erweitert die Codegenerierung von CWICs um die Subsprachen ISO, PSEUDO und MACHOP, die die Besonderheiten des Zielcomputers von der Sprache des Generators für das Crawlen von Bäumen trennen.
LISP 2 Bäume und Listen
Das dynamische Speicherverwaltungssystem der LISP 2-basierten Generatorsprache ist eine Schlüsselkomponente. Listen werden in der Sprache in eckigen Klammern angegeben, deren Komponenten durch Kommas getrennt sind, dh eine Liste mit drei Elementen [a, b, c].
Bäume:
ADD
/ \
MPY 3
/ \
5 x
werden durch Listen dargestellt, deren erster Eintrag ein Knotenobjekt ist:
[ADD,[MPY,5,x],3]
Bäume werden normalerweise mit dem separaten Knoten vor den Zweigen angezeigt:
ADD[MPY[5,x],3]
Aufheben der Analyse mit LISP 2-basierten Generatorfunktionen
Eine Generatorfunktion ist eine benannte Menge von (unparse) => Aktion> Paaren ...
<NAME>(<unparse>)=><action>;
(<unparse>)=><action>;
...
(<unparse>)=><action>;
Unparse-Ausdrücke sind Tests, die mit Baummustern und / oder Objekttypen übereinstimmen, die diese aufteilen und diese Teile einer lokalen Variablen zuweisen, die von ihrer prozeduralen Aktion verarbeitet werden soll. Ein bisschen wie eine überladene Funktion, die verschiedene Argumenttypen verwendet. Außer die () => ... Tests werden in der angegebenen Reihenfolge versucht. Die erste erfolgreiche unparse, die ihre entsprechende Aktion ausführt. Die unparsen Ausdrücke sind Zerlegungsprüfungen. ADD [x, y] entspricht einem ADD-Baum mit zwei Zweigen, der seine Zweige den lokalen Variablen x und y zuweist. Die Aktion kann ein einfacher Ausdruck oder ein gebundener Codeblock .BEGIN ... .END sein. Ich würde heute Blöcke im c-Stil {...} verwenden. Baumabgleich, [], unparse Regeln können Generatoren aufrufen, die die zurückgegebenen Ergebnisse an die Aktion übergeben:
expr_gen(ADD[expr_gen(x),expr_gen(y)])=> x+y;
Insbesondere stimmt die obige unparse expr_gen mit einem ADD-Baum mit zwei Zweigen überein. Innerhalb des Testmusters wird ein einzelner Argumentgenerator, der in einem Ast platziert ist, mit diesem Zweig aufgerufen. Die Argumentliste besteht jedoch aus lokalen Variablen, denen zurückgegebene Objekte zugewiesen wurden. Über dem unparse wird angegeben, dass zwei Zweige die ADD-Baumzerlegung sind, wobei rekursiv jeder Zweig auf expr_gen gedrückt wird. Die linke Verzweigungsrückgabe wird in die lokalen Variablen x eingefügt. Ebenso wurde der rechte Zweig mit y dem Rückgabeobjekt an expr_gen übergeben. Das Obige könnte Teil eines Evaluators für numerische Ausdrücke sein. Es gab Verknüpfungsmerkmale, die als Vektoren bezeichnet wurden. Oben konnte anstelle der Knotenzeichenfolge ein Knotenvektor mit einem Vektor entsprechender Aktionen verwendet werden:
expr_gen(#node[expr_gen(x),expr_gen(y)])=> #action;
node: ADD, SUB, MPY, DIV;
action: x+y, x-y, x*y, x/y;
(NUMBER(x))=> x;
(SYMBOL(x))=> val:(x);
Der obige vollständigere Ausdrucksauswerter weist die Rückgabe vom linken Zweig expr_gen an x und den rechten Zweig an y zu. Der entsprechende Aktionsvektor, der für x und y ausgeführt wurde, wurde zurückgegeben. Die letzten unparse => Aktionspaare stimmen mit numerischen und Symbolobjekten überein.
Symbol und Symbolattribute
Symbole können benannte Attribute haben. val: (x) Zugriff auf das val-Attribut des in x enthaltenen Symbolobjekts. Ein verallgemeinerter Symboltabellenstapel ist Teil von SLIC. Die SYMBOL-Tabelle kann verschoben und geöffnet werden, um lokale Symbole für Funktionen bereitzustellen. Neu erstellte Symbole werden in der oberen Symboltabelle katalogisiert. Die Symbolsuche durchsucht den Symboltabellenstapel von der obersten Tabelle zuerst rückwärts den Stapel hinunter.
Maschinenunabhängigen Code generieren
Die Generatorsprache von SLIC erzeugt PSEUDO-Anweisungsobjekte und hängt sie an eine Abschnittscodeliste an. Ein .FLUSH bewirkt, dass seine PSEUDO-Codeliste ausgeführt wird, wobei jeder PSEUDO-Befehl aus der Liste entfernt und aufgerufen wird. Nach der Ausführung wird ein PSEUDO-Objektspeicher freigegeben. Die Verfahrensorgane der PSEUDO- und GENERATOR-Aktionen sind bis auf ihre Ausgabe grundsätzlich dieselbe Sprache. PSEUDO sollen als Assembly-Makros fungieren und eine maschinenunabhängige Codesequenzierung ermöglichen. Sie bieten eine Trennung der spezifischen Zielmaschine von der Sprache des Baumcrawling-Generators. PSEUDOs rufen MACHOP-Funktionen auf, um Maschinencode auszugeben. MACHOPs werden verwendet, um Assembly-Pseudo-Ops (wie DC, Konstante usw.) und Maschinenanweisungen oder eine Familie von ähnlich formatierten Anweisungen unter Verwendung eines vektorisierten Eintrags zu definieren. Sie transformieren einfach ihre Parameter in eine Folge von Bitfeldern, aus denen der Befehl besteht. MACHOP-Aufrufe sollen wie Assembly aussehen und eine Druckformatierung der Felder bereitstellen, wenn Assembly in der Kompilierungsliste angezeigt wird. Im Beispielcode verwende ich Kommentare im C-Stil, die leicht hinzugefügt werden konnten, aber nicht in den Originalsprache waren. MACHOPs erzeugen Code in einem bitadressierbaren Speicher. Der SLIC-Linker verarbeitet die Ausgabe des Compilers. Ein MACHOP für die Anweisungen im DEC-10-Benutzermodus unter Verwendung eines vektorisierten Eintrags: MACHOPs erzeugen Code in einem bitadressierbaren Speicher. Der SLIC-Linker verarbeitet die Ausgabe des Compilers. Ein MACHOP für die Anweisungen im DEC-10-Benutzermodus unter Verwendung eines vektorisierten Eintrags: MACHOPs erzeugen Code in einem bitadressierbaren Speicher. Der SLIC-Linker verarbeitet die Ausgabe des Compilers. Ein MACHOP für die Anweisungen im DEC-10-Benutzermodus unter Verwendung eines vektorisierten Eintrags:
.MACHOP #opnm register,@indirect offset (index): // Instruction's parameters.
.MORG 36, O(18): $/36; // Align to 36 bit boundary print format: 18 bit octal $/36
O(9): #opcd; // Op code 9 bit octal print out
(4): register; // 4 bit register field appended print
(1): indirect; // 1 bit appended print
(4): index; // 4 bit index register appended print
O(18): if (#opcd&&3==1) offset // immediate mode use value else
else offset/36; // memory address divide by 36
// to get word address.
// Vectored entry opcode table:
#opnm := MOVE, MOVEI, MOVEM, MOVES, MOVS, MOVSI, MOVSM, MOVSS,
MOVN, MOVNI, MOVNM, MOVNS, MOVM, MOVMI, MOVMM, MOVMS,
IMUL, IMULI, IMULM, IMULB, MUL, MULI, MULM, MULB,
...
TDO, TSO, TDOE, TSOE, TDOA, TSOA, TDON, TSON;
// corresponding opcode value:
#opcd := 0O200, 0O201, 0O202, 0O203, 0O204, 0O205, 0O206, 0O207,
0O210, 0O211, 0O212, 0O213, 0O214, 0O215, 0O216, 0O217,
0O220, 0O221, 0O222, 0O223, 0O224, 0O225, 0O226, 0O227,
...
0O670, 0O671, 0O672, 0O673, 0O674, 0O675, 0O676, 0O677;
Die .MORG 36, O (18): $ / 36; Richtet die Position an einer 36-Bit-Grenze aus, wobei die Position $ / 36-Wortadresse mit 18 Bit im Oktal gedruckt wird. Das 9-Bit-Operationsregister, das 4-Bit-Register, das indirekte Bit und das 4-Bit-Indexregister werden kombiniert und gedruckt, als ob ein einzelnes 18-Bit-Feld. Die 18-Bit-Adresse / 36 oder der unmittelbare Wert wird ausgegeben und oktal gedruckt. Ein MOVEI-Beispielausdruck mit r1 = 1 und r2 = 2:
400020 201082 000005 MOVEI r1,5(r2)
Mit der Compiler-Assembly-Option erhalten Sie den generierten Assembly-Code in der Compile-Liste.
Verknüpfe es miteinander
Der SLIC-Linker wird als Bibliothek geliefert, die die Verknüpfungs- und Symbolauflösungen verwaltet. Die zielspezifische Formatierung der Ausgabedatei muss jedoch für Zielcomputer geschrieben und mit der Linkerbibliotheksbibliothek verknüpft werden.
Die Generatorsprache kann Bäume in eine Datei schreiben und lesen, sodass ein Multipass-Compiler implementiert werden kann.
Kurze Zusammenfassung der Codegenerierung und -herkunft
Ich habe zuerst die Codegenerierung durchgesehen, um sicherzustellen, dass SLIC ein echter Compiler-Compiler ist. SLIC wurde von CWIC (Compiler for Writing and Implementing Compilers) inspiriert, das Ende der 1960er Jahre bei der Systems Development Corporation entwickelt wurde. CWIC hatte nur SYNTAX- und GENERATOR-Sprachen, die numerischen Bytecode aus der GENERATOR-Sprache erzeugten. Bytecode wurde in Speicherpuffer, die benannten Abschnitten zugeordnet sind, eingefügt oder eingefügt (der in der CWIC-Dokumentation verwendete Begriff) und durch eine .FLUSH-Anweisung ausgeschrieben. Ein ACM-Dokument zu CWIC ist im ACM-Archiv erhältlich.
Erfolgreiche Implementierung einer wichtigen Programmiersprache
In den späten 1970er Jahren wurde SLIC verwendet, um einen COBOL-Cross-Compiler zu schreiben. Fertiggestellt in ca. 3 Monaten meist von einem einzigen Programmierer. Ich habe nach Bedarf ein bisschen mit dem Programmierer gearbeitet. Ein anderer Programmierer hat die Laufzeitbibliothek und MACHOPs für den Ziel-TI-990-Mini-COMPUTER geschrieben. Dieser COBOL-Compiler hat wesentlich mehr Zeilen pro Sekunde kompiliert als der native DEC-10-COBOL-Compiler, der in Assembly geschrieben wurde.
Mehr zu einem Compiler, über den dann normalerweise gesprochen wird
Ein großer Teil des Schreibens eines Compilers von Grund auf ist die Laufzeitbibliothek. Sie benötigen eine Symboltabelle. Sie benötigen Eingabe und Ausgabe. Dynamische Speicherverwaltung usw. Das Schreiben der Laufzeitbibliothek für einen Compiler kann einfacher sein als das Schreiben des Compilers. Mit SLIC ist diese Laufzeitbibliothek jedoch allen in SLIC entwickelten Compilern gemeinsam. Beachten Sie, dass es zwei Laufzeitbibliotheken gibt. Eine für die Zielmaschine der Sprache (z. B. COBOL). Die andere ist die Laufzeitbibliothek des Compilers.
Ich glaube, ich habe festgestellt, dass dies keine Parser-Generatoren waren. Mit ein wenig Verständnis des Backends kann ich nun die Parser-Programmiersprache erklären.
Parser-Programmiersprache
Der Parser wird mit einer Formel geschrieben, die in Form einfacher Gleichungen geschrieben ist.
<name> <formula type operator> <expression> ;
Das Sprachelement auf der untersten Ebene ist das Zeichen. Token werden aus einer Teilmenge der Zeichen der Sprache gebildet. Zeichenklassen werden verwendet, um diese Zeichenuntergruppen zu benennen und zu definieren. Der Operator, der die Zeichenklasse definiert, ist das Doppelpunktzeichen (:). Zeichen, die Mitglieder der Klasse sind, werden auf der rechten Seite der Definition codiert. Druckbare Zeichen sind in Primzahlen-Einzelzeichenfolgen eingeschlossen. Nicht druckbare und Sonderzeichen können durch ihre numerische Ordnungszahl dargestellt werden. Klassenmitglieder werden durch eine Alternative getrennt Operator. Eine Klassenformel endet mit einem Semikolon. Zeichenklassen können zuvor definierte Klassen enthalten:
/* Character Class Formula class_mask */
bin: '0'|'1'; // 0b00000010
oct: bin|'2'|'3'|'4'|'5'|'6'|'7'; // 0b00000110
dgt: oct|'8'|'9'; // 0b00001110
hex: dgt|'A'|'B'|'C'|'D'|'E'|'F'|'a'|'b'|'c'|'d'|'e'|'f'; // 0b00011110
upr: 'A'|'B'|'C'|'D'|'E'|'F'|'G'|'H'|'I'|'J'|'K'|'L'|'M'|
'N'|'O'|'P'|'Q'|'R'|'S'|'T'|'U'|'V'|'W'|'X'|'Y'|'Z'; // 0b00100000
lwr: 'a'|'b'|'c'|'d'|'e'|'f'|'g'|'h'|'i'|'j'|'k'|'l'|'m'|
'n'|'o'|'p'|'q'|'r'|'s'|'t'|'u'|'v'|'w'|'x'|'y'|'z'; // 0b01000000
alpha: upr|lwr; // 0b01100000
alphanum: alpha|dgt; // 0b01101110
Die skip_class 0b00000001 ist vordefiniert, kann jedoch überlastet sein, um eine skip_class zu definieren.
Zusammenfassend: Eine Zeichenklasse ist eine Liste von Alternativen, die nur eine Zeichenkonstante, eine Ordnungszahl eines Zeichens oder eine zuvor definierte Zeichenklasse sein kann. Wie ich Zeichenklassen implementiert habe: Der Klassenformel wird eine Klassenbitmaske zugewiesen. (In den obigen Kommentaren gezeigt) Jede Klassenformel mit einem Zeichenliteral oder einer Ordnungszahl bewirkt, dass ein Klassenbit zugewiesen wird. Eine Maske wird erstellt, indem die Klassenmaske (n) der enthaltenen Klasse (n) zusammen mit dem zugewiesenen Bit (falls vorhanden) geordnet werden. Aus den Zeichenklassen wird eine Klassentabelle erstellt. Ein durch die Ordnungszahl eines Charakters indizierter Eintrag enthält Bits, die die Klassenmitgliedschaften des Charakters angeben. Klassentests werden inline durchgeführt. Ein IA-86-Codebeispiel mit der Ordnungszahl des Zeichens in eax veranschaulicht das Testen von Klassen:
test byte ptr [eax+_classmap],dgt
Gefolgt von einem:
jne <success>
oder
je <failure>
IA-86-Anweisungscodebeispiele werden verwendet, weil ich denke, dass IA-86-Anweisungen heute bekannter sind. Der Klassenname, der zu seiner Klassenmaske ausgewertet wird, ist zerstörungsfrei UND-verknüpft mit der Klassentabelle, die durch die Ordnungszahl (in eax) indiziert ist. Ein Ergebnis ungleich Null zeigt eine Klassenmitgliedschaft an. (EAX ist auf Null gesetzt, mit Ausnahme von al (den niedrigen 8 Bits von EAX), das das Zeichen enthält).
Tokens waren in diesen alten Compilern etwas anders. Schlüsselwörter wurden nicht als Token erklärt. Sie wurden einfach durch Anführungszeichen in der Parser-Sprache abgeglichen. Anführungszeichen werden normalerweise nicht beibehalten. Modifikatoren können verwendet werden. A + hält die Zeichenfolge übereinstimmend. (dh + '-' stimmt mit einem Zeichen überein, das das Zeichen bei Erfolg beibehält.) Die Operation, (dh 'E') fügt die Zeichenfolge in das Token ein. Leerzeichen werden durch die Tokenformel behandelt, die führende SKIP_CLASS-Zeichen überspringt, bis eine erste Übereinstimmung hergestellt wird. Beachten Sie, dass eine explizite Übereinstimmung mit den Zeichen "skip_class" das Überspringen stoppt, sodass ein Token mit einem Zeichen "skip_class" beginnen kann. Die Zeichenfolgen-Token-Formel überspringt führende skip_class-Zeichen, die mit einem einfachen Anführungszeichen oder einer doppelten Zeichenfolge übereinstimmen. Von Interesse ist die Übereinstimmung eines "Zeichens innerhalb einer" Zeichenfolge in Anführungszeichen:
string .. (''' .ANY ''' | '"' $(-"""" .ANY | """""","""") '"') MAKSTR[];
Die erste Alternative entspricht einem einfachen Anführungszeichen. Die richtige Alternative entspricht einer Zeichenfolge in doppelten Anführungszeichen, die doppelte Anführungszeichen enthalten kann, wobei zwei "Zeichen zusammen verwendet werden, um ein einzelnes" Zeichen darzustellen. Diese Formel definiert die in ihrer eigenen Definition verwendeten Zeichenfolgen. Die innere rechte Alternative '"' $ (-" "" ".ANY |" "" "", "" "") '"' entspricht einer doppelten Anführungszeichenfolge. Wir können ein einfaches Anführungszeichen verwenden, um mit einem doppelten Anführungszeichen übereinzustimmen. Wenn wir jedoch innerhalb der doppelten Anführungszeichenfolge ein Zeichen verwenden möchten, müssen wir zwei Zeichen verwenden, um eines zu erhalten. Zum Beispiel in der inneren linken Alternative, die mit einem beliebigen Zeichen außer einem Zitat übereinstimmt:
-"""" .ANY
Ein negativer Blick voraus - "" "wird verwendet, der bei Erfolg (ohne Übereinstimmung mit einem" Zeichen) mit jedem Zeichen übereinstimmt (das kein "Zeichen" sein kann, weil - "" "diese Möglichkeit beseitigt hat). Die richtige Alternative ist es, "" "" einen Charakter zu finden und zu scheitern, waren die richtige Alternative:
"""""",""""
versucht, zwei "Zeichen, die sie durch ein einzelnes Doppel ersetzen, mit", "" zu vergleichen, um das einzelne "Zeichen einzufügen. Beide inneren Alternativen, bei denen das schließende Zeichen in Anführungszeichen nicht erfüllt ist, werden abgeglichen und MAKSTR [] wird aufgerufen, um ein Zeichenfolgenobjekt zu erstellen Sequenz, Schleife, während erfolgreich, Operator wird zum Abgleichen einer Sequenz verwendet. Token-Formel überspringen führende Zeichen der Sprungklasse (mit Leerzeichen). Sobald eine erste Übereinstimmung hergestellt wurde, ist das Überspringen von skip_class deaktiviert. Mit []. MAKSTR können in anderen Sprachen programmierte Funktionen aufgerufen werden [], MAKBIN [], MAKOCT [], MAKHEX [], MAKFLOAT [] und MAKINT [] werden mit Bibliotheksfunktionen geliefert, die eine übereinstimmende Tokenzeichenfolge in ein typisiertes Objekt konvertieren. Die folgende Zahlenformel veranschaulicht eine recht komplexe Tokenerkennung:
number .. "0B" bin $bin MAKBIN[] // binary integer
|"0O" oct $oct MAKOCT[] // octal integer
|("0H"|"0X") hex $hex MAKHEX[] // hexadecimal integer
// look for decimal number determining if integer or floating point.
| ('+'|+'-'|--) // only - matters
dgt $dgt // integer part
( +'.' $dgt // fractional part?
((+'E'|'e','E') // exponent part
('+'|+'-'|--) // Only negative matters
dgt(dgt(dgt|--)|--)|--) // 1 2 or 3 digit exponent
MAKFLOAT[] ) // floating point
MAKINT[]; // decimal integer
Die obige Zahlentokenformel erkennt Ganzzahl- und Gleitkommazahlen. Die - Alternativen sind immer erfolgreich. Bei Berechnungen können numerische Objekte verwendet werden. Die Token-Objekte werden nach Erfolg der Formel auf den Analysestapel verschoben. Interessant ist die Exponentenführung in (+ 'E' | 'e', 'E'). Wir möchten immer ein Großbuchstabe E für MAKEFLOAT [] haben. Wir erlauben jedoch, dass ein 'e' in Kleinbuchstaben durch 'E' ersetzt wird.
Möglicherweise haben Sie Konsistenzen zwischen Zeichenklasse und Tokenformel festgestellt. Die Parsing-Formel setzt das Hinzufügen von Backtracking-Alternativen und Baumkonstruktionsoperatoren fort. Alternative Operatoren für das Zurückverfolgen und Nicht-Zurückverfolgen dürfen nicht innerhalb einer Ausdrucksebene gemischt werden. Möglicherweise müssen Sie nicht (a | b \ c) Non-Backtracking | mischen mit \ backtracking Alternative. (a \ b \ c), (a | b | c) und ((a | b) \ c) sind gültig. Eine \ backtracking-Alternative speichert den Analysezustand, bevor die linke Alternative versucht wird, und stellt bei einem Fehler den Analysezustand wieder her, bevor die rechte Alternative versucht wird. In einer Folge von Alternativen erfüllt die erste erfolgreiche Alternative die Gruppe. Weitere Alternativen werden nicht versucht. Factoring und Gruppierung sorgen für eine kontinuierliche Analyse. Die Backtrack-Alternative erstellt einen gespeicherten Status der Analyse, bevor sie die linke Alternative versucht. Backtracking ist erforderlich, wenn die Analyse möglicherweise teilweise übereinstimmt und dann fehlschlägt:
(a b | c d)\ e
Im obigen Fall wird bei einem Rückgabefehler die alternative CD versucht. Wenn dann c einen Fehler zurückgibt, wird die Backtrack-Alternative versucht. Wenn a erfolgreich ist und b fehlschlägt, wird die Analyse zurückverfolgt und versucht. Ebenso ist ein fehlgeschlagenes c erfolgreich und b fehlgeschlagen. Die Analyse wird zurückverfolgt und die Alternative e genommen. Das Backtracking ist nicht auf eine Formel beschränkt. Wenn eine Analyseformel zu irgendeinem Zeitpunkt teilweise übereinstimmt und dann fehlschlägt, wird die Analyse auf den oberen Backtrack zurückgesetzt und ihre Alternative gewählt. Ein Kompilierungsfehler kann auftreten, wenn Code ausgegeben wurde und der Backtrack erstellt wurde. Vor dem Start der Kompilierung wird ein Backtrack gesetzt. Das Zurückgeben eines Fehlers oder das Zurückverfolgen ist ein Compilerfehler. Backtracks werden gestapelt. Wir können negativ verwenden - und positiv? Peek / Look-Ahead-Operatoren zum Testen, ohne die Analyse voranzutreiben. Ein String-Test ist ein Blick voraus, bei dem nur der Eingabestatus gespeichert und zurückgesetzt werden muss. Ein Blick nach vorne wäre ein Parsing-Ausdruck, der eine teilweise Übereinstimmung ergibt, bevor er fehlschlägt. Ein Blick nach vorne wird durch Backtracking implementiert.
Die Parser-Sprache ist weder ein LL- noch ein LR-Parser. Aber eine Programmiersprache zum Schreiben eines rekursiven anständigen Parsers, in dem Sie die Baumkonstruktion programmieren:
:<node name> creates a node object and pushes it onto the node stack.
.. Token formula create token objects and push them onto
the parse stack.
!<number> pops the top node object and top <number> of parstack
entries into a list representation of the tree. The
tree then pushed onto the parse stack.
+[ ... ]+ creates a list of the parse stack entries created
between them:
'(' +[argument $(',' argument]+ ')'
could parse an argument list. into a list.
Ein häufig verwendetes Parsing-Beispiel ist ein arithmetischer Ausdruck:
Exp = Term $(('+':ADD|'-':SUB) Term!2);
Term = Factor $(('*':MPY|'/':DIV) Factor!2);
Factor = ( number
| id ( '(' +[Exp $(',' Exp)]+ ')' :FUN!2
| --)
| '(' Exp ')" )
(^' Factor:XPO!2 |--);
Exp und Term mit einer Schleife erstellen einen Baum für Linkshänder. Der Faktor, der die rechte Rekursion verwendet, erzeugt einen rechtshändigen Baum:
d^(x+5)^3-a+b*c => ADD[SUB[EXP[EXP[d,ADD[x,5]],3],a],MPY[b,c]]
ADD
/ \
SUB MPY
/ \ / \
EXP a b c
/ \
d EXP
/ \
ADD 3
/ \
x 5
Hier ist ein Teil des cc-Compilers, einer aktualisierten Version von SLIC mit Kommentaren im c-Stil. Funktionstypen (Grammatik, Token, Zeichenklasse, Generator, PSEUDO oder MACHOP) werden durch ihre anfängliche Syntax nach ihrer ID bestimmt. Mit diesen Top-Down-Parsern beginnen Sie mit einer programmdefinierenden Formel:
program = $((declaration // A program is a sequence of
// declarations terminated by
|.EOF .STOP) // End Of File finish & stop compile
\ // Backtrack: .EOF failed or
// declaration long-failed.
(ERRORX["?Error?"] // report unknown error
// flagging furthest parse point.
$(-';' (.ANY // find a ';'. skiping .ANY
| .STOP)) // character: .ANY fails on end of file
// so .STOP ends the compile.
// (-';') failing breaks loop.
';')); // Match ';' and continue
declaration = "#" directive // Compiler directive.
| comment // skips comment text
| global DECLAR[*1] // Global linkage
|(id // functions starting with an id:
( formula PARSER[*1] // Parsing formula
| sequencer GENERATOR[*1] // Code generator
| optimizer ISO[*1] // Optimizer
| pseudo_op PRODUCTION[*1] // Pseudo instruction
| emitor_op MACHOP[*1] // Machine instruction
) // All the above start with an identifier
\ (ERRORX["Syntax error."]
garbol); // skip over error.
// Beachten Sie, wie die ID beim Erstellen des Baums berücksichtigt und später kombiniert wird.
formula = ("==" syntax :BCKTRAK // backtrack grammar formula
|'=' syntax :SYNTAX // grammar formula.
|':' chclass :CLASS // character class define
|".." token :TOKEN // token formula
)';' !2 // Combine node name with id
// parsed in calling declaration
// formula and tree produced
// by the called syntax, token
// or character class formula.
$(-(.NL |"/*") (.ANY|.STOP)); Comment ; to line separator?
chclass = +[ letter $('|' letter) ]+;// a simple list of character codes
// except
letter = char | number | id; // when including another class
syntax = seq ('|' alt1|'\' alt2 |--);
alt1 = seq:ALT!2 ('|' alt1|--); Non-backtrack alternative sequence.
alt2 = seq:BKTK!2 ('\' alt2|--); backtrack alternative sequence
seq = +[oper $oper]+;
oper = test | action | '(' syntax ')' | comment;
test = string | id ('[' (arg_list| ,NILL) ']':GENCALL!2|.EMPTY);
action = ':' id:NODE!1
| '!' number:MAKTREE!1
| "+[" seq "]+" :MAKLST!1;
// C style comments
comment = "//" $(-.NL .ANY)
| "/*" $(-"*/" .ANY) "*/";
Bemerkenswert ist, wie die Parser-Sprache mit Kommentaren und der Fehlerbehebung umgeht.
Ich glaube, ich habe die Frage beantwortet. Nachdem er einen großen Teil des SLIC-Nachfolgers geschrieben hat, ist die CC-Sprache an sich hier. Es gibt noch keinen Compiler dafür. Aber ich kann es von Hand in Assembler-Code kompilieren, nackte asm c- oder c ++ - Funktionen.