/* * Mesa 3-D graphics library * * Copyright (C) 2005-2008 Brian Paul All Rights Reserved. * Copyright (C) 2009 VMware, Inc. All Rights Reserved. * * Permission is hereby granted, free of charge, to any person obtaining a * copy of this software and associated documentation files (the "Software"), * to deal in the Software without restriction, including without limitation * the rights to use, copy, modify, merge, publish, distribute, sublicense, * and/or sell copies of the Software, and to permit persons to whom the * Software is furnished to do so, subject to the following conditions: * * The above copyright notice and this permission notice shall be included * in all copies or substantial portions of the Software. * * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS * OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL * BRIAN PAUL BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN * AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN * CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. */ /** * \file slang_preprocess.c * slang preprocessor * \author Michal Krol */ #include "main/imports.h" #include "shader/grammar/grammar_mesa.h" #include "slang_preprocess.h" LONGSTRING static const char *slang_pp_directives_syn = #include "library/slang_pp_directives_syn.h" ; LONGSTRING static const char *slang_pp_expression_syn = #include "library/slang_pp_expression_syn.h" ; LONGSTRING static const char *slang_pp_version_syn = #include "library/slang_pp_version_syn.h" ; static GLvoid grammar_error_to_log (slang_info_log *log) { char buf[1024]; GLint pos; grammar_get_last_error ((byte *) (buf), sizeof (buf), &pos); if (buf[0] == 0) { _mesa_snprintf(buf, sizeof(buf), "Preprocessor error"); } slang_info_log_error (log, buf); } GLboolean _slang_preprocess_version (const char *text, GLuint *version, GLuint *eaten, slang_info_log *log) { grammar id; byte *prod, *I; unsigned int size; id = grammar_load_from_text ((const byte *) (slang_pp_version_syn)); if (id == 0) { grammar_error_to_log (log); return GL_FALSE; } if (!grammar_fast_check (id, (const byte *) (text), &prod, &size, 8)) { grammar_error_to_log (log); grammar_destroy (id); return GL_FALSE; } /* there can be multiple #version directives - grab the last one */ I = &prod[size - 6]; *version = (GLuint) (I[0]) + (GLuint) (I[1]) * 100; *eaten = (GLuint) (I[2]) + ((GLuint) (I[3]) << 8) + ((GLuint) (I[4]) << 16) + ((GLuint) (I[5]) << 24); grammar_destroy (id); grammar_alloc_free (prod); return GL_TRUE; } /* * The preprocessor does the following work. * 1. Remove comments. Each comment block is replaced with a single space and if the * block contains new-lines, they are preserved. This ensures that line numbers * stay the same and if a comment block delimits two tokens, the are delitmited * by the space after comment removal. * 2. Remove preprocessor directives from the source string, checking their syntax and * executing them if appropriate. Again, new-lines are preserved. * 3. Expand macros. * 4. Tokenize the source string by ensuring there is at least one space between every * two adjacent tokens. */ #define PP_ANNOTATE 0 static GLvoid pp_annotate (slang_string *output, const char *fmt, ...) { #if PP_ANNOTATE va_list va; char buffer[1024]; va_start (va, fmt); _mesa_vsprintf (buffer, fmt, va); va_end (va); slang_string_pushs (output, buffer, _mesa_strlen (buffer)); #else (GLvoid) (output); (GLvoid) (fmt); #endif } /* * The expression is executed on a fixed-sized stack. The PUSH macro makes a runtime * check if the stack is not overflown by too complex expressions. In that situation the * GLSL preprocessor should report internal compiler error. * The BINARYDIV makes a runtime check if the divider is not 0. If it is, it reports * compilation error. */ #define EXECUTION_STACK_SIZE 1024 #define PUSH(x)\ do {\ if (sp == 0) {\ slang_info_log_error (elog, "internal compiler error: preprocessor execution stack overflow.");\ return GL_FALSE;\ }\ stack[--sp] = x;\ } while (GL_FALSE) #define POP(x)\ do {\ assert (sp < EXECUTION_STACK_SIZE);\ x = stack[sp++];\ } while (GL_FALSE) #define BINARY(op)\ do {\ GLint a, b;\ POP(b);\ POP(a);\ PUSH(a op b);\ } while (GL_FALSE) #define BINARYDIV(op)\ do {\ GLint a, b;\ POP(b);\ POP(a);\ if (b == 0) {\ slang_info_log_error (elog, "division by zero in preprocessor expression.");\ return GL_FALSE;\ }\ PUSH(a op b);\ } while (GL_FALSE) #define UNARY(op)\ do {\ GLint a;\ POP(a);\ PUSH(op a);\ } while (GL_FALSE) #define OP_END 0 #define OP_PUSHINT 1 #define OP_LOGICALOR 2 #define OP_LOGICALAND 3 #define OP_OR 4 #define OP_XOR 5 #define OP_AND 6 #define OP_EQUAL 7 #define OP_NOTEQUAL 8 #define OP_LESSEQUAL 9 #define OP_GREATEREQUAL 10 #define OP_LESS 11 #define OP_GREATER 12 #define OP_LEFTSHIFT 13 #define OP_RIGHTSHIFT 14 #define OP_ADD 15 #define OP_SUBTRACT 16 #define OP_MULTIPLY 17 #define OP_DIVIDE 18 #define OP_MODULUS 19 #define OP_PLUS 20 #define OP_MINUS 21 #define OP_NEGATE 22 #define OP_COMPLEMENT 23 static GLboolean execute_expression (slang_string *output, const byte *code, GLuint *pi, GLint *result, slang_info_log *elog) { GLuint i = *pi; GLint stack[EXECUTION_STACK_SIZE]; GLuint sp = EXECUTION_STACK_SIZE; while (code[i] != OP_END) { switch (code[i++]) { case OP_PUSHINT: i++; PUSH(_mesa_atoi ((const char *) (&code[i]))); i += _mesa_strlen ((const char *) (&code[i])) + 1; break; case OP_LOGICALOR: BINARY(||); break; case OP_LOGICALAND: BINARY(&&); break; case OP_OR: BINARY(|); break; case OP_XOR: BINARY(^); break; case OP_AND: BINARY(&); break; case OP_EQUAL: BINARY(==); break; case OP_NOTEQUAL: BINARY(!=); break; case OP_LESSEQUAL: BINARY(<=); break; case OP_GREATEREQUAL: BINARY(>=); break; case OP_LESS: BINARY(<); break; case OP_GREATER: BINARY(>); break; case OP_LEFTSHIFT: BINARY(<<); break; case OP_RIGHTSHIFT: BINARY(>>); break; case OP_ADD: BINARY(+); break; case OP_SUBTRACT: BINARY(-); break; case OP_MULTIPLY: BINARY(*); break; case OP_DIVIDE: BINARYDIV(/); break; case OP_MODULUS: BINARYDIV(%); break; case OP_PLUS: UNARY(+); break; case OP_MINUS: UNARY(-); break; case OP_NEGATE: UNARY(!); break; case OP_COMPLEMENT: UNARY(~); break; default: assert (0); } } /* Write-back the index skipping the OP_END. */ *pi = i + 1; /* There should be exactly one value left on the stack. This is our result. */ POP(*result); pp_annotate (output, "%d ", *result); assert (sp == EXECUTION_STACK_SIZE); return GL_TRUE; } /* * Function execute_expressions() executes up to 2 expressions. The second expression is there * for the #line directive which takes 1 or 2 expressions that indicate line and file numbers. * If it fails, it returns 0. If it succeeds, it returns the number of executed expressions. */ #define EXP_END 0 #define EXP_EXPRESSION 1 static GLuint execute_expressions (slang_string *output, grammar eid, const byte *expr, GLint results[2], slang_info_log *elog) { GLint success; byte *code; GLuint size, count = 0; success = grammar_fast_check (eid, expr, &code, &size, 64); if (success) { GLuint i = 0; while (code[i++] == EXP_EXPRESSION) { assert (count < 2); if (!execute_expression (output, code, &i, &results[count], elog)) { count = 0; break; } count++; } grammar_alloc_free (code); } else { slang_info_log_error (elog, "syntax error in preprocessor expression.");\ } return count; } /* * The pp_symbol structure is used to hold macro definitions and macro formal parameters. The * pp_symbols strcture is a collection of pp_symbol. It is used both for storing macro formal * parameters and all global macro definitions. Making this unification wastes some memory, * becuse macro formal parameters don't need further lists of symbols. We lose 8 bytes per * formal parameter here, but making this we can use the same code to substitute macro parameters * as well as macros in the source string. */ typedef struct { struct pp_symbol_ *symbols; GLuint count; } pp_symbols; static GLvoid pp_symbols_init (pp_symbols *self) { self->symbols = NULL; self->count = 0; } static GLvoid pp_symbols_free (pp_symbols *); typedef struct pp_symbol_ { slang_string name; slang_string replacement; pp_symbols parameters; } pp_symbol; static GLvoid pp_symbol_init (pp_symbol *self) { slang_string_init (&self->name); slang_string_init (&self->replacement); pp_symbols_init (&self->parameters); } static GLvoid pp_symbol_free (pp_symbol *self) { slang_string_free (&self->name); slang_string_free (&self->replacement); pp_symbols_free (&self->parameters); } static GLvoid pp_symbol_reset (pp_symbol *self) { /* Leave symbol name intact. */ slang_string_reset (&self->replacement); pp_symbols_free (&self->parameters); pp_symbols_init (&self->parameters); } static GLvoid pp_symbols_free (pp_symbols *self) { GLuint i; for (i = 0; i < self->count; i++) pp_symbol_free (&self->symbols[i]); _mesa_free (self->symbols); } static pp_symbol * pp_symbols_push (pp_symbols *self) { self->symbols = (pp_symbol *) (_mesa_realloc (self->symbols, self->count * sizeof (pp_symbol), (self->count + 1) * sizeof (pp_symbol))); if (self->symbols == NULL) return NULL; pp_symbol_init (&self->symbols[self->count]); return &self->symbols[self->count++]; } static GLboolean pp_symbols_erase (pp_symbols *self, pp_symbol *symbol) { assert (symbol >= self->symbols && symbol < self->symbols + self->count); self->count--; pp_symbol_free (symbol); if (symbol < self->symbols + self->count) _mesa_memcpy (symbol, symbol + 1, sizeof (pp_symbol) * (self->symbols + self->count - symbol)); self->symbols = (pp_symbol *) (_mesa_realloc (self->symbols, (self->count + 1) * sizeof (pp_symbol), self->count * sizeof (pp_symbol))); return self->symbols != NULL; } static pp_symbol * pp_symbols_find (pp_symbols *self, const char *name) { GLuint i; for (i = 0; i < self->count; i++) if (_mesa_strcmp (name, slang_string_cstr (&self->symbols[i].name)) == 0) return &self->symbols[i]; return NULL; } /* * The condition context of a single #if/#else/#endif level. Those can be nested, so there * is a stack of condition contexts. * There is a special global context on the bottom of the stack. It is there to simplify * context handling. */ typedef struct { GLboolean current; /* The condition value of this level. */ GLboolean effective; /* The effective product of current condition, outer level conditions * and position within #if-#else-#endif sections. */ GLboolean else_allowed; /* TRUE if in #if-#else section, FALSE if in #else-#endif section * and for global context. */ GLboolean endif_required; /* FALSE for global context only. */ } pp_cond_ctx; /* Should be enuff. */ #define CONDITION_STACK_SIZE 64 typedef struct { pp_cond_ctx stack[CONDITION_STACK_SIZE]; pp_cond_ctx *top; } pp_cond_stack; static GLboolean pp_cond_stack_push (pp_cond_stack *self, slang_info_log *elog) { if (self->top == self->stack) { slang_info_log_error (elog, "internal compiler error: preprocessor condition stack overflow."); return GL_FALSE; } self->top--; return GL_TRUE; } static GLvoid pp_cond_stack_reevaluate (pp_cond_stack *self) { /* There must be at least 2 conditions on the stack - one global and one being evaluated. */ assert (self->top <= &self->stack[CONDITION_STACK_SIZE - 2]); self->top->effective = self->top->current && self->top[1].effective; } /** * Extension enables through #extension directive. * NOTE: Currently, only enable/disable state is stored. */ typedef struct { GLboolean ARB_draw_buffers; GLboolean ARB_texture_rectangle; } pp_ext; /** * Disable all extensions. Called at startup and on #extension all: disable. */ static GLvoid pp_ext_disable_all(pp_ext *self) { _mesa_memset(self, 0, sizeof(self)); } /** * Called during preprocessor initialization to set the initial enable/disable * state of extensions. */ static GLvoid pp_ext_init(pp_ext *self, const struct gl_extensions *extensions) { pp_ext_disable_all (self); self->ARB_draw_buffers = GL_TRUE; if (extensions->NV_texture_rectangle) self->ARB_texture_rectangle = GL_TRUE; } /** * Called in response to #extension directives to enable/disable * the named extension. */ static GLboolean pp_ext_set(pp_ext *self, const char *name, GLboolean enable) { if (_mesa_strcmp (name, "GL_ARB_draw_buffers") == 0) self->ARB_draw_buffers = enable; else if (_mesa_strcmp (name, "GL_ARB_texture_rectangle") == 0) self->ARB_texture_rectangle = enable; else return GL_FALSE; return GL_TRUE; } /** * Called in response to #pragma. For example, "#pragma debug(on)" would * call this function as pp_pragma("debug", "on"). * \return GL_TRUE if pragma is valid, GL_FALSE if invalid */ static GLboolean pp_pragma(struct gl_sl_pragmas *pragmas, const char *pragma, const char *param) { #if 0 printf("#pragma %s %s\n", pragma, param); #endif if (_mesa_strcmp(pragma, "optimize") == 0) { if (!param) return GL_FALSE; /* missing required param */ if (_mesa_strcmp(param, "on") == 0) { if (!pragmas->IgnoreOptimize) pragmas->Optimize = GL_TRUE; } else if (_mesa_strcmp(param, "off") == 0) { if (!pragmas->IgnoreOptimize) pragmas->Optimize = GL_FALSE; } else { return GL_FALSE; /* invalid param */ } } else if (_mesa_strcmp(pragma, "debug") == 0) { if (!param) return GL_FALSE; /* missing required param */ if (_mesa_strcmp(param, "on") == 0) { if (!pragmas->IgnoreDebug) pragmas->Debug = GL_TRUE; } else if (_mesa_strcmp(param, "off") == 0) { if (!pragmas->IgnoreDebug) pragmas->Debug = GL_FALSE; } else { return GL_FALSE; /* invalid param */ } } /* all other pragmas are silently ignored */ return GL_TRUE; } /** * The state of preprocessor: current line, file and version number, list * of all defined macros and the #if/#endif context. */ typedef struct { GLint line; GLint file; GLint version; pp_symbols symbols; pp_ext ext; slang_info_log *elog; pp_cond_stack cond; } pp_state; static GLvoid pp_state_init (pp_state *self, slang_info_log *elog, const struct gl_extensions *extensions) { self->line = 0; self->file = 1; #if FEATURE_es2_glsl self->version = 100; #else self->version = 110; #endif pp_symbols_init (&self->symbols); pp_ext_init (&self->ext, extensions); self->elog = elog; /* Initialize condition stack and create the global context. */ self->cond.top = &self->cond.stack[CONDITION_STACK_SIZE - 1]; self->cond.top->current = GL_TRUE; self->cond.top->effective = GL_TRUE; self->cond.top->else_allowed = GL_FALSE; self->cond.top->endif_required = GL_FALSE; } static GLvoid pp_state_free (pp_state *self) { pp_symbols_free (&self->symbols); } #define IS_FIRST_ID_CHAR(x) (((x) >= 'a' && (x) <= 'z') || ((x) >= 'A' && (x) <= 'Z') || (x) == '_') #define IS_NEXT_ID_CHAR(x) (IS_FIRST_ID_CHAR(x) || ((x) >= '0' && (x) <= '9')) #define IS_WHITE(x) ((x) == ' ' || (x) == '\n') #define IS_NULL(x) ((x) == '\0') #define SKIP_WHITE(x) do { while (IS_WHITE(*(x))) (x)++; } while (GL_FALSE) typedef struct { slang_string *output; const char *input; pp_state *state; } expand_state; static GLboolean expand_defined (expand_state *e, slang_string *buffer) { GLboolean in_paren = GL_FALSE; const char *id; /* Parse the optional opening parenthesis. */ SKIP_WHITE(e->input); if (*e->input == '(') { e->input++; in_paren = GL_TRUE; SKIP_WHITE(e->input); } /* Parse operand. */ if (!IS_FIRST_ID_CHAR(*e->input)) { slang_info_log_error (e->state->elog, "preprocess error: identifier expected after operator 'defined'."); return GL_FALSE; } slang_string_reset (buffer); slang_string_pushc (buffer, *e->input++); while (IS_NEXT_ID_CHAR(*e->input)) slang_string_pushc (buffer, *e->input++); id = slang_string_cstr (buffer); /* Check if the operand is defined. Output 1 if it is defined, output 0 if not. */ if (pp_symbols_find (&e->state->symbols, id) == NULL) slang_string_pushs (e->output, " 0 ", 3); else slang_string_pushs (e->output, " 1 ", 3); /* Parse the closing parentehesis if the opening one was there. */ if (in_paren) { SKIP_WHITE(e->input); if (*e->input != ')') { slang_info_log_error (e->state->elog, "preprocess error: ')' expected."); return GL_FALSE; } e->input++; SKIP_WHITE(e->input); } return GL_TRUE; } static GLboolean expand (expand_state *, pp_symbols *); static GLboolean expand_symbol (expand_state *e, pp_symbol *symbol) { expand_state es; /* If the macro has some parameters, we need to parse them. */ if (symbol->parameters.count != 0) { GLuint i; /* Parse the opening parenthesis. */ SKIP_WHITE(e->input); if (*e->input != '(') { slang_info_log_error (e->state->elog, "preprocess error: '(' expected."); return GL_FALSE; } e->input++; SKIP_WHITE(e->input); /* Parse macro actual parameters. This can be anything, separated by a colon. */ for (i = 0; i < symbol->parameters.count; i++) { GLuint nested_paren_count = 0; /* track number of nested parentheses */ if (*e->input == ')') { slang_info_log_error (e->state->elog, "preprocess error: unexpected ')'."); return GL_FALSE; } /* Eat all characters up to the comma or closing parentheses. */ pp_symbol_reset (&symbol->parameters.symbols[i]); while (!IS_NULL(*e->input)) { /* Exit loop only when all nested parens have been eaten. */ if (nested_paren_count == 0 && (*e->input == ',' || *e->input == ')')) break; /* Actually count nested parens here. */ if (*e->input == '(') nested_paren_count++; else if (*e->input == ')') nested_paren_count--; slang_string_pushc (&symbol->parameters.symbols[i].replacement, *e->input++); } /* If it was not the last paremeter, skip the comma. Otherwise, skip the * closing parentheses. */ if (i + 1 == symbol->parameters.count) { /* This is the last paremeter - skip the closing parentheses. */ if (*e->input != ')') { slang_info_log_error (e->state->elog, "preprocess error: ')' expected."); return GL_FALSE; } e->input++; SKIP_WHITE(e->input); } else { /* Skip the separating comma. */ if (*e->input != ',') { slang_info_log_error (e->state->elog, "preprocess error: ',' expected."); return GL_FALSE; } e->input++; SKIP_WHITE(e->input); } } } /* Expand the macro. Use its parameters as a priority symbol list to expand * macro parameters correctly. */ es.output = e->output; es.input = slang_string_cstr (&symbol->replacement); es.state = e->state; slang_string_pushc (e->output, ' '); if (!expand (&es, &symbol->parameters)) return GL_FALSE; slang_string_pushc (e->output, ' '); return GL_TRUE; } /* * Function expand() expands source text from to . The expansion is made using * the list passed in parameter. It allows us to expand macro formal parameters with * actual parameters. The global list of symbols from pp state is used when doing a recursive * call of expand(). */ static GLboolean expand (expand_state *e, pp_symbols *symbols) { while (!IS_NULL(*e->input)) { if (IS_FIRST_ID_CHAR(*e->input)) { slang_string buffer; const char *id; /* Parse the identifier. */ slang_string_init (&buffer); slang_string_pushc (&buffer, *e->input++); while (IS_NEXT_ID_CHAR(*e->input)) slang_string_pushc (&buffer, *e->input++); id = slang_string_cstr (&buffer); /* Now check if the identifier is special in some way. The "defined" identifier is * actually an operator that we must handle here and expand it either to " 0 " or " 1 ". * The other identifiers start with "__" and we expand it to appropriate values * taken from the preprocessor state. */ if (_mesa_strcmp (id, "defined") == 0) { if (!expand_defined (e, &buffer)) return GL_FALSE; } else if (_mesa_strcmp (id, "__LINE__") == 0) { slang_string_pushc (e->output, ' '); slang_string_pushi (e->output, e->state->line); slang_string_pushc (e->output, ' '); } else if (_mesa_strcmp (id, "__FILE__") == 0) { slang_string_pushc (e->output, ' '); slang_string_pushi (e->output, e->state->file); slang_string_pushc (e->output, ' '); } else if (_mesa_strcmp (id, "__VERSION__") == 0) { slang_string_pushc (e->output, ' '); slang_string_pushi (e->output, e->state->version); slang_string_pushc (e->output, ' '); } #if FEATURE_es2_glsl else if (_mesa_strcmp (id, "GL_ES") == 0 || _mesa_strcmp (id, "GL_FRAGMENT_PRECISION_HIGH") == 0) { slang_string_pushc (e->output, ' '); slang_string_pushi (e->output, '1'); slang_string_pushc (e->output, ' '); } #endif else { pp_symbol *symbol; /* The list of symbols from take precedence over the list from . * Note that in some cases this is the same list so avoid double look-up. */ symbol = pp_symbols_find (symbols, id); if (symbol == NULL && symbols != &e->state->symbols) symbol = pp_symbols_find (&e->state->symbols, id); /* If the symbol was found, recursively expand its definition. */ if (symbol != NULL) { if (!expand_symbol (e, symbol)) { slang_string_free (&buffer); return GL_FALSE; } } else { slang_string_push (e->output, &buffer); } } slang_string_free (&buffer); } else if (IS_WHITE(*e->input)) { slang_string_pushc (e->output, *e->input++); } else { while (!IS_WHITE(*e->input) && !IS_NULL(*e->input) && !IS_FIRST_ID_CHAR(*e->input)) slang_string_pushc (e->output, *e->input++); } } return GL_TRUE; } static GLboolean parse_if (slang_string *output, const byte *prod, GLuint *pi, GLint *result, pp_state *state, grammar eid) { const char *text; GLuint len; text = (const char *) (&prod[*pi]); len = _mesa_strlen (text); if (state->cond.top->effective) { slang_string expr; GLuint count; GLint results[2]; expand_state es; /* Expand the expression. */ slang_string_init (&expr); es.output = &expr; es.input = text; es.state = state; if (!expand (&es, &state->symbols)) return GL_FALSE; /* Execute the expression. */ count = execute_expressions (output, eid, (const byte *) (slang_string_cstr (&expr)), results, state->elog); slang_string_free (&expr); if (count != 1) return GL_FALSE; *result = results[0]; } else { /* The directive is dead. */ *result = 0; } *pi += len + 1; return GL_TRUE; } #define ESCAPE_TOKEN 0 #define TOKEN_END 0 #define TOKEN_DEFINE 1 #define TOKEN_UNDEF 2 #define TOKEN_IF 3 #define TOKEN_ELSE 4 #define TOKEN_ELIF 5 #define TOKEN_ENDIF 6 #define TOKEN_ERROR 7 #define TOKEN_PRAGMA 8 #define TOKEN_EXTENSION 9 #define TOKEN_LINE 10 #define PARAM_END 0 #define PARAM_PARAMETER 1 #define BEHAVIOR_REQUIRE 1 #define BEHAVIOR_ENABLE 2 #define BEHAVIOR_WARN 3 #define BEHAVIOR_DISABLE 4 #define PRAGMA_NO_PARAM 0 #define PRAGMA_PARAM 1 static GLboolean preprocess_source (slang_string *output, const char *source, grammar pid, grammar eid, slang_info_log *elog, const struct gl_extensions *extensions, struct gl_sl_pragmas *pragmas) { static const char *predefined[] = { "__FILE__", "__LINE__", "__VERSION__", #if FEATURE_es2_glsl "GL_ES", "GL_FRAGMENT_PRECISION_HIGH", #endif NULL }; byte *prod; GLuint size, i; pp_state state; if (!grammar_fast_check (pid, (const byte *) (source), &prod, &size, 65536)) { grammar_error_to_log (elog); return GL_FALSE; } pp_state_init (&state, elog, extensions); /* add the predefined symbols to the symbol table */ for (i = 0; predefined[i]; i++) { pp_symbol *symbol = NULL; symbol = pp_symbols_push(&state.symbols); assert(symbol); slang_string_pushs(&symbol->name, predefined[i], _mesa_strlen(predefined[i])); } i = 0; while (i < size) { if (prod[i] != ESCAPE_TOKEN) { if (state.cond.top->effective) { slang_string input; expand_state es; /* Eat only one line of source code to expand it. * FIXME: This approach has one drawback. If a macro with parameters spans across * multiple lines, the preprocessor will raise an error. */ slang_string_init (&input); while (prod[i] != '\0' && prod[i] != '\n') slang_string_pushc (&input, prod[i++]); if (prod[i] != '\0') slang_string_pushc (&input, prod[i++]); /* Increment line number. */ state.line++; es.output = output; es.input = slang_string_cstr (&input); es.state = &state; if (!expand (&es, &state.symbols)) goto error; slang_string_free (&input); } else { /* Condition stack is disabled - keep track on line numbers and output only newlines. */ if (prod[i] == '\n') { state.line++; /*pp_annotate (output, "%c", prod[i]);*/ } else { /*pp_annotate (output, "%c", prod[i]);*/ } i++; } } else { const char *id; GLuint idlen; GLubyte token; i++; token = prod[i++]; switch (token) { case TOKEN_END: /* End of source string. * Check if all #ifs have been terminated by matching #endifs. * On condition stack there should be only the global condition context. */ if (state.cond.top->endif_required) { slang_info_log_error (elog, "end of source without matching #endif."); return GL_FALSE; } break; case TOKEN_DEFINE: { pp_symbol *symbol = NULL; slang_string replacement; expand_state es; /* Parse macro name. */ id = (const char *) (&prod[i]); idlen = _mesa_strlen (id); if (state.cond.top->effective) { pp_annotate (output, "// #define %s(", id); /* If the symbol is already defined, override it. */ symbol = pp_symbols_find (&state.symbols, id); if (symbol == NULL) { symbol = pp_symbols_push (&state.symbols); if (symbol == NULL) goto error; slang_string_pushs (&symbol->name, id, idlen); } else { pp_symbol_reset (symbol); } } i += idlen + 1; /* Parse optional macro parameters. */ while (prod[i++] != PARAM_END) { pp_symbol *param; id = (const char *) (&prod[i]); idlen = _mesa_strlen (id); if (state.cond.top->effective) { pp_annotate (output, "%s, ", id); param = pp_symbols_push (&symbol->parameters); if (param == NULL) goto error; slang_string_pushs (¶m->name, id, idlen); } i += idlen + 1; } /* Parse macro replacement. */ id = (const char *) (&prod[i]); idlen = _mesa_strlen (id); if (state.cond.top->effective) { pp_annotate (output, ") %s", id); } slang_string_init(&replacement); slang_string_pushs(&replacement, id, idlen); i += idlen + 1; /* Expand macro replacement. */ es.output = &symbol->replacement; es.input = slang_string_cstr(&replacement); es.state = &state; if (!expand(&es, &state.symbols)) { slang_string_free(&replacement); goto error; } slang_string_free(&replacement); } break; case TOKEN_UNDEF: id = (const char *) (&prod[i]); i += _mesa_strlen (id) + 1; if (state.cond.top->effective) { pp_symbol *symbol; pp_annotate (output, "// #undef %s", id); /* Try to find symbol with given name and remove it. */ symbol = pp_symbols_find (&state.symbols, id); if (symbol != NULL) if (!pp_symbols_erase (&state.symbols, symbol)) goto error; } break; case TOKEN_IF: { GLint result; /* Parse #if expression end execute it. */ pp_annotate (output, "// #if "); if (!parse_if (output, prod, &i, &result, &state, eid)) goto error; /* Push new condition on the stack. */ if (!pp_cond_stack_push (&state.cond, state.elog)) goto error; state.cond.top->current = result ? GL_TRUE : GL_FALSE; state.cond.top->else_allowed = GL_TRUE; state.cond.top->endif_required = GL_TRUE; pp_cond_stack_reevaluate (&state.cond); } break; case TOKEN_ELSE: /* Check if #else is alloved here. */ if (!state.cond.top->else_allowed) { slang_info_log_error (elog, "#else without matching #if."); goto error; } /* Negate current condition and reevaluate it. */ state.cond.top->current = !state.cond.top->current; state.cond.top->else_allowed = GL_FALSE; pp_cond_stack_reevaluate (&state.cond); if (state.cond.top->effective) pp_annotate (output, "// #else"); break; case TOKEN_ELIF: /* Check if #elif is alloved here. */ if (!state.cond.top->else_allowed) { slang_info_log_error (elog, "#elif without matching #if."); goto error; } /* Negate current condition and reevaluate it. */ state.cond.top->current = !state.cond.top->current; pp_cond_stack_reevaluate (&state.cond); if (state.cond.top->effective) pp_annotate (output, "// #elif "); { GLint result; /* Parse #elif expression end execute it. */ if (!parse_if (output, prod, &i, &result, &state, eid)) goto error; /* Update current condition and reevaluate it. */ state.cond.top->current = result ? GL_TRUE : GL_FALSE; pp_cond_stack_reevaluate (&state.cond); } break; case TOKEN_ENDIF: /* Check if #endif is alloved here. */ if (!state.cond.top->endif_required) { slang_info_log_error (elog, "#endif without matching #if."); goto error; } /* Pop the condition off the stack. */ state.cond.top++; if (state.cond.top->effective) pp_annotate (output, "// #endif"); break; case TOKEN_EXTENSION: /* Parse the extension name. */ id = (const char *) (&prod[i]); i += _mesa_strlen (id) + 1; if (state.cond.top->effective) pp_annotate (output, "// #extension %s: ", id); /* Parse and apply extension behavior. */ if (state.cond.top->effective) { switch (prod[i++]) { case BEHAVIOR_REQUIRE: pp_annotate (output, "require"); if (!pp_ext_set (&state.ext, id, GL_TRUE)) { if (_mesa_strcmp (id, "all") == 0) { slang_info_log_error (elog, "require: bad behavior for #extension all."); goto error; } else { slang_info_log_error (elog, "%s: required extension is not supported.", id); goto error; } } break; case BEHAVIOR_ENABLE: pp_annotate (output, "enable"); if (!pp_ext_set (&state.ext, id, GL_TRUE)) { if (_mesa_strcmp (id, "all") == 0) { slang_info_log_error (elog, "enable: bad behavior for #extension all."); goto error; } else { slang_info_log_warning (elog, "%s: enabled extension is not supported.", id); } } break; case BEHAVIOR_WARN: pp_annotate (output, "warn"); if (!pp_ext_set (&state.ext, id, GL_TRUE)) { if (_mesa_strcmp (id, "all") != 0) { slang_info_log_warning (elog, "%s: enabled extension is not supported.", id); } } break; case BEHAVIOR_DISABLE: pp_annotate (output, "disable"); if (!pp_ext_set (&state.ext, id, GL_FALSE)) { if (_mesa_strcmp (id, "all") == 0) { pp_ext_disable_all (&state.ext); } else { slang_info_log_warning (elog, "%s: disabled extension is not supported.", id); } } break; default: assert (0); } } break; case TOKEN_PRAGMA: { GLint have_param; const char *pragma, *param; pragma = (const char *) (&prod[i]); i += _mesa_strlen(pragma) + 1; have_param = (prod[i++] == PRAGMA_PARAM); if (have_param) { param = (const char *) (&prod[i]); i += _mesa_strlen(param) + 1; } else { param = NULL; } pp_pragma(pragmas, pragma, param); } break; case TOKEN_LINE: id = (const char *) (&prod[i]); i += _mesa_strlen (id) + 1; if (state.cond.top->effective) { slang_string buffer; GLuint count; GLint results[2]; expand_state es; slang_string_init (&buffer); state.line++; es.output = &buffer; es.input = id; es.state = &state; if (!expand (&es, &state.symbols)) goto error; pp_annotate (output, "// #line "); count = execute_expressions (output, eid, (const byte *) (slang_string_cstr (&buffer)), results, state.elog); slang_string_free (&buffer); if (count == 0) goto error; state.line = results[0] - 1; if (count == 2) state.file = results[1]; } break; } } } /* Check for missing #endifs. */ if (state.cond.top->endif_required) { slang_info_log_error (elog, "#endif expected but end of source found."); goto error; } grammar_alloc_free(prod); pp_state_free (&state); return GL_TRUE; error: grammar_alloc_free(prod); pp_state_free (&state); return GL_FALSE; } /** * Run preprocessor on source code. * \param extensions indicates which GL extensions are enabled * \param output the post-process results * \param input the input text * \param elog log to record warnings, errors * \param extensions out extension settings * \param pragmas in/out #pragma settings * \return GL_TRUE for success, GL_FALSE for error */ GLboolean _slang_preprocess_directives(slang_string *output, const char *input, slang_info_log *elog, const struct gl_extensions *extensions, struct gl_sl_pragmas *pragmas) { grammar pid, eid; GLboolean success; pid = grammar_load_from_text ((const byte *) (slang_pp_directives_syn)); if (pid == 0) { grammar_error_to_log (elog); return GL_FALSE; } eid = grammar_load_from_text ((const byte *) (slang_pp_expression_syn)); if (eid == 0) { grammar_error_to_log (elog); grammar_destroy (pid); return GL_FALSE; } success = preprocess_source (output, input, pid, eid, elog, extensions, pragmas); grammar_destroy (eid); grammar_destroy (pid); if (0) { _mesa_printf("Post-processed shader:\n"); _mesa_printf("%s", output->data); _mesa_printf("----------------------\n"); } return success; }