Generátor parserů pro PHP

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 PEAR.net:

PHP_Parser­Generator 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