mirror of
https://gitlab.winehq.org/wine/vkd3d.git
synced 2024-11-21 16:46:41 -08:00
903 lines
30 KiB
Plaintext
903 lines
30 KiB
Plaintext
/*
|
|
* 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
|
|
*/
|
|
|
|
%{
|
|
|
|
#include "preproc.tab.h"
|
|
|
|
#undef ERROR /* defined in wingdi.h */
|
|
|
|
#define YYSTYPE PREPROC_YYSTYPE
|
|
#define YYLTYPE PREPROC_YYLTYPE
|
|
|
|
#define YY_DECL static int preproc_lexer_lex(YYSTYPE *yylval_param, YYLTYPE *yylloc_param, yyscan_t yyscanner)
|
|
|
|
static struct preproc_expansion *preproc_get_top_expansion(struct preproc_ctx *ctx)
|
|
{
|
|
if (!ctx->expansion_count)
|
|
return NULL;
|
|
return &ctx->expansion_stack[ctx->expansion_count - 1];
|
|
}
|
|
|
|
static void update_location(struct preproc_ctx *ctx);
|
|
|
|
#define YY_USER_ACTION update_location(yyget_extra(yyscanner));
|
|
|
|
%}
|
|
|
|
%option 8bit
|
|
%option bison-bridge
|
|
%option bison-locations
|
|
%option extra-type="struct preproc_ctx *"
|
|
%option never-interactive
|
|
%option nodefault
|
|
%option noinput
|
|
%option nounput
|
|
%option noyy_top_state
|
|
%option noyywrap
|
|
%option prefix="preproc_yy"
|
|
%option reentrant
|
|
%option stack
|
|
|
|
/* Because these can both be terminated by EOF, we need states for them. */
|
|
%s C_COMMENT
|
|
%s CXX_COMMENT
|
|
|
|
%s ERROR
|
|
%s INCLUDE
|
|
%s LINE
|
|
|
|
NEWLINE \r?\n
|
|
WS [ \t\r]
|
|
IDENTIFIER (::)?[A-Za-z_]((::)?[A-Za-z0-9_]+)*
|
|
INT_SUFFIX [uUlL]{0,2}
|
|
|
|
%%
|
|
|
|
<INITIAL>"//" {yy_push_state(CXX_COMMENT, yyscanner);}
|
|
<INITIAL>"/*" {yy_push_state(C_COMMENT, yyscanner);}
|
|
<CXX_COMMENT>\\{NEWLINE} {}
|
|
<CXX_COMMENT>\n {
|
|
yy_pop_state(yyscanner);
|
|
BEGIN(INITIAL);
|
|
return T_NEWLINE;
|
|
}
|
|
<C_COMMENT>"*/" {yy_pop_state(yyscanner);}
|
|
<C_COMMENT,CXX_COMMENT><<EOF>> {yy_pop_state(yyscanner);}
|
|
<C_COMMENT,CXX_COMMENT>. {}
|
|
<C_COMMENT>\n {}
|
|
|
|
<ERROR>(\\{NEWLINE}|[^\n])* {return T_STRING;}
|
|
|
|
<INITIAL>defined/\( {return T_DEFINED;}
|
|
<INITIAL>defined {return T_DEFINED;}
|
|
<INITIAL>{IDENTIFIER}/\( {return T_IDENTIFIER_PAREN;}
|
|
<INITIAL>{IDENTIFIER} {return T_IDENTIFIER;}
|
|
|
|
<INITIAL>"<=" {return T_LE;}
|
|
<INITIAL>">=" {return T_GE;}
|
|
<INITIAL>"==" {return T_EQ;}
|
|
<INITIAL>"!=" {return T_NE;}
|
|
<INITIAL>"&&" {return T_AND;}
|
|
<INITIAL>"||" {return T_OR;}
|
|
|
|
/* We have no use for floats, but shouldn't parse them as integers. */
|
|
|
|
<INITIAL>[0-9]*\.[0-9]+([eE][+-]?[0-9]+)?[hHfF]? {return T_TEXT;}
|
|
<INITIAL>[0-9]+\.([eE][+-]?[0-9]+)?[hHfF]? {return T_TEXT;}
|
|
<INITIAL>[0-9]+([eE][+-]?[0-9]+)?[hHfF] {return T_TEXT;}
|
|
<INITIAL>[0-9]+[eE][+-]?[0-9]+ {return T_TEXT;}
|
|
<INITIAL,LINE>0[xX][0-9a-fA-f]+{INT_SUFFIX} {return T_INTEGER;}
|
|
<INITIAL,LINE>0[0-7]*{INT_SUFFIX} {return T_INTEGER;}
|
|
<INITIAL,LINE>[1-9][0-9]*{INT_SUFFIX} {return T_INTEGER;}
|
|
|
|
<INITIAL>## {return T_CONCAT;}
|
|
|
|
<INITIAL>"++" {return T_TEXT;}
|
|
<INITIAL>"--" {return T_TEXT;}
|
|
<INITIAL>"<<"=? {return T_TEXT;}
|
|
<INITIAL>">>"=? {return T_TEXT;}
|
|
<INITIAL>[-+*/%&|^]= {return T_TEXT;}
|
|
|
|
/* Native doesn't preserve these tokens when running the preprocessor on its
|
|
* own, but there's no good reason to emulate that difference yet. */
|
|
<INITIAL>[pv]s\.[123]\.[0-4x] {return T_TEXT;}
|
|
|
|
<INCLUDE,LINE>\"[^"]*\" {return T_STRING;}
|
|
<INCLUDE>\<[^>]*\> {return T_STRING;}
|
|
|
|
/* C strings (including escaped quotes). */
|
|
<INITIAL>\"([^"\\]|\\.)*\" {return T_TEXT;}
|
|
|
|
<INITIAL>#{WS}*{IDENTIFIER} {
|
|
struct preproc_ctx *ctx = yyget_extra(yyscanner);
|
|
const char *p;
|
|
|
|
if (!ctx->last_was_newline)
|
|
{
|
|
struct preproc_expansion *exp;
|
|
|
|
/* Stringification is only done for function-like macro bodies.
|
|
* Anywhere else, we need to parse it as two separate tokens.
|
|
* We could use a state for this, but yyless() is easier and cheap.
|
|
*/
|
|
|
|
if ((exp = preproc_get_top_expansion(ctx)) && exp->macro && exp->macro->arg_count)
|
|
return T_HASHSTRING;
|
|
|
|
yyless(1);
|
|
return T_TEXT;
|
|
}
|
|
|
|
for (p = yytext + 1; strchr(" \t", *p); ++p)
|
|
;
|
|
|
|
if (!strcmp(p, "error"))
|
|
{
|
|
BEGIN(ERROR);
|
|
return T_ERROR;
|
|
}
|
|
|
|
if (!strcmp(p, "include"))
|
|
{
|
|
BEGIN(INCLUDE);
|
|
return T_INCLUDE;
|
|
}
|
|
|
|
if (!strcmp(p, "line"))
|
|
{
|
|
BEGIN(LINE);
|
|
return T_LINE;
|
|
}
|
|
|
|
if (!strcmp(p, "define"))
|
|
return T_DEFINE;
|
|
if (!strcmp(p, "elif"))
|
|
return T_ELIF;
|
|
if (!strcmp(p, "else"))
|
|
return T_ELSE;
|
|
if (!strcmp(p, "endif"))
|
|
return T_ENDIF;
|
|
if (!strcmp(p, "if"))
|
|
return T_IF;
|
|
if (!strcmp(p, "ifdef"))
|
|
return T_IFDEF;
|
|
if (!strcmp(p, "ifndef"))
|
|
return T_IFNDEF;
|
|
if (!strcmp(p, "pragma"))
|
|
return T_PRAGMA;
|
|
if (!strcmp(p, "undef"))
|
|
return T_UNDEF;
|
|
|
|
preproc_warning(ctx, yyget_lloc(yyscanner), VKD3D_SHADER_WARNING_PP_UNKNOWN_DIRECTIVE,
|
|
"Ignoring unknown directive \"%s\".", yytext);
|
|
return T_TEXT;
|
|
}
|
|
|
|
<INITIAL,INCLUDE,LINE>\\{NEWLINE} {}
|
|
<INITIAL,INCLUDE,ERROR,LINE>{NEWLINE} {
|
|
BEGIN(INITIAL);
|
|
return T_NEWLINE;
|
|
}
|
|
|
|
<INITIAL,INCLUDE,LINE>{WS}+ {}
|
|
<INITIAL>[-()\[\]{},+!*/<>&|^?:] {return yytext[0];}
|
|
<INITIAL,INCLUDE,LINE>. {return T_TEXT;}
|
|
|
|
%%
|
|
|
|
static void update_location(struct preproc_ctx *ctx)
|
|
{
|
|
struct preproc_buffer *buffer = &preproc_get_top_file(ctx)->buffer;
|
|
unsigned int i, leng = yyget_leng(ctx->scanner);
|
|
const char *text = yyget_text(ctx->scanner);
|
|
|
|
/* We want to do this here, rather than before calling yylex(), because
|
|
* some tokens are skipped by the lexer. */
|
|
|
|
*yyget_lloc(ctx->scanner) = buffer->location;
|
|
|
|
for (i = 0; i < leng; ++i)
|
|
{
|
|
++buffer->location.column;
|
|
if (text[i] == '\n')
|
|
{
|
|
buffer->location.column = 1;
|
|
++buffer->location.line;
|
|
}
|
|
}
|
|
}
|
|
|
|
static bool preproc_is_writing(struct preproc_ctx *ctx)
|
|
{
|
|
const struct preproc_file *file;
|
|
|
|
/* This can happen while checking for unterminated macro invocation. */
|
|
if (!ctx->file_count)
|
|
return true;
|
|
file = preproc_get_top_file(ctx);
|
|
if (!file->if_count)
|
|
return true;
|
|
return file->if_stack[file->if_count - 1].current_true;
|
|
}
|
|
|
|
/* Concatenation is not done for object-like macros, but is done for both
|
|
* function-like macro bodies and their arguments. */
|
|
static bool should_concat(struct preproc_ctx *ctx)
|
|
{
|
|
struct preproc_macro *macro;
|
|
|
|
if (!ctx->expansion_count)
|
|
return false;
|
|
macro = ctx->expansion_stack[ctx->expansion_count - 1].macro;
|
|
return !macro || macro->arg_count;
|
|
}
|
|
|
|
static void preproc_pop_buffer(struct preproc_ctx *ctx)
|
|
{
|
|
if (ctx->expansion_count)
|
|
{
|
|
struct preproc_expansion *exp = &ctx->expansion_stack[ctx->expansion_count - 1];
|
|
|
|
yy_delete_buffer(exp->buffer.lexer_buffer, ctx->scanner);
|
|
|
|
if (exp->macro)
|
|
{
|
|
for (unsigned int i = 0; i < exp->macro->arg_count; ++i)
|
|
vkd3d_string_buffer_cleanup(&exp->arg_values[i].text);
|
|
free(exp->arg_values);
|
|
}
|
|
--ctx->expansion_count;
|
|
TRACE("Expansion stack size is now %zu.\n", ctx->expansion_count);
|
|
}
|
|
else
|
|
{
|
|
struct preproc_file *file = preproc_get_top_file(ctx);
|
|
|
|
if (ctx->file_count > 1)
|
|
preproc_close_include(ctx, &file->code);
|
|
|
|
if (file->if_count)
|
|
{
|
|
const struct vkd3d_shader_location loc = {.source_name = file->filename};
|
|
|
|
preproc_warning(ctx, &loc, VKD3D_SHADER_WARNING_PP_UNTERMINATED_IF, "Unterminated #if block.");
|
|
}
|
|
vkd3d_free(file->if_stack);
|
|
|
|
vkd3d_free(file->filename);
|
|
|
|
yy_delete_buffer(file->buffer.lexer_buffer, ctx->scanner);
|
|
|
|
--ctx->file_count;
|
|
TRACE("File stack size is now %zu.\n", ctx->file_count);
|
|
}
|
|
|
|
if (ctx->expansion_count)
|
|
yy_switch_to_buffer(ctx->expansion_stack[ctx->expansion_count - 1].buffer.lexer_buffer, ctx->scanner);
|
|
else if (ctx->file_count)
|
|
yy_switch_to_buffer(ctx->file_stack[ctx->file_count - 1].buffer.lexer_buffer, ctx->scanner);
|
|
}
|
|
|
|
static int return_token(int token, YYSTYPE *lval, const char *text)
|
|
{
|
|
switch (token)
|
|
{
|
|
case T_HASHSTRING:
|
|
case T_IDENTIFIER:
|
|
case T_IDENTIFIER_PAREN:
|
|
case T_INTEGER:
|
|
case T_STRING:
|
|
case T_TEXT:
|
|
if (!(lval->string = vkd3d_strdup(text)))
|
|
return 0;
|
|
break;
|
|
}
|
|
|
|
return token;
|
|
}
|
|
|
|
static const struct preproc_text *find_arg_expansion(struct preproc_ctx *ctx, const char *s)
|
|
{
|
|
struct preproc_expansion *exp;
|
|
unsigned int i;
|
|
|
|
if ((exp = preproc_get_top_expansion(ctx)) && exp->macro)
|
|
{
|
|
for (i = 0; i < exp->macro->arg_count; ++i)
|
|
{
|
|
if (!strcmp(s, exp->macro->arg_names[i]))
|
|
return &exp->arg_values[i];
|
|
}
|
|
}
|
|
return NULL;
|
|
}
|
|
|
|
static void preproc_text_add(struct preproc_text *text, const char *string)
|
|
{
|
|
vkd3d_string_buffer_printf(&text->text, "%s", string);
|
|
}
|
|
|
|
static bool preproc_push_expansion(struct preproc_ctx *ctx,
|
|
const struct preproc_text *text, struct preproc_macro *macro, struct preproc_text *arg_values)
|
|
{
|
|
struct preproc_expansion *exp;
|
|
|
|
if (!vkd3d_array_reserve((void **)&ctx->expansion_stack, &ctx->expansion_stack_size,
|
|
ctx->expansion_count + 1, sizeof(*ctx->expansion_stack)))
|
|
return false;
|
|
exp = &ctx->expansion_stack[ctx->expansion_count++];
|
|
exp->text = text;
|
|
exp->buffer.lexer_buffer = yy_scan_bytes(text->text.buffer, text->text.content_size, ctx->scanner);
|
|
exp->buffer.location = text->location;
|
|
exp->macro = macro;
|
|
exp->arg_values = arg_values;
|
|
TRACE("Expansion stack size is now %zu.\n", ctx->expansion_count);
|
|
return true;
|
|
}
|
|
|
|
static void preproc_stringify(struct preproc_ctx *ctx, struct vkd3d_string_buffer *buffer, const char *text)
|
|
{
|
|
const struct preproc_text *expansion;
|
|
const char *p = text + 1;
|
|
unsigned int i;
|
|
|
|
while (*p == ' ' || *p == '\t')
|
|
++p;
|
|
|
|
vkd3d_string_buffer_printf(buffer, "\"");
|
|
if ((expansion = find_arg_expansion(ctx, p)))
|
|
{
|
|
size_t len = expansion->text.content_size;
|
|
size_t start = 0;
|
|
|
|
while (len && strchr(" \t\r\n", expansion->text.buffer[len - 1]))
|
|
--len;
|
|
|
|
while (start < len && strchr(" \t\r\n", expansion->text.buffer[start]))
|
|
++start;
|
|
|
|
for (i = start; i < len; ++i)
|
|
{
|
|
char c = expansion->text.buffer[i];
|
|
|
|
if (c == '\\' || c == '"')
|
|
vkd3d_string_buffer_printf(buffer, "\\");
|
|
vkd3d_string_buffer_printf(buffer, "%c", c);
|
|
}
|
|
}
|
|
else
|
|
{
|
|
vkd3d_string_buffer_printf(buffer, "%s", p);
|
|
}
|
|
vkd3d_string_buffer_printf(buffer, "\"");
|
|
}
|
|
|
|
int yylex(YYSTYPE *lval, YYLTYPE *lloc, yyscan_t scanner)
|
|
{
|
|
struct preproc_ctx *ctx = yyget_extra(scanner);
|
|
|
|
for (;;)
|
|
{
|
|
struct preproc_func_state *func_state;
|
|
const char *text;
|
|
int token;
|
|
|
|
if (ctx->lookahead_token)
|
|
{
|
|
token = ctx->lookahead_token;
|
|
text = yyget_text(scanner);
|
|
}
|
|
else
|
|
{
|
|
if (ctx->last_was_eof)
|
|
{
|
|
preproc_pop_buffer(ctx);
|
|
if (!ctx->file_count)
|
|
return 0;
|
|
}
|
|
ctx->last_was_eof = false;
|
|
|
|
VKD3D_ASSERT(ctx->file_count);
|
|
if (!(token = preproc_lexer_lex(lval, lloc, scanner)))
|
|
{
|
|
ctx->last_was_eof = true;
|
|
|
|
/* If we have reached the end of an included file, inject a newline. */
|
|
if (ctx->expansion_count)
|
|
continue;
|
|
token = T_NEWLINE;
|
|
text = "\n";
|
|
}
|
|
else
|
|
{
|
|
text = yyget_text(scanner);
|
|
}
|
|
|
|
if (ctx->last_was_newline)
|
|
{
|
|
switch (token)
|
|
{
|
|
case T_DEFINE:
|
|
case T_ELIF:
|
|
case T_ELSE:
|
|
case T_ENDIF:
|
|
case T_ERROR:
|
|
case T_IF:
|
|
case T_IFDEF:
|
|
case T_IFNDEF:
|
|
case T_INCLUDE:
|
|
case T_LINE:
|
|
case T_PRAGMA:
|
|
case T_UNDEF:
|
|
ctx->current_directive = token;
|
|
break;
|
|
|
|
default:
|
|
ctx->current_directive = 0;
|
|
}
|
|
}
|
|
|
|
ctx->last_was_newline = (token == T_NEWLINE);
|
|
}
|
|
|
|
if (ctx->current_directive && token == T_DEFINED)
|
|
ctx->last_was_defined = true;
|
|
|
|
func_state = ctx->current_directive ? &ctx->directive_func : &ctx->text_func;
|
|
|
|
TRACE("Parsing token %d%s, line %d, in directive %d, state %#x, string %s.\n",
|
|
token, ctx->lookahead_token ? " (lookahead)" : "", lloc->line,
|
|
ctx->current_directive, func_state->state, debugstr_a(text));
|
|
|
|
ctx->lookahead_token = 0;
|
|
|
|
switch (ctx->current_directive)
|
|
{
|
|
case T_ELIF:
|
|
case T_ELSE:
|
|
case T_ENDIF:
|
|
case T_IF:
|
|
case T_IFDEF:
|
|
case T_IFNDEF:
|
|
break;
|
|
|
|
default:
|
|
if (!preproc_is_writing(ctx))
|
|
continue;
|
|
}
|
|
|
|
if (ctx->current_directive == T_PRAGMA)
|
|
{
|
|
/* Print all tokens verbatim. */
|
|
if (token == T_PRAGMA)
|
|
vkd3d_string_buffer_printf(&ctx->buffer, "#pragma ");
|
|
else
|
|
vkd3d_string_buffer_printf(&ctx->buffer, "%s", text);
|
|
continue;
|
|
}
|
|
|
|
switch (func_state->state)
|
|
{
|
|
case STATE_NONE:
|
|
if (token == T_CONCAT && should_concat(ctx))
|
|
{
|
|
while (ctx->buffer.content_size
|
|
&& strchr(" \t\r\n", ctx->buffer.buffer[ctx->buffer.content_size - 1]))
|
|
--ctx->buffer.content_size;
|
|
break;
|
|
}
|
|
|
|
if (token == T_HASHSTRING)
|
|
{
|
|
struct vkd3d_string_buffer buffer;
|
|
|
|
if (ctx->current_directive)
|
|
return return_token(token, lval, text);
|
|
|
|
vkd3d_string_buffer_init(&buffer);
|
|
preproc_stringify(ctx, &buffer, text);
|
|
vkd3d_string_buffer_printf(&ctx->buffer, "%s", buffer.buffer);
|
|
vkd3d_string_buffer_cleanup(&buffer);
|
|
break;
|
|
}
|
|
|
|
if (token == T_IDENTIFIER || token == T_IDENTIFIER_PAREN)
|
|
{
|
|
const struct preproc_text *expansion;
|
|
struct preproc_macro *macro;
|
|
|
|
switch (ctx->current_directive)
|
|
{
|
|
case T_DEFINE:
|
|
case T_IFDEF:
|
|
case T_IFNDEF:
|
|
case T_UNDEF:
|
|
/* Return identifiers verbatim. */
|
|
return return_token(token, lval, text);
|
|
|
|
case T_IF:
|
|
case T_ELIF:
|
|
/* Return identifiers verbatim only if they're the
|
|
* argument to "defined". */
|
|
if (ctx->last_was_defined)
|
|
{
|
|
ctx->last_was_defined = false;
|
|
return return_token(token, lval, text);
|
|
}
|
|
break;
|
|
}
|
|
|
|
/* Otherwise, expand a macro if there is one. */
|
|
|
|
if ((expansion = find_arg_expansion(ctx, text)))
|
|
{
|
|
preproc_push_expansion(ctx, expansion, NULL, NULL);
|
|
continue;
|
|
}
|
|
|
|
if ((macro = preproc_find_macro(ctx, text)))
|
|
{
|
|
if (!macro->arg_count)
|
|
{
|
|
preproc_push_expansion(ctx, ¯o->body, macro, NULL);
|
|
}
|
|
else
|
|
{
|
|
func_state->state = STATE_IDENTIFIER;
|
|
func_state->macro = macro;
|
|
}
|
|
continue;
|
|
}
|
|
|
|
if (!strcmp(text, "__FILE__"))
|
|
{
|
|
const struct preproc_file *file = preproc_get_top_file(ctx);
|
|
|
|
/* Not the current file name, but rather the file name
|
|
* before invoking any macros. */
|
|
|
|
if (ctx->current_directive)
|
|
{
|
|
char *string;
|
|
|
|
if (!(string = vkd3d_malloc(strlen(file->filename) + 3)))
|
|
return 0;
|
|
sprintf(string, "\"%s\"", file->filename);
|
|
lval->string = string;
|
|
return T_STRING;
|
|
}
|
|
|
|
if (preproc_is_writing(ctx))
|
|
vkd3d_string_buffer_printf(&ctx->buffer, "\"%s\" ", file->filename);
|
|
continue;
|
|
}
|
|
|
|
if (!strcmp(text, "__LINE__"))
|
|
{
|
|
const struct preproc_file *file = preproc_get_top_file(ctx);
|
|
|
|
/* Not the current line number, but rather the line
|
|
* number before invoking any macros. */
|
|
|
|
if (ctx->current_directive)
|
|
{
|
|
char string[13];
|
|
|
|
sprintf(string, "%d", file->buffer.location.line);
|
|
return return_token(T_INTEGER, lval, string);
|
|
}
|
|
|
|
if (preproc_is_writing(ctx))
|
|
vkd3d_string_buffer_printf(&ctx->buffer, "%d ", file->buffer.location.line);
|
|
continue;
|
|
}
|
|
}
|
|
|
|
if (ctx->current_directive)
|
|
return return_token(token, lval, text);
|
|
|
|
if (isspace(text[0]))
|
|
vkd3d_string_buffer_printf(&ctx->buffer, "%s", text);
|
|
else
|
|
vkd3d_string_buffer_printf(&ctx->buffer, "%s ", text);
|
|
break;
|
|
|
|
case STATE_IDENTIFIER:
|
|
if (token == '(')
|
|
{
|
|
struct preproc_text *arg_values;
|
|
|
|
if (!(arg_values = calloc(func_state->macro->arg_count, sizeof(*arg_values))))
|
|
return 0;
|
|
|
|
for (unsigned int i = 0; i < func_state->macro->arg_count; ++i)
|
|
vkd3d_string_buffer_init(&arg_values[i].text);
|
|
arg_values[0].location = *lloc;
|
|
|
|
func_state->arg_count = 0;
|
|
func_state->paren_depth = 1;
|
|
func_state->state = STATE_ARGS;
|
|
func_state->arg_values = arg_values;
|
|
}
|
|
else
|
|
{
|
|
const char *name = func_state->macro->name;
|
|
|
|
ctx->lookahead_token = token;
|
|
func_state->macro = NULL;
|
|
func_state->state = STATE_NONE;
|
|
|
|
if (ctx->current_directive)
|
|
return return_token(T_IDENTIFIER, lval, name);
|
|
|
|
vkd3d_string_buffer_printf(&ctx->buffer, "%s ", name);
|
|
}
|
|
break;
|
|
|
|
case STATE_ARGS:
|
|
{
|
|
struct preproc_text *current_arg = NULL;
|
|
|
|
VKD3D_ASSERT(func_state->macro->arg_count);
|
|
|
|
if (func_state->arg_count < func_state->macro->arg_count)
|
|
current_arg = &func_state->arg_values[func_state->arg_count];
|
|
|
|
switch (token)
|
|
{
|
|
/* Most text gets left alone (e.g. if it contains macros,
|
|
* the macros should be evaluated later).
|
|
* Arguments are a special case, and are replaced with
|
|
* their values immediately. */
|
|
case T_IDENTIFIER:
|
|
case T_IDENTIFIER_PAREN:
|
|
{
|
|
const struct preproc_text *expansion;
|
|
|
|
if ((expansion = find_arg_expansion(ctx, text)))
|
|
{
|
|
preproc_push_expansion(ctx, expansion, NULL, NULL);
|
|
continue;
|
|
}
|
|
|
|
if (current_arg)
|
|
preproc_text_add(current_arg, text);
|
|
break;
|
|
}
|
|
|
|
/* Stringification is another special case. Unsurprisingly,
|
|
* we need to stringify if this is an argument. More
|
|
* surprisingly, we need to stringify even if it's not. */
|
|
case T_HASHSTRING:
|
|
{
|
|
struct vkd3d_string_buffer buffer;
|
|
|
|
vkd3d_string_buffer_init(&buffer);
|
|
preproc_stringify(ctx, &buffer, text);
|
|
if (current_arg)
|
|
preproc_text_add(current_arg, buffer.buffer);
|
|
vkd3d_string_buffer_cleanup(&buffer);
|
|
break;
|
|
}
|
|
|
|
case T_NEWLINE:
|
|
if (current_arg)
|
|
preproc_text_add(current_arg, " ");
|
|
break;
|
|
|
|
case ')':
|
|
case ']':
|
|
case '}':
|
|
if (!--func_state->paren_depth)
|
|
{
|
|
if (++func_state->arg_count == func_state->macro->arg_count)
|
|
{
|
|
preproc_push_expansion(ctx, &func_state->macro->body,
|
|
func_state->macro, func_state->arg_values);
|
|
}
|
|
else
|
|
{
|
|
preproc_warning(ctx, lloc, VKD3D_SHADER_WARNING_PP_ARGUMENT_COUNT_MISMATCH,
|
|
"Wrong number of arguments to macro \"%s\": expected %zu, got %zu.",
|
|
func_state->macro->name, func_state->macro->arg_count, func_state->arg_count);
|
|
|
|
if (ctx->current_directive)
|
|
return return_token(T_IDENTIFIER, lval, func_state->macro->name);
|
|
|
|
vkd3d_string_buffer_printf(&ctx->buffer, "%s ", func_state->macro->name);
|
|
}
|
|
func_state->macro = NULL;
|
|
func_state->state = STATE_NONE;
|
|
}
|
|
else
|
|
{
|
|
if (current_arg)
|
|
preproc_text_add(current_arg, text);
|
|
}
|
|
break;
|
|
|
|
case ',':
|
|
if (func_state->paren_depth == 1)
|
|
{
|
|
++func_state->arg_count;
|
|
if (current_arg)
|
|
current_arg->location = *lloc;
|
|
}
|
|
else if (current_arg)
|
|
{
|
|
preproc_text_add(current_arg, text);
|
|
}
|
|
break;
|
|
|
|
case '(':
|
|
case '[':
|
|
case '{':
|
|
++func_state->paren_depth;
|
|
/* fall through */
|
|
|
|
default:
|
|
if (current_arg)
|
|
preproc_text_add(current_arg, text);
|
|
}
|
|
|
|
if (current_arg)
|
|
preproc_text_add(current_arg, " ");
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
bool preproc_push_include(struct preproc_ctx *ctx, char *filename, const struct vkd3d_shader_code *code)
|
|
{
|
|
struct preproc_file *file;
|
|
|
|
if (!vkd3d_array_reserve((void **)&ctx->file_stack, &ctx->file_stack_size,
|
|
ctx->file_count + 1, sizeof(*ctx->file_stack)))
|
|
return false;
|
|
file = &ctx->file_stack[ctx->file_count++];
|
|
memset(file, 0, sizeof(*file));
|
|
file->code = *code;
|
|
file->filename = filename;
|
|
file->buffer.lexer_buffer = yy_scan_bytes(code->code, code->size, ctx->scanner);
|
|
file->buffer.location.source_name = file->filename;
|
|
file->buffer.location.line = 1;
|
|
file->buffer.location.column = 1;
|
|
TRACE("File stack size is now %zu.\n", ctx->file_count);
|
|
ctx->last_was_newline = true;
|
|
return true;
|
|
}
|
|
|
|
static int preproc_macro_compare(const void *key, const struct rb_entry *entry)
|
|
{
|
|
const struct preproc_macro *macro = RB_ENTRY_VALUE(entry, struct preproc_macro, entry);
|
|
const char *name = key;
|
|
|
|
return strcmp(name, macro->name);
|
|
}
|
|
|
|
static void preproc_macro_rb_free(struct rb_entry *entry, void *ctx)
|
|
{
|
|
preproc_free_macro(RB_ENTRY_VALUE(entry, struct preproc_macro, entry));
|
|
}
|
|
|
|
int preproc_lexer_parse(const struct vkd3d_shader_compile_info *compile_info,
|
|
struct vkd3d_shader_code *out, struct vkd3d_shader_message_context *message_context)
|
|
{
|
|
static const struct vkd3d_shader_preprocess_info default_preprocess_info = {0};
|
|
struct preproc_ctx ctx = {0};
|
|
char *source_name = NULL;
|
|
void *output_code;
|
|
unsigned int i;
|
|
|
|
vkd3d_string_buffer_init(&ctx.buffer);
|
|
rb_init(&ctx.macros, preproc_macro_compare);
|
|
if (!(ctx.preprocess_info = vkd3d_find_struct(compile_info->next, PREPROCESS_INFO)))
|
|
ctx.preprocess_info = &default_preprocess_info;
|
|
ctx.message_context = message_context;
|
|
|
|
if (!(source_name = vkd3d_strdup(compile_info->source_name ? compile_info->source_name : "<anonymous>")))
|
|
goto fail;
|
|
|
|
for (i = 0; i < ctx.preprocess_info->macro_count; ++i)
|
|
{
|
|
const struct vkd3d_shader_location loc = {.source_name = source_name};
|
|
struct vkd3d_string_buffer body;
|
|
char *name;
|
|
|
|
vkd3d_string_buffer_init(&body);
|
|
vkd3d_string_buffer_printf(&body, "%s", ctx.preprocess_info->macros[i].value);
|
|
if (!(name = vkd3d_strdup(ctx.preprocess_info->macros[i].name)))
|
|
{
|
|
vkd3d_string_buffer_cleanup(&body);
|
|
goto fail;
|
|
}
|
|
if (!preproc_add_macro(&ctx, &loc, name, NULL, 0, &loc, &body))
|
|
{
|
|
vkd3d_free(name);
|
|
vkd3d_string_buffer_cleanup(&body);
|
|
goto fail;
|
|
}
|
|
}
|
|
|
|
yylex_init_extra(&ctx, &ctx.scanner);
|
|
if (!preproc_push_include(&ctx, source_name, &compile_info->source))
|
|
{
|
|
yylex_destroy(ctx.scanner);
|
|
goto fail;
|
|
}
|
|
|
|
preproc_yyparse(ctx.scanner, &ctx);
|
|
|
|
switch (ctx.text_func.state)
|
|
{
|
|
case STATE_NONE:
|
|
break;
|
|
|
|
case STATE_ARGS:
|
|
{
|
|
const struct vkd3d_shader_location loc = {.source_name = source_name};
|
|
|
|
preproc_warning(&ctx, &loc, VKD3D_SHADER_WARNING_PP_UNTERMINATED_MACRO,
|
|
"Unterminated macro invocation.");
|
|
}
|
|
/* fall through */
|
|
|
|
case STATE_IDENTIFIER:
|
|
if (preproc_is_writing(&ctx))
|
|
vkd3d_string_buffer_printf(&ctx.buffer, "%s ", ctx.text_func.macro->name);
|
|
break;
|
|
}
|
|
|
|
while (ctx.file_count)
|
|
preproc_pop_buffer(&ctx);
|
|
yylex_destroy(ctx.scanner);
|
|
|
|
rb_destroy(&ctx.macros, preproc_macro_rb_free, NULL);
|
|
vkd3d_free(ctx.file_stack);
|
|
vkd3d_free(ctx.expansion_stack);
|
|
|
|
if (ctx.error)
|
|
{
|
|
WARN("Failed to preprocess.\n");
|
|
vkd3d_string_buffer_cleanup(&ctx.buffer);
|
|
return VKD3D_ERROR_INVALID_SHADER;
|
|
}
|
|
|
|
if (!(output_code = vkd3d_malloc(ctx.buffer.content_size)))
|
|
{
|
|
vkd3d_string_buffer_cleanup(&ctx.buffer);
|
|
return VKD3D_ERROR_OUT_OF_MEMORY;
|
|
}
|
|
memcpy(output_code, ctx.buffer.buffer, ctx.buffer.content_size);
|
|
out->size = ctx.buffer.content_size;
|
|
out->code = output_code;
|
|
vkd3d_string_buffer_trace(&ctx.buffer);
|
|
vkd3d_string_buffer_cleanup(&ctx.buffer);
|
|
return VKD3D_OK;
|
|
|
|
fail:
|
|
rb_destroy(&ctx.macros, preproc_macro_rb_free, NULL);
|
|
vkd3d_free(source_name);
|
|
vkd3d_string_buffer_cleanup(&ctx.buffer);
|
|
return VKD3D_ERROR_OUT_OF_MEMORY;
|
|
}
|