| /* |
| * Mesa 3-D graphics library |
| * Version: 6.5.2 |
| * |
| * Copyright (C) 2005-2006 Brian Paul 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); |
| 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 MESA_shader_debug; /* GL_MESA_shader_debug enable */ |
| GLboolean ARB_texture_rectangle; /* GL_ARB_texture_rectangle enable */ |
| } pp_ext; |
| |
| /* |
| * Disable all extensions. Called at startup and on #extension all: disable. |
| */ |
| static GLvoid |
| pp_ext_disable_all (pp_ext *self) |
| { |
| self->MESA_shader_debug = GL_FALSE; |
| } |
| |
| static GLvoid |
| pp_ext_init (pp_ext *self) |
| { |
| pp_ext_disable_all (self); |
| self->ARB_texture_rectangle = GL_TRUE; |
| /* Other initialization code goes here. */ |
| } |
| |
| static GLboolean |
| pp_ext_set (pp_ext *self, const char *name, GLboolean enable) |
| { |
| if (_mesa_strcmp (name, "MESA_shader_debug") == 0) |
| self->MESA_shader_debug = enable; |
| else if (_mesa_strcmp (name, "GL_ARB_texture_rectangle") == 0) |
| self->ARB_texture_rectangle = enable; |
| /* Next extension name tests go here. */ |
| else |
| return GL_FALSE; |
| 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) |
| { |
| 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); |
| 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. |
| * TODO: What about nested/grouped parameters by parenthesis? */ |
| for (i = 0; i < symbol->parameters.count; i++) { |
| 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) && *e->input != ',' && *e->input != ')') |
| 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 <input> to <output>. The expansion is made using |
| * the list passed in <symbols> 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 <symbols> take precedence over the list from <state>. |
| * 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 |
| |
| static GLboolean |
| preprocess_source (slang_string *output, const char *source, grammar pid, grammar eid, |
| slang_info_log *elog) |
| { |
| 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); |
| |
| /* 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; |
| |
| i++; |
| switch (prod[i++]) { |
| |
| 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; |
| |
| /* 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) { |
| if (state.cond.top->effective) { |
| pp_symbol *param; |
| |
| id = (const char *) (&prod[i]); |
| idlen = _mesa_strlen (id); |
| 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_pushs (&symbol->replacement, id, idlen); |
| } |
| i += idlen + 1; |
| } |
| 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_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; |
| } |
| |
| GLboolean |
| _slang_preprocess_directives (slang_string *output, const char *input, slang_info_log *elog) |
| { |
| 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); |
| grammar_destroy (eid); |
| grammar_destroy (pid); |
| return success; |
| } |
| |