Cleanup
[carla.git] / source / modules / ysfx / sources / ysfx_parse.cpp
blob8e9f93f35d405a9111102a02f11fc048dcef84a5
1 // Copyright 2021 Jean Pierre Cimalando
2 //
3 // Licensed under the Apache License, Version 2.0 (the "License");
4 // you may not use this file except in compliance with the License.
5 // You may obtain a copy of the License at
6 //
7 // http://www.apache.org/licenses/LICENSE-2.0
8 //
9 // Unless required by applicable law or agreed to in writing, software
10 // distributed under the License is distributed on an "AS IS" BASIS,
11 // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12 // See the License for the specific language governing permissions and
13 // limitations under the License.
15 // SPDX-License-Identifier: Apache-2.0
18 #include "ysfx_parse.hpp"
19 #include "ysfx_utils.hpp"
20 #include <cstdlib>
21 #include <cstring>
23 bool ysfx_parse_toplevel(ysfx::text_reader &reader, ysfx_toplevel_t &toplevel, ysfx_parse_error *error)
25 toplevel = ysfx_toplevel_t{};
27 ysfx_section_t *current = new ysfx_section_t;
28 toplevel.header.reset(current);
30 std::string line;
31 uint32_t lineno = 0;
33 line.reserve(256);
35 while (reader.read_next_line(line)) {
36 const char *linep = line.c_str();
38 if (linep[0] == '@') {
39 // a new section starts
40 ysfx::string_list tokens = ysfx::split_strings_noempty(linep, &ysfx::ascii_isspace);
42 current = new ysfx_section_t;
44 if (tokens[0] == "@init")
45 toplevel.init.reset(current);
46 else if (tokens[0] == "@slider")
47 toplevel.slider.reset(current);
48 else if (tokens[0] == "@block")
49 toplevel.block.reset(current);
50 else if (tokens[0] == "@sample")
51 toplevel.sample.reset(current);
52 else if (tokens[0] == "@serialize")
53 toplevel.serialize.reset(current);
54 else if (tokens[0] == "@gfx") {
55 toplevel.gfx.reset(current);
56 long gfx_w = 0;
57 long gfx_h = 0;
58 if (tokens.size() > 1)
59 gfx_w = (long)ysfx::dot_atof(tokens[1].c_str());
60 if (tokens.size() > 2)
61 gfx_h = (long)ysfx::dot_atof(tokens[2].c_str());
62 toplevel.gfx_w = (gfx_w > 0) ? (uint32_t)gfx_w : 0;
63 toplevel.gfx_h = (gfx_h > 0) ? (uint32_t)gfx_h : 0;
65 else {
66 delete current;
67 if (error) {
68 error->line = lineno;
69 error->message = std::string("Invalid section: ") + line;
71 return false;
73 current->line_offset = lineno + 1;
75 else {
76 current->text.append(line);
77 current->text.push_back('\n');
80 ++lineno;
83 return true;
86 void ysfx_parse_header(ysfx_section_t *section, ysfx_header_t &header)
88 header = ysfx_header_t{};
90 ysfx::string_text_reader reader(section->text.c_str());
92 std::string line;
93 //uint32_t lineno = section->line_offset;
95 line.reserve(256);
97 ///
98 auto unprefix = [](const char *text, const char **restp, const char *prefix) -> bool {
99 size_t len = strlen(prefix);
100 if (strncmp(text, prefix, len))
101 return false;
102 if (restp)
103 *restp = text + len;
104 return true;
107 //--------------------------------------------------------------------------
108 // pass 1: regular metadata
110 while (reader.read_next_line(line)) {
111 const char *linep = line.c_str();
112 const char *rest = nullptr;
114 ysfx_slider_t slider;
115 ysfx_parsed_filename_t filename;
118 if (unprefix(linep, &rest, "desc:")) {
119 if (header.desc.empty())
120 header.desc = ysfx::trim(rest, &ysfx::ascii_isspace);
122 else if (unprefix(linep, &rest, "author:")) {
123 if (header.author.empty())
124 header.author = ysfx::trim(rest, &ysfx::ascii_isspace);
126 else if (unprefix(linep, &rest, "tags:")) {
127 if (header.tags.empty()) {
128 for (const std::string &tag : ysfx::split_strings_noempty(rest, &ysfx::ascii_isspace))
129 header.tags.push_back(tag);
132 else if (unprefix(linep, &rest, "in_pin:")) {
133 header.explicit_pins = true;
134 header.in_pins.push_back(ysfx::trim(rest, &ysfx::ascii_isspace));
136 else if (unprefix(linep, &rest, "out_pin:")) {
137 header.explicit_pins = true;
138 header.out_pins.push_back(ysfx::trim(rest, &ysfx::ascii_isspace));
140 else if (unprefix(linep, &rest, "options:")) {
141 for (const std::string &opt : ysfx::split_strings_noempty(rest, &ysfx::ascii_isspace)) {
142 size_t pos = opt.find('=');
143 std::string name = (pos == opt.npos) ? opt : opt.substr(0, pos);
144 std::string value = (pos == opt.npos) ? std::string{} : opt.substr(pos + 1);
145 if (name == "gmem")
146 header.options.gmem = value;
147 else if (name == "maxmem") {
148 int32_t maxmem = (int32_t)ysfx::dot_atof(value.c_str());
149 header.options.maxmem = (maxmem < 0) ? 0 : (uint32_t)maxmem;
151 else if (name == "want_all_kb")
152 header.options.want_all_kb = true;
153 else if (name == "no_meter")
154 header.options.no_meter = true;
157 else if (unprefix(linep, &rest, "import") && ysfx::ascii_isspace(rest[0]))
158 header.imports.push_back(ysfx::trim(rest + 1, &ysfx::ascii_isspace));
159 else if (ysfx_parse_slider(linep, slider)) {
160 if (slider.id >= ysfx_max_sliders)
161 continue;
162 slider.exists = true;
163 header.sliders[slider.id] = slider;
165 else if (ysfx_parse_filename(linep, filename)) {
166 if (filename.index != header.filenames.size())
167 continue;
168 header.filenames.push_back(std::move(filename.filename));
171 //++lineno;
174 //--------------------------------------------------------------------------
175 // pass 2: comments
177 reader = ysfx::string_text_reader{section->text.c_str()};
179 while (reader.read_next_line(line)) {
180 const char *linep = line.c_str();
181 const char *rest = nullptr;
183 // some files contain metadata in the form of comments
184 // this is not part of spec, but we'll take this info regardless
186 if (unprefix(linep, &rest, "//author:")) {
187 if (header.author.empty())
188 header.author = ysfx::trim(rest, &ysfx::ascii_isspace);
190 else if (unprefix(linep, &rest, "//tags:")) {
191 if (header.tags.empty()) {
192 for (const std::string &tag : ysfx::split_strings_noempty(rest, &ysfx::ascii_isspace))
193 header.tags.push_back(tag);
198 //--------------------------------------------------------------------------
199 if (header.in_pins.size() == 1 && !ysfx::ascii_casecmp(header.in_pins.front().c_str(), "none"))
200 header.in_pins.clear();
201 if (header.out_pins.size() == 1 && !ysfx::ascii_casecmp(header.out_pins.front().c_str(), "none"))
202 header.out_pins.clear();
204 if (header.in_pins.size() > ysfx_max_channels)
205 header.in_pins.resize(ysfx_max_channels);
206 if (header.out_pins.size() > ysfx_max_channels)
207 header.out_pins.resize(ysfx_max_channels);
210 bool ysfx_parse_slider(const char *line, ysfx_slider_t &slider)
212 // NOTE this parser is intentionally very permissive,
213 // in order to match the reference behavior
215 slider = ysfx_slider_t{};
217 #define PARSE_FAIL do { \
218 /*fprintf(stderr, "parse error (line %d): `%s`\n", __LINE__, line);*/ \
219 return false; \
220 } while (0)
222 const char *cur = line;
224 for (const char *p = "slider"; *p; ++p) {
225 if (*cur++ != *p)
226 PARSE_FAIL;
229 // parse ID (1-index)
230 unsigned long id = strtoul(cur, (char **)&cur, 10);
231 if (id < 1 || id > ysfx_max_sliders)
232 PARSE_FAIL;
233 slider.id = (uint32_t)--id;
235 // semicolon
236 if (*cur++ != ':')
237 PARSE_FAIL;
239 // search if there is an '=' sign prior to any '<' or ','
240 // if there is, it's a custom variable
242 const char *pos = cur;
243 for (char c; pos && (c = *pos) && c != '='; )
244 pos = (c == '<' || c == ',') ? nullptr : (pos + 1);
245 if (pos && *pos) {
246 slider.var.assign(cur, pos);
247 cur = pos + 1;
249 else
250 slider.var = "slider" + std::to_string(id + 1);
253 // a regular slider
254 if (*cur != '/') {
255 slider.def = (ysfx_real)ysfx::dot_strtod(cur, (char **)&cur);
257 while (*cur && *cur != ',' && *cur != '<') ++cur;
258 if (!*cur) PARSE_FAIL;
260 // no range specification
261 if (*cur == ',')
262 ++cur;
263 // range specification
264 else if (*cur == '<') {
265 ++cur;
267 // min
268 slider.min = (ysfx_real)ysfx::dot_strtod(cur, (char **)&cur);
270 while (*cur && *cur != ',' && *cur != '>') ++cur;
271 if (!*cur) PARSE_FAIL;
273 // max
274 if (*cur == ',') {
275 ++cur;
276 slider.max = (ysfx_real)ysfx::dot_strtod(cur, (char **)&cur);
278 while (*cur && *cur != ',' && *cur != '>') ++cur;
279 if (!*cur) PARSE_FAIL;
282 // inc
283 if (*cur == ',') {
284 ++cur;
285 slider.inc = (ysfx_real)ysfx::dot_strtod(cur, (char **)&cur);
287 while (*cur && *cur != '{' && *cur != ',' && *cur != '>') ++cur;
288 if (!*cur) PARSE_FAIL;
290 // enumeration values
291 if (*cur == '{') {
292 const char *pos = ++cur;
294 while (*cur && *cur != '}' && *cur != '>') ++cur;
295 if (!*cur) PARSE_FAIL;
297 slider.is_enum = true;
298 slider.enum_names = ysfx::split_strings_noempty(
299 std::string(pos, cur).c_str(),
300 [](char c) -> bool { return c == ','; });
301 for (std::string &name : slider.enum_names)
302 name = ysfx::trim(name.c_str(), &ysfx::ascii_isspace);
306 while (*cur && *cur != '>') ++cur;
307 if (!*cur) PARSE_FAIL;
309 ++cur;
311 else
312 PARSE_FAIL;
314 // NOTE: skip ',' and whitespace. not sure why, it's how it is
315 while (*cur && (*cur == ',' || ysfx::ascii_isspace(*cur))) ++cur;
316 if (!*cur) PARSE_FAIL;
318 // a path slider
319 else
321 const char *pos = cur;
322 while (*cur && *cur != ':') ++cur;
323 if (!*cur) PARSE_FAIL;
325 slider.path.assign(pos, cur);
326 ++cur;
327 slider.def = (ysfx_real)ysfx::dot_strtod(cur, (char **)&cur);
328 slider.inc = 1;
329 slider.is_enum = true;
331 while (*cur && *cur != ':') ++cur;
332 if (!*cur) PARSE_FAIL;
334 ++cur;
337 // description and visibility
338 while (ysfx::ascii_isspace(*cur))
339 ++cur;
341 slider.initially_visible = true;
342 if (*cur == '-') {
343 ++cur;
344 slider.initially_visible = false;
347 slider.desc = ysfx::trim(cur, &ysfx::ascii_isspace);
348 if (slider.desc.empty())
349 PARSE_FAIL;
352 #undef PARSE_FAIL
354 return true;
357 bool ysfx_parse_filename(const char *line, ysfx_parsed_filename_t &filename)
359 filename = ysfx_parsed_filename_t{};
361 const char *cur = line;
363 for (const char *p = "filename:"; *p; ++p) {
364 if (*cur++ != *p)
365 return false;
368 int64_t index = (int64_t)ysfx::dot_strtod(cur, (char **)&cur);
369 if (index < 0 || index > ~(uint32_t)0)
370 return false;
372 while (*cur && *cur != ',') ++cur;
373 if (!*cur) return false;;
375 ++cur;
377 filename.index = (uint32_t)index;
378 filename.filename.assign(cur);
379 return true;