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"
31 #define PSI_CPP_SEARCH
32 #define PSI_CPP_PREDEF
33 #include "php_psi_cpp.h"
37 static void free_cpp_def(zval
*p
)
39 if (Z_TYPE_P(p
) == IS_PTR
) {
40 psi_cpp_macro_decl_free((void *) &Z_PTR_P(p
));
44 struct psi_cpp
*psi_cpp_init(struct psi_parser
*P
)
46 struct psi_cpp
*cpp
= calloc(1, sizeof(*cpp
));
49 zend_hash_init(&cpp
->defs
, 0, NULL
, free_cpp_def
, 1);
50 zend_hash_init(&cpp
->once
, 0, NULL
, NULL
, 1);
55 bool psi_cpp_load_defaults(struct psi_cpp
*cpp
)
57 struct psi_parser_input
*predef
;
59 if ((predef
= psi_parser_open_string(cpp
->parser
, psi_cpp_predef
, sizeof(psi_cpp_predef
) - 1))) {
60 bool parsed
= psi_parser_parse(cpp
->parser
, predef
);
68 static int dump_def(zval
*p
)
70 struct psi_cpp_macro_decl
*decl
= Z_PTR_P(p
);
73 dprintf(2, "#define ");
74 psi_cpp_macro_decl_dump(2, decl
);
77 return ZEND_HASH_APPLY_KEEP
;
80 void psi_cpp_free(struct psi_cpp
**cpp_ptr
)
83 struct psi_cpp
*cpp
= *cpp_ptr
;
86 if (cpp
->parser
->flags
& PSI_DEBUG
) {
87 fprintf(stderr
, "PSI: CPP decls:\n");
88 zend_hash_apply(&cpp
->defs
, dump_def
);
90 zend_hash_destroy(&cpp
->defs
);
91 zend_hash_destroy(&cpp
->once
);
96 static bool psi_cpp_stage1(struct psi_cpp
*cpp
)
98 bool name
= false, define
= false, hash
= false, eol
= true, esc
= false, ws
= false;
100 PSI_DEBUG_PRINT(cpp
->parser
, "PSI: CPP %s\n", "stage1");
102 psi_cpp_tokiter_reset(cpp
);
103 while (psi_cpp_tokiter_valid(cpp
)) {
104 struct psi_token
*token
= psi_cpp_tokiter_current(cpp
);
106 /* strip comments and attributes */
107 if (token
->type
== PSI_T_COMMENT
108 || token
->type
== PSI_T_CPP_ATTRIBUTE
) {
109 psi_cpp_tokiter_del_cur(cpp
, true);
113 /* line continuations */
114 if (token
->type
== PSI_T_EOL
) {
116 psi_cpp_tokiter_del_range(cpp
, psi_cpp_tokiter_index(cpp
) - 1, 2, true);
117 psi_cpp_tokiter_prev(cpp
);
121 } else if (token
->type
== PSI_T_BSLASH
) {
127 /* this whole turf is needed to distinct between:
128 * #define foo (1,2,3)
132 if (token
->type
== PSI_T_WHITESPACE
) {
137 psi_cpp_tokiter_del_cur(cpp
, true);
141 switch (token
->type
) {
167 /* mask special token for parser */
168 struct psi_token
*no_ws
= psi_token_copy(token
);
170 no_ws
->type
= PSI_T_NO_WHITESPACE
;
171 no_ws
->text
[0] = '\xA0';
172 psi_cpp_tokiter_ins_cur(cpp
, no_ws
);
178 name
= define
= hash
= eol
= false;
183 psi_cpp_tokiter_next(cpp
);
189 static bool psi_cpp_stage2(struct psi_cpp
*cpp
)
191 struct psi_plist
*parser_tokens
= psi_plist_init((psi_plist_dtor
) psi_token_free
);
192 bool is_eol
= true, do_cpp
= false, do_expansion
= true, skip_paren
= false, skip_all
= false;
194 PSI_DEBUG_PRINT(cpp
->parser
, "PSI: CPP %s\n", "stage2");
196 psi_cpp_tokiter_reset(cpp
);
197 while (psi_cpp_tokiter_valid(cpp
)) {
198 struct psi_token
*current
= psi_cpp_tokiter_current(cpp
);
200 if (current
->type
== PSI_T_HASH
) {
205 } else if (current
->type
== PSI_T_EOL
) {
207 fprintf(stderr
, "PSI: CPP do_expansion=true, PSI_T_EOL\n");
213 psi_cpp_tokiter_del_cur(cpp
, true);
220 switch (current
->type
) {
223 fprintf(stderr
, "PSI: CPP do_expansion=false, PSI_T_DEFINE, skip_all\n");
225 do_expansion
= false;
235 fprintf(stderr
, "PSI: CPP do_expansion=false, PSI_T_{IF{,N},UN}DEF\n");
237 do_expansion
= false;
247 fprintf(stderr
, "PSI: CPP do_expansion=true, PSI_T_LPAREN, !skip_all, !skip_paren\n");
255 do_expansion
= !skip_all
;
257 fprintf(stderr
, "PSI: CPP do_expansion=%s, <- !skip_all\n", do_expansion
?"true":"false");
264 /* FIXME: del_range */
267 fprintf(stderr
, "PSI: CPP skip ");
268 psi_token_dump(2, current
);
270 psi_cpp_tokiter_del_cur(cpp
, true);
275 if (do_expansion
&& current
->type
== PSI_T_NAME
&& psi_cpp_tokiter_defined(cpp
)) {
276 bool expanded
= false;
278 while (psi_cpp_tokiter_expand(cpp
)) {
287 parser_tokens
= psi_plist_add(parser_tokens
, ¤t
);
290 size_t processed
= 0;
291 bool parsed
= psi_parser_process(cpp
->parser
, parser_tokens
, &processed
);
294 psi_plist_pop(parser_tokens
, NULL
);
295 psi_plist_clean(parser_tokens
);
299 psi_plist_free(parser_tokens
);
303 /* leave EOLs in the input stream, else we might end up
304 * with a hash not preceded with a new line after include */
305 psi_cpp_tokiter_del_cur(cpp
, false);
308 #if PSI_CPP_DEBUG > 1
309 psi_cpp_tokiter_dump(2, cpp
);
315 psi_cpp_tokiter_next(cpp
);
318 psi_plist_free(parser_tokens
);
323 bool psi_cpp_process(struct psi_cpp
*cpp
, struct psi_plist
**tokens
)
326 struct psi_cpp temp
= *cpp
;
328 cpp
->tokens
= *tokens
;
329 if (psi_cpp_stage1(cpp
) && psi_cpp_stage2(cpp
)) {
332 *tokens
= cpp
->tokens
;
335 cpp
->tokens
= temp
.tokens
;
336 cpp
->index
= temp
.index
;
342 bool psi_cpp_defined(struct psi_cpp
*cpp
, struct psi_token
*tok
)
346 if (tok
->type
== PSI_T_NAME
) {
347 defined
= zend_hash_str_exists(&cpp
->defs
, tok
->text
, tok
->size
);
353 fprintf(stderr
, "PSI: CPP defined -> %s ", defined
? "true" : "false");
355 struct psi_cpp_macro_decl
*macro
= zend_hash_str_find_ptr(&cpp
->defs
, tok
->text
, tok
->size
);
356 fprintf(stderr
, " @ %s:%u ", macro
->token
->file
, macro
->token
->line
);
358 psi_token_dump(2, tok
);
364 void psi_cpp_define(struct psi_cpp
*cpp
, struct psi_cpp_macro_decl
*decl
)
366 struct psi_cpp_macro_decl
*old
= zend_hash_str_find_ptr(&cpp
->defs
, decl
->token
->text
, decl
->token
->size
);
368 if (old
&& !psi_cpp_macro_decl_equal(old
, decl
)) {
369 cpp
->parser
->error(PSI_DATA(cpp
->parser
), decl
->token
, PSI_WARNING
,
370 "'%s' redefined", decl
->token
->text
);
371 cpp
->parser
->error(PSI_DATA(cpp
->parser
), old
->token
, PSI_WARNING
,
372 "'%s' previously defined", old
->token
->text
);
374 zend_hash_str_update_ptr(&cpp
->defs
, decl
->token
->text
, decl
->token
->size
, decl
);
377 bool psi_cpp_undef(struct psi_cpp
*cpp
, struct psi_token
*tok
)
379 return SUCCESS
== zend_hash_str_del(&cpp
->defs
, tok
->text
, tok
->size
);
382 bool psi_cpp_if(struct psi_cpp
*cpp
, struct psi_cpp_exp
*exp
)
384 if (!psi_num_exp_validate(PSI_DATA(cpp
->parser
), exp
->data
.num
, NULL
, NULL
, NULL
, NULL
, NULL
)) {
387 if (!psi_long_num_exp(exp
->data
.num
, NULL
, &cpp
->defs
)) {
393 static inline bool try_include(struct psi_cpp
*cpp
, const char *path
, bool *parsed
)
395 struct psi_parser_input
*include
;
397 PSI_DEBUG_PRINT(cpp
->parser
, "PSI: CPP include trying %s\n", path
);
399 include
= psi_parser_open_file(cpp
->parser
, path
, false);
401 struct psi_plist
*tokens
;
403 PSI_DEBUG_PRINT(cpp
->parser
, "PSI: CPP include scanning %s\n", path
);
405 tokens
= psi_parser_scan(cpp
->parser
, include
);
407 *parsed
= psi_cpp_process(cpp
, &tokens
);
410 size_t num_tokens
= psi_plist_count(tokens
);
413 psi_cpp_tokiter_ins_range(cpp
, cpp
->index
,
414 num_tokens
, psi_plist_eles(tokens
));
415 /* skip already processed tokens */
416 cpp
->index
+= num_tokens
;
419 psi_plist_free(tokens
);
424 zend_hash_str_add_empty_element(&cpp
->once
, path
, strlen(path
));
430 bool psi_cpp_include(struct psi_cpp
*cpp
, const char *file
, unsigned flags
)
433 int f_len
= strlen(file
);
435 if (!(flags
& PSI_CPP_INCLUDE_NEXT
) || *file
== '/') {
436 /* first try as is, full or relative path */
437 if ((flags
& PSI_CPP_INCLUDE_ONCE
) && zend_hash_str_exists(&cpp
->once
, file
, f_len
)) {
440 if (try_include(cpp
, file
, &parsed
)) {
446 /* look through search paths */
452 if ((flags
& PSI_CPP_INCLUDE_NEXT
) && cpp
->search
) {
453 if ((sep
= strchr(cpp
->search
, ':'))) {
454 cpp
->search
= sep
+ 1;
456 /* point to end of string */
457 cpp
->search
+= strlen(cpp
->search
);
461 if (!(flags
& PSI_CPP_INCLUDE_NEXT
) || !cpp
->search
) {
462 cpp
->search
= PSI_G(search_path
);
468 sep
= strchr(cpp
->search
, ':');
469 d_len
= sep
? sep
- cpp
->search
: strlen(cpp
->search
);
471 if (PATH_MAX
> (p_len
= snprintf(path
, PATH_MAX
, "%.*s/%.*s", d_len
, cpp
->search
, f_len
, file
))) {
472 if ((flags
& PSI_CPP_INCLUDE_ONCE
) && zend_hash_str_exists(&cpp
->once
, path
, p_len
)) {
475 if (try_include(cpp
, path
, &parsed
)) {
481 cpp
->search
= sep
+ 1;