====== EBNF Parser & Syntax Diagram Renderer ====== The //EBNF Parser & Syntax Diagram Renderer// can be used stand-alone or as Dokuwiki-Plugin. ===== Download and Install===== * https://www.dokuwiki.org/plugin:ebnf * Requirements: Make sure to have the [[http://www.php.net/manual/en/image.requirements.php|GD library]] ready :!: ===== How to use ===== "EBNF defined in itself" { syntax = [ title ] "{" { production } "}" [ comment ]. production = identifier "=" expression ( "." | ";" ) . expression = term { "|" term } . term = factor { factor } . factor = identifier | literal | "[" expression "]" | "(" expression ")" | "{" expression "}" . identifier = character { character } . title = literal . comment = literal . literal = "'" character { character } "'" | '"' character { character } '"' . } "EBNF defined in itself" { syntax = [ title ] "{" { production } "}" [ comment ]. production = identifier "=" expression ( "." | ";" ) . expression = term { "|" term } . term = factor { factor } . factor = identifier | literal | "[" expression "]" | "(" expression ")" | "{" expression "}" . identifier = character { character } . title = literal . comment = literal . literal = "'" character { character } "'" | '"' character { character } '"' . } The script is also available as stand-alone version under: http://karmin.ch/ebnf.php http://karmin.ch/ebnf.php?syntax="EBNF"{ hello = "Hello World". }"Some additional Comments" Note: The stand-alone script supports also XML output http://karmin.ch/ebnf.php?syntax={ hello = "EBNF Hello World in XML". }&format=xml ===== Syntax ===== * See examples: [[examples]] * [[wp>EBNF]] * [[wp>Wirth_syntax_notation]] ===== Todo & Ideas ===== * Ellipse ... for enumerations. E.g. ''digits'': * temp hack in v0.2: ''function render_node($node, $lefttoright)'' ... else { if ($text!="...") rr($im, UNIT, 0, $w-UNIT-1, $h-1, UNIT/2, $black); imagestring($im, FONT, 2*UNIT, ($h-imagefontheight(FONT))/2, $text, $text!="..."?$blue:$black); } ... * Other fonts * Configurable dimensions * escape-chars and or unicode in terminals * Adding //Regexp// beside terminal and identifiers * using a lightweight syntaxtree instead DOM * using some standard parsing mechanism * support for comments & special-sequences ''? ... ?'' * http://www.cs.man.ac.uk/~pjj/bnf/ebnf.html * replace antialias function ===== Code ===== ebnf syntax * * @license GPL3 * @author Vincent Tscherter * @version 0.1 */ if(!defined('DOKU_INC')) define('DOKU_INC',realpath(dirname(__FILE__).'/../../').'/'); if(!defined('DOKU_PLUGIN')) define('DOKU_PLUGIN',DOKU_INC.'lib/plugins/'); require_once(DOKU_PLUGIN.'syntax.php'); class syntax_plugin_ebnf extends DokuWiki_Syntax_Plugin { function getType(){ return 'substition'; } function getSort(){ return 999; } function connectTo($mode) { $this->Lexer->addSpecialPattern('.*?',$mode,'plugin_ebnf'); } function handle($match, $state, $pos, &$handler){ switch ($state) { case DOKU_LEXER_ENTER : break; case DOKU_LEXER_MATCHED : break; case DOKU_LEXER_UNMATCHED : break; case DOKU_LEXER_EXIT : break; case DOKU_LEXER_SPECIAL : break; } return array($match); } function render($mode, &$renderer, $data) { if($mode == 'xhtml'){ try { $text = substr($data[0], 6, strlen($data[0])-13); $text = preg_replace( "/[<>]+/", "", $text); $text = preg_replace( "/[\\n\\r\\t ]+/", " ", $text); $text = urlencode($text); $renderer->doc .= "$text"; // ptype = 'normal' } catch (Exception $e) { $renderer->doc .= "
".htmlentities($text)."\n".$e."
"; } return true; } return false; } } ?>
. Vincent Tscherter, tscherter@karmin.ch, Solothurn, 2009-01-18 2009-01-18 version 0.1 first release 2009-01-02 version 0.2 - title und comment literal added - ";" als terminator-symbol added */ error_reporting(E_ALL|E_STRICT); // define('META', 'xis/ebnf v0.2 https://www.dokuwiki.org/plugin:ebnf gpl3'); // parser define('EBNF_OPERATOR_TOKEN', 1); define('EBNF_LITERAL_TOKEN', 2); define('EBNF_WHITESPACE_TOKEN', 3); define('EBNF_IDENTIFIER_TOKEN', 4); // rendering define('FONT', 3); define('UNIT', 10); define('AW', 3); // lexemes $ebnf_lexemes[] = array( 'type' => EBNF_OPERATOR_TOKEN, 'expr' => '[={}()|.;[\]]' ); $ebnf_lexemes[] = array( 'type' => EBNF_LITERAL_TOKEN, 'expr' => "\"[^\"]*\"" ); $ebnf_lexemes[] = array( 'type' => EBNF_LITERAL_TOKEN, 'expr' => "'[^']*'" ); $ebnf_lexemes[] = array( 'type' => EBNF_IDENTIFIER_TOKEN, 'expr' => "[a-zA-Z0-9_-]+" ); $ebnf_lexemes[] = array( 'type' => EBNF_WHITESPACE_TOKEN, 'expr' => "\\s+" ); // input example $input = <<saveXML(); } else { render_node($dom->firstChild, true); } } catch (Exception $e) { header('Content-Type: text/plain'); $dom = new DOMDocument(); $syntax = $dom->createElement("syntax"); $syntax->setAttribute('title', 'EBNF - Syntax Error'); $syntax->setAttribute('meta', $e->getMessage()); $dom->appendChild($syntax); render_node($dom->firstChild, true); } function rr($im, $x1, $y1, $x2, $y2, $r, $black){ imageline($im, $x1+$r, $y1, $x2-$r, $y1, $black); imageline($im, $x1+$r, $y2, $x2-$r, $y2, $black); imageline($im, $x1, $y1+$r, $x1, $y2-$r, $black); imageline($im, $x2, $y1+$r, $x2, $y2-$r, $black); imagearc($im, $x1+$r, $y1+$r, 2*$r, 2*$r, 180, 270, $black); imagearc($im, $x2-$r, $y1+$r, 2*$r, 2*$r, 270, 360, $black); imagearc($im, $x1+$r, $y2-$r, 2*$r, 2*$r, 90, 180, $black); imagearc($im, $x2-$r, $y2-$r, 2*$r, 2*$r, 0, 90, $black); } function create_image($w, $h) { global $white, $black, $blue, $red, $green, $silver; $im = imagecreatetruecolor($w, $h) or die("no img"); imageantialias($im, true); $white = imagecolorallocate ($im, 255, 255, 255); $black = imagecolorallocate ($im, 0, 0, 0); $blue = imagecolorallocate ($im, 0, 0, 255); $red = imagecolorallocate ($im, 255, 0, 0); $green = imagecolorallocate ($im, 0, 200, 0); $silver = imagecolorallocate ($im, 127, 127, 127); imagefilledrectangle($im, 0,0,$w,$h,$white); return $im; } function arrow($image, $x, $y, $lefttoright) { global $white, $black; if (!$lefttoright) imagefilledpolygon($image, array($x, $y-UNIT/3, $x-UNIT, $y, $x, $y+UNIT/3), 3, $black); else imagefilledpolygon($image, array($x-UNIT, $y-UNIT/3, $x, $y, $x-UNIT, $y+UNIT/3), 3, $black); } function render_node($node, $lefttoright) { global $white, $black, $blue, $red, $green, $silver; if ($node->nodeName=='identifier' || $node->nodeName=='terminal') { $text = $node->getAttribute('value'); $w = imagefontwidth(FONT)*(strlen($text)) + 4*UNIT; $h = 2*UNIT; $im = create_image($w, $h); if ($node->nodeName!='terminal') { imagerectangle($im, UNIT, 0, $w-UNIT-1, $h-1, $black); imagestring($im, FONT, 2*UNIT, ($h-imagefontheight(FONT))/2, $text, $red); } else { if ($text!="...") rr($im, UNIT, 0, $w-UNIT-1, $h-1, UNIT/2, $black); imagestring($im, FONT, 2*UNIT, ($h-imagefontheight(FONT))/2, $text, $text!="..."?$blue:$black); } imageline($im,0,UNIT, UNIT, UNIT, $black); imageline($im,$w-UNIT,UNIT, $w+1, UNIT, $black); return $im; } else if ($node->nodeName=='option' || $node->nodeName=='loop') { if ($node->nodeName=='loop') $lefttoright = ! $lefttoright; $inner = render_node($node->firstChild, $lefttoright); $w = imagesx($inner)+6*UNIT; $h = imagesy($inner)+2*UNIT; $im = create_image($w, $h); imagecopy($im, $inner, 3*UNIT, 2*UNIT, 0,0, imagesx($inner), imagesy($inner)); imageline($im,0,UNIT, $w, UNIT, $black); arrow($im, $w/2+UNIT/2, UNIT, $node->nodeName=='loop'?!$lefttoright:$lefttoright); arrow($im, 3*UNIT, 3*UNIT, $lefttoright); arrow($im, $w-2*UNIT, 3*UNIT, $lefttoright); imageline($im,UNIT,UNIT, UNIT, 3*UNIT, $black); imageline($im,UNIT,3*UNIT, 2*UNIT, 3*UNIT, $black); imageline($im,$w-UNIT,UNIT, $w-UNIT, 3*UNIT, $black); imageline($im,$w-3*UNIT-1,3*UNIT, $w-UNIT, 3*UNIT, $black); return $im; } else if ($node->nodeName=='sequence') { $inner = render_childs($node, $lefttoright); if (!$lefttoright) $inner = array_reverse($inner); $w = count($inner)*UNIT-UNIT; $h = 0; for ($i = 0; $inodeName=='choise') { $inner = render_childs($node, $lefttoright); $h = (count($inner)-1)*UNIT; $w = 0; for ($i = 0; $inodeName=='syntax') { $title = $node->getAttribute('title'); $meta = $node->getAttribute('meta'); $node = $node->firstChild; $names = array(); $images = array(); while ($node!=null) { $names[] = $node->getAttribute('name'); $im = render_node($node->firstChild, $lefttoright); $images[] = $im; $node = $node->nextSibling; } $wn = 0; $wr = 0; $h = 5*UNIT; for ($i = 0; $ifirstChild; while ($node!=null) { $childs[] = render_node($node, $lefttoright); $node = $node->nextSibling; } return $childs; } function ebnf_scan(&$input) { global $ebnf_lexemes; $i = 0; $n = strlen($input); $m = count($ebnf_lexemes); $tokens = array(); while ($i < $n) { $j = 0; while ($j < $m && preg_match("/^{$ebnf_lexemes[$j]['expr']}/", substr($input,$i), $matches)==0) $j++; if ($j<$m) { if ($ebnf_lexemes[$j]['type']!=EBNF_WHITESPACE_TOKEN) $tokens[] = array('type' => $ebnf_lexemes[$j]['type'], 'value' => $matches[0], 'pos' => $i); $i += strlen($matches[0]); } else throw new Exception("Invalid token at position: $i"); } return $tokens; } function ebnf_check_token($token, $type, $value) { return $token['type']==$type && $token['value']==$value; } function ebnf_parse_syntax(&$tokens) { $dom = new DOMDocument(); $syntax = $dom->createElement("syntax"); $syntax->setAttribute('meta', META); $dom->appendChild($syntax); $i = 0; $token = $tokens[$i++]; if ($token['type'] == EBNF_LITERAL_TOKEN) { $syntax->setAttribute('title', stripcslashes(substr($token['value'], 1, strlen($token['value'])-2 ))); $token = $tokens[$i++]; } if (!ebnf_check_token($token, EBNF_OPERATOR_TOKEN, '{') ) throw new Exception("Syntax must start with '{': {$token['pos']}"); $token = $tokens[$i]; while ($i < count($tokens) && $token['type'] == EBNF_IDENTIFIER_TOKEN) { $syntax->appendChild(ebnf_parse_production($dom, $tokens, $i)); if ($isetAttribute('meta', stripcslashes(substr($token['value'], 1, strlen($token['value'])-2 ))); } } return $dom; } function ebnf_parse_production(&$dom, &$tokens, &$i) { $token = $tokens[$i++]; if ($token['type']!=EBNF_IDENTIFIER_TOKEN) throw new Exception("Production must start with an identifier'{': {$token['pos']}"); $production = $dom->createElement("rule"); $production->setAttribute('name', $token['value']); $token = $tokens[$i++]; if (!ebnf_check_token($token, EBNF_OPERATOR_TOKEN, "=")) throw new Exception("Identifier must be followed by '=': {$token['pos']}"); $production->appendChild( ebnf_parse_expression($dom, $tokens, $i)); $token = $tokens[$i++]; if (!ebnf_check_token($token, EBNF_OPERATOR_TOKEN, '.') && !ebnf_check_token($token, EBNF_OPERATOR_TOKEN, ';')) throw new Exception("Rule must end with '.' or ';' : {$token['pos']}"); return $production; } function ebnf_parse_expression(&$dom, &$tokens, &$i) { $choise = $dom->createElement("choise"); $choise->appendChild(ebnf_parse_term($dom, $tokens, $i)); $token=$tokens[$i]; $mul = false; while (ebnf_check_token($token, EBNF_OPERATOR_TOKEN, '|')) { $i++; $choise->appendChild(ebnf_parse_term($dom, $tokens, $i)); $token=$tokens[$i]; $mul = true; } return $mul ? $choise : $choise->removeChild($choise->firstChild); } function ebnf_parse_term(&$dom, &$tokens, &$i) { $sequence = $dom->createElement("sequence"); $factor = ebnf_parse_factor($dom, $tokens, $i); $sequence->appendChild($factor); $token=$tokens[$i]; $mul = false; while ($token['value']!='.' && $token['value']!='=' && $token['value']!='|' && $token['value']!=')' && $token['value']!=']' && $token['value']!='}') { $sequence->appendChild(ebnf_parse_factor($dom, $tokens, $i)); $token=$tokens[$i]; $mul = true; } return $mul ? $sequence: $sequence->removeChild($sequence->firstChild); } function ebnf_parse_factor(&$dom, &$tokens, &$i) { $token = $tokens[$i++]; if ($token['type']==EBNF_IDENTIFIER_TOKEN) { $identifier = $dom->createElement("identifier"); $identifier->setAttribute('value', $token['value']); return $identifier; } if ($token['type']==EBNF_LITERAL_TOKEN){ $literal = $dom->createElement("terminal"); $literal->setAttribute('value', stripcslashes(substr($token['value'], 1, strlen($token['value'])-2 ))); return $literal; } if (ebnf_check_token($token, EBNF_OPERATOR_TOKEN, '(')) { $expression = ebnf_parse_expression($dom, $tokens, $i); $token = $tokens[$i++]; if (!ebnf_check_token($token, EBNF_OPERATOR_TOKEN, ')')) throw new Exception("Group must end with ')': {$token['pos']}"); return $expression; } if (ebnf_check_token($token, EBNF_OPERATOR_TOKEN, '[')) { $option = $dom->createElement("option"); $option->appendChild(ebnf_parse_expression($dom, $tokens, $i)); $token = $tokens[$i++]; if (!ebnf_check_token($token, EBNF_OPERATOR_TOKEN, ']')) throw new Exception("Option must end with ']': {$token['pos']}"); return $option; } if (ebnf_check_token($token, EBNF_OPERATOR_TOKEN, '{')) { $loop = $dom->createElement("loop"); $loop->appendChild(ebnf_parse_expression($dom, $tokens, $i)); $token = $tokens[$i++]; if (!ebnf_check_token($token, EBNF_OPERATOR_TOKEN, '}')) throw new Exception("Loop must end with '}': {$token['pos']}"); return $loop; } throw new Exception("Factor expected: {$token['pos']}"); } ?>