diff options
author | marha <marha@users.sourceforge.net> | 2009-10-09 06:31:44 +0000 |
---|---|---|
committer | marha <marha@users.sourceforge.net> | 2009-10-09 06:31:44 +0000 |
commit | 06456f5db88b434c3634ede42bdbfdce78fc4249 (patch) | |
tree | 97f5174e2d3da40faee7f2ad8858233da3d0166e /mesalib/src/mesa/shader/slang/slang_preprocess.c | |
parent | 7b230a3fe2d6c83488d9eec43067fe8ba8ac081b (diff) | |
parent | a0c4815433ccd57322f4f7703ca35e9ccfa59250 (diff) | |
download | vcxsrv-06456f5db88b434c3634ede42bdbfdce78fc4249.tar.gz vcxsrv-06456f5db88b434c3634ede42bdbfdce78fc4249.tar.bz2 vcxsrv-06456f5db88b434c3634ede42bdbfdce78fc4249.zip |
svn merge ^/branches/released . --username marha
Diffstat (limited to 'mesalib/src/mesa/shader/slang/slang_preprocess.c')
-rw-r--r-- | mesalib/src/mesa/shader/slang/slang_preprocess.c | 1406 |
1 files changed, 1406 insertions, 0 deletions
diff --git a/mesalib/src/mesa/shader/slang/slang_preprocess.c b/mesalib/src/mesa/shader/slang/slang_preprocess.c new file mode 100644 index 000000000..e9a24cc00 --- /dev/null +++ b/mesalib/src/mesa/shader/slang/slang_preprocess.c @@ -0,0 +1,1406 @@ +/* + * 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 <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 + +#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; + + /* 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) { + slang_string replacement; + expand_state es; + + pp_annotate (output, ") %s", id); + + slang_string_init(&replacement); + slang_string_pushs(&replacement, id, idlen); + + /* 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); + } + 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_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; +} + + +/** + * Remove the continuation characters from the input string. + * This is the very first step in preprocessing and is effective + * even inside comment blocks. + * If there is a whitespace between a backslash and a newline, + * this is not considered as a line continuation. + * \return GL_TRUE for success, GL_FALSE otherwise. + */ +static GLboolean +_slang_preprocess_backslashes(slang_string *output, + const char *input) +{ + while (*input) { + if (input[0] == '\\') { + /* If a newline follows, eat the backslash and the newline. */ + if (input[1] == '\r') { + if (input[2] == '\n') { + input += 3; + } else { + input += 2; + } + } else if (input[1] == '\n') { + if (input[2] == '\r') { + input += 3; + } else { + input += 2; + } + } else { + /* Leave the backslash alone. */ + slang_string_pushc(output, *input++); + } + } else { + slang_string_pushc(output, *input++); + } + } + return GL_TRUE; +} + + +/** + * 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; + slang_string without_backslashes; + + 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; + } + + slang_string_init(&without_backslashes); + success = _slang_preprocess_backslashes(&without_backslashes, input); + + if (0) { + _mesa_printf("Pre-processed shader:\n"); + _mesa_printf("%s", slang_string_cstr(&without_backslashes)); + _mesa_printf("----------------------\n"); + } + + if (success) { + success = preprocess_source(output, + slang_string_cstr(&without_backslashes), + pid, + eid, + elog, + extensions, + pragmas); + } + + slang_string_free(&without_backslashes); + grammar_destroy (eid); + grammar_destroy (pid); + + if (0) { + _mesa_printf("Post-processed shader:\n"); + _mesa_printf("%s", slang_string_cstr(output)); + _mesa_printf("----------------------\n"); + } + + return success; +} + |