diff --git a/plugins/editor/tools/layout-maker/sources/main.cpp b/plugins/editor/tools/layout-maker/sources/main.cpp index 3ef47982..022b1f52 100644 --- a/plugins/editor/tools/layout-maker/sources/main.cpp +++ b/plugins/editor/tools/layout-maker/sources/main.cpp @@ -52,6 +52,52 @@ static Metadata metadata_from_comment(absl::string_view comment) return md; } +/// +static std::string cstrQuote(absl::string_view text) +{ + std::string cstr; + cstr.reserve(2 * text.size() + 2); + + cstr.push_back('"'); + + for (char c : text) { + switch (c) { + case '\a' : + cstr.append("\\a"); + break; + case '\b' : + cstr.append("\\b"); + break; + case '\t' : + cstr.append("\\t"); + break; + case '\n' : + cstr.append("\\n"); + break; + case '\v' : + cstr.append("\\v"); + break; + case '\f' : + cstr.append("\\f"); + break; + case '\r' : + cstr.append("\\r"); + break; + case '"': + case '\\': + cstr.push_back('\\'); + cstr.push_back(c); + break; + default: + cstr.push_back(c); + break; + } + } + + cstr.push_back('"'); + return cstr; +} + /// static void codegen_item(int& idCounter, int parentId, int parentX, int parentY, const LayoutItem& item, absl::string_view oldTheme) { @@ -95,7 +141,7 @@ static void codegen_item(int& idCounter, int parentId, int parentX, int parentY, else if (item.align & 8) align = "kRightText"; - std::cout << "auto"/*item.classname*/ << "* const view__" << id << " = create" << item.classname << "(CRect(" << relX << ", " << relY << ", " << (relX + item.w) << ", " << (relY + item.h) << "), " << tag << ", \"" << label << "\", " << align << ", " << item.labelsize << ");\n"; + std::cout << "auto"/*item.classname*/ << "* const view__" << id << " = create" << item.classname << "(CRect(" << relX << ", " << relY << ", " << (relX + item.w) << ", " << (relY + item.h) << "), " << tag << ", " << cstrQuote(label) << ", " << align << ", " << item.labelsize << ");\n"; if (!item.id.empty()) std::cout << item.id << " = view__" << id << ";\n"; diff --git a/plugins/editor/tools/layout-maker/sources/reader.cpp b/plugins/editor/tools/layout-maker/sources/reader.cpp index eef4aaaa..ab2fbdf8 100644 --- a/plugins/editor/tools/layout-maker/sources/reader.cpp +++ b/plugins/editor/tools/layout-maker/sources/reader.cpp @@ -11,214 +11,6 @@ #include #include -typedef std::vector TokenList; -static bool read_file_tokens(const char *filename, TokenList &tokens); -static Layout read_tokens_layout(TokenList::iterator &tok_it, TokenList::iterator tok_end); - -Layout read_file_layout(const char *filename) -{ - std::vector tokens; - if (!read_file_tokens(filename, tokens)) - throw std::runtime_error("Cannot read fluid design file."); - - TokenList::iterator tok_it = tokens.begin(); - TokenList::iterator tok_end = tokens.end(); - return read_tokens_layout(tok_it, tok_end); -} - -static std::string consume_next_token(TokenList::iterator &tok_it, TokenList::iterator tok_end) -{ - if (tok_it == tok_end) - throw file_format_error("Premature end of tokens"); - return *tok_it++; -} - -static bool try_consume_next_token(const char *text, TokenList::iterator &tok_it, TokenList::iterator tok_end) -{ - if (tok_it == tok_end) - return false; - - if (*tok_it != text) - return false; - - ++tok_it; - return true; -} - -static void ensure_next_token(const char *text, TokenList::iterator &tok_it, TokenList::iterator tok_end) -{ - std::string tok = consume_next_token(tok_it, tok_end); - if (tok != text) - throw file_format_error("Unexpected token: " + tok); -} - -static std::string consume_enclosed_string(TokenList::iterator &tok_it, TokenList::iterator tok_end) -{ - ensure_next_token("{", tok_it, tok_end); - unsigned depth = 1; - - std::string text; - for (;;) { - std::string part = consume_next_token(tok_it, tok_end); - if (part == "}") { - if (--depth == 0) - return text; - } - else if (part == "{") - ++depth; - if (!text.empty()) - text.push_back(' '); - text.append(part); - } - - return text; -} - -static std::string consume_any_string(TokenList::iterator &tok_it, TokenList::iterator tok_end) -{ - if (tok_it != tok_end && *tok_it == "{") - return consume_enclosed_string(tok_it, tok_end); - else - return consume_next_token(tok_it, tok_end); -} - -static int consume_int_token(TokenList::iterator &tok_it, TokenList::iterator tok_end) -{ - std::string text = consume_next_token(tok_it, tok_end); - return std::stoi(text); -} - -static int consume_real_token(TokenList::iterator &tok_it, TokenList::iterator tok_end) -{ - std::string text = consume_next_token(tok_it, tok_end); - return std::stod(text); -} - -static void consume_layout_item_properties(LayoutItem &item, TokenList::iterator &tok_it, TokenList::iterator tok_end) -{ - ensure_next_token("{", tok_it, tok_end); - for (bool have = true; have;) { - if (try_consume_next_token("open", tok_it, tok_end)) - ; // skip - else if (try_consume_next_token("selected", tok_it, tok_end)) - ; // skip - else if (try_consume_next_token("label", tok_it, tok_end)) - item.label = consume_any_string(tok_it, tok_end); - else if (try_consume_next_token("xywh", tok_it, tok_end)) { - ensure_next_token("{", tok_it, tok_end); - item.x = consume_int_token(tok_it, tok_end); - item.y = consume_int_token(tok_it, tok_end); - item.w = consume_int_token(tok_it, tok_end); - item.h = consume_int_token(tok_it, tok_end); - ensure_next_token("}", tok_it, tok_end); - } - else if (try_consume_next_token("box", tok_it, tok_end)) - item.box = consume_next_token(tok_it, tok_end); - else if (try_consume_next_token("down_box", tok_it, tok_end)) - item.down_box = consume_next_token(tok_it, tok_end); - else if (try_consume_next_token("labelfont", tok_it, tok_end)) - item.labelfont = consume_int_token(tok_it, tok_end); - else if (try_consume_next_token("labelsize", tok_it, tok_end)) - item.labelsize = consume_int_token(tok_it, tok_end); - else if (try_consume_next_token("labeltype", tok_it, tok_end)) - item.labeltype = consume_any_string(tok_it, tok_end); - else if (try_consume_next_token("textsize", tok_it, tok_end)) - item.textsize = consume_int_token(tok_it, tok_end); - else if (try_consume_next_token("align", tok_it, tok_end)) - item.align = consume_int_token(tok_it, tok_end); - else if (try_consume_next_token("type", tok_it, tok_end)) - item.type = consume_any_string(tok_it, tok_end); - else if (try_consume_next_token("callback", tok_it, tok_end)) - item.callback = consume_any_string(tok_it, tok_end); - else if (try_consume_next_token("class", tok_it, tok_end)) - item.classname = consume_any_string(tok_it, tok_end); - else if (try_consume_next_token("value", tok_it, tok_end)) - item.value = consume_real_token(tok_it, tok_end); - else if (try_consume_next_token("minimum", tok_it, tok_end)) - item.minimum = consume_real_token(tok_it, tok_end); - else if (try_consume_next_token("maximum", tok_it, tok_end)) - item.maximum = consume_real_token(tok_it, tok_end); - else if (try_consume_next_token("step", tok_it, tok_end)) - item.step = consume_real_token(tok_it, tok_end); - else if (try_consume_next_token("image", tok_it, tok_end)) - item.image.filepath = consume_any_string(tok_it, tok_end); - else if (try_consume_next_token("hide", tok_it, tok_end)) - item.hidden = true; - else if (try_consume_next_token("visible", tok_it, tok_end)) - /* skip */; - else if (try_consume_next_token("comment", tok_it, tok_end)) - item.comment = consume_any_string(tok_it, tok_end); - else - have = false; - } - ensure_next_token("}", tok_it, tok_end); -} - -static LayoutItem consume_layout_item(const std::string &classname, TokenList::iterator &tok_it, TokenList::iterator tok_end, bool anonymous = false) -{ - LayoutItem item; - item.classname = classname; - if (!anonymous) - item.id = consume_any_string(tok_it, tok_end); - consume_layout_item_properties(item, tok_it, tok_end); - if (tok_it != tok_end && *tok_it == "{") { - consume_next_token(tok_it, tok_end); - for (std::string text; (text = consume_next_token(tok_it, tok_end)) != "}";) { - if (text == "decl") { - consume_any_string(tok_it, tok_end); - consume_any_string(tok_it, tok_end); - } - else if (text == "Function") { - consume_any_string(tok_it, tok_end); - consume_any_string(tok_it, tok_end); - consume_any_string(tok_it, tok_end); - } - else - item.items.push_back(consume_layout_item(text, tok_it, tok_end)); - } - } - return item; -} - -static Layout read_tokens_layout(TokenList::iterator &tok_it, TokenList::iterator tok_end) -{ - Layout layout; - - std::string version_name; - std::string header_name; - std::string code_name; - - while (tok_it != tok_end) { - std::string key = consume_next_token(tok_it, tok_end); - - if (key == "version") - version_name = consume_next_token(tok_it, tok_end); - else if (key == "header_name") { - ensure_next_token("{", tok_it, tok_end); - header_name = consume_next_token(tok_it, tok_end); - ensure_next_token("}", tok_it, tok_end); - } - else if (key == "code_name") { - ensure_next_token("{", tok_it, tok_end); - code_name = consume_next_token(tok_it, tok_end); - ensure_next_token("}", tok_it, tok_end); - } - else if (key == "decl") { - consume_any_string(tok_it, tok_end); - consume_any_string(tok_it, tok_end); - } - else if (key == "widget_class") { - key = consume_next_token(tok_it, tok_end); - layout.items.push_back(consume_layout_item(key, tok_it, tok_end, true)); - layout.items.back().id = key; - } - else - layout.items.push_back(consume_layout_item(key, tok_it, tok_end)); - } - - return layout; -} - /// class tokenizer { public: @@ -227,7 +19,21 @@ public: absl::string_view dropped_delims, absl::string_view kept_delims); - absl::string_view next(); + absl::string_view next(bool consume); + std::string string_until(char closing, bool consume); + bool at_end() const; + +private: + bool is_dropped(char c) const { + return dropped_delims_.find(c) != dropped_delims_.npos; + } + bool is_kept(char c) const { + return kept_delims_.find(c) != kept_delims_.npos; + } + bool is_delim(char c) const { + return dropped_delims_.find(c) != dropped_delims_.npos || + kept_delims_.find(c) != kept_delims_.npos; + } private: absl::string_view text_; @@ -243,19 +49,8 @@ tokenizer::tokenizer( { } -absl::string_view tokenizer::next() +absl::string_view tokenizer::next(bool consume) { - auto is_dropped = [this](char c) -> bool { - return dropped_delims_.find(c) != dropped_delims_.npos; - }; - auto is_kept = [this](char c) -> bool { - return kept_delims_.find(c) != kept_delims_.npos; - }; - auto is_delim = [this](char c) -> bool { - return dropped_delims_.find(c) != dropped_delims_.npos || - kept_delims_.find(c) != kept_delims_.npos; - }; - absl::string_view text = text_; while (!text.empty() && is_dropped(text[0])) @@ -266,7 +61,7 @@ absl::string_view tokenizer::next() size_t pos; { - auto it = std::find_if(text.begin(), text.end(), is_delim); + auto it = std::find_if(text.begin(), text.end(), [this](char c) -> bool { return is_delim(c); }); if (it == text.end()) pos = text.size(); else { @@ -276,17 +71,240 @@ absl::string_view tokenizer::next() } absl::string_view token = text.substr(0, pos); - text_ = text.substr(pos); + if (consume) + text_ = text.substr(pos); return token; } +std::string tokenizer::string_until(char closing, bool consume) +{ + absl::string_view text = text_; + std::string result; + + result.reserve(256); + + for (char c; !text.empty() && + ((c = text[0]), text.remove_prefix(1), c != closing); ) { + if (c != '\\') + result.push_back(c); + else { + if (!text.empty()) { + c = text[0]; + text.remove_prefix(1); + result.push_back(c); + } + } + } + + if (consume) + text_ = text; + return result; +} + +bool tokenizer::at_end() const +{ + absl::string_view text = text_; + + while (!text.empty() && is_dropped(text[0])) + text.remove_prefix(1); + + return text.empty(); +} + /// -static bool read_file_tokens(const char *filename, TokenList &tokens) +typedef std::vector TokenList; +static bool read_file_lines(const char *filename, std::string &text); +static Layout read_tokens_layout(tokenizer &tkzr); + +Layout read_file_layout(const char *filename) +{ + std::string text; + if (!read_file_lines(filename, text)) + throw std::runtime_error("Cannot read fluid design file."); + + tokenizer tok(text, " \t\r\n", "{}"); + return read_tokens_layout(tok); +} + +static std::string consume_next_token(tokenizer &tkzr) +{ + if (tkzr.at_end()) + throw file_format_error("Premature end of tokens"); + return std::string(tkzr.next(true)); +} + +static bool try_consume_next_token(const char *text, tokenizer &tkzr) +{ + if (tkzr.at_end()) + return false; + + if (tkzr.next(false) != text) + return false; + + tkzr.next(true); + return true; +} + +static void ensure_next_token(const char *text, tokenizer &tkzr) +{ + std::string tok = consume_next_token(tkzr); + if (tok != text) + throw file_format_error("Unexpected token: " + tok); +} + +static std::string consume_any_string(tokenizer &tkzr) +{ + if (!tkzr.at_end() && tkzr.next(false) == "{") { + tkzr.next(true); + return tkzr.string_until('}', true); + } + else + return consume_next_token(tkzr); +} + +static int consume_int_token(tokenizer &tkzr) +{ + std::string text = consume_next_token(tkzr); + return std::stoi(text); +} + +static int consume_real_token(tokenizer &tkzr) +{ + std::string text = consume_next_token(tkzr); + return std::stod(text); +} + +static void consume_layout_item_properties(LayoutItem &item, tokenizer &tkzr) +{ + ensure_next_token("{", tkzr); + for (bool have = true; have;) { + if (try_consume_next_token("open", tkzr)) + ; // skip + else if (try_consume_next_token("selected", tkzr)) + ; // skip + else if (try_consume_next_token("label", tkzr)) + item.label = consume_any_string(tkzr); + else if (try_consume_next_token("xywh", tkzr)) { + ensure_next_token("{", tkzr); + item.x = consume_int_token(tkzr); + item.y = consume_int_token(tkzr); + item.w = consume_int_token(tkzr); + item.h = consume_int_token(tkzr); + ensure_next_token("}", tkzr); + } + else if (try_consume_next_token("box", tkzr)) + item.box = consume_next_token(tkzr); + else if (try_consume_next_token("down_box", tkzr)) + item.down_box = consume_next_token(tkzr); + else if (try_consume_next_token("labelfont", tkzr)) + item.labelfont = consume_int_token(tkzr); + else if (try_consume_next_token("labelsize", tkzr)) + item.labelsize = consume_int_token(tkzr); + else if (try_consume_next_token("labeltype", tkzr)) + item.labeltype = consume_any_string(tkzr); + else if (try_consume_next_token("textsize", tkzr)) + item.textsize = consume_int_token(tkzr); + else if (try_consume_next_token("align", tkzr)) + item.align = consume_int_token(tkzr); + else if (try_consume_next_token("type", tkzr)) + item.type = consume_any_string(tkzr); + else if (try_consume_next_token("callback", tkzr)) + item.callback = consume_any_string(tkzr); + else if (try_consume_next_token("class", tkzr)) + item.classname = consume_any_string(tkzr); + else if (try_consume_next_token("value", tkzr)) + item.value = consume_real_token(tkzr); + else if (try_consume_next_token("minimum", tkzr)) + item.minimum = consume_real_token(tkzr); + else if (try_consume_next_token("maximum", tkzr)) + item.maximum = consume_real_token(tkzr); + else if (try_consume_next_token("step", tkzr)) + item.step = consume_real_token(tkzr); + else if (try_consume_next_token("image", tkzr)) + item.image.filepath = consume_any_string(tkzr); + else if (try_consume_next_token("hide", tkzr)) + item.hidden = true; + else if (try_consume_next_token("visible", tkzr)) + /* skip */; + else if (try_consume_next_token("comment", tkzr)) + item.comment = consume_any_string(tkzr); + else + have = false; + } + ensure_next_token("}", tkzr); +} + +static LayoutItem consume_layout_item(const std::string &classname, tokenizer &tkzr, bool anonymous = false) +{ + LayoutItem item; + item.classname = classname; + if (!anonymous) + item.id = consume_any_string(tkzr); + consume_layout_item_properties(item, tkzr); + if (!tkzr.at_end() && tkzr.next(false) == "{") { + consume_next_token(tkzr); + for (std::string text; (text = consume_next_token(tkzr)) != "}";) { + if (text == "decl") { + consume_any_string(tkzr); + consume_any_string(tkzr); + } + else if (text == "Function") { + consume_any_string(tkzr); + consume_any_string(tkzr); + consume_any_string(tkzr); + } + else + item.items.push_back(consume_layout_item(text, tkzr)); + } + } + return item; +} + +static Layout read_tokens_layout(tokenizer &tkzr) +{ + Layout layout; + + std::string version_name; + std::string header_name; + std::string code_name; + + while (!tkzr.at_end()) { + std::string key = consume_next_token(tkzr); + + if (key == "version") + version_name = consume_next_token(tkzr); + else if (key == "header_name") { + ensure_next_token("{", tkzr); + header_name = consume_next_token(tkzr); + ensure_next_token("}", tkzr); + } + else if (key == "code_name") { + ensure_next_token("{", tkzr); + code_name = consume_next_token(tkzr); + ensure_next_token("}", tkzr); + } + else if (key == "decl") { + consume_any_string(tkzr); + consume_any_string(tkzr); + } + else if (key == "widget_class") { + key = consume_next_token(tkzr); + layout.items.push_back(consume_layout_item(key, tkzr, true)); + layout.items.back().id = key; + } + else + layout.items.push_back(consume_layout_item(key, tkzr)); + } + + return layout; +} + +/// +static bool read_file_lines(const char *filename, std::string &text) { std::ifstream stream(filename); std::string line; - std::string text; while (std::getline(stream, line)) { if (!line.empty() && line[0] != '#') { text.append(line); @@ -294,13 +312,5 @@ static bool read_file_tokens(const char *filename, TokenList &tokens) } } - if (stream.bad()) - return false; - - tokenizer tok(text, " \t\r\n", "{}"); - absl::string_view token; - while (!(token = tok.next()).empty()) - tokens.emplace_back(token); - return !stream.bad(); }