From 0c6bf5a7a97d2a1b9403506dd12226bfc68af461 Mon Sep 17 00:00:00 2001 From: Jean Pierre Cimalando Date: Thu, 14 May 2020 21:24:24 +0200 Subject: [PATCH] parser: support recursive $-expansions --- src/sfizz/parser/Parser.cpp | 67 +++++++++++++++++++++++-------------- tests/ParsingT.cpp | 32 ++++++++++++++++++ 2 files changed, 73 insertions(+), 26 deletions(-) diff --git a/src/sfizz/parser/Parser.cpp b/src/sfizz/parser/Parser.cpp index 2c349e20..51eab71e 100644 --- a/src/sfizz/parser/Parser.cpp +++ b/src/sfizz/parser/Parser.cpp @@ -455,38 +455,53 @@ size_t Parser::extractToEol(Reader& reader, std::string* dst) std::string Parser::expandDollarVars(const SourceRange& range, absl::string_view src) { std::string dst; + std::string srcbuf; // temporary for retries when recursive + std::string name; // temporary for variable name + bool keepExpanding = true; + dst.reserve(2 * src.size()); + name.reserve(64); - size_t i = 0; - size_t n = src.size(); - while (i < n) { - char c = src[i++]; + while (keepExpanding) { + size_t i = 0; + size_t n = src.size(); + size_t numExpansions = 0; + while (i < n) { + char c = src[i++]; - if (c != '$') - dst.push_back(c); - else { - std::string name; - name.reserve(64); + if (c != '$') + dst.push_back(c); + else { + ++numExpansions; + name.clear(); - // ARIA: we will accumulate any chars after $, until this is the - // name of a known variable - auto def = _currentDefinitions.end(); - while (i < n && isIdentifierChar(src[i]) && def == _currentDefinitions.end()) { - name.push_back(src[i++]); - def = _currentDefinitions.find(name); + // ARIA: we will accumulate any chars after $, until this is the + // name of a known variable + auto def = _currentDefinitions.end(); + while (i < n && isIdentifierChar(src[i]) && def == _currentDefinitions.end()) { + name.push_back(src[i++]); + def = _currentDefinitions.find(name); + } + + if (name.empty()) { + emitWarning(range, "Expected variable name after $."); + continue; + } + + if (def == _currentDefinitions.end()) { + emitWarning(range, "The variable `" + name + "` is not defined."); + continue; + } + + dst.append(def->second); } + } - if (name.empty()) { - emitWarning(range, "Expected variable name after $."); - continue; - } - - if (def == _currentDefinitions.end()) { - emitWarning(range, "The variable `" + name + "` is not defined."); - continue; - } - - dst.append(def->second); + keepExpanding = numExpansions > 0; + if (keepExpanding) { + srcbuf = dst; + src = srcbuf; + dst.clear(); } } diff --git a/tests/ParsingT.cpp b/tests/ParsingT.cpp index b259e4a7..2c4127d5 100644 --- a/tests/ParsingT.cpp +++ b/tests/ParsingT.cpp @@ -589,3 +589,35 @@ R"(#define $a foo #define $b bar sample=$a-$b.wav REQUIRE(mock.fullBlockHeaders == expectedHeaders); REQUIRE(mock.fullBlockMembers == expectedMembers); } + +TEST_CASE("[Parsing] Recursive expansion") +{ + sfz::Parser parser; + ParsingMocker mock; + parser.setListener(&mock); + parser.parseString("/recursiveExpansion.sfz", +R"(#define $B foo-$A-baz +#define $A bar + sample=$B.wav)"); + + std::vector> expectedMembers = { + {{"sample", "foo-bar-baz.wav"}}, + }; + std::vector expectedHeaders = { + "region" + }; + std::vector expectedOpcodes; + + for (auto& members: expectedMembers) + for (auto& opcode: members) + expectedOpcodes.push_back(opcode); + + REQUIRE(mock.beginnings == 1); + REQUIRE(mock.endings == 1); + REQUIRE(mock.errors.empty()); + REQUIRE(mock.warnings.empty()); + REQUIRE(mock.opcodes == expectedOpcodes); + REQUIRE(mock.headers == expectedHeaders); + REQUIRE(mock.fullBlockHeaders == expectedHeaders); + REQUIRE(mock.fullBlockMembers == expectedMembers); +}