Generátor parserů pro PHP – ukázkový příklad
Chtěli by jste si vytvořit vlastní mikroformát pro svoji webovou aplikaci? Tento návod vám s tím pomůže.
Kde sehnat potřebné nástroje?
Pro PHP existují generátory parserů and lexerů, které lze stáhnout na http://pear.php.net:
- PHP_LexerGenerator (0.3.4)
- PHP_ParserGenerator (0.1.5)
- code-substitute-fix.patch – oprava drobného problému se substitucí kódu @X
PHP_ParserGenerator je push parser ala lemon, takže ve smyčce voláme lexer a tlačíme tokeny do parseru, který je následně zpracovává.
Kód parseru
Kompletní zdrojové kódy si můžete také stáhnout.
/* vim: set ft=php : <? */ %name items_parser %declare_class {class items_parser} %syntax_error { foreach ($this->yy_get_expected_tokens($yymajor) as $token) $expect[] = self::$yyTokenName[$token]; throw new Exception('Unexpected ' . $this->tokenName($yymajor) . '(' . $TOKEN . ') on line '.$this->line.', expected one of: ' . implode(',', $expect)); } %include { /* AST */ class options_list { public $list; public function __construct() { $this->list = array(); } public function add($key, $value) { $this->list[$key][] = $value; } public function __get($name) { if (isset($this->list[$name])) return $this->list[$name][0]; } } class data { public $items = array(); public function add($name, $opts) { $this->items[$name] = $opts; } } } %include_class { private $token; private $value; private $data; private $N; private $line; private $root_expr; public function parse($str) { $this->data = $str; $this->N = 0; $this->line = 1; $this->root = new data; try { while ($this->yylex()) { if ($this->token < 0) continue; $this->doParse($this->token, $this->value); } $this->doParse(0, 0); return $this->root; } catch (Exception $ex) { throw $ex; return null; } } /*!lex2php %input $this->data %counter $this->N %token $this->token %value $this->value %line $this->line ident = /[a-zA-Z_][a-z_A-Z0-9-]{0,}/ number = /[0-9]+/ string = /"(""|[^"]+)*"/ whitespace = /[ \t\n]+/ comment = /\/\*[\s\S]*?\*\// any = /./ */ /*!lex2php number { $this->token = self::INTEGER; } string { $this->token = self::STRING; } "[" { $this->token = self::LSB; } "]" { $this->token = self::RSB; } ident { $this->token = self::IDENT; if ($this->value == 'item') $this->token = self::ITEM; } comment { $this->token = -1; } whitespace { $this->token = -1; } any { return false; } */ } start ::= items. /* items */ items ::= item. items ::= items item. item ::= ITEM name(N) opt_options(O). { $this->root->add(N, O); } /* options */ opt_options(Y) ::= . { Y = new options_list; } opt_options(Y) ::= options(X). { Y = X; } options(Y) ::= LSB options_list(X) RSB. { Y = X; } options(Y) ::= LSB RSB. { Y = new options_list; } options_list(Y) ::= option(X). { Y = new options_list; Y->add(X[0], X[1]); } options_list(Y) ::= options_list(L) option(X). { Y = L; Y->add(X[0], X[1]); } option(Y) ::= name(K) option_value(X). { Y = array(K, X); } option_value(Y) ::= . { Y = true; } option_value(Y) ::= string(X). { Y = X; } option_value(Y) ::= integer(X). { Y = X; } option_value(Y) ::= options(X). { Y = X; } /* basic terms */ string(Y) ::= STRING(S). { Y = str_replace('""', '"', substr(S, 1, strlen(S) - 2)); } integer(Y) ::= INTEGER(I). { Y = (int)I; } name(Y) ::= IDENT(I). { Y = I; }
Ukázka použití parseru
Nejprve musíme parser zkompilovat. To provedeme pomocí následujících příkazů:
#!/bin/sh plex parser.yl mv parser.php parser.y phplemon parser.y rm -f parser.out rm -f parser.y php -w parser.php > parser-strip.php mv parser-strip.php parser.php
Nyní můžeme parser použít k parsování textu:
<?php require_once "parser.php"; $p = new items_parser(); $data = $p->parse(' item item_x [ opt_str "asdasd" opt_flag opt_int 100 ] item item_y [deleted] '); print_r($data); foreach ($data->items as $name => $item) echo "$name : " . ($item->deleted ? "deleted" : "not deleted") . "\n"; ?>
Výstup:
data Object
(
[items] => Array
(
[item_x] => options_list Object
(
[list] => Array
(
[opt_str] => Array
(
[0] => asdasd
)
[opt_flag] => Array
(
[0] => 1
)
[opt_int] => Array
(
[0] => 100
)
)
)
[item_y] => options_list Object
(
[list] => Array
(
[deleted] => Array
(
[0] => 1
)
)
)
)
)
item_x : not deleted
item_y : deleted
