Szablon przeznaczony do kompilacji był prosty:
<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN"
"http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd">
<html xmlns="http://www.w3.org/1999/xhtml" lang="en_US" xml:lang="en_US">
<head>
<title>HI UNIVERSE</title>
</head>
<body>
<h1>HI UNIVERSE!!!</h1>
<p>Hi Universe, the OPT2 compiler is finally able to generate something useful :)</p>
<opt:section name="aaa">
<p>This is a test: {$test}</p>
</opt:section>
</body>
</html>
Po kompilacji prezentował się on następująco (przełamania linii dodane dla czytelności):
<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd"> <html><head><title>HI UNIVERSE</title></head> <body><h1>HI UNIVERSE!!!</h1><p>Hi Universe, the OPT2 compiler is finally able to generate something useful :)</p> <?php if(is_array($this->data['aaa'])){ foreach($this->data['aaa'] as $name => $value){ ?> <p>This is a test: <?php echo $this->data['test']; ?></p><?php } } ?></body></html>
Pierwszą rzeczą rzucającą się w oczy jest brak atrybutów w znacznikach. Bez obawy, ich umieszczanie w kodzie wynikowym nie zostało jeszcze zaimplementowane, ale to kwestia napisania parunastu linijek. Drugą, zapewne bardziej zauważalną, jest wycięcie niepotrzebnych białych znaków - rzecz, której pomimo prób w OPT1 nie udało się nigdy zaimplementować. W nowym kompilatorze jest to prosta formalność, którą można wyłączyć jedną dyrektywą, jeśli się komuś nie podoba. Ponadto użyta w teście instrukcja opt:section póki co dodana jest dla picu, aby przetestować linker.
W poprzedniej wersji, kompilator działał dwuprzebiegowo. Na początku kod wejściowy był przerabiany na drzewko węzłów, które było następnie przechodzone metodą preorder i każda napotkana instrukcja wrzucała swój kod do jednego wielkiego bufora wyjściowego. Tutaj podejście takie nie zdałoby egzaminu, poważnie ograniczając funkcjonalność. Załóżmy, że jakaś instrukcja chce dodać do swojego rodzica atrybut class o dynamicznie zmieniającej się zawartości. Od razu widać, że byłoby to niemożliwe, ponieważ rodzic już wysłałby swój kod do bufora. Dlatego też utworzyłem trzy przebiegi:
- Robienie drzewka.
- Przetwarzanie drzewka. Każdy węzeł ma do dyspozycji do 12 buforów w postaci tablic reprezentujących różne fragmenty znacznika, do których można dokleić kod PHP. Jeśli instrukcja chce otoczyć całego swojego rodzica warunkiem if, dopisuje kod "if(costam){" do bufora TAG_BEFORE, a następnie zamykający nawias klamrowy do TAG_AFTER. Każdy atrybut znacznika posiada do trzech buforów.
- Linkowanie. Kompilator przechodzi drzewko ponownie, w odpowiedniej kolejności skleja zawartość buforów ze sobą i generuje znaczniki XML, a całość wyrzuca na wyjście.
Tak jak pisałem poprzednio, drzewo węzłów jest zarządzane przez mechanizm mocno wzorowany na DOM (zbieżność nazw i kolejności argumentów wszędzie tam, gdzie to było możliwe). Aktualna implementacja przedstawia się następująco:
- optCodeBuffer - zarządzanie buforami kodu
- addCode($buffer, $code) - dodaje kod do bufora.
- bufferSize($buffer) - wypisuje rozmiar bufora.
- buildCode(...) - linkuje zawartość podanych buforów.
- optNode extends optCodeBuffer - abstrakcyjne klasa węzła
- setParent($parent) - dododaje rodzica węzła.
- getParent() - zwraca rodzica.
- optScannable extends optNode implements IteratorAggregate - węzeł z podelementami
- appendChild($node) - dododaje podwęzeł.
- insertBefore($newnode, $refnode) - dodaje podwęzeł w określone miejsce. $refnode może być zarówno obiektem węzła, jak i numerem.
- removeChild($node) - usuwa podwęzeł.
- replaceChild($newnode, $refnode) - zamienia węzeł.
- hasChildren() - prawda, jeśli są w węźle dzieci.
- countChildren() - liczy dzieci.
- getLastChild() - zwraca ostatni podwęzeł.
- optCharacterData extends optNode - dane tekstowe
- __construct($text) - tworzy nowy węzeł tekstowy.
- appendData($text) - dokleja dane na koniec tekstu.
- insertData($offset, $text) - ddokleja dane po wybranej pozycji. Niezaimplementowane.
- deleteData($offset, $count) - usuwa fragment. Niezaimplementowane.
- replaceData($offset, $count, $text) - zamienia fragment. Niezaimplementowane.
- substringData($offset, $count) - zwykły substr().
- optExpression extends optNode - węzeł na wyrażenia umieszczane w nawiasach klamrowych.
- optText extends optScannable - pojemnik na optCharacterData oraz optExpression.
- appendData($text) - dokleja dane na koniec tekstu i jeśli to konieczne, rozpoczyna nowy podwęzeł optCharacterData
- optElement extends optScannable - znacznik
- __construct($name) - tworzy nowy węzeł znacznika.
- setName($name) - ustawia nową nazwę. Można podać przed dwukropkiem przestrzeń nazw.
- setNamespace($namespace) - ustawia nową przestrzeń nazw.
- getName() - pobiera nazwę.
- getNamespace() - pobiera przestrzeń nazw.
- getXmlName() - pobiera nazwę sklejoną z przestrzenią nazw.
- getAttributes() - pobiera tablicę atrybutów.
- addAttribute($attribute) - dodaje atrybut.
- removeAttribute($name) - usuwa atrybut.
- hasAttributes() - prawda, jeśli posiada atrybuty.
- optAttribute extends optCodeBuffer - atrybut znacznika. Posiada identyczne metody do zarządzania nazwą, jak optElement, zaś wartość może być modyfikowana bezpośrednio.
Ponadto pojawią się jeszcze metody getElementsByTagName() oraz getElementsById() - najprawdopodobniej będą częścią klasy optElement i będą działać liniowo na swoich bezpośrednich potomkach.
Podobnie jak poprzednio, OPT będzie potrafił samodzielnie sprawdzać składnię wyrażeń w stylu $a + $b * ($c - $d). Nowy ich parser także został już rozpoczęty, lecz jego możliwości nie są póki co imponujące. Obsługiwane są podstawowe bloki, kilka prostych operatorów arytmetycznych, liczby i ciągi tekstowe. Ten fragment także musi być przepisany na nowo z powodu ulepszeń algorytmu, lecz dzięki nim pojawią się m.in. operatory preinkrementacji oraz predekrementacji. Przebudowie uległ zestaw operatorów. Zniknęło wszystko, w czym występowały symbole <, > oraz & i teraz w większości przypadków niestety programista będzie skazany na tekstowe wersje w stylu lt, gt itd. - sprawdzanie równości pozostało jednak tak, jak było. Do łączenia ciągów używana będzie tylda na wzór języka D, dzięki czemu zwalnia się operator :: do zastosowań w obiektówce. Co ciekawe, OPT będzie go używać do odwoływania się jednocześnie do elementów obiektu, jak i statycznych metod/pól klas. Można to bardzo łatwo rozpoznać po kontekście (jest zmienna, robimy obiekt, jest identyfikator, robimy obsługę statyczną) i nie widzę powodu, aby miało to być inaczej. Zastanawiam się nad udostępnieniem operatorów new oraz clone. Oczywiście wszystkie odwołania się do klas miałyby ograniczenie - takowe elementy muszą być wcześniej zarejestrowane w bibliotece. Bloki zostają po staremu, jedynie przejmę sobie na specjalny użytek jeszcze $sequence obok $opt :).
Ostateczny zestaw instrukcji nie jest jeszcze w pełni opracowany. Na pewno będą wszystkie konstrukcje programistyczne: opt:if, opt:for, opt:foreach. Na pewno będzie znacznik opt:put pełniący tę samą rolę, co klamerki, ale mogący pobierać atrybuty. Poniżej hardcore'owy przykład, jak jednym znacznikiem wyświetlić np. 10 wartości odseparowanych slashem:
<opt:put data="$something" opt:section="something" str:separator=" / "/>Inny przykład to warunkowe wyświetlanie:
<opt:put data="$something" opt:if="$a == 1"/>Z wysokopoziomowych, pojawią się sekcje, rendering drzew, stronicowanie, separatory, sekwencje (taki wysokopoziomowy include do inteligentnego składania kodu z kawałków) i ulepszone komponenty. Będzie parę rzeczy do dynamicznego składania znaczników, jak w XSLT (konieczność), a co dalej, to się zobaczy. Głównymi założeniami przy ich projektowaniu jest reguła KISS (Keep It Simple, Stupid) oraz zasada, aby mówić kompilatorowi, co chce się wykonać, natomiast jak to zrobić, to już jego zmartwienie, a nie twórcy szablonów. Jest to najprostszy sposób na wyrugowanie programowania z warstwy prezentacji oraz najpoważniejszy argument przeciwko NIEstosowaniu systemów szablonów. Przykładem porównawczym niech będzie dynamiczne pole formularza (komunikaty błędów, podświetalnie, autouzupełnianie itd.). Na początek kod PHP wygenerowany przez OPT:
<tr <?php echo $this->data[$formName]->getClass('amount'); ?>> <td class="desc"><label for="f_amount"><?php echo $this->i18n->put('issue', 'form_amount'); ?></label></td> <td><?php $__component_0 = new opfInput('amount'); $__component_0 -> setOptInstance($this); $__component_0 -> set('name', 'amount'); $__component_0 -> set('id', 'f_amount'); $__component_0 -> begin(); $__component_0 -> end(); if($__component_0 -> onMessage('msg')) { if(sizeof($this->vars['msg']) > 0){ foreach($this->vars['msg'] as $__f__id => &$__f__val){ $this -> vars['id'] = $__f__id; $this -> vars['val'] = &$__f__val; ?> <p class="error"><?php echo $this->vars['val']; ?></p> <?php } } ?> <?php } ?></td> </tr>
Oczywiście inteligentny programista potrafi to całkiem elegancko skrócić. Jedno z przykładowych rozwiązań:
<?php $form -> amount = new inputControl('amount'); $form -> amount -> id = "f_amount"; ?> <tr <?php echo $form -> amount -> getClass(); ?>> <td><label for="f_amount"><?php echo _('f_amount'); ?></label></td> <td><?php $form -> amount -> display(); if(!$form -> amount -> valid()) { displayErrors($form -> amount -> getErrors()); } ?></td> </td>
W dalszym ciągu musimy sami powiedzieć, jak coś wykonać, już nie wspominając o wszystkich, którzy martwią się, że twórca szablonów może nie umieć (dobrze) programować. W OPT1 kod z użyciem komponentów wyglądał następująco:
<tr opt:classfor="amount"> <td><label for="f_amount">{$form@amount}</label></td> <td><opt:opfInput name="amount" id="f_amount"><opt:load event="aMessage"/></opt:Input></td> </tr>
Tu już jest lepiej - mówimy, że chcemy mieć tu pole tekstowe z obsługą błędów aMessage i kolorowaniem całego wiersza w przypadku problemu, a szczegółami martwi się OPT. Jednak osobiście przeszkadza mi tu pewien drobiazg. Mianowicie jak tworzę formularz, to nie klepię wszystkiego od zera, tylko robię Ctrl+C, Ctrl+V, Ctrl+V, Ctrl+V... i później zamieniam nazwy. W tym wypadku aby ukończyć jedno pole, muszę nazwę zmienić w czterech miejscach. W OPT2 w trybie XML, przy mądrze zaprojektowanych komponentach znika i ten kłopot:
<tr> <td>{$form@amount}</td> <td><opf:input name="amount"><opt:load event="aMessage"/></opf:input></td> </tr>
Pozostałe rzeczy komponent może sobie dokleić automatycznie tam, gdzie potrzeba.
Na sam koniec mała statystyka:
- opt.class.php - 10,9 KB
- opt.compiler.php - 28,6 KB
- opt.support.php - 1,3 KB
- opt.error.php - 1,2 KB
- opt.instructions.php - 6,2 KB















Napisał greensky w czwartek, 29 listopada 2007 o 20:19
no to pozostaje czekać na pierwszą wersję ;] zapowiada się ciekawie