Hallo!
Ich arbeite mich zur Zeit in das Thema Compilerbau ein und habe mir zu
diesem Zweck das Dragon Book (Compiler) von Alfred V. Aho, Monica S.
Lam, Ravi Sethi und Jeffrey D. Ullman zugelegt. Dieses arbeite ich
gerade durch und konstruiere zur Zeit eine komplexere kontextfreie
Grammatik, die eine einfache Sprache erkennen soll. Das ist natürlich
nur eine kleine Übung sein.
Ich baue dabei auf eine einfache Grammatik auf, die im Rahmen dieses
Exkurses vorgestellt wird. Bisher habe ich die Grammatik um einen
Modulo-Operator, die logischen Operatoren "|", "^" und "&", die
zusammengesetzten Zuweisungsoperatoren ("*=", "%=" usw.), Post- und Pre-
Increment- und Decrement-Operatoren, eine for-Schleife, ein
switch-case-Konstrukt, sowie um Funktionsaufrufe und die Befehle DUMP,
REUTRN und EXIT erweitert. Leider bin ich mir an einigen Stellen, wie
zum Beispiel bei der Konstruktion des switch-case-Konstrukts, nichts
sicher ob dies so korrekt ist.
Überhaupt keine Ahnung habe ich, wie und wo ich in der Grammatik das
Typecasting, den Trinären-Operator "(bool) ? (bool) : (bool)", einen
sizeof()-Operator, die Deklaration eingener Funktionen, sowie eine
Fehlerbehandlung mit REPORT oder THROW einbauen muss.
Da diese Grammatik auch die Deklaration von Feldern ermöglicht, wäre es
natürlich als kleines Bonbon noch sehr interessant, wie man Operatoren
auf Mengen, wie union und intersect noch mit reinnehmen könnte, oder ob
man diese auch einfach unter Funktionsaufrufe steckt und deren Namen und
Operatoranzahl in einer Tabelle nachschlagen lässt. So würde ich es mit
allen nicht elementaren Funktionen der von der Grammatik akzeptierten
Sprache machen. Das bedeutet sie kommen in der Grammatik einfach nicht
vor.
Wäre dies ein übliches vorgehen? Wie sollte man sonst den später
entstehenden Compiler erweitern, ohne überall im Quellcode herumpfuschen
zu müssen.
Würde mich sehr freuen, wenn sich jemand finden würde, der sich mit
diesem Thema auskennt und Lust hat mir etwas unter die Arme zu greifen.
Habe schon in einem anderen Forum gefragt und wurde nett darauf
hingewiesen, dass der dort anzutreffende Programmierer-Mainstream
(tolles Wortkonstrukt) davon nur wenig bis keine Ahnung hat.
Vielen Dank im Voraus.
Liebe Grüße,
Andreas
Ich habe mir am leichtesten getan, wenn ich Grammatiken gleich als LL(1)
Grammatiken aufgebaut habe.
So was
expr -> expr + term
| expr - term
| term
ist nämlich IMHO nicht zu implementieren und nur als theoretisches
Konstrukt brauchbar. Du hast nichts in der Hand was deinen Parser leiten
würde. Welche der 3 Alternativen ist denn die richtige?
Deine Grammatik ist super, wenn es darum geht unter einen Satz die
entsprechenden terminalen Symbole zu schreiben und sich dann von dort
aus bottom-up bis zum obersten Symbol durchzutasten. Aber die Umkehrung,
einen Parser zu schreiben, der sich vom obersten Symbol zu den
terminalen Symbolen durchtasten muss und Übereinstimmung mit den im Satz
vorliegenden Symbolen festzustellen - hmmmmm
Eine implementierbare Grammatik für Expression sieht zb in EBNF so aus
expr -> term { ( "+" term ) | ( "-" term ) } .
term -> factor { ( "*" factor ) | ( "/" factor ) } .
factor -> ["-"] ( identifier
| number
| "sqrt" "(" expr ")"
) .
Mit deiner Grammatik könnte ich nicht viel anfangen, die in ein Programm
umzusetzen ... ich wüsste nicht recht wie ich das machen soll, ohne
ständig in Endlosrekursionen zu laufen.
Muss allerdings auch dazu sagen, dass meine Compilerbauvorlesungen schon
lange her sind.
Guten Abend!
Vielen Dank für deine Antwort.
Das Umformen meiner Grammatik zu einer LL(1) oder zumindest einer
rechtsseitigen ableitenden Grammatik ist ja möglich. Dazu kann man die
Tabelle der First- und Follow-Mengen der einzelnen Nichtterminale
aufstellen. Diese können auch dabei helfen eine Sprache, wie sie von mir
konstruiert wurde korrekt zu parsen. Endlosschleifen können außerdem
recht einfach durch die richtige Verwendung des EPSILON Terminals, des
leeren Wortes, vermieden werden.
In deinem Beispiel sehe ich leider noch kein Unterschied zu meiner
Grammatik oder soll dass meine in EBNF sein?
Gibt es eigentlich eine Möglichkeit zu beweisen, dass eine Grammatik das
tut, was sie tun soll, nämlich eine bestimmte Sprache erkennt. Kann mich
nicht an einen konkreten Beweis erinnern.
Liebe Grüße,
Andreas
PS: Werde heute oder morgen kurz die Regeln zur Umformung erläutern und
auf meine Grammatik anwenden.
EDIT: Entschuldigt der Beitrag meines Vorredners wurde gelöscht.
Andreas Wilhelm schrieb:
> EDIT: Entschuldigt der Beitrag meines Vorredners wurde gelöscht.
Ich wollte ihn eigentlich zurückziehen, weil ich mir nicht sicher bin,
ob ich dir überhaupt damit helfen kann.
Der Punkt ist der: Deine Grammatik ist eine schöne Übung, aber wie
gesagt, zur Verwendung in einem Parser IMHO so nicht zu gebrauchen. D.h.
ich denke du machst dir hier zuviel Arbeit.
Auf der anderen Seite hab ich schon lange nichts anderes als LL(1)
Grammatiken mehr gebaut. D.h. ich bin mit allem anderen reichlich aus
der Übung.
Karl heinz Buchegger schrieb:
> expr -> expr + term
> | expr - term
> | term
>
> ist nämlich IMHO nicht zu implementieren und nur als theoretisches
> Konstrukt brauchbar.
Diese linksrekursive Ausdrucksweise ist aber bei den Parser-Generatoren
wie Yacc allgemein verbreitet, um nicht zu sagen geradezu typisch.
Solange es sich im den gleichen Präfix handelt (hier "expr") entsteht
kein Problem, weil der Parser sich noch garnicht für eine bestimmte
Alternative entscheiden muss.
http://www.cs.man.ac.uk/~pjj/cs212/ho/node5.html#SECTION00052000000000000000
Beim recursive descent parser sieht das freilich etwas anders aus.
Andreas Wilhelm schrieb:
> Gibt es eigentlich eine Möglichkeit zu beweisen, dass eine Grammatik das
> tut, was sie tun soll, nämlich eine bestimmte Sprache erkennt. Kann mich
> nicht an einen konkreten Beweis erinnern.
Sowas gibt es durchaus, allerdings in der (Uni-)Praxis nur an mehr oder
weniger kleinen Trivialbeispielen. Bei allen größeren Sachen (wie z.B.
etwas was schon halbwegs wie eine Programmiersprache aussieht) hat man
ruck-zuck das Problem, wie man denn nun definiert, was man überhaupt
erkennen möchte. Wenn man das nicht über eine Grammatik machen kann (das
will man ja letztlich beweisen) dann gehen einem da sehr schnell die
Mittel aus.
Für Sprachen wie a^n b^n etc. besteht die Grammatik aus zwei bis drei
Regeln, da hat man das schnell bewiesen, d.h. man entwickelt eine
Konstruktionsvorschrift, die jedem Wort dieser Form eine Ableitungsfolge
zuordnet und eben nachweist, dass diese Ableitungsfolge genau jenes Wort
konstruiert. Umgekehrt muss man natürlich auch noch beweisen, dass jede
(endliche) Ableitungsfolge die zur Grammatik passt in einem Wort der
entsprechenden Sprache resultiert.
Aber beschreib mal alle gültigen C-Programme in einer Form, die sich
nicht auf einer Grammatik abstützt. Das kannst Du nicht sinnvoll machen,
d.h. man bekommt nichtmal ordentlich eine Behauptung hingeschrieben, die
man dann beweisen kann.
Zu Deiner Grammatik: Die ist unnötig kompliziert, weil Du viele der
Regeln zusammenfassen könntest, indem Du mehr Arbeit auf den (regulären)
Tokenizer abwälzt. So kannst Du z.B. die Relations-Regeln (warum hast Du
die überhaupt in "equality" und "rel" unterteilt?) zu einer
zusammenfassen, die in der Mitte einen RELOP enthält. Es ist dann
Aufgabe des lexing-Schritts, ">=", "<=", "==" etc. als RELOP zu
erkennen. Später im Syntaxbaum kannst Du dann an einem Attribut des
RELOP erkennen, welche Relation an dieser Stelle gemeint war.
Damit werden es nicht nur erheblich weniger Regeln, sondern es wird auch
klarer ersichtlich, welche Präzedenz Du bei Deinen Operatoren hast.
Viele Grüße,
Simon
A. K. schrieb:
> Diese linksrekursive Ausdrucksweise ist aber bei den Parser-Generatoren
> wie Yacc allgemein verbreitet, um nicht zu sagen geradezu typisch.
> Solange es sich im den gleichen Präfix handelt (hier "expr") entsteht
> kein Problem, weil der Parser sich noch garnicht für eine bestimmte
> Alternative entscheiden muss.
[...]
> Beim recursive descent parser sieht das freilich etwas anders aus.
Nunja, es ist aber eine Standard-Technik, linksrekursive Grammatiken
entsprechend umzuformen, dass die Linksrekursion aufgelöst wird. Das ist
genau das was lex und yacc machen.
Für einen recursive descent parser kann man das natürlich von Hand
machhen. Ok, es lässt sich natürlich drüber streiten, ob das noch die
"gleiche" Grammatik ist, das ist aber eher eine philosophische Frage: es
ist sicher, dass die gleiche Sprache erkannt wird.
Viele Grüße,
Simon
Andreas Wilhelm schrieb:
> Gibt es eigentlich eine Möglichkeit zu beweisen, dass eine Grammatik das
> tut, was sie tun soll, nämlich eine bestimmte Sprache erkennt. Kann mich
> nicht an einen konkreten Beweis erinnern.
Für einen solchen Beweis benötigst du eine formale Sprachdefinition,
also eine Grammatik. Es läuft folglich darauf hinaus, die Äquivalenz
zweier Grammatiken zu beweisen.
Simon Budig schrieb:
> Nunja, es ist aber eine Standard-Technik, linksrekursive Grammatiken
> entsprechend umzuformen, dass die Linksrekursion aufgelöst wird. Das ist
> genau das was lex und yacc machen.
Nur gibt es keinen Grund, sich eigens zu verknoten um eine rekursive
Form zu vermeiden, wenn das der Parser-Generator nicht nur selber
aufdröseln kann, sondern diese Formulierung geradezu erzwingt.
Bei Yacc/Bison wird die in EBNF mit { ... } dargestellte
Wiederholungform eben gerade per links/rechtsrekursiver Formulierung
dargestellt. Man also die KHB'sche Form erst wieder in die passende
rekursive Darstellung überführen muss - und dabei noch drauf achten
muss, dass die optisch naheliegende Rechtsrekursion hier die falsche
wäre.
Insofern hängt die sinnvolle Spezifikation also davon ab, mit welchem
Werkzeug man arbeitet. Bei Yacc/Bison rekursiv, bei recursive descent
nicht (auch wenn's sprachlich naheläge ;-).
Hallo zusammen!
Erst einmal vielen Dank für die zahlreichen Antworten.
Simon Budig schrieb:
> Für Sprachen wie a^n b^n etc. besteht die Grammatik aus zwei bis drei
> Regeln, da hat man das schnell bewiesen ...
Das kenne ich tatsächlich auch aus Vorlesungen, wie Info II,
Theoretische Informatik oder Diskrete Mathematik. Wirklich helfen tut
soetwas leider nicht wirklich, wenn man mal eine wirklich umfangreiche
Grammatik vor sich hat. Da hast du wohl Recht.
Simon Budig schrieb:
> Zu Deiner Grammatik: Die ist unnötig kompliziert, weil Du viele der
> Regeln zusammenfassen könntest, ... So kannst Du z.B. die Relations-
> Regeln ... zu einer zusammenfassen, die in der Mitte einen RELOP
> enthält.
Das werde ich tun. Vielen Dank für diesen Tipp. Ich werde die neue
Grammatik dann in nächster Zeit hier präsentieren.
Karl heinz Buchegger schrieb:
> Eine implementierbare Grammatik für Expression sieht zb in EBNF so aus
Die nächste Grammatik werde ich auch in EBNF, wie sie YACC und Co.
verwenden, vorstellen.
Interessieren würde mich nun noch, wie ich in der alten Grammatik
Funktionen und die Definition eigener Funktionen unterbringe. Erfasse
ich in meiner Grammatik einfach eine Tokenfolge "ID LBRACKET params
RBRACKET" und schaue dann im Schritt des Lexers, wie es auch Simon Budig
für die Vergleichsoperatoren vorgeschlagen hat, den Namen und die damit
verbundene Parameteranzahl nach?
Freue mich auf weitere Anregungen und Hilfestellungen.
Liebe Grüße,
Andreas
Andreas Wilhelm schrieb:
> Interessieren würde mich nun noch, wie ich in der alten Grammatik
> Funktionen und die Definition eigener Funktionen unterbringe. Erfasse
> ich in meiner Grammatik einfach eine Tokenfolge "ID LBRACKET params
> RBRACKET" und schaue dann im Schritt des Lexers, wie es auch Simon Budig
> für die Vergleichsoperatoren vorgeschlagen hat, den Namen und die damit
> verbundene Parameteranzahl nach?
Ja. Sobald der Benutzer selber Namen wählen kann geht das normalerweise
über die reine Grammatik hinaus. Dann legst Du nur noch die
syntaktischen Regeln für die Identifier fest (also z.B. welche
Buchstaben man dafür verwenden darf).
In dem Syntaxbaum, den der Parser aufbaut, werden dann die Lexeme (also
das, was durch den Lexer in ein Token für den Parser gewandelt wurde)
mitgespeichert. Dadurch kann man dann beim Auswerten des Syntaxbaums bei
einem Funktionsaufruf feststellen, wie die aufgerufene Funktion denn
hieß und dann entsprechend in einer Symboltabelle prüfen, ob diese
Funktion existiert und sie ggf. ausführen.
(An dieser Stelle verlässt die Korrektheit eines Programmtexts übrigens
die Ebene der "Kontextfreiheit" - nur weil der Parser das Programm
korrekt ableiten kann muss es nicht korrekt sein, z.B. weil eine
Funktion aufgerufen wird, die nicht definiert wurde.)
Viele Grüße,
Simon
Andreas Wilhelm schrieb:
> Interessieren würde mich nun noch, wie ich in der alten Grammatik
> Funktionen und die Definition eigener Funktionen unterbringe. Erfasse
> ich in meiner Grammatik einfach eine Tokenfolge "ID LBRACKET params
> RBRACKET" und schaue dann im Schritt des Lexers, wie es auch Simon Budig
> für die Vergleichsoperatoren vorgeschlagen hat, den Namen und die damit
> verbundene Parameteranzahl nach?
Ich habe Simon so verstanden, dass er den Lexer dazu bringen will, ein
einziges Token für alle Vergleichsoperatoren gleicher Priorität
abzuliefern, dessen Wert der exakte Operator ist, um die Grammatik zu
vereinfachen. Das ist sinnvoll.
Bei einem Symbol kann es zwar durchaus sinnvoll sein, wenn der Lexer
bereits einen Verweis auf die Symboltabelle liefert, sofern das nicht in
Konflikt mit disjunkten Namespaces gerät, aber ein Parser wird
üblicherweise die Parameter einer Funktion erst alle einsammeln und
allenfalls abschliessend feststellen, ob die Anzahl aktueller Parameter
mit der Deklaration zusammenpasst. Syntaktisch ist es also zulässig,
mehr oder weniger Parameter anzugeben, als für die Funktion deklariert
sind.
Hallo!
Danke für die Antworten.
A. K. schrieb:
> Ich habe Simon so verstanden, dass er den Lexer dazu bringen will, ein
> einziges Token für alle Vergleichsoperatoren gleicher Priorität
> abzuliefern, dessen Wert der exakte Operator ist, um die Grammatik zu
> vereinfachen. Das ist sinnvoll.
Das habe ich auch so verstanden, die Frage war ja nur, ob man ein
ähnliches Verfahren für Standard-Funktionen der Sprache verwenden kann.
Allerdings hast du natürlich Recht mit deinem Einwand bezüglich eines
auftretenden Fehlers bei der Überprüfung der Parameteranzahl. Ein
solcher Fehler tritt im Parser und nicht im Lexer auf.
Simon Budig schrieb:
> Sobald der Benutzer selber Namen wählen kann geht das normalerweise
> über die reine Grammatik hinaus.
Wie gesagt geht es mir momentan nicht um durch den Benutzer definierte
Fuktionen, sondern um Standard-Funktionen, die Teil der Sprache sind.
Wobei sich in diesem Zusammenhang natürlich auch die Frage stellt, ob
diese nicht zusammengefasst werden könnten. Der Aufruf müsste dann ja
die folgende Form haben:
1 | call -> ID ( optparams )
| 2 | | factor
| 3 | optparams -> params
| 4 | | EPSILON
| 5 | params -> params , bool
| 6 | | bool
|
Welche Form muss aber eine Funktionsdeklaration erfüllen?
1 | calldef -> ID ( optdefparams ) ASSIGN bool ;
| 2 | | factor
| 3 | optdefparams -> defparams
| 4 | | EPSILON
| 5 | defparams -> defparams , ID
| 6 | | ID
|
Wäre das so richtig und falls ja, wo sollte ich das einbauen?
Simon Budig schrieb:
> ... warum hast Du die überhaupt in "equality" und "rel" unterteilt?
Hier noch ein kurzer Nachtrag. Die Operatoren in equality und rel haben
eine unterschiedliche Priorität - daher diese Unterteilung.
Bezüglich der Priorität hätte ich auch noch eine Frage. Ich habe mich an
einer Tabelle orientiert, die die Priorisierung der Operatoren in C
erklärt
(http://de.wikibooks.org/wiki/C-Programmierung:_Liste_der_Operatoren_nach_Priorität).
Leider ist mir nicht ganz klar, wieso Präfix- und Postfix-Inkrement die
gleiche Prioriät besitzen. Das ist meiner Meinung nach nicht wirklich
logisch.
Liebe Grüße,
Andreas
Andreas Wilhelm schrieb:
> Wäre das so richtig
Ja.
> Leider ist mir nicht ganz klar, wieso Präfix- und Postfix-Inkrement die
> gleiche Prioriät besitzen. Das ist meiner Meinung nach nicht wirklich
> logisch.
Wo ist das Problem?
Hallo!
A. K. schrieb:
> Wo ist das Problem?
Das Frage ich mich auch gerade. Hatte mich etwas an der Tatsache
gestört, dass einmal vor allen anderen Aktionen der Wert erhöht wird und
einmal danach.
A. K. schrieb:
>> Wäre das so richtig
>
> Ja.
Das ist doch schonmal gut,wobei mir gerade auffällt, dass es wie folgt
richtiger wäre.
1 | calldef -> ID ( optdefparams ) block ;
| 2 | | bool
| 3 | optdefparams -> defparams
| 4 | | EPSILON
| 5 | defparams -> defparams , ID
| 6 | | ID
|
calldef sollte zur Zuweisung kein Gleichheitszeichen verwenden, sondern
es sollte einfach ein Block folgen. Außerdem sollte calldef wenn
überhaupt auf bool und nicht auf factor zeigen. Die Frage ist nun, wo
man es in der Grammatik einfügen müsste. Macht es nicht Sinn, es einfach
gleich an den Anfang einzubauen? In diesem Fall sollte allerdings die
Alternative bool wegfallen. Nachfolgend ein kleines Beispiel:
1 | prgm -> calldefs
| 2 | calldefs -> calldefs calldef
| 3 | | calldef
| 4 | calldef -> ID ( optdefparams ) block
| 5 | optdefparams -> defparams
| 6 | | EPSILON
| 7 | defparams -> defparams , ID
| 8 | | ID
|
Auf diese Weise wäre alles funktionsbasiert und man könnte, ähnlich wie
in C, einfach beim Parsen nach einer main-Funktion suchen, die dann
ausgeführt wird.
Sollte ich das so in meine Grammatik, die ich noch einmal umgestellt,
verkürzt und korrigiert in den Anhang gestellt habe, stellt sich mir
noch die Frage, wie und wo ich Include-Befehle und Bedingungen (a ? b :
c) einbauen kann. Außerdem bleibt noch die Frage, ob mein Konstrukt für
eine Switch-Case-Anweisung korrekt ist.
Meiner Meinung nach müssten Includes in das obige Beispiel wie folgt
eingepflegt werden.
1 | prgm -> includes calldefs
| 2 | includes -> includes include
| 3 | | EPSILON
| 4 | include -> INCLUDE " STRING ";
| 5 | calldefs -> calldefs calldef
| 6 | | EPSILON
| 7 | calldef -> ID ( optdefparams ) block
| 8 | optdefparams -> defparams
| 9 | | EPSILON
| 10 | defparams -> defparams , ID
| 11 | | ID
|
Würde mich über weitere Rückmeldungen, Korrekturen und Anregungen
Freuen.
Vielen Dank und liebe Grüße,
Andreas
\EDIT: Konnte Datei leider nicht anhängen, daher hier im Klartext. 1 | G := (N, T, P, S)
| 2 |
| 3 | S:= prgm
| 4 |
| 5 | T := {
| 6 | "{",
| 7 | "}",
| 8 | CONST,
| 9 | EPSILON,
| 10 | ID,
| 11 | NUMBER,
| 12 | "[",
| 13 | "]",
| 14 | BASIC,
| 15 | "||",
| 16 | "&&",
| 17 | EQUOP,
| 18 | RELOP,
| 19 | ASSIGN,
| 20 | ";",
| 21 | IF,
| 22 | "(",
| 23 | ")",
| 24 | ELSE,
| 25 | WHILE,
| 26 | DO,
| 27 | SWITCH,
| 28 | BREAK,
| 29 | RETURN,
| 30 | STRING,
| 31 | DEFAULT,
| 32 | CASE,
| 33 | "|",
| 34 | "^",
| 35 | "&",
| 36 | "<<",
| 37 | ">>",
| 38 | "+",
| 39 | "-",
| 40 | "*",
| 41 | "/",
| 42 | "%",
| 43 | "~",
| 44 | "!",
| 45 | "^",
| 46 | ",",
| 47 | TRUE,
| 48 | FALSE
| 49 | }
| 50 |
| 51 | N := {
| 52 | prgm,
| 53 | block,
| 54 | decls,
| 55 | const,
| 56 | decl,
| 57 | type,
| 58 | stmts,
| 59 | stmt,
| 60 | loc,
| 61 | optexpr,
| 62 | cases,
| 63 | bool,
| 64 | join,
| 65 | bitor,
| 66 | bitxor,
| 67 | bitand,
| 68 | equality,
| 69 | rel,
| 70 | shift,
| 71 | expr,
| 72 | term,
| 73 | unary,
| 74 | call,
| 75 | optparams,
| 76 | params,
| 77 | factor
| 78 | }
| 79 |
| 80 | P := {
| 81 | prgm -> block
| 82 | block -> { decls stmts }
| 83 | decls -> decls const
| 84 | | EPSILON
| 85 | const -> CONST decl
| 86 | | decl
| 87 | decl -> type ID;
| 88 | type -> type [NUMBER]
| 89 | | BASIC
| 90 | stmts -> stmts stmt
| 91 | | EPSILON
| 92 | stmt -> loc ASSIGN bool ;
| 93 | | IF ( bool ) stmt
| 94 | | IF ( bool ) stmt ELSE stmt
| 95 | | WHILE ( bool ) stmt
| 96 | | DO stmt WHILE ( bool ) ;
| 97 | | FOR ( optexpr ; optexpr ; optexpr ) stmt
| 98 | | SWITCH ( bool ) cases
| 99 | | BREAK ;
| 100 | | RETURN bool ;
| 101 | | block
| 102 | loc -> loc [ bool ]
| 103 | | ID
| 104 | optexpr -> EPSILON
| 105 | | bool
| 106 | cases -> cases DEFAULT stmt
| 107 | | cases CASE bool : stmt
| 108 | | CASE stmt
| 109 | bool -> bool || join
| 110 | | join
| 111 | join -> join && bitor
| 112 | | bitor
| 113 | bitor -> bitor | bitxor
| 114 | | bitxor
| 115 | bitxor -> bitxor ^ bitand
| 116 | | bitand
| 117 | bitand -> bitand & equality
| 118 | | equality
| 119 | equality -> equality EQUOP rel
| 120 | | rel
| 121 | rel -> bitor RELOP shift
| 122 | | shift
| 123 | shift -> shift << expr
| 124 | | shift >> expr
| 125 | | expr
| 126 | expr -> expr + term
| 127 | | expr - term
| 128 | | term
| 129 | term -> term * unary
| 130 | | term / unary
| 131 | | term % unary
| 132 | | unary
| 133 | unary -> + unary
| 134 | | - unary
| 135 | | ! unary
| 136 | | ~ unary
| 137 | | ++ unary
| 138 | | -- unary
| 139 | | unary ++
| 140 | | unary --
| 141 | | ( BASIC ) unary
| 142 | | SIZEOF BASIC unary
| 143 | | SIZEOF ID unary
| 144 | | call
| 145 | call -> ID ( optparams )
| 146 | | factor
| 147 | optparams -> params
| 148 | | EPSILON
| 149 | params -> params , bool
| 150 | | bool
| 151 | factor -> ( bool )
| 152 | | loc
| 153 | | NUMBER
| 154 | | STRING
| 155 | | TRUE
| 156 | | FALSE
| 157 | }
|
Bitte melde dich an um einen Beitrag zu schreiben. Anmeldung ist kostenlos und dauert nur eine Minute.
|