#include "gfx/uniform_parser.h" #include #include #include #include #include #include namespace fn::gfx { static const std::unordered_set k_reserved = { "u_resolution", "u_time", "u_mouse" }; // Parse a comma-separated list of up to 4 floats into out[]. Returns count parsed. static int parse_floats(const std::string& s, float out[4]) { int n = 0; std::istringstream ss(s); std::string tok; while (n < 4 && std::getline(ss, tok, ',')) { try { out[n++] = std::stof(tok); } catch (...) {} } return n; } // Parse `key=value` pairs from annotation tail. static std::unordered_map parse_props(const std::string& tail) { std::unordered_map props; std::regex kv_re(R"((\w+)=([^\s]+))"); auto begin = std::sregex_iterator(tail.begin(), tail.end(), kv_re); auto end = std::sregex_iterator(); for (auto it = begin; it != end; ++it) { props[(*it)[1].str()] = (*it)[2].str(); } return props; } static void apply_defaults_for_type(UniformDescriptor& d) { switch (d.glsl_type) { case GLSLType::Float: d.widget = WidgetKind::Slider; d.min[0] = 0.0f; d.max[0] = 1.0f; d.defaults[0] = 0.0f; break; case GLSLType::Int: d.widget = WidgetKind::Slider; d.min[0] = 0.0f; d.max[0] = 10.0f; d.step = 1.0f; d.defaults[0] = 0.0f; break; case GLSLType::Bool: d.widget = WidgetKind::Toggle; d.default_bool = false; d.defaults[0] = 0.0f; break; case GLSLType::Vec2: d.widget = WidgetKind::XY; d.min[0] = d.min[1] = 0.0f; d.max[0] = d.max[1] = 1.0f; d.defaults[0] = d.defaults[1] = 0.5f; break; case GLSLType::Vec3: d.widget = WidgetKind::Color; d.defaults[0] = d.defaults[1] = d.defaults[2] = 1.0f; break; case GLSLType::Vec4: d.widget = WidgetKind::Color; d.defaults[0] = d.defaults[1] = d.defaults[2] = d.defaults[3] = 1.0f; break; } } static void apply_props(UniformDescriptor& d, const std::unordered_map& props) { auto get = [&](const char* k) -> const std::string* { auto it = props.find(k); return it != props.end() ? &it->second : nullptr; }; if (auto* v = get("min")) { float tmp[4] = {d.min[0], d.min[1], d.min[2], d.min[3]}; int n = parse_floats(*v, tmp); for (int i = 0; i < 4; i++) d.min[i] = (i < n) ? tmp[i] : tmp[n > 0 ? n-1 : 0]; } if (auto* v = get("max")) { float tmp[4] = {d.max[0], d.max[1], d.max[2], d.max[3]}; int n = parse_floats(*v, tmp); for (int i = 0; i < 4; i++) d.max[i] = (i < n) ? tmp[i] : tmp[n > 0 ? n-1 : 0]; } if (auto* v = get("default")) { if (d.glsl_type == GLSLType::Bool) { d.default_bool = (*v == "true" || *v == "1"); d.defaults[0] = d.default_bool ? 1.0f : 0.0f; } else { float tmp[4] = {}; int n = parse_floats(*v, tmp); for (int i = 0; i < 4; i++) d.defaults[i] = (i < n) ? tmp[i] : 0.0f; } } if (auto* v = get("step")) { try { d.step = std::stof(*v); } catch (...) {} } if (auto* v = get("log")) { d.log_scale = (*v == "true"); } } std::vector parse_uniforms(const std::string& glsl_source) { // Match: uniform ; optionally followed by // @ static const std::regex line_re( R"(^\s*uniform\s+(\w+)\s+(\w+)\s*;(?:\s*//\s*@(\w+)(.*))?\s*$)" ); std::vector result; std::istringstream stream(glsl_source); std::string line; while (std::getline(stream, line)) { std::smatch m; if (!std::regex_match(line, m, line_re)) continue; std::string type_str = m[1].str(); std::string name_str = m[2].str(); bool has_annotation = m[3].matched; std::string widget_str = has_annotation ? m[3].str() : ""; std::string props_str = has_annotation ? m[4].str() : ""; // Skip reserved and unsupported if (k_reserved.count(name_str)) continue; if (type_str == "sampler2D") continue; UniformDescriptor d; d.name = name_str; // Map GLSL type if (type_str == "float") d.glsl_type = GLSLType::Float; else if (type_str == "int") d.glsl_type = GLSLType::Int; else if (type_str == "bool") d.glsl_type = GLSLType::Bool; else if (type_str == "vec2") d.glsl_type = GLSLType::Vec2; else if (type_str == "vec3") d.glsl_type = GLSLType::Vec3; else if (type_str == "vec4") d.glsl_type = GLSLType::Vec4; else continue; // unknown type — skip // Set defaults for type first apply_defaults_for_type(d); if (has_annotation) { // Override widget kind if (widget_str == "slider") { d.widget = (d.glsl_type == GLSLType::Vec2) ? WidgetKind::XY : WidgetKind::Slider; } else if (widget_str == "color") d.widget = WidgetKind::Color; else if (widget_str == "toggle") d.widget = WidgetKind::Toggle; else if (widget_str == "xy") d.widget = WidgetKind::XY; auto props = parse_props(props_str); apply_props(d, props); } result.push_back(d); } return result; } } // namespace fn::gfx // ────────────────────────────────────────────── // Inline tests — compile with -DUNIFORM_PARSER_TEST // ────────────────────────────────────────────── #ifdef UNIFORM_PARSER_TEST #include #include using namespace fn::gfx; int main() { // Test 1: sin anotación float → Slider 0..1 { auto v = parse_uniforms("uniform float u_x;"); assert(v.size() == 1); assert(v[0].name == "u_x"); assert(v[0].glsl_type == GLSLType::Float); assert(v[0].widget == WidgetKind::Slider); assert(v[0].min[0] == 0.0f); assert(v[0].max[0] == 1.0f); assert(v[0].defaults[0] == 0.0f); printf("PASS: sin anotacion float -> Slider 0..1\n"); } // Test 2: @slider min=0 max=5 default=1 sobre float { auto v = parse_uniforms("uniform float u_speed; // @slider min=0 max=5 default=1"); assert(v.size() == 1); assert(v[0].widget == WidgetKind::Slider); assert(v[0].min[0] == 0.0f); assert(v[0].max[0] == 5.0f); assert(v[0].defaults[0] == 1.0f); printf("PASS: @slider min=0 max=5 default=1 sobre float\n"); } // Test 3: @color default=0.1,0.2,0.5 sobre vec3 { auto v = parse_uniforms("uniform vec3 u_color; // @color default=0.1,0.2,0.5"); assert(v.size() == 1); assert(v[0].glsl_type == GLSLType::Vec3); assert(v[0].widget == WidgetKind::Color); assert(v[0].defaults[0] == 0.1f); assert(v[0].defaults[1] == 0.2f); assert(v[0].defaults[2] == 0.5f); printf("PASS: @color default=0.1,0.2,0.5 sobre vec3\n"); } // Test 4: @xy min=-1 max=1 default=0,0 sobre vec2 { auto v = parse_uniforms("uniform vec2 u_pos; // @xy min=-1 max=1 default=0,0"); assert(v.size() == 1); assert(v[0].glsl_type == GLSLType::Vec2); assert(v[0].widget == WidgetKind::XY); assert(v[0].min[0] == -1.0f); assert(v[0].max[0] == 1.0f); assert(v[0].defaults[0] == 0.0f); assert(v[0].defaults[1] == 0.0f); printf("PASS: @xy min=-1 max=1 default=0,0 sobre vec2\n"); } // Test 5: uniform vec2 u_resolution ignorado (reservado) { auto v = parse_uniforms("uniform vec2 u_resolution;"); assert(v.empty()); printf("PASS: uniform vec2 u_resolution ignorado\n"); } // Test 6: @toggle default=true sobre bool { auto v = parse_uniforms("uniform bool u_debug; // @toggle default=true"); assert(v.size() == 1); assert(v[0].glsl_type == GLSLType::Bool); assert(v[0].widget == WidgetKind::Toggle); assert(v[0].default_bool == true); assert(v[0].defaults[0] == 1.0f); printf("PASS: @toggle default=true sobre bool\n"); } printf("---\nAll 6 tests passed.\n"); return 0; } #endif