/* * HLSL preprocessor * * Copyright 2020 Zebediah Figura for CodeWeavers * * This library is free software; you can redistribute it and/or * modify it under the terms of the GNU Lesser General Public * License as published by the Free Software Foundation; either * version 2.1 of the License, or (at your option) any later version. * * This library is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU * Lesser General Public License for more details. * * You should have received a copy of the GNU Lesser General Public * License along with this library; if not, write to the Free Software * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301, USA */ %code requires { #include "vkd3d_shader_private.h" #include "preproc.h" #include #include #define PREPROC_YYLTYPE struct vkd3d_shader_location struct parse_arg_names { char **args; size_t count; }; } %code provides { int preproc_yylex(PREPROC_YYSTYPE *yylval_param, PREPROC_YYLTYPE *yylloc_param, void *scanner); } %code { #define YYLLOC_DEFAULT(cur, rhs, n) (cur) = YYRHSLOC(rhs, !!n) #ifndef S_ISREG # define S_ISREG(m) (((m) & S_IFMT) == S_IFREG) #endif static void preproc_error(struct preproc_ctx *ctx, const struct vkd3d_shader_location *loc, enum vkd3d_shader_error error, const char *format, ...) { va_list args; va_start(args, format); vkd3d_shader_verror(ctx->message_context, loc, error, format, args); va_end(args); ctx->error = true; } void preproc_warning(struct preproc_ctx *ctx, const struct vkd3d_shader_location *loc, enum vkd3d_shader_error error, const char *format, ...) { va_list args; va_start(args, format); vkd3d_shader_vwarning(ctx->message_context, loc, error, format, args); va_end(args); } static void yyerror(const YYLTYPE *loc, void *scanner, struct preproc_ctx *ctx, const char *string) { preproc_error(ctx, loc, VKD3D_SHADER_ERROR_PP_INVALID_SYNTAX, "%s", string); } struct preproc_macro *preproc_find_macro(struct preproc_ctx *ctx, const char *name) { struct rb_entry *entry; if ((entry = rb_get(&ctx->macros, name))) return RB_ENTRY_VALUE(entry, struct preproc_macro, entry); return NULL; } bool preproc_add_macro(struct preproc_ctx *ctx, const struct vkd3d_shader_location *loc, char *name, char **arg_names, size_t arg_count, const struct vkd3d_shader_location *body_loc, struct vkd3d_string_buffer *body) { struct preproc_macro *macro; unsigned int i; int ret; if ((macro = preproc_find_macro(ctx, name))) { preproc_warning(ctx, loc, VKD3D_SHADER_WARNING_PP_ALREADY_DEFINED, "Redefinition of %s.", name); rb_remove(&ctx->macros, ¯o->entry); preproc_free_macro(macro); } TRACE("Defining new macro %s with %zu arguments.\n", debugstr_a(name), arg_count); if (!(macro = vkd3d_malloc(sizeof(*macro)))) return false; macro->name = name; macro->arg_names = arg_names; macro->arg_count = arg_count; macro->arg_values = NULL; if (arg_count && !(macro->arg_values = vkd3d_calloc(arg_count, sizeof(*macro->arg_values)))) { vkd3d_free(macro); return false; } for (i = 0; i < arg_count; ++i) vkd3d_string_buffer_init(¯o->arg_values[i].text); macro->body.text = *body; macro->body.location = *body_loc; ret = rb_put(&ctx->macros, name, ¯o->entry); assert(!ret); return true; } void preproc_free_macro(struct preproc_macro *macro) { unsigned int i; vkd3d_free(macro->name); for (i = 0; i < macro->arg_count; ++i) { vkd3d_string_buffer_cleanup(¯o->arg_values[i].text); vkd3d_free(macro->arg_names[i]); } vkd3d_free(macro->arg_names); vkd3d_free(macro->arg_values); vkd3d_string_buffer_cleanup(¯o->body.text); vkd3d_free(macro); } static bool preproc_was_writing(struct preproc_ctx *ctx) { const struct preproc_file *file = preproc_get_top_file(ctx); /* This applies across files, since we can't #include anyway if we weren't * writing. */ if (file->if_count < 2) return true; return file->if_stack[file->if_count - 2].current_true; } static bool preproc_push_if(struct preproc_ctx *ctx, bool condition) { struct preproc_file *file = preproc_get_top_file(ctx); struct preproc_if_state *state; if (!vkd3d_array_reserve((void **)&file->if_stack, &file->if_stack_size, file->if_count + 1, sizeof(*file->if_stack))) return false; state = &file->if_stack[file->if_count++]; state->current_true = condition && preproc_was_writing(ctx); state->seen_true = condition; state->seen_else = false; return true; } static int char_to_int(char c) { if ('0' <= c && c <= '9') return c - '0'; if ('A' <= c && c <= 'F') return c - 'A' + 10; if ('a' <= c && c <= 'f') return c - 'a' + 10; return -1; } static uint32_t preproc_parse_integer(const char *s) { uint32_t base = 10, ret = 0; int digit; if (*s == '0') { base = 8; ++s; if (*s == 'x' || *s == 'X') { base = 16; ++s; } } while ((digit = char_to_int(*s++)) >= 0) ret = ret * base + (uint32_t)digit; return ret; } static int default_open_include(const char *filename, bool local, const char *parent_data, void *context, struct vkd3d_shader_code *out) { uint8_t *data, *new_data; size_t size = 4096; struct stat st; size_t pos = 0; size_t ret; FILE *f; if (!(f = fopen(filename, "rb"))) { ERR("Unable to open %s for reading.\n", debugstr_a(filename)); return VKD3D_ERROR; } if (fstat(fileno(f), &st) == -1) { ERR("Could not stat file %s.\n", debugstr_a(filename)); fclose(f); return VKD3D_ERROR; } if (S_ISREG(st.st_mode)) size = st.st_size; if (!(data = vkd3d_malloc(size))) { fclose(f); return VKD3D_ERROR_OUT_OF_MEMORY; } for (;;) { if (pos >= size) { if (size > SIZE_MAX / 2 || !(new_data = vkd3d_realloc(data, size * 2))) { vkd3d_free(data); fclose(f); return VKD3D_ERROR_OUT_OF_MEMORY; } data = new_data; size *= 2; } if (!(ret = fread(&data[pos], 1, size - pos, f))) break; pos += ret; } if (!feof(f)) { vkd3d_free(data); return VKD3D_ERROR; } fclose(f); out->code = data; out->size = pos; return VKD3D_OK; } static void default_close_include(const struct vkd3d_shader_code *code, void *context) { vkd3d_free((void *)code->code); } void preproc_close_include(struct preproc_ctx *ctx, const struct vkd3d_shader_code *code) { PFN_vkd3d_shader_close_include close_include = ctx->preprocess_info->pfn_close_include; if (!close_include) close_include = default_close_include; close_include(code, ctx->preprocess_info->include_context); } static const void *get_parent_data(struct preproc_ctx *ctx) { if (ctx->file_count == 1) return NULL; return preproc_get_top_file(ctx)->code.code; } static void free_parse_arg_names(struct parse_arg_names *args) { unsigned int i; for (i = 0; i < args->count; ++i) vkd3d_free(args->args[i]); vkd3d_free(args->args); } } %define api.prefix {preproc_yy} %define api.pure full %define parse.error verbose %expect 0 %locations %lex-param {yyscan_t scanner} %parse-param {void *scanner} %parse-param {struct preproc_ctx *ctx} %union { char *string; const char *const_string; uint32_t integer; struct vkd3d_string_buffer string_buffer; struct parse_arg_names arg_names; } %token T_HASHSTRING %token T_IDENTIFIER %token T_IDENTIFIER_PAREN %token T_INTEGER %token T_STRING %token T_TEXT %token T_NEWLINE %token T_DEFINE "#define" %token T_ERROR "#error" %token T_ELIF "#elif" %token T_ELSE "#else" %token T_ENDIF "#endif" %token T_IF "#if" %token T_IFDEF "#ifdef" %token T_IFNDEF "#ifndef" %token T_INCLUDE "#include" %token T_LINE "#line" %token T_PRAGMA "#pragma" %token T_UNDEF "#undef" %token T_CONCAT "##" %token T_LE "<=" %token T_GE ">=" %token T_EQ "==" %token T_NE "!=" %token T_AND "&&" %token T_OR "||" %token T_DEFINED "defined" %type primary_expr %type unary_expr %type mul_expr %type add_expr %type ineq_expr %type eq_expr %type bitand_expr %type bitxor_expr %type bitor_expr %type logicand_expr %type logicor_expr %type expr %type body_token %type body_token_const %type body_text %type identifier_list %% shader_text : %empty | shader_text directive { vkd3d_string_buffer_printf(&ctx->buffer, "\n"); } identifier_list : T_IDENTIFIER { if (!($$.args = vkd3d_malloc(sizeof(*$$.args)))) YYABORT; $$.args[0] = $1; $$.count = 1; } | identifier_list ',' T_IDENTIFIER { char **new_array; if (!(new_array = vkd3d_realloc($1.args, ($1.count + 1) * sizeof(*$$.args)))) { free_parse_arg_names(&$1); YYABORT; } $$.args = new_array; $$.count = $1.count + 1; $$.args[$1.count] = $3; } body_text : %empty { vkd3d_string_buffer_init(&$$); } | body_text body_token { if (vkd3d_string_buffer_printf(&$$, "%s ", $2) < 0) { vkd3d_free($2); YYABORT; } vkd3d_free($2); } | body_text body_token_const { if (vkd3d_string_buffer_printf(&$$, "%s ", $2) < 0) YYABORT; } body_token : T_HASHSTRING | T_IDENTIFIER | T_IDENTIFIER_PAREN | T_INTEGER | T_TEXT body_token_const : '(' { $$ = "("; } | ')' { $$ = ")"; } | '[' { $$ = "["; } | ']' { $$ = "]"; } | '{' { $$ = "{"; } | '}' { $$ = "}"; } | ',' { $$ = ","; } | '+' { $$ = "+"; } | '-' { $$ = "-"; } | '!' { $$ = "!"; } | '*' { $$ = "*"; } | '/' { $$ = "/"; } | '<' { $$ = "<"; } | '>' { $$ = ">"; } | '&' { $$ = "&"; } | '|' { $$ = "|"; } | '^' { $$ = "^"; } | '?' { $$ = "?"; } | ':' { $$ = ":"; } | T_CONCAT { $$ = "##"; } | T_LE { $$ = "<="; } | T_GE { $$ = ">="; } | T_EQ { $$ = "=="; } | T_NE { $$ = "!="; } | T_AND { $$ = "&&"; } | T_OR { $$ = "||"; } | T_DEFINED { $$ = "defined"; } directive : T_DEFINE T_IDENTIFIER body_text T_NEWLINE { if (!preproc_add_macro(ctx, &@$, $2, NULL, 0, &@3, &$3)) { vkd3d_free($2); vkd3d_string_buffer_cleanup(&$3); YYABORT; } } | T_DEFINE T_IDENTIFIER_PAREN '(' identifier_list ')' body_text T_NEWLINE { if (!preproc_add_macro(ctx, &@6, $2, $4.args, $4.count, &@6, &$6)) { vkd3d_free($2); free_parse_arg_names(&$4); vkd3d_string_buffer_cleanup(&$6); YYABORT; } } | T_UNDEF T_IDENTIFIER T_NEWLINE { struct preproc_macro *macro; if ((macro = preproc_find_macro(ctx, $2))) { TRACE("Removing macro definition %s.\n", debugstr_a($2)); rb_remove(&ctx->macros, ¯o->entry); preproc_free_macro(macro); } vkd3d_free($2); } | T_IF expr T_NEWLINE { if (!preproc_push_if(ctx, !!$2)) YYABORT; } | T_IFDEF T_IDENTIFIER T_NEWLINE { preproc_push_if(ctx, !!preproc_find_macro(ctx, $2)); vkd3d_free($2); } | T_IFNDEF T_IDENTIFIER T_NEWLINE { preproc_push_if(ctx, !preproc_find_macro(ctx, $2)); vkd3d_free($2); } | T_ELIF expr T_NEWLINE { const struct preproc_file *file = preproc_get_top_file(ctx); if (file->if_count) { struct preproc_if_state *state = &file->if_stack[file->if_count - 1]; if (state->seen_else) { preproc_warning(ctx, &@$, VKD3D_SHADER_WARNING_PP_INVALID_DIRECTIVE, "Ignoring #elif after #else."); } else { state->current_true = $2 && !state->seen_true && preproc_was_writing(ctx); state->seen_true = $2 || state->seen_true; } } else { preproc_warning(ctx, &@$, VKD3D_SHADER_WARNING_PP_INVALID_DIRECTIVE, "Ignoring #elif without prior #if."); } } | T_ELSE T_NEWLINE { const struct preproc_file *file = preproc_get_top_file(ctx); if (file->if_count) { struct preproc_if_state *state = &file->if_stack[file->if_count - 1]; if (state->seen_else) { preproc_warning(ctx, &@$, VKD3D_SHADER_WARNING_PP_INVALID_DIRECTIVE, "Ignoring #else after #else."); } else { state->current_true = !state->seen_true && preproc_was_writing(ctx); state->seen_else = true; } } else { preproc_warning(ctx, &@$, VKD3D_SHADER_WARNING_PP_INVALID_DIRECTIVE, "Ignoring #else without prior #if."); } } | T_ENDIF T_NEWLINE { struct preproc_file *file = preproc_get_top_file(ctx); if (file->if_count) --file->if_count; else preproc_warning(ctx, &@$, VKD3D_SHADER_WARNING_PP_INVALID_DIRECTIVE, "Ignoring #endif without prior #if."); } | T_ERROR T_NEWLINE { preproc_error(ctx, &@$, VKD3D_SHADER_ERROR_PP_ERROR_DIRECTIVE, "Error directive."); } | T_ERROR T_STRING T_NEWLINE { preproc_error(ctx, &@$, VKD3D_SHADER_ERROR_PP_ERROR_DIRECTIVE, "Error directive: %s", $2); vkd3d_free($2); } | T_INCLUDE T_STRING T_NEWLINE { PFN_vkd3d_shader_open_include open_include = ctx->preprocess_info->pfn_open_include; struct vkd3d_shader_code code; char *filename; int result; if (!(filename = vkd3d_malloc(strlen($2) - 1))) YYABORT; if (!open_include) open_include = default_open_include; memcpy(filename, $2 + 1, strlen($2) - 2); filename[strlen($2) - 2] = 0; if (!(result = open_include(filename, $2[0] == '"', get_parent_data(ctx), ctx->preprocess_info->include_context, &code))) { if (!preproc_push_include(ctx, filename, &code)) { preproc_close_include(ctx, &code); vkd3d_free(filename); } } else { preproc_error(ctx, &@$, VKD3D_SHADER_ERROR_PP_INCLUDE_FAILED, "Failed to open %s.", $2); vkd3d_free(filename); } vkd3d_free($2); } | T_LINE T_INTEGER T_NEWLINE { FIXME("#line directive.\n"); vkd3d_free($2); } | T_LINE T_INTEGER T_STRING T_NEWLINE { FIXME("#line directive.\n"); vkd3d_free($2); vkd3d_free($3); } primary_expr : T_INTEGER { $$ = preproc_parse_integer($1); vkd3d_free($1); } | T_IDENTIFIER { $$ = 0; vkd3d_free($1); } | T_DEFINED T_IDENTIFIER { $$ = !!preproc_find_macro(ctx, $2); vkd3d_free($2); } | T_DEFINED '(' T_IDENTIFIER ')' { $$ = !!preproc_find_macro(ctx, $3); vkd3d_free($3); } | '(' expr ')' { $$ = $2; } unary_expr : primary_expr | '+' unary_expr { $$ = $2; } | '-' unary_expr { $$ = -$2; } | '!' unary_expr { $$ = !$2; } mul_expr : unary_expr | mul_expr '*' unary_expr { $$ = $1 * $3; } | mul_expr '/' unary_expr { if (!$3) { preproc_warning(ctx, &@3, VKD3D_SHADER_WARNING_PP_DIV_BY_ZERO, "Division by zero."); $3 = 1; } $$ = $1 / $3; } add_expr : mul_expr | add_expr '+' mul_expr { $$ = $1 + $3; } | add_expr '-' mul_expr { $$ = $1 - $3; } ineq_expr : add_expr | ineq_expr '<' add_expr { $$ = $1 < $3; } | ineq_expr '>' add_expr { $$ = $1 > $3; } | ineq_expr T_LE add_expr { $$ = $1 <= $3; } | ineq_expr T_GE add_expr { $$ = $1 >= $3; } eq_expr : ineq_expr | eq_expr T_EQ ineq_expr { $$ = $1 == $3; } | eq_expr T_NE ineq_expr { $$ = $1 != $3; } bitand_expr : eq_expr | bitand_expr '&' eq_expr { $$ = $1 & $3; } bitxor_expr : bitand_expr | bitxor_expr '^' bitand_expr { $$ = $1 ^ $3; } bitor_expr : bitxor_expr | bitor_expr '|' bitxor_expr { $$ = $1 | $3; } logicand_expr : bitor_expr | logicand_expr T_AND bitor_expr { $$ = $1 && $3; } logicor_expr : logicand_expr | logicor_expr T_OR logicand_expr { $$ = $1 || $3; } expr : logicor_expr | expr '?' logicor_expr ':' logicor_expr { $$ = $1 ? $3 : $5; }