/* * 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 } %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) 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; } static bool preproc_add_macro(struct preproc_ctx *ctx, const struct vkd3d_shader_location *loc, char *name, const struct vkd3d_shader_location *body_loc, struct vkd3d_string_buffer *body) { struct preproc_macro *macro; 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.\n", debugstr_a(name)); if (!(macro = vkd3d_malloc(sizeof(*macro)))) return false; macro->name = name; 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) { vkd3d_free(macro->name); 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; } } %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; uint32_t integer; struct vkd3d_string_buffer string_buffer; } %token T_IDENTIFIER %token T_INTEGER %token T_STRING %token T_TEXT %token T_NEWLINE %token T_DEFINE "#define" %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_UNDEF "#undef" %type expr %type body_token %type body_text %% shader_text : %empty | shader_text directive { vkd3d_string_buffer_printf(&ctx->buffer, "\n"); } 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_token : T_IDENTIFIER | T_INTEGER | T_TEXT directive : T_DEFINE T_IDENTIFIER body_text T_NEWLINE { if (!preproc_add_macro(ctx, &@$, $2, &@3, &$3)) { vkd3d_free($2); vkd3d_string_buffer_cleanup(&$3); 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_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); } expr : T_INTEGER { $$ = preproc_parse_integer($1); vkd3d_free($1); }