Initial parser rework

This commit is contained in:
Jean Pierre Cimalando 2020-03-14 17:36:26 +01:00 committed by Paul Ferrand
parent fa150e2668
commit 30c8d25a59
14 changed files with 1100 additions and 7 deletions

View file

@ -19,6 +19,8 @@ set (SFIZZ_SOURCES
sfizz/Curve.cpp
sfizz/Wavetables.cpp
sfizz/Effects.cpp
sfizz/parser/Parser.cpp
sfizz/parser/ParserPrivate.cpp
sfizz/effects/Nothing.cpp
sfizz/effects/Filter.cpp
sfizz/effects/Eq.cpp

View file

@ -19,7 +19,7 @@ void removeCommentOnLine(absl::string_view& line)
line.remove_suffix(line.size() - position);
}
bool sfz::Parser::loadSfzFile(const fs::path& file)
bool sfz::OldParser::loadSfzFile(const fs::path& file)
{
includedFiles.clear();
@ -56,7 +56,7 @@ bool sfz::Parser::loadSfzFile(const fs::path& file)
return true;
}
void sfz::Parser::readSfzFile(const fs::path& fileName, std::vector<std::string>& lines) noexcept
void sfz::OldParser::readSfzFile(const fs::path& fileName, std::vector<std::string>& lines) noexcept
{
std::ifstream fileStream(fileName.c_str());
if (!fileStream)

View file

@ -14,9 +14,9 @@
#include <vector>
namespace sfz {
class Parser {
class OldParser {
public:
virtual ~Parser() = default;
virtual ~OldParser() = default;
virtual bool loadSfzFile(const fs::path& file);
const std::map<std::string, std::string>& getDefines() const noexcept { return defines; }
const std::vector<fs::path>& getIncludedFiles() const noexcept { return includedFiles; }

View file

@ -283,7 +283,7 @@ bool sfz::Synth::loadSfzFile(const fs::path& file)
}
clear();
auto parserReturned = sfz::Parser::loadSfzFile(file);
auto parserReturned = sfz::OldParser::loadSfzFile(file);
if (!parserReturned)
return false;

View file

@ -59,7 +59,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 Parser {
class Synth : public OldParser {
public:
/**
* @brief Construct a new Synth object with no voices. If you want sound

383
src/sfizz/parser/Parser.cpp Normal file
View file

@ -0,0 +1,383 @@
// SPDX-License-Identifier: BSD-2-Clause
// This code is part of the sfizz library and is licensed under a BSD 2-clause
// license. You should have receive a LICENSE.md file along with the code.
// If not, contact the sfizz maintainers at https://github.com/sfztools/sfizz
#include "Parser.h"
#include "ParserPrivate.h"
#include "absl/memory/memory.h"
namespace sfz {
Parser::Parser()
{
}
Parser::~Parser()
{
}
void Parser::addDefinition(absl::string_view id, absl::string_view value)
{
_definitions[id] = std::string(value);
}
void Parser::parseFile(const fs::path& path)
{
_pathsIncluded.clear();
_lastHeader.clear();
_errorCount = 0;
_warningCount = 0;
if (_listener)
_listener->onParseBegin();
includeNewFile(path);
processTopLevel();
if (_listener)
_listener->onParseEnd();
}
void Parser::includeNewFile(const fs::path& path)
{
fs::path fullPath =
(path.empty() || path.is_absolute()) ? path : _originalDirectory / path;
if (!_pathsIncluded.insert(fullPath.string()).second)
return;
auto fileReader = absl::make_unique<FileReader>(fullPath);
if (fileReader->hasError()) {
SourceLocation loc = fileReader->location();
emitError({ loc, loc }, "Cannot open file for reading: " + fullPath.string());
}
_included.push_back(std::move(fileReader));
}
void Parser::processTopLevel()
{
while (!_included.empty()) {
Reader& reader = *_included.back();
while (reader.skipChars(" \t\r\n") || skipComment(reader));
switch (reader.peekChar()) {
case Reader::kEof:
_included.pop_back();
break;
case '#':
processDirective();
break;
case '<':
processHeader();
break;
default:
processOpcode();
break;
}
}
}
void Parser::processDirective()
{
Reader& reader = *_included.back();
SourceLocation start = reader.location();
if (reader.getChar() != '#') {
SourceLocation end = reader.location();
emitError({ start, end }, "Expected `#` at start of directive.");
recover();
return;
}
std::string directive;
reader.extractWhile(&directive, isIdentifierChar);
if (directive == "define") {
reader.skipChars(" \t");
std::string id;
if (!reader.extractExactChar('$') || !reader.extractWhile(&id, isIdentifierChar)) {
SourceLocation end = reader.location();
emitError({ start, end }, "Expected $identifier after #define.");
recover();
return;
}
reader.skipChars(" \t");
std::string value;
extractToEol(reader, &value);
trimRight(value);
addDefinition(id, value);
}
else if (directive == "include") {
reader.skipChars(" \t");
std::string path;
bool valid = false;
if (reader.extractExactChar('"')) {
reader.extractWhile(&path, [](char c) { return c != '"' && c != '\r' && c != '\n'; });
valid = reader.extractExactChar('"');
}
if (!valid) {
SourceLocation end = reader.location();
emitError({ start, end }, "Expected \"file.sfz\" after #include.");
recover();
return;
}
includeNewFile(path);
}
else {
SourceLocation end = reader.location();
emitError({ start, end }, "Unrecognized directive `" + directive + "`");
recover();
}
}
void Parser::processHeader()
{
Reader& reader = *_included.back();
SourceLocation start = reader.location();
if (reader.getChar() != '<') {
SourceLocation end = reader.location();
emitError({ start, end }, "Expected `<` at start of header.");
recover();
return;
}
std::string name;
reader.extractWhile(&name, [](char c) {
return c != '\r' && c != '\n' && c != '>';
});
if (reader.peekChar() != '>') {
SourceLocation end = reader.location();
emitError({ start, end }, "Expected `>` at end of header.");
recover();
return;
}
reader.getChar();
SourceLocation end = reader.location();
if (!isIdentifier(name)) {
emitError({ start, end }, "The header name `" + name + "` is not a valid identifier.");
recover();
return;
}
_lastHeader = name;
if (_listener)
_listener->onParseHeader({ start, end }, name);
}
void Parser::processOpcode()
{
Reader& reader = *_included.back();
SourceLocation opcodeStart = reader.location();
auto isRawOpcodeNameChar = [](char c) {
return isIdentifierChar(c) || c == '$';
};
std::string nameRaw;
reader.extractWhile(&nameRaw, isRawOpcodeNameChar);
SourceLocation opcodeEnd = reader.location();
if (nameRaw.empty()) {
emitError({ opcodeStart, opcodeEnd }, "Expected opcode name.");
recover();
return;
}
if (reader.peekChar() != '=') {
emitError({ opcodeStart, opcodeEnd }, "Expected `=` after opcode name.");
recover();
return;
}
std::string nameExpanded = expandDollarVars({ opcodeStart, opcodeEnd }, nameRaw);
if (!isIdentifier(nameExpanded)) {
emitError({ opcodeStart, opcodeEnd }, "The opcode name `" + nameExpanded + "` is not a valid identifier.");
recover();
return;
}
reader.getChar();
SourceLocation valueStart = reader.location();
std::string valueRaw;
extractToEol(reader, &valueRaw);
// if another "=" character was hit, it means we read too far
size_t position = valueRaw.find('=');
if (position != valueRaw.npos) {
while (position > 0 && isRawOpcodeNameChar(valueRaw[position - 1]))
--position;
while (position > 0 && isSpaceChar(valueRaw[position - 1]))
--position;
absl::string_view excess(&valueRaw[position], valueRaw.size() - position);
reader.putBackChars(excess);
valueRaw.resize(position);
// ensure that we are landing back next to a space char
if (!reader.hasOneOfChars(" \t\r\n")) {
SourceLocation end = reader.location();
emitError({ valueStart, end }, "Unexpected `=` in opcode value.");
recover();
return;
}
}
while (!valueRaw.empty() && isSpaceChar(valueRaw.back())) {
reader.putBackChar(valueRaw.back());
valueRaw.pop_back();
}
SourceLocation valueEnd = reader.location();
if (_lastHeader.empty())
emitWarning({ opcodeStart, valueEnd }, "The opcode is not under any header.");
std::string valueExpanded = expandDollarVars({ valueStart, valueEnd }, valueRaw);
if (_listener)
_listener->onParseOpcode({ opcodeStart, opcodeEnd }, { valueStart, valueEnd }, nameExpanded, valueExpanded);
}
void Parser::emitError(const SourceRange& range, const std::string& message)
{
++_errorCount;
if (_listener)
_listener->onParseError(range, message);
}
void Parser::emitWarning(const SourceRange& range, const std::string& message)
{
++_warningCount;
if (_listener)
_listener->onParseWarning(range, message);
}
void Parser::recover()
{
Reader& reader = *_included.back();
// skip the current line and let the parser proceed at the next
reader.skipWhile([](char c) { return c != '\n'; });
}
bool Parser::hasComment(Reader& reader)
{
if (reader.peekChar() != '/')
return false;
reader.getChar();
if (reader.peekChar() != '/') {
reader.putBackChar('/');
return false;
}
return true;
}
size_t Parser::skipComment(Reader& reader)
{
if (!hasComment(reader))
return 0;
size_t count = 2;
reader.getChar();
reader.getChar();
int c;
while ((c = reader.getChar()) != Reader::kEof && c != '\r' && c != '\n')
++count;
return count;
}
void Parser::trimRight(std::string& text)
{
while (!text.empty() && isSpaceChar(text.back()))
text.pop_back();
}
size_t Parser::extractToEol(Reader& reader, std::string* dst)
{
return reader.extractWhile(dst, [&reader](char c) {
return c != '\r' && c != '\n' && !(c == '/' && reader.peekChar() == '/');
});
}
std::string Parser::expandDollarVars(const SourceRange& range, absl::string_view src)
{
std::string dst;
dst.reserve(2 * src.size());
size_t i = 0;
size_t n = src.size();
while (i < n) {
char c = src[i++];
if (c != '$')
dst.push_back(c);
else {
std::string name;
name.reserve(64);
while (i < n && isIdentifierChar(src[i]))
name.push_back(src[i++]);
if (name.empty()) {
emitWarning(range, "Expected variable name after $.");
continue;
}
auto it = _definitions.find(name);
if (it == _definitions.end()) {
emitWarning(range, "The variable `" + name + "` is not defined.");
continue;
}
dst.append(it->second);
}
}
return dst;
}
bool Parser::isIdentifierChar(char c)
{
return c == '_' ||
(c >= 'a' && c <= 'z') || (c >= 'A' && c <= 'Z') ||
(c >= '0' && c <= '9');
}
bool Parser::isSpaceChar(char c)
{
return c == ' ' || c == '\t';
}
bool Parser::isIdentifier(absl::string_view s)
{
if (s.empty())
return false;
for (char c : s) {
if (!isIdentifierChar(c))
return false;
}
return true;
}
} // namespace sfz

109
src/sfizz/parser/Parser.h Normal file
View file

@ -0,0 +1,109 @@
// SPDX-License-Identifier: BSD-2-Clause
// This code is part of the sfizz library and is licensed under a BSD 2-clause
// license. You should have receive a LICENSE.md file along with the code.
// If not, contact the sfizz maintainers at https://github.com/sfztools/sfizz
#pragma once
#include "ghc/fs_std.hpp"
#include "absl/container/flat_hash_map.h"
#include "absl/container/flat_hash_set.h"
#include <string>
#include <memory>
namespace sfz {
class Reader;
struct SourceLocation;
struct SourceRange;
/**
* @brief Context-dependent parser for SFZ files
*/
class Parser {
public:
Parser();
~Parser();
void addDefinition(absl::string_view id, absl::string_view value);
void parseFile(const fs::path& path);
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;
};
void setListener(Listener* listener) noexcept { _listener = listener; }
private:
void includeNewFile(const fs::path& path);
void processTopLevel();
void processDirective();
void processHeader();
void processOpcode();
// errors and warnings
void emitError(const SourceRange& range, const std::string& message);
void emitWarning(const SourceRange& range, const std::string& message);
// recover after error
void recover();
// helpers
static bool hasComment(Reader& reader);
static size_t skipComment(Reader& reader);
static void trimRight(std::string& text);
static size_t extractToEol(Reader& reader, std::string* dst); // ignores comment
std::string expandDollarVars(const SourceRange& range, absl::string_view src);
// predicates
static bool isIdentifierChar(char c);
static bool isSpaceChar(char c);
static bool isIdentifier(absl::string_view s);
private:
Listener* _listener = nullptr;
fs::path _originalDirectory { fs::current_path() };
absl::flat_hash_map<std::string, std::string> _definitions;
// a current list of files included, last one at the back
std::vector<std::unique_ptr<Reader>> _included;
// recursive include guard
absl::flat_hash_set<std::string> _pathsIncluded;
// parsing state
std::string _lastHeader;
// errors and warnings
size_t _errorCount = 0;
size_t _warningCount = 0;
};
/**
* @brief Source file location for errors and warnings.
*/
struct SourceLocation {
std::shared_ptr<fs::path> filePath;
size_t lineNumber = 0;
size_t columnNumber = 0;
};
/**
* @brief Range of source file.
*/
struct SourceRange {
SourceLocation start;
SourceLocation end;
};
} // namespace sfz

View file

@ -0,0 +1,142 @@
// SPDX-License-Identifier: BSD-2-Clause
// This code is part of the sfizz library and is licensed under a BSD 2-clause
// license. You should have receive a LICENSE.md file along with the code.
// If not, contact the sfizz maintainers at https://github.com/sfztools/sfizz
#include "ParserPrivate.h"
namespace sfz {
Reader::Reader(const fs::path& filePath)
{
_accum.reserve(256);
_loc.filePath = std::make_shared<fs::path>(filePath);
_lineNumColumns.reserve(256);
}
int Reader::getChar()
{
int byte;
if (_accum.empty())
byte = getNextStreamByte();
else {
byte = static_cast<unsigned char>(_accum.back());
_accum.pop_back();
}
if (byte != kEof)
updateSourceLocationAdding(byte);
return byte;
}
int Reader::peekChar()
{
int byte;
if (_accum.empty()) {
byte = getChar();
putBackChar(byte);
}
else
byte = static_cast<unsigned char>(_accum.back());
return byte;
}
bool Reader::extractExactChar(char c)
{
int next = peekChar();
if (next == kEof || next != static_cast<unsigned char>(c))
return false;
getChar();
return true;
}
void Reader::putBackChar(int c)
{
if (c == kEof)
return;
char c8bit = static_cast<unsigned char>(c);
putBackChars(absl::string_view(&c8bit, 1));
}
void Reader::putBackChars(absl::string_view characters)
{
_accum.append(characters.rbegin(), characters.rend());
for (size_t i = characters.size(); i-- > 0;)
updateSourceLocationRemoving(static_cast<unsigned char>(characters[i]));
}
size_t Reader::skipChars(absl::string_view chars)
{
size_t count = 0;
while (chars.find(static_cast<unsigned char>(peekChar())) != chars.npos) {
getChar();
++count;
}
return count;
}
bool Reader::hasEof()
{
return peekChar() == kEof;
}
bool Reader::hasOneOfChars(absl::string_view chars)
{
int c = peekChar();
if (c == kEof)
return false;
return chars.find(static_cast<unsigned char>(c)) != chars.npos;
}
void Reader::updateSourceLocationAdding(int byte)
{
if (byte != '\n')
_loc.columnNumber += 1;
else {
_lineNumColumns.push_back(_loc.columnNumber);
_loc.lineNumber += 1;
_loc.columnNumber = 0;
}
}
void Reader::updateSourceLocationRemoving(int byte)
{
if (byte != '\n')
_loc.columnNumber -= 1;
else {
_loc.lineNumber -= 1;
_loc.columnNumber = _lineNumColumns[_loc.lineNumber];
_lineNumColumns.pop_back();
}
}
//------------------------------------------------------------------------------
FileReader::FileReader(const fs::path& filePath)
: Reader(filePath), _fileStream(filePath)
{
}
bool FileReader::hasError()
{
return !_fileStream.is_open() || _fileStream.bad();
}
int FileReader::getNextStreamByte()
{
return _fileStream.get();
}
} // namespace sfz

View file

@ -0,0 +1,123 @@
// SPDX-License-Identifier: BSD-2-Clause
// This code is part of the sfizz library and is licensed under a BSD 2-clause
// license. You should have receive a LICENSE.md file along with the code.
// If not, contact the sfizz maintainers at https://github.com/sfztools/sfizz
#pragma once
#include "Parser.h"
#include "ghc/fs_std.hpp"
#include "absl/strings/string_view.h"
#include <string>
namespace sfz {
/**
* @brief Utility to extract characters and strings from a source of any kind.
*/
class Reader {
public:
explicit Reader(const fs::path& filePath);
virtual ~Reader() {}
/**
* @brief Get the current source location.
*/
const SourceLocation& location() const { return _loc; }
/**
* @brief Value of the end-of-file marker.
*/
static constexpr int kEof = std::char_traits<char>::eof();
/**
* @brief Extract the next character.
*/
int getChar();
/**
* @brief Get the next character without extracting it.
*/
int peekChar();
/**
* @brief Put a previously extracted character back into the reader.
*/
void putBackChar(int c);
/**
* @brief Put some previously extracted characters back into the reader.
*/
void putBackChars(absl::string_view characters);
/**
* @brief Extract as long as a predicate holds on the next character.
*/
template <class P> size_t extractWhile(std::string* dst, const P& pred);
/**
* @brief Extract until as a predicate does not hold on the next character.
*/
template <class P> size_t extractUntil(std::string* dst, const P& pred);
/**
* @brief Extract a character if it is equal to the expected value.
*/
bool extractExactChar(char c);
/**
* @brief Skip characters which belong to a given set
*/
size_t skipChars(absl::string_view chars);
/**
* @brief Skip as long as a predicate holds on the next character.
*/
template <class P> size_t skipWhile(const P& pred);
/**
* @brief Skip until as a predicate does not hold on the next character.
*/
template <class P> size_t skipUntil(const P& pred);
/**
* @brief Check if the reader has no more characters.
*/
bool hasEof();
/**
* @brief Check if the reader has one of the following characters next.
*/
bool hasOneOfChars(absl::string_view chars);
protected:
virtual int getNextStreamByte() = 0;
private:
void updateSourceLocationAdding(int byte);
void updateSourceLocationRemoving(int byte);
private:
std::string _accum; // new characters at the front, old at the back
SourceLocation _loc;
std::vector<int> _lineNumColumns;
};
/**
* @brief File-based version of Reader.
*/
class FileReader : public Reader {
public:
explicit FileReader(const fs::path& filePath);
bool hasError();
protected:
int getNextStreamByte() override;
private:
fs::ifstream _fileStream;
};
} // namespace sfz
#include "ParserPrivate.hpp"

View file

@ -0,0 +1,50 @@
// SPDX-License-Identifier: BSD-2-Clause
// This code is part of the sfizz library and is licensed under a BSD 2-clause
// license. You should have receive a LICENSE.md file along with the code.
// If not, contact the sfizz maintainers at https://github.com/sfztools/sfizz
#include "ParserPrivate.h"
namespace sfz {
template <class P>
size_t Reader::extractWhile(std::string* dst, const P& pred)
{
int byte;
size_t count = 0;
bool more = true;
while (more && (byte = getChar()) != EOF) {
more = pred(static_cast<unsigned char>(byte));
if (!more)
putBackChar(byte);
else {
if (dst)
dst->push_back(static_cast<unsigned char>(byte));
++count;
}
}
return count;
}
template <class P>
size_t Reader::extractUntil(std::string* dst, const P& pred)
{
return extractWhile(dst, [&pred](char c) -> bool { return !pred(c); });
}
template <class P>
size_t Reader::skipWhile(const P& pred)
{
return extractWhile(nullptr, pred);
}
template <class P>
size_t Reader::skipUntil(const P& pred)
{
return extractUntil(nullptr, pred);
}
} // namespace sfz

View file

@ -55,6 +55,10 @@ if(JACK_FOUND AND TARGET Qt5::Widgets)
target_include_directories(sfizz_demo_wavetables PRIVATE ${JACK_INCLUDE_DIRS})
target_link_libraries(sfizz_demo_wavetables PRIVATE sfizz::sfizz Qt5::Widgets ${JACK_LIBRARIES})
set_target_properties(sfizz_demo_wavetables PROPERTIES AUTOUIC ON)
add_executable(sfizz_demo_parser DemoParser.cpp)
target_link_libraries(sfizz_demo_parser PRIVATE sfizz::sfizz Qt5::Widgets)
set_target_properties(sfizz_demo_parser PROPERTIES AUTOUIC ON)
endif()
add_executable(eq_apply EQ.cpp)

228
tests/DemoParser.cpp Normal file
View file

@ -0,0 +1,228 @@
#include "ui_DemoParser.h"
#include "sfizz/parser/Parser.h"
#include <QApplication>
#include <QTimer>
#include <QTemporaryFile>
#include <QFileInfo>
#include <QDir>
#include <QTextBlock>
#include <QDebug>
class Application : public QApplication, public sfz::Parser::Listener {
public:
Application(int& argc, char *argv[]) : QApplication(argc, argv) {}
void init();
private:
void requestParseCheck();
void runParseCheck();
private:
void onParseBegin() override;
void onParseEnd() override;
void onParseHeader(const sfz::SourceRange& range, const std::string& header) override;
void onParseOpcode(const sfz::SourceRange& rangeOpcode, const sfz::SourceRange& rangeValue, const std::string& name, const std::string& value) override;
void onParseError(const sfz::SourceRange& range, const std::string& message) override;
void onParseWarning(const sfz::SourceRange& range, const std::string& message) override;
static QTextCursor selectSourceRange(QTextDocument* doc, const sfz::SourceRange& range);
private:
sfz::Parser _parser;
Ui::MainWindow _ui;
QTimer* _recheckTimer = nullptr;
bool _blockTextChanged = false;
};
static const char defaultSfzText[] = R"SFZ(
//----------------------------------------------------------------------------//
// This is a SFZ test file with many problems. //
//----------------------------------------------------------------------------//
// opcode without header
not_in_header=on // warning
// invalid headers
<> // empty
<ab@cd> // bad identifier
<region>
sample=*sine key=69
sample=My Directory/My Wave.wav // path with spaces
sample=My Directory/My Wave.wav key=69 // path with spaces, and other opcode following
sample=Foo=Bar.wav // path invalid: it cannot contain the '=' sign
#include "FileWhichDoesNotExist.sfz"
// malformed includes
#include "MyFileWhichDoesNotExist1.sfz
#include MyFileWhichDoesNotExist1.sfz"
// #define with some bad variable names
#define $foo 1234
#define Foo 1234
#define $ 1234
// #define with empty expansion, accepted
#define $foo
// expansion
abc$foo=1
abcdef=$foo
// expansion of undefined variables
abc$toto=1
abcdef=$tata
// opcode name which expands to invalid identifier
$titi=1
)SFZ";
void Application::init()
{
QMainWindow* window = new QMainWindow;
_ui.setupUi(window);
_recheckTimer = new QTimer;
_recheckTimer->setSingleShot(true);
_recheckTimer->setInterval(200);
connect(
_recheckTimer, &QTimer::timeout,
this, [this]() { runParseCheck(); });
_ui.sfzEdit->setFont(QFontDatabase::systemFont(QFontDatabase::FixedFont));
_ui.sfzEdit->setPlainText(QString::fromLatin1(defaultSfzText));
connect(
_ui.sfzEdit, &QTextEdit::textChanged,
this, [this]() { if (!_blockTextChanged) requestParseCheck(); });
_ui.messageTable->setColumnCount(3);
_ui.messageTable->setHorizontalHeaderLabels(QStringList() << tr("Type") << tr("Line") << tr("Message"));
_ui.messageTable->horizontalHeader()->setStretchLastSection(true);
window->show();
_parser.setListener(this);
requestParseCheck();
}
void Application::requestParseCheck()
{
_recheckTimer->start();
}
void Application::runParseCheck()
{
QByteArray code = _ui.sfzEdit->toPlainText().toLatin1();
QTemporaryFile temp(QDir::tempPath() + "/parseXXXXXX.sfz");
temp.open();
temp.write(code);
temp.flush();
_blockTextChanged = true;
_parser.parseFile(temp.fileName().toStdString());
_blockTextChanged = false;
}
void Application::onParseBegin()
{
QTextDocument* doc = _ui.sfzEdit->document();
QTextCursor cur(doc);
cur.movePosition(QTextCursor::Start);
cur.movePosition(QTextCursor::End, QTextCursor::KeepAnchor);
QTextCharFormat cfmt;
cur.setCharFormat(cfmt);
QTableWidget* t = _ui.messageTable;
while (t->rowCount() > 0)
t->removeRow(t->rowCount() - 1);
}
void Application::onParseEnd()
{
}
void Application::onParseHeader(const sfz::SourceRange& range, const std::string& header)
{
(void)header;
QTextDocument* doc = _ui.sfzEdit->document();
QTextCursor cur = selectSourceRange(doc, range);
QTextCharFormat cfmt;
cfmt.setForeground(QColor::fromRgb(0x4E9A06));
cur.mergeCharFormat(cfmt);
}
void Application::onParseOpcode(const sfz::SourceRange& rangeOpcode, const sfz::SourceRange& rangeValue, const std::string& name, const std::string& value)
{
(void)name;
(void)value;
QTextDocument* doc = _ui.sfzEdit->document();
QTextCursor curOpcode = selectSourceRange(doc, rangeOpcode);
QTextCursor curValue = selectSourceRange(doc, rangeValue);
QTextCharFormat cfmtOpcode;
cfmtOpcode.setForeground(QColor::fromRgb(0x75507B));
QTextCharFormat cfmtValue;
cfmtValue.setForeground(QColor::fromRgb(0x3465A4));
curOpcode.mergeCharFormat(cfmtOpcode);
curValue.mergeCharFormat(cfmtValue);
}
void Application::onParseError(const sfz::SourceRange& range, const std::string& message)
{
QTableWidget* t = _ui.messageTable;
int row = t->rowCount();
t->insertRow(row);
// QString path = QString::fromStdString(*range.start.filePath);
// QString file = QFileInfo(path).fileName();
t->setItem(row, 0, new QTableWidgetItem(tr("Error")));
t->setItem(row, 1, new QTableWidgetItem(QString::number(range.start.lineNumber)));
t->setItem(row, 2, new QTableWidgetItem(QString::fromStdString(message)));
QTextDocument* doc = _ui.sfzEdit->document();
QTextCursor cur = selectSourceRange(doc, range);
QTextCharFormat cfmt;
cfmt.setUnderlineStyle(QTextCharFormat::SingleUnderline);
cfmt.setUnderlineColor(Qt::red);
cur.mergeCharFormat(cfmt);
}
void Application::onParseWarning(const sfz::SourceRange& range, const std::string& message)
{
QTableWidget* t = _ui.messageTable;
int row = t->rowCount();
t->insertRow(row);
// QString path = QString::fromStdString(*range.start.filePath);
// QString file = QFileInfo(path).fileName();
t->setItem(row, 0, new QTableWidgetItem(tr("Warning")));
t->setItem(row, 1, new QTableWidgetItem(QString::number(range.start.lineNumber)));
t->setItem(row, 2, new QTableWidgetItem(QString::fromStdString(message)));
QTextDocument* doc = _ui.sfzEdit->document();
QTextCursor cur = selectSourceRange(doc, range);
QTextCharFormat cfmt;
cfmt.setUnderlineStyle(QTextCharFormat::SingleUnderline);
cfmt.setUnderlineColor(Qt::gray);
cur.mergeCharFormat(cfmt);
}
QTextCursor Application::selectSourceRange(QTextDocument* doc, const sfz::SourceRange& range)
{
QTextCursor cur(doc->findBlockByLineNumber(range.start.lineNumber));
cur.movePosition(QTextCursor::NextCharacter, QTextCursor::MoveAnchor, range.start.columnNumber);
cur.movePosition(QTextCursor::Down, QTextCursor::KeepAnchor, range.end.lineNumber - range.start.lineNumber);
cur.movePosition(QTextCursor::StartOfLine, QTextCursor::KeepAnchor);
cur.movePosition(QTextCursor::NextCharacter, QTextCursor::KeepAnchor, range.end.columnNumber);
return cur;
}
int main(int argc, char* argv[])
{
Application app(argc, argv);
app.init();
return app.exec();
}

52
tests/DemoParser.ui Normal file
View file

@ -0,0 +1,52 @@
<?xml version="1.0" encoding="UTF-8"?>
<ui version="4.0">
<class>MainWindow</class>
<widget class="QMainWindow" name="MainWindow">
<property name="geometry">
<rect>
<x>0</x>
<y>0</y>
<width>800</width>
<height>600</height>
</rect>
</property>
<property name="windowTitle">
<string>Sfizz parser demo</string>
</property>
<widget class="QWidget" name="centralwidget">
<layout class="QVBoxLayout" name="verticalLayout">
<item>
<widget class="QSplitter" name="splitter">
<property name="orientation">
<enum>Qt::Vertical</enum>
</property>
<widget class="QTextEdit" name="sfzEdit"/>
<widget class="QGroupBox" name="groupBox">
<property name="title">
<string>Messages</string>
</property>
<layout class="QVBoxLayout" name="verticalLayout_2">
<item>
<widget class="QTableWidget" name="messageTable"/>
</item>
</layout>
</widget>
</widget>
</item>
</layout>
</widget>
<widget class="QMenuBar" name="menubar">
<property name="geometry">
<rect>
<x>0</x>
<y>0</y>
<width>800</width>
<height>20</height>
</rect>
</property>
</widget>
<widget class="QStatusBar" name="statusbar"/>
</widget>
<resources/>
<connections/>
</ui>

View file

@ -36,7 +36,7 @@ static void usage()
/**
* @brief Parser which extracts the configuration of curves
*/
class CurveParser : public sfz::Parser {
class CurveParser : public sfz::OldParser {
public:
explicit CurveParser(sfz::CurveSet& curveSet)
: curveSet(curveSet)