From a758eb0b84a91a2cdd08ec42e2e9074a84aa348e Mon Sep 17 00:00:00 2001 From: Artem Chernyshev Date: Fri, 15 Nov 2019 03:31:52 +0300 Subject: [PATCH] C++ element definitions now support any kind of arithmetic types Updated static list parser. Now it can have same syntax as any script may have. E.g.: - `{1,2,3}` lua style list; - `[1,2,3]` js style list; - `(a,b,c)` python tuple; Old syntax without brackets is also supported. Additionally fixed several issues in tests, improved test coverage, added tests for parser, field validation. Found a problem with `required` field not working for fields that are missing from the xml node (only if field parsing failed we were getting an error). --- README.md | 2 +- src/imvue.cpp | 26 --- src/imvue_element.cpp | 27 ++- src/imvue_element.h | 313 +++++++++++++++++--------- src/imvue_generated.h | 19 ++ src/imvue_script.cpp | 18 +- src/imvue_script.h | 26 ++- src/lua/script.cpp | 4 +- tests/unit/imvue_tests.cpp | 4 +- tests/unit/parser.cpp | 316 +++++++++++++++++++++++++++ tools/config.yaml | 2 + tools/templates/imvue_generated.h.j2 | 19 ++ 12 files changed, 621 insertions(+), 155 deletions(-) diff --git a/README.md b/README.md index 1965a05..67b699b 100644 --- a/README.md +++ b/README.md @@ -46,7 +46,7 @@ Vue Special Syntax - `v-if/v-else-if/v-else`. - `v-for` (does not support int index e.g.: `value, key, index`). - `v-on`. - - `v-on:(click:mousedown|mouseup|mouseover|mouseout)[.[ctrl|alt|meta|shift|exact]]`. + - `v-on:(click|mousedown|mouseup|mouseover|mouseout)[.[ctrl|alt|meta|shift|exact]]`. - `v-on:(keyup|keydown|keypress)[.][]`. - `v-on:change`. - Attributes starting with `:` are treated as `v-bind:...`. diff --git a/src/imvue.cpp b/src/imvue.cpp index 2d461ae..ed7a780 100644 --- a/src/imvue.cpp +++ b/src/imvue.cpp @@ -257,32 +257,6 @@ namespace ImVue { return; } - if(!mConfigured) { - // register some built-in element types - mCtx->factory->element("template"); - mCtx->factory->element("__element__") - .handler("click") - .handler("mousedown") - .handler("mouseup") - .handler("mouseover") - .handler("mouseout") - .handler("mouseout") - .handler("change") - .handler("keydown") - .handler("keyup") - .handler("keypress") - .attribute("id", &Element::id) - .attribute("key", &Element::key) - .attribute("ref", &Element::ref); - - mCtx->factory->element("slot"); - - mCtx->factory->element("svg-image") - .attribute("size", &SvgImage::size) - .attribute("src", &SvgImage::src, true) - .attribute("tint-col", &SvgImage::tint_col); - } - mScriptState = mCtx->script; std::stringstream ss; diff --git a/src/imvue_element.cpp b/src/imvue_element.cpp index bdd74a4..ab919f8 100644 --- a/src/imvue_element.cpp +++ b/src/imvue_element.cpp @@ -225,6 +225,7 @@ namespace ImVue { , mScriptContext(0) , mInvalidFlags(0) , mFlags(0) + , mRequiredAttrsCount(0) , mConfigured(false) { } @@ -349,6 +350,9 @@ namespace ImVue { Attribute* reader = mBuilder->get(id); if(reader) { reader->read(id, value, this, mScriptState, flags, fields); + if(reader->required) { + mRequiredAttrsCount++; + } return true; } @@ -366,6 +370,7 @@ namespace ImVue { } int flags = mConfigured ? 0 : Attribute::BIND_LISTENERS; + mRequiredAttrsCount = 0; for(const rapidxml::xml_attribute<>* a = mNode->first_attribute(); a; a = a->next_attribute()) { readProperty(a->name(), a->value(), flags); @@ -376,6 +381,26 @@ namespace ImVue { if(ref && mScriptState) { mScriptState->addReference(ref, this); } + + const ElementBuilder::RequiredAttrs& requiredAttrs = mBuilder->getRequiredAttrs(); + if(mRequiredAttrsCount != requiredAttrs.size()) { + std::map visited; + // failed validation, now scan props and detect which one is missing + for(const rapidxml::xml_attribute<>* a = mNode->first_attribute(); a; a = a->next_attribute()) { + visited[a->name()] = true; + } + + std::stringstream missed; + for(int i = 0; i < requiredAttrs.size(); ++i) { + const char* name = requiredAttrs[i]; + if(!visited[name]) { + missed << (missed.str().empty() ? "" : ", ") << '"' << name << '"'; + } + } + + IMVUE_EXCEPTION(ElementError, "failed to build element %s, missing required properties %s", getType(), missed.str().c_str()); + return false; + } return enabled; } @@ -483,7 +508,7 @@ namespace ImVue { EventHandler* handler = builder->createHandler(handlerName, fullName, value); if(!handler) { - IMVUE_EXCEPTION(ElementError, "failed create handler of type %s", handlerName); + IMVUE_EXCEPTION(ElementError, "failed to create handler of type %s", handlerName); return; } mHandlers[name] = handler; diff --git a/src/imvue_element.h b/src/imvue_element.h index 63b3a90..900e4a4 100644 --- a/src/imvue_element.h +++ b/src/imvue_element.h @@ -36,51 +36,19 @@ SOFTWARE. #include #include #include - -#define PARSE_NUMERIC_TYPE(type) \ - inline bool read(const char* value, type* dest) { \ - *dest = parse_integral_type(value); \ - return true; \ - } \ - inline bool read(Object& obj, type* dest) { \ - *dest = obj.as(); \ - return true; \ - } \ - inline bool read(Object& obj, type** dest) \ - { \ - if(*dest) { \ - ImGui::MemFree(*dest); \ - *dest = NULL; \ - } \ - \ - size_t index = 0; \ - ImVector values; \ - for(Object::iterator iter = obj.begin(); iter != obj.end(); ++iter, ++index) { \ - values.push_back(iter.value.as()); \ - } \ - \ - *dest = (type*)ImGui::MemAlloc(values.size()); \ - memcpy(*dest, values.Data, sizeof(type) * values.size()); \ - return true; \ - } \ - inline bool read(const char* value, type** dest) \ - { \ - if(*dest) { \ - ImGui::MemFree(*dest); \ - *dest = NULL; \ - } \ - ImVector values; \ - parse_integral_type_array(value, values); \ - if(values.size() == 0) { \ - return false; \ - } \ - *dest = (type*)ImGui::MemAlloc(values.size() * sizeof(type)); \ - memcpy(*dest, values.Data, sizeof(type) * values.size()); \ - return true; \ - } +#include +#include namespace ImVue { + struct CmpChar { + + bool operator()(const char* a, const char* b) const + { + return std::strcmp(a, b) < 0; + } + }; + /** * ID for element text pseudo-attribute name */ @@ -91,32 +59,56 @@ namespace ImVue { namespace detail { template - C parse_integral_type(const char* value) { - C res; - std::stringstream ss; - ss << value; - ss >> res; - return res; + typename std::enable_if::value, bool>::type + str_to_number(const char* value, C* dest) { + C res = 0; + try { + switch(sizeof(C)) { + case sizeof(float): + res = (C)std::stof(value); + break; + case sizeof(double): + res = (C)std::stod(value); + break; + case sizeof(long double): + res = (C)std::stold(value); + break; + } + } catch(std::exception const & e) { + return false; + } + *dest = res; + return true; } template - void parse_integral_type_array(const char* value, ImVector& values) { - char buffer[64]; - int index = 0; - for(int i = 0; i < 1024; i++) { - char c = value[i]; - - if(c == ',' || c == '\0') { - buffer[index] = '\0'; - values.push_back(parse_integral_type(&buffer[0])); - index = 0; + typename std::enable_if::value, bool>::type + str_to_number(const char* value, C* dest) { + C res = 0; + try { + if(std::is_signed::value) { + switch(sizeof(C)) { + case sizeof(long long): + res = (C)std::stoll(value); + break; + default: + res = (C)std::stol(value); + } } else { - buffer[index++] = c; + switch(sizeof(C)) { + case sizeof(unsigned long long): + res = (C)std::stoull(value); + break; + default: + res = (C)std::stoul(value); + } } - - if(c == '\0') - break; + } catch(std::exception const & e) { + return false; } + + *dest = res; + return true; } inline bool read(const char* value, char** dest) { @@ -130,13 +122,14 @@ namespace ImVue { return true; } - PARSE_NUMERIC_TYPE(int) - PARSE_NUMERIC_TYPE(float) - PARSE_NUMERIC_TYPE(double) - PARSE_NUMERIC_TYPE(size_t) + template + bool parse_array(const char* value, ImVector& values); inline bool read(const char* value, ImGuiID* dest) { - *dest = ImHashStr(value); + if(!str_to_number(value, dest)) { + *dest = ImHashStr(value); + } + return true; } @@ -152,15 +145,16 @@ namespace ImVue { return true; } - *dest = parse_integral_type(value); - return true; + return str_to_number(value, dest); } inline bool read(const char* value, ImVec2* dest) { ImVector values; values.reserve(2); - parse_integral_type_array(value, values); + if(!parse_array(value, values)) { + return false; + } dest->x = values[0]; dest->y = values[1]; return true; @@ -170,7 +164,9 @@ namespace ImVue { { ImVector values; values.reserve(4); - parse_integral_type_array(value, values); + if(!parse_array(value, values)) { + return false; + } dest->x = values[0]; dest->y = values[1]; dest->z = values[2]; @@ -183,19 +179,39 @@ namespace ImVue { { ImVector values; values.reserve(N); - parse_integral_type_array(value, values); + if(!parse_array(value, values) || values.size() != N) { + return false; + } memcpy(*dest, values.Data, N * sizeof(C)); return true; } - inline bool read(Object object, Object* dest) + template + typename std::enable_if::value, bool>::type + read(const char* value, C** dest) { - *dest = object; + if(*dest) { + ImGui::MemFree(*dest); + *dest = NULL; + } + ImVector values; + if(!parse_array(value, values)) { + return false; + } + *dest = (C*)ImGui::MemAlloc(values.size_in_bytes()); + memcpy(*dest, values.Data, values.size_in_bytes()); return true; } template - bool read(const char* value, C dest) + typename std::enable_if::value, bool>::type + read(const char* value, C* dest) { + return str_to_number(value, dest); + } + + template + typename std::enable_if::value, bool>::type + read(const char* value, C* dest) { IM_ASSERT("Detected unsupported field"); (void)value; @@ -204,8 +220,73 @@ namespace ImVue { return false; } + template + bool parse_array(const char* value, ImVector& values) + { + char endsym = 0; + const char* start = value; + while(isspace(start[0])) { + start++; + } + switch(start[0]) { + case '{': + endsym = '}'; + start++; + break; + case '[': + endsym = ']'; + start++; + break; + case '(': + endsym = ')'; + start++; + break; + } + + const char* end = value + strlen(value); + const size_t bufferSize = 512; + char part[bufferSize] = {0}; + + end = (endsym == 0 ? end : ImStrchrRange(value, end, endsym)) - 1; + while(start < end + 1) { + const char* next = ImStrchrRange(start, end, ','); + if(next == 0) { + next = end + 1; + } + + size_t len = (size_t)(next - start); + if(len == 0) { + break; + } + + if(len > bufferSize) { + IMVUE_EXCEPTION(ElementError, "failed to parse array element. Max supported length is %d, got %d", bufferSize, len); + start = next + 1; + return false; + } + + memcpy(&part[0], start, len); + part[len + 1] = '\0'; + + C element; + if(read(&part[0], &element)) { + values.push_back(element); + } else { + return false; + } + start = next + 1; + } + return true; + } + // Script state object conversion + inline bool read(Object object, Object* dest) + { + *dest = object; + return true; + } + template inline bool read(Object& obj, C (*dest)[N]) { @@ -217,7 +298,16 @@ namespace ImVue { } inline bool read(Object& obj, ImGuiID* dest) { - *dest = ImHashStr(obj.as().get()); + switch(obj.type()) { + case ObjectType::STRING: + *dest = ImHashStr(obj.as().get()); + break; + case ObjectType::NUMBER: + *dest = obj.as(); + break; + default: + return false; + } return true; } @@ -262,7 +352,20 @@ namespace ImVue { } template - bool read(Object& value, C dest) + typename std::enable_if::value, bool>::type + read(Object& value, C* dest) + { + try { + *dest = value.as(); + } catch(const ScriptError& e) { + return false; + } + return true; + } + + template + typename std::enable_if::value, bool>::type + read(Object& value, C* dest) { IM_ASSERT("Detected unsupported field"); (void)value; @@ -472,6 +575,7 @@ namespace ImVue { unsigned int mInvalidFlags; unsigned int mFlags; + int mRequiredAttrsCount; bool mConfigured; @@ -500,6 +604,11 @@ namespace ImVue { SCRIPT = 1 << 2 }; + Attribute(bool r = false) + : required(r) + { + } + virtual ~Attribute() {} /** @@ -529,6 +638,8 @@ namespace ImVue { * @return true if copy was successful */ virtual bool copy(Element* element, Object& dest) const = 0; + + bool required; }; /** @@ -538,8 +649,8 @@ namespace ImVue { class AttributeMemPtr : public Attribute { public: AttributeMemPtr(D C::* memPtr, bool required = false) - : mMemPtr(memPtr) - , mRequired(required) + : Attribute(required) + , mMemPtr(memPtr) { } @@ -598,7 +709,7 @@ namespace ImVue { success = detail::read(str, dest); } - if(!success && mRequired) { + if(!success && required) { IMVUE_EXCEPTION(ElementError, "failed to read required attribute %s", attribute); return; } @@ -609,9 +720,9 @@ namespace ImVue { D* value = &(static_cast(element)->*mMemPtr); return dest.set(value, typeID::value()); } + private: D C::* mMemPtr; - bool mRequired; }; /** @@ -640,6 +751,9 @@ namespace ImVue { */ class ElementBuilder { public: + typedef ImVector RequiredAttrs; + RequiredAttrs mRequiredAttrs; + virtual ~ElementBuilder() { for(Attributes::iterator iter = mAttributes.begin(); iter != mAttributes.end(); ++iter) { @@ -673,6 +787,11 @@ namespace ImVue { return nullptr; } + const ElementBuilder::RequiredAttrs& getRequiredAttrs() const + { + return mRequiredAttrs; + } + protected: friend class ElementFactory; @@ -695,23 +814,22 @@ namespace ImVue { mHandlers[iter->first] = iter->second; } } + + if(other.mRequiredAttrs.size() != 0) { + int oldSize = mRequiredAttrs.size(); + mRequiredAttrs.resize(oldSize + other.mRequiredAttrs.size()); + memcpy(&mRequiredAttrs.Data[oldSize], other.mRequiredAttrs.Data, other.mRequiredAttrs.size_in_bytes()); + } } int getLayer(ElementFactory* f); void readInheritance(ElementFactory* f); - struct CmpAttributeName { - bool operator()(const char* a, const char* b) const - { - return std::strcmp(const_cast(a), const_cast(b)) < 0; - } - }; - - typedef std::map Attributes; + typedef std::map Attributes; Attributes mAttributes; - typedef std::map Handlers; + typedef std::map Handlers; Handlers mHandlers; typedef std::vector Inheritance; @@ -749,6 +867,9 @@ namespace ImVue { { mAttributes[name] = new AttributeMemPtr(memPtr, required); mAttributes[name]->owner = this; + if(required) { + mRequiredAttrs.push_back(name); + } return *this; } @@ -763,6 +884,9 @@ namespace ImVue { { mAttributes[TEXT_ID] = new AttributeMemPtr(memPtr, required); mAttributes[TEXT_ID]->owner = this; + if(required) { + mRequiredAttrs.push_back(TEXT_ID); + } return *this; } @@ -801,14 +925,7 @@ namespace ImVue { */ class ElementFactory { - struct CmpElementID { - bool operator()(const char* a, const char* b) const - { - return std::strcmp(a, b) < 0; - } - }; - - typedef std::map ElementBuilders; + typedef std::map ElementBuilders; public: ElementFactory() diff --git a/src/imvue_generated.h b/src/imvue_generated.h index 2bee75e..cbaa09d 100644 --- a/src/imvue_generated.h +++ b/src/imvue_generated.h @@ -4,6 +4,7 @@ #define __IMVUE_CONFIG_H__ #include "imgui.h" +#include "imvue.h" #include "imvue_element.h" #include "imvue_errors.h" @@ -2164,6 +2165,24 @@ namespace ImVue { factory.element("collapsing-header") .attribute("label", &CollapsingHeader::label) .attribute("flags", &CollapsingHeader::flags); + + // register some built-in element types + factory.element("template"); + factory.element("__element__") + .handler("click") + .handler("mousedown") + .handler("mouseup") + .handler("mouseover") + .handler("mouseout") + .handler("change") + .handler("keydown") + .handler("keyup") + .handler("keypress") + .attribute("id", &Element::id) + .attribute("key", &Element::key) + .attribute("ref", &Element::ref); + + factory.element("slot"); return res; } } diff --git a/src/imvue_script.cpp b/src/imvue_script.cpp index 5b938ef..ad30e8d 100644 --- a/src/imvue_script.cpp +++ b/src/imvue_script.cpp @@ -96,7 +96,7 @@ namespace ImVue { } break; case ObjectType::INTEGER: - mObject->setInteger(*reinterpret_cast(value)); + mObject->setInteger(*reinterpret_cast(value)); break; case ObjectType::NUMBER: mObject->setNumber(*reinterpret_cast(value)); @@ -151,22 +151,6 @@ namespace ImVue { return (float)as(); } - template<> - int Object::as() const - { - if(!mObject) { - return 0; - } - - return mObject->readInt(); - } - - template<> - size_t Object::as() const - { - return (size_t)as(); - } - void ScriptState::ReactListener::trigger() { if(propertyID.get()) { diff --git a/src/imvue_script.h b/src/imvue_script.h index 1c52f41..a6a6ba0 100644 --- a/src/imvue_script.h +++ b/src/imvue_script.h @@ -119,7 +119,7 @@ namespace ImVue { /** * Read value as integer */ - virtual int readInt() = 0; + virtual long readInt() = 0; /** * Read value as string @@ -138,7 +138,7 @@ namespace ImVue { // mandatory setters virtual void setObject(Object& value) = 0; - virtual void setInteger(unsigned long value) = 0; + virtual void setInteger(long value) = 0; virtual void setNumber(double value) = 0; virtual void setString(const char* value) = 0; virtual void setBool(bool value) = 0; @@ -183,6 +183,22 @@ namespace ImVue { template C as() const { + return as(std::is_integral()); + } + + template + C as(std::true_type) const + { + if(!mObject) { + return 0; + } + + return (C)mObject->readInt(); + } + + template + C as(std::false_type) const + { IMVUE_EXCEPTION(ScriptError, "unsupported cast"); return C{}; } @@ -208,12 +224,6 @@ namespace ImVue { template<> float Object::as() const; - template<> - int Object::as() const; - - template<> - size_t Object::as() const; - class ObjectIterator { public: ObjectIterator(int index = -1) diff --git a/src/lua/script.cpp b/src/lua/script.cpp index 77abd73..d307fbb 100644 --- a/src/lua/script.cpp +++ b/src/lua/script.cpp @@ -216,7 +216,7 @@ extern "C" { virtual void keys(ObjectKeys& res); - virtual int readInt() { + virtual long readInt() { StackGuard g(mLuaState); unwrap(); return lua_tointeger(mLuaState, -1); @@ -251,7 +251,7 @@ extern "C" { assign(); } - virtual void setInteger(unsigned long value) + virtual void setInteger(long value) { lua_pushinteger(mLuaState, value); assign(); diff --git a/tests/unit/imvue_tests.cpp b/tests/unit/imvue_tests.cpp index 67eb8ce..e507f98 100644 --- a/tests/unit/imvue_tests.cpp +++ b/tests/unit/imvue_tests.cpp @@ -695,7 +695,7 @@ TEST(TestGeneratedElement, Separator) const char* testDocumentVSliderFloat = ""; @@ -1020,7 +1020,7 @@ TEST(TestGeneratedElement, ArrowButton) const char* testDocumentSliderFloat = ""; diff --git a/tests/unit/parser.cpp b/tests/unit/parser.cpp index 155902b..0668c3f 100644 --- a/tests/unit/parser.cpp +++ b/tests/unit/parser.cpp @@ -91,3 +91,319 @@ TEST(DocumentParser, DocumentCopy) ImVue::Document duplicate = document; renderDocument(duplicate); } + +#if defined(WITH_LUA) +#include "lua/script.h" +extern "C" { + #include + #include + #include +} +#endif + +class Validator : public ImVue::Element +{ + public: + Validator() + : arrVariadic(nullptr) + , ch(nullptr) + { + } + + ~Validator() + { + if(ch) ImGui::MemFree(ch); + if(arrVariadic) ImGui::MemFree(arrVariadic); + } + + void renderBody() + { + } + + bool flag; + float fl; + double dbl; + ImU16 u16; + ImU32 u32; + ImU64 u64; + short i16; + int i32; + long i64; + ImVec2 vec2; + ImVec4 vec4; + char* ch; + int arrFixed[3]; + int* arrVariadic; +#if defined(WITH_LUA) + ImVue::Object object; +#endif +}; + +typedef std::tuple ReadTypesParam; + +class ReadTypesTest : public ::testing::Test, public testing::WithParamInterface { +#if defined(WITH_LUA) + protected: + void SetUp() override { + L = luaL_newstate(); + luaL_openlibs(L); + ImVue::registerBindings(L); + } + + void TearDown() override { + lua_close(L); + } + + lua_State* L; +#endif +}; + +/** + * Test parsing various types + */ +TEST_P(ReadTypesTest, ParseTypes) +{ + ImVue::ElementFactory* factory = ImVue::createElementFactory(); + factory->element("validator") + .attribute("flag", &Validator::flag, true) + .attribute("float", &Validator::fl, true) + .attribute("double", &Validator::dbl, true) + .attribute("u16", &Validator::u16, true) + .attribute("u32", &Validator::u32, true) + .attribute("u64", &Validator::u64, true) + .attribute("i16", &Validator::i16, true) + .attribute("i32", &Validator::i32, true) + .attribute("i64", &Validator::i64, true) + .attribute("ch", &Validator::ch, true) + .attribute("vec2", &Validator::vec2, true) + .attribute("vec4", &Validator::vec4, true) + .attribute("arr-fixed", &Validator::arrFixed, true) + .attribute("arr-variadic", &Validator::arrVariadic, true) +#if defined(WITH_LUA) + .attribute("object", &Validator::object) +#endif + ; + + ImVue::Context* ctx = ImVue::createContext( + factory +#if defined(WITH_LUA) + , + new ImVue::LuaScriptState(L) +#endif + ); + ImVue::Document document(ctx); + const char* body = NULL; + bool checkObject; + std::tie(body, checkObject) = GetParam(); + document.parse(body); + renderDocument(document); + ImVector children = document.getChildren("validator", true); + Validator* el = children[0]; + EXPECT_EQ(el->u16, 65535); + EXPECT_EQ(el->u32, 65536); + EXPECT_EQ(el->u64, 4294967295); + EXPECT_EQ(el->i16, -32765); + EXPECT_EQ(el->i32, -65536); + EXPECT_EQ(el->i64, -1073741823); + EXPECT_FLOAT_EQ(el->vec2.x, 2); + EXPECT_FLOAT_EQ(el->vec2.y, 2); + + EXPECT_FLOAT_EQ(el->vec4.x, 2); + EXPECT_FLOAT_EQ(el->vec4.y, 2); + EXPECT_FLOAT_EQ(el->vec4.z, 2); + EXPECT_FLOAT_EQ(el->vec4.w, 2); + + EXPECT_FLOAT_EQ(el->fl, 0.1); + EXPECT_DOUBLE_EQ(el->dbl, 0.05); + + EXPECT_STREQ(el->ch, "hello"); + for(size_t i = 0; i < 3; ++i) { + EXPECT_EQ(el->arrFixed[i], i + 1); + } + + ASSERT_NE(el->arrVariadic, nullptr); + for(size_t i = 0; i < 5; ++i) { + EXPECT_EQ(el->arrVariadic[i], i + 1); + } +#if defined(WITH_LUA) + if(checkObject) { + ASSERT_NE(el->object.type(), ImVue::ObjectType::NIL); + EXPECT_STREQ(el->object["it"].as(), "works"); + } +#endif +} + +const char* dataStatic = ""; + +const char* dataBind = "" +""; + +INSTANTIATE_TEST_CASE_P( + ParseTypes, + ReadTypesTest, + ::testing::Values( + std::make_tuple(dataStatic, false) +#if defined(WITH_LUA) + , + std::make_tuple(dataBind, true) +#endif + ) +); + +/** + * Test reading various types + * low level test + */ +TEST(DocumentParser, StaticArrayReader) { + // reading array of length 1 + { + float vals[1] = {0.0f}; + EXPECT_TRUE(ImVue::detail::read("{1.2}", &vals)); + EXPECT_FLOAT_EQ(vals[0], 1.2); + } + // reading array of length 2 + { + double vals[2] = {0.0}; + EXPECT_TRUE(ImVue::detail::read("{1.2, 2.4}", &vals)); + EXPECT_DOUBLE_EQ(vals[0], 1.2); + EXPECT_DOUBLE_EQ(vals[1], 2.4); + } + // various correct inputs with traling spaces/different bracket types + { + const char* cases[4] = { + " \t\n{1.2, 2.4}\n\t", + "[1.2, 2.4]", + "(1.2, 2.4)", + "1.2, 2.4" + }; + + for (int i = 0; i < 4; ++i) { + double vals[2] = {0.0}; + EXPECT_TRUE(ImVue::detail::read(cases[i], &vals)); + EXPECT_DOUBLE_EQ(vals[0], 1.2); + EXPECT_DOUBLE_EQ(vals[1], 2.4); + } + } + // various incorrect inputs + { + const char* cases[4] = { + "{[1.2, 2.4]", + "(1.2, 2.4}", + "{}", + " " + }; + + for (int i = 0; i < 4; ++i) { + double vals[2] = {0.0}; + EXPECT_FALSE(ImVue::detail::read(cases[i], &vals)); + EXPECT_DOUBLE_EQ(vals[0], 0.0); + EXPECT_DOUBLE_EQ(vals[1], 0.0); + } + } + // variable array length + { + int* vars = nullptr; + EXPECT_TRUE(ImVue::detail::read("{1,2,3,4,5,6}", &vars)); + EXPECT_NE(vars, nullptr); + for (int i = 0; i < 6; ++i) { + EXPECT_EQ(vars[i], i+1); + } + // re-read to verify proper memory usage + EXPECT_TRUE(ImVue::detail::read("{1,2,3,4,5,6}", &vars)); + EXPECT_NE(vars, nullptr); + for (int i = 0; i < 6; ++i) { + EXPECT_EQ(vars[i], i+1); + } + ImGui::MemFree(vars); + } +} + +class BaseRequiredValidator : public ImVue::Element +{ + public: + void renderBody() + { + } + + int base; +}; + +class RequiredValidator : public BaseRequiredValidator +{ + public: + int value; +}; +/** + * Test required fields validation + */ +TEST(DocumentParser, RequiredPropsValidation) { + + ImVue::ElementFactory* factory = ImVue::createElementFactory(); + factory->element("base-validator") + .attribute("base", &BaseRequiredValidator::base, true); + + factory->element("validator") + .inherits("base-validator") + .attribute("value", &RequiredValidator::value, true); + + ImVue::Context* ctx = ImVue::createContext( + factory + ); + + ImVue::Document document(ctx); + { + const char* body = ""; + + bool gotError = false; + ASSERT_THROW(document.parse(body), ImVue::ElementError); + renderDocument(document); + } + + { + const char* body = ""; + + bool gotError = false; + ASSERT_THROW(document.parse(body), ImVue::ElementError); + renderDocument(document); + } + + { + const char* body = ""; + + bool gotError = false; + ASSERT_THROW(document.parse(body), ImVue::ElementError); + renderDocument(document); + } + + // now pass + { + const char* body = ""; + + bool gotError = false; + document.parse(body); + renderDocument(document); + } +} diff --git a/tools/config.yaml b/tools/config.yaml index c8178be..28ad03e 100644 --- a/tools/config.yaml +++ b/tools/config.yaml @@ -157,12 +157,14 @@ elements_rules: - v test_values: p_data: 1 + v: 0.0 SliderFloat: required_fields: - label - v test_values: p_data: 1 + v: 0.0 # test generator config section test_values_by_type: diff --git a/tools/templates/imvue_generated.h.j2 b/tools/templates/imvue_generated.h.j2 index 3b3d066..938f345 100644 --- a/tools/templates/imvue_generated.h.j2 +++ b/tools/templates/imvue_generated.h.j2 @@ -15,6 +15,7 @@ #define __IMVUE_CONFIG_H__ #include "imgui.h" +#include "imvue.h" #include "imvue_element.h" #include "imvue_errors.h" @@ -112,6 +113,24 @@ namespace ImVue { factory.element("collapsing-header") .attribute("label", &CollapsingHeader::label) .attribute("flags", &CollapsingHeader::flags); + + // register some built-in element types + factory.element("template"); + factory.element("__element__") + .handler("click") + .handler("mousedown") + .handler("mouseup") + .handler("mouseover") + .handler("mouseout") + .handler("change") + .handler("keydown") + .handler("keyup") + .handler("keypress") + .attribute("id", &Element::id) + .attribute("key", &Element::key) + .attribute("ref", &Element::ref); + + factory.element("slot"); return res; } }