1 // Copyright 2021 Jean Pierre Cimalando
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
7 // http://www.apache.org/licenses/LICENSE-2.0
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"
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
);
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
);
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;
69 error
->message
= std::string("Invalid section: ") + line
;
73 current
->line_offset
= lineno
+ 1;
76 current
->text
.append(line
);
77 current
->text
.push_back('\n');
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());
93 //uint32_t lineno = section->line_offset;
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
))
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);
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
)
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())
168 header
.filenames
.push_back(std::move(filename
.filename
));
174 //--------------------------------------------------------------------------
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);*/ \
222 const char *cur
= line
;
224 for (const char *p
= "slider"; *p
; ++p
) {
229 // parse ID (1-index)
230 unsigned long id
= strtoul(cur
, (char **)&cur
, 10);
231 if (id
< 1 || id
> ysfx_max_sliders
)
233 slider
.id
= (uint32_t)--id
;
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);
246 slider
.var
.assign(cur
, pos
);
250 slider
.var
= "slider" + std::to_string(id
+ 1);
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
263 // range specification
264 else if (*cur
== '<') {
268 slider
.min
= (ysfx_real
)ysfx::dot_strtod(cur
, (char **)&cur
);
270 while (*cur
&& *cur
!= ',' && *cur
!= '>') ++cur
;
271 if (!*cur
) PARSE_FAIL
;
276 slider
.max
= (ysfx_real
)ysfx::dot_strtod(cur
, (char **)&cur
);
278 while (*cur
&& *cur
!= ',' && *cur
!= '>') ++cur
;
279 if (!*cur
) PARSE_FAIL
;
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
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
;
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
;
321 const char *pos
= cur
;
322 while (*cur
&& *cur
!= ':') ++cur
;
323 if (!*cur
) PARSE_FAIL
;
325 slider
.path
.assign(pos
, cur
);
327 slider
.def
= (ysfx_real
)ysfx::dot_strtod(cur
, (char **)&cur
);
329 slider
.is_enum
= true;
331 while (*cur
&& *cur
!= ':') ++cur
;
332 if (!*cur
) PARSE_FAIL
;
337 // description and visibility
338 while (ysfx::ascii_isspace(*cur
))
341 slider
.initially_visible
= true;
344 slider
.initially_visible
= false;
347 slider
.desc
= ysfx::trim(cur
, &ysfx::ascii_isspace
);
348 if (slider
.desc
.empty())
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
) {
368 int64_t index
= (int64_t)ysfx::dot_strtod(cur
, (char **)&cur
);
369 if (index
< 0 || index
> ~(uint32_t)0)
372 while (*cur
&& *cur
!= ',') ++cur
;
373 if (!*cur
) return false;;
377 filename
.index
= (uint32_t)index
;
378 filename
.filename
.assign(cur
);