====== Parser pro popis gramatiky a uložení gramatiky do stromu =====
http://github.com/zdenekzc/plain-grammar/blob/main/plain_grammar.py
Na vstupu bude nějaká gramatika, například:
while_stat : "while" "(" expr ")" stat ;
if_stat : "if" "(" expr ")" stat ( "else" stat )? ;
compound_stat : "{" ( stat )* "}" ;
simple_stat : expr ";" ;
empty_stat : ";" ;
stat : while_stat | if_stat | compound_stat | simple_stat | empty_stat ;
( Tento vstup představující několik gramatických pravidel by měl vyhovovat již popsané gramatice
[[prekl::gram|gramatika popisující gramatiku]] )
Jednotlivá pravidla chceme přečíst a uložit do paměti počítače.
===== Třídy pro uložení gramatiky =====
Několik tříd pro ukládání informací:
class Rule (object) :
def __init__ (self) :
self.name = ""
self.expr = None
class Expression (object) :
def __init__ (self) :
self.alternatives = [ ]
class Alternative (object) :
def __init__ (self) :
self.items = [ ]
class Ebnf (object) :
def __init__ (self) :
self.mark = ""
self.expr = None
class Nonterminal (object) :
def __init__ (self) :
self.rule_name = ""
class Terminal (object) :
def __init__ (self) :
self.text = ""
self.multiterminal_name = ""
===== Parser popisu gramatik =====
class Grammar (Lexer) :
def __init__ (self) :
super (Grammar, self).__init__ ()
self.rules = [ ]
self.multiterminals = [ "identifier",
"number",
"real_number",
"character_literal",
"string_literal" ]
def setPosition (self, item) :
item.src_line = self.tokenLineNum
item.src_column = self.tokenColNum
item.src_pos = self.tokenByteOfs
=== Pravidla ===
Dokud neskončí zdrojový text, čteme jednotlivá pravidla.
def parseRules (self) :
while not self.isEndOfSource () :
self.parseRule ()
Informace o jednotlivých pravidlech uložíme do instance třídy **Rule**. \\
Přečteme ze vstupu jmého pravidla, \\
zkontrolujeme dvojtečku, \\
popis za dvojtčkou přečte funkce **parseExpression**, \\
zkontrolujeme středník.
def parseRule (self) :
rule = Rule ()
rule.name = self.readIdentifier ("Rule identifier expected")
self.setPosition (rule)
self.checkSeparator (':')
rule.expr = self.parseExpression ()
self.checkSeparator (';')
self.rules.append (rule)
=== Výrazy - popisy pravidel ===
Popis pravidla ukládáme do třídy **Expression**. \\
Přečteme jednu alternativu a vložíme do seznamu **expr.alternatives**. \\
Pokud následuje **|**, čteme další alternativy a ukládáme je do seznamu.
def parseExpression (self) :
expr = Expression ()
self.setPosition (expr)
alt = self.parseAlternative ()
expr.alternatives.append (alt)
while self.isSeparator ('|') :
self.nextToken ()
alt = self.parseAlternative ()
expr.alternatives.append (alt)
return expr
=== Alternativy ===
Čteme jednotlivé položky, dokud nenarazíme na svislou čáru, pravou závorku nebo středník.
Alternativy jsou posloupnosti identifikátorů (neterminálních symbolů), \\
řetězců v uvozovkách (terminálních symbolů) \\
nebo výrazů v závorkách.
Alternativa může být i prázdná.
Jednotlivé položky ukládáme do seznamu **items** ve třídě **Alternative**.
def parseAlternative (self) :
alt = Alternative ()
self.setPosition (alt)
while not self.isSeparator ('|') and not self.isSeparator (')') and not self.isSeparator (';') :
if self.token == self.identifier :
if self.tokenText in self.multiterminals :
item = self.parseMultiterminal ()
else :
item = self.parseNonterminal ()
alt.items.append (item)
elif self.token == self.character_literal or self.token == self.string_literal :
item = self.parseTerminal ()
alt.items.append (item)
elif self.isSeparator ('(') :
ebnf = self.parseEbnf ()
alt.items.append (ebnf)
else :
self.error ("Unknown grammar item")
return alt
=== Výrazy v závorkách ===
Zkontrolujeme a přeskočíme levou závorku. \\
Přečteme popis uvnitř závorek, rekurzivně použijeme již zmíněnou funkci **parseExpression**. \\
Zkontrolujeme a přeskočíme pravou závorku.
//Tj. stejný postup jako u aritmetických výrazů.//
Pokud za závorkou je otazník, plus nebo hvězdička tak také tuto značku přečteme.
def parseEbnf (self) :
item = Ebnf ()
self.setPosition (item)
self.checkSeparator ('(')
item.expr = self.parseExpression ()
self.checkSeparator (')')
if self.isSeparator ('?') or self.isSeparator ('+') or self.isSeparator ('*') :
item.mark = self.tokenText
self.nextToken ()
return item
=== Terminály a neterminály ===
Neterminální symboly - jméno uložime do třídy **Nonterminal**.
Terminální symboly - text uvnitř uvozovek uložime do třídy **Terminal**.
def parseNonterminal (self) :
item = Nonterminal ()
self.setPosition (item)
item.rule_name = self.tokenText
self.nextToken ()
return item
def parseTerminal (self) :
item = Terminal ()
self.setPosition (item)
item.text = self.tokenText
self.nextToken ()
return item
=== Multi-terminály ===
Moje nestandardní pojmenování pro terminální symboly, které představují celé skupiny terminálních symbolů.
Jsou to identifikátory, celá a desetinná čísla, řetězce v jednoduchých a dovojitých uvozovkách.
* identifier
* number
* real_number
* character_literal
* string_literal
Například v pravidle
simple_expr : identifier | number | "(" expr ")" ;
* simple_expr a expr jsou neterminální symboly
* "(" a ")" jsou terminální symboly
* identifier a number jsou multi-terminály ( a také současně terminální symboly )
// Jelikož se zapisují pomocí identifikátorů bez uvozovek, tak připomínají neterminály. //
// Tyto skupinové terminální symboly potřebujeme, aby množiny terminálních symbolů byly konečné. // \\
// ( Množina symbolů z kterých se vytváří výstupní posloupnost (výstupní páska) lexikální analýzy. ) // \\
// ( A to je současně množina symbolů z kterých je tvořena vstupní posloupnost (páska) syntaktické analýzy. ) //
def parseMultiterminal (self) :
item = Terminal ()
self.setPosition (item)
item.multiterminal_name = self.tokenText
self.nextToken ()
return item
===== Uložení gramatiky ve stromové podobě =====
https://github.com/zdenekzc/plain-grammar
spustit python plain_view.py
V pravé části okna je vstupní gramatika, v levém stromu jsou zobrazeny informace o načtené gramatice.
{{prekl::gram_tree.png}}