CodeDoc/Parser.php
[PHP_CodeDoc] / CodeDoc / Parser.php
1 <?php
2
3 // +----------------------------------------------------------------------+
4 // | PHP Version 4                                                        |
5 // +----------------------------------------------------------------------+
6 // | Copyright (c) 1997-2002 The PHP Group                                |
7 // +----------------------------------------------------------------------+
8 // | This source file is subject to version 2.02 of the PHP license,      |
9 // | that is bundled with this package in the file LICENSE, and is        |
10 // | available at through the world-wide-web at                           |
11 // | http://www.php.net/license/2_02.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: Alan Knowles <alan@akbkhome.com>                      |
17 // +----------------------------------------------------------------------+
18 //
19 /**
20 *   Docbook data container (for pages and sections)
21 *
22 *   @package  PHP_CodeDoc
23 *   @access   public
24 *   @author   Alan Knowles <alan@akbkhome.com>
25 *
26 */ 
27 PEAR::loadExtension('tokenizer');
28
29 ini_set("memory_limit","128M");
30 set_time_limit(0);
31 error_reporting(E_ALL);
32
33
34
35 require_once("PHP/CodeDoc/Data/Class.php");
36 require_once("PHP/CodeDoc/Data/Var.php");
37 require_once("PHP/CodeDoc/Data/Define.php");
38 require_once("PHP/CodeDoc/Data/Method.php");
39 require_once("PHP/CodeDoc/Data/PhpDoc.php");
40 require_once("PHP/CodeDoc/Data/Directory.php");
41 require_once("PHP/CodeDoc/Data/Package.php");
42 require_once("PHP/CodeDoc/Data/Docbook.php");
43
44 require_once("PHP/CodeDoc/Parser/Class.php");
45 require_once("PHP/CodeDoc/Parser/Var.php");
46 require_once("PHP/CodeDoc/Parser/Define.php");
47 require_once("PHP/CodeDoc/Parser/Method.php");
48 require_once("PHP/CodeDoc/Parser/Comment.php");
49 require_once("PHP/CodeDoc/Parser/Docbook.php");
50
51
52  
53 class PHP_CodeDoc_Parser {
54
55     var $original_URL_base; // base url.
56
57     var $_level; // what level is the parser at (0 = base, 1=in class)
58     var $pos=0; // current position.
59     var $total=0; // total number of tokens in current file
60     var $tokens; // the array of tokens
61     
62     var $_active_class =0;  // id of active class being created 
63     var $active_class; //during output this is the currently dumped class object
64     var $activeFile; // active file being parsed
65     var $active_package; // active used package object
66     var $active_directory; // active used directory object
67     
68     var $start_file = "";  // the file (or directory) that was called at the start..
69     var $packages; // associative array of package name -> package object
70     var $_base_dir; // root of tree !
71     var $_base_dir_len; // root of tree !
72     
73     
74     var $_parser_class; // class parser
75     var $_parser_method;// method parser
76     var $_parser_var;// var parser
77     var $_parser_comment;// comment parser
78      
79     var $classes = array(); // array of classes
80     var $defines = array(); // array of defines
81     
82     
83     
84     function start() { // Main entry - start parsing a file or directory
85         /* initiate global parsers */
86         $options = PHP_CodeDoc::$options;
87         
88        
89         $filename = $options['source'];
90         
91         $this->_initializeParsers();
92         if (is_dir($filename)) {
93             $this->_base_dir = dirname(realpath($filename));
94             $this->_base_dir_len = strlen($this->_base_dir );
95             $this->_build($filename);
96             $n=0;
97             foreach($this->files as $dir => $files) {
98                 foreach($files as $file) 
99                     
100                     $this->_parseFile($dir . "/".$file);
101                     $n++;
102                     if (@$options['maxFiles'] && $n > $options['maxFiles']) {
103                         return;
104                     }
105                     
106             }
107         } else {
108             $this->_parseFile($filename);
109         }
110     }
111     
112     var $_init =0; //initialization flag (has init been run?)
113
114
115     function _initializeParsers() {
116         if ($this->_init) return;
117         $this->_init = 1;
118         
119         
120         
121         $this->packages['No Package']= new PHP_CodeDoc_Data_Package;
122         $this->packages['No Package']->name = "No Package"; 
123
124     }
125     
126     
127
128     var $_pre_output =0; // pre output flag;
129          
130     function _build( $filename) {  // build a list of file to parse
131        
132        
133         $this->files[$filename] = array();     
134        
135         $fh = opendir($filename);
136         $dirs = array();
137         $files = array();
138         while (($file =readdir($fh)) !== FALSE) {
139             if ($file{0} == ".") continue;
140              //echo "READ: $filename/$file\n";
141             if (is_file($filename."/".$file))  {
142                 
143                 if (!ereg("\.(inc|php|class)$",$file)) continue;
144                 $this->files[$filename][] = $file;
145                  
146             } elseif (is_dir($filename."/".$file)) {
147                 if ($file  == "CVS") continue;
148                 if ($file  == "RCS") continue;
149             
150                 $this->_build($filename . "/". $file);
151                 
152             }
153         }
154         closedir($fh);
155         //echo "\nDO STUFF\n";
156         
157           
158     }    
159     function _parseFile($filename) { // tockenize a file and start parsing
160         echo "PARSING: $filename\n";
161         
162         
163         $this->activeFile = $filename;
164         if (!filesize ($filename)) return;
165          
166         
167         $this->rdir = substr(dirname(realpath($this->activeFile)),$this->_base_dir_len);
168         $this->rfilename = substr(realpath($this->activeFile),$this->_base_dir_len);
169         $tmpar = explode ('/', $this->rfilename);
170         $tmpar[1] = '';
171         $base = substr(implode('/',$tmpar),2);
172         $this->file_url = $this->original_URL_base . $base;
173         
174         if (@$this->tokens) unset($this->tokens);
175         $data = implode('',file($filename));
176         $this->tokens = token_get_all($data);
177         /* use this if you have a broken tokenizer
178         $fh = popen(dirname(__FILE__)."/tokenizer_serialize.php $filename","r");
179         $this->tokens= unserialize(fread($fh,filesize($filename) * 16));
180         pclose($fh);
181         */
182         
183         $pname= 'No Package';
184        $options = PHP_CodeDoc::$options;
185         if (!$options['perDirPackages']) {
186             if (preg_match('/^\s*\*\s*@package\s*(.*)$/im',$data,$args)) {
187                 //echo "\nGot package {$this->package}\n";
188                 $pname = trim($args[1]);
189             }
190         } else {
191             $parts = explode('/',$this->rfilename);
192             $pname = $parts[1];
193             if ($this->rfilename{0} == "/") $pname = $parts[2];
194             $pname = preg_replace('/\.php$/','',$pname);
195             if (!$pname) $pname = "No Package";
196             echo "\nUsing package (from dir {$this->rfilename} ) {$pname}\n";
197         }
198         
199        
200         
201         if (!@$this->packages[$pname]) {
202             $this->packages[$pname] = new PHP_Codedoc_Data_Package;
203             $this->packages[$pname]->name = $pname;
204         }
205         $this->active_package = &$this->packages[$pname];
206         
207        
208         if (!@$this->active_package->directories[$this->rdir]) {
209             $this->active_package->directories[$this->rdir] = new PHP_Codedoc_Data_Directory;
210             $this->active_package->directories[$this->rdir]->name = $this->rdir;
211         }
212         $this->active_directory = &$this->active_package->directories[$this->rdir];
213
214         //print_r($data); exit;
215         //    $this->all_tokens[$filename] = token_get_all($contents);
216          //   $this->tokens =& $this->all_tokens[$filename];
217         //global $tokens;
218         // 
219         // $this->tokens =  &$tokens;
220         $this->total = count($this->tokens);
221         if ($this->total)
222             $this->_parse();
223         unset($this->tokens);
224         unset($this->total);
225             
226     }
227         
228     var $last_comment_block = ""; // last comment block found
229     
230     
231     function _parse() { // read the tokens and make the classes
232         $options = PHP_CodeDoc::$options;
233     
234         
235         $this->_level=0;
236         $this->pos =0;
237         
238         $this->copyright= " No Copyright specified ";
239         $inclass = 0;
240         $class_found =0;
241         $inbrak = 0;
242         $this->total = count($this->tokens);
243         while ($this->pos < $this->total) {
244             if (@$options['debug']) {
245                 echo "{$this->activeFile}:{$this->pos}/{$this->total}\n";
246             }
247             $v = $this->tokens[$this->pos];
248             if (is_array($v)) {
249                 if (!$inbrak && $v[0] == T_WHITESPACE) { 
250                     $this->pos++;
251                     continue 1;
252                 }
253                 //if ($v[0] == T_CURLY_OPEN) { 
254                 //    $this->level++;
255                 //    $this->pos++;
256                 //    continue 1;
257               //  $this->debug(__METHOD__, "{$this->pos}:" .token_name($v[0]) . ":". $v[1]);
258                 switch ($v[0]) {
259                     case T_CLASS:
260                         PHP_CodeDoc_Parser_Class::read();
261                         $class_found=1;
262                         $inclass =1;
263                         break;
264                     case T_FUNCTION:
265                         PHP_CodeDoc_Parser_Method::read($inclass);
266                         break;
267                     case T_VAR:
268                         PHP_CodeDoc_Parser_Var::read($inclass);
269                         break;
270                     case T_STRING:
271                         PHP_CodeDoc_Parser_Define::read($v[1]);
272                         break;
273                     case T_DOC_COMMENT:
274                     case T_COMMENT:
275                     
276                         // merge forthcomming comments
277                         
278                     
279                         $this->last_comment_block = $v[1];
280                         if (substr(trim($v[1]),0,2) == "//") {
281                             
282                             //echo "Got //\n";
283                             $this->pos++;
284                             break;
285                         /*
286                             while (1) {
287                                 //echo "Got ". serialize($this->tokens[$this->pos]) ."\n";
288                                 if (!isset($this->tokens[$this->pos]) || !is_array($this->tokens[$this->pos])) {
289                                     break;
290                                 }
291                                 if ($this->tokens[$this->pos][0] != T_COMMENT) break;
292                                 if (substr(trim($this->tokens[$this->pos][1]),0,2) != "//") break;
293                                 $this->last_comment_block .= $this->tokens[$this->pos][1];
294                                 $this->pos++;
295                             }
296                             $this->pos--;
297                             */
298                         }
299                         if (!$inclass) {
300                         
301                         
302                             if (preg_match('/copyright/is', $this->last_comment_block)) {
303                                 $comment = preg_replace('/^\s*\/(\*)+/m','',$this->last_comment_block);
304                                 $comment = preg_replace('/(\*)+\/\s*$/m','',$comment);
305                                 $comment = preg_replace('/^\s*(\*)+/m','',$comment);
306                                 // tecnically should look back and front and gather all comments
307                                 $this->copyright = $comment;
308                                 //echo "-------storing\n";
309                             }
310                         }
311
312                         
313                         $this->last_comment_block = $v[1];
314                         PHP_CodeDoc_Parser_Docbook::read($this->last_comment_block,$inclass);
315                         break;
316                     default:
317                         //echo "{$this->pos}:" .
318                         //    token_name($v[0]) .
319                         //    ":{$this->level}:". $v[1] .":\n";
320                         //if ($v[1] && $v[1]{0} == "(")
321                         //    $inbrak++;
322                 }
323                 //if ($level < 3) 
324                    //
325             } else {
326                 if (!$inbrak && trim($v) == "}") $this->_level--;
327                 if (!$inbrak && trim($v) == "{") $this->_level++;
328                 if (trim($v) == "(") $inbrak++;
329                 if (trim($v) == ")") $inbrak--;
330                 //if ($level < 3) 
331                     //echo "{$this->pos}:RAW:{$v}\n";
332             }
333             $this->pos++;
334         }
335         return $class_found;
336     }
337     var $classes_by_name; // array assoc name=>class
338     var $classes_by_directory; // array assoc directory=>name=>class
339     var $classes_by_package; // array assoc package=>directory=>name=>class
340     
341     
342     
343     
344         
345     
346     
347     
348     
349     
350     
351      
352     
353     
354     function debugToken($pos) { // print out a token for debugging
355         $v = !is_numeric($pos) ? $pos :  $this->tokens[$pos];
356         if (is_array($v)) {
357             echo ":" .token_name($v[0]) . ":". $v[1] .":\n";
358         } else {
359             echo ":RAW: ". $v .":\n";
360         }
361     }
362
363     /*
364     * find token pos(19,array(T_EXTENDS),"{");
365     * look can be 
366     *   array(T_EXTENDS)
367     *   array(T_STRING,'define')
368     *   '{' or a string
369     *   '' = any string 
370     * look for T_EXTENDS, stop when it gets to 
371     *@ return int Position of token.
372     * 
373     */
374     function find_token_pos($start,$look,$stop) {
375         $p = $start;
376         while ($p < $this->total) {
377             if ($this->compare_token($p,$look))
378                 return $p;
379             if ($this->compare_token($p,$stop))
380                 return FALSE;
381             $p++;
382         }
383     }
384     
385     /**
386      * @returns  false|STRING|constant
387      */
388     
389     function look_nws($num, $stop= '')
390     {
391         $dir = $num > 0 ? 1 : -1;
392         if (!$num) {
393             throw new Exception('invalid direction');
394         }
395         $p = $this->pos + $dir;
396         $c = 0;
397         while(true) {
398             if (!isset($this->tokens[$p] )) {
399                 return false;
400             }
401             $t  = $this->tokens[$p];
402             if (in_array($t[0],array(T_DOC_COMMENT,T_WHITESPACE,T_COMMENT))) {
403                 $p += $dir;
404                 continue;
405             }
406             if ($stop != '' && $t == $stop) {
407                 return false;
408             }
409             $c++;
410             $p += $dir;
411             if ($c >= abs($num)) {
412                 return is_array($t) ? $t[0] : $t;
413             }
414             
415         }
416         
417     }
418     
419     
420     function compare_token($pos,$token) {
421         $t = $this->tokens[$pos];
422         // array compare!
423         if (is_array($t)) {
424             if (!is_array($token)) return FALSE;
425             if ($t[0] != $token[0]) return FALSE;
426             if (count($token) == 1) return TRUE;
427             if (trim($t[1]) != trim($token[1])) return FALSE;
428             return TRUE;
429         }
430         if (is_array($token)) return FALSE;
431         if ($token == "") return TRUE; // looking for a strng
432         if (trim($t) != trim($token)) return FALSE;
433         return TRUE;
434     }
435         
436     /* not sure if this is the best place for this!*/
437     function output_prune($dir) {
438         // say we get /xxx/yyy/zzz
439         // count = 4
440         // we need to replate 1
441         $d = explode('/',$dir);
442         $c= count($d) -2;
443         for ($i = 1; $i< $c;$i++)
444             $d[$i] = "..";
445         return implode('/',$d);
446     }
447     
448     function debug($meth, $com) {
449         return;
450         
451         $t = $this->tokens[$this->pos];
452         
453         echo "{$t[2]}: $meth : $com \n";
454     }
455     function debugTok($pos) {
456         if (!isset($this->tokens[$pos]) || empty($this->tokens[$pos])) {
457             return;
458         }
459         $v = $this->tokens[$pos];
460         var_dump($v);
461         echo "{$v[2]}: $pos:" .token_name($v[0]) . ":". $v[1] ."\n";
462     }
463     
464      
465