// 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 #include 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; }