Add keyword completion to shader editor

This commit is contained in:
Yuri Roubinsky 2022-04-27 14:18:32 +03:00
parent cbdc33bcf7
commit 15032e01e6
2 changed files with 495 additions and 109 deletions

View File

@ -237,92 +237,141 @@ ShaderLanguage::Token ShaderLanguage::_make_token(TokenType p_type, const String
return tk;
}
enum ContextFlag : uint32_t {
CF_UNSPECIFIED = 0U,
CF_BLOCK = 1U, // "void test() { <x> }"
CF_FUNC_DECL_PARAM_SPEC = 2U, // "void test(<x> int param) {}"
CF_FUNC_DECL_PARAM_TYPE = 4U, // "void test(<x> param) {}"
CF_IF_DECL = 8U, // "if(<x>) {}"
CF_BOOLEAN = 16U, // "bool t = <x>;"
CF_GLOBAL_SPACE = 32U, // "struct", "const", "void" etc.
CF_DATATYPE = 64U, // "<x> value;"
CF_UNIFORM_TYPE = 128U, // "uniform <x> myUniform;"
CF_VARYING_TYPE = 256U, // "varying <x> myVarying;"
CF_PRECISION_MODIFIER = 512U, // "<x> vec4 a = vec4(0.0, 1.0, 2.0, 3.0);"
CF_INTERPOLATION_QUALIFIER = 1024U, // "varying <x> vec3 myColor;"
CF_UNIFORM_KEYWORD = 2048U, // "uniform"
CF_CONST_KEYWORD = 4096U, // "const"
CF_UNIFORM_QUALIFIER = 8192U, // "<x> uniform float t;"
};
const uint32_t KCF_DATATYPE = CF_BLOCK | CF_GLOBAL_SPACE | CF_DATATYPE | CF_FUNC_DECL_PARAM_TYPE | CF_UNIFORM_TYPE;
const uint32_t KCF_SAMPLER_DATATYPE = CF_FUNC_DECL_PARAM_TYPE | CF_UNIFORM_TYPE;
const ShaderLanguage::KeyWord ShaderLanguage::keyword_list[] = {
{ TK_TRUE, "true" },
{ TK_FALSE, "false" },
{ TK_TYPE_VOID, "void" },
{ TK_TYPE_BOOL, "bool" },
{ TK_TYPE_BVEC2, "bvec2" },
{ TK_TYPE_BVEC3, "bvec3" },
{ TK_TYPE_BVEC4, "bvec4" },
{ TK_TYPE_INT, "int" },
{ TK_TYPE_IVEC2, "ivec2" },
{ TK_TYPE_IVEC3, "ivec3" },
{ TK_TYPE_IVEC4, "ivec4" },
{ TK_TYPE_UINT, "uint" },
{ TK_TYPE_UVEC2, "uvec2" },
{ TK_TYPE_UVEC3, "uvec3" },
{ TK_TYPE_UVEC4, "uvec4" },
{ TK_TYPE_FLOAT, "float" },
{ TK_TYPE_VEC2, "vec2" },
{ TK_TYPE_VEC3, "vec3" },
{ TK_TYPE_VEC4, "vec4" },
{ TK_TYPE_MAT2, "mat2" },
{ TK_TYPE_MAT3, "mat3" },
{ TK_TYPE_MAT4, "mat4" },
{ TK_TYPE_SAMPLER2D, "sampler2D" },
{ TK_TYPE_ISAMPLER2D, "isampler2D" },
{ TK_TYPE_USAMPLER2D, "usampler2D" },
{ TK_TYPE_SAMPLER2DARRAY, "sampler2DArray" },
{ TK_TYPE_ISAMPLER2DARRAY, "isampler2DArray" },
{ TK_TYPE_USAMPLER2DARRAY, "usampler2DArray" },
{ TK_TYPE_SAMPLER3D, "sampler3D" },
{ TK_TYPE_ISAMPLER3D, "isampler3D" },
{ TK_TYPE_USAMPLER3D, "usampler3D" },
{ TK_TYPE_SAMPLERCUBE, "samplerCube" },
{ TK_TYPE_SAMPLERCUBEARRAY, "samplerCubeArray" },
{ TK_INTERPOLATION_FLAT, "flat" },
{ TK_INTERPOLATION_SMOOTH, "smooth" },
{ TK_CONST, "const" },
{ TK_STRUCT, "struct" },
{ TK_PRECISION_LOW, "lowp" },
{ TK_PRECISION_MID, "mediump" },
{ TK_PRECISION_HIGH, "highp" },
{ TK_CF_IF, "if" },
{ TK_CF_ELSE, "else" },
{ TK_CF_FOR, "for" },
{ TK_CF_WHILE, "while" },
{ TK_CF_DO, "do" },
{ TK_CF_SWITCH, "switch" },
{ TK_CF_CASE, "case" },
{ TK_CF_DEFAULT, "default" },
{ TK_CF_BREAK, "break" },
{ TK_CF_CONTINUE, "continue" },
{ TK_CF_RETURN, "return" },
{ TK_CF_DISCARD, "discard" },
{ TK_UNIFORM, "uniform" },
{ TK_INSTANCE, "instance" },
{ TK_GLOBAL, "global" },
{ TK_VARYING, "varying" },
{ TK_ARG_IN, "in" },
{ TK_ARG_OUT, "out" },
{ TK_ARG_INOUT, "inout" },
{ TK_RENDER_MODE, "render_mode" },
{ TK_HINT_WHITE_TEXTURE, "hint_white" },
{ TK_HINT_BLACK_TEXTURE, "hint_black" },
{ TK_HINT_NORMAL_TEXTURE, "hint_normal" },
{ TK_HINT_ROUGHNESS_NORMAL_TEXTURE, "hint_roughness_normal" },
{ TK_HINT_ROUGHNESS_R, "hint_roughness_r" },
{ TK_HINT_ROUGHNESS_G, "hint_roughness_g" },
{ TK_HINT_ROUGHNESS_B, "hint_roughness_b" },
{ TK_HINT_ROUGHNESS_A, "hint_roughness_a" },
{ TK_HINT_ROUGHNESS_GRAY, "hint_roughness_gray" },
{ TK_HINT_ANISOTROPY_TEXTURE, "hint_anisotropy" },
{ TK_HINT_ALBEDO_TEXTURE, "hint_albedo" },
{ TK_HINT_BLACK_ALBEDO_TEXTURE, "hint_black_albedo" },
{ TK_HINT_COLOR, "hint_color" },
{ TK_HINT_RANGE, "hint_range" },
{ TK_HINT_INSTANCE_INDEX, "instance_index" },
{ TK_FILTER_NEAREST, "filter_nearest" },
{ TK_FILTER_LINEAR, "filter_linear" },
{ TK_FILTER_NEAREST_MIPMAP, "filter_nearest_mipmap" },
{ TK_FILTER_LINEAR_MIPMAP, "filter_linear_mipmap" },
{ TK_FILTER_NEAREST_MIPMAP_ANISOTROPIC, "filter_nearest_mipmap_anisotropic" },
{ TK_FILTER_LINEAR_MIPMAP_ANISOTROPIC, "filter_linear_mipmap_anisotropic" },
{ TK_REPEAT_ENABLE, "repeat_enable" },
{ TK_REPEAT_DISABLE, "repeat_disable" },
{ TK_SHADER_TYPE, "shader_type" },
{ TK_ERROR, nullptr }
{ TK_TRUE, "true", CF_BLOCK | CF_IF_DECL | CF_BOOLEAN, {}, {} },
{ TK_FALSE, "false", CF_BLOCK | CF_IF_DECL | CF_BOOLEAN, {}, {} },
// data types
{ TK_TYPE_VOID, "void", CF_GLOBAL_SPACE, {}, {} },
{ TK_TYPE_BOOL, "bool", KCF_DATATYPE, {}, {} },
{ TK_TYPE_BVEC2, "bvec2", KCF_DATATYPE, {}, {} },
{ TK_TYPE_BVEC3, "bvec3", KCF_DATATYPE, {}, {} },
{ TK_TYPE_BVEC4, "bvec4", KCF_DATATYPE, {}, {} },
{ TK_TYPE_INT, "int", KCF_DATATYPE, {}, {} },
{ TK_TYPE_IVEC2, "ivec2", KCF_DATATYPE, {}, {} },
{ TK_TYPE_IVEC3, "ivec3", KCF_DATATYPE, {}, {} },
{ TK_TYPE_IVEC4, "ivec4", KCF_DATATYPE, {}, {} },
{ TK_TYPE_UINT, "uint", KCF_DATATYPE, {}, {} },
{ TK_TYPE_UVEC2, "uvec2", KCF_DATATYPE, {}, {} },
{ TK_TYPE_UVEC3, "uvec3", KCF_DATATYPE, {}, {} },
{ TK_TYPE_UVEC4, "uvec4", KCF_DATATYPE, {}, {} },
{ TK_TYPE_FLOAT, "float", KCF_DATATYPE | CF_VARYING_TYPE, {}, {} },
{ TK_TYPE_VEC2, "vec2", KCF_DATATYPE | CF_VARYING_TYPE, {}, {} },
{ TK_TYPE_VEC3, "vec3", KCF_DATATYPE | CF_VARYING_TYPE, {}, {} },
{ TK_TYPE_VEC4, "vec4", KCF_DATATYPE | CF_VARYING_TYPE, {}, {} },
{ TK_TYPE_MAT2, "mat2", KCF_DATATYPE | CF_VARYING_TYPE, {}, {} },
{ TK_TYPE_MAT3, "mat3", KCF_DATATYPE | CF_VARYING_TYPE, {}, {} },
{ TK_TYPE_MAT4, "mat4", KCF_DATATYPE | CF_VARYING_TYPE, {}, {} },
{ TK_TYPE_SAMPLER2D, "sampler2D", KCF_SAMPLER_DATATYPE, {}, {} },
{ TK_TYPE_ISAMPLER2D, "isampler2D", KCF_SAMPLER_DATATYPE, {}, {} },
{ TK_TYPE_USAMPLER2D, "usampler2D", KCF_SAMPLER_DATATYPE, {}, {} },
{ TK_TYPE_SAMPLER2DARRAY, "sampler2DArray", KCF_SAMPLER_DATATYPE, {}, {} },
{ TK_TYPE_ISAMPLER2DARRAY, "isampler2DArray", KCF_SAMPLER_DATATYPE, {}, {} },
{ TK_TYPE_USAMPLER2DARRAY, "usampler2DArray", KCF_SAMPLER_DATATYPE, {}, {} },
{ TK_TYPE_SAMPLER3D, "sampler3D", KCF_SAMPLER_DATATYPE, {}, {} },
{ TK_TYPE_ISAMPLER3D, "isampler3D", KCF_SAMPLER_DATATYPE, {}, {} },
{ TK_TYPE_USAMPLER3D, "usampler3D", KCF_SAMPLER_DATATYPE, {}, {} },
{ TK_TYPE_SAMPLERCUBE, "samplerCube", KCF_SAMPLER_DATATYPE, {}, {} },
{ TK_TYPE_SAMPLERCUBEARRAY, "samplerCubeArray", KCF_SAMPLER_DATATYPE, {}, {} },
// interpolation qualifiers
{ TK_INTERPOLATION_FLAT, "flat", CF_INTERPOLATION_QUALIFIER, {}, {} },
{ TK_INTERPOLATION_SMOOTH, "smooth", CF_INTERPOLATION_QUALIFIER, {}, {} },
// precision modifiers
{ TK_PRECISION_LOW, "lowp", CF_BLOCK | CF_PRECISION_MODIFIER, {}, {} },
{ TK_PRECISION_MID, "mediump", CF_BLOCK | CF_PRECISION_MODIFIER, {}, {} },
{ TK_PRECISION_HIGH, "highp", CF_BLOCK | CF_PRECISION_MODIFIER, {}, {} },
// global space keywords
{ TK_UNIFORM, "uniform", CF_GLOBAL_SPACE | CF_UNIFORM_KEYWORD, {}, {} },
{ TK_VARYING, "varying", CF_GLOBAL_SPACE, { "particles", "sky", "fog" }, {} },
{ TK_CONST, "const", CF_BLOCK | CF_GLOBAL_SPACE | CF_CONST_KEYWORD, {}, {} },
{ TK_STRUCT, "struct", CF_GLOBAL_SPACE, {}, {} },
{ TK_SHADER_TYPE, "shader_type", CF_GLOBAL_SPACE, {}, {} },
{ TK_RENDER_MODE, "render_mode", CF_GLOBAL_SPACE, {}, {} },
// uniform qualifiers
{ TK_INSTANCE, "instance", CF_GLOBAL_SPACE | CF_UNIFORM_QUALIFIER, {}, {} },
{ TK_GLOBAL, "global", CF_GLOBAL_SPACE | CF_UNIFORM_QUALIFIER, {}, {} },
// block keywords
{ TK_CF_IF, "if", CF_BLOCK, {}, {} },
{ TK_CF_ELSE, "else", CF_BLOCK, {}, {} },
{ TK_CF_FOR, "for", CF_BLOCK, {}, {} },
{ TK_CF_WHILE, "while", CF_BLOCK, {}, {} },
{ TK_CF_DO, "do", CF_BLOCK, {}, {} },
{ TK_CF_SWITCH, "switch", CF_BLOCK, {}, {} },
{ TK_CF_CASE, "case", CF_BLOCK, {}, {} },
{ TK_CF_DEFAULT, "default", CF_BLOCK, {}, {} },
{ TK_CF_BREAK, "break", CF_BLOCK, {}, {} },
{ TK_CF_CONTINUE, "continue", CF_BLOCK, {}, {} },
{ TK_CF_RETURN, "return", CF_BLOCK, {}, {} },
{ TK_CF_DISCARD, "discard", CF_BLOCK, { "particles", "sky", "fog" }, { "fragment" } },
// function specifier keywords
{ TK_ARG_IN, "in", CF_FUNC_DECL_PARAM_SPEC, {}, {} },
{ TK_ARG_OUT, "out", CF_FUNC_DECL_PARAM_SPEC, {}, {} },
{ TK_ARG_INOUT, "inout", CF_FUNC_DECL_PARAM_SPEC, {}, {} },
// hints
{ TK_HINT_RANGE, "hint_range", CF_UNSPECIFIED, {}, {} },
{ TK_HINT_COLOR, "hint_color", CF_UNSPECIFIED, {}, {} },
{ TK_HINT_INSTANCE_INDEX, "instance_index", CF_UNSPECIFIED, {}, {} },
// sampler hints
{ TK_HINT_ALBEDO_TEXTURE, "hint_albedo", CF_UNSPECIFIED, {}, {} },
{ TK_HINT_BLACK_ALBEDO_TEXTURE, "hint_black_albedo", CF_UNSPECIFIED, {}, {} },
{ TK_HINT_NORMAL_TEXTURE, "hint_normal", CF_UNSPECIFIED, {}, {} },
{ TK_HINT_WHITE_TEXTURE, "hint_white", CF_UNSPECIFIED, {}, {} },
{ TK_HINT_BLACK_TEXTURE, "hint_black", CF_UNSPECIFIED, {}, {} },
{ TK_HINT_ANISOTROPY_TEXTURE, "hint_anisotropy", CF_UNSPECIFIED, {}, {} },
{ TK_HINT_ROUGHNESS_R, "hint_roughness_r", CF_UNSPECIFIED, {}, {} },
{ TK_HINT_ROUGHNESS_G, "hint_roughness_g", CF_UNSPECIFIED, {}, {} },
{ TK_HINT_ROUGHNESS_B, "hint_roughness_b", CF_UNSPECIFIED, {}, {} },
{ TK_HINT_ROUGHNESS_A, "hint_roughness_a", CF_UNSPECIFIED, {}, {} },
{ TK_HINT_ROUGHNESS_NORMAL_TEXTURE, "hint_roughness_normal", CF_UNSPECIFIED, {}, {} },
{ TK_HINT_ROUGHNESS_GRAY, "hint_roughness_gray", CF_UNSPECIFIED, {}, {} },
{ TK_FILTER_NEAREST, "filter_nearest", CF_UNSPECIFIED, {}, {} },
{ TK_FILTER_LINEAR, "filter_linear", CF_UNSPECIFIED, {}, {} },
{ TK_FILTER_NEAREST_MIPMAP, "filter_nearest_mipmap", CF_UNSPECIFIED, {}, {} },
{ TK_FILTER_LINEAR_MIPMAP, "filter_linear_mipmap", CF_UNSPECIFIED, {}, {} },
{ TK_FILTER_NEAREST_MIPMAP_ANISOTROPIC, "filter_nearest_mipmap_anisotropic", CF_UNSPECIFIED, {}, {} },
{ TK_FILTER_LINEAR_MIPMAP_ANISOTROPIC, "filter_linear_mipmap_anisotropic", CF_UNSPECIFIED, {}, {} },
{ TK_REPEAT_ENABLE, "repeat_enable", CF_UNSPECIFIED, {}, {} },
{ TK_REPEAT_DISABLE, "repeat_disable", CF_UNSPECIFIED, {}, {} },
{ TK_ERROR, nullptr, CF_UNSPECIFIED, {}, {} }
};
ShaderLanguage::Token ShaderLanguage::_get_token() {
@ -752,6 +801,19 @@ ShaderLanguage::Token ShaderLanguage::_get_token() {
#undef GETCHAR
}
bool ShaderLanguage::_lookup_next(Token &r_tk) {
TkPos pre_pos = _get_tkpos();
int line = pre_pos.tk_line;
_get_token();
Token tk = _get_token();
_set_tkpos(pre_pos);
if (tk.line == line) {
r_tk = tk;
return true;
}
return false;
}
String ShaderLanguage::token_debug(const String &p_code) {
clear();
@ -852,6 +914,13 @@ bool ShaderLanguage::is_token_precision(TokenType p_type) {
p_type == TK_PRECISION_HIGH);
}
bool ShaderLanguage::is_token_arg_qual(TokenType p_type) {
return (
p_type == TK_ARG_IN ||
p_type == TK_ARG_OUT ||
p_type == TK_ARG_INOUT);
}
ShaderLanguage::DataPrecision ShaderLanguage::get_token_precision(TokenType p_type) {
if (p_type == TK_PRECISION_LOW) {
return PRECISION_LOWP;
@ -967,6 +1036,7 @@ void ShaderLanguage::clear() {
completion_base_array = false;
#ifdef DEBUG_ENABLED
keyword_completion_context = CF_GLOBAL_SPACE;
used_constants.clear();
used_varyings.clear();
used_uniforms.clear();
@ -6373,8 +6443,10 @@ ShaderLanguage::Node *ShaderLanguage::_parse_and_reduce_expression(BlockNode *p_
Error ShaderLanguage::_parse_block(BlockNode *p_block, const FunctionInfo &p_function_info, bool p_just_one, bool p_can_break, bool p_can_continue) {
while (true) {
TkPos pos = _get_tkpos();
Token tk = _get_token();
#ifdef DEBUG_ENABLED
Token next;
#endif // DEBUG_ENABLED
if (p_block && p_block->block_type == BlockNode::BLOCK_TYPE_SWITCH) {
if (tk.type != TK_CF_CASE && tk.type != TK_CF_DEFAULT && tk.type != TK_CURLY_BRACKET_CLOSE) {
@ -6407,6 +6479,16 @@ Error ShaderLanguage::_parse_block(BlockNode *p_block, const FunctionInfo &p_fun
}
#endif // DEBUG_ENABLED
}
#ifdef DEBUG_ENABLED
uint32_t precision_flag = CF_PRECISION_MODIFIER;
keyword_completion_context = CF_DATATYPE;
if (!is_token_precision(tk.type)) {
if (!is_struct) {
keyword_completion_context |= precision_flag;
}
}
#endif // DEBUG_ENABLED
bool is_const = false;
@ -6428,6 +6510,26 @@ Error ShaderLanguage::_parse_block(BlockNode *p_block, const FunctionInfo &p_fun
if (!is_struct) {
is_struct = shader->structs.has(tk.text); // check again.
}
#ifdef DEBUG_ENABLED
if (keyword_completion_context & precision_flag) {
keyword_completion_context ^= precision_flag;
}
#endif // DEBUG_ENABLED
}
#ifdef DEBUG_ENABLED
if (is_const && _lookup_next(next)) {
if (is_token_precision(next.type)) {
keyword_completion_context = CF_UNSPECIFIED;
}
if (is_token_datatype(next.type)) {
keyword_completion_context ^= CF_DATATYPE;
}
}
#endif // DEBUG_ENABLED
if (precision != PRECISION_DEFAULT) {
if (!is_token_nonvoid_datatype(tk.type)) {
_set_error(RTR("Expected variable type after precision modifier."));
return ERR_PARSE_ERROR;
@ -6451,6 +6553,10 @@ Error ShaderLanguage::_parse_block(BlockNode *p_block, const FunctionInfo &p_fun
return ERR_PARSE_ERROR;
}
#ifdef DEBUG_ENABLED
keyword_completion_context = CF_UNSPECIFIED;
#endif // DEBUG_ENABLED
int array_size = 0;
bool fixed_array_size = false;
bool first = true;
@ -6554,7 +6660,11 @@ Error ShaderLanguage::_parse_block(BlockNode *p_block, const FunctionInfo &p_fun
tk = _get_token();
}
#ifdef DEBUG_ENABLED
if (var.type == DataType::TYPE_BOOL) {
keyword_completion_context = CF_BOOLEAN;
}
#endif // DEBUG_ENABLED
if (var.array_size > 0 || unknown_size) {
bool full_def = false;
@ -6801,7 +6911,9 @@ Error ShaderLanguage::_parse_block(BlockNode *p_block, const FunctionInfo &p_fun
return ERR_PARSE_ERROR;
}
} while (tk.type == TK_COMMA); //another variable
#ifdef DEBUG_ENABLED
keyword_completion_context = CF_BLOCK;
#endif // DEBUG_ENABLED
p_block->statements.push_back(static_cast<Node *>(vdnode));
} else if (tk.type == TK_CURLY_BRACKET_OPEN) {
//a sub block, just because..
@ -6821,10 +6933,16 @@ Error ShaderLanguage::_parse_block(BlockNode *p_block, const FunctionInfo &p_fun
ControlFlowNode *cf = alloc_node<ControlFlowNode>();
cf->flow_op = FLOW_OP_IF;
#ifdef DEBUG_ENABLED
keyword_completion_context = CF_IF_DECL;
#endif // DEBUG_ENABLED
Node *n = _parse_and_reduce_expression(p_block, p_function_info);
if (!n) {
return ERR_PARSE_ERROR;
}
#ifdef DEBUG_ENABLED
keyword_completion_context = CF_BLOCK;
#endif // DEBUG_ENABLED
if (n->get_datatype() != TYPE_BOOL) {
_set_error(RTR("Expected a boolean expression."));
@ -7162,10 +7280,17 @@ Error ShaderLanguage::_parse_block(BlockNode *p_block, const FunctionInfo &p_fun
init_block->parent_block = p_block;
init_block->single_statement = true;
cf->blocks.push_back(init_block);
#ifdef DEBUG_ENABLED
keyword_completion_context = CF_DATATYPE;
#endif // DEBUG_ENABLED
Error err = _parse_block(init_block, p_function_info, true, false, false);
if (err != OK) {
return err;
}
#ifdef DEBUG_ENABLED
keyword_completion_context = CF_UNSPECIFIED;
#endif // DEBUG_ENABLED
BlockNode *condition_block = alloc_node<BlockNode>();
condition_block->block_type = BlockNode::BLOCK_TYPE_FOR_CONDITION;
@ -7194,6 +7319,9 @@ Error ShaderLanguage::_parse_block(BlockNode *p_block, const FunctionInfo &p_fun
cf->blocks.push_back(block);
p_block->statements.push_back(cf);
#ifdef DEBUG_ENABLED
keyword_completion_context = CF_BLOCK;
#endif // DEBUG_ENABLED
err = _parse_block(block, p_function_info, true, true, true);
if (err != OK) {
return err;
@ -7238,6 +7366,12 @@ Error ShaderLanguage::_parse_block(BlockNode *p_block, const FunctionInfo &p_fun
} else {
_set_tkpos(pos); //rollback, wants expression
#ifdef DEBUG_ENABLED
if (b->parent_function->return_type == DataType::TYPE_BOOL) {
keyword_completion_context = CF_BOOLEAN;
}
#endif // DEBUG_ENABLED
Node *expr = _parse_and_reduce_expression(p_block, p_function_info);
if (!expr) {
return ERR_PARSE_ERROR;
@ -7254,6 +7388,12 @@ Error ShaderLanguage::_parse_block(BlockNode *p_block, const FunctionInfo &p_fun
return ERR_PARSE_ERROR;
}
#ifdef DEBUG_ENABLED
if (b->parent_function->return_type == DataType::TYPE_BOOL) {
keyword_completion_context = CF_BLOCK;
}
#endif // DEBUG_ENABLED
flow->expressions.push_back(expr);
}
@ -7480,15 +7620,17 @@ Error ShaderLanguage::_validate_datatype(DataType p_type) {
Error ShaderLanguage::_parse_shader(const Map<StringName, FunctionInfo> &p_functions, const Vector<ModeInfo> &p_render_modes, const Set<String> &p_shader_types) {
Token tk = _get_token();
TkPos prev_pos;
Token next;
if (tk.type != TK_SHADER_TYPE) {
_set_error(vformat(RTR("Expected '%s' at the beginning of shader. Valid types are: %s."), "shader_type", _get_shader_type_list(p_shader_types)));
return ERR_PARSE_ERROR;
}
#ifdef DEBUG_ENABLED
keyword_completion_context = CF_UNSPECIFIED;
#endif // DEBUG_ENABLED
StringName shader_type_identifier;
_get_completable_identifier(nullptr, COMPLETION_SHADER_TYPE, shader_type_identifier);
if (shader_type_identifier == StringName()) {
_set_error(vformat(RTR("Expected an identifier after '%s', indicating the type of shader. Valid types are: %s."), "shader_type", _get_shader_type_list(p_shader_types)));
return ERR_PARSE_ERROR;
@ -7506,6 +7648,9 @@ Error ShaderLanguage::_parse_shader(const Map<StringName, FunctionInfo> &p_funct
return ERR_PARSE_ERROR;
}
#ifdef DEBUG_ENABLED
keyword_completion_context = CF_GLOBAL_SPACE;
#endif // DEBUG_ENABLED
tk = _get_token();
int texture_uniforms = 0;
@ -7599,7 +7744,9 @@ Error ShaderLanguage::_parse_shader(const Map<StringName, FunctionInfo> &p_funct
case TK_STRUCT: {
ShaderNode::Struct st;
DataType type;
#ifdef DEBUG_ENABLED
keyword_completion_context = CF_UNSPECIFIED;
#endif // DEBUG_ENABLED
tk = _get_token();
if (tk.type == TK_IDENTIFIER) {
st.name = tk.text;
@ -7622,7 +7769,12 @@ Error ShaderLanguage::_parse_shader(const Map<StringName, FunctionInfo> &p_funct
int member_count = 0;
Set<String> member_names;
while (true) { // variables list
#ifdef DEBUG_ENABLED
keyword_completion_context = CF_DATATYPE | CF_PRECISION_MODIFIER;
#endif // DEBUG_ENABLED
tk = _get_token();
if (tk.type == TK_CURLY_BRACKET_CLOSE) {
break;
@ -7639,6 +7791,9 @@ Error ShaderLanguage::_parse_shader(const Map<StringName, FunctionInfo> &p_funct
if (is_token_precision(tk.type)) {
precision = get_token_precision(tk.type);
tk = _get_token();
#ifdef DEBUG_ENABLED
keyword_completion_context ^= CF_PRECISION_MODIFIER;
#endif // DEBUG_ENABLED
}
if (shader->structs.has(tk.text)) {
@ -7665,6 +7820,9 @@ Error ShaderLanguage::_parse_shader(const Map<StringName, FunctionInfo> &p_funct
_set_error(vformat(RTR("A '%s' data type is not allowed here."), get_datatype_name(type)));
return ERR_PARSE_ERROR;
}
#ifdef DEBUG_ENABLED
keyword_completion_context = CF_UNSPECIFIED;
#endif // DEBUG_ENABLED
bool first = true;
bool fixed_array_size = false;
@ -7736,12 +7894,19 @@ Error ShaderLanguage::_parse_shader(const Map<StringName, FunctionInfo> &p_funct
_set_error(RTR("Empty structs are not allowed."));
return ERR_PARSE_ERROR;
}
#ifdef DEBUG_ENABLED
keyword_completion_context = CF_UNSPECIFIED;
#endif // DEBUG_ENABLED
tk = _get_token();
if (tk.type != TK_SEMICOLON) {
_set_expected_error(";");
return ERR_PARSE_ERROR;
}
#ifdef DEBUG_ENABLED
keyword_completion_context = CF_GLOBAL_SPACE;
#endif // DEBUG_ENABLED
shader->structs[st.name] = st;
shader->vstructs.push_back(st); // struct's order is important!
#ifdef DEBUG_ENABLED
@ -7751,6 +7916,14 @@ Error ShaderLanguage::_parse_shader(const Map<StringName, FunctionInfo> &p_funct
#endif // DEBUG_ENABLED
} break;
case TK_GLOBAL: {
#ifdef DEBUG_ENABLED
keyword_completion_context = CF_UNIFORM_KEYWORD;
if (_lookup_next(next)) {
if (next.type == TK_UNIFORM) {
keyword_completion_context ^= CF_UNIFORM_KEYWORD;
}
}
#endif // DEBUG_ENABLED
tk = _get_token();
if (tk.type != TK_UNIFORM) {
_set_expected_after_error("uniform", "global");
@ -7760,6 +7933,14 @@ Error ShaderLanguage::_parse_shader(const Map<StringName, FunctionInfo> &p_funct
};
[[fallthrough]];
case TK_INSTANCE: {
#ifdef DEBUG_ENABLED
keyword_completion_context = CF_UNIFORM_KEYWORD;
if (_lookup_next(next)) {
if (next.type == TK_UNIFORM) {
keyword_completion_context ^= CF_UNIFORM_KEYWORD;
}
}
#endif // DEBUG_ENABLED
if (uniform_scope == ShaderNode::Uniform::SCOPE_LOCAL) {
tk = _get_token();
if (tk.type != TK_UNIFORM) {
@ -7773,14 +7954,15 @@ Error ShaderLanguage::_parse_shader(const Map<StringName, FunctionInfo> &p_funct
case TK_UNIFORM:
case TK_VARYING: {
bool uniform = tk.type == TK_UNIFORM;
#ifdef DEBUG_ENABLED
keyword_completion_context = CF_UNSPECIFIED;
#endif // DEBUG_ENABLED
if (!uniform) {
if (shader_type_identifier == "particles" || shader_type_identifier == "sky" || shader_type_identifier == "fog") {
_set_error(vformat(RTR("Varyings cannot be used in '%s' shaders."), shader_type_identifier));
return ERR_PARSE_ERROR;
}
}
DataPrecision precision = PRECISION_DEFAULT;
DataInterpolation interpolation = INTERPOLATION_SMOOTH;
DataType type;
@ -7788,18 +7970,81 @@ Error ShaderLanguage::_parse_shader(const Map<StringName, FunctionInfo> &p_funct
int array_size = 0;
tk = _get_token();
#ifdef DEBUG_ENABLED
bool temp_error = false;
uint32_t datatype_flag;
if (!uniform) {
datatype_flag = CF_VARYING_TYPE;
keyword_completion_context = CF_INTERPOLATION_QUALIFIER | CF_PRECISION_MODIFIER | datatype_flag;
if (_lookup_next(next)) {
if (is_token_interpolation(next.type)) {
keyword_completion_context ^= (CF_INTERPOLATION_QUALIFIER | datatype_flag);
} else if (is_token_precision(next.type)) {
keyword_completion_context ^= (CF_PRECISION_MODIFIER | datatype_flag);
} else if (is_token_datatype(next.type)) {
keyword_completion_context ^= datatype_flag;
}
}
} else {
datatype_flag = CF_UNIFORM_TYPE;
keyword_completion_context = CF_PRECISION_MODIFIER | datatype_flag;
if (_lookup_next(next)) {
if (is_token_precision(next.type)) {
keyword_completion_context ^= (CF_PRECISION_MODIFIER | datatype_flag);
} else if (is_token_datatype(next.type)) {
keyword_completion_context ^= datatype_flag;
}
}
}
#endif // DEBUG_ENABLED
if (is_token_interpolation(tk.type)) {
if (uniform) {
_set_error(RTR("Interpolation qualifiers are not supported for uniforms."));
#ifdef DEBUG_ENABLED
temp_error = true;
#else
return ERR_PARSE_ERROR;
#endif // DEBUG_ENABLED
}
interpolation = get_token_interpolation(tk.type);
tk = _get_token();
#ifdef DEBUG_ENABLED
if (keyword_completion_context & CF_INTERPOLATION_QUALIFIER) {
keyword_completion_context ^= CF_INTERPOLATION_QUALIFIER;
}
if (_lookup_next(next)) {
if (is_token_precision(next.type)) {
keyword_completion_context ^= CF_PRECISION_MODIFIER;
} else if (is_token_datatype(next.type)) {
keyword_completion_context ^= datatype_flag;
}
}
if (temp_error) {
return ERR_PARSE_ERROR;
}
#endif // DEBUG_ENABLED
}
if (is_token_precision(tk.type)) {
precision = get_token_precision(tk.type);
tk = _get_token();
#ifdef DEBUG_ENABLED
if (keyword_completion_context & CF_INTERPOLATION_QUALIFIER) {
keyword_completion_context ^= CF_INTERPOLATION_QUALIFIER;
}
if (keyword_completion_context & CF_PRECISION_MODIFIER) {
keyword_completion_context ^= CF_PRECISION_MODIFIER;
}
if (_lookup_next(next)) {
if (is_token_datatype(next.type)) {
keyword_completion_context = CF_UNSPECIFIED;
}
}
#endif // DEBUG_ENABLED
}
if (shader->structs.has(tk.text)) {
@ -7833,6 +8078,9 @@ Error ShaderLanguage::_parse_shader(const Map<StringName, FunctionInfo> &p_funct
return ERR_PARSE_ERROR;
}
#ifdef DEBUG_ENABLED
keyword_completion_context = CF_UNSPECIFIED;
#endif // DEBUG_ENABLED
tk = _get_token();
if (tk.type != TK_IDENTIFIER && tk.type != TK_BRACKET_OPEN) {
@ -8204,6 +8452,9 @@ Error ShaderLanguage::_parse_shader(const Map<StringName, FunctionInfo> &p_funct
return ERR_PARSE_ERROR;
}
#ifdef DEBUG_ENABLED
keyword_completion_context = CF_GLOBAL_SPACE;
#endif // DEBUG_ENABLED
completion_type = COMPLETION_NONE;
} else { // varying
ShaderNode::Varying varying;
@ -8269,6 +8520,13 @@ Error ShaderLanguage::_parse_shader(const Map<StringName, FunctionInfo> &p_funct
is_struct = true;
struct_name = tk.text;
} else {
#ifdef DEBUG_ENABLED
if (_lookup_next(next)) {
if (next.type == TK_UNIFORM) {
keyword_completion_context = CF_UNIFORM_QUALIFIER;
}
}
#endif // DEBUG_ENABLED
if (!is_token_datatype(tk.type)) {
_set_error(RTR("Expected constant, function, uniform or varying."));
return ERR_PARSE_ERROR;
@ -8297,6 +8555,10 @@ Error ShaderLanguage::_parse_shader(const Map<StringName, FunctionInfo> &p_funct
prev_pos = _get_tkpos();
tk = _get_token();
#ifdef DEBUG_ENABLED
keyword_completion_context = CF_UNSPECIFIED;
#endif // DEBUG_ENABLED
bool unknown_size = false;
bool fixed_array_size = false;
@ -8533,11 +8795,22 @@ Error ShaderLanguage::_parse_shader(const Map<StringName, FunctionInfo> &p_funct
constant.initializer = static_cast<ConstantNode *>(expr);
} else {
#ifdef DEBUG_ENABLED
if (constant.type == DataType::TYPE_BOOL) {
keyword_completion_context = CF_BOOLEAN;
}
#endif // DEBUG_ENABLED
//variable created with assignment! must parse an expression
Node *expr = _parse_and_reduce_expression(nullptr, constants);
if (!expr) {
return ERR_PARSE_ERROR;
}
#ifdef DEBUG_ENABLED
if (constant.type == DataType::TYPE_BOOL) {
keyword_completion_context = CF_GLOBAL_SPACE;
}
#endif // DEBUG_ENABLED
if (expr->type == Node::TYPE_OPERATOR && static_cast<OperatorNode *>(expr)->op == OP_CALL) {
OperatorNode *op = static_cast<OperatorNode *>(expr);
for (int i = 1; i < op->arguments.size(); i++) {
@ -8669,31 +8942,88 @@ Error ShaderLanguage::_parse_shader(const Map<StringName, FunctionInfo> &p_funct
if (tk.type == TK_PARENTHESIS_CLOSE) {
break;
}
#ifdef DEBUG_ENABLED
keyword_completion_context = CF_CONST_KEYWORD | CF_FUNC_DECL_PARAM_SPEC | CF_PRECISION_MODIFIER | CF_FUNC_DECL_PARAM_TYPE; // eg. const in mediump float
if (_lookup_next(next)) {
if (next.type == TK_CONST) {
keyword_completion_context = CF_UNSPECIFIED;
} else if (is_token_arg_qual(next.type)) {
keyword_completion_context = CF_CONST_KEYWORD;
} else if (is_token_precision(next.type)) {
keyword_completion_context = (CF_CONST_KEYWORD | CF_FUNC_DECL_PARAM_SPEC | CF_FUNC_DECL_PARAM_TYPE);
} else if (is_token_datatype(next.type)) {
keyword_completion_context = (CF_CONST_KEYWORD | CF_FUNC_DECL_PARAM_SPEC | CF_PRECISION_MODIFIER);
}
}
#endif // DEBUG_ENABLED
bool param_is_const = false;
if (tk.type == TK_CONST) {
param_is_const = true;
tk = _get_token();
#ifdef DEBUG_ENABLED
if (keyword_completion_context & CF_CONST_KEYWORD) {
keyword_completion_context ^= CF_CONST_KEYWORD;
}
if (_lookup_next(next)) {
if (is_token_arg_qual(next.type)) {
keyword_completion_context = CF_UNSPECIFIED;
} else if (is_token_precision(next.type)) {
keyword_completion_context = (CF_FUNC_DECL_PARAM_SPEC | CF_FUNC_DECL_PARAM_TYPE);
} else if (is_token_datatype(next.type)) {
keyword_completion_context = (CF_FUNC_DECL_PARAM_SPEC | CF_PRECISION_MODIFIER);
}
}
#endif // DEBUG_ENABLED
}
ArgumentQualifier param_qualifier = ARGUMENT_QUALIFIER_IN;
if (tk.type == TK_ARG_IN) {
param_qualifier = ARGUMENT_QUALIFIER_IN;
if (is_token_arg_qual(tk.type)) {
bool error = false;
switch (tk.type) {
case TK_ARG_IN: {
param_qualifier = ARGUMENT_QUALIFIER_IN;
} break;
case TK_ARG_OUT: {
if (param_is_const) {
_set_error(vformat(RTR("The '%s' qualifier cannot be used within a function parameter declared with '%s'."), "out", "const"));
error = true;
}
param_qualifier = ARGUMENT_QUALIFIER_OUT;
} break;
case TK_ARG_INOUT: {
if (param_is_const) {
_set_error(vformat(RTR("The '%s' qualifier cannot be used within a function parameter declared with '%s'."), "inout", "const"));
error = true;
}
param_qualifier = ARGUMENT_QUALIFIER_INOUT;
} break;
default:
error = true;
break;
}
tk = _get_token();
} else if (tk.type == TK_ARG_OUT) {
if (param_is_const) {
_set_error(vformat(RTR("The '%s' qualifier cannot be used within a function parameter declared with '%s'."), "out", "const"));
#ifdef DEBUG_ENABLED
if (keyword_completion_context & CF_CONST_KEYWORD) {
keyword_completion_context ^= CF_CONST_KEYWORD;
}
if (keyword_completion_context & CF_FUNC_DECL_PARAM_SPEC) {
keyword_completion_context ^= CF_FUNC_DECL_PARAM_SPEC;
}
if (_lookup_next(next)) {
if (is_token_precision(next.type)) {
keyword_completion_context = CF_FUNC_DECL_PARAM_TYPE;
} else if (is_token_datatype(next.type)) {
keyword_completion_context = CF_PRECISION_MODIFIER;
}
}
#endif // DEBUG_ENABLED
if (error) {
return ERR_PARSE_ERROR;
}
param_qualifier = ARGUMENT_QUALIFIER_OUT;
tk = _get_token();
} else if (tk.type == TK_ARG_INOUT) {
if (param_is_const) {
_set_error(vformat(RTR("The '%s' qualifier cannot be used within a function parameter declared with '%s'."), "inout", "const"));
return ERR_PARSE_ERROR;
}
param_qualifier = ARGUMENT_QUALIFIER_INOUT;
tk = _get_token();
}
DataType param_type;
@ -8705,6 +9035,23 @@ Error ShaderLanguage::_parse_shader(const Map<StringName, FunctionInfo> &p_funct
if (is_token_precision(tk.type)) {
param_precision = get_token_precision(tk.type);
tk = _get_token();
#ifdef DEBUG_ENABLED
if (keyword_completion_context & CF_CONST_KEYWORD) {
keyword_completion_context ^= CF_CONST_KEYWORD;
}
if (keyword_completion_context & CF_FUNC_DECL_PARAM_SPEC) {
keyword_completion_context ^= CF_FUNC_DECL_PARAM_SPEC;
}
if (keyword_completion_context & CF_PRECISION_MODIFIER) {
keyword_completion_context ^= CF_PRECISION_MODIFIER;
}
if (_lookup_next(next)) {
if (is_token_datatype(next.type)) {
keyword_completion_context = CF_UNSPECIFIED;
}
}
#endif // DEBUG_ENABLED
}
is_struct = false;
@ -8747,7 +9094,9 @@ Error ShaderLanguage::_parse_shader(const Map<StringName, FunctionInfo> &p_funct
if (param_precision != PRECISION_DEFAULT && _validate_precision(param_type, param_precision) != OK) {
return ERR_PARSE_ERROR;
}
#ifdef DEBUG_ENABLED
keyword_completion_context = CF_UNSPECIFIED;
#endif // DEBUG_ENABLED
tk = _get_token();
if (tk.type == TK_BRACKET_OPEN) {
@ -8831,11 +9180,16 @@ Error ShaderLanguage::_parse_shader(const Map<StringName, FunctionInfo> &p_funct
current_function = name;
#ifdef DEBUG_ENABLED
keyword_completion_context = CF_BLOCK;
#endif // DEBUG_ENABLED
Error err = _parse_block(func_node->body, builtins);
if (err) {
return err;
}
#ifdef DEBUG_ENABLED
keyword_completion_context = CF_GLOBAL_SPACE;
#endif // DEBUG_ENABLED
if (func_node->return_type != DataType::TYPE_VOID) {
BlockNode *block = func_node->body;
if (_find_last_flow_op_in_block(block, FlowOperation::FLOW_OP_RETURN) != OK) {
@ -9070,6 +9424,28 @@ Error ShaderLanguage::complete(const String &p_code, const ShaderCompileInfo &p_
shader = alloc_node<ShaderNode>();
_parse_shader(p_info.functions, p_info.render_modes, p_info.shader_types);
#ifdef DEBUG_ENABLED
// Adds context keywords.
if (keyword_completion_context != CF_UNSPECIFIED) {
int sz = sizeof(keyword_list) / sizeof(KeyWord);
for (int i = 0; i < sz; i++) {
if (keyword_list[i].flags == CF_UNSPECIFIED) {
break; // Ignore hint keywords (parsed below).
}
if (keyword_list[i].flags & keyword_completion_context) {
if (keyword_list[i].excluded_shader_types.has(shader_type_identifier)) {
continue;
}
if (!keyword_list[i].functions.is_empty() && !keyword_list[i].functions.has(current_function)) {
continue;
}
ScriptLanguage::CodeCompletionOption option(keyword_list[i].text, ScriptLanguage::CODE_COMPLETION_KIND_PLAIN_TEXT);
r_options->push_back(option);
}
}
}
#endif // DEBUG_ENABLED
switch (completion_type) {
case COMPLETION_NONE: {
//do nothing

View File

@ -756,6 +756,7 @@ public:
static bool is_token_interpolation(TokenType p_type);
static DataInterpolation get_token_interpolation(TokenType p_type);
static bool is_token_precision(TokenType p_type);
static bool is_token_arg_qual(TokenType p_type);
static DataPrecision get_token_precision(TokenType p_type);
static String get_precision_name(DataPrecision p_type);
static String get_datatype_name(DataType p_type);
@ -870,6 +871,9 @@ private:
struct KeyWord {
TokenType token;
const char *text;
uint32_t flags;
const Vector<String> excluded_shader_types;
const Vector<String> functions;
};
static const KeyWord keyword_list[];
@ -920,6 +924,7 @@ private:
int char_idx;
int tk_line;
StringName shader_type_identifier;
StringName current_function;
bool last_const = false;
StringName last_name;
@ -972,6 +977,7 @@ private:
Token _make_token(TokenType p_type, const StringName &p_text = StringName());
Token _get_token();
bool _lookup_next(Token &r_tk);
ShaderNode *shader = nullptr;
@ -1029,6 +1035,10 @@ private:
StringName completion_struct;
int completion_argument;
#ifdef DEBUG_ENABLED
uint32_t keyword_completion_context;
#endif // DEBUG_ENABLED
const Map<StringName, FunctionInfo> *stages = nullptr;
bool _get_completable_identifier(BlockNode *p_block, CompletionType p_type, StringName &identifier);