In hoeverre kunnen we het aan de computer overlaten om de structuur van een gegeven uitdrukking zichtbaar te maken aan de hand van een in BNF opgestelde grammatica? We hebben bijvoorbeeld de volgende productieregels voor rekenkundige uitdrukkingen bedacht:
En nu willen we de uitdrukking −n−(n+n)*−n^−n^n zodanig laten annoteren dat de door bovenstaande regels opgelegde evaluatievolgorde zichtbaar wordt. Bijvoorbeeld met behulp van accolades:
{{{{{−}{{{n}}}}}}{−}{{{{{({{{{{{n}}}}}{+}{{{{n}}}}})}}}}{*}{{−}{{{n}^{{−}{{{n}^{{{n}}}}}}}}}}}
Eventueel te vereenvoudigen tot:
{−n}−{{({n+n})}*{−{n^{−{n^n}}}}}
Het parsen van de BNF zelf is geen onderdeel van deze opgave. Je mag elke notatie kiezen waarmee je de betekenis van de regels gemakkelijk in de toegepaste programmeertaal kunt uitdrukken. Zo zou het rechterlid van de vierde productieregel in Python heel praktisch kunnen worden geformuleerd als ((primary,), (primary, '^', factor)), dus als een tuple van alternatieven die ieder op hun beurt als een tuple van opeenvolgende terminals en/of non-terminals zijn weergegeven.
BNF is een notatiewijze voor generatieve grammatica's (zie ook https://www.hcc.nl/forum/formele-grammatica-s#antwoord-21931). Daarom lijkt het me onvermijdelijk dat het te bouwen programma zich ook moet gedragen als een expressiegenerator die geen enkele mogelijkheid tot een match met de te analyseren uitdrukking over het hoofd ziet en dus ook eventuele ambiguïteiten blootlegt. Nu kan zo'n generator in theorie oneindig veel geldige expressies genereren, wat vrij veel is. Het is dus zaak te onderkennen waar het generatieproces kan worden afgebroken, omdat doorgaan op de ingeslagen weg geen enkele zin meer heeft en de resterende energie beter kan worden gestoken in kansrijkere alternatieven. Als handreiking naar criteria hiervoor heb ik één extra BNF-spelregel toegevoegd: een non-terminal mag nooit een lege string opleveren. Alle grammatica's zijn indien nodig wel zo te herformuleren dat hieraan wordt voldaan.
Is dit oplosbaar? Na mijn experimenten op papier en op de pc ziet het daar wel naar uit. Althans in theorie. Mijn poging om de hierboven gegeven uitdrukking te laten analyseren door een hiertoe geschreven Lua-script (draaiend via de LuaJIT-compiler) was na 24 uur nog steeds aan de gang. Een wat minder ambitieuze onderneming om dan maar −n−(x)*−n^−n^n te analyseren op basis van een vereenvoudigde regel <primary> ::= "n" | "(x)" duurde toch altijd nog bijna 50 minuten en had toen 232.610.143 generatoraanroepen achter de rug, die samen 53.901.073 expressies hadden voortgebracht. De grootste recursiediepte daarbij was 88. Al die tijd was de processorbelasting torenhoog, maar besloeg het benodigde werkgeheugen slechts rond de 2 MB en vond er geen I/O plaats. Het is me in elk geval duidelijk dat ik deze taak momenteel aanmerkelijk sneller zelf kan volbrengen dan mijn pc dat kan. Daar staat tegenover dat ik eerder fouten pleeg te maken.
De volgende vraag is nu: kan de performance worden verbeterd? Aperte programmeeronhandigheden had ik al verwijderd (leverde een factor 10 snelheidswinst op). Ik heb gebruik gemaakt van dynamische arrays (tables in Lua en lists in Python) die ik inderdaad laat uitdijen en inkrimpen. Proeven met constant houden van de fysieke arraygrootte en administratief alleen de daarin ondergebrachte virtuele arrays in grootte laten variëren leverde slechts marginale snelheidswinst op. Mogelijk moeten er andere datastructuren te hulp worden geroepen via talen als C, Lisp (linked lists?) en Forth (stacks?). Maar gelet op de aanwas van het aantal generatoraanroepen en geproduceerde expressies vrees ik dat het performanceprobleem daarmee slechts een paar stappen voor ons uit te schuiven zal zijn.
Daarmee wordt de kwestie eerder: valt er niet een fundamenteel beter, intelligenter algoritme te verzinnen om het stelsel van productieregels en de te analyseren uitdrukking zodanig met elkaar te confronteren dat alle theoretisch mogelijke interpretaties van die uitdrukking nog steeds worden onderkend? Misschien krijgt de computer dan toch weer de kans het van mij te winnen. Ik heb inmiddels wel twee verschillende richtingen in gedachten die uitzicht bieden op een veel economischer verwerking.
Mijn knutsels tot nu toe heb ik ondergebracht in bijgesloten ZIP-bestand. Je kunt het geheel opvatten als een spoiler, als een voetstuk om op voort te bouwen of als een na kennisneming terzijde te leggen want uitzichtloze aanpak. Doe ermee wat je wilt. Van het bestand lees-mij.txt kun je in elk geval zonder bezwaar kennisnemen, want het legt slechts uit wat er in de bundel te vinden is en welke grammatica's en testgevallen ik beproefd heb. Het geeft niets prijs over mijn algoritme. De meeverpakte Lua- en Python-scripts doen dat uiteraard wel.
<aexpr> ::= <term> | <aexpr> <addop> <term>
<term> ::= <factor> | <term> <multop> <factor>
<factor> ::= <ufactor> | <addop> <factor>
<ufactor> ::= <primary> | <primary> "^" <factor>
<primary> ::= "n" | "(" <aexpr> ")"
<addop> ::= "+" | "-"
<multop> ::= "*" | "/"
En nu willen we de uitdrukking −n−(n+n)*−n^−n^n zodanig laten annoteren dat de door bovenstaande regels opgelegde evaluatievolgorde zichtbaar wordt. Bijvoorbeeld met behulp van accolades:
{{{{{−}{{{n}}}}}}{−}{{{{{({{{{{{n}}}}}{+}{{{{n}}}}})}}}}{*}{{−}{{{n}^{{−}{{{n}^{{{n}}}}}}}}}}}
Eventueel te vereenvoudigen tot:
{−n}−{{({n+n})}*{−{n^{−{n^n}}}}}
Het parsen van de BNF zelf is geen onderdeel van deze opgave. Je mag elke notatie kiezen waarmee je de betekenis van de regels gemakkelijk in de toegepaste programmeertaal kunt uitdrukken. Zo zou het rechterlid van de vierde productieregel in Python heel praktisch kunnen worden geformuleerd als ((primary,), (primary, '^', factor)), dus als een tuple van alternatieven die ieder op hun beurt als een tuple van opeenvolgende terminals en/of non-terminals zijn weergegeven.
BNF is een notatiewijze voor generatieve grammatica's (zie ook https://www.hcc.nl/forum/formele-grammatica-s#antwoord-21931). Daarom lijkt het me onvermijdelijk dat het te bouwen programma zich ook moet gedragen als een expressiegenerator die geen enkele mogelijkheid tot een match met de te analyseren uitdrukking over het hoofd ziet en dus ook eventuele ambiguïteiten blootlegt. Nu kan zo'n generator in theorie oneindig veel geldige expressies genereren, wat vrij veel is. Het is dus zaak te onderkennen waar het generatieproces kan worden afgebroken, omdat doorgaan op de ingeslagen weg geen enkele zin meer heeft en de resterende energie beter kan worden gestoken in kansrijkere alternatieven. Als handreiking naar criteria hiervoor heb ik één extra BNF-spelregel toegevoegd: een non-terminal mag nooit een lege string opleveren. Alle grammatica's zijn indien nodig wel zo te herformuleren dat hieraan wordt voldaan.
Is dit oplosbaar? Na mijn experimenten op papier en op de pc ziet het daar wel naar uit. Althans in theorie. Mijn poging om de hierboven gegeven uitdrukking te laten analyseren door een hiertoe geschreven Lua-script (draaiend via de LuaJIT-compiler) was na 24 uur nog steeds aan de gang. Een wat minder ambitieuze onderneming om dan maar −n−(x)*−n^−n^n te analyseren op basis van een vereenvoudigde regel <primary> ::= "n" | "(x)" duurde toch altijd nog bijna 50 minuten en had toen 232.610.143 generatoraanroepen achter de rug, die samen 53.901.073 expressies hadden voortgebracht. De grootste recursiediepte daarbij was 88. Al die tijd was de processorbelasting torenhoog, maar besloeg het benodigde werkgeheugen slechts rond de 2 MB en vond er geen I/O plaats. Het is me in elk geval duidelijk dat ik deze taak momenteel aanmerkelijk sneller zelf kan volbrengen dan mijn pc dat kan. Daar staat tegenover dat ik eerder fouten pleeg te maken.
De volgende vraag is nu: kan de performance worden verbeterd? Aperte programmeeronhandigheden had ik al verwijderd (leverde een factor 10 snelheidswinst op). Ik heb gebruik gemaakt van dynamische arrays (tables in Lua en lists in Python) die ik inderdaad laat uitdijen en inkrimpen. Proeven met constant houden van de fysieke arraygrootte en administratief alleen de daarin ondergebrachte virtuele arrays in grootte laten variëren leverde slechts marginale snelheidswinst op. Mogelijk moeten er andere datastructuren te hulp worden geroepen via talen als C, Lisp (linked lists?) en Forth (stacks?). Maar gelet op de aanwas van het aantal generatoraanroepen en geproduceerde expressies vrees ik dat het performanceprobleem daarmee slechts een paar stappen voor ons uit te schuiven zal zijn.
Daarmee wordt de kwestie eerder: valt er niet een fundamenteel beter, intelligenter algoritme te verzinnen om het stelsel van productieregels en de te analyseren uitdrukking zodanig met elkaar te confronteren dat alle theoretisch mogelijke interpretaties van die uitdrukking nog steeds worden onderkend? Misschien krijgt de computer dan toch weer de kans het van mij te winnen. Ik heb inmiddels wel twee verschillende richtingen in gedachten die uitzicht bieden op een veel economischer verwerking.
Mijn knutsels tot nu toe heb ik ondergebracht in bijgesloten ZIP-bestand. Je kunt het geheel opvatten als een spoiler, als een voetstuk om op voort te bouwen of als een na kennisneming terzijde te leggen want uitzichtloze aanpak. Doe ermee wat je wilt. Van het bestand lees-mij.txt kun je in elk geval zonder bezwaar kennisnemen, want het legt slechts uit wat er in de bundel te vinden is en welke grammatica's en testgevallen ik beproefd heb. Het geeft niets prijs over mijn algoritme. De meeverpakte Lua- en Python-scripts doen dat uiteraard wel.
- Pagina :
- 1
Er zijn nog geen reacties op dit bericht.
Reageer als een van de eersten op dit bericht!
Reageer als een van de eersten op dit bericht!