Audio plugin host https://kx.studio/carla
You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

381 lines
12KB

  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.
  14. //
  15. // SPDX-License-Identifier: Apache-2.0
  16. //
  17. #include "ysfx_parse.hpp"
  18. #include "ysfx_utils.hpp"
  19. #include <cstdlib>
  20. #include <cstring>
  21. bool ysfx_parse_toplevel(ysfx::text_reader &reader, ysfx_toplevel_t &toplevel, ysfx_parse_error *error)
  22. {
  23. toplevel = ysfx_toplevel_t{};
  24. ysfx_section_t *current = new ysfx_section_t;
  25. toplevel.header.reset(current);
  26. std::string line;
  27. uint32_t lineno = 0;
  28. line.reserve(256);
  29. while (reader.read_next_line(line)) {
  30. const char *linep = line.c_str();
  31. if (linep[0] == '@') {
  32. // a new section starts
  33. ysfx::string_list tokens = ysfx::split_strings_noempty(linep, &ysfx::ascii_isspace);
  34. current = new ysfx_section_t;
  35. if (tokens[0] == "@init")
  36. toplevel.init.reset(current);
  37. else if (tokens[0] == "@slider")
  38. toplevel.slider.reset(current);
  39. else if (tokens[0] == "@block")
  40. toplevel.block.reset(current);
  41. else if (tokens[0] == "@sample")
  42. toplevel.sample.reset(current);
  43. else if (tokens[0] == "@serialize")
  44. toplevel.serialize.reset(current);
  45. else if (tokens[0] == "@gfx") {
  46. toplevel.gfx.reset(current);
  47. long gfx_w = 0;
  48. long gfx_h = 0;
  49. if (tokens.size() > 1)
  50. gfx_w = (long)ysfx::dot_atof(tokens[1].c_str());
  51. if (tokens.size() > 2)
  52. gfx_h = (long)ysfx::dot_atof(tokens[2].c_str());
  53. toplevel.gfx_w = (gfx_w > 0) ? (uint32_t)gfx_w : 0;
  54. toplevel.gfx_h = (gfx_h > 0) ? (uint32_t)gfx_h : 0;
  55. }
  56. else {
  57. delete current;
  58. if (error) {
  59. error->line = lineno;
  60. error->message = std::string("Invalid section: ") + line;
  61. }
  62. return false;
  63. }
  64. current->line_offset = lineno + 1;
  65. }
  66. else {
  67. current->text.append(line);
  68. current->text.push_back('\n');
  69. }
  70. ++lineno;
  71. }
  72. return true;
  73. }
  74. void ysfx_parse_header(ysfx_section_t *section, ysfx_header_t &header)
  75. {
  76. header = ysfx_header_t{};
  77. ysfx::string_text_reader reader(section->text.c_str());
  78. std::string line;
  79. //uint32_t lineno = section->line_offset;
  80. line.reserve(256);
  81. ///
  82. auto unprefix = [](const char *text, const char **restp, const char *prefix) -> bool {
  83. size_t len = strlen(prefix);
  84. if (strncmp(text, prefix, len))
  85. return false;
  86. if (restp)
  87. *restp = text + len;
  88. return true;
  89. };
  90. //--------------------------------------------------------------------------
  91. // pass 1: regular metadata
  92. while (reader.read_next_line(line)) {
  93. const char *linep = line.c_str();
  94. const char *rest = nullptr;
  95. ysfx_slider_t slider;
  96. ysfx_parsed_filename_t filename;
  97. ///
  98. if (unprefix(linep, &rest, "desc:")) {
  99. if (header.desc.empty())
  100. header.desc = ysfx::trim(rest, &ysfx::ascii_isspace);
  101. }
  102. else if (unprefix(linep, &rest, "author:")) {
  103. if (header.author.empty())
  104. header.author = ysfx::trim(rest, &ysfx::ascii_isspace);
  105. }
  106. else if (unprefix(linep, &rest, "tags:")) {
  107. if (header.tags.empty()) {
  108. for (const std::string &tag : ysfx::split_strings_noempty(rest, &ysfx::ascii_isspace))
  109. header.tags.push_back(tag);
  110. }
  111. }
  112. else if (unprefix(linep, &rest, "in_pin:")) {
  113. header.explicit_pins = true;
  114. header.in_pins.push_back(ysfx::trim(rest, &ysfx::ascii_isspace));
  115. }
  116. else if (unprefix(linep, &rest, "out_pin:")) {
  117. header.explicit_pins = true;
  118. header.out_pins.push_back(ysfx::trim(rest, &ysfx::ascii_isspace));
  119. }
  120. else if (unprefix(linep, &rest, "options:")) {
  121. for (const std::string &opt : ysfx::split_strings_noempty(rest, &ysfx::ascii_isspace)) {
  122. size_t pos = opt.find('=');
  123. std::string name = (pos == opt.npos) ? opt : opt.substr(0, pos);
  124. std::string value = (pos == opt.npos) ? std::string{} : opt.substr(pos + 1);
  125. if (name == "gmem")
  126. header.options.gmem = value;
  127. else if (name == "maxmem") {
  128. int32_t maxmem = (int32_t)ysfx::dot_atof(value.c_str());
  129. header.options.maxmem = (maxmem < 0) ? 0 : (uint32_t)maxmem;
  130. }
  131. else if (name == "want_all_kb")
  132. header.options.want_all_kb = true;
  133. else if (name == "no_meter")
  134. header.options.no_meter = true;
  135. }
  136. }
  137. else if (unprefix(linep, &rest, "import") && ysfx::ascii_isspace(rest[0]))
  138. header.imports.push_back(ysfx::trim(rest + 1, &ysfx::ascii_isspace));
  139. else if (ysfx_parse_slider(linep, slider)) {
  140. if (slider.id >= ysfx_max_sliders)
  141. continue;
  142. slider.exists = true;
  143. header.sliders[slider.id] = slider;
  144. }
  145. else if (ysfx_parse_filename(linep, filename)) {
  146. if (filename.index != header.filenames.size())
  147. continue;
  148. header.filenames.push_back(std::move(filename.filename));
  149. }
  150. //++lineno;
  151. }
  152. //--------------------------------------------------------------------------
  153. // pass 2: comments
  154. reader = ysfx::string_text_reader{section->text.c_str()};
  155. while (reader.read_next_line(line)) {
  156. const char *linep = line.c_str();
  157. const char *rest = nullptr;
  158. // some files contain metadata in the form of comments
  159. // this is not part of spec, but we'll take this info regardless
  160. if (unprefix(linep, &rest, "//author:")) {
  161. if (header.author.empty())
  162. header.author = ysfx::trim(rest, &ysfx::ascii_isspace);
  163. }
  164. else if (unprefix(linep, &rest, "//tags:")) {
  165. if (header.tags.empty()) {
  166. for (const std::string &tag : ysfx::split_strings_noempty(rest, &ysfx::ascii_isspace))
  167. header.tags.push_back(tag);
  168. }
  169. }
  170. }
  171. //--------------------------------------------------------------------------
  172. if (header.in_pins.size() == 1 && !ysfx::ascii_casecmp(header.in_pins.front().c_str(), "none"))
  173. header.in_pins.clear();
  174. if (header.out_pins.size() == 1 && !ysfx::ascii_casecmp(header.out_pins.front().c_str(), "none"))
  175. header.out_pins.clear();
  176. if (header.in_pins.size() > ysfx_max_channels)
  177. header.in_pins.resize(ysfx_max_channels);
  178. if (header.out_pins.size() > ysfx_max_channels)
  179. header.out_pins.resize(ysfx_max_channels);
  180. }
  181. bool ysfx_parse_slider(const char *line, ysfx_slider_t &slider)
  182. {
  183. // NOTE this parser is intentionally very permissive,
  184. // in order to match the reference behavior
  185. slider = ysfx_slider_t{};
  186. #define PARSE_FAIL do { \
  187. /*fprintf(stderr, "parse error (line %d): `%s`\n", __LINE__, line);*/ \
  188. return false; \
  189. } while (0)
  190. const char *cur = line;
  191. for (const char *p = "slider"; *p; ++p) {
  192. if (*cur++ != *p)
  193. PARSE_FAIL;
  194. }
  195. // parse ID (1-index)
  196. unsigned long id = strtoul(cur, (char **)&cur, 10);
  197. if (id < 1 || id > ysfx_max_sliders)
  198. PARSE_FAIL;
  199. slider.id = (uint32_t)--id;
  200. // semicolon
  201. if (*cur++ != ':')
  202. PARSE_FAIL;
  203. // search if there is an '=' sign prior to any '<' or ','
  204. // if there is, it's a custom variable
  205. {
  206. const char *pos = cur;
  207. for (char c; pos && (c = *pos) && c != '='; )
  208. pos = (c == '<' || c == ',') ? nullptr : (pos + 1);
  209. if (pos && *pos) {
  210. slider.var.assign(cur, pos);
  211. cur = pos + 1;
  212. }
  213. else
  214. slider.var = "slider" + std::to_string(id + 1);
  215. }
  216. // a regular slider
  217. if (*cur != '/') {
  218. slider.def = (ysfx_real)ysfx::dot_strtod(cur, (char **)&cur);
  219. while (*cur && *cur != ',' && *cur != '<') ++cur;
  220. if (!*cur) PARSE_FAIL;
  221. // no range specification
  222. if (*cur == ',')
  223. ++cur;
  224. // range specification
  225. else if (*cur == '<') {
  226. ++cur;
  227. // min
  228. slider.min = (ysfx_real)ysfx::dot_strtod(cur, (char **)&cur);
  229. while (*cur && *cur != ',' && *cur != '>') ++cur;
  230. if (!*cur) PARSE_FAIL;
  231. // max
  232. if (*cur == ',') {
  233. ++cur;
  234. slider.max = (ysfx_real)ysfx::dot_strtod(cur, (char **)&cur);
  235. while (*cur && *cur != ',' && *cur != '>') ++cur;
  236. if (!*cur) PARSE_FAIL;
  237. }
  238. // inc
  239. if (*cur == ',') {
  240. ++cur;
  241. slider.inc = (ysfx_real)ysfx::dot_strtod(cur, (char **)&cur);
  242. while (*cur && *cur != '{' && *cur != ',' && *cur != '>') ++cur;
  243. if (!*cur) PARSE_FAIL;
  244. // enumeration values
  245. if (*cur == '{') {
  246. const char *pos = ++cur;
  247. while (*cur && *cur != '}' && *cur != '>') ++cur;
  248. if (!*cur) PARSE_FAIL;
  249. slider.is_enum = true;
  250. slider.enum_names = ysfx::split_strings_noempty(
  251. std::string(pos, cur).c_str(),
  252. [](char c) -> bool { return c == ','; });
  253. for (std::string &name : slider.enum_names)
  254. name = ysfx::trim(name.c_str(), &ysfx::ascii_isspace);
  255. }
  256. }
  257. while (*cur && *cur != '>') ++cur;
  258. if (!*cur) PARSE_FAIL;
  259. ++cur;
  260. }
  261. else
  262. PARSE_FAIL;
  263. // NOTE: skip ',' and whitespace. not sure why, it's how it is
  264. while (*cur && (*cur == ',' || ysfx::ascii_isspace(*cur))) ++cur;
  265. if (!*cur) PARSE_FAIL;
  266. }
  267. // a path slider
  268. else
  269. {
  270. const char *pos = cur;
  271. while (*cur && *cur != ':') ++cur;
  272. if (!*cur) PARSE_FAIL;
  273. slider.path.assign(pos, cur);
  274. ++cur;
  275. slider.def = (ysfx_real)ysfx::dot_strtod(cur, (char **)&cur);
  276. slider.inc = 1;
  277. slider.is_enum = true;
  278. while (*cur && *cur != ':') ++cur;
  279. if (!*cur) PARSE_FAIL;
  280. ++cur;
  281. }
  282. // description and visibility
  283. while (ysfx::ascii_isspace(*cur))
  284. ++cur;
  285. slider.initially_visible = true;
  286. if (*cur == '-') {
  287. ++cur;
  288. slider.initially_visible = false;
  289. }
  290. slider.desc = ysfx::trim(cur, &ysfx::ascii_isspace);
  291. if (slider.desc.empty())
  292. PARSE_FAIL;
  293. //
  294. #undef PARSE_FAIL
  295. return true;
  296. }
  297. bool ysfx_parse_filename(const char *line, ysfx_parsed_filename_t &filename)
  298. {
  299. filename = ysfx_parsed_filename_t{};
  300. const char *cur = line;
  301. for (const char *p = "filename:"; *p; ++p) {
  302. if (*cur++ != *p)
  303. return false;
  304. }
  305. int64_t index = (int64_t)ysfx::dot_strtod(cur, (char **)&cur);
  306. if (index < 0 || index > ~(uint32_t)0)
  307. return false;
  308. while (*cur && *cur != ',') ++cur;
  309. if (!*cur) return false;;
  310. ++cur;
  311. filename.index = (uint32_t)index;
  312. filename.filename.assign(cur);
  313. return true;
  314. }