1 /*******************************************************************************
2 Copyright (c) 2017, Michael Wallner <mike@php.net>.
5 Redistribution and use in source and binary forms, with or without
6 modification, are permitted provided that the following conditions are met:
8 * Redistributions of source code must retain the above copyright notice,
9 this list of conditions and the following disclaimer.
10 * Redistributions in binary form must reproduce the above copyright
11 notice, this list of conditions and the following disclaimer in the
12 documentation and/or other materials provided with the distribution.
14 THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
15 AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
16 IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
17 DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE
18 FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
19 DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
20 SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
21 CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
22 OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
23 OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
24 *******************************************************************************/
26 #include "php_psi_stdinc.h"
33 #define PSI_CPP_SEARCH
34 #define PSI_CPP_PREDEF
35 #include "php_psi_cpp.h"
39 static void free_cpp_def(zval
*p
)
41 if (Z_TYPE_P(p
) == IS_PTR
) {
42 psi_cpp_macro_decl_free((void *) &Z_PTR_P(p
));
46 struct psi_cpp
*psi_cpp_init(struct psi_parser
*P
)
48 struct psi_cpp
*cpp
= calloc(1, sizeof(*cpp
));
51 zend_hash_init(&cpp
->defs
, 0, NULL
, free_cpp_def
, 1);
52 zend_hash_init(&cpp
->once
, 0, NULL
, NULL
, 1);
57 bool psi_cpp_load_defaults(struct psi_cpp
*cpp
)
59 struct psi_parser_input
*predef
;
61 if ((predef
= psi_parser_open_string(cpp
->parser
, psi_cpp_predef
, sizeof(psi_cpp_predef
) - 1))) {
62 bool parsed
= psi_parser_parse(cpp
->parser
, predef
);
71 static int dump_def(zval
*p
)
73 struct psi_cpp_macro_decl
*decl
= Z_PTR_P(p
);
76 dprintf(2, "#define ");
77 psi_cpp_macro_decl_dump(2, decl
);
80 return ZEND_HASH_APPLY_KEEP
;
84 void psi_cpp_free(struct psi_cpp
**cpp_ptr
)
87 struct psi_cpp
*cpp
= *cpp_ptr
;
90 fprintf(stderr
, "PSI: CPP decls:\n");
91 zend_hash_apply(&cpp
->defs
, dump_def
);
94 zend_hash_destroy(&cpp
->defs
);
95 zend_hash_destroy(&cpp
->once
);
100 static bool psi_cpp_stage1(struct psi_cpp
*cpp
)
102 bool name
= false, define
= false, hash
= false, eol
= true, esc
= false, ws
= false;
104 PSI_DEBUG_PRINT(cpp
->parser
, "PSI: CPP %s\n", "stage1");
106 psi_cpp_tokiter_reset(cpp
);
107 while (psi_cpp_tokiter_valid(cpp
)) {
108 struct psi_token
*token
= psi_cpp_tokiter_current(cpp
);
110 /* strip comments and attributes */
111 if (token
->type
== PSI_T_COMMENT
112 || token
->type
== PSI_T_CPP_ATTRIBUTE
) {
113 psi_cpp_tokiter_del_cur(cpp
, true);
117 /* line continuations */
118 if (token
->type
== PSI_T_EOL
) {
120 psi_cpp_tokiter_del_range(cpp
, psi_cpp_tokiter_index(cpp
) - 1, 2, true);
121 psi_cpp_tokiter_prev(cpp
);
125 } else if (token
->type
== PSI_T_BSLASH
) {
131 /* this whole turf is needed to distinct between:
132 * #define foo (1,2,3)
136 if (token
->type
== PSI_T_WHITESPACE
) {
141 psi_cpp_tokiter_del_cur(cpp
, true);
145 switch (token
->type
) {
171 /* mask special token for parser */
172 struct psi_token
*no_ws
= psi_token_copy(token
);
174 no_ws
->type
= PSI_T_NO_WHITESPACE
;
175 no_ws
->text
[0] = '\xA0';
176 psi_cpp_tokiter_ins_cur(cpp
, no_ws
);
182 name
= define
= hash
= eol
= false;
187 psi_cpp_tokiter_next(cpp
);
193 static bool psi_cpp_stage2(struct psi_cpp
*cpp
)
195 struct psi_plist
*parser_tokens
= psi_plist_init((psi_plist_dtor
) psi_token_free
);
196 bool is_eol
= true, do_cpp
= false, do_expansion
= true, skip_paren
= false, skip_all
= false;
198 PSI_DEBUG_PRINT(cpp
->parser
, "PSI: CPP %s\n", "stage2");
200 psi_cpp_tokiter_reset(cpp
);
201 while (psi_cpp_tokiter_valid(cpp
)) {
202 struct psi_token
*current
= psi_cpp_tokiter_current(cpp
);
204 if (current
->type
== PSI_T_HASH
) {
209 } else if (current
->type
== PSI_T_EOL
) {
211 fprintf(stderr
, "PSI: CPP do_expansion=true, PSI_T_EOL\n");
217 psi_cpp_tokiter_del_cur(cpp
, true);
224 switch (current
->type
) {
227 fprintf(stderr
, "PSI: CPP do_expansion=false, PSI_T_DEFINE, skip_all\n");
229 do_expansion
= false;
239 fprintf(stderr
, "PSI: CPP do_expansion=false, PSI_T_{IF{,N},UN}DEF\n");
241 do_expansion
= false;
251 fprintf(stderr
, "PSI: CPP do_expansion=true, PSI_T_LPAREN, !skip_all, !skip_paren\n");
259 do_expansion
= !skip_all
;
261 fprintf(stderr
, "PSI: CPP do_expansion=%s, <- !skip_all\n", do_expansion
?"true":"false");
268 /* FIXME: del_range */
271 fprintf(stderr
, "PSI: CPP skip ");
272 psi_token_dump(2, current
);
274 psi_cpp_tokiter_del_cur(cpp
, true);
279 if (do_expansion
&& current
->type
== PSI_T_NAME
&& psi_cpp_tokiter_defined(cpp
)) {
280 bool expanded
= false;
282 while (psi_cpp_tokiter_expand(cpp
)) {
291 parser_tokens
= psi_plist_add(parser_tokens
, ¤t
);
294 size_t processed
= 0;
295 bool parsed
= psi_parser_process(cpp
->parser
, parser_tokens
, &processed
);
298 psi_plist_pop(parser_tokens
, NULL
);
299 psi_plist_clean(parser_tokens
);
303 psi_plist_free(parser_tokens
);
307 /* leave EOLs in the input stream, else we might end up
308 * with a hash not preceded with a new line after include */
309 psi_cpp_tokiter_del_cur(cpp
, false);
312 #if PSI_CPP_DEBUG > 1
313 psi_cpp_tokiter_dump(2, cpp
);
319 psi_cpp_tokiter_next(cpp
);
322 psi_plist_free(parser_tokens
);
327 bool psi_cpp_process(struct psi_cpp
*cpp
, struct psi_plist
**tokens
)
330 struct psi_cpp temp
= *cpp
;
332 cpp
->tokens
= *tokens
;
333 if (psi_cpp_stage1(cpp
) && psi_cpp_stage2(cpp
)) {
336 *tokens
= cpp
->tokens
;
339 cpp
->tokens
= temp
.tokens
;
340 cpp
->index
= temp
.index
;
346 bool psi_cpp_defined(struct psi_cpp
*cpp
, struct psi_token
*tok
)
350 if (tok
->type
== PSI_T_NAME
) {
351 defined
= zend_hash_str_exists(&cpp
->defs
, tok
->text
, tok
->size
);
357 fprintf(stderr
, "PSI: CPP defined -> %s ", defined
? "true" : "false");
359 struct psi_cpp_macro_decl
*macro
= zend_hash_str_find_ptr(&cpp
->defs
, tok
->text
, tok
->size
);
360 fprintf(stderr
, " @ %s:%u ", macro
->token
->file
, macro
->token
->line
);
362 psi_token_dump(2, tok
);
368 void psi_cpp_define(struct psi_cpp
*cpp
, struct psi_cpp_macro_decl
*decl
)
370 struct psi_cpp_macro_decl
*old
= zend_hash_str_find_ptr(&cpp
->defs
, decl
->token
->text
, decl
->token
->size
);
372 if (old
&& !psi_cpp_macro_decl_equal(old
, decl
)) {
373 cpp
->parser
->error(PSI_DATA(cpp
->parser
), decl
->token
, PSI_WARNING
,
374 "'%s' redefined", decl
->token
->text
);
375 cpp
->parser
->error(PSI_DATA(cpp
->parser
), old
->token
, PSI_WARNING
,
376 "'%s' previously defined", old
->token
->text
);
380 fprintf(stderr
, "MACRO: num_exp: ", decl
->token
->text
);
381 } else if (decl
->tokens
) {
382 fprintf(stderr
, "MACRO: decl : ", decl
->token
->text
);
384 psi_cpp_macro_decl_dump(2, decl
);
385 fprintf(stderr
, "\n");
387 zend_hash_str_update_ptr(&cpp
->defs
, decl
->token
->text
, decl
->token
->size
, decl
);
390 bool psi_cpp_undef(struct psi_cpp
*cpp
, struct psi_token
*tok
)
392 return SUCCESS
== zend_hash_str_del(&cpp
->defs
, tok
->text
, tok
->size
);
395 bool psi_cpp_if(struct psi_cpp
*cpp
, struct psi_cpp_exp
*exp
)
397 struct psi_validate_scope scope
= {0};
399 scope
.defs
= &cpp
->defs
;
400 if (!psi_num_exp_validate(PSI_DATA(cpp
->parser
), exp
->data
.num
, &scope
)) {
403 if (!psi_num_exp_get_long(exp
->data
.num
, NULL
, &cpp
->defs
)) {
409 static inline bool try_include(struct psi_cpp
*cpp
, const char *path
, bool *parsed
)
411 struct psi_parser_input
*include
;
413 PSI_DEBUG_PRINT(cpp
->parser
, "PSI: CPP include trying %s\n", path
);
415 include
= psi_parser_open_file(cpp
->parser
, path
, false);
417 struct psi_plist
*tokens
;
419 PSI_DEBUG_PRINT(cpp
->parser
, "PSI: CPP include scanning %s\n", path
);
421 tokens
= psi_parser_scan(cpp
->parser
, include
);
423 *parsed
= psi_cpp_process(cpp
, &tokens
);
426 size_t num_tokens
= psi_plist_count(tokens
);
429 psi_cpp_tokiter_ins_range(cpp
, cpp
->index
,
430 num_tokens
, psi_plist_eles(tokens
));
431 /* skip already processed tokens */
432 cpp
->index
+= num_tokens
;
435 psi_plist_free(tokens
);
440 zend_hash_str_add_empty_element(&cpp
->once
, path
, strlen(path
));
446 static inline void include_path(const struct psi_token
*file
, char **path
)
448 if (*file
->text
== '/') {
454 strncpy(*path
, file
->file
, PATH_MAX
);
456 dir
= dirname(*path
);
459 assert(len
+ file
->size
+ 1 < PATH_MAX
);
461 memmove(*path
, dir
, len
);
463 memcpy(&(*path
)[len
+ 1], file
->text
, file
->size
+ 1);
467 bool psi_cpp_include(struct psi_cpp
*cpp
, const struct psi_token
*file
, unsigned flags
)
470 int f_len
= strlen(file
->text
);
472 if (file
->type
== PSI_T_QUOTED_STRING
&& (!(flags
& PSI_CPP_INCLUDE_NEXT
) || *file
->text
== '/')) {
473 /* first try as is, full or relative path */
474 char temp
[PATH_MAX
], *path
= temp
;
476 include_path(file
, &path
);
478 if ((flags
& PSI_CPP_INCLUDE_ONCE
) && zend_hash_str_exists(&cpp
->once
, path
, f_len
)) {
481 if (try_include(cpp
, path
, &parsed
)) {
487 /* look through search paths */
488 if (*file
->text
!= '/') {
493 if ((flags
& PSI_CPP_INCLUDE_NEXT
) && cpp
->search
) {
494 if ((sep
= strchr(cpp
->search
, ':'))) {
495 cpp
->search
= sep
+ 1;
497 /* point to end of string */
498 cpp
->search
+= strlen(cpp
->search
);
502 if (!(flags
& PSI_CPP_INCLUDE_NEXT
)) {
503 cpp
->search
= PSI_G(search_path
);
509 sep
= strchr(cpp
->search
, ':');
510 d_len
= sep
? sep
- cpp
->search
: strlen(cpp
->search
);
512 if (PATH_MAX
> (p_len
= snprintf(path
, PATH_MAX
, "%.*s/%.*s", d_len
, cpp
->search
, f_len
, file
->text
))) {
513 if ((flags
& PSI_CPP_INCLUDE_ONCE
) && zend_hash_str_exists(&cpp
->once
, path
, p_len
)) {
516 if (try_include(cpp
, path
, &parsed
)) {
522 cpp
->search
= sep
+ 1;