From c03345e95045ac39406faecf7ecd58248b3301ee Mon Sep 17 00:00:00 2001 From: Jean Pierre Cimalando Date: Tue, 17 Mar 2020 22:14:35 +0100 Subject: [PATCH] Adapt the newer parser to Sfizz --- clients/jack_client.cpp | 6 +++--- src/sfizz/Opcode.cpp | 6 ++---- src/sfizz/Opcode.h | 4 ++-- src/sfizz/Synth.cpp | 25 ++++++++++++++++------ src/sfizz/Synth.h | 32 ++++++++++++++++++++++++++--- src/sfizz/parser/Parser.cpp | 38 ++++++++++++++++++++++++++++++---- src/sfizz/parser/Parser.h | 41 +++++++++++++++++++++++++++++-------- tests/FilesT.cpp | 11 ++++++---- tests/PlotCurve.cpp | 17 ++++++++------- 9 files changed, 138 insertions(+), 42 deletions(-) diff --git a/clients/jack_client.cpp b/clients/jack_client.cpp index 599e0db3..771241e9 100644 --- a/clients/jack_client.cpp +++ b/clients/jack_client.cpp @@ -210,11 +210,11 @@ int main(int argc, char** argv) std::cout << "\tPreloadedSamples: " << synth.getNumPreloadedSamples() << '\n'; std::cout << "==========" << '\n'; std::cout << "Included files:" << '\n'; - for (auto& file : synth.getIncludedFiles()) - std::cout << '\t' << file.string() << '\n'; + for (auto& file : synth.getParser().getIncludedFiles()) + std::cout << '\t' << file << '\n'; std::cout << "==========" << '\n'; std::cout << "Defines:" << '\n'; - for (auto& define : synth.getDefines()) + for (auto& define : synth.getParser().getDefines()) std::cout << '\t' << define.first << '=' << define.second << '\n'; std::cout << "==========" << '\n'; std::cout << "Unknown opcodes:"; diff --git a/src/sfizz/Opcode.cpp b/src/sfizz/Opcode.cpp index cc022c11..fbe8bcc5 100644 --- a/src/sfizz/Opcode.cpp +++ b/src/sfizz/Opcode.cpp @@ -9,11 +9,9 @@ #include sfz::Opcode::Opcode(absl::string_view inputOpcode, absl::string_view inputValue) - : opcode(inputOpcode) - , value(inputValue) + : opcode(trim(inputOpcode)) + , value(trim(inputValue)) { - trimInPlace(value); - trimInPlace(opcode); size_t nextCharIndex { 0 }; int parameterPosition { 0 }; auto nextNumIndex = opcode.find_first_of("1234567890"); diff --git a/src/sfizz/Opcode.h b/src/sfizz/Opcode.h index c510f5a8..e4ff6c26 100644 --- a/src/sfizz/Opcode.h +++ b/src/sfizz/Opcode.h @@ -28,8 +28,8 @@ namespace sfz { struct Opcode { Opcode() = delete; Opcode(absl::string_view inputOpcode, absl::string_view inputValue); - absl::string_view opcode {}; - absl::string_view value {}; + std::string opcode {}; + std::string value {}; uint64_t lettersOnlyHash { Fnv1aBasis }; // This is to handle the integer parameters of some opcodes std::vector parameters; diff --git a/src/sfizz/Synth.cpp b/src/sfizz/Synth.cpp index de1db07e..1f0ab2eb 100644 --- a/src/sfizz/Synth.cpp +++ b/src/sfizz/Synth.cpp @@ -28,6 +28,8 @@ sfz::Synth::Synth() sfz::Synth::Synth(int numVoices) { + parser.setListener(this); + effectFactory.registerStandardEffectTypes(); effectBuses.reserve(5); // sufficient room for main and fx1-4 @@ -48,7 +50,7 @@ sfz::Synth::~Synth() resources.filePool.emptyFileLoadingQueues(); } -void sfz::Synth::callback(absl::string_view header, const std::vector& members) +void sfz::Synth::onParseFullBlock(const std::string& header, const std::vector& members) { switch (hash(header)) { case hash("global"): @@ -81,6 +83,16 @@ void sfz::Synth::callback(absl::string_view header, const std::vector& m } } +void sfz::Synth::onParseError(const SourceRange& range, const std::string& message) +{ + std::cerr << "Parse error L" << range.start.lineNumber << ": " << message << '\n'; +} + +void sfz::Synth::onParseWarning(const SourceRange& range, const std::string& message) +{ + std::cerr << "Parse warning L" << range.start.lineNumber << ": " << message << '\n'; +} + void sfz::Synth::buildRegion(const std::vector& regionOpcodes) { auto lastRegion = absl::make_unique(resources.midiState, defaultPath); @@ -283,14 +295,15 @@ bool sfz::Synth::loadSfzFile(const fs::path& file) } clear(); - auto parserReturned = sfz::OldParser::loadSfzFile(file); - if (!parserReturned) + + parser.parseFile(file); + if (parser.getErrorCount() > 0) return false; if (regions.empty()) return false; - resources.filePool.setRootDirectory(this->originalDirectory); + resources.filePool.setRootDirectory(parser.originalDirectory()); auto currentRegion = regions.begin(); auto lastRegion = regions.rbegin(); @@ -403,7 +416,7 @@ bool sfz::Synth::loadSfzFile(const fs::path& file) voice->setMaxEQsPerVoice(maxEQs); } - return parserReturned; + return true; } sfz::Voice* sfz::Synth::findFreeVoice() noexcept @@ -939,7 +952,7 @@ void sfz::Synth::resetAllControllers(int delay) noexcept fs::file_time_type sfz::Synth::checkModificationTime() { auto returnedTime = modificationTime; - for (const auto& file: getIncludedFiles()) { + for (const auto& file: parser.getIncludedFiles()) { const auto fileTime = fs::last_write_time(file); if (returnedTime < fileTime) returnedTime = fileTime; diff --git a/src/sfizz/Synth.h b/src/sfizz/Synth.h index 0eea55f2..f42d67e3 100644 --- a/src/sfizz/Synth.h +++ b/src/sfizz/Synth.h @@ -14,6 +14,7 @@ #include "LeakDetector.h" #include "MidiState.h" #include "AudioSpan.h" +#include "parser/Parser.h" #include "absl/types/span.h" #include #include @@ -59,7 +60,7 @@ namespace sfz { * The jack_client.cpp file contains examples of the most classical usage of the * synth and can be used as a reference. */ -class Synth : public OldParser { +class Synth final : public Parser::Listener { public: /** * @brief Construct a new Synth object with no voices. If you want sound @@ -88,7 +89,7 @@ public: * @return true * @return false if the file was not found or no regions were loaded. */ - bool loadSfzFile(const fs::path& file) final; + bool loadSfzFile(const fs::path& file); /** * @brief Get the current number of regions loaded * @@ -373,6 +374,20 @@ public: * */ void allSoundOff() noexcept; + + /** + * @brief Get the parser. + * + * @return A reference to the parser. + */ + Parser& getParser() noexcept { return parser; } + /** + * @brief Get the parser. + * + * @return A reference to the parser. + */ + const Parser& getParser() const noexcept { return parser; } + protected: /** * @brief The parser callback; this is called by the parent object each time @@ -382,7 +397,17 @@ protected: * @param header the header for the set of opcodes * @param members the opcode members */ - void callback(absl::string_view header, const std::vector& members) final; + void onParseFullBlock(const std::string& header, const std::vector& members) override; + + /** + * @brief The parser callback when an error occurs. + */ + void onParseError(const SourceRange& range, const std::string& message) override; + + /** + * @brief The parser callback when a warning occurs. + */ + void onParseWarning(const SourceRange& range, const std::string& message) override; private: /** @@ -505,6 +530,7 @@ private: Duration dispatchDuration { 0 }; + Parser parser; fs::file_time_type modificationTime { }; LEAK_DETECTOR(Synth); diff --git a/src/sfizz/parser/Parser.cpp b/src/sfizz/parser/Parser.cpp index f594e4e9..7efb4f62 100644 --- a/src/sfizz/parser/Parser.cpp +++ b/src/sfizz/parser/Parser.cpp @@ -26,7 +26,8 @@ void Parser::addDefinition(absl::string_view id, absl::string_view value) void Parser::parseFile(const fs::path& path) { _pathsIncluded.clear(); - _lastHeader.clear(); + _currentHeader.reset(); + _currentOpcodes.clear(); _errorCount = 0; _warningCount = 0; @@ -35,6 +36,7 @@ void Parser::parseFile(const fs::path& path) includeNewFile(path); processTopLevel(); + flushCurrentHeader(); if (_listener) _listener->onParseEnd(); @@ -45,15 +47,28 @@ void Parser::includeNewFile(const fs::path& path) fs::path fullPath = (path.empty() || path.is_absolute()) ? path : _originalDirectory / path; - if (!_pathsIncluded.insert(fullPath.string()).second) + if (_pathsIncluded.empty()) + _originalDirectory = fullPath.parent_path(); + else if (_pathsIncluded.find(fullPath.string()) != _pathsIncluded.end()) { + if (_recursiveIncludeGuardEnabled) + return; + } + + if (_included.size() == _maxIncludeDepth) { + SourceLocation loc; + loc.filePath = std::make_shared(fullPath); + emitError({ loc, loc }, "Exceeded maximum include depth (" + std::to_string(_maxIncludeDepth) + ")"); return; + } auto fileReader = absl::make_unique(fullPath); if (fileReader->hasError()) { SourceLocation loc = fileReader->location(); emitError({ loc, loc }, "Cannot open file for reading: " + fullPath.string()); + return; } + _pathsIncluded.insert(fullPath.string()); _included.push_back(std::move(fileReader)); } @@ -174,7 +189,9 @@ void Parser::processHeader() return; } - _lastHeader = name; + flushCurrentHeader(); + + _currentHeader = name; if (_listener) _listener->onParseHeader({ start, end }, name); } @@ -245,10 +262,12 @@ void Parser::processOpcode() } SourceLocation valueEnd = reader.location(); - if (_lastHeader.empty()) + if (!_currentHeader) emitWarning({ opcodeStart, valueEnd }, "The opcode is not under any header."); std::string valueExpanded = expandDollarVars({ valueStart, valueEnd }, valueRaw); + _currentOpcodes.emplace_back(nameExpanded, valueExpanded); + if (_listener) _listener->onParseOpcode({ opcodeStart, opcodeEnd }, { valueStart, valueEnd }, nameExpanded, valueExpanded); } @@ -275,6 +294,17 @@ void Parser::recover() reader.skipWhile([](char c) { return c != '\n'; }); } +void Parser::flushCurrentHeader() +{ + if (_currentHeader) { + if (_listener) + _listener->onParseFullBlock(*_currentHeader, _currentOpcodes); + _currentHeader.reset(); + } + + _currentOpcodes.clear(); +} + bool Parser::hasComment(Reader& reader) { if (reader.peekChar() != '/') diff --git a/src/sfizz/parser/Parser.h b/src/sfizz/parser/Parser.h index 8f97ae41..cdcb50cd 100644 --- a/src/sfizz/parser/Parser.h +++ b/src/sfizz/parser/Parser.h @@ -5,7 +5,9 @@ // If not, contact the sfizz maintainers at https://github.com/sfztools/sfizz #pragma once +#include "../Opcode.h" #include "ghc/fs_std.hpp" +#include "absl/types/optional.h" #include "absl/container/flat_hash_map.h" #include "absl/container/flat_hash_set.h" #include @@ -28,17 +30,32 @@ public: void addDefinition(absl::string_view id, absl::string_view value); void parseFile(const fs::path& path); + void setRecursiveIncludeGuardEnabled(bool en) { _recursiveIncludeGuardEnabled = en; } + void setMaximumIncludeDepth(size_t depth) { _maxIncludeDepth = depth; } + + const fs::path& originalDirectory() const noexcept { return _originalDirectory; } + + typedef absl::flat_hash_set IncludeFileSet; + typedef absl::flat_hash_map DefinitionSet; + + const IncludeFileSet& getIncludedFiles() const noexcept { return _pathsIncluded; } + const DefinitionSet& getDefines() const noexcept { return _definitions; } + size_t getErrorCount() const noexcept { return _errorCount; } size_t getWarningCount() const noexcept { return _warningCount; } class Listener { public: - virtual void onParseBegin() = 0; - virtual void onParseEnd() = 0; - virtual void onParseHeader(const SourceRange& range, const std::string& header) = 0; - virtual void onParseOpcode(const SourceRange& rangeOpcode, const SourceRange& rangeValue, const std::string& name, const std::string& value) = 0; - virtual void onParseError(const SourceRange& range, const std::string& message) = 0; - virtual void onParseWarning(const SourceRange& range, const std::string& message) = 0; + // low-level parsing + virtual void onParseBegin() {} + virtual void onParseEnd() {} + virtual void onParseHeader(const SourceRange& /*range*/, const std::string& /*header*/) {} + virtual void onParseOpcode(const SourceRange& /*rangeOpcode*/, const SourceRange& /*rangeValue*/, const std::string& /*name*/, const std::string& /*value*/) {} + virtual void onParseError(const SourceRange& /*range*/, const std::string& /*message*/) {} + virtual void onParseWarning(const SourceRange& /*range*/, const std::string& /*message*/) {} + + // high-level parsing + virtual void onParseFullBlock(const std::string& /*header*/, const std::vector& /*opcodes*/) {} }; void setListener(Listener* listener) noexcept { _listener = listener; } @@ -57,6 +74,9 @@ private: // recover after error void recover(); + // state handling + void flushCurrentHeader(); + // helpers static bool hasComment(Reader& reader); static size_t skipComment(Reader& reader); @@ -73,16 +93,19 @@ private: Listener* _listener = nullptr; fs::path _originalDirectory { fs::current_path() }; - absl::flat_hash_map _definitions; + DefinitionSet _definitions; // a current list of files included, last one at the back std::vector> _included; // recursive include guard - absl::flat_hash_set _pathsIncluded; + size_t _maxIncludeDepth = 32; + bool _recursiveIncludeGuardEnabled = false; + IncludeFileSet _pathsIncluded; // parsing state - std::string _lastHeader; + absl::optional _currentHeader; + std::vector _currentOpcodes; // errors and warnings size_t _errorCount = 0; diff --git a/tests/FilesT.cpp b/tests/FilesT.cpp index b397d154..6284ec6c 100644 --- a/tests/FilesT.cpp +++ b/tests/FilesT.cpp @@ -98,7 +98,8 @@ TEST_CASE("[Files] Subdir include Win") TEST_CASE("[Files] Recursive include (with include guard)") { sfz::Synth synth; - synth.enableRecursiveIncludeGuard(); + sfz::Parser& parser = synth.getParser(); + parser.setRecursiveIncludeGuardEnabled(true); synth.loadSfzFile(fs::current_path() / "tests/TestFiles/Includes/root_recursive.sfz"); REQUIRE(synth.getNumRegions() == 2); REQUIRE(synth.getRegionView(0)->sample == "dummy_recursive2.wav"); @@ -108,7 +109,8 @@ TEST_CASE("[Files] Recursive include (with include guard)") TEST_CASE("[Files] Include loops (with include guard)") { sfz::Synth synth; - synth.enableRecursiveIncludeGuard(); + sfz::Parser& parser = synth.getParser(); + parser.setRecursiveIncludeGuardEnabled(true); synth.loadSfzFile(fs::current_path() / "tests/TestFiles/Includes/root_loop.sfz"); REQUIRE(synth.getNumRegions() == 2); REQUIRE(synth.getRegionView(0)->sample == "dummy_loop2.wav"); @@ -502,8 +504,9 @@ TEST_CASE("[Files] Case sentitiveness") TEST_CASE("[Files] Empty file") { sfz::Synth synth; + sfz::Parser& parser = synth.getParser(); REQUIRE(!synth.loadSfzFile("")); - REQUIRE(synth.getIncludedFiles().empty()); + REQUIRE(parser.getIncludedFiles().empty()); REQUIRE(!synth.loadSfzFile({})); - REQUIRE(synth.getIncludedFiles().empty()); + REQUIRE(parser.getIncludedFiles().empty()); } diff --git a/tests/PlotCurve.cpp b/tests/PlotCurve.cpp index c1846ba2..954a7946 100644 --- a/tests/PlotCurve.cpp +++ b/tests/PlotCurve.cpp @@ -16,7 +16,7 @@ */ #include "sfizz/Curve.h" -#include "sfizz/Parser.h" +#include "sfizz/parser/Parser.h" #include "absl/strings/numbers.h" #include "absl/types/span.h" #include @@ -34,16 +34,16 @@ static void usage() } /** - * @brief Parser which extracts the configuration of curves + * @brief Parser listener which extracts the configuration of curves */ -class CurveParser : public sfz::OldParser { +class CurveParserListener : public sfz::Parser::Listener { public: - explicit CurveParser(sfz::CurveSet& curveSet) + explicit CurveParserListener(sfz::CurveSet& curveSet) : curveSet(curveSet) { } - void callback(absl::string_view header, const std::vector& members) override + void onParseFullBlock(const std::string& header, const std::vector& members) override { if (header == "curve") curveSet.addCurveFromHeader(members); @@ -73,8 +73,11 @@ int main(int argc, char* argv[]) sfz::CurveSet curveSet = sfz::CurveSet::createPredefined(); if (!filePath.empty()) { - CurveParser parser(curveSet); - if (!parser.loadSfzFile(filePath)) { + sfz::Parser parser; + CurveParserListener listener(curveSet); + parser.setListener(&listener); + parser.parseFile(filePath); + if (parser.getErrorCount() > 0) { std::cerr << "Cannot load SFZ: " << filePath << "\n"; return 1; }