1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16 #include "general.h"
17 #include "parse.h"
18 #include "read.h"
19 #include "vstring.h"
20 #include "keyword.h"
21 #include "entry.h"
22 #include "routines.h"
23 #include "debug.h"
24
25 #include <string.h>
26
27 #define SCOPE_SEPARATOR "::"
28
29
30 typedef enum {
31 KEYWORD_NONE = -1,
32 KEYWORD_abstract,
33 KEYWORD_and,
34 KEYWORD_as,
35 KEYWORD_break,
36 KEYWORD_callable,
37 KEYWORD_case,
38 KEYWORD_catch,
39 KEYWORD_class,
40 KEYWORD_clone,
41 KEYWORD_const,
42 KEYWORD_continue,
43 KEYWORD_declare,
44 KEYWORD_define,
45 KEYWORD_default,
46 KEYWORD_do,
47 KEYWORD_echo,
48 KEYWORD_else,
49 KEYWORD_elif,
50 KEYWORD_enddeclare,
51 KEYWORD_endfor,
52 KEYWORD_endforeach,
53 KEYWORD_endif,
54 KEYWORD_endswitch,
55 KEYWORD_endwhile,
56 KEYWORD_extends,
57 KEYWORD_final,
58 KEYWORD_finally,
59 KEYWORD_for,
60 KEYWORD_foreach,
61 KEYWORD_function,
62 KEYWORD_global,
63 KEYWORD_goto,
64 KEYWORD_if,
65 KEYWORD_implements,
66 KEYWORD_include,
67 KEYWORD_include_once,
68 KEYWORD_instanceof,
69 KEYWORD_insteadof,
70 KEYWORD_interface,
71 KEYWORD_namespace,
72 KEYWORD_new,
73 KEYWORD_or,
74 KEYWORD_print,
75 KEYWORD_private,
76 KEYWORD_protected,
77 KEYWORD_public,
78 KEYWORD_require,
79 KEYWORD_require_once,
80 KEYWORD_return,
81 KEYWORD_static,
82 KEYWORD_switch,
83 KEYWORD_throw,
84 KEYWORD_trait,
85 KEYWORD_try,
86 KEYWORD_use,
87 KEYWORD_var,
88 KEYWORD_while,
89 KEYWORD_xor,
90 KEYWORD_yield
91 } keywordId;
92
93 typedef enum {
94 ACCESS_UNDEFINED,
95 ACCESS_PRIVATE,
96 ACCESS_PROTECTED,
97 ACCESS_PUBLIC,
98 COUNT_ACCESS
99 } accessType;
100
101 typedef enum {
102 IMPL_UNDEFINED,
103 IMPL_ABSTRACT,
104 COUNT_IMPL
105 } implType;
106
107 typedef enum {
108 K_CLASS,
109 K_DEFINE,
110 K_FUNCTION,
111 K_INTERFACE,
112 K_LOCAL_VARIABLE,
113 K_NAMESPACE,
114 K_TRAIT,
115 K_VARIABLE,
116 COUNT_KIND
117 } phpKind;
118
119 static kindOption PhpKinds[COUNT_KIND] = {
120 { TRUE, 'c', "class", "classes" },
121 { TRUE, 'd', "define", "constant definitions" },
122 { TRUE, 'f', "function", "functions" },
123 { TRUE, 'i', "interface", "interfaces" },
124 { FALSE, 'l', "local", "local variables" },
125 { TRUE, 'n', "namespace", "namespaces" },
126 { TRUE, 't', "trait", "traits" },
127 { TRUE, 'v', "variable", "variables" }
128 };
129
130 typedef struct {
131 const char *name;
132 keywordId id;
133 } keywordDesc;
134
135 static const keywordDesc PhpKeywordTable[] = {
136
137 { "abstract", KEYWORD_abstract },
138 { "and", KEYWORD_and },
139 { "as", KEYWORD_as },
140 { "break", KEYWORD_break },
141 { "callable", KEYWORD_callable },
142 { "case", KEYWORD_case },
143 { "catch", KEYWORD_catch },
144 { "cfunction", KEYWORD_function },
145 { "class", KEYWORD_class },
146 { "clone", KEYWORD_clone },
147 { "const", KEYWORD_const },
148 { "continue", KEYWORD_continue },
149 { "declare", KEYWORD_declare },
150 { "define", KEYWORD_define },
151 { "default", KEYWORD_default },
152 { "do", KEYWORD_do },
153 { "echo", KEYWORD_echo },
154 { "else", KEYWORD_else },
155 { "elseif", KEYWORD_elif },
156 { "enddeclare", KEYWORD_enddeclare },
157 { "endfor", KEYWORD_endfor },
158 { "endforeach", KEYWORD_endforeach },
159 { "endif", KEYWORD_endif },
160 { "endswitch", KEYWORD_endswitch },
161 { "endwhile", KEYWORD_endwhile },
162 { "extends", KEYWORD_extends },
163 { "final", KEYWORD_final },
164 { "finally", KEYWORD_finally },
165 { "for", KEYWORD_for },
166 { "foreach", KEYWORD_foreach },
167 { "function", KEYWORD_function },
168 { "global", KEYWORD_global },
169 { "goto", KEYWORD_goto },
170 { "if", KEYWORD_if },
171 { "implements", KEYWORD_implements },
172 { "include", KEYWORD_include },
173 { "include_once", KEYWORD_include_once },
174 { "instanceof", KEYWORD_instanceof },
175 { "insteadof", KEYWORD_insteadof },
176 { "interface", KEYWORD_interface },
177 { "namespace", KEYWORD_namespace },
178 { "new", KEYWORD_new },
179 { "or", KEYWORD_or },
180 { "print", KEYWORD_print },
181 { "private", KEYWORD_private },
182 { "protected", KEYWORD_protected },
183 { "public", KEYWORD_public },
184 { "require", KEYWORD_require },
185 { "require_once", KEYWORD_require_once },
186 { "return", KEYWORD_return },
187 { "static", KEYWORD_static },
188 { "switch", KEYWORD_switch },
189 { "throw", KEYWORD_throw },
190 { "trait", KEYWORD_trait },
191 { "try", KEYWORD_try },
192 { "use", KEYWORD_use },
193 { "var", KEYWORD_var },
194 { "while", KEYWORD_while },
195 { "xor", KEYWORD_xor },
196 { "yield", KEYWORD_yield }
197 };
198
199
200 typedef enum eTokenType {
201 TOKEN_UNDEFINED,
202 TOKEN_EOF,
203 TOKEN_CHARACTER,
204 TOKEN_CLOSE_PAREN,
205 TOKEN_SEMICOLON,
206 TOKEN_COLON,
207 TOKEN_COMMA,
208 TOKEN_KEYWORD,
209 TOKEN_OPEN_PAREN,
210 TOKEN_OPERATOR,
211 TOKEN_IDENTIFIER,
212 TOKEN_STRING,
213 TOKEN_PERIOD,
214 TOKEN_OPEN_CURLY,
215 TOKEN_CLOSE_CURLY,
216 TOKEN_EQUAL_SIGN,
217 TOKEN_OPEN_SQUARE,
218 TOKEN_CLOSE_SQUARE,
219 TOKEN_VARIABLE,
220 TOKEN_AMPERSAND
221 } tokenType;
222
223 typedef struct {
224 tokenType type;
225 keywordId keyword;
226 vString * string;
227 vString * scope;
228 unsigned long lineNumber;
229 fpos_t filePosition;
230 int parentKind;
231 } tokenInfo;
232
233 static langType Lang_php;
234
235 static boolean InPhp = FALSE;
236
237
238 struct {
239 accessType access;
240 implType impl;
241 vString* docblock;
242 } CurrentStatement;
243
244
245 vString *CurrentNamesapce;
246
247
248 static void buildPhpKeywordHash (void)
249 {
250 const size_t count = sizeof (PhpKeywordTable) / sizeof (PhpKeywordTable[0]);
251 size_t i;
252 for (i = 0; i < count ; i++)
253 {
254 const keywordDesc* const p = &PhpKeywordTable[i];
255 addKeyword (p->name, Lang_php, (int) p->id);
256 }
257 }
258
259 static const char *accessToString (const accessType access)
260 {
261 static const char *const names[COUNT_ACCESS] = {
262 "undefined",
263 "private",
264 "protected",
265 "public"
266 };
267
268 Assert (access < COUNT_ACCESS);
269
270 return names[access];
271 }
272
273 static const char *implToString (const implType impl)
274 {
275 static const char *const names[COUNT_IMPL] = {
276 "undefined",
277 "abstract"
278 };
279
280 Assert (impl < COUNT_IMPL);
281
282 return names[impl];
283 }
284
285 static void encodeDocBlock (vString *const encoded, vString const *docblock)
286 {
287 int i, len;
288 char *buff, c;
289 buff = vStringValue (docblock);
290 len = strlen (buff);
291
292 vStringClear (encoded);
293
294 for (i = 0; i < len; ++i) {
295 c = buff[i];
296 if (c == '\n')
297 vStringCatS (encoded, "\\n");
298 else if (c == '\r')
299 vStringCatS (encoded, "\\r");
300 else if (c == '\t')
301 vStringCatS (encoded, "\\t");
302 else
303 vStringPut (encoded, c);
304
305 }
306 vStringTerminate (encoded);
307 }
308
309 static void initPhpEntry (tagEntryInfo *const e, const tokenInfo *const token,
310 const phpKind kind, const accessType access, vString *const docblock)
311 {
312 static vString *fullScope = NULL;
313 int parentKind = -1;
314
315 if (fullScope == NULL)
316 fullScope = vStringNew ();
317 else
318 vStringClear (fullScope);
319
320 if (vStringLength (CurrentNamesapce) > 0)
321 {
322 vStringCopy (fullScope, CurrentNamesapce);
323 parentKind = K_NAMESPACE;
324 }
325
326 initTagEntry (e, vStringValue (token->string));
327
328 e->lineNumber = token->lineNumber;
329 e->filePosition = token->filePosition;
330 e->kindName = PhpKinds[kind].name;
331 e->kind = (char) PhpKinds[kind].letter;
332
333 if (access != ACCESS_UNDEFINED)
334 e->extensionFields.access = accessToString (access);
335 if (vStringLength (token->scope) > 0)
336 {
337 parentKind = token->parentKind;
338 if (vStringLength (fullScope) > 0)
339 vStringCatS (fullScope, SCOPE_SEPARATOR);
340 vStringCat (fullScope, token->scope);
341 }
342 if (vStringLength (fullScope) > 0)
343 {
344 Assert (parentKind >= 0);
345
346 vStringTerminate (fullScope);
347 e->extensionFields.scope[0] = PhpKinds[parentKind].name;
348 e->extensionFields.scope[1] = vStringValue (fullScope);
349 }
350 if (vStringLength (docblock) > 0)
351 {
352 e->extensionFields.docblock = vStringValue (docblock);
353 }
354 }
355
356 static void makeSimplePhpTag (const tokenInfo *const token, const phpKind kind,
357 const accessType access)
358 {
359 if (PhpKinds[kind].enabled)
360 {
361 tagEntryInfo e;
362
363 initPhpEntry (&e, token, kind, access, CurrentStatement.docblock);
364 makeTagEntry (&e);
365 }
366 }
367
368 static void makeNamespacePhpTag (const tokenInfo *const token, const vString *const name)
369 {
370 if (PhpKinds[K_NAMESPACE].enabled)
371 {
372 tagEntryInfo e;
373
374 initTagEntry (&e, vStringValue (name));
375
376 e.lineNumber = token->lineNumber;
377 e.filePosition = token->filePosition;
378 e.kindName = PhpKinds[K_NAMESPACE].name;
379 e.kind = (char) PhpKinds[K_NAMESPACE].letter;
380
381 makeTagEntry (&e);
382 }
383 }
384
385 static void makeClassOrIfaceTag (const phpKind kind, const tokenInfo *const token,
386 vString *const inheritance, const implType impl)
387 {
388 if (PhpKinds[kind].enabled)
389 {
390 tagEntryInfo e;
391
392 initPhpEntry (&e, token, kind, ACCESS_UNDEFINED, CurrentStatement.docblock);
393
394 if (impl != IMPL_UNDEFINED)
395 e.extensionFields.implementation = implToString (impl);
396 if (vStringLength (inheritance) > 0)
397 e.extensionFields.inheritance = vStringValue (inheritance);
398
399 makeTagEntry (&e);
400 }
401 }
402
403 static void makeFunctionTag (const tokenInfo *const token,
404 const vString *const arglist,
405 const accessType access, const implType impl)
406 {
407 if (PhpKinds[K_FUNCTION].enabled)
408 {
409 tagEntryInfo e;
410
411 initPhpEntry (&e, token, K_FUNCTION, access, CurrentStatement.docblock);
412
413 if (impl != IMPL_UNDEFINED)
414 e.extensionFields.implementation = implToString (impl);
415 if (arglist)
416 e.extensionFields.signature = vStringValue (arglist);
417
418 makeTagEntry (&e);
419 }
420 }
421
422 static tokenInfo *newToken (void)
423 {
424 tokenInfo *const token = xMalloc (1, tokenInfo);
425
426 token->type = TOKEN_UNDEFINED;
427 token->keyword = KEYWORD_NONE;
428 token->string = vStringNew ();
429 token->scope = vStringNew ();
430 token->lineNumber = getSourceLineNumber ();
431 token->filePosition = getInputFilePosition ();
432 token->parentKind = -1;
433
434 return token;
435 }
436
437 static void deleteToken (tokenInfo *const token)
438 {
439 vStringDelete (token->string);
440 vStringDelete (token->scope);
441 eFree (token);
442 }
443
444 static void copyToken (tokenInfo *const dest, const tokenInfo *const src,
445 boolean scope)
446 {
447 dest->lineNumber = src->lineNumber;
448 dest->filePosition = src->filePosition;
449 dest->type = src->type;
450 dest->keyword = src->keyword;
451 vStringCopy(dest->string, src->string);
452 dest->parentKind = src->parentKind;
453 if (scope)
454 vStringCopy(dest->scope, src->scope);
455 }
456
457 #if 0
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
484
485
486
487
488
489
490
491
492
493
494
495
496
497
498
499
500
501
502
503
504
505
506
507
508
509
510
511
512
513
514
515
516
517
518
519
520
521
522 #endif
523
524 static void addToScope (tokenInfo *const token, const vString *const extra)
525 {
526 if (vStringLength (token->scope) > 0)
527 vStringCatS (token->scope, SCOPE_SEPARATOR);
528 vStringCatS (token->scope, vStringValue (extra));
529 vStringTerminate(token->scope);
530 }
531
532 static boolean isIdentChar (const int c)
533 {
534 return (isalnum (c) || c == '_' || c >= 0x80);
535 }
536
537 static int skipToCharacter (const int c)
538 {
539 int d;
540 do
541 {
542 d = fileGetc ();
543 } while (d != EOF && d != c);
544 return d;
545 }
546
547 static int collectToCharacter (vString *const string, const int c)
548 {
549 int d;
550 do
551 {
552 d = fileGetc ();
553 vStringPut (string, (char) d);
554 } while (d != EOF && d != c);
555 vStringTerminate (string);
556 return d;
557 }
558
559 static void parseString (vString *const string, const int delimiter)
560 {
561 while (TRUE)
562 {
563 int c = fileGetc ();
564
565 if (c == '\\' && (c = fileGetc ()) != EOF)
566 vStringPut (string, (char) c);
567 else if (c == EOF || c == delimiter)
568 break;
569 else
570 vStringPut (string, (char) c);
571 }
572 vStringTerminate (string);
573 }
574
575
576
577
578
579
580
581
582
583
584
585
586
587
588
589
590
591
592
593
594
595
596 static void parseHeredoc (vString *const string)
597 {
598 int c;
599 unsigned int len;
600 char delimiter[64];
601 int quote = 0;
602
603 do
604 {
605 c = fileGetc ();
606 }
607 while (c == ' ' || c == '\t');
608
609 if (c == '\'' || c == '"')
610 {
611 quote = c;
612 c = fileGetc ();
613 }
614 for (len = 0; len < (sizeof delimiter / sizeof delimiter[0]) - 1; len++)
615 {
616 if (! isIdentChar (c))
617 break;
618 delimiter[len] = (char) c;
619 c = fileGetc ();
620 }
621 delimiter[len] = 0;
622
623 if (len == 0)
624 goto error;
625 if (quote)
626 {
627 if (c != quote)
628 goto error;
629 c = fileGetc ();
630 }
631 if (c != '\r' && c != '\n')
632 goto error;
633
634 do
635 {
636 c = fileGetc ();
637
638 if (c != '\r' && c != '\n')
639 vStringPut (string, (char) c);
640 else
641 {
642
643 int nl = c;
644 int extra = EOF;
645
646 c = fileGetc ();
647 for (len = 0; c != 0 && (c - delimiter[len]) == 0; len++)
648 c = fileGetc ();
649
650 if (delimiter[len] != 0)
651 fileUngetc (c);
652 else
653 {
654
655
656 if (c == '\r' || c == '\n')
657 {
658 fileUngetc (c);
659 break;
660 }
661 else if (c == ';')
662 {
663 int d = fileGetc ();
664 if (d == '\r' || d == '\n')
665 {
666
667
668
669 fileUngetc (';');
670 break;
671 }
672 else
673 {
674
675 extra = ';';
676 fileUngetc (d);
677 }
678 }
679 }
680
681
682 vStringPut (string, (char) nl);
683 vStringNCatS (string, delimiter, len);
684 if (extra != EOF)
685 vStringPut (string, (char) extra);
686 }
687 }
688 while (c != EOF);
689
690 vStringTerminate (string);
691
692 return;
693
694 error:
695 fileUngetc (c);
696 }
697
698 static void parseIdentifier (vString *const string, const int firstChar)
699 {
700 int c = firstChar;
701 do
702 {
703 vStringPut (string, (char) c);
704 c = fileGetc ();
705 } while (isIdentChar (c));
706 fileUngetc (c);
707 vStringTerminate (string);
708 }
709
710 static int skipWhitespaces (int c)
711 {
712 while (c == '\t' || c == ' ' || c == '\n' || c == '\r')
713 c = fileGetc ();
714 return c;
715 }
716
717
718
719
720
721 static boolean isOpenScriptLanguagePhp (int c)
722 {
723 int quote = 0;
724
725
726 if (c != '<' ||
727 tolower ((c = fileGetc ())) != 's' ||
728 tolower ((c = fileGetc ())) != 'c' ||
729 tolower ((c = fileGetc ())) != 'r' ||
730 tolower ((c = fileGetc ())) != 'i' ||
731 tolower ((c = fileGetc ())) != 'p' ||
732 tolower ((c = fileGetc ())) != 't' ||
733 ((c = fileGetc ()) != '\t' &&
734 c != ' ' &&
735 c != '\n' &&
736 c != '\r') ||
737 tolower ((c = skipWhitespaces (c))) != 'l' ||
738 tolower ((c = fileGetc ())) != 'a' ||
739 tolower ((c = fileGetc ())) != 'n' ||
740 tolower ((c = fileGetc ())) != 'g' ||
741 tolower ((c = fileGetc ())) != 'u' ||
742 tolower ((c = fileGetc ())) != 'a' ||
743 tolower ((c = fileGetc ())) != 'g' ||
744 tolower ((c = fileGetc ())) != 'e' ||
745 (c = skipWhitespaces (fileGetc ())) != '=')
746 return FALSE;
747
748
749 c = skipWhitespaces (fileGetc ());
750 if (c == '"' || c == '\'')
751 {
752 quote = c;
753 c = fileGetc ();
754 }
755 if (tolower (c) != 'p' ||
756 tolower ((c = fileGetc ())) != 'h' ||
757 tolower ((c = fileGetc ())) != 'p' ||
758 (quote != 0 && (c = fileGetc ()) != quote) ||
759 (c = skipWhitespaces (fileGetc ())) != '>')
760 return FALSE;
761
762 return TRUE;
763 }
764
765 static int findPhpStart (void)
766 {
767 int c;
768 do
769 {
770 if ((c = fileGetc ()) == '<')
771 {
772 c = fileGetc ();
773
774 if (c == '?')
775 {
776
777 if (tolower ((c = fileGetc ())) != 'x' ||
778 tolower ((c = fileGetc ())) != 'm' ||
779 tolower ((c = fileGetc ())) != 'l')
780 {
781 break;
782 }
783 }
784
785 else
786 {
787 fileUngetc (c);
788 if (isOpenScriptLanguagePhp ('<'))
789 break;
790 }
791 }
792 }
793 while (c != EOF);
794
795 return c;
796 }
797
798 static int skipSingleComment (void)
799 {
800 int c;
801 do
802 {
803 c = fileGetc ();
804 if (c == '\r')
805 {
806 int next = fileGetc ();
807 if (next != '\n')
808 fileUngetc (next);
809 else
810 c = next;
811 }
812
813 else if (c == '?')
814 {
815 int next = fileGetc ();
816 if (next == '>')
817 InPhp = FALSE;
818 else
819 fileUngetc (next);
820 }
821 } while (InPhp && c != EOF && c != '\n' && c != '\r');
822 return c;
823 }
824
825 static void readToken (tokenInfo *const token)
826 {
827 int c;
828
829 token->type = TOKEN_UNDEFINED;
830 token->keyword = KEYWORD_NONE;
831 vStringClear (token->string);
832
833 getNextChar:
834
835 if (! InPhp)
836 {
837 c = findPhpStart ();
838 if (c != EOF)
839 InPhp = TRUE;
840 }
841 else
842 c = fileGetc ();
843
844 while (c == '\t' || c == ' ' || c == '\n' || c == '\r')
845 {
846 c = fileGetc ();
847 }
848
849 token->lineNumber = getSourceLineNumber ();
850 token->filePosition = getInputFilePosition ();
851
852 switch (c)
853 {
854 case EOF: token->type = TOKEN_EOF; break;
855 case '(': token->type = TOKEN_OPEN_PAREN; break;
856 case ')': token->type = TOKEN_CLOSE_PAREN; break;
857 case ';': token->type = TOKEN_SEMICOLON; break;
858 case ',': token->type = TOKEN_COMMA; break;
859 case '.': token->type = TOKEN_PERIOD; break;
860 case ':': token->type = TOKEN_COLON; break;
861 case '{': token->type = TOKEN_OPEN_CURLY; break;
862 case '}': token->type = TOKEN_CLOSE_CURLY; break;
863 case '[': token->type = TOKEN_OPEN_SQUARE; break;
864 case ']': token->type = TOKEN_CLOSE_SQUARE; break;
865 case '&': token->type = TOKEN_AMPERSAND; break;
866
867 case '=':
868 {
869 int d = fileGetc ();
870 if (d == '=' || d == '>')
871 token->type = TOKEN_OPERATOR;
872 else
873 {
874 fileUngetc (d);
875 token->type = TOKEN_EQUAL_SIGN;
876 }
877 break;
878 }
879
880 case '\'':
881 case '"':
882 token->type = TOKEN_STRING;
883 parseString (token->string, c);
884 token->lineNumber = getSourceLineNumber ();
885 token->filePosition = getInputFilePosition ();
886 break;
887
888 case '<':
889 {
890 int d = fileGetc ();
891 if (d == '/')
892 {
893
894 if (tolower ((d = fileGetc ())) == 's' &&
895 tolower ((d = fileGetc ())) == 'c' &&
896 tolower ((d = fileGetc ())) == 'r' &&
897 tolower ((d = fileGetc ())) == 'i' &&
898 tolower ((d = fileGetc ())) == 'p' &&
899 tolower ((d = fileGetc ())) == 't' &&
900 (d = skipWhitespaces (fileGetc ())) == '>')
901 {
902 InPhp = FALSE;
903 goto getNextChar;
904 }
905 else
906 {
907 fileUngetc (d);
908 token->type = TOKEN_UNDEFINED;
909 }
910 }
911 else if (d == '<' && (d = fileGetc ()) == '<')
912 {
913 token->type = TOKEN_STRING;
914 parseHeredoc (token->string);
915 }
916 else
917 {
918 fileUngetc (d);
919 token->type = TOKEN_UNDEFINED;
920 }
921 break;
922 }
923
924 case '#':
925 skipSingleComment ();
926 goto getNextChar;
927 break;
928
929 case '+':
930 case '-':
931 case '*':
932 case '%':
933 {
934 int d = fileGetc ();
935 if (d != '=')
936 fileUngetc (d);
937 token->type = TOKEN_OPERATOR;
938 break;
939 }
940
941 case '/':
942 {
943 int d = fileGetc ();
944 if (d == '/')
945 {
946 skipSingleComment ();
947 goto getNextChar;
948 }
949 else if (d == '*')
950 {
951 boolean isDocBlock = FALSE;
952 int d2 = fileGetc ();
953 vString *docblock = NULL;
954 if (d2 == '*') {
955 isDocBlock = TRUE;
956 docblock = vStringNew ();
957 }
958 else
959 fileUngetc (d2);
960
961
962 do
963 {
964 if (isDocBlock == FALSE)
965 c = skipToCharacter ('*');
966 else
967 c = collectToCharacter (docblock, '*');
968
969 if (c != EOF)
970 {
971 c = fileGetc ();
972 if (c == '/')
973 break;
974 else
975 fileUngetc (c);
976 }
977 } while (c != EOF && c != '\0');
978 if (isDocBlock) {
979 vString *encoded = vStringNew ();
980 encodeDocBlock (encoded, docblock);
981 vStringDelete (docblock);
982
983 vStringClear (CurrentStatement.docblock);
984 vStringCatS (CurrentStatement.docblock, "/**");
985 vStringCat (CurrentStatement.docblock, encoded);
986 vStringCatS (CurrentStatement.docblock, "/");
987
988 vStringDelete (encoded);
989 }
990 goto getNextChar;
991 }
992 else
993 {
994 if (d != '=')
995 fileUngetc (d);
996 token->type = TOKEN_OPERATOR;
997 }
998 break;
999 }
1000
1001 case '$':
1002 {
1003 int d = fileGetc ();
1004 if (! isIdentChar (d))
1005 {
1006 fileUngetc (d);
1007 token->type = TOKEN_UNDEFINED;
1008 }
1009 else
1010 {
1011 parseIdentifier (token->string, d);
1012 token->type = TOKEN_VARIABLE;
1013 }
1014 break;
1015 }
1016
1017 case '?':
1018 {
1019 int d = fileGetc ();
1020 if (d == '>')
1021 {
1022 InPhp = FALSE;
1023 goto getNextChar;
1024 }
1025 else
1026 {
1027 fileUngetc (d);
1028 token->type = TOKEN_UNDEFINED;
1029 }
1030 break;
1031 }
1032
1033 default:
1034 if (! isIdentChar (c))
1035 token->type = TOKEN_UNDEFINED;
1036 else
1037 {
1038 parseIdentifier (token->string, c);
1039 token->keyword = analyzeToken (token->string, Lang_php);
1040 if (token->keyword == KEYWORD_NONE)
1041 token->type = TOKEN_IDENTIFIER;
1042 else
1043 token->type = TOKEN_KEYWORD;
1044 }
1045 break;
1046 }
1047
1048 if (token->type == TOKEN_SEMICOLON ||
1049 token->type == TOKEN_OPEN_CURLY ||
1050 token->type == TOKEN_CLOSE_CURLY)
1051 {
1052
1053
1054
1055
1056 CurrentStatement.access = ACCESS_UNDEFINED;
1057 CurrentStatement.impl = IMPL_UNDEFINED;
1058 }
1059 }
1060
1061 static void enterScope (tokenInfo *const parentToken,
1062 const vString *const extraScope,
1063 const int parentKind);
1064
1065
1066
1067
1068
1069
1070
1071 static boolean parseClassOrIface (tokenInfo *const token, const phpKind kind)
1072 {
1073 boolean readNext = TRUE;
1074 implType impl = CurrentStatement.impl;
1075 tokenInfo *name;
1076 vString *inheritance = NULL;
1077
1078 readToken (token);
1079 if (token->type != TOKEN_IDENTIFIER)
1080 return FALSE;
1081
1082 name = newToken ();
1083 copyToken (name, token, TRUE);
1084
1085 inheritance = vStringNew ();
1086
1087
1088 do
1089 {
1090 readToken (token);
1091
1092 if (token->type == TOKEN_IDENTIFIER)
1093 {
1094 if (vStringLength (inheritance) > 0)
1095 vStringPut (inheritance, ',');
1096 vStringCat (inheritance, token->string);
1097 }
1098 }
1099 while (token->type != TOKEN_EOF &&
1100 token->type != TOKEN_OPEN_CURLY);
1101
1102 makeClassOrIfaceTag (kind, name, inheritance, impl);
1103
1104 if (token->type == TOKEN_OPEN_CURLY)
1105 enterScope (token, name->string, K_CLASS);
1106 else
1107 readNext = FALSE;
1108
1109 deleteToken (name);
1110 vStringDelete (inheritance);
1111
1112 return readNext;
1113 }
1114
1115
1116
1117 static boolean parseTrait (tokenInfo *const token)
1118 {
1119 boolean readNext = TRUE;
1120 tokenInfo *name;
1121
1122 readToken (token);
1123 if (token->type != TOKEN_IDENTIFIER)
1124 return FALSE;
1125
1126 name = newToken ();
1127 copyToken (name, token, TRUE);
1128
1129 makeSimplePhpTag (name, K_TRAIT, ACCESS_UNDEFINED);
1130
1131 readToken (token);
1132 if (token->type == TOKEN_OPEN_CURLY)
1133 enterScope (token, name->string, K_TRAIT);
1134 else
1135 readNext = FALSE;
1136
1137 deleteToken (name);
1138
1139 return readNext;
1140 }
1141
1142
1143
1144
1145
1146
1147
1148
1149
1150
1151
1152 static boolean parseFunction (tokenInfo *const token, const tokenInfo *name)
1153 {
1154 boolean readNext = TRUE;
1155 accessType access = CurrentStatement.access;
1156 implType impl = CurrentStatement.impl;
1157 tokenInfo *nameFree = NULL;
1158
1159 readToken (token);
1160
1161 if (token->type == TOKEN_AMPERSAND)
1162 readToken (token);
1163
1164 if (! name)
1165 {
1166 if (token->type != TOKEN_IDENTIFIER)
1167 return FALSE;
1168
1169 name = nameFree = newToken ();
1170 copyToken (nameFree, token, TRUE);
1171 readToken (token);
1172 }
1173
1174 if (token->type == TOKEN_OPEN_PAREN)
1175 {
1176 vString *arglist = vStringNew ();
1177 int depth = 1;
1178
1179 vStringPut (arglist, '(');
1180 do
1181 {
1182 readToken (token);
1183
1184 switch (token->type)
1185 {
1186 case TOKEN_OPEN_PAREN: depth++; break;
1187 case TOKEN_CLOSE_PAREN: depth--; break;
1188 default: break;
1189 }
1190
1191 switch (token->type)
1192 {
1193 case TOKEN_AMPERSAND: vStringPut (arglist, '&'); break;
1194 case TOKEN_CLOSE_CURLY: vStringPut (arglist, '}'); break;
1195 case TOKEN_CLOSE_PAREN: vStringPut (arglist, ')'); break;
1196 case TOKEN_CLOSE_SQUARE: vStringPut (arglist, ']'); break;
1197 case TOKEN_COLON: vStringPut (arglist, ':'); break;
1198 case TOKEN_COMMA: vStringCatS (arglist, ", "); break;
1199 case TOKEN_EQUAL_SIGN: vStringCatS (arglist, " = "); break;
1200 case TOKEN_OPEN_CURLY: vStringPut (arglist, '{'); break;
1201 case TOKEN_OPEN_PAREN: vStringPut (arglist, '('); break;
1202 case TOKEN_OPEN_SQUARE: vStringPut (arglist, '['); break;
1203 case TOKEN_PERIOD: vStringPut (arglist, '.'); break;
1204 case TOKEN_SEMICOLON: vStringPut (arglist, ';'); break;
1205 case TOKEN_STRING:
1206 {
1207 vStringCatS (arglist, "'");
1208 vStringCat (arglist, token->string);
1209 vStringCatS (arglist, "'");
1210 break;
1211 }
1212
1213 case TOKEN_IDENTIFIER:
1214 case TOKEN_KEYWORD:
1215 case TOKEN_VARIABLE:
1216 {
1217 switch (vStringLast (arglist))
1218 {
1219 case 0:
1220 case ' ':
1221 case '{':
1222 case '(':
1223 case '[':
1224 case '.':
1225
1226 break;
1227
1228 default:
1229 vStringPut (arglist, ' ');
1230 break;
1231 }
1232 if (token->type == TOKEN_VARIABLE)
1233 vStringPut (arglist, '$');
1234 vStringCat (arglist, token->string);
1235 break;
1236 }
1237
1238 default: break;
1239 }
1240 }
1241 while (token->type != TOKEN_EOF && depth > 0);
1242
1243 vStringTerminate (arglist);
1244
1245 makeFunctionTag (name, arglist, access, impl);
1246 vStringDelete (arglist);
1247
1248 readToken (token);
1249 }
1250
1251
1252 if (token->type == TOKEN_KEYWORD && token->keyword == KEYWORD_use)
1253 {
1254 readToken (token);
1255 if (token->type == TOKEN_OPEN_PAREN)
1256 {
1257 int depth = 1;
1258
1259 do
1260 {
1261 readToken (token);
1262 switch (token->type)
1263 {
1264 case TOKEN_OPEN_PAREN: depth++; break;
1265 case TOKEN_CLOSE_PAREN: depth--; break;
1266 default: break;
1267 }
1268 }
1269 while (token->type != TOKEN_EOF && depth > 0);
1270
1271 readToken (token);
1272 }
1273 }
1274
1275 if (token->type == TOKEN_OPEN_CURLY)
1276 enterScope (token, name->string, K_FUNCTION);
1277 else
1278 readNext = FALSE;
1279
1280 if (nameFree)
1281 deleteToken (nameFree);
1282
1283 return readNext;
1284 }
1285
1286
1287
1288 static boolean parseConstant (tokenInfo *const token)
1289 {
1290 tokenInfo *name;
1291
1292 readToken (token);
1293 if (token->type != TOKEN_IDENTIFIER)
1294 return FALSE;
1295
1296 name = newToken ();
1297 copyToken (name, token, TRUE);
1298
1299 readToken (token);
1300 if (token->type == TOKEN_EQUAL_SIGN)
1301 makeSimplePhpTag (name, K_DEFINE, ACCESS_UNDEFINED);
1302
1303 deleteToken (name);
1304
1305 return token->type == TOKEN_EQUAL_SIGN;
1306 }
1307
1308
1309
1310
1311 static boolean parseDefine (tokenInfo *const token)
1312 {
1313 int depth = 1;
1314
1315 readToken (token);
1316 if (token->type != TOKEN_OPEN_PAREN)
1317 return FALSE;
1318
1319 readToken (token);
1320 if (token->type == TOKEN_STRING ||
1321 token->type == TOKEN_IDENTIFIER)
1322 {
1323 makeSimplePhpTag (token, K_DEFINE, ACCESS_UNDEFINED);
1324 readToken (token);
1325 }
1326
1327
1328
1329
1330
1331
1332
1333 while (token->type != TOKEN_EOF && depth > 0)
1334 {
1335 switch (token->type)
1336 {
1337 case TOKEN_OPEN_PAREN: depth++; break;
1338 case TOKEN_CLOSE_PAREN: depth--; break;
1339 default: break;
1340 }
1341 readToken (token);
1342 }
1343
1344 return FALSE;
1345 }
1346
1347
1348
1349
1350 static boolean parseVariable (tokenInfo *const token)
1351 {
1352 tokenInfo *name;
1353 boolean readNext = TRUE;
1354 accessType access = CurrentStatement.access;
1355
1356 name = newToken ();
1357 copyToken (name, token, TRUE);
1358
1359 readToken (token);
1360 if (token->type == TOKEN_EQUAL_SIGN)
1361 {
1362 phpKind kind = K_VARIABLE;
1363
1364 if (token->parentKind == K_FUNCTION)
1365 kind = K_LOCAL_VARIABLE;
1366
1367 readToken (token);
1368 if (token->type == TOKEN_KEYWORD &&
1369 token->keyword == KEYWORD_function &&
1370 PhpKinds[kind].enabled)
1371 {
1372 if (parseFunction (token, name))
1373 readToken (token);
1374 readNext = (boolean) (token->type == TOKEN_SEMICOLON);
1375 }
1376 else
1377 {
1378 makeSimplePhpTag (name, kind, access);
1379 readNext = FALSE;
1380 }
1381 }
1382 else if (token->type == TOKEN_SEMICOLON)
1383 {
1384
1385
1386
1387
1388
1389 if (token->parentKind == K_CLASS || token->parentKind == K_INTERFACE)
1390 makeSimplePhpTag (name, K_VARIABLE, access);
1391 }
1392 else
1393 readNext = FALSE;
1394
1395 deleteToken (name);
1396
1397 return readNext;
1398 }
1399
1400
1401
1402
1403
1404
1405
1406
1407 static boolean parseNamespace (tokenInfo *const token)
1408 {
1409 tokenInfo *nsToken = newToken ();
1410
1411 vStringClear (CurrentNamesapce);
1412 copyToken (nsToken, token, FALSE);
1413
1414 do
1415 {
1416 readToken (token);
1417 if (token->type == TOKEN_IDENTIFIER)
1418 {
1419 if (vStringLength (CurrentNamesapce) > 0)
1420 vStringPut (CurrentNamesapce, '\\');
1421 vStringCat (CurrentNamesapce, token->string);
1422 }
1423 }
1424 while (token->type != TOKEN_EOF &&
1425 token->type != TOKEN_SEMICOLON &&
1426 token->type != TOKEN_OPEN_CURLY);
1427
1428 vStringTerminate (CurrentNamesapce);
1429 if (vStringLength (CurrentNamesapce) > 0)
1430 makeNamespacePhpTag (nsToken, CurrentNamesapce);
1431
1432 if (token->type == TOKEN_OPEN_CURLY)
1433 enterScope (token, NULL, -1);
1434
1435 deleteToken (nsToken);
1436
1437 return TRUE;
1438 }
1439
1440 static void enterScope (tokenInfo *const parentToken,
1441 const vString *const extraScope,
1442 const int parentKind)
1443 {
1444 tokenInfo *token = newToken ();
1445 int origParentKind = parentToken->parentKind;
1446
1447 copyToken (token, parentToken, TRUE);
1448
1449 if (extraScope)
1450 {
1451 addToScope (token, extraScope);
1452 token->parentKind = parentKind;
1453 }
1454
1455 readToken (token);
1456 while (token->type != TOKEN_EOF &&
1457 token->type != TOKEN_CLOSE_CURLY)
1458 {
1459 boolean readNext = TRUE;
1460
1461 switch (token->type)
1462 {
1463 case TOKEN_OPEN_CURLY:
1464 enterScope (token, NULL, -1);
1465 vStringClear (CurrentStatement.docblock);
1466 break;
1467
1468 case TOKEN_KEYWORD:
1469 switch (token->keyword)
1470 {
1471 case KEYWORD_class:
1472 readNext = parseClassOrIface (token, K_CLASS);
1473 vStringClear (CurrentStatement.docblock);
1474 break;
1475 case KEYWORD_interface:
1476 readNext = parseClassOrIface (token, K_INTERFACE);
1477 vStringClear (CurrentStatement.docblock);
1478 break;
1479 case KEYWORD_trait:
1480 readNext = parseTrait (token);
1481 vStringClear (CurrentStatement.docblock);
1482 break;
1483 case KEYWORD_function:
1484 readNext = parseFunction (token, NULL);
1485 vStringClear (CurrentStatement.docblock);
1486 break;
1487 case KEYWORD_const:
1488 readNext = parseConstant (token);
1489 vStringClear (CurrentStatement.docblock);
1490 break;
1491 case KEYWORD_define:
1492 readNext = parseDefine (token);
1493 vStringClear (CurrentStatement.docblock);
1494 break;
1495
1496 case KEYWORD_namespace:
1497 readNext = parseNamespace (token);
1498 vStringClear (CurrentStatement.docblock);
1499 break;
1500
1501 case KEYWORD_private: CurrentStatement.access = ACCESS_PRIVATE; break;
1502 case KEYWORD_protected: CurrentStatement.access = ACCESS_PROTECTED; break;
1503 case KEYWORD_public: CurrentStatement.access = ACCESS_PUBLIC; break;
1504 case KEYWORD_var:
1505 CurrentStatement.access = ACCESS_PUBLIC;
1506 vStringClear (CurrentStatement.docblock);
1507 break;
1508
1509 case KEYWORD_abstract: CurrentStatement.impl = IMPL_ABSTRACT; break;
1510
1511 default: break;
1512 }
1513 break;
1514
1515 case TOKEN_VARIABLE:
1516 readNext = parseVariable (token);
1517 vStringClear (CurrentStatement.docblock);
1518 break;
1519
1520 default: break;
1521 }
1522
1523 if (readNext)
1524 readToken (token);
1525 }
1526
1527 copyToken (parentToken, token, FALSE);
1528 parentToken->parentKind = origParentKind;
1529 deleteToken (token);
1530 }
1531
1532 static void findPhpTags (void)
1533 {
1534 tokenInfo *const token = newToken ();
1535
1536 InPhp = FALSE;
1537 CurrentStatement.access = ACCESS_UNDEFINED;
1538 CurrentStatement.impl = IMPL_UNDEFINED;
1539 CurrentStatement.docblock = vStringNew ();
1540 CurrentNamesapce = vStringNew ();
1541
1542 do
1543 {
1544 enterScope (token, NULL, -1);
1545 }
1546 while (token->type != TOKEN_EOF);
1547
1548 vStringDelete (CurrentNamesapce);
1549 deleteToken (token);
1550 }
1551
1552 static void initialize (const langType language)
1553 {
1554 Lang_php = language;
1555 buildPhpKeywordHash ();
1556 }
1557
1558 extern parserDefinition* PhpParser (void)
1559 {
1560 static const char *const extensions [] = { "php", "php3", "php4", "php5", "phtml", NULL };
1561 parserDefinition* def = parserNew ("PHP");
1562 def->kinds = PhpKinds;
1563 def->kindCount = KIND_COUNT (PhpKinds);
1564 def->extensions = extensions;
1565 def->parser = findPhpTags;
1566 def->initialize = initialize;
1567 return def;
1568 }
1569
1570