diff options
Diffstat (limited to 'mesalib/src/glsl/glcpp')
-rw-r--r-- | mesalib/src/glsl/glcpp/glcpp-lex.l | 449 | ||||
-rw-r--r-- | mesalib/src/glsl/glcpp/glcpp-parse.y | 426 | ||||
-rw-r--r-- | mesalib/src/glsl/glcpp/glcpp.c | 6 | ||||
-rw-r--r-- | mesalib/src/glsl/glcpp/glcpp.h | 6 | ||||
-rw-r--r-- | mesalib/src/glsl/glcpp/pp.c | 96 |
5 files changed, 732 insertions, 251 deletions
diff --git a/mesalib/src/glsl/glcpp/glcpp-lex.l b/mesalib/src/glsl/glcpp/glcpp-lex.l index a1a8e76af..98d500ec0 100644 --- a/mesalib/src/glsl/glcpp/glcpp-lex.l +++ b/mesalib/src/glsl/glcpp/glcpp-lex.l @@ -52,14 +52,107 @@ void glcpp_set_column (int column_no , yyscan_t yyscanner); yylloc->last_column = yycolumn + 1; \ parser->has_new_line_number = 0; \ parser->has_new_source_number = 0; \ - } while(0); + } while(0); #define YY_USER_INIT \ do { \ yylineno = 1; \ - yycolumn = 1; \ + yycolumn = 0; \ yylloc->source = 0; \ } while(0) + +/* It's ugly to have macros that have return statements inside of + * them, but flex-based lexer generation is all built around the + * return statement. + * + * To mitigate the ugliness, we defer as much of the logic as possible + * to an actual function, not a macro (see + * glcpplex_update_state_per_token) and we make the word RETURN + * prominent in all of the macros which may return. + * + * The most-commonly-used macro is RETURN_TOKEN which will perform all + * necessary state updates based on the provided token,, then + * conditionally return the token. It will not return a token if the + * parser is currently skipping tokens, (such as within #if + * 0...#else). + * + * The RETURN_TOKEN_NEVER_SKIP macro is a lower-level variant that + * makes the token returning unconditional. This is needed for things + * like #if and the tokens of its condition, (since these must be + * evaluated by the parser even when otherwise skipping). + * + * Finally, RETURN_STRING_TOKEN is a simple convenience wrapper on top + * of RETURN_TOKEN that performs a string copy of yytext before the + * return. + */ +#define RETURN_TOKEN_NEVER_SKIP(token) \ + do { \ + if (glcpp_lex_update_state_per_token (parser, token)) \ + return token; \ + } while (0) + +#define RETURN_TOKEN(token) \ + do { \ + if (! parser->skipping) { \ + RETURN_TOKEN_NEVER_SKIP(token); \ + } \ + } while(0) + +#define RETURN_STRING_TOKEN(token) \ + do { \ + if (! parser->skipping) { \ + yylval->str = ralloc_strdup (yyextra, yytext); \ + RETURN_TOKEN_NEVER_SKIP (token); \ + } \ + } while(0) + + +/* Update all state necessary for each token being returned. + * + * Here we'll be tracking newlines and spaces so that the lexer can + * alter its behavior as necessary, (for example, '#' has special + * significance if it is the first non-whitespace, non-comment token + * in a line, but does not otherwise). + * + * NOTE: If this function returns FALSE, then no token should be + * returned at all. This is used to suprress duplicate SPACE tokens. + */ +static int +glcpp_lex_update_state_per_token (glcpp_parser_t *parser, int token) +{ + /* After the first non-space token in a line, we won't + * allow any '#' to introduce a directive. */ + if (token == NEWLINE) { + parser->first_non_space_token_this_line = 1; + } else if (token != SPACE) { + parser->first_non_space_token_this_line = 0; + } + + /* Track newlines just to know whether a newline needs + * to be inserted if end-of-file comes early. */ + if (token == NEWLINE) { + parser->last_token_was_newline = 1; + } else { + parser->last_token_was_newline = 0; + } + + /* Track spaces to avoid emitting multiple SPACE + * tokens in a row. */ + if (token == SPACE) { + if (! parser->last_token_was_space) { + parser->last_token_was_space = 1; + return 1; + } else { + parser->last_token_was_space = 1; + return 0; + } + } else { + parser->last_token_was_space = 0; + return 1; + } +} + + %} %option bison-bridge bison-locations reentrant noyywrap @@ -67,14 +160,19 @@ void glcpp_set_column (int column_no , yyscan_t yyscanner); %option prefix="glcpp_" %option stack %option never-interactive +%option warn nodefault + + /* Note: When adding any start conditions to this list, you must also + * update the "Internal compiler error" catch-all rule near the end of + * this file. */ -%x DONE COMMENT UNREACHABLE SKIP DEFINE NEWLINE_CATCHUP +%x COMMENT DEFINE DONE HASH NEWLINE_CATCHUP UNREACHABLE SPACE [[:space:]] NONSPACE [^[:space:]] -NEWLINE [\n] HSPACE [ \t] -HASH ^{HSPACE}*#{HSPACE}* +HASH # +NEWLINE (\r\n|\n\r|\r|\n) IDENTIFIER [_a-zA-Z][_a-zA-Z0-9]* PP_NUMBER [.]?[0-9]([._a-zA-Z0-9]|[eEpP][-+])* PUNCTUATION [][(){}.&*~!/%<>^|;,=+-] @@ -111,270 +209,357 @@ HEXADECIMAL_INTEGER 0[xX][0-9a-fA-F]+[uU]? parser->commented_newlines--; if (parser->commented_newlines == 0) BEGIN INITIAL; - return NEWLINE; + RETURN_TOKEN_NEVER_SKIP (NEWLINE); } - /* The handling of the SKIP vs INITIAL start states requires - * some special handling. Typically, a lexer would change - * start states with statements like "BEGIN SKIP" within the - * lexer rules. We can't get away with that here, since we - * need the parser to actually evaluate expressions for - * directives like "#if". + /* Set up the parser->skipping bit here before doing any lexing. + * + * This bit controls whether tokens are skipped, (as implemented by + * RETURN_TOKEN), such as between "#if 0" and "#endif". * - * So, here, in code that will be executed on every call to - * the lexer,and before any rules, we examine the skip_stack - * as set by the parser to know whether to change from INITIAL - * to SKIP or from SKIP back to INITIAL. + * The parser maintains a skip_stack indicating whether we should be + * skipping, (and nested levels of #if/#ifdef/#ifndef/#endif) will + * push and pop items from the stack. * - * Three cases cause us to switch out of the SKIP state and - * back to the INITIAL state: + * Here are the rules for determining whether we are skipping: * - * 1. The top of the skip_stack is of type SKIP_NO_SKIP - * This means we're still evaluating some #if - * hierarchy, but we're on a branch of it where - * content should not be skipped (such as "#if 1" or - * "#else" or so). + * 1. If the skip stack is NULL, we are outside of all #if blocks + * and we are not skipping. * - * 2. The skip_stack is NULL meaning that we've reached - * the last #endif. + * 2. If the skip stack is non-NULL, the type of the top node in + * the stack determines whether to skip. A type of + * SKIP_NO_SKIP is used for blocks wheere we are emitting + * tokens, (such as between #if 1 and #endif, or after the + * #else of an #if 0, etc.). * - * 3. The lexing_directive bit is set. This indicates that we are - * lexing a pre-processor directive, (such as #if, #elif, or - * #else). For the #if and #elif directives we always need to - * parse the conditions, (even if otherwise within an #if - * 0). And for #else, we want to be able to generate an error - * if any garbage follows #else. + * 3. The lexing_directive bit overrides the skip stack. This bit + * is set when we are actively lexing the expression for a + * pre-processor condition, (such as #if, #elif, or #else). In + * this case, even if otherwise skipping, we need to emit the + * tokens for this condition so that the parser can evaluate + * the expression. (For, #else, there's no expression, but we + * emit tokens so the parser can generate a nice error message + * if there are any tokens here). */ - if (YY_START == INITIAL || YY_START == SKIP) { - if (parser->lexing_directive || - parser->skip_stack == NULL || - parser->skip_stack->type == SKIP_NO_SKIP) - { - BEGIN INITIAL; - } else { - BEGIN SKIP; - } + if (parser->skip_stack && + parser->skip_stack->type != SKIP_NO_SKIP && + ! parser->lexing_directive) + { + parser->skipping = 1; + } else { + parser->skipping = 0; } /* Single-line comments */ -"//"[^\n]* { +<INITIAL,DEFINE,HASH>"//"[^\r\n]* { } /* Multi-line comments */ -"/*" { yy_push_state(COMMENT, yyscanner); } -<COMMENT>[^*\n]* -<COMMENT>[^*\n]*\n { yylineno++; yycolumn = 0; parser->commented_newlines++; } -<COMMENT>"*"+[^*/\n]* -<COMMENT>"*"+[^*/\n]*\n { yylineno++; yycolumn = 0; parser->commented_newlines++; } +<INITIAL,DEFINE,HASH>"/*" { yy_push_state(COMMENT, yyscanner); } +<COMMENT>[^*\r\n]* +<COMMENT>[^*\r\n]*{NEWLINE} { yylineno++; yycolumn = 0; parser->commented_newlines++; } +<COMMENT>"*"+[^*/\r\n]* +<COMMENT>"*"+[^*/\r\n]*{NEWLINE} { yylineno++; yycolumn = 0; parser->commented_newlines++; } <COMMENT>"*"+"/" { yy_pop_state(yyscanner); - if (yyextra->space_tokens) - return SPACE; + /* In the <HASH> start condition, we don't want any SPACE token. */ + if (yyextra->space_tokens && YY_START != HASH) + RETURN_TOKEN (SPACE); +} + +{HASH} { + + /* If the '#' is the first non-whitespace, non-comment token on this + * line, then it introduces a directive, switch to the <HASH> start + * condition. + * + * Otherwise, this is just punctuation, so return the HASH_TOKEN + * token. */ + if (parser->first_non_space_token_this_line) { + BEGIN HASH; + } + + RETURN_TOKEN_NEVER_SKIP (HASH_TOKEN); } -{HASH}version{HSPACE}+ { - yylval->str = ralloc_strdup (yyextra, yytext); +<HASH>version{HSPACE}+ { + BEGIN INITIAL; yyextra->space_tokens = 0; - return HASH_VERSION; + RETURN_STRING_TOKEN (VERSION_TOKEN); +} + + /* Swallow empty #pragma directives, (to avoid confusing the + * downstream compiler). */ +<HASH>pragma{HSPACE}*/{NEWLINE} { + BEGIN INITIAL; } /* glcpp doesn't handle #extension, #version, or #pragma directives. * Simply pass them through to the main compiler's lexer/parser. */ -{HASH}(extension|pragma)[^\n]* { - if (parser->commented_newlines) - BEGIN NEWLINE_CATCHUP; - yylval->str = ralloc_strdup (yyextra, yytext); - yylineno++; - yycolumn = 0; - return OTHER; +<HASH>(extension|pragma)[^\r\n]* { + BEGIN INITIAL; + RETURN_STRING_TOKEN (PRAGMA); } -{HASH}line{HSPACE}+ { - return HASH_LINE; +<HASH>line{HSPACE}+ { + BEGIN INITIAL; + RETURN_TOKEN (LINE); } -<SKIP,INITIAL>{ -{HASH}ifdef { +<HASH>{NEWLINE} { + BEGIN INITIAL; + RETURN_TOKEN_NEVER_SKIP (NEWLINE); +} + + /* For the pre-processor directives, we return these tokens + * even when we are otherwise skipping. */ +<HASH>ifdef { + BEGIN INITIAL; yyextra->lexing_directive = 1; yyextra->space_tokens = 0; - return HASH_IFDEF; + RETURN_TOKEN_NEVER_SKIP (IFDEF); } -{HASH}ifndef { +<HASH>ifndef { + BEGIN INITIAL; yyextra->lexing_directive = 1; yyextra->space_tokens = 0; - return HASH_IFNDEF; + RETURN_TOKEN_NEVER_SKIP (IFNDEF); } -{HASH}if/[^_a-zA-Z0-9] { +<HASH>if/[^_a-zA-Z0-9] { + BEGIN INITIAL; yyextra->lexing_directive = 1; yyextra->space_tokens = 0; - return HASH_IF; + RETURN_TOKEN_NEVER_SKIP (IF); } -{HASH}elif/[^_a-zA-Z0-9] { +<HASH>elif/[^_a-zA-Z0-9] { + BEGIN INITIAL; yyextra->lexing_directive = 1; yyextra->space_tokens = 0; - return HASH_ELIF; + RETURN_TOKEN_NEVER_SKIP (ELIF); } -{HASH}else { +<HASH>else { + BEGIN INITIAL; yyextra->space_tokens = 0; - return HASH_ELSE; + RETURN_TOKEN_NEVER_SKIP (ELSE); } -{HASH}endif { +<HASH>endif { + BEGIN INITIAL; yyextra->space_tokens = 0; - return HASH_ENDIF; -} + RETURN_TOKEN_NEVER_SKIP (ENDIF); } -<SKIP>[^\n] { - if (parser->commented_newlines) - BEGIN NEWLINE_CATCHUP; +<HASH>error[^\r\n]* { + BEGIN INITIAL; + RETURN_STRING_TOKEN (ERROR_TOKEN); } -{HASH}error.* { - char *p; - for (p = yytext; !isalpha(p[0]); p++); /* skip " # " */ - p += 5; /* skip "error" */ - glcpp_error(yylloc, yyextra, "#error%s", p); + /* After we see a "#define" we enter the <DEFINE> start state + * for the lexer. Within <DEFINE> we are looking for the first + * identifier and specifically checking whether the identifier + * is followed by a '(' or not, (to lex either a + * FUNC_IDENTIFIER or an OBJ_IDENITIFIER token). + * + * While in the <DEFINE> state we also need to explicitly + * handle a few other things that may appear before the + * identifier: + * + * * Comments, (handled above with the main support for + * comments). + * + * * Whitespace (simply ignored) + * + * * Anything else, (not an identifier, not a comment, + * and not whitespace). This will generate an error. + */ +<HASH>define{HSPACE}* { + if (! parser->skipping) { + BEGIN DEFINE; + yyextra->space_tokens = 0; + RETURN_TOKEN (DEFINE_TOKEN); + } } -{HASH}define{HSPACE}+ { +<HASH>undef { + BEGIN INITIAL; yyextra->space_tokens = 0; - yy_push_state(DEFINE, yyscanner); - return HASH_DEFINE; + RETURN_TOKEN (UNDEF); } +<HASH>{HSPACE}+ { + /* Nothing to do here. Importantly, don't leave the <HASH> + * start condition, since it's legal to have space between the + * '#' and the directive.. */ +} + + /* This will catch any non-directive garbage after a HASH */ +<HASH>{NONSPACE} { + BEGIN INITIAL; + RETURN_TOKEN (GARBAGE); +} + + /* An identifier immediately followed by '(' */ <DEFINE>{IDENTIFIER}/"(" { - yy_pop_state(yyscanner); - yylval->str = ralloc_strdup (yyextra, yytext); - return FUNC_IDENTIFIER; + BEGIN INITIAL; + RETURN_STRING_TOKEN (FUNC_IDENTIFIER); } + /* An identifier not immediately followed by '(' */ <DEFINE>{IDENTIFIER} { - yy_pop_state(yyscanner); - yylval->str = ralloc_strdup (yyextra, yytext); - return OBJ_IDENTIFIER; + BEGIN INITIAL; + RETURN_STRING_TOKEN (OBJ_IDENTIFIER); } -{HASH}undef { - yyextra->space_tokens = 0; - return HASH_UNDEF; + /* Whitespace */ +<DEFINE>{HSPACE}+ { + /* Just ignore it. Nothing to do here. */ } -{HASH} { - yyextra->space_tokens = 0; - return HASH; + /* '/' not followed by '*', so not a comment. This is an error. */ +<DEFINE>[/][^*]{NONSPACE}* { + BEGIN INITIAL; + glcpp_error(yylloc, yyextra, "#define followed by a non-identifier: %s", yytext); + RETURN_STRING_TOKEN (INTEGER_STRING); +} + + /* A character that can't start an identifier, comment, or + * space. This is an error. */ +<DEFINE>[^_a-zA-Z/[:space:]]{NONSPACE}* { + BEGIN INITIAL; + glcpp_error(yylloc, yyextra, "#define followed by a non-identifier: %s", yytext); + RETURN_STRING_TOKEN (INTEGER_STRING); } {DECIMAL_INTEGER} { - yylval->str = ralloc_strdup (yyextra, yytext); - return INTEGER_STRING; + RETURN_STRING_TOKEN (INTEGER_STRING); } {OCTAL_INTEGER} { - yylval->str = ralloc_strdup (yyextra, yytext); - return INTEGER_STRING; + RETURN_STRING_TOKEN (INTEGER_STRING); } {HEXADECIMAL_INTEGER} { - yylval->str = ralloc_strdup (yyextra, yytext); - return INTEGER_STRING; + RETURN_STRING_TOKEN (INTEGER_STRING); } "<<" { - return LEFT_SHIFT; + RETURN_TOKEN (LEFT_SHIFT); } ">>" { - return RIGHT_SHIFT; + RETURN_TOKEN (RIGHT_SHIFT); } "<=" { - return LESS_OR_EQUAL; + RETURN_TOKEN (LESS_OR_EQUAL); } ">=" { - return GREATER_OR_EQUAL; + RETURN_TOKEN (GREATER_OR_EQUAL); } "==" { - return EQUAL; + RETURN_TOKEN (EQUAL); } "!=" { - return NOT_EQUAL; + RETURN_TOKEN (NOT_EQUAL); } "&&" { - return AND; + RETURN_TOKEN (AND); } "||" { - return OR; + RETURN_TOKEN (OR); +} + +"++" { + RETURN_TOKEN (PLUS_PLUS); +} + +"--" { + RETURN_TOKEN (MINUS_MINUS); } "##" { - if (parser->is_gles) - glcpp_error(yylloc, yyextra, "Token pasting (##) is illegal in GLES"); - return PASTE; + if (! parser->skipping) { + if (parser->is_gles) + glcpp_error(yylloc, yyextra, "Token pasting (##) is illegal in GLES"); + RETURN_TOKEN (PASTE); + } } "defined" { - return DEFINED; + RETURN_TOKEN (DEFINED); } {IDENTIFIER} { - yylval->str = ralloc_strdup (yyextra, yytext); - return IDENTIFIER; + RETURN_STRING_TOKEN (IDENTIFIER); } {PP_NUMBER} { - yylval->str = ralloc_strdup (yyextra, yytext); - return OTHER; + RETURN_STRING_TOKEN (OTHER); } {PUNCTUATION} { - return yytext[0]; + RETURN_TOKEN (yytext[0]); } {OTHER}+ { - yylval->str = ralloc_strdup (yyextra, yytext); - return OTHER; + RETURN_STRING_TOKEN (OTHER); } {HSPACE} { if (yyextra->space_tokens) { - return SPACE; + RETURN_TOKEN (SPACE); } } -<SKIP,INITIAL>\n { + /* We preserve all newlines, even between #if 0..#endif, so no + skipping.. */ +<*>{NEWLINE} { if (parser->commented_newlines) { BEGIN NEWLINE_CATCHUP; + } else { + BEGIN INITIAL; } + yyextra->space_tokens = 1; yyextra->lexing_directive = 0; yylineno++; yycolumn = 0; - return NEWLINE; + RETURN_TOKEN_NEVER_SKIP (NEWLINE); } - /* Handle missing newline at EOF. */ -<INITIAL><<EOF>> { +<INITIAL,COMMENT,DEFINE,HASH><<EOF>> { + if (YY_START == COMMENT) + glcpp_error(yylloc, yyextra, "Unterminated comment"); BEGIN DONE; /* Don't keep matching this rule forever. */ yyextra->lexing_directive = 0; - return NEWLINE; + if (! parser->last_token_was_newline) + RETURN_TOKEN (NEWLINE); } + /* This is a catch-all to avoid the annoying default flex action which + * matches any character and prints it. If any input ever matches this + * rule, then we have made a mistake above and need to fix one or more + * of the preceding patterns to match that input. */ + +<*>. { + glcpp_error(yylloc, yyextra, "Internal compiler error: Unexpected character: %s", yytext); + /* We don't actually use the UNREACHABLE start condition. We - only have this action here so that we can pretend to call some + only have this block here so that we can pretend to call some generated functions, (to avoid "defined but not used" warnings. */ -<UNREACHABLE>. { - unput('.'); - yy_top_state(yyextra); + if (YY_START == UNREACHABLE) { + unput('.'); + yy_top_state(yyextra); + } } %% diff --git a/mesalib/src/glsl/glcpp/glcpp-parse.y b/mesalib/src/glsl/glcpp/glcpp-parse.y index 084078eb0..a61697394 100644 --- a/mesalib/src/glsl/glcpp/glcpp-parse.y +++ b/mesalib/src/glsl/glcpp/glcpp-parse.y @@ -57,6 +57,9 @@ _string_list_append_item (string_list_t *list, const char *str); static int _string_list_contains (string_list_t *list, const char *member, int *index); +static const char * +_string_list_has_duplicate (string_list_t *list); + static int _string_list_length (string_list_t *list); @@ -105,18 +108,25 @@ _parser_active_list_pop (glcpp_parser_t *parser); static int _parser_active_list_contains (glcpp_parser_t *parser, const char *identifier); +typedef enum { + EXPANSION_MODE_IGNORE_DEFINED, + EXPANSION_MODE_EVALUATE_DEFINED +} expansion_mode_t; + /* Expand list, and begin lexing from the result (after first * prefixing a token of type 'head_token_type'). */ static void _glcpp_parser_expand_and_lex_from (glcpp_parser_t *parser, int head_token_type, - token_list_t *list); + token_list_t *list, + expansion_mode_t mode); /* Perform macro expansion in-place on the given list. */ static void _glcpp_parser_expand_token_list (glcpp_parser_t *parser, - token_list_t *list); + token_list_t *list, + expansion_mode_t mode); static void _glcpp_parser_print_expanded_token_list (glcpp_parser_t *parser, @@ -164,14 +174,18 @@ add_builtin_define(glcpp_parser_t *parser, const char *name, int value); %lex-param {glcpp_parser_t *parser} %expect 0 -%token COMMA_FINAL DEFINED ELIF_EXPANDED HASH HASH_DEFINE FUNC_IDENTIFIER OBJ_IDENTIFIER HASH_ELIF HASH_ELSE HASH_ENDIF HASH_IF HASH_IFDEF HASH_IFNDEF HASH_LINE HASH_UNDEF HASH_VERSION IDENTIFIER IF_EXPANDED INTEGER INTEGER_STRING LINE_EXPANDED NEWLINE OTHER PLACEHOLDER SPACE + + /* We use HASH_TOKEN, DEFINE_TOKEN and VERSION_TOKEN (as opposed to + * HASH, DEFINE, and VERSION) to avoid conflicts with other symbols, + * (such as the <HASH> and <DEFINE> start conditions in the lexer). */ +%token DEFINED ELIF_EXPANDED HASH_TOKEN DEFINE_TOKEN FUNC_IDENTIFIER OBJ_IDENTIFIER ELIF ELSE ENDIF ERROR_TOKEN IF IFDEF IFNDEF LINE PRAGMA UNDEF VERSION_TOKEN GARBAGE IDENTIFIER IF_EXPANDED INTEGER INTEGER_STRING LINE_EXPANDED NEWLINE OTHER PLACEHOLDER SPACE PLUS_PLUS MINUS_MINUS %token PASTE %type <ival> INTEGER operator SPACE integer_constant %type <expression_value> expression -%type <str> IDENTIFIER FUNC_IDENTIFIER OBJ_IDENTIFIER INTEGER_STRING OTHER +%type <str> IDENTIFIER FUNC_IDENTIFIER OBJ_IDENTIFIER INTEGER_STRING OTHER ERROR_TOKEN PRAGMA %type <string_list> identifier_list -%type <token> preprocessing_token conditional_token -%type <token_list> pp_tokens replacement_list text_line conditional_tokens +%type <token> preprocessing_token +%type <token_list> pp_tokens replacement_list text_line %left OR %left AND %left '|' @@ -184,6 +198,8 @@ add_builtin_define(glcpp_parser_t *parser, const char *name, int value); %left '*' '/' '%' %right UNARY +%debug + %% input: @@ -192,27 +208,14 @@ input: ; line: - control_line { - ralloc_asprintf_rewrite_tail (&parser->output, &parser->output_length, "\n"); - } -| HASH_LINE { - glcpp_parser_resolve_implicit_version(parser); - } pp_tokens NEWLINE { - - if (parser->skip_stack == NULL || - parser->skip_stack->type == SKIP_NO_SKIP) - { - _glcpp_parser_expand_and_lex_from (parser, - LINE_EXPANDED, $3); - } - } + control_line +| SPACE control_line | text_line { _glcpp_parser_print_expanded_token_list (parser, $1); ralloc_asprintf_rewrite_tail (&parser->output, &parser->output_length, "\n"); ralloc_free ($1); } | expanded_line -| HASH non_directive ; expanded_line: @@ -259,29 +262,48 @@ define: ; control_line: - HASH_DEFINE { + control_line_success { + ralloc_asprintf_rewrite_tail (&parser->output, &parser->output_length, "\n"); + } +| control_line_error +| HASH_TOKEN LINE { + glcpp_parser_resolve_implicit_version(parser); + } pp_tokens NEWLINE { + + if (parser->skip_stack == NULL || + parser->skip_stack->type == SKIP_NO_SKIP) + { + _glcpp_parser_expand_and_lex_from (parser, + LINE_EXPANDED, $4, + EXPANSION_MODE_IGNORE_DEFINED); + } + } +; + +control_line_success: + HASH_TOKEN DEFINE_TOKEN { glcpp_parser_resolve_implicit_version(parser); } define -| HASH_UNDEF { +| HASH_TOKEN UNDEF { glcpp_parser_resolve_implicit_version(parser); } IDENTIFIER NEWLINE { macro_t *macro; - if (strcmp("__LINE__", $3) == 0 - || strcmp("__FILE__", $3) == 0 - || strcmp("__VERSION__", $3) == 0) + if (strcmp("__LINE__", $4) == 0 + || strcmp("__FILE__", $4) == 0 + || strcmp("__VERSION__", $4) == 0) glcpp_error(& @1, parser, "Built-in (pre-defined)" " macro names can not be undefined."); - macro = hash_table_find (parser->defines, $3); + macro = hash_table_find (parser->defines, $4); if (macro) { - hash_table_remove (parser->defines, $3); + hash_table_remove (parser->defines, $4); ralloc_free (macro); } - ralloc_free ($3); + ralloc_free ($4); } -| HASH_IF { +| HASH_TOKEN IF { glcpp_parser_resolve_implicit_version(parser); - } conditional_tokens NEWLINE { + } pp_tokens NEWLINE { /* Be careful to only evaluate the 'if' expression if * we are not skipping. When we are skipping, we * simply push a new 0-valued 'if' onto the skip @@ -293,7 +315,8 @@ control_line: parser->skip_stack->type == SKIP_NO_SKIP) { _glcpp_parser_expand_and_lex_from (parser, - IF_EXPANDED, $3); + IF_EXPANDED, $4, + EXPANSION_MODE_EVALUATE_DEFINED); } else { @@ -301,7 +324,7 @@ control_line: parser->skip_stack->type = SKIP_TO_ENDIF; } } -| HASH_IF NEWLINE { +| HASH_TOKEN IF NEWLINE { /* #if without an expression is only an error if we * are not skipping */ if (parser->skip_stack == NULL || @@ -311,21 +334,21 @@ control_line: } _glcpp_parser_skip_stack_push_if (parser, & @1, 0); } -| HASH_IFDEF { +| HASH_TOKEN IFDEF { glcpp_parser_resolve_implicit_version(parser); } IDENTIFIER junk NEWLINE { - macro_t *macro = hash_table_find (parser->defines, $3); - ralloc_free ($3); + macro_t *macro = hash_table_find (parser->defines, $4); + ralloc_free ($4); _glcpp_parser_skip_stack_push_if (parser, & @1, macro != NULL); } -| HASH_IFNDEF { +| HASH_TOKEN IFNDEF { glcpp_parser_resolve_implicit_version(parser); } IDENTIFIER junk NEWLINE { - macro_t *macro = hash_table_find (parser->defines, $3); - ralloc_free ($3); - _glcpp_parser_skip_stack_push_if (parser, & @2, macro == NULL); + macro_t *macro = hash_table_find (parser->defines, $4); + ralloc_free ($4); + _glcpp_parser_skip_stack_push_if (parser, & @3, macro == NULL); } -| HASH_ELIF conditional_tokens NEWLINE { +| HASH_TOKEN ELIF pp_tokens NEWLINE { /* Be careful to only evaluate the 'elif' expression * if we are not skipping. When we are skipping, we * simply change to a 0-valued 'elif' on the skip @@ -337,7 +360,8 @@ control_line: parser->skip_stack->type == SKIP_TO_ELSE) { _glcpp_parser_expand_and_lex_from (parser, - ELIF_EXPANDED, $2); + ELIF_EXPANDED, $3, + EXPANSION_MODE_EVALUATE_DEFINED); } else if (parser->skip_stack && parser->skip_stack->has_else) @@ -350,7 +374,7 @@ control_line: "elif", 0); } } -| HASH_ELIF NEWLINE { +| HASH_TOKEN ELIF NEWLINE { /* #elif without an expression is an error unless we * are skipping. */ if (parser->skip_stack && @@ -370,7 +394,7 @@ control_line: glcpp_warning(& @1, parser, "ignoring illegal #elif without expression"); } } -| HASH_ELSE { parser->lexing_directive = 1; } NEWLINE { +| HASH_TOKEN ELSE { parser->lexing_directive = 1; } NEWLINE { if (parser->skip_stack && parser->skip_stack->has_else) { @@ -383,24 +407,39 @@ control_line: parser->skip_stack->has_else = true; } } -| HASH_ENDIF { +| HASH_TOKEN ENDIF { _glcpp_parser_skip_stack_pop (parser, & @1); } NEWLINE -| HASH_VERSION integer_constant NEWLINE { +| HASH_TOKEN VERSION_TOKEN integer_constant NEWLINE { if (parser->version_resolved) { glcpp_error(& @1, parser, "#version must appear on the first line"); } - _glcpp_parser_handle_version_declaration(parser, $2, NULL, true); + _glcpp_parser_handle_version_declaration(parser, $3, NULL, true); } -| HASH_VERSION integer_constant IDENTIFIER NEWLINE { +| HASH_TOKEN VERSION_TOKEN integer_constant IDENTIFIER NEWLINE { if (parser->version_resolved) { glcpp_error(& @1, parser, "#version must appear on the first line"); } - _glcpp_parser_handle_version_declaration(parser, $2, $3, true); + _glcpp_parser_handle_version_declaration(parser, $3, $4, true); } -| HASH NEWLINE { +| HASH_TOKEN NEWLINE { glcpp_parser_resolve_implicit_version(parser); } +| HASH_TOKEN PRAGMA NEWLINE { + ralloc_asprintf_rewrite_tail (&parser->output, &parser->output_length, "#%s", $2); + } +; + +control_line_error: + HASH_TOKEN ERROR_TOKEN NEWLINE { + glcpp_error(& @1, parser, "#%s", $2); + } +| HASH_TOKEN DEFINE_TOKEN NEWLINE { + glcpp_error (& @1, parser, "#define without macro name"); + } +| HASH_TOKEN GARBAGE pp_tokens NEWLINE { + glcpp_error (& @1, parser, "Illegal non-directive after #"); + } ; integer_constant: @@ -612,12 +651,6 @@ text_line: | pp_tokens NEWLINE ; -non_directive: - pp_tokens NEWLINE { - yyerror (& @1, parser, "Invalid tokens after #"); - } -; - replacement_list: /* empty */ { $$ = NULL; } | pp_tokens @@ -630,31 +663,6 @@ junk: } ; -conditional_token: - /* Handle "defined" operator */ - DEFINED IDENTIFIER { - int v = hash_table_find (parser->defines, $2) ? 1 : 0; - $$ = _token_create_ival (parser, INTEGER, v); - } -| DEFINED '(' IDENTIFIER ')' { - int v = hash_table_find (parser->defines, $3) ? 1 : 0; - $$ = _token_create_ival (parser, INTEGER, v); - } -| preprocessing_token -; - -conditional_tokens: - /* Exactly the same as pp_tokens, but using conditional_token */ - conditional_token { - $$ = _token_list_create (parser); - _token_list_append ($$, $1); - } -| conditional_tokens conditional_token { - $$ = $1; - _token_list_append ($$, $2); - } -; - pp_tokens: preprocessing_token { parser->space_tokens = 1; @@ -680,6 +688,10 @@ preprocessing_token: $$ = _token_create_ival (parser, $1, $1); $$->location = yylloc; } +| DEFINED { + $$ = _token_create_ival (parser, DEFINED, DEFINED); + $$->location = yylloc; + } | OTHER { $$ = _token_create_str (parser, OTHER, $1); $$->location = yylloc; @@ -722,6 +734,8 @@ operator: | ',' { $$ = ','; } | '=' { $$ = '='; } | PASTE { $$ = PASTE; } +| PLUS_PLUS { $$ = PLUS_PLUS; } +| MINUS_MINUS { $$ = MINUS_MINUS; } ; %% @@ -777,6 +791,25 @@ _string_list_contains (string_list_t *list, const char *member, int *index) return 0; } +/* Return duplicate string in list (if any), NULL otherwise. */ +const char * +_string_list_has_duplicate (string_list_t *list) +{ + string_node_t *node, *dup; + + if (list == NULL) + return NULL; + + for (node = list->head; node; node = node->next) { + for (dup = node->next; dup; dup = dup->next) { + if (strcmp (node->str, dup->str) == 0) + return node->str; + } + } + + return NULL; +} + int _string_list_length (string_list_t *list) { @@ -1123,14 +1156,21 @@ _token_print (char **out, size_t *len, token_t *token) case PASTE: ralloc_asprintf_rewrite_tail (out, len, "##"); break; - case COMMA_FINAL: - ralloc_asprintf_rewrite_tail (out, len, ","); + case PLUS_PLUS: + ralloc_asprintf_rewrite_tail (out, len, "++"); + break; + case MINUS_MINUS: + ralloc_asprintf_rewrite_tail (out, len, "--"); + break; + case DEFINED: + ralloc_asprintf_rewrite_tail (out, len, "defined"); break; case PLACEHOLDER: /* Nothing to print. */ break; default: assert(!"Error: Don't know how to print token."); + break; } } @@ -1308,12 +1348,16 @@ glcpp_parser_create (const struct gl_extensions *extensions, gl_api api) parser->active = NULL; parser->lexing_directive = 0; parser->space_tokens = 1; + parser->last_token_was_newline = 0; + parser->last_token_was_space = 0; + parser->first_non_space_token_this_line = 1; parser->newline_as_space = 0; parser->in_control_line = 0; parser->paren_count = 0; parser->commented_newlines = 0; parser->skip_stack = NULL; + parser->skipping = 0; parser->lex_from_list = NULL; parser->lex_from_node = NULL; @@ -1459,15 +1503,143 @@ _token_list_create_with_one_integer (void *ctx, int ival) return _token_list_create_with_one_ival (ctx, INTEGER, ival); } +/* Evaluate a DEFINED token node (based on subsequent tokens in the list). + * + * Note: This function must only be called when "node" is a DEFINED token, + * (and will abort with an assertion failure otherwise). + * + * If "node" is followed, (ignoring any SPACE tokens), by an IDENTIFIER token + * (optionally preceded and followed by '(' and ')' tokens) then the following + * occurs: + * + * If the identifier is a defined macro, this function returns 1. + * + * If the identifier is not a defined macro, this function returns 0. + * + * In either case, *last will be updated to the last node in the list + * consumed by the evaluation, (either the token of the identifier or the + * token of the closing parenthesis). + * + * In all other cases, (such as "node is the final node of the list", or + * "missing closing parenthesis", etc.), this function generates a + * preprocessor error, returns -1 and *last will not be set. + */ +static int +_glcpp_parser_evaluate_defined (glcpp_parser_t *parser, + token_node_t *node, + token_node_t **last) +{ + token_node_t *argument, *defined = node; + + assert (node->token->type == DEFINED); + + node = node->next; + + /* Ignore whitespace after DEFINED token. */ + while (node && node->token->type == SPACE) + node = node->next; + + if (node == NULL) + goto FAIL; + + if (node->token->type == IDENTIFIER || node->token->type == OTHER) { + argument = node; + } else if (node->token->type == '(') { + node = node->next; + + /* Ignore whitespace after '(' token. */ + while (node && node->token->type == SPACE) + node = node->next; + + if (node == NULL || (node->token->type != IDENTIFIER && + node->token->type != OTHER)) + { + goto FAIL; + } + + argument = node; + + node = node->next; + + /* Ignore whitespace after identifier, before ')' token. */ + while (node && node->token->type == SPACE) + node = node->next; + + if (node == NULL || node->token->type != ')') + goto FAIL; + } else { + goto FAIL; + } + + *last = node; + + return hash_table_find (parser->defines, + argument->token->value.str) ? 1 : 0; + +FAIL: + glcpp_error (&defined->token->location, parser, + "\"defined\" not followed by an identifier"); + return -1; +} + +/* Evaluate all DEFINED nodes in a given list, modifying the list in place. + */ +static void +_glcpp_parser_evaluate_defined_in_list (glcpp_parser_t *parser, + token_list_t *list) +{ + token_node_t *node, *node_prev, *replacement, *last = NULL; + int value; + + if (list == NULL) + return; + + node_prev = NULL; + node = list->head; + + while (node) { + + if (node->token->type != DEFINED) + goto NEXT; + + value = _glcpp_parser_evaluate_defined (parser, node, &last); + if (value == -1) + goto NEXT; + + replacement = ralloc (list, token_node_t); + replacement->token = _token_create_ival (list, INTEGER, value); + + /* Splice replacement node into list, replacing from "node" + * through "last". */ + if (node_prev) + node_prev->next = replacement; + else + list->head = replacement; + replacement->next = last->next; + if (last == list->tail) + list->tail = replacement; + + node = replacement; + + NEXT: + node_prev = node; + node = node->next; + } +} + /* Perform macro expansion on 'list', placing the resulting tokens * into a new list which is initialized with a first token of type * 'head_token_type'. Then begin lexing from the resulting list, * (return to the current lexing source when this list is exhausted). + * + * See the documentation of _glcpp_parser_expand_token_list for a description + * of the "mode" parameter. */ static void _glcpp_parser_expand_and_lex_from (glcpp_parser_t *parser, int head_token_type, - token_list_t *list) + token_list_t *list, + expansion_mode_t mode) { token_list_t *expanded; token_t *token; @@ -1475,7 +1647,7 @@ _glcpp_parser_expand_and_lex_from (glcpp_parser_t *parser, expanded = _token_list_create (parser); token = _token_create_ival (parser, head_token_type, head_token_type); _token_list_append (expanded, token); - _glcpp_parser_expand_token_list (parser, list); + _glcpp_parser_expand_token_list (parser, list, mode); _token_list_append_list (expanded, list); glcpp_parser_lex_from (parser, expanded); } @@ -1538,12 +1710,15 @@ _glcpp_parser_apply_pastes (glcpp_parser_t *parser, token_list_t *list) * *last to the last node in the list that was consumed by the * expansion. Specifically, *last will be set as follows: as the * token of the closing right parenthesis. + * + * See the documentation of _glcpp_parser_expand_token_list for a description + * of the "mode" parameter. */ static token_list_t * _glcpp_parser_expand_function (glcpp_parser_t *parser, token_node_t *node, - token_node_t **last) - + token_node_t **last, + expansion_mode_t mode) { macro_t *macro; const char *identifier; @@ -1612,7 +1787,8 @@ _glcpp_parser_expand_function (glcpp_parser_t *parser, expanded_argument = _token_list_copy (parser, argument); _glcpp_parser_expand_token_list (parser, - expanded_argument); + expanded_argument, + mode); _token_list_append_list (substituted, expanded_argument); } else { @@ -1652,11 +1828,15 @@ _glcpp_parser_expand_function (glcpp_parser_t *parser, * * As the token of the closing right parenthesis in the case of * function-like macro expansion. + * + * See the documentation of _glcpp_parser_expand_token_list for a description + * of the "mode" parameter. */ static token_list_t * _glcpp_parser_expand_node (glcpp_parser_t *parser, token_node_t *node, - token_node_t **last) + token_node_t **last, + expansion_mode_t mode) { token_t *token = node->token; const char *identifier; @@ -1664,14 +1844,6 @@ _glcpp_parser_expand_node (glcpp_parser_t *parser, /* We only expand identifiers */ if (token->type != IDENTIFIER) { - /* We change any COMMA into a COMMA_FINAL to prevent - * it being mistaken for an argument separator - * later. */ - if (token->type == ',') { - token->type = COMMA_FINAL; - token->value.ival = COMMA_FINAL; - } - return NULL; } @@ -1723,7 +1895,7 @@ _glcpp_parser_expand_node (glcpp_parser_t *parser, return replacement; } - return _glcpp_parser_expand_function (parser, node, last); + return _glcpp_parser_expand_function (parser, node, last, mode); } /* Push a new identifier onto the parser's active list. @@ -1782,11 +1954,28 @@ _parser_active_list_contains (glcpp_parser_t *parser, const char *identifier) /* Walk over the token list replacing nodes with their expansion. * Whenever nodes are expanded the walking will walk over the new * nodes, continuing to expand as necessary. The results are placed in - * 'list' itself; + * 'list' itself. + * + * The "mode" argument controls the handling of any DEFINED tokens that + * result from expansion as follows: + * + * EXPANSION_MODE_IGNORE_DEFINED: Any resulting DEFINED tokens will be + * left in the final list, unevaluated. This is the correct mode + * for expanding any list in any context other than a + * preprocessor conditional, (#if or #elif). + * + * EXPANSION_MODE_EVALUATE_DEFINED: Any resulting DEFINED tokens will be + * evaluated to 0 or 1 tokens depending on whether the following + * token is the name of a defined macro. If the DEFINED token is + * not followed by an (optionally parenthesized) identifier, then + * an error will be generated. This the correct mode for + * expanding any list in the context of a preprocessor + * conditional, (#if or #elif). */ static void _glcpp_parser_expand_token_list (glcpp_parser_t *parser, - token_list_t *list) + token_list_t *list, + expansion_mode_t mode) { token_node_t *node_prev; token_node_t *node, *last = NULL; @@ -1801,15 +1990,23 @@ _glcpp_parser_expand_token_list (glcpp_parser_t *parser, node_prev = NULL; node = list->head; + if (mode == EXPANSION_MODE_EVALUATE_DEFINED) + _glcpp_parser_evaluate_defined_in_list (parser, list); + while (node) { while (parser->active && parser->active->marker == node) _parser_active_list_pop (parser); - expansion = _glcpp_parser_expand_node (parser, node, &last); + expansion = _glcpp_parser_expand_node (parser, node, &last, mode); if (expansion) { token_node_t *n; + if (mode == EXPANSION_MODE_EVALUATE_DEFINED) { + _glcpp_parser_evaluate_defined_in_list (parser, + expansion); + } + for (n = node; n != last->next; n = n->next) while (parser->active && parser->active->marker == n) @@ -1862,7 +2059,7 @@ _glcpp_parser_print_expanded_token_list (glcpp_parser_t *parser, if (list == NULL) return; - _glcpp_parser_expand_token_list (parser, list); + _glcpp_parser_expand_token_list (parser, list, EXPANSION_MODE_IGNORE_DEFINED); _token_list_trim_trailing_space (list); @@ -1923,6 +2120,10 @@ _define_object_macro (glcpp_parser_t *parser, { macro_t *macro, *previous; + /* We define pre-defined macros before we've started parsing the + * actual file. So if there's no location defined yet, that's what + * were doing and we don't want to generate an error for using the + * reserved names. */ if (loc != NULL) _check_for_reserved_macro_name(parser, loc, identifier); @@ -1955,9 +2156,16 @@ _define_function_macro (glcpp_parser_t *parser, token_list_t *replacements) { macro_t *macro, *previous; + const char *dup; _check_for_reserved_macro_name(parser, loc, identifier); + /* Check for any duplicate parameter names. */ + if ((dup = _string_list_has_duplicate (parameters)) != NULL) { + glcpp_error (loc, parser, "Duplicate macro parameter \"%s\"", + dup); + } + macro = ralloc (parser, macro_t); ralloc_steal (macro, parameters); ralloc_steal (macro, replacements); @@ -2020,11 +2228,11 @@ glcpp_parser_lex (YYSTYPE *yylval, YYLTYPE *yylloc, glcpp_parser_t *parser) if (ret == NEWLINE) parser->in_control_line = 0; } - else if (ret == HASH_DEFINE || - ret == HASH_UNDEF || ret == HASH_IF || - ret == HASH_IFDEF || ret == HASH_IFNDEF || - ret == HASH_ELIF || ret == HASH_ELSE || - ret == HASH_ENDIF || ret == HASH) + else if (ret == DEFINE_TOKEN || + ret == UNDEF || ret == IF || + ret == IFDEF || ret == IFNDEF || + ret == ELIF || ret == ELSE || + ret == ENDIF || ret == HASH_TOKEN) { parser->in_control_line = 1; } @@ -2117,7 +2325,7 @@ _glcpp_parser_skip_stack_change_if (glcpp_parser_t *parser, YYLTYPE *loc, const char *type, int condition) { if (parser->skip_stack == NULL) { - glcpp_error (loc, parser, "%s without #if\n", type); + glcpp_error (loc, parser, "#%s without #if\n", type); return; } @@ -2170,6 +2378,8 @@ _glcpp_parser_handle_version_declaration(glcpp_parser_t *parser, intmax_t versio if (extensions != NULL) { if (extensions->OES_EGL_image_external) add_builtin_define(parser, "GL_OES_EGL_image_external", 1); + if (extensions->OES_standard_derivatives) + add_builtin_define(parser, "GL_OES_standard_derivatives", 1); } } else { add_builtin_define(parser, "GL_ARB_draw_buffers", 1); diff --git a/mesalib/src/glsl/glcpp/glcpp.c b/mesalib/src/glsl/glcpp/glcpp.c index 07b1500b6..ca188015c 100644 --- a/mesalib/src/glsl/glcpp/glcpp.c +++ b/mesalib/src/glsl/glcpp/glcpp.c @@ -124,6 +124,7 @@ enum { const static struct option long_options[] = { {"disable-line-continuations", no_argument, 0, DISABLE_LINE_CONTINUATIONS_OPT }, + {"debug", no_argument, 0, 'd'}, {0, 0, 0, 0 } }; @@ -140,11 +141,14 @@ main (int argc, char *argv[]) init_fake_gl_context (&gl_ctx); - while ((c = getopt_long(argc, argv, "", long_options, NULL)) != -1) { + while ((c = getopt_long(argc, argv, "d", long_options, NULL)) != -1) { switch (c) { case DISABLE_LINE_CONTINUATIONS_OPT: gl_ctx.Const.DisableGLSLLineContinuations = true; break; + case 'd': + glcpp_parser_debug = 1; + break; default: usage (); exit (1); diff --git a/mesalib/src/glsl/glcpp/glcpp.h b/mesalib/src/glsl/glcpp/glcpp.h index 64b487202..70aa14b6e 100644 --- a/mesalib/src/glsl/glcpp/glcpp.h +++ b/mesalib/src/glsl/glcpp/glcpp.h @@ -29,7 +29,7 @@ #include "main/mtypes.h" -#include "../ralloc.h" +#include "util/ralloc.h" #include "program/hash_table.h" @@ -177,11 +177,15 @@ struct glcpp_parser { active_list_t *active; int lexing_directive; int space_tokens; + int last_token_was_newline; + int last_token_was_space; + int first_non_space_token_this_line; int newline_as_space; int in_control_line; int paren_count; int commented_newlines; skip_node_t *skip_stack; + int skipping; token_list_t *lex_from_list; token_node_t *lex_from_node; char *output; diff --git a/mesalib/src/glsl/glcpp/pp.c b/mesalib/src/glsl/glcpp/pp.c index 4a623f81e..a54bcbe16 100644 --- a/mesalib/src/glsl/glcpp/pp.c +++ b/mesalib/src/glsl/glcpp/pp.c @@ -70,6 +70,42 @@ glcpp_warning (YYLTYPE *locp, glcpp_parser_t *parser, const char *fmt, ...) &parser->info_log_length, "\n"); } +/* Given str, (that's expected to start with a newline terminator of some + * sort), return a pointer to the first character in str after the newline. + * + * A newline terminator can be any of the following sequences: + * + * "\r\n" + * "\n\r" + * "\n" + * "\r" + * + * And the longest such sequence will be skipped. + */ +static const char * +skip_newline (const char *str) +{ + const char *ret = str; + + if (ret == NULL) + return ret; + + if (*ret == '\0') + return ret; + + if (*ret == '\r') { + ret++; + if (*ret && *ret == '\n') + ret++; + } else if (*ret == '\n') { + ret++; + if (*ret && *ret == '\r') + ret++; + } + + return ret; +} + /* Remove any line continuation characters in the shader, (whether in * preprocessing directives or in GLSL code). */ @@ -78,10 +114,49 @@ remove_line_continuations(glcpp_parser_t *ctx, const char *shader) { char *clean = ralloc_strdup(ctx, ""); const char *backslash, *newline, *search_start; + const char *cr, *lf; + char newline_separator[3]; int collapsed_newlines = 0; search_start = shader; + /* Determine what flavor of newlines this shader is using. GLSL + * provides for 4 different possible ways to separate lines, (using + * one or two characters): + * + * "\n" (line-feed, like Linux, Unix, and new Mac OS) + * "\r" (carriage-return, like old Mac files) + * "\r\n" (carriage-return + line-feed, like DOS files) + * "\n\r" (line-feed + carriage-return, like nothing, really) + * + * This code explicitly supports a shader that uses a mixture of + * newline terminators and will properly handle line continuation + * backslashes followed by any of the above. + * + * But, since we must also insert additional newlines in the output + * (for any collapsed lines) we attempt to maintain consistency by + * examining the first encountered newline terminator, and using the + * same terminator for any newlines we insert. + */ + cr = strchr(search_start, '\r'); + lf = strchr(search_start, '\n'); + + newline_separator[0] = '\n'; + newline_separator[1] = '\0'; + newline_separator[2] = '\0'; + + if (cr == NULL) { + /* Nothing to do. */ + } else if (lf == NULL) { + newline_separator[0] = '\r'; + } else if (lf == cr + 1) { + newline_separator[0] = '\r'; + newline_separator[1] = '\n'; + } else if (cr == lf + 1) { + newline_separator[0] = '\n'; + newline_separator[1] = '\r'; + } + while (true) { backslash = strchr(search_start, '\\'); @@ -91,17 +166,24 @@ remove_line_continuations(glcpp_parser_t *ctx, const char *shader) * line numbers. */ if (collapsed_newlines) { - newline = strchr(search_start, '\n'); + cr = strchr (search_start, '\r'); + lf = strchr (search_start, '\n'); + if (cr && lf) + newline = cr < lf ? cr : lf; + else if (cr) + newline = cr; + else + newline = lf; if (newline && (backslash == NULL || newline < backslash)) { ralloc_strncat(&clean, shader, newline - shader + 1); while (collapsed_newlines) { - ralloc_strcat(&clean, "\n"); + ralloc_strcat(&clean, newline_separator); collapsed_newlines--; } - shader = newline + 1; + shader = skip_newline (newline); search_start = shader; } } @@ -116,15 +198,11 @@ remove_line_continuations(glcpp_parser_t *ctx, const char *shader) * advance the shader pointer to the character after the * newline. */ - if (backslash[1] == '\n' || - (backslash[1] == '\r' && backslash[2] == '\n')) + if (backslash[1] == '\r' || backslash[1] == '\n') { collapsed_newlines++; ralloc_strncat(&clean, shader, backslash - shader); - if (backslash[1] == '\n') - shader = backslash + 2; - else - shader = backslash + 3; + shader = skip_newline (backslash + 1); search_start = shader; } } |