Forum: PC-Programmierung Konstruktion einer kontextfreien Grammatik


von Andreas W. (avedo)


Angehängte Dateien:

Lesenswert?

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

von Karl H. (kbuchegg)


Lesenswert?

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.

: Wiederhergestellt durch User
von Andreas W. (avedo)


Lesenswert?

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.

von Karl H. (kbuchegg)


Lesenswert?

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.

von (prx) A. K. (prx)


Lesenswert?

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.

von Simon B. (nomis)


Lesenswert?

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

von Simon B. (nomis)


Lesenswert?

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

von (prx) A. K. (prx)


Lesenswert?

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.

von (prx) A. K. (prx)


Lesenswert?

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 ;-).

von Andreas W. (avedo)


Lesenswert?

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

von Simon B. (nomis)


Lesenswert?

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

von (prx) A. K. (prx)


Lesenswert?

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.

von Andreas W. (avedo)


Lesenswert?

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

von (prx) A. K. (prx)


Lesenswert?

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?

von Andreas W. (avedo)


Lesenswert?

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.
Bestehender Account
Schon ein Account bei Google/GoogleMail? Keine Anmeldung erforderlich!
Mit Google-Account einloggen
Noch kein Account? Hier anmelden.