aboutsummaryrefslogtreecommitdiff
path: root/mesalib/src/mesa/shader/slang/slang_preprocess.c
diff options
context:
space:
mode:
Diffstat (limited to 'mesalib/src/mesa/shader/slang/slang_preprocess.c')
-rw-r--r--mesalib/src/mesa/shader/slang/slang_preprocess.c1406
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 (&param->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;
+}
+