phpDocumentor PHP_LexerGenerator
[ class tree: PHP_LexerGenerator ] [ index: PHP_LexerGenerator ] [ all elements ]

Source for file Lexer.php

Documentation is available at Lexer.php

  1. <?php
  2. /**
  3.  * PHP_LexerGenerator, a php 5 lexer generator.
  4.  * 
  5.  * This lexer generator translates a file in a format similar to
  6.  * re2c ({@link http://re2c.org}) and translates it into a PHP 5-based lexer
  7.  *
  8.  * PHP version 5
  9.  *
  10.  * LICENSE: This source file is subject to version 3.01 of the PHP license
  11.  * that is available through the world-wide-web at the following URI:
  12.  * http://www.php.net/license/3_01.txt.  If you did not receive a copy of
  13.  * the PHP License and are unable to obtain it through the web, please
  14.  * send a note to license@php.net so we can mail you a copy immediately.
  15.  *
  16.  * @category   php
  17.  * @package    PHP_LexerGenerator
  18.  * @author     Gregory Beaver <cellog@php.net>
  19.  * @copyright  2006 Gregory Beaver
  20.  * @license    http://www.php.net/license/3_01.txt  PHP License 3.01
  21.  * @version    CVS: $Id$
  22.  * @since      File available since Release 0.1.0
  23.  */
  24. require_once 'PHP/LexerGenerator/Parser.php';
  25. /**
  26.  * Token scanner for plex files.
  27.  * 
  28.  * This scanner detects comments beginning with "/*!lex2php" and
  29.  * then returns their components (processing instructions, patterns, strings
  30.  * action code, and regexes)
  31.  * @package    PHP_LexerGenerator
  32.  * @author     Gregory Beaver <cellog@php.net>
  33.  * @copyright  2006 Gregory Beaver
  34.  * @license    http://www.php.net/license/3_01.txt  PHP License 3.01
  35.  * @version    @package_version@
  36.  * @since      Class available since Release 0.1.0
  37.  */
  38. {
  39.     private $data;
  40.     private $N;
  41.     private $state;
  42.     /**
  43.      * Current line number in input
  44.      * @var int 
  45.      */
  46.     public $line;
  47.     /**
  48.      * Number of scanning errors detected
  49.      * @var int 
  50.      */
  51.     public $errors = 0;
  52.     /**
  53.      * integer identifier of the current token
  54.      * @var int 
  55.      */
  56.     public $token;
  57.     /**
  58.      * string content of current token
  59.      * @var string 
  60.      */
  61.     public $value;
  62.  
  63.     const PHPCODE PHP_LexerGenerator_Parser::PHPCODE;
  64.     const COMMENTSTART PHP_LexerGenerator_Parser::COMMENTSTART;
  65.     const COMMENTEND PHP_LexerGenerator_Parser::COMMENTEND;
  66.     const QUOTE PHP_LexerGenerator_Parser::QUOTE;
  67.     const PATTERN PHP_LexerGenerator_Parser::PATTERN;
  68.     const CODE PHP_LexerGenerator_Parser::CODE;
  69.     const SUBPATTERN PHP_LexerGenerator_Parser::SUBPATTERN;
  70.     const PI PHP_LexerGenerator_Parser::PI;
  71.  
  72.     /**
  73.      * prepare scanning
  74.      * @param string the input
  75.      */
  76.     function __construct($data)
  77.     {
  78.         $this->data str_replace("\r\n""\n"$data);
  79.         $this->0;
  80.         $this->line = 1;
  81.         $this->state 'Start';
  82.         $this->errors = 0;
  83.     }
  84.  
  85.     /**
  86.      * Output an error message
  87.      * @param string 
  88.      */
  89.     private function error($msg)
  90.     {
  91.         echo 'Error on line ' $this->line . ': ' $msg;
  92.         $this->errors++;
  93.     }
  94.  
  95.     /**
  96.      * Initial scanning state lexer
  97.      * @return boolean 
  98.      */
  99.     private function lexStart()
  100.     {
  101.         if ($this->>= strlen($this->data)) {
  102.             return false;
  103.         }
  104.         $a strpos($this->data'/*!lex2php' "\n"$this->N);
  105.         if ($a === false{
  106.             $this->value = substr($this->data$this->N);
  107.             $this->strlen($this->data);
  108.             $this->token = self::PHPCODE;
  109.             return true;
  110.         }
  111.         if ($a $this->N{
  112.             $this->value = substr($this->data$this->N$a $this->N);
  113.             $this->$a;
  114.             $this->token = self::PHPCODE;
  115.             return true;
  116.         }
  117.         $this->value = '/*!lex2php' "\n";
  118.         $this->+= 11// strlen("/*lex2php\n")
  119.         $this->token = self::COMMENTSTART;
  120.         $this->state 'Declare';
  121.         return true;
  122.     }
  123.  
  124.     /**
  125.      * lexer for top-level canning state after the initial declaration comment
  126.      * @return boolean 
  127.      */
  128.     private function lexStartNonDeclare()
  129.     {
  130.         if ($this->>= strlen($this->data)) {
  131.             return false;
  132.         }
  133.         $a strpos($this->data'/*!lex2php' "\n"$this->N);
  134.         if ($a === false{
  135.             $this->value = substr($this->data$this->N);
  136.             $this->strlen($this->data);
  137.             $this->token = self::PHPCODE;
  138.             return true;
  139.         }
  140.         if ($a $this->N{
  141.             $this->value = substr($this->data$this->N$a $this->N);
  142.             $this->$a;
  143.             $this->token = self::PHPCODE;
  144.             return true;
  145.         }
  146.         $this->value = '/*!lex2php' "\n";
  147.         $this->+= 11// strlen("/*lex2php\n")
  148.         $this->token = self::COMMENTSTART;
  149.         $this->state 'Rule';
  150.         return true;
  151.     }
  152.  
  153.     /**
  154.      * lexer for declaration comment state
  155.      * @return boolean 
  156.      */
  157.     private function lexDeclare()
  158.     {
  159.         if ($this->data[$this->N== '*' && $this->data[$this->1== '/'{
  160.             $this->state 'StartNonDeclare';
  161.             $this->value = '*/';
  162.             $this->+= 2;
  163.             $this->token = self::COMMENTEND;
  164.             return true;
  165.         }
  166.         if (preg_match('/^%([a-z]+)/'substr($this->data$this->N)$token)) {
  167.             $this->value = $token[1];
  168.             $this->+= strlen($token[1]1;
  169.             $this->state 'DeclarePI';
  170.             $this->token = self::PI;
  171.             return true;
  172.         }
  173.         if (preg_match('/^[a-zA-Z_]+/'substr($this->data$this->N)$token)) {
  174.             $this->value = $token[0];
  175.             $this->token = self::PATTERN;
  176.             $this->+= strlen($token[0]);
  177.             $this->state 'DeclareEquals';
  178.             return true;
  179.         else {
  180.             $this->error('expecting declaration of sub-patterns');
  181.             return false;
  182.         }
  183.     }
  184.  
  185.     /**
  186.      * lexer for processor instructions within declaration comment
  187.      * @return boolean 
  188.      */
  189.     private function lexDeclarePI()
  190.     {
  191.         while ($this->strlen($this->data&& 
  192.                 ($this->data[$this->N== ' ' ||
  193.                  $this->data[$this->N== "\t")) {
  194.             $this->N++// skip whitespace
  195.         }
  196.         if ($this->data[$this->N== "\n"{
  197.             $this->N++;
  198.             $this->state 'Declare';
  199.             $this->line++;
  200.             return $this->lexDeclare();
  201.         }
  202.         if ($this->data[$this->N== '{'{
  203.             return $this->lexCode();
  204.         }
  205.         if (!preg_match("/[^\n]+/"substr($this->data$this->N)$token)) {
  206.             $this->error('Unexpected end of file');
  207.             return false;
  208.         }
  209.         $this->value = $token[0];
  210.         $this->+= strlen($this->value);
  211.         $this->token = self::SUBPATTERN;
  212.         return true;
  213.     }
  214.  
  215.     /**
  216.      * lexer for processor instructions inside rule comments
  217.      * @return boolean 
  218.      */
  219.     private function lexDeclarePIRule()
  220.     {
  221.         while ($this->strlen($this->data&& 
  222.                 ($this->data[$this->N== ' ' ||
  223.                  $this->data[$this->N== "\t")) {
  224.             $this->N++// skip whitespace
  225.         }
  226.         if ($this->data[$this->N== "\n"{
  227.             $this->N++;
  228.             $this->state 'Rule';
  229.             $this->line++;
  230.             return $this->lexRule();
  231.         }
  232.         if ($this->data[$this->N== '{'{
  233.             return $this->lexCode();
  234.         }
  235.         if (!preg_match("/[^\n]+/"substr($this->data$this->N)$token)) {
  236.             $this->error('Unexpected end of file');
  237.             return false;
  238.         }
  239.         $this->value = $token[0];
  240.         $this->+= strlen($this->value);
  241.         $this->token = self::SUBPATTERN;
  242.         return true;
  243.     }
  244.  
  245.     /**
  246.      * lexer for the state representing scanning between a pattern and the "=" sign
  247.      * @return boolean 
  248.      */
  249.     private function lexDeclareEquals()
  250.     {
  251.         while ($this->strlen($this->data&& 
  252.                 ($this->data[$this->N== ' ' || $this->data[$this->N== "\t")) {
  253.             $this->N++// skip whitespace
  254.         }
  255.         if ($this->>= strlen($this->data)) {
  256.             $this->error('unexpected end of input, expecting "=" for sub-pattern declaration');
  257.         }
  258.         if ($this->data[$this->N!= '='{
  259.             $this->error('expecting "=" for sub-pattern declaration');
  260.             return false;
  261.         }
  262.         $this->N++;
  263.         $this->state 'DeclareRightside';
  264.         while ($this->strlen($this->data&& 
  265.                 ($this->data[$this->N== ' ' || $this->data[$this->N== "\t")) {
  266.             $this->N++// skip whitespace
  267.         }
  268.         if ($this->>= strlen($this->data)) {
  269.             $this->error('unexpected end of file, expecting right side of sub-pattern declaration');
  270.             return false;
  271.         }
  272.         return $this->lexDeclareRightside();
  273.     }
  274.  
  275.     /**
  276.      * lexer for the right side of a pattern, detects quotes or regexes
  277.      * @return boolean 
  278.      */
  279.     private function lexDeclareRightside()
  280.     {
  281.         if ($this->data[$this->N== "\n"{
  282.             $this->state 'lexDeclare';
  283.             $this->N++;
  284.             $this->line++;
  285.             return $this->lexDeclare();
  286.         }
  287.         if ($this->data[$this->N== '"'{
  288.             return $this->lexQuote();
  289.         }
  290.         while ($this->strlen($this->data&& 
  291.                 ($this->data[$this->N== ' ' ||
  292.                  $this->data[$this->N== "\t")) {
  293.             $this->N++// skip all whitespace
  294.         }
  295.         // match a pattern
  296.         $test $this->data[$this->N];
  297.         $token $this->1;
  298.         $a 0;
  299.         do {
  300.             if ($a++{
  301.                 $token++;
  302.             }
  303.             $token strpos($this->data$test$token);
  304.         while ($token !== false && ($this->data[$token 1== '\\'
  305.                  && $this->data[$token 2!= '\\'));
  306.         if ($token === false{
  307.             $this->error('Unterminated regex pattern (started with "' $test '"');
  308.             return false;
  309.         }
  310.         if (substr_count($this->data"\n"$this->N$token $this->N)) {
  311.             $this->error('Regex pattern extends over multiple lines');
  312.             return false;
  313.         }
  314.         $this->value = substr($this->data$this->1$token $this->1);
  315.         // unescape the regex marker
  316.         // we will re-escape when creating the final regex
  317.         $this->value = str_replace('\\' $test$test$this->value);
  318.         $this->$token 1;
  319.         $this->token = self::SUBPATTERN;
  320.         return true;
  321.     }
  322.  
  323.     /**
  324.      * lexer for quoted literals
  325.      * @return boolean 
  326.      */
  327.     private function lexQuote()
  328.     {
  329.         $token $this->1;
  330.         $a 0;
  331.         do {
  332.             if ($a++{
  333.                 $token++;
  334.             }
  335.             $token strpos($this->data'"'$token);
  336.         while ($token !== false && $token strlen($this->data&&
  337.                   ($this->data[$token 1== '\\' && $this->data[$token 2!= '\\'));
  338.         if ($token === false{
  339.             $this->error('unterminated quote');
  340.             return false;
  341.         }
  342.         if (substr_count($this->data"\n"$this->N$token $this->N)) {
  343.             $this->error('quote extends over multiple lines');
  344.             return false;
  345.         }
  346.         $this->value = substr($this->data$this->1$token $this->1);
  347.         $this->value = str_replace('\\"''"'$this->value);
  348.         $this->value = str_replace('\\\\''\\'$this->value);
  349.         $this->$token 1;
  350.         $this->token = self::QUOTE;
  351.         return true;
  352.     }
  353.  
  354.     /**
  355.      * lexer for rules
  356.      * @return boolean 
  357.      */
  358.     private function lexRule()
  359.     {
  360.         while ($this->strlen($this->data&& 
  361.                 ($this->data[$this->N== ' ' ||
  362.                  $this->data[$this->N== "\t" ||
  363.                  $this->data[$this->N== "\n")) {
  364.             if ($this->data[$this->N== "\n"{
  365.                 $this->line++;
  366.             }
  367.             $this->N++// skip all whitespace
  368.         }
  369.         if ($this->>= strlen($this->data)) {
  370.             $this->error('unexpected end of input, expecting rule declaration');
  371.         }
  372.         if ($this->data[$this->N== '*' && $this->data[$this->1== '/'{
  373.             $this->state 'StartNonDeclare';
  374.             $this->value = '*/';
  375.             $this->+= 2;
  376.             $this->token = self::COMMENTEND;
  377.             return true;
  378.         }
  379.         if (preg_match('/^%([a-z]+)/'substr($this->data$this->N)$token)) {
  380.             $this->value = $token[1];
  381.             $this->+= strlen($token[1]1;
  382.             $this->state 'DeclarePIRule';
  383.             $this->token = self::PI;
  384.             return true;
  385.         }
  386.         if ($this->data[$this->N== "{"{
  387.             return $this->lexCode();
  388.         }
  389.         if ($this->data[$this->N== '"'{
  390.             return $this->lexQuote();
  391.         }
  392.         if (preg_match('/^[a-zA-Z_]+/'substr($this->data$this->N)$token)) {
  393.             $this->value = $token[0];
  394.             $this->+= strlen($token[0]);
  395.             $this->token = self::SUBPATTERN;
  396.             return true;
  397.         else {
  398.             $this->error('expecting token rule (quotes or sub-patterns)');
  399.             return false;
  400.         }
  401.     }
  402.  
  403.     /**
  404.      * lexer for php code blocks
  405.      * @return boolean 
  406.      */
  407.     private function lexCode()
  408.     {
  409.         $cp $this->1;
  410.         for ($level 1$cp strlen($this->data&& ($level || $this->data[$cp!= '}')$cp++{
  411.             if ($this->data[$cp== '{'{
  412.                 $level++;
  413.             elseif ($this->data[$cp== '}'{
  414.                 $level--;
  415.             elseif ($this->data[$cp== '/' && $this->data[$cp 1== '/'{
  416.                 /* Skip C++ style comments */
  417.                 $cp += 2;
  418.                 $z strpos($this->data"\n"$cp);
  419.                 if ($z === false{
  420.                     $cp strlen($this->data);
  421.                     break;
  422.                 }
  423.                 $cp $z;
  424.             elseif ($this->data[$cp== "'" || $this->data[$cp== '"'{
  425.                 /* String a character literals */
  426.                 $startchar $this->data[$cp];
  427.                 $prevc 0;
  428.                 for ($cp++$cp strlen($this->data&& ($this->data[$cp!= $startchar || $prevc === '\\')$cp++{
  429.                &