parser: support recursive $-expansions

This commit is contained in:
Jean Pierre Cimalando 2020-05-14 21:24:24 +02:00
parent 650c6140bd
commit 0c6bf5a7a9
2 changed files with 73 additions and 26 deletions

View file

@ -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 Parser::expandDollarVars(const SourceRange& range, absl::string_view src)
{ {
std::string dst; 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()); dst.reserve(2 * src.size());
name.reserve(64);
size_t i = 0; while (keepExpanding) {
size_t n = src.size(); size_t i = 0;
while (i < n) { size_t n = src.size();
char c = src[i++]; size_t numExpansions = 0;
while (i < n) {
char c = src[i++];
if (c != '$') if (c != '$')
dst.push_back(c); dst.push_back(c);
else { else {
std::string name; ++numExpansions;
name.reserve(64); name.clear();
// ARIA: we will accumulate any chars after $, until this is the // ARIA: we will accumulate any chars after $, until this is the
// name of a known variable // name of a known variable
auto def = _currentDefinitions.end(); auto def = _currentDefinitions.end();
while (i < n && isIdentifierChar(src[i]) && def == _currentDefinitions.end()) { while (i < n && isIdentifierChar(src[i]) && def == _currentDefinitions.end()) {
name.push_back(src[i++]); name.push_back(src[i++]);
def = _currentDefinitions.find(name); 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()) { keepExpanding = numExpansions > 0;
emitWarning(range, "Expected variable name after $."); if (keepExpanding) {
continue; srcbuf = dst;
} src = srcbuf;
dst.clear();
if (def == _currentDefinitions.end()) {
emitWarning(range, "The variable `" + name + "` is not defined.");
continue;
}
dst.append(def->second);
} }
} }

View file

@ -589,3 +589,35 @@ R"(#define $a foo #define $b bar <region> sample=$a-$b.wav
REQUIRE(mock.fullBlockHeaders == expectedHeaders); REQUIRE(mock.fullBlockHeaders == expectedHeaders);
REQUIRE(mock.fullBlockMembers == expectedMembers); 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
<region> sample=$B.wav)");
std::vector<std::vector<sfz::Opcode>> expectedMembers = {
{{"sample", "foo-bar-baz.wav"}},
};
std::vector<std::string> expectedHeaders = {
"region"
};
std::vector<sfz::Opcode> 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);
}