diff options
author | Tavian Barnes <tavianator@gmail.com> | 2010-02-09 18:58:56 -0500 |
---|---|---|
committer | Tavian Barnes <tavianator@gmail.com> | 2010-02-09 19:13:46 -0500 |
commit | d411e681e571ac054352b9665487f10037d9325d (patch) | |
tree | cf5d425426624ca4798b286fc81116e46f31cb1d | |
parent | 7eef42c72cc172a1ed8087e842905f42e737131d (diff) | |
download | dimension-d411e681e571ac054352b9665487f10037d9325d.tar.xz |
Support #include.
-rw-r--r-- | dimension/common.nonterminals | 3 | ||||
-rw-r--r-- | dimension/common.rules | 8 | ||||
-rw-r--r-- | dimension/directives.rules | 6 | ||||
-rw-r--r-- | dimension/grammar.epilogue | 2 | ||||
-rw-r--r-- | dimension/lexer.l | 24 | ||||
-rw-r--r-- | dimension/parse.c | 8 | ||||
-rw-r--r-- | dimension/parse.h | 5 | ||||
-rw-r--r-- | dimension/tokenize.c | 227 | ||||
-rw-r--r-- | dimension/tokenize.h | 6 | ||||
-rw-r--r-- | tests/dimension/directives.inc | 21 | ||||
-rw-r--r-- | tests/dimension/directives.pov | 7 | ||||
-rwxr-xr-x | tests/dimension/directives.sh | 5 |
12 files changed, 298 insertions, 24 deletions
diff --git a/dimension/common.nonterminals b/dimension/common.nonterminals index 66cee71..7513f9c 100644 --- a/dimension/common.nonterminals +++ b/dimension/common.nonterminals @@ -19,8 +19,9 @@ * along with this program. If not, see <http://www.gnu.org/licenses/>. * *************************************************************************/ -/* Identifiers */ +/* Fundamental language elements */ %type <astnode> IDENTIFIER +%type <astnode> STRING /* Transformations */ %type <astnode> TRANSFORMATION diff --git a/dimension/common.rules b/dimension/common.rules index 0bf2557..f0e8eae 100644 --- a/dimension/common.rules +++ b/dimension/common.rules @@ -19,7 +19,7 @@ * along with this program. If not, see <http://www.gnu.org/licenses/>. * *************************************************************************/ -/* Identifiers */ +/* Fundamental language elements */ IDENTIFIER: "identifier" { $$ = dmnsn_new_astnode(DMNSN_AST_IDENTIFIER, @$); @@ -27,6 +27,12 @@ IDENTIFIER: "identifier" { } ; +STRING: "string" { + $$ = dmnsn_new_astnode(DMNSN_AST_STRING, @$); + $$.ptr = $1; + } +; + /* Transformations */ TRANSFORMATION: "rotate" VECTOR { diff --git a/dimension/directives.rules b/dimension/directives.rules index eb03df4..48cdd77 100644 --- a/dimension/directives.rules +++ b/dimension/directives.rules @@ -4,7 +4,11 @@ * Start symbol */ -LANGUAGE_DIRECTIVE: "#declare" "identifier" "=" RVALUE { +LANGUAGE_DIRECTIVE: "#include" STRING { + dmnsn_declare_symbol(symtable, "__include__", $2); + dmnsn_delete_astnode($2); + } + | "#declare" "identifier" "=" RVALUE { dmnsn_declare_symbol(symtable, $2, $4); free($2); dmnsn_delete_astnode($4); diff --git a/dimension/grammar.epilogue b/dimension/grammar.epilogue index 32e0b83..e8760c7 100644 --- a/dimension/grammar.epilogue +++ b/dimension/grammar.epilogue @@ -152,6 +152,8 @@ dmnsn_astnode_string(dmnsn_astnode_type astnode_type) dmnsn_astnode_map(DMNSN_AST_STRING, "string"); + dmnsn_astnode_map(DMNSN_AST_ARRAY, "array"); + default: fprintf(stderr, "Warning: unrecognised astnode type %d.\n", (int)astnode_type); diff --git a/dimension/lexer.l b/dimension/lexer.l index 90c4807..9dec45d 100644 --- a/dimension/lexer.l +++ b/dimension/lexer.l @@ -273,6 +273,30 @@ unsigned long wchar; %% +void * +dmnsn_yy_make_buffer(FILE *file, void *scanner) +{ + return dmnsn_yy_create_buffer(file, YY_BUF_SIZE, scanner); +} + +void * +dmnsn_yy_make_string_buffer(const char *str, void *scanner) +{ + return dmnsn_yy_scan_string(str, scanner); +} + +void +dmnsn_yy_push_buffer(void *buffer, void *scanner) +{ + dmnsn_yypush_buffer_state(buffer, scanner); +} + +void +dmnsn_yy_pop_buffer(void *scanner) +{ + dmnsn_yypop_buffer_state(scanner); +} + dmnsn_array * dmnsn_tokenize(FILE *file, const char *filename) { diff --git a/dimension/parse.c b/dimension/parse.c index e15a8cf..6951974 100644 --- a/dimension/parse.c +++ b/dimension/parse.c @@ -42,6 +42,14 @@ dmnsn_new_astnode(dmnsn_astnode_type type) } dmnsn_astnode +dmnsn_new_ast_array() +{ + dmnsn_astnode astnode = dmnsn_new_astnode(DMNSN_AST_ARRAY); + astnode.children = dmnsn_new_array(sizeof(dmnsn_astnode)); + return astnode; +} + +dmnsn_astnode dmnsn_new_ast_integer(long value) { dmnsn_astnode astnode = dmnsn_new_astnode(DMNSN_AST_INTEGER); diff --git a/dimension/parse.h b/dimension/parse.h index 28997af..b2658f3 100644 --- a/dimension/parse.h +++ b/dimension/parse.h @@ -96,7 +96,9 @@ typedef enum { DMNSN_AST_IDENTIFIER, - DMNSN_AST_STRING + DMNSN_AST_STRING, + + DMNSN_AST_ARRAY } dmnsn_astnode_type; /* Abstract syntax tree node (a dmnsn_array* of these is an AST) */ @@ -119,6 +121,7 @@ typedef struct dmnsn_astnode { typedef dmnsn_array dmnsn_astree; +dmnsn_astnode dmnsn_new_ast_array(); dmnsn_astnode dmnsn_new_ast_integer(long value); dmnsn_astnode dmnsn_new_ast_float(double value); dmnsn_astnode dmnsn_new_ast_string(const char *value); diff --git a/dimension/tokenize.c b/dimension/tokenize.c index 63435c9..95aaa9b 100644 --- a/dimension/tokenize.c +++ b/dimension/tokenize.c @@ -20,7 +20,9 @@ #include "tokenize.h" #include "directives.h" #include "utility.h" +#include <libgen.h> #include <stdbool.h> +#include <string.h> typedef struct dmnsn_buffered_token { int type; @@ -37,10 +39,13 @@ typedef struct dmnsn_token_buffer { unsigned int i; struct dmnsn_token_buffer *prev; + + const char *filename; + void *ptr; } dmnsn_token_buffer; static dmnsn_token_buffer * -dmnsn_new_token_buffer(int type, dmnsn_token_buffer *prev) +dmnsn_new_token_buffer(int type, dmnsn_token_buffer *prev, const char *filename) { dmnsn_token_buffer *tbuffer = malloc(sizeof(dmnsn_token_buffer)); if (!tbuffer) { @@ -51,6 +56,8 @@ dmnsn_new_token_buffer(int type, dmnsn_token_buffer *prev) tbuffer->buffered = dmnsn_new_array(sizeof(dmnsn_buffered_token)); tbuffer->i = 0; tbuffer->prev = prev; + tbuffer->filename = filename; + tbuffer->ptr = NULL; return tbuffer; } @@ -72,18 +79,177 @@ dmnsn_delete_token_buffer(dmnsn_token_buffer *tbuffer) static int dmnsn_yylex_wrapper(dmnsn_parse_item *lvalp, dmnsn_parse_location *llocp, - const char *filename, void *yyscanner); + const char *filename, dmnsn_symbol_table *symtable, + void *yyscanner); int dmnsn_ld_yyparse(const char *filename, void *yyscanner, dmnsn_symbol_table *symtable); static dmnsn_token_buffer * +dmnsn_include_buffer(int token, dmnsn_token_buffer *prev, + dmnsn_parse_item *lvalp, dmnsn_parse_location *llocp, + const char *filename, dmnsn_symbol_table *symtable, + void *yyscanner) +{ + dmnsn_token_buffer *include_buffer + = dmnsn_new_token_buffer(DMNSN_T_LEX_VERBATIM, prev, filename); + + /* Buffer the current token */ + dmnsn_buffered_token buffered = { + .type = token, + .lval = *lvalp, + .lloc = *llocp + }; + dmnsn_array_push(include_buffer->buffered, &buffered); + + /* Recursive call */ + buffered.type = dmnsn_yylex(&buffered.lval, &buffered.lloc, + filename, symtable, yyscanner); + + if (buffered.type == DMNSN_T_EOF) { + dmnsn_diagnostic(buffered.lloc.first_filename, buffered.lloc.first_line, + buffered.lloc.first_column, + "syntax error, unexpected end-of-file"); + dmnsn_delete_token_buffer(include_buffer); + return NULL; + } else if (buffered.type == DMNSN_T_LEX_ERROR) { + dmnsn_delete_token_buffer(include_buffer); + return NULL; + } + /* Buffer the next token */ + dmnsn_array_push(include_buffer->buffered, &buffered); + + bool is_strexp = buffered.type != DMNSN_T_STRING; + if (is_strexp && buffered.type == DMNSN_T_IDENTIFIER) { + /* Check if it's a string identifier or a macro */ + dmnsn_astnode *inode = dmnsn_find_symbol(symtable, buffered.lval.value); + if (!inode || inode->type == DMNSN_AST_STRING) { + is_strexp = false; + } + } + + if (is_strexp) { + /* Grab all the tokens belonging to the string expression */ + int parenlevel = -1; + while (1) { + /* Recursive call - permit other directives inside the condition */ + buffered.type = dmnsn_yylex(&buffered.lval, &buffered.lloc, + filename, symtable, yyscanner); + + if (buffered.type == DMNSN_T_EOF) { + dmnsn_diagnostic(buffered.lloc.first_filename, buffered.lloc.first_line, + buffered.lloc.first_column, + "syntax error, unexpected end-of-file"); + dmnsn_delete_token_buffer(include_buffer); + return NULL; + } else if (buffered.type == DMNSN_T_LEX_ERROR) { + dmnsn_delete_token_buffer(include_buffer); + return NULL; + } + + dmnsn_array_push(include_buffer->buffered, &buffered); + + if (buffered.type == DMNSN_T_LPAREN) { + if (parenlevel < 0) + parenlevel = 1; + else + ++parenlevel; + } else if (buffered.type == DMNSN_T_RPAREN) { + --parenlevel; + if (parenlevel == 0) { + break; + } + } + } + } + + /* Fake EOF */ + buffered.type = DMNSN_T_EOF; + buffered.lval.value = NULL; + dmnsn_array_push(include_buffer->buffered, &buffered); + + dmnsn_yyset_extra(include_buffer, yyscanner); + if (dmnsn_ld_yyparse(filename, yyscanner, symtable) != 0) { + dmnsn_yyset_extra(include_buffer->prev, yyscanner); + dmnsn_delete_token_buffer(include_buffer); + return NULL; + } + + dmnsn_yyset_extra(include_buffer->prev, yyscanner); + dmnsn_delete_token_buffer(include_buffer); + + dmnsn_token_buffer *tbuffer = dmnsn_new_token_buffer(token, prev, filename); + + dmnsn_astnode *inode = dmnsn_find_symbol(symtable, "__include__"); + if (!inode) { + dmnsn_error(DMNSN_SEVERITY_HIGH, "__include__ unset."); + } + + const char *include = inode->ptr; + if (inode->type != DMNSN_AST_STRING) { + dmnsn_error(DMNSN_SEVERITY_HIGH, "__include__ has wrong type."); + } + + char *filename_copy = strdup(filename); + char *localdir = dirname(filename_copy); + char *local_include = malloc(strlen(localdir) + strlen(include) + 2); + strcpy(local_include, localdir); + strcat(local_include, "/"); + strcat(local_include, include); + free(filename_copy); + dmnsn_undef_symbol(symtable, "__include__"); + + FILE *file = fopen(local_include, "r"); + if (!file) { + dmnsn_diagnostic(llocp->first_filename, llocp->first_line, + llocp->first_column, + "Couldn't open include file '%s'", local_include); + free(local_include); + dmnsn_delete_token_buffer(tbuffer); + return NULL; + } + tbuffer->ptr = file; + + void *buffer = dmnsn_yy_make_buffer(file, yyscanner); + if (!buffer) { + dmnsn_diagnostic(llocp->first_filename, llocp->first_line, + llocp->first_column, + "Couldn't allocate buffer for include file '%s'", + local_include); + fclose(file); + free(local_include); + dmnsn_delete_token_buffer(tbuffer); + return NULL; + } + dmnsn_yy_push_buffer(buffer, yyscanner); + + /* Stuff the filename in the symbol table to persist it */ + dmnsn_astnode *includes = dmnsn_find_symbol(symtable, "__includes__"); + if (!includes) { + dmnsn_declare_symbol(symtable, "__includes__", dmnsn_new_ast_array()); + includes = dmnsn_find_symbol(symtable, "__includes__"); + } + if (includes->type != DMNSN_AST_ARRAY) { + dmnsn_error(DMNSN_SEVERITY_HIGH, "__includes__ has wrong type."); + } + + dmnsn_astnode fnode = dmnsn_new_ast_string(local_include); + free(local_include); + tbuffer->filename = fnode.ptr; + dmnsn_array_push(includes->children, &fnode); + + dmnsn_push_scope(symtable); + + return tbuffer; +} + +static dmnsn_token_buffer * dmnsn_declaration_buffer(int token, dmnsn_token_buffer *prev, dmnsn_parse_item *lvalp, dmnsn_parse_location *llocp, const char *filename, dmnsn_symbol_table *symtable, void *yyscanner) { dmnsn_token_buffer *tbuffer - = dmnsn_new_token_buffer(DMNSN_T_LEX_VERBATIM, prev); + = dmnsn_new_token_buffer(DMNSN_T_LEX_VERBATIM, prev, filename); /* Buffer the current token */ dmnsn_buffered_token buffered = { @@ -144,7 +310,7 @@ dmnsn_undef_buffer(int token, dmnsn_token_buffer *prev, void *yyscanner) { dmnsn_token_buffer *tbuffer - = dmnsn_new_token_buffer(DMNSN_T_LEX_VERBATIM, prev); + = dmnsn_new_token_buffer(DMNSN_T_LEX_VERBATIM, prev, filename); /* Buffer the current token */ dmnsn_buffered_token buffered = { @@ -186,7 +352,7 @@ dmnsn_if_buffer(int token, dmnsn_token_buffer *prev, void *yyscanner) { dmnsn_token_buffer *cond_buffer - = dmnsn_new_token_buffer(DMNSN_T_LEX_VERBATIM, prev); + = dmnsn_new_token_buffer(DMNSN_T_LEX_VERBATIM, prev, filename); /* Buffer the current token */ dmnsn_buffered_token buffered = { @@ -204,7 +370,7 @@ dmnsn_if_buffer(int token, dmnsn_token_buffer *prev, filename, symtable, yyscanner); if (buffered.type == DMNSN_T_EOF) { - dmnsn_diagnostic(filename, buffered.lloc.first_line, + dmnsn_diagnostic(buffered.lloc.first_filename, buffered.lloc.first_line, buffered.lloc.first_column, "syntax error, unexpected end-of-file"); dmnsn_delete_token_buffer(cond_buffer); @@ -244,7 +410,7 @@ dmnsn_if_buffer(int token, dmnsn_token_buffer *prev, dmnsn_yyset_extra(cond_buffer->prev, yyscanner); dmnsn_delete_token_buffer(cond_buffer); - dmnsn_token_buffer *tbuffer = dmnsn_new_token_buffer(token, prev); + dmnsn_token_buffer *tbuffer = dmnsn_new_token_buffer(token, prev, filename); dmnsn_astnode *cnode = dmnsn_find_symbol(symtable, "__cond__"); if (!cnode) { @@ -266,7 +432,7 @@ dmnsn_if_buffer(int token, dmnsn_token_buffer *prev, while (1) { /* Non-recursive call */ buffered.type = dmnsn_yylex_wrapper(&buffered.lval, &buffered.lloc, - filename, yyscanner); + filename, symtable, yyscanner); if (buffered.type == DMNSN_T_EOF) { dmnsn_diagnostic(filename, buffered.lloc.first_line, @@ -328,7 +494,7 @@ dmnsn_while_buffer(int token, dmnsn_token_buffer *prev, const char *filename, dmnsn_symbol_table *symtable, void *yyscanner) { - dmnsn_token_buffer *tbuffer = dmnsn_new_token_buffer(token, prev); + dmnsn_token_buffer *tbuffer = dmnsn_new_token_buffer(token, prev, filename); /* Pretend to be an if */ dmnsn_buffered_token buffered = { @@ -343,7 +509,7 @@ dmnsn_while_buffer(int token, dmnsn_token_buffer *prev, while (1) { /* Non-recursive call */ buffered.type = dmnsn_yylex_wrapper(&buffered.lval, &buffered.lloc, - filename, yyscanner); + filename, symtable, yyscanner); if (buffered.type == DMNSN_T_EOF) { dmnsn_diagnostic(filename, buffered.lloc.first_line, @@ -388,7 +554,7 @@ dmnsn_version_buffer(int token, dmnsn_token_buffer *prev, void *yyscanner) { dmnsn_token_buffer *tbuffer - = dmnsn_new_token_buffer(DMNSN_T_LEX_VERBATIM, prev); + = dmnsn_new_token_buffer(DMNSN_T_LEX_VERBATIM, prev, filename); /* Buffer the current token */ dmnsn_buffered_token buffered = { @@ -430,11 +596,12 @@ int dmnsn_yylex_impl(dmnsn_parse_item *lvalp, dmnsn_parse_location *llocp, static int dmnsn_yylex_wrapper(dmnsn_parse_item *lvalp, dmnsn_parse_location *llocp, - const char *filename, void *yyscanner) + const char *filename, dmnsn_symbol_table *symtable, + void *yyscanner) { dmnsn_token_buffer *tbuffer = dmnsn_yyget_extra(yyscanner); - if (tbuffer) { + if (tbuffer && tbuffer->type != DMNSN_T_INCLUDE) { while (tbuffer && tbuffer->i >= dmnsn_array_size(tbuffer->buffered)) { if (tbuffer->type == DMNSN_T_WHILE) { tbuffer->i = 0; @@ -456,7 +623,7 @@ dmnsn_yylex_wrapper(dmnsn_parse_item *lvalp, dmnsn_parse_location *llocp, int token; - if (tbuffer) { + if (tbuffer && tbuffer->type != DMNSN_T_INCLUDE) { /* Return buffered tokens */ dmnsn_buffered_token buffered; @@ -472,7 +639,16 @@ dmnsn_yylex_wrapper(dmnsn_parse_item *lvalp, dmnsn_parse_location *llocp, *llocp = buffered.lloc; ++tbuffer->i; } else { - token = dmnsn_yylex_impl(lvalp, llocp, filename, yyscanner); + const char *real_filename = tbuffer ? tbuffer->filename : filename; + token = dmnsn_yylex_impl(lvalp, llocp, real_filename, yyscanner); + if (token == DMNSN_T_EOF && tbuffer && tbuffer->type == DMNSN_T_INCLUDE) { + dmnsn_yy_pop_buffer(yyscanner); + fclose(tbuffer->ptr); + dmnsn_pop_scope(symtable); + dmnsn_yyset_extra(tbuffer->prev, yyscanner); + dmnsn_delete_token_buffer(tbuffer); + return dmnsn_yylex_wrapper(lvalp, llocp, filename, symtable, yyscanner); + } } return token; @@ -496,7 +672,9 @@ dmnsn_yylex(dmnsn_parse_item *lvalp, dmnsn_parse_location *llocp, */ while (1) { - int token = dmnsn_yylex_wrapper(lvalp, llocp, filename, yyscanner); + int token = dmnsn_yylex_wrapper( + lvalp, llocp, filename, symtable, yyscanner + ); dmnsn_token_buffer *tbuffer = dmnsn_yyget_extra(yyscanner); if (tbuffer && tbuffer->type == DMNSN_T_LEX_VERBATIM && tbuffer->i == 1) { @@ -504,6 +682,19 @@ dmnsn_yylex(dmnsn_parse_item *lvalp, dmnsn_parse_location *llocp, } switch (token) { + case DMNSN_T_INCLUDE: + { + dmnsn_token_buffer *tb = dmnsn_include_buffer( + token, tbuffer, lvalp, llocp, filename, symtable, yyscanner + ); + if (!tb) { + return DMNSN_T_LEX_ERROR; + } + + dmnsn_yyset_extra(tb, yyscanner); + break; + } + case DMNSN_T_DECLARE: case DMNSN_T_LOCAL: { @@ -561,7 +752,7 @@ dmnsn_yylex(dmnsn_parse_item *lvalp, dmnsn_parse_location *llocp, } dmnsn_yyset_extra(tb, yyscanner); - continue; + break; } case DMNSN_T_WHILE: @@ -574,7 +765,7 @@ dmnsn_yylex(dmnsn_parse_item *lvalp, dmnsn_parse_location *llocp, } dmnsn_yyset_extra(tb, yyscanner); - continue; + break; } case DMNSN_T_VERSION: diff --git a/dimension/tokenize.h b/dimension/tokenize.h index 6dafc97..86be4c9 100644 --- a/dimension/tokenize.h +++ b/dimension/tokenize.h @@ -47,12 +47,18 @@ struct dmnsn_token { }; /* Scanner manipulation */ + int dmnsn_yylex_init(void **scannerp); void dmnsn_yyset_in(FILE *file, void *scanner); int dmnsn_yylex_destroy(void *scanner); void *dmnsn_yyget_extra(void *scanner); void dmnsn_yyset_extra(void *arbitrary_data, void *scanner); +void *dmnsn_yy_make_buffer(FILE *file, void *scanner); +void *dmnsn_yy_make_string_buffer(const char *str, void *scanner); +void dmnsn_yy_push_buffer(void *buffer, void *scanner); +void dmnsn_yy_pop_buffer(void *scanner); + /* Actual lexer */ int dmnsn_yylex(dmnsn_parse_item *lvalp, dmnsn_parse_location *llocp, const char *filename, dmnsn_symbol_table *symtable, diff --git a/tests/dimension/directives.inc b/tests/dimension/directives.inc new file mode 100644 index 0000000..515b2f5 --- /dev/null +++ b/tests/dimension/directives.inc @@ -0,0 +1,21 @@ +/************************************************************************* + * Copyright (C) 2010 Tavian Barnes <tavianator@gmail.com> * + * * + * This file is part of The Dimension Test Suite. * + * * + * The Dimension Test Suite is free software; you can redistribute it * + * and/or modify it under the terms of the GNU General Public License as * + * published by the Free Software Foundation; either version 3 of the * + * License, or (at your option) any later version. * + * * + * The Dimension Test Suite is distributed in the hope that it will be * + * useful, but WITHOUT ANY WARRANTY; without even the implied warranty * + * of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU * + * General Public License for more details. * + * * + * You should have received a copy of the GNU General Public License * + * along with this program. If not, see <http://www.gnu.org/licenses/>. * + *************************************************************************/ + +#declare Center = 0; +#local Local = -1; diff --git a/tests/dimension/directives.pov b/tests/dimension/directives.pov index b621709..75c8a81 100644 --- a/tests/dimension/directives.pov +++ b/tests/dimension/directives.pov @@ -21,13 +21,18 @@ #version 3.6; -#declare Center = 0; +#include "directives.inc" + #declare R = 1; #local Color = rgb <1, 0, 1>; #declare Unused = -1; #undef Unused +#ifdef (Local) + Illegal +#end + #ifdef (Unused) Illegal #end diff --git a/tests/dimension/directives.sh b/tests/dimension/directives.sh index 8d0bbb9..3893acc 100755 --- a/tests/dimension/directives.sh +++ b/tests/dimension/directives.sh @@ -22,11 +22,14 @@ directives=$(${top_builddir}/dimension/dimension --tokenize --parse ${srcdir}/directives.pov) directives_exp="$(echo -n \ '(#version (float "3.6") ; - #declare (identifier "Center") = (integer "0") ; + #include (string "directives.inc") #declare (identifier "R") = (integer "1") ; #local (identifier "Color") = rgb < (integer "1") , (integer "0") , (integer "1") > ; #declare (identifier "Unused") = - (integer "1") ; #undef (identifier "Unused") + #ifdef \( (identifier "Local") \) + (identifier "Illegal") + #end #ifdef \( (identifier "Unused") \) (identifier "Illegal") #end |