[php] template parser: Stacken control structures

Pagina: 1
Acties:

  • drm
  • Registratie: Februari 2001
  • Laatst online: 09-06-2025

drm

f0pc0dert

Topicstarter
Ik was dus al een tijdje bezig met mijn eigen template parser, in PHP (clutch of niet, kan me niet schelen :P), maar ik kom 1 ranzig probleem tegen.

Ik zal eerst even in het kort uitleggen hoe hij ongeveer werkt.

Je hebt een html-template bestandje van de volgende vorm:
code:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
<html>
<head>
   <title>{docTitle}</title>
</head>

<body>
<b>{title}</b>
<table>
~for items
   <a href="detail.php?itemID={itemID}">{itemName}</a><br>
~/for
</table>
</body>
</html>

Nu is het de bedoeling, dat je aan een gecreerd template object een array met variabelen meegeeft. De variabelen op het eerste niveau van de array:
code:
1
2
3
4
$data = array ( 
   "docTitle" => "drm's template parser voorbeeldje"
   "title"    => "Items"
);

worden in het docje zo vervangen:
pseudo:
code:
1
$outputString = str_replace ( "{$key}", $data [ $key ], $outputString );

niet letterlijk, maar goed het idee is duidelijk.

Nu ziet het oplettende oog ook een forloopje staan. Die tilde (~) ervoor is om aan te geven dat het om een template control structure gaat en niet om platte tekst.

Nu heb je een beginpunt met de syntax:
code:
1
~for arrayName

waar arrayName dus een gewone niet associatieve array is met allemaal records uit vermoedelijk een database, waar de records wel weer associatief zijn.

dus
code:
1
2
3
4
5
6
7
8
$data = array (
   "docTitle" => "...",
   "title"    => "...",
   "items"    => array (
    array ( "itemID" => "0", "itemName" => "naam0" ),
    array ( "itemID" => "1", "itemName" => "naam1" )
   )
);

Het is dan natuurlijk de bedoeling dat de scope verandert van de array "data" naar de array die genoemd wordt in het for-loopje. Dan kunnen namelijk op vrijwel dezelfde manier de {...} dingetjes ge-replaced worden.


[/intro]
[eigenlijke probleem]

Maar... de grap is, dat die forloopjes ook genest moeten kunnen zijn. Dus met een soort van /.../e regex, gaat het niet lukken.

In mijn korte tijd op de HIO heb ik vernomen, dat je dat soort dingen oplost door er een stack voor op te zetten.
Goed, ik vol goede moed een stack gemaakt, de offset in de string van het begin van de forloop opslaan, en de string verder doorlopen totdat ik een forloop-sluitertje (~/for) tegenkom.

Dan heb ik dus een gedeelte van de template, een scope (die verschuift dus mee met het forloopje) en een data-array, die dus in feite de scope is van het forloopje, en tevens een childelement van $data, of verder genest...

[edit]
Na conversatie met Pelle over ICQ, kwam * drm erachter dat hij het probleem niet goed geformuleerd had.

Het feitelijke probleem is, dat ik niet weet hoe ik de content van een forloopje af moet handelen op het moment dat ik een sluit-tag tegenkom (en dus de corresponderende open-tag van de stack pop.
[/edit]

* drm cracked his brain for a few hours, but couldn't figure out how to code this sh*t

Als je tips, ideeen en of vragen hebt hoor ik het graag. Het is namelijk een ongelooflijk interessant test-case, maar ik kom er zo in m'n eentje echt niet uit...

edit:
als je code wilt zien kun je het ook even melden, maar ik denk dat dat voor dit probleem niet echt interessant is... Met pseudo-code komen we er wel ;)

Music is the pleasure the human mind experiences from counting without being aware that it is counting
~ Gottfried Leibniz


  • Pelle
  • Registratie: Januari 2001
  • Laatst online: 12:57

Pelle

🚴‍♂️

* Pelle is hier geen held in, maar probeert een voorzetje te geven

Stel, dit is de structuur van je ~for-loopjes:
code:
1
2
3
4
5
6
7
8
~for  
    ~for
      ~for
      ~/for
      ~for
      ~/for
    ~/for
~/for

Het lijkt mij erg handig om per ~for-loopje de inhoud op te slaan, en of (en zo ja welke) het loopje childs heeft. Objectje maken dus van elk loopje. Als je al die objecten op een logische manier in een array krijgt, dan kun je er recursief doorheenwandelen om de output in de goeie volgorde te genereren.

Dit zou er in mijn oplossing ongeveer zo uit komen te zien:
PHP:
1
2
3
4
5
6
7
8
<?
$loopje[0]->content = ".."       // alles tussen ~for en ~/for dus
$loopje[0]->childs  = array(1);  // child loopje
$loopje[1]->content = ".."       // alles ertussen dus
$loopje[1]->childs  = array(2,3) // 2 childs loopjes
$loopje[2]->content = ".."       // alles ertussen dus
$loopje[3]->content = ".."       // alles ertussen dus
?>

Vervolgens loop je door $loopje heen, daarbij alle childs volgend tot het diepste niveau. Loopje uitvoeren, en code teruggeven aan z'n parent, loopje uitvoeren, teruggeven aan parent, tot je bij $loopje[0] bent aangekomen.

* Pelle begint het, naarmate hij er langer over nadenkt, ook steeds lastiger te vinden

Dit is zeker weten wel voor verbetering vatbaar, want mijn eerste stapjes op OO-progsel-gebied zijn nog vers, so forgive me :)

  • drm
  • Registratie: Februari 2001
  • Laatst online: 09-06-2025

drm

f0pc0dert

Topicstarter
Op maandag 26 november 2001 02:20 schreef Pelle het een en ander
Het idee is goed...
* drm denkt hoe dit toe te passen...

Music is the pleasure the human mind experiences from counting without being aware that it is counting
~ Gottfried Leibniz


  • tomato
  • Registratie: November 1999
  • Niet online
drm liet merken dat hij van quoten houdt
*LOL* :D

[edit] jammer hij heeft het door >:)

  • Pelle
  • Registratie: Januari 2001
  • Laatst online: 12:57

Pelle

🚴‍♂️

Op maandag 26 november 2001 02:32 schreef tomato het volgende:
*LOL* :D

[edit] jammer hij heeft het door >:)
Correction: * Pelle had het door en vertelde het aan drm via ICQ ;)

  • drm
  • Registratie: Februari 2001
  • Laatst online: 09-06-2025

drm

f0pc0dert

Topicstarter
Op maandag 26 november 2001 02:35 schreef Pelle het volgende:

[..]

Correction: * Pelle had het door en vertelde het aan drm via ICQ ;)
ere wie ere toe komt :)

Music is the pleasure the human mind experiences from counting without being aware that it is counting
~ Gottfried Leibniz


  • mbravenboer
  • Registratie: Januari 2000
  • Laatst online: 06-11-2025
Ik ben niet echt een template-engine schrijver, maar ik denk toch dat ik wel iets zinnigs kan zeggen over je probleem en wat kan brabbelen over hoe je het op een duidelijke en overzichtelijke manier kan aanpakken :) .

De gebruikelijke manier van evaluatie van talen gaat meestal via een environment, wat een mapping is van de variabelen naar de huidige waarden. Deze environment kan uitgebreid en ingekrompen worden. In dit geval kan je dit idee perfect toepassen in combinatie met een gelinkte lijst (of als je dat wilt een Stack).

Een schets:

1. Introduceer een 'Environment'. Deze environment kan aan de hand van een variabelenaam, de waarde van die variabele opzoeken via een methode lookup.

2. Als je een variabele tegenkomt zoek je deze op in de environment. Je moet er dus alleen voor zorgen dat de environment altijd de goede waarden bevat.

3. In de environment werk je met een stack (of een gelinkte lijst). Elk element op de stack is een zo'n associatieve array van strings naar strings. In het begin bevat deze stack maar 1 element: een associatieve array met de normale, globale, variabelen.

4. Een lookup in de environment loopt de stack (of liever de gelinkte lijst) af en zoekt in elke associatie array de waarde van een variabele. Merk op dat als een variabele dus niet op de top van de stack gevonden wordt, hij ook in een lager element gedefinieerd kan zijn. Variabelen kunnen elkaar zo ook (tijdelijk!) vervangen.

5. Als je een for loop tegen komt zet je de associatieve array van het eerste element op de stack in de environment. Als je klaar bent, haal je deze weer van de stack af. Op deze manier loop je alle elementen af. Het opzoeken van een variabele gaat in een for-loop dus gewoon op dezelfde manier. Merk ook op dat geneste for-loops dus perfect afgehandeld worden en dat de scoping van variabelen perfect verloopt.

Voorbeeldje:

Stel dat je deze data hebt:
code:
1
2
3
4
5
6
7
8
"docTitle" => "doc title",
"title"    => "title",
"items"    => 
    "itemID" => "0", "itemName" => "naam0"
    "itemID" => "1", "itemName" => "naam1"
      "sub-items" => 
         "sub-itemID" => "2", "subItemName" => "naam2"
         "sub-itemID" => "3", "subItemName" => "naam3"

Je environment ziet er dan eerst zo uit:
code:
1
[ "docTitle" -> "doc title", "title" -> "title"]

Bij het tegenkoemn van het eerste element ziet de environment er zo uit:
code:
1
2
[ "docTitle" -> "doc title", "title" -> "title"]
[ "itemID" -> "0", "itemName" -> "naam0"]

Als je een sub-items for loop tegenkomt voeg je weer een array toe:
code:
1
2
3
[ "docTitle" -> "doc title", "title" -> "title"]
[ "itemID" -> "0", "itemName" -> "naam0"]
[ "sub-itemID" -> "2", "subItemName" -> "naam2"]

Aan het einde van de for-loop haal je bovenste element eraf en voeg je de volgende toe:
code:
1
2
3
[ "docTitle" -> "doc title", "title" -> "title"]
[ "itemID" -> "0", "itemName" -> "naam0"]
[ "sub-itemID" -> "3", "subItemName" -> "naam3"]

Na deze loop zijn er geen elementen meer en haal je dus alleen het bovenste element weg:
code:
1
2
[ "docTitle" -> "doc title", "title" -> "title"]
[ "itemID" -> "0", "itemName" -> "naam0"]

Na de items loop haal je ook dat element weg en voeg je de volgende toe:
code:
1
2
[ "docTitle" -> "doc title", "title" -> "title"]
[ "itemID" -> "1", "itemName" -> "naam1"]

Na deze loop haal je hem weer weg en wordt het dus dit:
code:
1
[ "docTitle" -> "doc title", "title" -> "title"]

Uiteraard moet je ook even bijhouden welke items er aanwezig zijn om over te itereren en wat de variabelen daarin zijn. Dat kan je ook mooi in de environment doen.

Andere aanpak

Eigenlijk moet je dergelijke stuff toch anders aanpakken denk ik. Als je een professionele template engine wilt hebben zou ik het volgende doen:

1. Ontwerp een goede grammatica voor je template 'taal'.

2. Parse de template naar een parse tree.

3. Vereenvoudig deze een beetje tot een abstract syntax tree

4. evalueer de template.

Je zou de abstracte syntax zo aan kunnen pakken (alles is een expressie)
code:
1
2
3
Constant: String        -> Expression
Variable: String        -> Expression
ForLoop : String, [Expression]  -> Expression

Een Constant is een stuk tekst. Je maakt een Constant met een String. Een Variabele is natuurlijk gewoon een variabele. Ook deze kan je gewoon maken met een String. Een ForLoop bestaat uit een String (de items waarover wordt geitereerd) en een lijst van Expressions. Expressions kunnen dus andere ForLoops zijn, maar ook Variabelen of Constanten.

Het voordeel van deze aanpak is dat je een schone en duidelijke definitie van je template taal hebt. Ook kan je makkelijk nieuwe constructies toevoegen zoals if, while of switch.

Als je eenmaal een boom hebt kan je deze gaan evalueren. Dat evalueren kan je op een vergelijkbare manier doen als boven. Met environments dus.

Als je het op deze manier aanpakt heb je een duidelijke scheiding tussen componenten: parsen en evalueren.

Blog, Stratego/XT: Program Transformation, SDF: Syntax Definition, Nix: Software Deployment


  • drm
  • Registratie: Februari 2001
  • Laatst online: 09-06-2025

drm

f0pc0dert

Topicstarter
Op maandag 26 november 2001 ging mbravenboer laat naar bed om drm e.e.a. over parse-engines uit te leggen
Ok, ik begrijp je geloof ik aardig...

Die abstractie kun je dus (als ik het goed begrijp) zo doen, dat alle code die je in je template hebt staan op een of andere manier herleidt tot een expressie, die je kan evalueren.

Als ik dat idee nou eens combineer met dat van Pelle, kom ik volgens mij een heel eind... Die abstractie is idd interessant...

* drm gaat er maar weer eens aan klooien...

Music is the pleasure the human mind experiences from counting without being aware that it is counting
~ Gottfried Leibniz


  • drm
  • Registratie: Februari 2001
  • Laatst online: 09-06-2025

drm

f0pc0dert

Topicstarter
ok, ik heb nu een codeStacker die werkt zoals ik wil. Dus, het nesten gaat nu helemaal ok

Ik post dus even wat code, bij wijze van "ik hou jullie op de hoogte":
PHP:
1
2
3
4
5
<?
/*** SNIP ***/
// nieuwere versie beschikbaar.
/*** SNIP ***/
?>

de classes CodeEntry en Stack spreken wel voor zich, denk ik.

* drm gaat het weer verder uitwerken

Je ziet dat de verwerking van de for start-en-stop tags nog zo amateuristisch gaat als maar zijn kan, maar ik ga het verder allemaal wat abstracter maken, zodat uitbreidbaarheid ook niet zo'n punt is ;)

Ook moet de replacement van het codeblock op een wat minder brakke manier kunnen. Ga ik ook mee aan de slag

* I 'll keep you informed :)

edit: bij deze
Als je geinteresseerd bent doe maar ff mailen of icq-en. zie profile

Music is the pleasure the human mind experiences from counting without being aware that it is counting
~ Gottfried Leibniz

Pagina: 1