Php/PhpSimpleParser
From Php-Classes
[edit] Php_Simple_Parser
[edit] class usage
Here is a basic usage for this class parser :
$file = __FILE__;
$args = $_SERVER['argv'];
if(count($args) > 1)
$file = $args[1];
# create a new php parser
$parser = new Php_Parser_Simple();
$start = microtime(true);
# parse a php-file and return an object that handle php declarations
$php_main_block = $parser->parse_file($file);
# get declared classes
$classes = $php_main_block->get_classes();
# and display methods and attributes.
foreach($classes as $class){
printf("class %s (%d:%s)\n", $class->name(), $class->line(), $class->file());
foreach($class->attributes() as $attr)
printf(" %s\n", $attr->to_text());
foreach($class->methods() as $meth)
printf(" %s\n", $meth->to_text());
# should do the same :
# echo $class->to_text($full_print=true, $sort=true);
}
[edit] complete source code
<?php error_reporting(E_ALL); /** author : Marc Quinton - january 2007. licence : LGPL a simple php parser ; can be used for a PHP IDE to browse classe tree with methods and attributes. - usage : see at the bottom of file. - documentation : to be done. this parser is partial : - it does not handle very right argument list - does not know any thing about method body, - does not handle global scope variables and functions - methods and attributes options (private, protected, virtual) need to be writen if need ; token are ready to use Notes : - this parser is heavy based on php tokenizer : http://www.php.net/manual/en/ref.tokenizer.php - there is some other parser for php but not working correctly with php5. - all classes are prefixed with Php_Parser_Simple except main class whose name is Php_Parser_Simple to avaoid class name conflits - classes are used for : - container classes (stacks) - php attributes containers for class, methods and attributes : - Php_Parser_Simple_ClassBlock - Php_Parser_Simple_MethodDecl - Php_Parser_Simple_AttributeDecl - to handle tokens from php tokenizer benchmarks : - 3Ghz CPU, - runs in 128ms for this file without printf : - 21 classes, - 76 methods bugs : - function arguments with default value ($xx=123) not correctly handeled : foobar($v1, $v2=123) is seen as function foobar($v1, $v2, 123) todo: - redesign class tree and class name for this parser : - there is block, stack, declarations, tokens classes - may be confusing. - handle methods and attributes options (private, protected, virtual) - handle methods body, - handle global scope function and variables - handle class tree (extends keyword) - handle include, require keyword - parse comment for documentation extraction. - build a nice GUI for php-gtk. basic usage : # create a new php parser $parser = new Php_Parser_Simple(); # parse a php-file and return an object that handle php declarations $php_main_block = $parser->parse_file($file); # get declared classes $classes = $php_main_block->get_classes(); # and display methods and attributes. foreach($classes as $class){ printf("class %s (%d:%s)\n", $class->name(), $class->line(), $class->file()); # echo $class->to_text() . "\n"; foreach($class->methods() as $meth) printf(" %s\n", $meth->to_text()); foreach($class->attributes() as $attr) printf(" %s\n", $attr->to_text()); } Synopsis for main classes (in logical usage order) : * Php_Parser_Simple - __construct() with no args, - parse_source($php_source) ; returns a Php_Parser_Simple_MainBlock * Php_Parser_Simple_MainBlock - do not __construct this class ; objects are create from Php_Parser_Simple - get_classes(sort=false) : return an array of classes (Php_Parser_Simple_ClassBlock) * Php_Parser_Simple_ClassBlock - do not __construct this class ; objects are create from Php_Parser_Simple - methods($sort=true) : lists methods for this class ; ; returns a list of attributes as a list of Php_Parser_Simple_MethodDecl - attributes($sort=true) : lists attributes ; returns a list of attributes as a list of Php_Parser_Simple_AttributeDecl - to_text($full_print=false, $sort=false) ; usefull for debugging - name() : returns class name, - line(), file() : returns where this class is located in php source code * Php_Parser_Simple_MethodDecl * Php_Parser_Simple_AttributeDecl - do not __construct this class ; objects are create from Php_Parser_Simple - to_text() : for debugging - name : returns (method|atribute) name, - line(), file() : returns where this (method|class) is located in php source code all other classes are for internal use. links : - parser : http://pear.php.net/package/PHP_Parser http://greg.chiaraquartet.net/archives/137-a-parser-generator-for-PHP-finally.html http://pear.chiaraquartet.net/PHP_ParserGenerator/PHP_ParserGenerator/_ParserGenerator.php.html http://pear.chiaraquartet.net/lemon/ http://netevil.org/node.php?nid=941 http://www.hwaci.com/sw/lemon/lemon.html */ /** * here we need to build different list (stacks) * * - Stack (core stack methods) * - Block * - TokenList * - StatementBlock * - PhpBlock * - ParamBlock * * Notes : * - may be some are unused. * - typed list are very conveniant with print_r(), perhaps they are not really needed (mandatory) for this parser. */ class Php_Parser_Simple_Stack{ protected $data; protected $index; #iterator index function __construct(){ $this->data = array(); $this->index = 0; } public function push($data){ $this->data[] = $data; } public function add($data){ $this->data[] = $data; } public function pop(){ return array_pop($this->data); } public function next(){ if(isset($this->data[$this->index])) return $this->data[$this->index++]; return null; } public function has_next(){ if($this->index < count($this->data)) return true; return false; } public function reset(){ $this->index = 0; } function to_text(){ return sprintf("block - Stack(%s)", count($this->data)); } function text(){ return ; } function find($name){ $list = array(); foreach($this->data as $obj) if($obj->text() == $name) $list[] = $obj; return $list; } } class Php_Parser_Simple_TokenList extends Php_Parser_Simple_Stack{ } class Php_Parser_Simple_Block extends Php_Parser_Simple_Stack{ } class Php_Parser_Simple_StatementBlock extends Php_Parser_Simple_Stack{ } class Php_Parser_Simple_PhpBlock extends Php_Parser_Simple_Block{ protected $level; public function __construct($level){ $this->level = $level; parent::__construct(); } public function level(){ return $this->level; } function to_text($level = 0){ $level = $this->level; $sub_level = max(0,$this->level-1); $indent = str_repeat(' ', $level); $sub_indent = str_repeat(' ', $sub_level); $text = "{\n$indent"; while($this->has_next()){ $token = $this->next(); if(is_a($token, 'Php_Parser_Simple_TextToken') && $token->text() == ';') $text .= ";\n$indent"; elseif(is_a($token, 'Php_Parser_Simple_PhpBlock')) $text .= $token->to_text(); else $text .= $token->to_text() . ' '; } $text .= "\n$sub_indent}\n$sub_indent"; return $text; } } class Php_Parser_Simple_ParamBlock extends Php_Parser_Simple_Block{ public function to_text(){ if(count($this->data) > 0){ foreach($this->data as $obj) $list[] = $obj->to_text(); $txt = join(', ', $list); # fixme : function($var=123) is not supported ; returns function($var,123) return "($txt)"; } else return '()'; } } class Php_Parser_Simple_Declaration { protected $name; protected $options; protected $token; protected $type = null; public function name(){ return $this->name; } public function line(){ return $this->token->line(); } public function file(){ return $this->token->file(); } function to_text(){ return sprintf('%s - (%d:%s)', $this->name(), $this->line(), $this->file()); } } class Php_Parser_Simple_MethodDecl extends Php_Parser_Simple_Declaration{ protected $args; protected $body; function __construct($name, $options, $args, $body, $token){ $this->type = "method"; $this->name = $name; $this->options = $options; $this->args = $args; $this->body = $body; $this->token = $token; } function to_text(){ return sprintf('%s() - (%d:%s)', $this->name(), $this->line(), $this->file()); } } class Php_Parser_Simple_AttributeDecl extends Php_Parser_Simple_Declaration{ function __construct($name, $options,$token){ $this->type = "attribute"; $this->name = $name; $this->options = $options; $this->token = $token; } } /** * ClassBlock : * * - handle class attributes * - internaly, parse class tokens (parse_body()) * * */ class Php_Parser_Simple_ClassBlock extends Php_Parser_Simple_Block{ protected $name; protected $options; protected $body; protected $token; function __construct($name, $options, $body, $token){ $this->name = $name; $this->options = $options; $this->body = $body; $this->token = $token; $this->body = $this->parse_body(); } function to_text($full_print=false, $sort=false){ if(!$full_print) return sprintf("class %s (%d:%s)", $this->name(), $this->line(), $this->file()); $txt = $this->to_text(false) . "\n"; foreach($this->attributes($sort) as $attr) $txt .= " - " . $attr->to_text() . "\n"; foreach($this->methods($sort) as $meth) $txt .= " - " . $meth->to_text() . "\n"; return $txt; } public function name(){ return $this->name; } public function line(){ return $this->token->line(); } public function file(){ return $this->token->file(); } public function methods($sort=true){ $list = array(); $this->body->reset(); while($this->body->has_next()){ $obj = $this->body->next(); if(is_a($obj, 'Php_Parser_Simple_MethodDecl')) $list[$obj->name()] = $obj; } if($sort) ksort($list); return $list; } public function attributes($sort=true){ $list = array(); $this->body->reset(); while($this->body->has_next()){ $obj = $this->body->next(); if(is_a($obj, 'Php_Parser_Simple_AttributeDecl')) $list[$obj->name()] = $obj; } if($sort) ksort($list); return $list; } /** * given a list of tokens for a php class, try to detect * - class attributes with declarations * - class methods with attributes, arguments and body */ protected function parse_body(){ $data = new Php_Parser_Simple_PhpBlock($level_fixme=0); # fixme : we lost block level here $statement = array(); $options = array(); $decl = array(); $option_list = array(T_PROTECTED, T_PUBLIC, T_PRIVATE, T_ABSTRACT); while($this->body->has_next()){ $token = $this->body->next(); if(is_a($token, 'Php_Parser_Simple_TextToken') && $token->text() == ';'){ # this is an end of statement (block) $name = $decl[0]->text(); $data->add($attr = new Php_Parser_Simple_AttributeDecl($name, $options, $decl[0])); # reset context $statement = array(); $options = array(); $decl = array(); }elseif(is_a($token, 'Php_Parser_Simple_PhpToken') && $token->id() == T_VARIABLE){ # this is a (class) attribute declaration $decl[] = $token; }elseif(is_a($token, 'Php_Parser_Simple_PhpToken') && $token->id() == T_FUNCTION){ # this is a (class) attribute declaration $name = $this->body->next()->text(); $args = $this->body->next(); $body = $this->body->next(); $data->add($meth = new Php_Parser_Simple_MethodDecl($name, $options, $args, $body, $token)); # reset context $statement = array(); $options = array(); $decl = array(); } elseif(is_a($token, 'Php_Parser_Simple_PhpToken') && in_array($token->id(), $option_list)) $options[] = $token; else $statement[] = $token; } return $data; } } /** * a class to handle global scope for a list of PhpTokens * */ class Php_Parser_Simple_MainBlock extends Php_Parser_Simple_PhpBlock{ public function __construct($level=0){ $this->level = $level; parent::__construct(0); } public function get_classes($sort=true){ $classes = array(); $this->reset(); while($this->has_next()){ $token = $this->next(); if(is_a($token, 'Php_Parser_Simple_ClassBlock')){ $classes[$token->name()] = $token; } } if($sort) ksort($classes); return $classes; } } /** * below : a list of class to handle token attributes : * * - PhpToken * - SimpleToken * - ScalarToken * - TextToken * - CommentToken * - SpacingToken * - NullToken */ class Php_Parser_Simple_PhpToken{ protected $id; # token ID as in http://www.php.net/manual/en/tokens.php protected $text; # token content protected $block_level; # block level : change with { and } match protected $line; protected $col; protected $file; protected $name; # for debug un print_r() public function __construct($id, $text, $level, $line=null, $col=null, $file=null){ $this->id = $id; $this->text = $text; $this->block_level = $level; $this->line = $line; $this->col = $col; $this->file = $file; $this->name = $this->name(); # debug } public function to_text(){ return $this->text; } public function debug(){ return sprintf('%d:%d - %s: %s (%d)', $this->line, $this->col, $this->name(), $this->text, $this->block_level); } public function name(){ return token_name($this->id); } public function id(){ return $this->id; } public function line(){ return $this->line; } public function file(){ return $this->file; } public function text(){ return $this->text; } } # simple token (textual) class Php_Parser_Simple_SimpleToken extends Php_Parser_Simple_PhpToken{ public function __construct($id, $text, $level, $line=null, $col=null, $file=null){ $this->id = null; $this->text = $text; $this->block_level = $level; $this->line = $line; $this->col = $col; $this->file = $file; $this->line_count = substr_count($text, "\n"); # debug } public function to_text(){ if($this->text == ';') return ";\n"; return $this->text; } public function debug(){ return sprintf('%d - text-token : "%s"', $this->line, $this->text); } } class Php_Parser_Simple_ScalarToken extends Php_Parser_Simple_SimpleToken{ public function to_text(){ return $this->text; } } # simple token (textual) class Php_Parser_Simple_TextToken extends Php_Parser_Simple_SimpleToken{ } class Php_Parser_Simple_CommentToken extends Php_Parser_Simple_TextToken{ } class Php_Parser_Simple_SpacingToken extends Php_Parser_Simple_TextToken{ } # uninteresting Token class Php_Parser_Simple_NullToken extends Php_Parser_Simple_SimpleToken{ } /* * T_ML_COMMENT does not exist in PHP 5. * The following three lines define it in order to * preserve backwards compatibility. * * The next two lines define the PHP 5 only T_DOC_COMMENT, * which we will mask as T_ML_COMMENT for PHP 4. */ if (!defined('T_ML_COMMENT')) { define('T_ML_COMMENT', T_COMMENT); } else { define('T_DOC_COMMENT', T_ML_COMMENT); } /** Php_Parser_Simple - the main class for this package. */ class Php_Parser_Simple{ protected $file; protected $line; protected $col; protected $block_level; protected $tokens; function __construct(){ if(!function_exists('token_get_all')) throw new Exception ('function token_get_all() not found : see http://www.php.net/manual/en/ref.tokenizer.php'); } protected function initialize(){ $this->line = 1; $this->col = 1; $this->block_level=0; $this->tokens = array(); } public function parse_source($source, $file=){ $this->initialize(); # get a list of tokens from php tokenizer. $token_list = $this->get_tokens($source, $file); # list of tokens is a flat list : try to parse block and build a tree of tokens for easy parsing. $php_main_block = $this->tokens_parse_blocks($token_list); # parse tokens and detect class declarations with methods and attributes. $php_main_block = $this->tokens_parse_class_blocks($php_main_block); return $php_main_block; } public function parse_file($file){ $this->initialize(); # read source file. $source = file_get_contents($file); # return a tree of classes reprentating source code. return $this->parse_source($source, $file); } protected function tokens_parse_blocks($token_list){ static $level = 0; if($level == 0) $block = new Php_Parser_Simple_MainBlock(); else $block = new Php_Parser_Simple_PhpBlock($level); $level++; while($token_list->has_next()){ $token = $token_list->next(); if(is_a($token,'Php_Parser_Simple_TextToken')){ switch($token->text()){ case '{': $block->add($this->tokens_parse_blocks($token_list)); break; case '}': $level--; return $block; case '(': $block->add($this->tokens_parse_blocks_params($token_list)); break; # case ')': # it's done in tokens_parse_blocks_params() method # return $array; # break; default: $block->add($token); } } else{ $block->add($token); } } $level--; return $block; } protected function tokens_parse_blocks_params($token_list){ $block = new Php_Parser_Simple_ParamBlock(); while($token_list->has_next()){ $token = $token_list->next(); if(is_a($token,'Php_Parser_Simple_TextToken')){ switch($token->text()){ case ')': return $block; } } else{ $block->add($token); } } return $block; } protected function tokens_parse_class_blocks($block){ if(!is_a($block, 'Php_Parser_Simple_MainBlock')) return; $new_block = new Php_Parser_Simple_MainBlock(); while($block->has_next()){ $token = $tokens[] = $block->next(); if($token->text() == 'class'){ $class_name_token = $block->next(); $class_opts = array(); while($block->has_next()){ $token = $block->next(); if(is_a($token, 'Php_Parser_Simple_PhpBlock')) break; $class_opts[] = $token; } $body = $token; $class = new Php_Parser_Simple_ClassBlock($class_name_token->text(), $class_opts, $body, $class_name_token); $new_block->add($class); } else $new_block->add($token); } return $new_block; } /** * for a given file, parse php tokens and return a flat list of tokens objects (SimpleToken and PhpToken) * */ protected function get_tokens($source, $file=){ $tokens = token_get_all($source); $token_list = new Php_Parser_Simple_TokenList(); foreach ($tokens as $token) { if (is_string($token)) { // simple 1-character token switch($token){ case ';': $token_list->add(new Php_Parser_Simple_TextToken(null, $token, $this->block_level, $this->line, $this->col, $file)); # end statement break; case '{': $this->block_level++; $token_list->add(new Php_Parser_Simple_TextToken(null, $token, $this->block_level, $this->line, $this->col, $file)); break; case '}': $this->block_level--; $token_list->add(new Php_Parser_Simple_TextToken(null, $token, $this->block_level, $this->line, $this->col, $file)); break; case '(': case ')': $token_list->add(new Php_Parser_Simple_TextToken(null, $token, $this->block_level, $this->line, $this->col, $file)); break; default: # do nothing. $token_list->add(new Php_Parser_Simple_TextToken(null, $token, $this->block_level, $this->line, $this->col, $file)); # echo "# get_tokens() (char) : $token\n"; } $this->update_line_and_column_positions($token, $this->line, $this->col); } else { // token array list($id, $text) = $token; switch ($id) { # ignore some tokens not interesting here. case T_COMMENT: case T_ML_COMMENT: // we've defined this case T_DOC_COMMENT: // and this # $token_list->add(new CommentToken($id, $text, $this->block_level, $this->line, $this->col, $file)); break; case T_WHITESPACE: # $token_list->add(new SpacingToken($id, $text, $this->block_level, $this->line, $this->col, $file)); break; case T_OPEN_TAG: case T_CLOSE_TAG: $token_list->add(new Php_Parser_Simple_TextToken($id, $text, $this->block_level, $this->line, $this->col, $file)); break; case T_CONSTANT_ENCAPSED_STRING: case T_LNUMBER: $token_list->add(new Php_Parser_Simple_ScalarToken($id, $text, $this->block_level, $this->line, $this->col, $file)); break; default: $token_list->add(new Php_Parser_Simple_PhpToken($id, $text, $this->block_level, $this->line, $this->col, $file)); break; } $this->update_line_and_column_positions($text, $this->line, $this->col); } } return $token_list; # list of tokens objects. } protected function update_line_and_column_positions($c, &$line, &$col){ // update line count $numNewLines = substr_count($c, "\n"); if (1 <= $numNewLines) { // have new lines, add them in $line += $numNewLines; $col = 1; // skip to right past the last new line, as it won't affect the column position $c = substr($c, strrpos($c, "\n") + 1); if ($c === false) { $c = ; } } // update column count $col += strlen($c); } } $file = __FILE__; $args = $_SERVER['argv']; if(count($args) > 1) $file = $args[1]; # create a new php parser $parser = new Php_Parser_Simple(); $start = microtime(true); # parse a php-file and return an object that handle php declarations $php_main_block = $parser->parse_file($file); # get declared classes $classes = $php_main_block->get_classes(); # and display methods and attributes. foreach($classes as $class){ printf("class %s (%d:%s)\n", $class->name(), $class->line(), $class->file()); foreach($class->attributes() as $attr) printf(" %s\n", $attr->to_text()); foreach($class->methods() as $meth) printf(" %s\n", $meth->to_text()); # should do the same : # echo $class->to_text($full_print=true, $sort=true); } $end = microtime(true); $delay = ($end - $start); echo "$delay\n"; ?>

