Fix #6692 - fix docs
[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 (!preg_match("/\.(inc|php|class)$/",$file)) {
144                     continue;
145                 }
146                 $this->files[$filename][] = $file;
147                  
148             } elseif (is_dir($filename."/".$file)) {
149                 if ($file  == "CVS") continue;
150                 if ($file  == "RCS") continue;
151             
152                 $this->_build($filename . "/". $file);
153                 
154             }
155         }
156         closedir($fh);
157         //echo "\nDO STUFF\n";
158         
159           
160     }    
161     function _parseFile($filename) { // tockenize a file and start parsing
162         echo "PARSING: $filename\n";
163         
164         
165         $this->activeFile = $filename;
166         if (!filesize ($filename)) return;
167          
168         
169         $this->rdir = substr(dirname(realpath($this->activeFile)),$this->_base_dir_len);
170         $this->rfilename = substr(realpath($this->activeFile),$this->_base_dir_len);
171         $tmpar = explode ('/', $this->rfilename);
172         $tmpar[1] = '';
173         $base = substr(implode('/',$tmpar),2);
174         $this->file_url = $this->original_URL_base . $base;
175         
176         if (@$this->tokens) unset($this->tokens);
177         $data = implode('',file($filename));
178         $this->tokens = token_get_all($data);
179         /* use this if you have a broken tokenizer
180         $fh = popen(dirname(__FILE__)."/tokenizer_serialize.php $filename","r");
181         $this->tokens= unserialize(fread($fh,filesize($filename) * 16));
182         pclose($fh);
183         */
184         
185         $pname= 'No Package';
186        $options = PHP_CodeDoc::$options;
187         if (!$options['perDirPackages']) {
188             if (preg_match('/^\s*\*\s*@package\s*(.*)$/im',$data,$args)) {
189                 //echo "\nGot package {$this->package}\n";
190                 $pname = trim($args[1]);
191             }
192         } else {
193             $parts = explode('/',$this->rfilename);
194             $pname = $parts[1];
195             if ($this->rfilename{0} == "/") $pname = $parts[2];
196             $pname = preg_replace('/\.php$/','',$pname);
197             if (!$pname) $pname = "No Package";
198             echo "\nUsing package (from dir {$this->rfilename} ) {$pname}\n";
199         }
200         
201        
202         
203         if (!@$this->packages[$pname]) {
204             $this->packages[$pname] = new PHP_Codedoc_Data_Package;
205             $this->packages[$pname]->name = $pname;
206         }
207         $this->active_package = &$this->packages[$pname];
208         
209        
210         if (!@$this->active_package->directories[$this->rdir]) {
211             $this->active_package->directories[$this->rdir] = new PHP_Codedoc_Data_Directory;
212             $this->active_package->directories[$this->rdir]->name = $this->rdir;
213         }
214         $this->active_directory = &$this->active_package->directories[$this->rdir];
215
216         //print_r($data); exit;
217         //    $this->all_tokens[$filename] = token_get_all($contents);
218          //   $this->tokens =& $this->all_tokens[$filename];
219         //global $tokens;
220         // 
221         // $this->tokens =  &$tokens;
222         $this->total = count($this->tokens);
223         if ($this->total)
224             $this->_parse();
225         unset($this->tokens);
226         unset($this->total);
227             
228     }
229         
230     var $last_comment_block = ""; // last comment block found
231     
232     
233     function _parse() { // read the tokens and make the classes
234         $options = PHP_CodeDoc::$options;
235     
236         
237         $this->_level=0;
238         $this->pos =0;
239         
240         $this->copyright= " No Copyright specified ";
241         $inclass = 0;
242         $class_found =0;
243         $inbrak = 0;
244         $this->total = count($this->tokens);
245         while ($this->pos < $this->total) {
246             if (@$options['debug']) {
247                 echo "{$this->activeFile}:{$this->pos}/{$this->total}\n";
248             }
249             $v = $this->tokens[$this->pos];
250             if (is_array($v)) {
251                 if (!$inbrak && $v[0] == T_WHITESPACE) { 
252                     $this->pos++;
253                     continue 1;
254                 }
255                 //if ($v[0] == T_CURLY_OPEN) { 
256                 //    $this->level++;
257                 //    $this->pos++;
258                 //    continue 1;
259               //  $this->debug(__METHOD__, "{$this->pos}:" .token_name($v[0]) . ":". $v[1]);
260                 switch ($v[0]) {
261                     case T_CLASS:
262                         PHP_CodeDoc_Parser_Class::read($this);
263                         $class_found=1;
264                         $inclass =1;
265                         break;
266                     case T_FUNCTION:
267                         PHP_CodeDoc_Parser_Method::read($this, $inclass);
268                         break;
269                     case T_CONST:
270                     case T_VAR:
271                         PHP_CodeDoc_Parser_Var::read($this, $inclass);
272                         break;
273                     case T_STRING:
274                         PHP_CodeDoc_Parser_Define::read($this, $v[1]);
275                         break;
276                     case T_DOC_COMMENT:
277                     case T_COMMENT:
278                     
279                         // merge forthcomming comments
280                         
281                     
282                         $this->last_comment_block = $v[1];
283                         if (substr(trim($v[1]),0,2) == "//") {
284                             
285                             //echo "Got //\n";
286                             $this->pos++;
287                             break;
288                         /*
289                             while (1) {
290                                 //echo "Got ". serialize($this->tokens[$this->pos]) ."\n";
291                                 if (!isset($this->tokens[$this->pos]) || !is_array($this->tokens[$this->pos])) {
292                                     break;
293                                 }
294                                 if ($this->tokens[$this->pos][0] != T_COMMENT) break;
295                                 if (substr(trim($this->tokens[$this->pos][1]),0,2) != "//") break;
296                                 $this->last_comment_block .= $this->tokens[$this->pos][1];
297                                 $this->pos++;
298                             }
299                             $this->pos--;
300                             */
301                         }
302                         if (!$inclass) {
303                         
304                         
305                             if (preg_match('/copyright/is', $this->last_comment_block)) {
306                                 $comment = preg_replace('/^\s*\/(\*)+/m','',$this->last_comment_block);
307                                 $comment = preg_replace('/(\*)+\/\s*$/m','',$comment);
308                                 $comment = preg_replace('/^\s*(\*)+/m','',$comment);
309                                 // tecnically should look back and front and gather all comments
310                                 $this->copyright = $comment;
311                                 //echo "-------storing\n";
312                             }
313                         }
314
315                         
316                         $this->last_comment_block = $v[1];
317                         PHP_CodeDoc_Parser_Docbook::read($this, $this->last_comment_block,$inclass);
318                         break;
319                     default:
320                         //echo "{$this->pos}:" .
321                         //    token_name($v[0]) .
322                         //    ":{$this->level}:". $v[1] .":\n";
323                         //if ($v[1] && $v[1]{0} == "(")
324                         //    $inbrak++;
325                 }
326                 //if ($level < 3) 
327                    //
328             } else {
329                 if (!$inbrak && trim($v) == "}") $this->_level--;
330                 if (!$inbrak && trim($v) == "{") $this->_level++;
331                 if (trim($v) == "(") $inbrak++;
332                 if (trim($v) == ")") $inbrak--;
333                 //if ($level < 3) 
334                     //echo "{$this->pos}:RAW:{$v}\n";
335             }
336             $this->pos++;
337         }
338         return $class_found;
339     }
340     var $classes_by_name; // array assoc name=>class
341     var $classes_by_directory; // array assoc directory=>name=>class
342     var $classes_by_package; // array assoc package=>directory=>name=>class
343     
344     
345     
346     
347         
348     
349     
350     
351     
352     
353     
354      
355     
356     
357     function debugToken($pos) { // print out a token for debugging
358         $v = !is_numeric($pos) ? $pos :  $this->tokens[$pos];
359         if (is_array($v)) {
360             echo ":" .token_name($v[0]) . ":". $v[1] .":\n";
361         } else {
362             echo ":RAW: ". $v .":\n";
363         }
364     }
365
366     /*
367     * find token pos(19,array(T_EXTENDS),"{");
368     * look can be 
369     *   array(T_EXTENDS)
370     *   array(T_STRING,'define')
371     *   '{' or a string
372     *   '' = any string 
373     * look for T_EXTENDS, stop when it gets to 
374     *@ return int Position of token.
375     * 
376     */
377     function find_token_pos($start,$look,$stop) {
378         $p = $start;
379         while ($p < $this->total) {
380             if ($this->compare_token($p,$look))
381                 return $p;
382             if ($this->compare_token($p,$stop))
383                 return FALSE;
384             $p++;
385         }
386     }
387     
388     /**
389      * look for items... its' really used to look backwards for public/static etc...
390      * @returns  false|STRING|constant
391      */
392     
393     function look_nws($num)
394     {
395         $dir = $num > 0 ? 1 : -1;
396         if (!$num) {
397             throw new Exception('invalid direction');
398         }
399         $p = $this->pos + $dir;
400         $c = 0;
401         while(true) {
402             if (!isset($this->tokens[$p] )) {
403                 return false;
404             }
405             $t  = $this->tokens[$p];
406             if (in_array($t[0],array(T_DOC_COMMENT,T_WHITESPACE,T_COMMENT))) {
407                 $p += $dir;
408                 continue;
409             }
410             if (is_string($t)) { // stop on puncutation...
411                 return false;
412             }
413             $c++;
414             $p += $dir;
415             if ($c >= abs($num)) {
416                 return is_array($t) ? $t[0] : $t;
417             }
418             
419         }
420         
421     }
422     
423     
424     function compare_token($pos,$token) {
425         $t = $this->tokens[$pos];
426         // array compare!
427         if (is_array($t)) {
428             if (!is_array($token)) return FALSE;
429             if ($t[0] != $token[0]) return FALSE;
430             if (count($token) == 1) return TRUE;
431             if (trim($t[1]) != trim($token[1])) return FALSE;
432             return TRUE;
433         }
434         if (is_array($token)) return FALSE;
435         if ($token == "") return TRUE; // looking for a strng
436         if (trim($t) != trim($token)) return FALSE;
437         return TRUE;
438     }
439         
440     /* not sure if this is the best place for this!*/
441     function output_prune($dir) {
442         // say we get /xxx/yyy/zzz
443         // count = 4
444         // we need to replate 1
445         $d = explode('/',$dir);
446         $c= count($d) -2;
447         for ($i = 1; $i< $c;$i++)
448             $d[$i] = "..";
449         return implode('/',$d);
450     }
451     
452     function debug($meth, $com) {
453         return;
454         
455         $t = $this->tokens[$this->pos];
456         
457         echo "{$t[2]}: $meth : $com \n";
458     }
459     function debugTok($pos) {
460         if (!isset($this->tokens[$pos]) || empty($this->tokens[$pos])) {
461             return;
462         }
463         $v = $this->tokens[$pos];
464         var_dump($v);
465         echo "{$v[2]}: $pos:" .token_name($v[0]) . ":". $v[1] ."\n";
466     }
467     
468      
469