|
- // Copyright 2021 Jean Pierre Cimalando
- //
- // Licensed under the Apache License, Version 2.0 (the "License");
- // you may not use this file except in compliance with the License.
- // You may obtain a copy of the License at
- //
- // http://www.apache.org/licenses/LICENSE-2.0
- //
- // Unless required by applicable law or agreed to in writing, software
- // distributed under the License is distributed on an "AS IS" BASIS,
- // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- // See the License for the specific language governing permissions and
- // limitations under the License.
- //
- // SPDX-License-Identifier: Apache-2.0
- //
-
- #include "ysfx_parse.hpp"
- #include "ysfx_utils.hpp"
- #include <cstdlib>
- #include <cstring>
-
- bool ysfx_parse_toplevel(ysfx::text_reader &reader, ysfx_toplevel_t &toplevel, ysfx_parse_error *error)
- {
- toplevel = ysfx_toplevel_t{};
-
- ysfx_section_t *current = new ysfx_section_t;
- toplevel.header.reset(current);
-
- std::string line;
- uint32_t lineno = 0;
-
- line.reserve(256);
-
- while (reader.read_next_line(line)) {
- const char *linep = line.c_str();
-
- if (linep[0] == '@') {
- // a new section starts
- ysfx::string_list tokens = ysfx::split_strings_noempty(linep, &ysfx::ascii_isspace);
-
- current = new ysfx_section_t;
-
- if (tokens[0] == "@init")
- toplevel.init.reset(current);
- else if (tokens[0] == "@slider")
- toplevel.slider.reset(current);
- else if (tokens[0] == "@block")
- toplevel.block.reset(current);
- else if (tokens[0] == "@sample")
- toplevel.sample.reset(current);
- else if (tokens[0] == "@serialize")
- toplevel.serialize.reset(current);
- else if (tokens[0] == "@gfx") {
- toplevel.gfx.reset(current);
- long gfx_w = 0;
- long gfx_h = 0;
- if (tokens.size() > 1)
- gfx_w = (long)ysfx::dot_atof(tokens[1].c_str());
- if (tokens.size() > 2)
- gfx_h = (long)ysfx::dot_atof(tokens[2].c_str());
- toplevel.gfx_w = (gfx_w > 0) ? (uint32_t)gfx_w : 0;
- toplevel.gfx_h = (gfx_h > 0) ? (uint32_t)gfx_h : 0;
- }
- else {
- delete current;
- if (error) {
- error->line = lineno;
- error->message = std::string("Invalid section: ") + line;
- }
- return false;
- }
- current->line_offset = lineno + 1;
- }
- else {
- current->text.append(line);
- current->text.push_back('\n');
- }
-
- ++lineno;
- }
-
- return true;
- }
-
- void ysfx_parse_header(ysfx_section_t *section, ysfx_header_t &header)
- {
- header = ysfx_header_t{};
-
- ysfx::string_text_reader reader(section->text.c_str());
-
- std::string line;
- //uint32_t lineno = section->line_offset;
-
- line.reserve(256);
-
- ///
- auto unprefix = [](const char *text, const char **restp, const char *prefix) -> bool {
- size_t len = strlen(prefix);
- if (strncmp(text, prefix, len))
- return false;
- if (restp)
- *restp = text + len;
- return true;
- };
-
- //--------------------------------------------------------------------------
- // pass 1: regular metadata
-
- while (reader.read_next_line(line)) {
- const char *linep = line.c_str();
- const char *rest = nullptr;
-
- ysfx_slider_t slider;
- ysfx_parsed_filename_t filename;
-
- ///
- if (unprefix(linep, &rest, "desc:")) {
- if (header.desc.empty())
- header.desc = ysfx::trim(rest, &ysfx::ascii_isspace);
- }
- else if (unprefix(linep, &rest, "author:")) {
- if (header.author.empty())
- header.author = ysfx::trim(rest, &ysfx::ascii_isspace);
- }
- else if (unprefix(linep, &rest, "tags:")) {
- if (header.tags.empty()) {
- for (const std::string &tag : ysfx::split_strings_noempty(rest, &ysfx::ascii_isspace))
- header.tags.push_back(tag);
- }
- }
- else if (unprefix(linep, &rest, "in_pin:")) {
- header.explicit_pins = true;
- header.in_pins.push_back(ysfx::trim(rest, &ysfx::ascii_isspace));
- }
- else if (unprefix(linep, &rest, "out_pin:")) {
- header.explicit_pins = true;
- header.out_pins.push_back(ysfx::trim(rest, &ysfx::ascii_isspace));
- }
- else if (unprefix(linep, &rest, "options:")) {
- for (const std::string &opt : ysfx::split_strings_noempty(rest, &ysfx::ascii_isspace)) {
- size_t pos = opt.find('=');
- std::string name = (pos == opt.npos) ? opt : opt.substr(0, pos);
- std::string value = (pos == opt.npos) ? std::string{} : opt.substr(pos + 1);
- if (name == "gmem")
- header.options.gmem = value;
- else if (name == "maxmem") {
- int32_t maxmem = (int32_t)ysfx::dot_atof(value.c_str());
- header.options.maxmem = (maxmem < 0) ? 0 : (uint32_t)maxmem;
- }
- else if (name == "want_all_kb")
- header.options.want_all_kb = true;
- else if (name == "no_meter")
- header.options.no_meter = true;
- }
- }
- else if (unprefix(linep, &rest, "import") && ysfx::ascii_isspace(rest[0]))
- header.imports.push_back(ysfx::trim(rest + 1, &ysfx::ascii_isspace));
- else if (ysfx_parse_slider(linep, slider)) {
- if (slider.id >= ysfx_max_sliders)
- continue;
- slider.exists = true;
- header.sliders[slider.id] = slider;
- }
- else if (ysfx_parse_filename(linep, filename)) {
- if (filename.index != header.filenames.size())
- continue;
- header.filenames.push_back(std::move(filename.filename));
- }
-
- //++lineno;
- }
-
- //--------------------------------------------------------------------------
- // pass 2: comments
-
- reader = ysfx::string_text_reader{section->text.c_str()};
-
- while (reader.read_next_line(line)) {
- const char *linep = line.c_str();
- const char *rest = nullptr;
-
- // some files contain metadata in the form of comments
- // this is not part of spec, but we'll take this info regardless
-
- if (unprefix(linep, &rest, "//author:")) {
- if (header.author.empty())
- header.author = ysfx::trim(rest, &ysfx::ascii_isspace);
- }
- else if (unprefix(linep, &rest, "//tags:")) {
- if (header.tags.empty()) {
- for (const std::string &tag : ysfx::split_strings_noempty(rest, &ysfx::ascii_isspace))
- header.tags.push_back(tag);
- }
- }
- }
-
- //--------------------------------------------------------------------------
- if (header.in_pins.size() == 1 && !ysfx::ascii_casecmp(header.in_pins.front().c_str(), "none"))
- header.in_pins.clear();
- if (header.out_pins.size() == 1 && !ysfx::ascii_casecmp(header.out_pins.front().c_str(), "none"))
- header.out_pins.clear();
-
- if (header.in_pins.size() > ysfx_max_channels)
- header.in_pins.resize(ysfx_max_channels);
- if (header.out_pins.size() > ysfx_max_channels)
- header.out_pins.resize(ysfx_max_channels);
- }
-
- bool ysfx_parse_slider(const char *line, ysfx_slider_t &slider)
- {
- // NOTE this parser is intentionally very permissive,
- // in order to match the reference behavior
-
- slider = ysfx_slider_t{};
-
- #define PARSE_FAIL do { \
- /*fprintf(stderr, "parse error (line %d): `%s`\n", __LINE__, line);*/ \
- return false; \
- } while (0)
-
- const char *cur = line;
-
- for (const char *p = "slider"; *p; ++p) {
- if (*cur++ != *p)
- PARSE_FAIL;
- }
-
- // parse ID (1-index)
- unsigned long id = strtoul(cur, (char **)&cur, 10);
- if (id < 1 || id > ysfx_max_sliders)
- PARSE_FAIL;
- slider.id = (uint32_t)--id;
-
- // semicolon
- if (*cur++ != ':')
- PARSE_FAIL;
-
- // search if there is an '=' sign prior to any '<' or ','
- // if there is, it's a custom variable
- {
- const char *pos = cur;
- for (char c; pos && (c = *pos) && c != '='; )
- pos = (c == '<' || c == ',') ? nullptr : (pos + 1);
- if (pos && *pos) {
- slider.var.assign(cur, pos);
- cur = pos + 1;
- }
- else
- slider.var = "slider" + std::to_string(id + 1);
- }
-
- // a regular slider
- if (*cur != '/') {
- slider.def = (ysfx_real)ysfx::dot_strtod(cur, (char **)&cur);
-
- while (*cur && *cur != ',' && *cur != '<') ++cur;
- if (!*cur) PARSE_FAIL;
-
- // no range specification
- if (*cur == ',')
- ++cur;
- // range specification
- else if (*cur == '<') {
- ++cur;
-
- // min
- slider.min = (ysfx_real)ysfx::dot_strtod(cur, (char **)&cur);
-
- while (*cur && *cur != ',' && *cur != '>') ++cur;
- if (!*cur) PARSE_FAIL;
-
- // max
- if (*cur == ',') {
- ++cur;
- slider.max = (ysfx_real)ysfx::dot_strtod(cur, (char **)&cur);
-
- while (*cur && *cur != ',' && *cur != '>') ++cur;
- if (!*cur) PARSE_FAIL;
- }
-
- // inc
- if (*cur == ',') {
- ++cur;
- slider.inc = (ysfx_real)ysfx::dot_strtod(cur, (char **)&cur);
-
- while (*cur && *cur != '{' && *cur != ',' && *cur != '>') ++cur;
- if (!*cur) PARSE_FAIL;
-
- // enumeration values
- if (*cur == '{') {
- const char *pos = ++cur;
-
- while (*cur && *cur != '}' && *cur != '>') ++cur;
- if (!*cur) PARSE_FAIL;
-
- slider.is_enum = true;
- slider.enum_names = ysfx::split_strings_noempty(
- std::string(pos, cur).c_str(),
- [](char c) -> bool { return c == ','; });
- for (std::string &name : slider.enum_names)
- name = ysfx::trim(name.c_str(), &ysfx::ascii_isspace);
- }
- }
-
- while (*cur && *cur != '>') ++cur;
- if (!*cur) PARSE_FAIL;
-
- ++cur;
- }
- else
- PARSE_FAIL;
-
- // NOTE: skip ',' and whitespace. not sure why, it's how it is
- while (*cur && (*cur == ',' || ysfx::ascii_isspace(*cur))) ++cur;
- if (!*cur) PARSE_FAIL;
- }
- // a path slider
- else
- {
- const char *pos = cur;
- while (*cur && *cur != ':') ++cur;
- if (!*cur) PARSE_FAIL;
-
- slider.path.assign(pos, cur);
- ++cur;
- slider.def = (ysfx_real)ysfx::dot_strtod(cur, (char **)&cur);
- slider.inc = 1;
- slider.is_enum = true;
-
- while (*cur && *cur != ':') ++cur;
- if (!*cur) PARSE_FAIL;
-
- ++cur;
- }
-
- // description and visibility
- while (ysfx::ascii_isspace(*cur))
- ++cur;
-
- slider.initially_visible = true;
- if (*cur == '-') {
- ++cur;
- slider.initially_visible = false;
- }
-
- slider.desc = ysfx::trim(cur, &ysfx::ascii_isspace);
- if (slider.desc.empty())
- PARSE_FAIL;
-
- //
- #undef PARSE_FAIL
-
- return true;
- }
-
- bool ysfx_parse_filename(const char *line, ysfx_parsed_filename_t &filename)
- {
- filename = ysfx_parsed_filename_t{};
-
- const char *cur = line;
-
- for (const char *p = "filename:"; *p; ++p) {
- if (*cur++ != *p)
- return false;
- }
-
- int64_t index = (int64_t)ysfx::dot_strtod(cur, (char **)&cur);
- if (index < 0 || index > ~(uint32_t)0)
- return false;
-
- while (*cur && *cur != ',') ++cur;
- if (!*cur) return false;;
-
- ++cur;
-
- filename.index = (uint32_t)index;
- filename.filename.assign(cur);
- return true;
- }
|