<?php
/*
Copyright 2008 Josh Heidenreich
This file is part of Pelzini.
Pelzini is free software: you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
the Free Software Foundation, either version 3 of the License, or
(at your option) any later version.
Pelzini is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU General Public License for more details.
You should have received a copy of the GNU General Public License
along with Pelzini. If not, see <http://www.gnu.org/licenses/>.
*/
/**
* PHP Parser
* @package Parsers
* @author Josh Heidenreich
* @since 0.1
**/
/**
* This is the parser for PHP files. It converts a file from the raw PHP into a document tree
*
* @todo Convert to new parser system (e.g. Javascript parser)
* @todo Add support for namespaces, even on PHP < 5.3
**/
class PhpParser
{
/**
* Parses a specific file
**/
function parseFile($base_dir, $filename)
{
// You can enable the following if you want to debug the parser
// If you enable the line after it (the 'strpos' line) instead,
// it will only debug files containing 'test' (e.g. 'php_test.php')
$debug = false;
// if (strpos ($filename, 'test') !== false) $debug = true;
if ($source == null) return null;
$current_file = new ParserFile ();
$current_file->name = $filename;
$current_file->source = $source;
// the vars that make it tick
$current_function = null;
$inside_function = null;
$current_class = null;
$inside_class = null;
$current_constant = null;
$next = null;
$namespace = 0;
$brace_count = 0;
$abstract = false;
$static = false;
$final = false;
$next_comment = null;
$file_has_comment = false;
$return = false;
$byref = false;
// debugger
if ($debug) {
echo '<style>';
echo 'span {color: green;}';
echo 'h3 {border: 4px black solid; padding: 3px; margin-top: 2em;}';
echo 'i {color: gray;}';
echo '</style>';
echo '<pre>';
}
$argument = null;
$visibility = null;
$param_type = null;
foreach ($tokens as $token) {
if (is_array($token) and
$token[0] == T_WHITESPACE
) continue;
// debugger
if ($debug) {
echo "\n";
} else {
}
}
if ($return) {
if ($token !== ';') {
$inside_function->has_return_stmt = true;
}
$return = false;
}
// opening of a function or class block
if ($token == '{') {
// opening of function
if ($current_function != null) {
if ($inside_class != null) {
if ($visibility != null) {
$current_function->visibility = $visibility;
$visibility = null;
}
$inside_class->functions[] = $current_function;
} else {
$current_file->functions[] = $current_function;
}
$current_function->post_load();
$inside_function = $current_function;
$current_function = null;
$argument = null;
// opening of class
} else if ($current_class != null) {
if ($visibility != null) {
$current_class->visibility = $visibility;
$visibility = null;
}
$current_file->classes[] = $current_class;
$inside_class = $current_class;
$current_class = null;
$next = null;
} else {
$brace_count++;
}
// function in an interface
} else if ($token == ';') {
if ($namespace == 1) {
$namespace = 2;
} else if ($current_function != null) {
if ($visibility != null) {
$current_function->visibility = $visibility;
$visibility = null;
}
$current_function->post_load();
$inside_class->functions[] = $current_function;
$current_function = null;
}
// closing of a class or function block
} else if ($token == '}') {
if ($brace_count == 0) {
if ($inside_function != null) {
$inside_function = null;
} else if ($inside_class != null) {
$inside_class = null;
}
} else {
$brace_count--;
}
} else if ($token == '&') {
if ($current_function != null) {
$byref = true;
}
}
} else {
// token array
list($id, $text, $linenum) = $token;
switch ($id) {
case T_CURLY_OPEN:
$brace_count++;
break;
case T_DOC_COMMENT:
if ($next_comment and ! $file_has_comment) {
$current_file->applyComment($next_comment);
$next_comment = null;
$file_has_comment = true;
}
$next_comment = $text;
break;
case T_FUNCTION:
if ($inside_function != null) {
break;
}
$current_function = new ParserFunction();
$current_function->linenum = $linenum;
if ($abstract) {
$current_function->abstract = true;
$abstract = false;
}
if ($static) {
$current_function->static = true;
$static = false;
}
if ($final) {
$current_function->final = true;
$final = false;
}
if ($next_comment) {
$current_function->applyComment($next_comment);
$next_comment = null;
}
$param_type = null;
break;
case T_CLASS:
$current_class = new ParserClass();
$current_class->linenum = $linenum;
if ($abstract) {
$current_class->abstract = true;
$abstract = false;
} else if ($final) {
$current_class->final = true;
$final = false;
}
if ($next_comment) {
$current_class->applyComment($next_comment);
$next_comment = null;
}
break;
case T_INTERFACE:
$current_class = new ParserInterface();
$current_class->linenum = $linenum;
if ($next_comment) {
$current_class->applyComment($next_comment);
$next_comment = null;
}
break;
// variables are added according to scope
// will become a ParserVariable or a ParserArgument
case T_VARIABLE:
if ($current_function != null) {
$argument = new ParserArgument();
$argument->linenum = $linenum;
$argument->name = $text;
if ($param_type != null) {
$argument->type = $param_type;
$param_type = null;
}
$argument->byref = $byref;
$current_function->args[] = $argument;
$byref = false;
} else if (($inside_class != null) && ($inside_function == null)) {
$variable = new ParserVariable();
$variable->linenum = $linenum;
$variable->name = $text;
$variable->visibility = $visibility ?: 'private';
$visibility = null;
if ($static) {
$variable->static = true;
$static = false;
}
if ($next_comment) {
$variable->applyComment($next_comment);
$next_comment = null;
}
$inside_class->variables[] = $variable;
}
break;
// A string my become an extends, implements
// function name or class name
// it could also be 'define' or 'null'
case T_STRING:
if ($next != null) {
if ($next == T_EXTENDS) {
$current_class->extends = $text;
} else if ($next == T_IMPLEMENTS) {
$current_class->implements[] = $text;
break;
} else if ($next == T_NAMESPACE and $namespace == 0) {
$current_file->namespace = array($text); $namespace = 1;
} else if ($next == T_NS_SEPARATOR and $namespace == 1) {
$current_file->namespace[] = $text;
}
$next = null;
if ($current_constant) {
$current_constant->value = 'NULL';
$current_file->constants[] = $current_constant;
$current_constant = null;
} else if ($argument) {
$argument->default = 'NULL';
}
if ($current_constant) {
$current_constant->value = 'TRUE';
$current_file->constants[] = $current_constant;
$current_constant = null;
} else if ($argument) {
$argument->default = 'TRUE';
}
if ($current_constant) {
$current_constant->value = 'FALSE';
$current_file->constants[] = $current_constant;
$current_constant = null;
} else if ($argument) {
$argument->default = 'FALSE';
}
} else if ($current_function != null) {
if ($current_function->name == '') {
$current_function->name = $text;
} else {
$param_type = $text;
}
} else if ($current_class != null) {
$current_class->name = $text;
$current_constant = new ParserConstant();
$current_constant->linenum = $linenum;
if ($next_comment) {
$current_constant->applyComment($next_comment);
$next_comment = null;
}
}
break;
case T_ARRAY:
if ($current_function != null) {
$param_type = $text;
}
break;
case T_CONSTANT_ENCAPSED_STRING:
// removes quotes, etc
$name_search = array("\'", '\"', "'", '"'); $name_replace = array("'", '"', '', ''); $text = str_replace($name_search, $name_replace, $text);
if ($current_constant) {
if ($current_constant->name == null) {
$current_constant->name = $text;
} else {
$current_constant->value = $text;
$current_file->constants[] = $current_constant;
$current_constant = null;
}
} else if ($argument) {
$argument->default = $text;
}
break;
case T_LNUMBER:
case T_DNUMBER:
if ($current_constant) {
if ($current_constant->name != null) {
$current_constant->value = $text;
$current_file->constants[] = $current_constant;
$current_constant = null;
}
} else if ($argument) {
$argument->default = $text;
}
break;
// visibility
case T_PRIVATE:
$visibility = 'private';
break;
case T_PROTECTED:
$visibility = 'protected';
break;
case T_PUBLIC:
$visibility = 'public';
break;
// the next token after one of these does the grunt work
case T_EXTENDS:
case T_IMPLEMENTS:
case T_NAMESPACE:
case T_NS_SEPARATOR:
$next = $id;
break;
case T_ABSTRACT:
$abstract = true;
break;
case T_STATIC:
if (! $inside_function) $static = true;
break;
case T_FINAL:
$final = true;
break;
case T_RETURN:
if ($inside_function) {
$return = true;
}
break;
}
}
}
// If there is a comment left that never got assigned,
// assign it to the file
if ($next_comment and ! $file_has_comment) {
$current_file->applyComment($next_comment);
$next_comment = null;
$file_has_comment = true;
}
if ($debug) echo '</pre>';
return $current_file;
}
}
?>