xref: /plugin/ebnf/ebnf.php (revision 3dfddad1c9d6d249f74ff8fc2e9e53fc7f9ca00b)
1a345322fSVincent.Tscherter<?php
2a345322fSVincent.Tscherter/*
3a345322fSVincent.Tscherter    This program is free software: you can redistribute it and/or modify
4a345322fSVincent.Tscherter    it under the terms of the GNU General Public License as published by
5a345322fSVincent.Tscherter    the Free Software Foundation, either version 3 of the License, or
6a345322fSVincent.Tscherter    (at your option) any later version.
7a345322fSVincent.Tscherter
8a345322fSVincent.Tscherter    This program is distributed in the hope that it will be useful,
9a345322fSVincent.Tscherter    but WITHOUT ANY WARRANTY; without even the implied warranty of
10a345322fSVincent.Tscherter    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
11a345322fSVincent.Tscherter    GNU General Public License for more details.
12a345322fSVincent.Tscherter
13a345322fSVincent.Tscherter    You should have received a copy of the GNU General Public License
14a345322fSVincent.Tscherter    along with this program.  If not, see <http://www.gnu.org/licenses/>.
15a345322fSVincent.Tscherter
16*3dfddad1SVincent Tscherter    Vincent Tscherter, tscherter@tscherter.net, Solothurn, 2009-01-18
17a345322fSVincent.Tscherter
18a345322fSVincent.Tscherter    2009-01-18 version 0.1 first release
19a345322fSVincent.Tscherter    2009-01-02 version 0.2
20a345322fSVincent.Tscherter      - title und comment literal added
21a345322fSVincent.Tscherter      - ";" als terminator-symbol added
223d5b504aSVincent Tscherter    2023-09-28 version 0.3 prefixed all constants
23*3dfddad1SVincent Tscherter    2025-09-05 version 0.4 PHP 8 compatibility and other fixes
24a345322fSVincent.Tscherter*/
25a345322fSVincent.Tscherter
2677a47d83SVincent Tscherterdefine('META', 'https://www.dokuwiki.org/plugin:ebnf');
27a345322fSVincent.Tscherter
28a345322fSVincent.Tscherter// parser
29a345322fSVincent.Tscherterdefine('EBNF_OPERATOR_TOKEN', 1);
30a345322fSVincent.Tscherterdefine('EBNF_LITERAL_TOKEN', 2);
31a345322fSVincent.Tscherterdefine('EBNF_WHITESPACE_TOKEN', 3);
32a345322fSVincent.Tscherterdefine('EBNF_IDENTIFIER_TOKEN', 4);
33a345322fSVincent.Tscherter
34a345322fSVincent.Tscherter// rendering
3577a47d83SVincent Tscherterdefine('EBNF_FONT', 2);
3677a47d83SVincent Tscherterdefine('EBNF_U', 10);
3777a47d83SVincent Tscherterdefine('EBNF_AW', 3);
38a345322fSVincent.Tscherter
39a345322fSVincent.Tscherter// lexemes
40a345322fSVincent.Tscherter$ebnf_lexemes[] = array( 'type' => EBNF_OPERATOR_TOKEN, 'expr' => '[={}()|.;[\]]' );
41a345322fSVincent.Tscherter$ebnf_lexemes[] = array( 'type' => EBNF_LITERAL_TOKEN,  'expr' => "\"[^\"]*\"" );
42a345322fSVincent.Tscherter$ebnf_lexemes[] = array( 'type' => EBNF_LITERAL_TOKEN,  'expr' => "'[^']*'" );
43a345322fSVincent.Tscherter$ebnf_lexemes[] = array( 'type' => EBNF_IDENTIFIER_TOKEN,  'expr' => "[a-zA-Z0-9_-]+" );
44a345322fSVincent.Tscherter$ebnf_lexemes[] = array( 'type' => EBNF_WHITESPACE_TOKEN,  'expr' => "\\s+" );
45a345322fSVincent.Tscherter
46a345322fSVincent.Tscherter// input example
47a345322fSVincent.Tscherter$input = <<<EOD
48a345322fSVincent.Tscherter"EBNF defined in itself" {
49a345322fSVincent.Tscherter  syntax     = [ title ] "{" { rule } "}" [ comment ].
50a345322fSVincent.Tscherter  rule       = identifier "=" expression ( "." | ";" ) .
51a345322fSVincent.Tscherter  expression = term { "|" term } .
52a345322fSVincent.Tscherter  term       = factor { factor } .
53a345322fSVincent.Tscherter  factor     = identifier
54a345322fSVincent.Tscherter             | literal
55a345322fSVincent.Tscherter             | "[" expression "]"
56a345322fSVincent.Tscherter             | "(" expression ")"
57a345322fSVincent.Tscherter             | "{" expression "}" .
58a345322fSVincent.Tscherter  identifier = character { character } .
59a345322fSVincent.Tscherter  title      = literal .
60a345322fSVincent.Tscherter  comment    = literal .
61a345322fSVincent.Tscherter  literal    = "'" character { character } "'"
62a345322fSVincent.Tscherter             | '"' character { character } '"' .
63a345322fSVincent.Tscherter}
64a345322fSVincent.TscherterEOD;
65a345322fSVincent.Tscherter
66a345322fSVincent.Tscherterif (isset($_GET['syntax'])) {
67a345322fSVincent.Tscherter  $input = $_GET['syntax'];
68a345322fSVincent.Tscherter  $input = stripslashes($input);
69a345322fSVincent.Tscherter}
70a345322fSVincent.Tscherter
71a345322fSVincent.Tscherter$format = "png";
72a345322fSVincent.Tscherterif (isset($_GET['format'])) $format = $_GET['format'];
73a345322fSVincent.Tscherter
74a345322fSVincent.Tschertertry {
75a345322fSVincent.Tscherter  $tokens = ebnf_scan($input, true);
76a345322fSVincent.Tscherter  $dom = ebnf_parse_syntax($tokens);
77a345322fSVincent.Tscherter  if ($format == 'xml') {
78a345322fSVincent.Tscherter    header('Content-Type: application/xml');
79a345322fSVincent.Tscherter    echo $dom->saveXML();
80a345322fSVincent.Tscherter  } else {
81a345322fSVincent.Tscherter    render_node($dom->firstChild, true);
82a345322fSVincent.Tscherter  }
835a779085SDamien Regad} catch (EbnfException $e) {
84a345322fSVincent.Tscherter    header('Content-Type: text/plain');
85a345322fSVincent.Tscherter    $dom = new DOMDocument();
86a345322fSVincent.Tscherter    $syntax = $dom->createElement("syntax");
87a345322fSVincent.Tscherter    $syntax->setAttribute('title', 'EBNF - Syntax Error');
885a779085SDamien Regad    $syntax->setAttribute('meta',
895a779085SDamien Regad        $e->getMessage()
905a779085SDamien Regad        . " - '" . substr($input, $e->getPos(), 30) . "...'"
915a779085SDamien Regad    );
92a345322fSVincent.Tscherter    $dom->appendChild($syntax);
93a345322fSVincent.Tscherter    render_node($dom->firstChild, true);
94a345322fSVincent.Tscherter}
95a345322fSVincent.Tscherter
96a345322fSVincent.Tscherterfunction rr($im, $x1, $y1, $x2, $y2, $r, $black){
97a345322fSVincent.Tscherter  imageline($im, $x1+$r, $y1, $x2-$r, $y1, $black);
98a345322fSVincent.Tscherter  imageline($im, $x1+$r, $y2, $x2-$r, $y2, $black);
99a345322fSVincent.Tscherter  imageline($im, $x1, $y1+$r, $x1, $y2-$r, $black);
100a345322fSVincent.Tscherter  imageline($im, $x2, $y1+$r, $x2, $y2-$r, $black);
101a345322fSVincent.Tscherter  imagearc($im, $x1+$r, $y1+$r, 2*$r, 2*$r, 180, 270, $black);
102a345322fSVincent.Tscherter  imagearc($im, $x2-$r, $y1+$r, 2*$r, 2*$r, 270, 360, $black);
103a345322fSVincent.Tscherter  imagearc($im, $x1+$r, $y2-$r, 2*$r, 2*$r, 90, 180, $black);
104a345322fSVincent.Tscherter  imagearc($im, $x2-$r, $y2-$r, 2*$r, 2*$r, 0, 90, $black);
105a345322fSVincent.Tscherter}
106a345322fSVincent.Tscherter
107a345322fSVincent.Tscherterfunction create_image($w, $h) {
108a345322fSVincent.Tscherter  global $white, $black, $blue, $red, $green, $silver;
109a345322fSVincent.Tscherter  $im = imagecreatetruecolor($w, $h) or die("no img");
110a345322fSVincent.Tscherter  imageantialias($im, true);
111a345322fSVincent.Tscherter  $white = imagecolorallocate ($im, 255, 255, 255);
112a345322fSVincent.Tscherter  $black = imagecolorallocate ($im, 0, 0, 0);
113a345322fSVincent.Tscherter  $blue = imagecolorallocate ($im, 0, 0, 255);
114a345322fSVincent.Tscherter  $red = imagecolorallocate ($im, 255, 0, 0);
115a345322fSVincent.Tscherter  $green = imagecolorallocate ($im, 0, 200, 0);
116a345322fSVincent.Tscherter  $silver = imagecolorallocate ($im, 127, 127, 127);
117a345322fSVincent.Tscherter  imagefilledrectangle($im, 0,0,$w,$h,$white);
118a345322fSVincent.Tscherter  return $im;
119a345322fSVincent.Tscherter}
120a345322fSVincent.Tscherter
121a345322fSVincent.Tscherterfunction arrow($image, $x, $y, $lefttoright) {
122a345322fSVincent.Tscherter  global $white, $black;
12370e79227SDamien Regad  if (!$lefttoright) {
12470e79227SDamien Regad      $points = array($x, $y - EBNF_U / 3, $x - EBNF_U, $y, $x, $y + EBNF_U / 3);
12570e79227SDamien Regad  } else {
12670e79227SDamien Regad      $points = array($x - EBNF_U, $y - EBNF_U / 3, $x, $y, $x - EBNF_U, $y + EBNF_U / 3);
12770e79227SDamien Regad  }
12870e79227SDamien Regad  if (PHP_VERSION_ID >= 80000 ) {
12970e79227SDamien Regad      imagefilledpolygon($image, $points, $black);
13070e79227SDamien Regad  } else {
13170e79227SDamien Regad      imagefilledpolygon($image, $points, 3, $black);
13270e79227SDamien Regad  }
133a345322fSVincent.Tscherter}
134a345322fSVincent.Tscherter
135a345322fSVincent.Tscherter
136a345322fSVincent.Tscherterfunction render_node($node, $lefttoright) {
137a345322fSVincent.Tscherter  global $white, $black, $blue, $red, $green, $silver;
138a345322fSVincent.Tscherter  if ($node->nodeName=='identifier' || $node->nodeName=='terminal') {
139b099bbf8SRalf Müller    $text = html_entity_decode($node->getAttribute('value'));
14077a47d83SVincent Tscherter    $w = imagefontwidth(EBNF_FONT)*(strlen($text)) + 4*EBNF_U;
14177a47d83SVincent Tscherter    $h = 2*EBNF_U;
142a345322fSVincent.Tscherter    $im = create_image($w, $h);
143a345322fSVincent.Tscherter
144a345322fSVincent.Tscherter    if ($node->nodeName!='terminal') {
14577a47d83SVincent Tscherter        imagerectangle($im, EBNF_U, 0, $w-EBNF_U-1, $h-1, $black);
1460f22d919SDamien Regad        imagestring($im, EBNF_FONT, 2*EBNF_U, intdiv($h-imagefontheight(EBNF_FONT), 2), $text, $red);
147a345322fSVincent.Tscherter    } else {
148a345322fSVincent.Tscherter      if ($text!="...")
14977a47d83SVincent Tscherter	      rr($im, EBNF_U, 0, $w-EBNF_U-1, $h-1, EBNF_U/2, $black);
1500f22d919SDamien Regad      imagestring($im, EBNF_FONT, 2*EBNF_U, intdiv($h-imagefontheight(EBNF_FONT), 2),
151a345322fSVincent.Tscherter        $text, $text!="..."?$blue:$black);
152a345322fSVincent.Tscherter    }
15377a47d83SVincent Tscherter    imageline($im,0,EBNF_U, EBNF_U, EBNF_U, $black);
15477a47d83SVincent Tscherter    imageline($im,$w-EBNF_U,EBNF_U, $w+1, EBNF_U, $black);
155a345322fSVincent.Tscherter    return $im;
156a345322fSVincent.Tscherter  } else if ($node->nodeName=='option' || $node->nodeName=='loop') {
157a345322fSVincent.Tscherter    if ($node->nodeName=='loop')
158a345322fSVincent.Tscherter      $lefttoright = ! $lefttoright;
159a345322fSVincent.Tscherter    $inner = render_node($node->firstChild, $lefttoright);
16077a47d83SVincent Tscherter    $w = imagesx($inner)+6*EBNF_U;
16177a47d83SVincent Tscherter    $h = imagesy($inner)+2*EBNF_U;
162a345322fSVincent.Tscherter    $im = create_image($w, $h);
16377a47d83SVincent Tscherter    imagecopy($im, $inner, 3*EBNF_U, 2*EBNF_U, 0,0, imagesx($inner), imagesy($inner));
16477a47d83SVincent Tscherter    imageline($im,0,EBNF_U, $w, EBNF_U, $black);
16577a47d83SVincent Tscherter    arrow($im, $w/2+EBNF_U/2, EBNF_U, $node->nodeName=='loop'?!$lefttoright:$lefttoright);
16677a47d83SVincent Tscherter    arrow($im, 3*EBNF_U, 3*EBNF_U, $lefttoright);
16777a47d83SVincent Tscherter    arrow($im, $w-2*EBNF_U, 3*EBNF_U, $lefttoright);
16877a47d83SVincent Tscherter    imageline($im,EBNF_U,EBNF_U, EBNF_U, 3*EBNF_U, $black);
16977a47d83SVincent Tscherter    imageline($im,EBNF_U,3*EBNF_U, 2*EBNF_U, 3*EBNF_U, $black);
17077a47d83SVincent Tscherter    imageline($im,$w-EBNF_U,EBNF_U, $w-EBNF_U, 3*EBNF_U, $black);
17177a47d83SVincent Tscherter	imageline($im,$w-3*EBNF_U-1,3*EBNF_U, $w-EBNF_U, 3*EBNF_U, $black);
172a345322fSVincent.Tscherter    return $im;
173a345322fSVincent.Tscherter  } else if ($node->nodeName=='sequence') {
174a345322fSVincent.Tscherter    $inner = render_childs($node, $lefttoright);
175a345322fSVincent.Tscherter    if (!$lefttoright)
176a345322fSVincent.Tscherter      $inner = array_reverse($inner);
17777a47d83SVincent Tscherter    $w = count($inner)*EBNF_U-EBNF_U; $h = 0;
178a345322fSVincent.Tscherter    for ($i = 0; $i<count($inner); $i++) {
179a345322fSVincent.Tscherter      $w += imagesx($inner[$i]);
180a345322fSVincent.Tscherter      $h = max($h, imagesy($inner[$i]));
181a345322fSVincent.Tscherter    } $im = create_image($w, $h);
182a345322fSVincent.Tscherter    imagecopy($im, $inner[0], 0, 0, 0,0, imagesx($inner[0]), imagesy($inner[0]));
18377a47d83SVincent Tscherter    $x = imagesx($inner[0])+EBNF_U;
184a345322fSVincent.Tscherter    for ($i = 1; $i<count($inner); $i++) {
18577a47d83SVincent Tscherter      imageline($im, $x-EBNF_U-1, EBNF_U, $x, EBNF_U, $black);
18677a47d83SVincent Tscherter      arrow($im, $x, EBNF_U, $lefttoright);
187a345322fSVincent.Tscherter      imagecopy($im, $inner[$i], $x, 0, 0,0, imagesx($inner[$i]), imagesy($inner[$i]));
18877a47d83SVincent Tscherter      $x += imagesx($inner[$i])+EBNF_U;
189a345322fSVincent.Tscherter    } return $im;
190a345322fSVincent.Tscherter  } else if ($node->nodeName=='choise') {
191a345322fSVincent.Tscherter    $inner = render_childs($node, $lefttoright);
19277a47d83SVincent Tscherter    $h = (count($inner)-1)*EBNF_U; $w = 0;
193a345322fSVincent.Tscherter    for ($i = 0; $i<count($inner); $i++) {
194a345322fSVincent.Tscherter      $h += imagesy($inner[$i]);
195a345322fSVincent.Tscherter      $w = max($w, imagesx($inner[$i]));
19677a47d83SVincent Tscherter    } $w += 6*EBNF_U; $im = create_image($w, $h); $y = 0;
19777a47d83SVincent Tscherter    imageline($im, 0, EBNF_U, EBNF_U, EBNF_U, $black);
19877a47d83SVincent Tscherter    imageline($im, $w-EBNF_U, EBNF_U, $w, EBNF_U, $black);
199a345322fSVincent.Tscherter    for ($i = 0; $i<count($inner); $i++) {
20077a47d83SVincent Tscherter      imageline($im, EBNF_U, $y+EBNF_U, $w-EBNF_U, $y+EBNF_U, $black);
20177a47d83SVincent Tscherter      imagecopy($im, $inner[$i], 3*EBNF_U, $y, 0,0, imagesx($inner[$i]), imagesy($inner[$i]));
20277a47d83SVincent Tscherter      arrow($im, 3*EBNF_U, $y+EBNF_U, $lefttoright);
20377a47d83SVincent Tscherter      arrow($im, $w-2*EBNF_U, $y+EBNF_U, $lefttoright);
20477a47d83SVincent Tscherter      $top = $y + EBNF_U;
20577a47d83SVincent Tscherter      $y += imagesy($inner[$i])+EBNF_U;
206a345322fSVincent.Tscherter    }
20777a47d83SVincent Tscherter    imageline($im, EBNF_U, EBNF_U, EBNF_U, $top, $black);
20877a47d83SVincent Tscherter    imageline($im, $w-EBNF_U, EBNF_U, $w-EBNF_U, $top, $black);
209a345322fSVincent.Tscherter    return $im;
210a345322fSVincent.Tscherter  } else if ($node->nodeName=='syntax') {
211a345322fSVincent.Tscherter    $title = $node->getAttribute('title');
212a345322fSVincent.Tscherter    $meta = $node->getAttribute('meta');
213a345322fSVincent.Tscherter    $node = $node->firstChild;
214a345322fSVincent.Tscherter    $names = array();
215a345322fSVincent.Tscherter    $images = array();
216a345322fSVincent.Tscherter    while ($node!=null) {
217a345322fSVincent.Tscherter	   $names[] = $node->getAttribute('name');
218a345322fSVincent.Tscherter	   $im = render_node($node->firstChild, $lefttoright);
219a345322fSVincent.Tscherter	   $images[] = $im;
220a345322fSVincent.Tscherter       $node = $node->nextSibling;
22177a47d83SVincent Tscherter    } $wn  = 0; $wr = 0; $h = 5*EBNF_U;
222a345322fSVincent.Tscherter    for ($i = 0; $i<count($images); $i++) {
22377a47d83SVincent Tscherter      $wn = max($wn, imagefontwidth(EBNF_FONT)*strlen($names[$i]));
224a345322fSVincent.Tscherter      $wr = max($wr, imagesx($images[$i]));
22577a47d83SVincent Tscherter	  $h += imagesy($images[$i])+2*EBNF_U;
226a345322fSVincent.Tscherter    }
22777a47d83SVincent Tscherter    if ($title=='') $h -= 2*EBNF_U;
22877a47d83SVincent Tscherter    if ($meta=='') $h -= 2*EBNF_U;
22977a47d83SVincent Tscherter    $w = max($wr+$wn+3*EBNF_U, imagefontwidth(1)*strlen($meta)+2*EBNF_U);
230a345322fSVincent.Tscherter    $im = create_image($w, $h);
23177a47d83SVincent Tscherter    $y = 2*EBNF_U;
232a345322fSVincent.Tscherter    if ($title!='') {
2330f22d919SDamien Regad      imagestring($im, EBNF_FONT, EBNF_U, intdiv(2*EBNF_U-imagefontheight(EBNF_FONT), 2),
234a345322fSVincent.Tscherter      $title, $green);
23577a47d83SVincent Tscherter      imageline($im, 0, 2*EBNF_U, $w, 2*EBNF_U, $green);
23677a47d83SVincent Tscherter      $y += 2*EBNF_U;
237a345322fSVincent.Tscherter    }
238a345322fSVincent.Tscherter    for ($i = 0; $i<count($images); $i++) {
2390f22d919SDamien Regad      imagestring($im, EBNF_FONT, EBNF_U, $y-EBNF_U+intdiv(2*EBNF_U-imagefontheight(EBNF_FONT), 2), $names[$i], $red);
24077a47d83SVincent Tscherter      imagecopy($im, $images[$i], $wn+2*EBNF_U, $y, 0,0, imagesx($images[$i]) , imagesy($images[$i]));
24177a47d83SVincent Tscherter      imageline($im, EBNF_U, $y+EBNF_U, $wn+2*EBNF_U, $y+EBNF_U, $black);
24277a47d83SVincent Tscherter      imageline($im, $wn+2*EBNF_U+imagesx($images[$i])-1, $y+EBNF_U, $w-EBNF_U, $y+EBNF_U, $black);
24377a47d83SVincent Tscherter      imageline($im, $w-EBNF_U, $y+EBNF_U/2, $w-EBNF_U ,$y+1.5*EBNF_U, $black);
24477a47d83SVincent Tscherter      $y += 2*EBNF_U + imagesy($images[$i]);
245a345322fSVincent.Tscherter    }
24677a47d83SVincent Tscherter    imagestring($im, 1, EBNF_U, $h-2*EBNF_U+(2*EBNF_U-imagefontheight(1))/2,
247a345322fSVincent.Tscherter      $meta, $silver);
24877a47d83SVincent Tscherter    rr($im, 0,0,$w-1, $h-1, EBNF_U/2, $green);
249a345322fSVincent.Tscherter    header('Content-Type: image/png');
250a345322fSVincent.Tscherter    imagepng($im);
251a345322fSVincent.Tscherter    return $im;
252a345322fSVincent.Tscherter  }
253a345322fSVincent.Tscherter}
254a345322fSVincent.Tscherter
255a345322fSVincent.Tscherterfunction render_childs($node, $lefttoright) {
256a345322fSVincent.Tscherter   $childs = array();
257a345322fSVincent.Tscherter   $node = $node->firstChild;
258a345322fSVincent.Tscherter   while ($node!=null) {
259a345322fSVincent.Tscherter     $childs[] = render_node($node, $lefttoright);
260a345322fSVincent.Tscherter     $node = $node->nextSibling;
261a345322fSVincent.Tscherter   } return $childs;
262a345322fSVincent.Tscherter}
263a345322fSVincent.Tscherter
264a345322fSVincent.Tscherterfunction ebnf_scan(&$input) {
265a345322fSVincent.Tscherter  global $ebnf_lexemes;
266a345322fSVincent.Tscherter  $i = 0; $n = strlen($input); $m = count($ebnf_lexemes); $tokens = array();
267a345322fSVincent.Tscherter  while ($i < $n) {
268a345322fSVincent.Tscherter    $j = 0;
269a345322fSVincent.Tscherter    while ($j < $m &&
270a345322fSVincent.Tscherter      preg_match("/^{$ebnf_lexemes[$j]['expr']}/", substr($input,$i), $matches)==0) $j++;
271a345322fSVincent.Tscherter    if ($j<$m) {
272a345322fSVincent.Tscherter      if ($ebnf_lexemes[$j]['type']!=EBNF_WHITESPACE_TOKEN)
273a345322fSVincent.Tscherter        $tokens[] = array('type' => $ebnf_lexemes[$j]['type'],
274a345322fSVincent.Tscherter          'value' => $matches[0], 'pos' => $i);
275a345322fSVincent.Tscherter      $i += strlen($matches[0]);
276a345322fSVincent.Tscherter	} else
2775a779085SDamien Regad	  throw new EbnfException("Invalid token at position", $i);
278a345322fSVincent.Tscherter  } return $tokens;
279a345322fSVincent.Tscherter}
280a345322fSVincent.Tscherter
281a345322fSVincent.Tscherter
282a345322fSVincent.Tscherterfunction ebnf_check_token($token, $type, $value) {
283a345322fSVincent.Tscherter  return $token['type']==$type && $token['value']==$value;
284a345322fSVincent.Tscherter}
285a345322fSVincent.Tscherter
286a345322fSVincent.Tscherterfunction ebnf_parse_syntax(&$tokens) {
287a345322fSVincent.Tscherter  $dom = new DOMDocument();
288a345322fSVincent.Tscherter  $syntax = $dom->createElement("syntax");
289a345322fSVincent.Tscherter  $syntax->setAttribute('meta', META);
290a345322fSVincent.Tscherter  $dom->appendChild($syntax);
291a345322fSVincent.Tscherter  $i = 0; $token = $tokens[$i++];
292a345322fSVincent.Tscherter  if ($token['type'] == EBNF_LITERAL_TOKEN) {
293a345322fSVincent.Tscherter    $syntax->setAttribute('title',
294a345322fSVincent.Tscherter      stripcslashes(substr($token['value'], 1, strlen($token['value'])-2 )));
295a345322fSVincent.Tscherter    $token = $tokens[$i++];
296a345322fSVincent.Tscherter  }
297a345322fSVincent.Tscherter  if (!ebnf_check_token($token, EBNF_OPERATOR_TOKEN, '{') )
2985a779085SDamien Regad    throw new EbnfException("Syntax must start with '{'", $token['pos']);
299a345322fSVincent.Tscherter  $token = $tokens[$i];
300a345322fSVincent.Tscherter  while ($i < count($tokens) && $token['type'] == EBNF_IDENTIFIER_TOKEN) {
301a345322fSVincent.Tscherter    $syntax->appendChild(ebnf_parse_production($dom, $tokens, $i));
302a345322fSVincent.Tscherter    if ($i<count($tokens)) $token = $tokens[$i];
303a345322fSVincent.Tscherter  } $i++; if (!ebnf_check_token($token, EBNF_OPERATOR_TOKEN, '}'))
3045a779085SDamien Regad    throw new EbnfException("Syntax must end with '}'", $tokens[count($tokens)-1]['pos']);
305a345322fSVincent.Tscherter  if ($i<count($tokens)) {
306a345322fSVincent.Tscherter    $token = $tokens[$i];
307a345322fSVincent.Tscherter    if ($token['type'] == EBNF_LITERAL_TOKEN) {
308a345322fSVincent.Tscherter      $syntax->setAttribute('meta',
309a345322fSVincent.Tscherter        stripcslashes(substr($token['value'], 1, strlen($token['value'])-2 )));
310a345322fSVincent.Tscherter    }
311a345322fSVincent.Tscherter  }
312a345322fSVincent.Tscherter    return $dom;
313a345322fSVincent.Tscherter}
314a345322fSVincent.Tscherter
315a345322fSVincent.Tscherterfunction ebnf_parse_production(&$dom, &$tokens, &$i) {
316a345322fSVincent.Tscherter  $token = $tokens[$i++];
317a345322fSVincent.Tscherter  if ($token['type']!=EBNF_IDENTIFIER_TOKEN)
3185a779085SDamien Regad    throw new EbnfException("Production must start with an identifier'{'", $token['pos']);
319a345322fSVincent.Tscherter  $production = $dom->createElement("rule");
320a345322fSVincent.Tscherter  $production->setAttribute('name', $token['value']);
321a345322fSVincent.Tscherter  $token = $tokens[$i++];
322a345322fSVincent.Tscherter  if (!ebnf_check_token($token, EBNF_OPERATOR_TOKEN, "="))
3235a779085SDamien Regad    throw new EbnfException("Identifier must be followed by '='", $token['pos']);
324a345322fSVincent.Tscherter  $production->appendChild( ebnf_parse_expression($dom, $tokens, $i));
325a345322fSVincent.Tscherter  $token = $tokens[$i++];
326a345322fSVincent.Tscherter  if (!ebnf_check_token($token, EBNF_OPERATOR_TOKEN, '.')
327a345322fSVincent.Tscherter    && !ebnf_check_token($token, EBNF_OPERATOR_TOKEN, ';'))
3285a779085SDamien Regad    throw new EbnfException("Rule must end with '.' or ';'", $token['pos']);
329a345322fSVincent.Tscherter  return $production;
330a345322fSVincent.Tscherter}
331a345322fSVincent.Tscherter
332a345322fSVincent.Tscherterfunction ebnf_parse_expression(&$dom, &$tokens, &$i) {
333a345322fSVincent.Tscherter  $choise = $dom->createElement("choise");
334a345322fSVincent.Tscherter  $choise->appendChild(ebnf_parse_term($dom, $tokens, $i));
335a345322fSVincent.Tscherter  $token=$tokens[$i]; $mul = false;
336a345322fSVincent.Tscherter  while (ebnf_check_token($token, EBNF_OPERATOR_TOKEN, '|')) {
337a345322fSVincent.Tscherter    $i++;
338a345322fSVincent.Tscherter    $choise->appendChild(ebnf_parse_term($dom, $tokens, $i));
339a345322fSVincent.Tscherter    $token=$tokens[$i]; $mul = true;
340a345322fSVincent.Tscherter  } return $mul ? $choise : $choise->removeChild($choise->firstChild);
341a345322fSVincent.Tscherter}
342a345322fSVincent.Tscherter
343a345322fSVincent.Tscherterfunction ebnf_parse_term(&$dom, &$tokens, &$i) {
344a345322fSVincent.Tscherter  $sequence = $dom->createElement("sequence");
345a345322fSVincent.Tscherter  $factor = ebnf_parse_factor($dom, $tokens, $i);
346a345322fSVincent.Tscherter  $sequence->appendChild($factor);
347a345322fSVincent.Tscherter  $token=$tokens[$i]; $mul = false;
348a345322fSVincent.Tscherter  while ($token['value']!='.' && $token['value']!='=' && $token['value']!='|'
349a345322fSVincent.Tscherter    && $token['value']!=')' && $token['value']!=']' && $token['value']!='}') {
350a345322fSVincent.Tscherter    $sequence->appendChild(ebnf_parse_factor($dom, $tokens, $i));
351a345322fSVincent.Tscherter    $token=$tokens[$i]; $mul = true;
352a345322fSVincent.Tscherter  } return $mul ? $sequence: $sequence->removeChild($sequence->firstChild);
353a345322fSVincent.Tscherter}
354a345322fSVincent.Tscherter
355a345322fSVincent.Tscherterfunction ebnf_parse_factor(&$dom, &$tokens, &$i) {
356a345322fSVincent.Tscherter  $token = $tokens[$i++];
357a345322fSVincent.Tscherter  if ($token['type']==EBNF_IDENTIFIER_TOKEN) {
358a345322fSVincent.Tscherter    $identifier = $dom->createElement("identifier");
359a345322fSVincent.Tscherter    $identifier->setAttribute('value', $token['value']);
360a345322fSVincent.Tscherter    return $identifier;
361a345322fSVincent.Tscherter  } if ($token['type']==EBNF_LITERAL_TOKEN){
362a345322fSVincent.Tscherter    $literal = $dom->createElement("terminal");
363a345322fSVincent.Tscherter    $literal->setAttribute('value', stripcslashes(substr($token['value'], 1, strlen($token['value'])-2 )));
364a345322fSVincent.Tscherter    return $literal;
365a345322fSVincent.Tscherter  } if (ebnf_check_token($token, EBNF_OPERATOR_TOKEN, '(')) {
366a345322fSVincent.Tscherter    $expression = ebnf_parse_expression($dom, $tokens, $i);
367a345322fSVincent.Tscherter    $token = $tokens[$i++];
368a345322fSVincent.Tscherter    if (!ebnf_check_token($token, EBNF_OPERATOR_TOKEN, ')'))
3695a779085SDamien Regad      throw new EbnfException("Group must end with ')'", $token['pos']);
370a345322fSVincent.Tscherter    return $expression;
371a345322fSVincent.Tscherter  } if (ebnf_check_token($token, EBNF_OPERATOR_TOKEN, '[')) {
372a345322fSVincent.Tscherter    $option = $dom->createElement("option");
373a345322fSVincent.Tscherter    $option->appendChild(ebnf_parse_expression($dom, $tokens, $i));
374a345322fSVincent.Tscherter    $token = $tokens[$i++];
375a345322fSVincent.Tscherter    if (!ebnf_check_token($token, EBNF_OPERATOR_TOKEN, ']'))
3765a779085SDamien Regad      throw new EbnfException("Option must end with ']'", $token['pos']);
377a345322fSVincent.Tscherter    return $option;
378a345322fSVincent.Tscherter  } if (ebnf_check_token($token, EBNF_OPERATOR_TOKEN, '{')) {
379a345322fSVincent.Tscherter    $loop = $dom->createElement("loop");
380a345322fSVincent.Tscherter    $loop->appendChild(ebnf_parse_expression($dom, $tokens, $i));
381a345322fSVincent.Tscherter    $token = $tokens[$i++];
382a345322fSVincent.Tscherter    if (!ebnf_check_token($token, EBNF_OPERATOR_TOKEN, '}'))
3835a779085SDamien Regad      throw new EbnfException("Loop must end with '}'", $token['pos']);
384a345322fSVincent.Tscherter    return $loop;
385a345322fSVincent.Tscherter  }
3865a779085SDamien Regad  throw new EbnfException("Factor expected", $token['pos']);
3875a779085SDamien Regad}
3885a779085SDamien Regad
3895a779085SDamien Regadclass EbnfException extends Exception {
3905a779085SDamien Regad    protected int $pos;
3915a779085SDamien Regad
3925a779085SDamien Regad    public function __construct($message, $pos) {
3935a779085SDamien Regad        $this->pos = $pos;
3945a779085SDamien Regad        parent::__construct($message . ": $pos");
3955a779085SDamien Regad    }
3965a779085SDamien Regad
3975a779085SDamien Regad    public function getPos() {
3985a779085SDamien Regad        return $this->pos;
3995a779085SDamien Regad    }
400a345322fSVincent.Tscherter}
401