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
);
70 static int dump_def(zval
*p
)
72 struct psi_cpp_macro_decl
*decl
= Z_PTR_P(p
);
75 dprintf(2, "#define ");
76 psi_cpp_macro_decl_dump(2, decl
);
79 return ZEND_HASH_APPLY_KEEP
;
82 void psi_cpp_free(struct psi_cpp
**cpp_ptr
)
85 struct psi_cpp
*cpp
= *cpp_ptr
;
88 if (cpp
->parser
->flags
& PSI_DEBUG
) {
89 fprintf(stderr
, "PSI: CPP decls:\n");
90 zend_hash_apply(&cpp
->defs
, dump_def
);
92 zend_hash_destroy(&cpp
->defs
);
93 zend_hash_destroy(&cpp
->once
);
98 static bool psi_cpp_stage1(struct psi_cpp
*cpp
)
100 bool name
= false, define
= false, hash
= false, eol
= true, esc
= false, ws
= false;
102 PSI_DEBUG_PRINT(cpp
->parser
, "PSI: CPP %s\n", "stage1");
104 psi_cpp_tokiter_reset(cpp
);
105 while (psi_cpp_tokiter_valid(cpp
)) {
106 struct psi_token
*token
= psi_cpp_tokiter_current(cpp
);
108 /* strip comments and attributes */
109 if (token
->type
== PSI_T_COMMENT
110 || token
->type
== PSI_T_CPP_ATTRIBUTE
) {
111 psi_cpp_tokiter_del_cur(cpp
, true);
115 /* line continuations */
116 if (token
->type
== PSI_T_EOL
) {
118 psi_cpp_tokiter_del_range(cpp
, psi_cpp_tokiter_index(cpp
) - 1, 2, true);
119 psi_cpp_tokiter_prev(cpp
);
123 } else if (token
->type
== PSI_T_BSLASH
) {
129 /* this whole turf is needed to distinct between:
130 * #define foo (1,2,3)
134 if (token
->type
== PSI_T_WHITESPACE
) {
139 psi_cpp_tokiter_del_cur(cpp
, true);
143 switch (token
->type
) {
169 /* mask special token for parser */
170 struct psi_token
*no_ws
= psi_token_copy(token
);
172 no_ws
->type
= PSI_T_NO_WHITESPACE
;
173 no_ws
->text
[0] = '\xA0';
174 psi_cpp_tokiter_ins_cur(cpp
, no_ws
);
180 name
= define
= hash
= eol
= false;
185 psi_cpp_tokiter_next(cpp
);
191 static bool psi_cpp_stage2(struct psi_cpp
*cpp
)
193 struct psi_plist
*parser_tokens
= psi_plist_init((psi_plist_dtor
) psi_token_free
);
194 bool is_eol
= true, do_cpp
= false, do_expansion
= true, skip_paren
= false, skip_all
= false;
196 PSI_DEBUG_PRINT(cpp
->parser
, "PSI: CPP %s\n", "stage2");
198 psi_cpp_tokiter_reset(cpp
);
199 while (psi_cpp_tokiter_valid(cpp
)) {
200 struct psi_token
*current
= psi_cpp_tokiter_current(cpp
);
202 if (current
->type
== PSI_T_HASH
) {
207 } else if (current
->type
== PSI_T_EOL
) {
209 fprintf(stderr
, "PSI: CPP do_expansion=true, PSI_T_EOL\n");
215 psi_cpp_tokiter_del_cur(cpp
, true);
222 switch (current
->type
) {
225 fprintf(stderr
, "PSI: CPP do_expansion=false, PSI_T_DEFINE, skip_all\n");
227 do_expansion
= false;
237 fprintf(stderr
, "PSI: CPP do_expansion=false, PSI_T_{IF{,N},UN}DEF\n");
239 do_expansion
= false;
249 fprintf(stderr
, "PSI: CPP do_expansion=true, PSI_T_LPAREN, !skip_all, !skip_paren\n");
257 do_expansion
= !skip_all
;
259 fprintf(stderr
, "PSI: CPP do_expansion=%s, <- !skip_all\n", do_expansion
?"true":"false");
266 /* FIXME: del_range */
269 fprintf(stderr
, "PSI: CPP skip ");
270 psi_token_dump(2, current
);
272 psi_cpp_tokiter_del_cur(cpp
, true);
277 if (do_expansion
&& current
->type
== PSI_T_NAME
&& psi_cpp_tokiter_defined(cpp
)) {
278 bool expanded
= false;
280 while (psi_cpp_tokiter_expand(cpp
)) {
289 parser_tokens
= psi_plist_add(parser_tokens
, ¤t
);
292 size_t processed
= 0;
293 bool parsed
= psi_parser_process(cpp
->parser
, parser_tokens
, &processed
);
296 psi_plist_pop(parser_tokens
, NULL
);
297 psi_plist_clean(parser_tokens
);
301 psi_plist_free(parser_tokens
);
305 /* leave EOLs in the input stream, else we might end up
306 * with a hash not preceded with a new line after include */
307 psi_cpp_tokiter_del_cur(cpp
, false);
310 #if PSI_CPP_DEBUG > 1
311 psi_cpp_tokiter_dump(2, cpp
);
317 psi_cpp_tokiter_next(cpp
);
320 psi_plist_free(parser_tokens
);
325 bool psi_cpp_process(struct psi_cpp
*cpp
, struct psi_plist
**tokens
)
328 struct psi_cpp temp
= *cpp
;
330 cpp
->tokens
= *tokens
;
331 if (psi_cpp_stage1(cpp
) && psi_cpp_stage2(cpp
)) {
334 *tokens
= cpp
->tokens
;
337 cpp
->tokens
= temp
.tokens
;
338 cpp
->index
= temp
.index
;
344 bool psi_cpp_defined(struct psi_cpp
*cpp
, struct psi_token
*tok
)
348 if (tok
->type
== PSI_T_NAME
) {
349 defined
= zend_hash_str_exists(&cpp
->defs
, tok
->text
, tok
->size
);
355 fprintf(stderr
, "PSI: CPP defined -> %s ", defined
? "true" : "false");
357 struct psi_cpp_macro_decl
*macro
= zend_hash_str_find_ptr(&cpp
->defs
, tok
->text
, tok
->size
);
358 fprintf(stderr
, " @ %s:%u ", macro
->token
->file
, macro
->token
->line
);
360 psi_token_dump(2, tok
);
366 void psi_cpp_define(struct psi_cpp
*cpp
, struct psi_cpp_macro_decl
*decl
)
368 struct psi_cpp_macro_decl
*old
= zend_hash_str_find_ptr(&cpp
->defs
, decl
->token
->text
, decl
->token
->size
);
370 if (old
&& !psi_cpp_macro_decl_equal(old
, decl
)) {
371 cpp
->parser
->error(PSI_DATA(cpp
->parser
), decl
->token
, PSI_WARNING
,
372 "'%s' redefined", decl
->token
->text
);
373 cpp
->parser
->error(PSI_DATA(cpp
->parser
), old
->token
, PSI_WARNING
,
374 "'%s' previously defined", old
->token
->text
);
376 zend_hash_str_update_ptr(&cpp
->defs
, decl
->token
->text
, decl
->token
->size
, decl
);
379 bool psi_cpp_undef(struct psi_cpp
*cpp
, struct psi_token
*tok
)
381 return SUCCESS
== zend_hash_str_del(&cpp
->defs
, tok
->text
, tok
->size
);
384 bool psi_cpp_if(struct psi_cpp
*cpp
, struct psi_cpp_exp
*exp
)
386 if (!psi_num_exp_validate(PSI_DATA(cpp
->parser
), exp
->data
.num
, NULL
, NULL
, NULL
, NULL
, NULL
)) {
389 if (!psi_long_num_exp(exp
->data
.num
, NULL
, &cpp
->defs
)) {
395 static inline bool try_include(struct psi_cpp
*cpp
, const char *path
, bool *parsed
)
397 struct psi_parser_input
*include
;
399 PSI_DEBUG_PRINT(cpp
->parser
, "PSI: CPP include trying %s\n", path
);
401 include
= psi_parser_open_file(cpp
->parser
, path
, false);
403 struct psi_plist
*tokens
;
405 PSI_DEBUG_PRINT(cpp
->parser
, "PSI: CPP include scanning %s\n", path
);
407 tokens
= psi_parser_scan(cpp
->parser
, include
);
409 *parsed
= psi_cpp_process(cpp
, &tokens
);
412 size_t num_tokens
= psi_plist_count(tokens
);
415 psi_cpp_tokiter_ins_range(cpp
, cpp
->index
,
416 num_tokens
, psi_plist_eles(tokens
));
417 /* skip already processed tokens */
418 cpp
->index
+= num_tokens
;
421 psi_plist_free(tokens
);
426 zend_hash_str_add_empty_element(&cpp
->once
, path
, strlen(path
));
432 static inline void include_path(const struct psi_token
*file
, char **path
)
434 if (*file
->text
== '/') {
440 strncpy(*path
, file
->file
, PATH_MAX
);
442 dir
= dirname(*path
);
445 assert(len
+ file
->size
+ 1 < PATH_MAX
);
447 memmove(*path
, dir
, len
);
449 memcpy(&(*path
)[len
+ 1], file
->text
, file
->size
+ 1);
453 bool psi_cpp_include(struct psi_cpp
*cpp
, const struct psi_token
*file
, unsigned flags
)
456 int f_len
= strlen(file
->text
);
458 if (!(flags
& PSI_CPP_INCLUDE_NEXT
) || *file
->text
== '/') {
459 /* first try as is, full or relative path */
460 char temp
[PATH_MAX
], *path
= temp
;
462 include_path(file
, &path
);
464 if ((flags
& PSI_CPP_INCLUDE_ONCE
) && zend_hash_str_exists(&cpp
->once
, path
, f_len
)) {
467 if (try_include(cpp
, path
, &parsed
)) {
473 /* look through search paths */
474 if (*file
->text
!= '/') {
479 if ((flags
& PSI_CPP_INCLUDE_NEXT
) && cpp
->search
) {
480 if ((sep
= strchr(cpp
->search
, ':'))) {
481 cpp
->search
= sep
+ 1;
483 /* point to end of string */
484 cpp
->search
+= strlen(cpp
->search
);
488 if (!(flags
& PSI_CPP_INCLUDE_NEXT
) || !cpp
->search
) {
489 cpp
->search
= PSI_G(search_path
);
495 sep
= strchr(cpp
->search
, ':');
496 d_len
= sep
? sep
- cpp
->search
: strlen(cpp
->search
);
498 if (PATH_MAX
> (p_len
= snprintf(path
, PATH_MAX
, "%.*s/%.*s", d_len
, cpp
->search
, f_len
, file
->text
))) {
499 if ((flags
& PSI_CPP_INCLUDE_ONCE
) && zend_hash_str_exists(&cpp
->once
, path
, p_len
)) {
502 if (try_include(cpp
, path
, &parsed
)) {
508 cpp
->search
= sep
+ 1;