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_cpp_tokiter_reset(cpp
);
101 while (psi_cpp_tokiter_valid(cpp
)) {
102 struct psi_token
*token
= psi_cpp_tokiter_current(cpp
);
104 /* strip comments and attributes */
105 if (token
->type
== PSI_T_COMMENT
106 || token
->type
== PSI_T_CPP_ATTRIBUTE
) {
107 psi_cpp_tokiter_del_cur(cpp
, true);
111 /* line continuations */
112 if (token
->type
== PSI_T_EOL
) {
114 psi_cpp_tokiter_del_range(cpp
, psi_cpp_tokiter_index(cpp
) - 1, 2, true);
115 psi_cpp_tokiter_prev(cpp
);
119 } else if (token
->type
== PSI_T_BSLASH
) {
125 /* this whole turf is needed to distinct between:
126 * #define foo (1,2,3)
130 if (token
->type
== PSI_T_WHITESPACE
) {
135 psi_cpp_tokiter_del_cur(cpp
, true);
139 switch (token
->type
) {
165 /* mask special token for parser */
166 struct psi_token
*no_ws
= psi_token_copy(token
);
168 no_ws
->type
= PSI_T_NO_WHITESPACE
;
169 no_ws
->text
[0] = '\xA0';
170 psi_cpp_tokiter_ins_cur(cpp
, no_ws
);
176 name
= define
= hash
= eol
= false;
181 psi_cpp_tokiter_next(cpp
);
187 static bool psi_cpp_stage2(struct psi_cpp
*cpp
)
189 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_cpp_tokiter_reset(cpp
);
196 while (psi_cpp_tokiter_valid(cpp
)) {
197 struct psi_token
*current
= psi_cpp_tokiter_current(cpp
);
199 if (current
->type
== PSI_T_HASH
) {
204 } else if (current
->type
== PSI_T_EOL
) {
206 fprintf(stderr
, "PSI: CPP do_expansion=true, PSI_T_EOL\n");
212 psi_cpp_tokiter_del_cur(cpp
, true);
219 switch (current
->type
) {
222 fprintf(stderr
, "PSI: CPP do_expansion=false, PSI_T_DEFINE, skip_all\n");
224 do_expansion
= false;
234 fprintf(stderr
, "PSI: CPP do_expansion=false, PSI_T_{IF{,N},UN}DEF\n");
236 do_expansion
= false;
246 fprintf(stderr
, "PSI: CPP do_expansion=true, PSI_T_LPAREN, !skip_all, !skip_paren\n");
254 do_expansion
= !skip_all
;
256 fprintf(stderr
, "PSI: CPP do_expansion=%s, <- !skip_all\n", do_expansion
?"true":"false");
263 /* FIXME: del_range */
266 fprintf(stderr
, "PSI: CPP skip ");
267 psi_token_dump(2, current
);
269 psi_cpp_tokiter_del_cur(cpp
, true);
274 if (do_expansion
&& current
->type
== PSI_T_NAME
&& psi_cpp_tokiter_defined(cpp
)) {
275 bool expanded
= false;
277 while (psi_cpp_tokiter_expand(cpp
)) {
286 parser_tokens
= psi_plist_add(parser_tokens
, ¤t
);
289 size_t processed
= 0;
290 bool parsed
= psi_parser_process(cpp
->parser
, parser_tokens
, &processed
);
293 psi_plist_pop(parser_tokens
, NULL
);
294 psi_plist_clean(parser_tokens
);
298 psi_plist_free(parser_tokens
);
302 /* leave EOLs in the input stream, else we might end up
303 * with a hash not preceded with a new line after include */
304 psi_cpp_tokiter_del_cur(cpp
, false);
307 #if PSI_CPP_DEBUG > 1
308 psi_cpp_tokiter_dump(2, cpp
);
314 psi_cpp_tokiter_next(cpp
);
316 } while (cpp
->expanded
);
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");
354 psi_token_dump(2, tok
);
360 void psi_cpp_define(struct psi_cpp
*cpp
, struct psi_cpp_macro_decl
*decl
)
362 struct psi_cpp_macro_decl
*old
= zend_hash_str_find_ptr(&cpp
->defs
, decl
->token
->text
, decl
->token
->size
);
364 if (old
&& !psi_cpp_macro_decl_equal(old
, decl
)) {
365 cpp
->parser
->error(PSI_DATA(cpp
->parser
), decl
->token
, PSI_WARNING
,
366 "'%s' redefined", decl
->token
->text
);
367 cpp
->parser
->error(PSI_DATA(cpp
->parser
), old
->token
, PSI_WARNING
,
368 "'%s' previously defined", old
->token
->text
);
370 zend_hash_str_update_ptr(&cpp
->defs
, decl
->token
->text
, decl
->token
->size
, decl
);
373 bool psi_cpp_undef(struct psi_cpp
*cpp
, struct psi_token
*tok
)
375 return SUCCESS
== zend_hash_str_del(&cpp
->defs
, tok
->text
, tok
->size
);
378 bool psi_cpp_if(struct psi_cpp
*cpp
, struct psi_cpp_exp
*exp
)
380 if (!psi_num_exp_validate(PSI_DATA(cpp
->parser
), exp
->data
.num
, NULL
, NULL
, NULL
, NULL
, NULL
)) {
383 if (!psi_long_num_exp(exp
->data
.num
, NULL
, &cpp
->defs
)) {
389 static inline bool try_include(struct psi_cpp
*cpp
, const char *path
, bool *parsed
)
391 struct psi_parser_input
*include
;
393 PSI_DEBUG_PRINT(cpp
->parser
, "PSI: CPP include trying %s\n", path
);
395 include
= psi_parser_open_file(cpp
->parser
, path
, false);
397 struct psi_plist
*tokens
;
399 PSI_DEBUG_PRINT(cpp
->parser
, "PSI: CPP include scanning %s\n", path
);
401 tokens
= psi_parser_scan(cpp
->parser
, include
);
403 *parsed
= psi_cpp_process(cpp
, &tokens
);
407 psi_cpp_tokiter_ins_range(cpp
, cpp
->index
,
408 psi_plist_count(tokens
), psi_plist_eles(tokens
));
411 psi_plist_free(tokens
);
416 zend_hash_str_add_empty_element(&cpp
->once
, path
, strlen(path
));
422 bool psi_cpp_include(struct psi_cpp
*cpp
, const char *file
, unsigned flags
)
425 int f_len
= strlen(file
);
427 if (!(flags
& PSI_CPP_INCLUDE_NEXT
) || *file
== '/') {
428 /* first try as is, full or relative path */
429 if ((flags
& PSI_CPP_INCLUDE_ONCE
) && zend_hash_str_exists(&cpp
->once
, file
, f_len
)) {
432 if (try_include(cpp
, file
, &parsed
)) {
438 /* look through search paths */
444 if ((flags
& PSI_CPP_INCLUDE_NEXT
) && cpp
->search
) {
445 if ((sep
= strchr(cpp
->search
, ':'))) {
446 cpp
->search
= sep
+ 1;
448 /* point to end of string */
449 cpp
->search
+= strlen(cpp
->search
);
453 if (!(flags
& PSI_CPP_INCLUDE_NEXT
) || !cpp
->search
) {
454 cpp
->search
= PSI_G(search_path
);
460 sep
= strchr(cpp
->search
, ':');
461 d_len
= sep
? sep
- cpp
->search
: strlen(cpp
->search
);
463 if (PATH_MAX
> (p_len
= snprintf(path
, PATH_MAX
, "%.*s/%.*s", d_len
, cpp
->search
, f_len
, file
))) {
464 if ((flags
& PSI_CPP_INCLUDE_ONCE
) && zend_hash_str_exists(&cpp
->once
, path
, p_len
)) {
467 if (try_include(cpp
, path
, &parsed
)) {
473 cpp
->search
= sep
+ 1;