1<?php
2//
3// +----------------------------------------------------------------------+
4// | PHP_Parser                                                           |
5// +----------------------------------------------------------------------+
6// | Copyright (c) 1997-2004 The PHP Group                                |
7// +----------------------------------------------------------------------+
8// | This source file is subject to version 3.0 of the PHP license,       |
9// | that is bundled with this package in the file LICENSE, and is        |
10// | available through the world-wide-web at the following url:           |
11// | http://www.php.net/license/3_0.txt.                                  |
12// | If you did not receive a copy of the PHP license and are unable to   |
13// | obtain it through the world-wide-web, please send a note to          |
14// | license@php.net so we can mail you a copy immediately.               |
15// +----------------------------------------------------------------------+
16// | Authors: Greg Beaver <cellog@php.net>                                |
17// |          Alan Knowles <alan_k@php.net>                               |
18// +----------------------------------------------------------------------+
19//
20// $Id: Parser.php 230453 2007-02-21 20:41:55Z cellog $
21//
22
23/*
24* usage :
25*   print_r(PHP_Parser::staticParseFile($filename));
26*/
27
28require_once 'System.php';
29// this will be used if the package is approved in PEAR
30//require_once 'Error/Stack.php';
31require_once 'PEAR/ErrorStack.php';
32
33define('PHP_PARSER_ERROR_NODRIVER', 1);
34define('PHP_PARSER_ERROR_NOTINITIALIZED', 2);
35define('PHP_PARSER_ERROR_NOINPUT', 3);
36
37class PHP_Parser {
38    const ERROR_NODRIVER = 1;
39    const ERROR_NOTINITIALIZED = 2;
40    const ERROR_NOINPUT = 3;
41    var $_parser;
42    var $_tokenizer;
43
44    /**
45     * Choose the parser and tokenizer
46     * @static
47     * @return PHP_Parser
48     */
49    function factory($parser='Core', $tokenizer='')
50    {
51        $ret = new PHP_Parser;
52        $ret->setParser($parser);
53        $ret->setTokenizer($tokenizer);
54        return $ret;
55    }
56
57    /**
58     * @param string|object
59     * @return bool
60     * @throws PHP_Parser_Exception
61     */
62    function setParser($parser='Core')
63    {
64        if (is_object($parser)) {
65            $this->_parser = $parser;
66            return false;
67        }
68
69
70
71        if (!class_exists($parser)) {
72            if ($this->isIncludeable('PHP/Parser/' . $parser . '.php')) {
73                include_once 'PHP/Parser/' . $parser . '.php';
74            }
75            if (!class_exists('PHP_Parser_' . $parser)) {
76                throw $this->raiseError("no parser driver \"$parser\" found",
77                    self::ERROR_NODRIVER, array('driver' => $parser,
78                    'type' => 'parse'));
79            }
80            $parser = "PHP_Parser_$parser";
81        }
82        $this->_parser = new $parser;
83        return true;
84    }
85
86    /**
87     * @param string|object
88     * @return PEAR_Error|false
89     */
90    function setTokenizer($tokenizer='')
91    {
92        if (is_object($tokenizer)) {
93            $this->_tokenizer = $tokenizer;
94            return false;
95        }
96        if ($tokenizer=='') {
97            $tokenizer = 'PHP_Parser_Tokenizer';
98            include_once 'PHP/Parser/Tokenizer.php';
99            $this->_tokenizer = new $tokenizer('',array('parser_class'=>get_class($this->_parser)));
100            return false;
101        }
102
103        if (!class_exists('PHP_Parser_Tokenizer_'.$tokenizer)) {
104            if ($this->isIncludeable('PHP/Parser/Tokenizer/' . $tokenizer . '.php')) {
105                include_once 'PHP/Parser/Tokenizer/' . $tokenizer . '.php';
106            }
107            if (!class_exists('PHP_Parser_Tokenizer_' . $tokenizer)) {
108                return $this->raiseError("no tokenizer driver \"$tokenizer\" found",
109                    self::ERROR_NODRIVER, array('driver' => $tokenizer,
110                    'type' => 'tokenize'));
111            }
112            $tokenizer = "PHP_Parser_Tokenizer_$tokenizer";
113        }
114        $this->_tokenizer = new $tokenizer;
115        return false;
116    }
117
118    /**
119     * @param string input to parse
120     * @param array options for the tokenizer
121     * @return PEAR_Error|false
122     */
123    function setTokenizerOptions($php, $options = array())
124    {
125        if (is_object($this->_tokenizer)) {
126            $this->_tokenizer->setOptions($php, $options);
127            return false;
128        }
129        return $this->raiseError("tokenizer must be initialized before setTokenizerOptions",
130            PHP_PARSER_ERROR_NOTINITIALIZED);
131    }
132
133    function raiseError($msg, $code, $params = array())
134    {
135        return PEAR_ErrorStack::staticPush('PHP_Parser', $code,
136            'exception', $params, $msg);
137    }
138
139    /**
140     * @param string $path relative or absolute include path
141     * @return boolean
142     * @static
143     */
144    function isIncludeable($path)
145    {
146        if (file_exists($path) && is_readable($path)) {
147            return true;
148        }
149        $ipath = explode(PATH_SEPARATOR, ini_get('include_path'));
150        foreach ($ipath as $include) {
151            $test = realpath($include . DIRECTORY_SEPARATOR . $path);
152            if (file_exists($test) && is_readable($test)) {
153                return true;
154            }
155        }
156        return false;
157    }
158
159    /**
160    * Parse a file with wddx caching options.
161    *
162    * parses a php file,
163    * @param    string  name of file to parse
164    * @param    false|string  false = no caching, '' = write to same directory, '/some/dir/' - cache directory
165    *
166    * @return   array| object PEAR_Error   should return an array of includes and classes.. will grow...
167    * @access   public
168    */
169    static function staticParseFile(
170                    $file,
171                    $options = array(),
172                    $tokenizeroptions = array(),
173                    $tokenizerClass = 'PHP_Parser_Tokenizer',
174                    $cacheDir=false
175                    )
176    {
177        if ($cacheDir === false) {
178            return self::parse(file_get_contents($file), $options, $tokenizeroptions, $tokenizerClass);
179        }
180        if (!strlen($cacheDir)) {
181            $cacheFile = dirname($file).'/.PHP_Parser/' . basename($file) . '.wddx';
182        } else {
183            $cacheFile = $cacheDir . $file . '.wddx';
184        }
185        if (!file_exists(dirname($cacheFile))) {
186            System::mkdir(dirname($cacheFile) ." -p");
187        }
188
189        //echo "Cache = $cacheFile\n";
190        if (file_exists($cacheFile) && (filemtime($cacheFile) > filemtime($file))) {
191            //echo "get cache";
192            return wddx_deserialize(file_get_contents($cacheFile));
193        }
194
195        // this whole caching needs a much nicer logic to it..
196        // but for the time being test the filename as md5 in /tmp/
197        $tmpCacheFile = '/tmp/'.md5($file).'.wddx';
198        if (file_exists($tmpCacheFile) && (filemtime($tmpCacheFile) > filemtime($file))) {
199            //echo "get cache";
200            return wddx_deserialize(file_get_contents($tmpCacheFile));
201        }
202
203        $result = PHP_Parser::parse(file_get_contents($file), $options, $tokenizeroptions, $tokenizerClass);
204        if (function_exists('wddx_set_indent')) {
205            wddx_set_indent(2);
206        }
207        //echo "Writing Cache = $cacheFile\n";
208        $fh = @fopen ($cacheFile,'w');
209        if (!$fh) {
210            $fh = fopen ($tmpCacheFile,'w');
211        }
212        fwrite($fh,wddx_serialize_value($result));
213        fclose($fh);
214        return $result;
215    }
216
217
218    /**
219    * Parse a string
220    *
221    * parses a php file,
222    *
223    *
224    * @param    string  name of file to parse
225    *
226    *
227    * @return   array| object PEAR_Error   should return an array of includes and classes.. will grow...
228    * @access   public
229    */
230
231
232    static function parse(
233            $string,
234            $options = array(),
235            $tokenizeroptions = array(),
236            $tokenizerClass = 'PHP_Parser_Tokenizer')
237    {
238        if (!trim($string)) {
239            throw new Exception('Nothing to parse');
240        }
241
242        if (($tokenizerClass == 'PHP_Parser_Tokenizer') && !class_exists($tokenizerClass)) {
243            require_once 'PHP/Parser/Tokenizer.php';
244        }
245
246        $yyInput = new $tokenizerClass($string, $tokenizeroptions);
247        //$yyInput->setOptions($string, $tokenizeroptions);
248        //xdebug_start_profiling();
249        $t = new PHP_Parser_Core($yyInput);
250        while ($yyInput->advance()) {
251            $t->doParse($yyInput->token, $yyInput->getValue(), $yyInput);
252        }
253        $t->doParse(0, 0);
254
255        return $t;
256    }
257
258    function parseString($php, $tokenoptions = array())
259    {
260        if (!trim($string)) {
261            throw new Exception('Nothing to parse');
262        }
263        $this->setTokenizerOptions($php, $tokenoptions);
264        $err = $this->_parser->yyparse($this->_tokenizer);
265        // some parser do not set stuff like this..
266        if ($err) {
267            return $err;
268        }
269        if (!isset($this->_parser->classes)) {
270            return;
271        }
272
273        return array(
274                'classes'     => $this->_parser->classes,
275                'interfaces'  => $this->_parser->interfaces,
276                'includes'   => $this->_parser->includes,
277                'functions'  => $this->_parser->functions,
278                'constants'  => $this->_parser->constants,
279                'globals'    => $this->_parser->globals
280            );
281    }
282}
283
284?>
285