--- a/src/parser.cpp Sat Oct 05 23:47:03 2019 +0300 +++ b/src/parser.cpp Sun Nov 03 12:17:41 2019 +0200 @@ -22,10 +22,16 @@ #include "objecttypes/conditionaledge.h" #include "objecttypes/edge.h" #include "objecttypes/errorline.h" +#include "objecttypes/metacommand.h" #include "objecttypes/modelobject.h" #include "objecttypes/polygon.h" #include "objecttypes/subfilereference.h" +struct BodyParseError +{ + QString message; +}; + /* * Constructs an LDraw parser */ @@ -41,7 +47,7 @@ return QString::fromUtf8(this->device.readLine()).trimmed(); } -static const QMap<QString, decltype(LDHeader::type)> typeStrings { +static const QMap<QString, LDHeader::FileType> typeStrings { {"Part", LDHeader::Part}, {"Subpart", LDHeader::Subpart}, {"Shortcut", LDHeader::Shortcut}, @@ -89,9 +95,9 @@ if (tokens.contains("Alias")) header.qualfiers |= LDHeader::Alias; if (tokens.contains("Physical_Color")) - header.qualfiers |= LDHeader::Physical_Color; + header.qualfiers |= LDHeader::PhysicalColour; if (tokens.contains("Flexible_Section")) - header.qualfiers |= LDHeader::Flexible_Section; + header.qualfiers |= LDHeader::FlexibleSection; return ParseSuccess; } else @@ -101,7 +107,7 @@ } else if (line == "0 BFC CERTIFY CCW") { - winding = CounterClockwise; + winding = Anticlockwise; return ParseSuccess; } else if (line == "0 BFC CERTIFY CW") @@ -248,104 +254,162 @@ invertNext = true; continue; } - modelobjects::BaseObject* object = parseFromString(editor, line); + std::unique_ptr<modelobjects::BaseObject> object = parseFromString(line); if (invertNext) { - editor.setObjectProperty(object, modelobjects::Property::IsInverted, true); + editor.setObjectProperty(object.get(), modelobjects::Property::IsInverted, true); } + editor.append(std::move(object)); invertNext = false; } } -namespace +static Color colorFromString(const QString& colorString) { - namespace regexes + bool colorSucceeded; + const Color color = {colorString.toInt(&colorSucceeded)}; + if (colorSucceeded) + { + return color; + } + else { - static const QRegExp comment {R"(^\s*0\s*\/\/\s*(.+)$)"}; - static const QRegExp metacommand {R"(^\s*0\s*(.+)$)"}; - static const QRegExp edgeline - { - R"(^\s*2)" // starting 2-token - R"(\s+(\d+))" // colour - R"(((?:\s+[^\s]+){3}))" // 1st vertex - R"(((?:\s+[^\s]+){3}))" // 2nd vertex - R"(\s*$)" // end - }; - static const QRegExp triangle + throw BodyParseError{"colour was not an integer value"}; + } +} + +static Vertex vertexFromStrings( + const QStringList& tokens, + const int startingPosition) +{ + bool ok_x; + const float x = tokens[startingPosition].toFloat(&ok_x); + bool ok_y; + const float y = tokens[startingPosition + 1].toFloat(&ok_y); + bool ok_z; + const float z = tokens[startingPosition + 2].toFloat(&ok_z); + if (not ok_x or not ok_y or not ok_z) + { + throw BodyParseError{"vertex contained illegal co-ordinates"}; + } + return {x, y, z}; +} + +static Matrix3x3 matrixFromStrings(const QStringList& tokens, const int startingPosition) +{ + Matrix3x3 result; + for (int i = 0; i < 9; i += 1) + { + const int row = i / 3; + const int column = i % 3; + const int index = i + startingPosition; + if (index >= tokens.size()) { - R"(^\s*3)" // starting 3-token - R"(\s+(\d+))" // colour - R"(((?:\s+[^\s]+){3}))" // 1st vertex - R"(((?:\s+[^\s]+){3}))" // 2nd vertex - R"(((?:\s+[^\s]+){3}))" // 3rd vertex - R"(\s*$)" // end - }; - static const QRegExp quadrilateral + throw BodyParseError{"too few tokens available"}; + } + bool ok; + result(row, column) = tokens[index].toFloat(&ok); + if (not ok) { - R"(^\s*4)" // starting 4-token - R"(\s+(\d+))" // colour - R"(((?:\s+[^\s]+){3}))" // 1st vertex - R"(((?:\s+[^\s]+){3}))" // 2nd vertex - R"(((?:\s+[^\s]+){3}))" // 3rd vertex - R"(((?:\s+[^\s]+){3}))" // 4th vertex - R"(\s*$)" // end - }; - static const QRegExp conditionaledge - { - R"(^\s*5)" // starting 5-token - R"(\s+(\d+))" // colour - R"(((?:\s+[^\s]+){3}))" // 1st vertex - R"(((?:\s+[^\s]+){3}))" // 2nd vertex - R"(((?:\s+[^\s]+){3}))" // 1st control point - R"(((?:\s+[^\s]+){3}))" // 2nd control point - R"(\s*$)" // end - }; + throw BodyParseError{"non-numeric values for matrix"}; + } + } + return result; +} + +static std::unique_ptr<modelobjects::BaseObject> parseType0Line( + const QString& line, + const QStringList& tokens) +{ + Q_UNUSED(tokens) + if (line.startsWith("0 //")) + { + return std::make_unique<modelobjects::Comment>(line.mid(std::strlen("0 //")).simplified()); + } + else + { + return std::make_unique<modelobjects::MetaCommand>(line.mid(1).simplified()); } } -static Vertex vertexFromString(const QString& vertex_string) +static std::unique_ptr<modelobjects::SubfileReference> parseType1Line( + const QString& line, + const QStringList& tokens) { - static const QRegExp pattern {R"(^\s*([^\s]+)\s+([^\s]+)\s+([^\s]+)\s*$)"}; - const bool succeeded = pattern.exactMatch(vertex_string); - if (succeeded) + Q_UNUSED(line) + constexpr int colorPosition = 1; + constexpr int transformPosition = 2; // 2..10 + constexpr int positionPosition = 11; // 11..13 + constexpr int namePosition = 14; + if (tokens.size() != 15) { - const float x = pattern.cap(1).toFloat(nullptr); - const float y = pattern.cap(2).toFloat(nullptr); - const float z = pattern.cap(3).toFloat(nullptr); - return {x, y, z}; + throw BodyParseError{"wrong amount of tokens in a type-1 line"}; } - else - { - return {}; - } + const Color color = colorFromString(tokens[colorPosition]); + const Vertex position = vertexFromStrings(tokens, positionPosition); + const Matrix3x3 transform = matrixFromStrings(tokens, transformPosition); + const QString& name = tokens[namePosition]; + return std::make_unique<modelobjects::SubfileReference>(position, transform, name, color); } -static modelobjects::Edge* parseEdgeline( - Model::EditContext& editor, - const QString& line) +template<typename T, int NumVertices> +static std::unique_ptr<T> parsePolygon( + const QString& line, + const QStringList& tokens) { - const bool succeeded = regexes::edgeline.exactMatch(line); - if (succeeded) + Q_UNUSED(line) + constexpr int colorPosition = 1; + auto vertexPosition = [](int n) { return 2 + 3*n; }; + if (tokens.size() != 2 + 3 * NumVertices) + { + throw BodyParseError{"wrong amount of tokens"}; + } + const Color color = colorFromString(tokens[colorPosition]); + QVector<Vertex> vertices; + vertices.reserve(NumVertices); + for (int i = 0; i < NumVertices; i += 1) + { + vertices.append(vertexFromStrings(tokens, vertexPosition(i))); + } + return std::make_unique<T>(vertices, color); +} + +std::unique_ptr<modelobjects::BaseObject> Parser::parseFromString(QString line) +{ + line = line.simplified(); + try { - const Color colour = {regexes::edgeline.cap(1).toInt(nullptr)}; - const Vertex v_1 = vertexFromString(regexes::edgeline.cap(2)); - const Vertex v_2 = vertexFromString(regexes::edgeline.cap(3)); - return editor.append<modelobjects::Edge>(v_1, v_2, colour); + const QStringList tokens = line.split(QRegExp{R"(\s+)"}); + if (tokens.empty()) + { + return std::make_unique<modelobjects::Empty>(); + } + bool ok_code; + const int code = tokens[0].toInt(&ok_code); + if (not ok_code) + { + throw BodyParseError{"line type was not an integer"}; + } + switch (code) + { + case 0: + return parseType0Line(line, tokens); + case 1: + return parseType1Line(line, tokens); + case 2: + return parsePolygon<modelobjects::Edge, 2>(line, tokens); + case 3: + return parsePolygon<modelobjects::Triangle, 3>(line, tokens); + case 4: + return parsePolygon<modelobjects::Quadrilateral, 4>(line, tokens); + case 5: + return parsePolygon<modelobjects::ConditionalEdge, 4>(line, tokens); + default: + throw BodyParseError{utility::format("bad line type '%1'", code)}; + } } - else + catch(const BodyParseError& error) { - return nullptr; + return std::make_unique<modelobjects::ErrorLine>(line, error.message); } } - -modelobjects::BaseObject* Parser::parseFromString( - Model::EditContext& editor, - const QString& line) -{ - modelobjects::Edge* edge = parseEdgeline(editor, line); - if (edge) - { - return edge; - } - return editor.append<modelobjects::ErrorLine>(line); -}