diff -r ae7f7fbb9cda -r 07e65a4c6611 src/parser.cpp
--- a/src/parser.cpp Mon Jul 04 15:37:22 2022 +0300
+++ b/src/parser.cpp Mon Jul 04 19:53:13 2022 +0300
@@ -16,238 +16,175 @@
* along with this program. If not, see .
*/
-#include
+#include
#include
#include "src/ldrawalgorithm.h"
#include "src/model.h"
#include "src/parser.h"
-struct BodyParseError
-{
- QString message;
-};
+#define NUMBER_REGEX R"([+-]?(?:(?:\d+\.?\d*)|(?:\.\d+)))"
+#define SPACE_REGEX R"(\s+)"
+#define VEC3_REGEX "(" NUMBER_REGEX SPACE_REGEX NUMBER_REGEX SPACE_REGEX NUMBER_REGEX ")"
+#define TWO_VECTORS VEC3_REGEX SPACE_REGEX VEC3_REGEX
+#define THREE_VECTORS TWO_VECTORS SPACE_REGEX VEC3_REGEX
+#define FOUR_VECTORS THREE_VECTORS SPACE_REGEX VEC3_REGEX
-/*
- * Constructs an LDraw parser
- */
-Parser::Parser(QTextStream& stream, QObject* parent) :
- QObject {parent},
- stream {stream} {}
-
-/*
- * Reads a single line from the device.
- */
-QString Parser::readLine()
+static const auto& exprs()
{
- return this->stream.readLine().trimmed();
+ static const struct
+ {
+ QRegularExpression subfileRe{QStringLiteral(
+ R"(^\s*(1)\s+(\d+)\s+)" FOUR_VECTORS SPACE_REGEX R"(([^ ]+)\s*$)"
+ )};
+ QRegularExpression edgeRe{QStringLiteral(
+ R"(^\s*(2)\s+(\d+)\s+)" TWO_VECTORS R"(\s*$)"
+ )};
+ QRegularExpression triangleRe{QStringLiteral(
+ R"(^\s*(3)\s+(\d+)\s+)" THREE_VECTORS R"(\s*$)"
+ )};
+ QRegularExpression quadRe{QStringLiteral(
+ R"(^\s*(4)\s+(\d+)\s+)" FOUR_VECTORS R"(\s*$)"
+ )};
+ QRegularExpression cedgeRe{QStringLiteral(
+ R"(^\s*(5)\s+(\d+)\s+)" FOUR_VECTORS R"(\s*$)"
+ )};
+#if 0
+ QRegularExpression bfcRe{QStringLiteral(
+ R"(^\s*(0) (BFC (?:CERTIFY CCW|CERTIFY CW|NOCERTIFY|INVERTNEXT|CLIP|NOCLIP))\s*$)"
+ )};
+#endif
+ QRegularExpression commentRe{QStringLiteral(
+ R"(^\s*(0)\s+(.+)$)"
+ )};
+ } result;
+ return result;
+}
+template
+QString attrib(const LineType& parsed)
+{
+ const int index = attribIndex, Attrib>;
+ const TextRange& range = parsed.positions[index];
+ return parsed.content.mid(range.start, range.length);
}
-/**
- * @brief Parses the model body into the given model.
- * @param editor Handle to model edit context
- */
-void Parser::parseBody(Model& model)
+template
+glm::vec3 vectorAttrib(const LineType& parsed)
{
- bool invertNext = false;
- while (not this->stream.atEnd())
- {
- // Some LDraw parts such as 53588.dat can contain "BFC INVERTNEXT" with multiple inner whitespaces.
- // So we need to pass the string through QString::simplified to catch these cases.
- const QString line = this->readLine().trimmed();
- if (line == "0 BFC INVERTNEXT" or line == "0 BFC CERTIFY INVERTNEXT")
- {
- invertNext = true;
- continue;
- }
- ModelElement element = parseLDrawLine(line);
- if (invertNext)
- {
- element = inverted(element);
- }
- model.append(element);
- invertNext = false;
- }
+ return glm::vec3{
+ attrib(parsed).toFloat(),
+ attrib(parsed).toFloat(),
+ attrib(parsed).toFloat(),
+ };
+}
+
+template
+ColorIndex colorAttrib(T* parsed)
+{
+ return ColorIndex{attrib(*parsed).toInt()};
}
-static ldraw::Color colorFromString(const QString& colorString)
+opt parse(const QString& line)
{
- bool colorSucceeded;
- const ldraw::Color color = {colorString.toInt(&colorSucceeded)};
- if (colorSucceeded)
- {
- return color;
- }
- else
- {
- throw BodyParseError{"colour was not an integer value"};
- }
-}
-
-static glm::vec3 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"};
+ const auto tryRe = [&line](const QRegularExpression& re){
+ opt result = re.match(line);
+ if (not result->hasMatch()) {
+ result.reset();
+ }
+ return result;
+ };
+ opt result;
+ //! \brief Put value into result, add matched ranges to it and return a pointer
+ const auto init = [&](T&& value, const QRegularExpressionMatch& match){
+ result = value;
+ T* parsed = &std::get(*result);
+ for (int i = 0; i < countof(T::attributes); ++i) {
+ parsed->positions[i] = {
+ .start = match.capturedStart(i + 1),
+ .length = match.capturedLength(i + 1),
+ };
+ }
+ return parsed;
+ };
+ if (auto line1Match = tryRe(exprs().subfileRe)) {
+ LineType1* const parsed = init(LineType1{}, *line1Match);
+ parsed->value = {
+ SubfileReference{
+ .name = attrib(*parsed),
+ .transformation = glm::mat4{
+ glm::vec4{vectorAttrib(*parsed), 0},
+ glm::vec4{vectorAttrib(*parsed), 0},
+ glm::vec4{vectorAttrib(*parsed), 0},
+ glm::vec4{vectorAttrib(*parsed), 0}
+ },
+ },
+ colorAttrib(parsed),
+ };
}
- return {x, y, z};
-}
-
-static glm::mat4 matrixFromStrings(
- const QStringList& tokens,
- const int startingPosition,
- const int positionStartingIndex)
-{
- glm::mat4 result = glm::mat4{1};
- 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())
- {
- throw BodyParseError{"too few tokens available"};
- }
- bool ok;
- // note that glm::mat4 is column-major
- result[column][row] = tokens[index].toFloat(&ok);
- if (not ok)
- {
- throw BodyParseError{"non-numeric values for matrix"};
- }
+ else if (auto line2Match = tryRe(exprs().edgeRe)) {
+ LineType2* const parsed = init(LineType2{}, *line2Match);
+ parsed->value = {
+ LineSegment{
+ .p1 = vectorAttrib(*parsed),
+ .p2 = vectorAttrib(*parsed),
+ },
+ colorAttrib(parsed),
+ };
+ }
+ else if (auto line3Match = tryRe(exprs().triangleRe)) {
+ LineType3* const parsed = init(LineType3{}, *line3Match);
+ parsed->value = {
+ Triangle{
+ .p1 = vectorAttrib(*parsed),
+ .p2 = vectorAttrib(*parsed),
+ .p3 = vectorAttrib(*parsed),
+ },
+ colorAttrib(parsed),
+ };
}
- for (int i = 0; i < 3; i += 1)
- {
- bool ok;
- const auto value = tokens[i + positionStartingIndex].toFloat(&ok);
- result[3][i] = value;
- if (not ok)
- {
- throw BodyParseError{"non-numeric values for matrix"};
- }
+ else if (auto line4Match = tryRe(exprs().quadRe)) {
+ LineType4* const parsed = init(LineType4{}, *line4Match);
+ parsed->value = {
+ Quadrilateral{
+ .p1 = vectorAttrib(*parsed),
+ .p2 = vectorAttrib(*parsed),
+ .p3 = vectorAttrib(*parsed),
+ .p4 = vectorAttrib(*parsed),
+ },
+ colorAttrib(parsed),
+ };
+ }
+ else if (auto line5Match = tryRe(exprs().cedgeRe)) {
+ LineType5* const parsed = init(LineType5{}, *line5Match);
+ parsed->value = {
+ ConditionalEdge{
+ .p1 = vectorAttrib(*parsed),
+ .p2 = vectorAttrib(*parsed),
+ .c1 = vectorAttrib(*parsed),
+ .c2 = vectorAttrib(*parsed),
+ },
+ colorAttrib(parsed),
+ };
}
return result;
}
-static Comment parseType0Line(const QString& line)
-{
- return {line.mid(1).trimmed()};
-}
-
-static ModelElement parseType1Line(const QStringList& tokens)
-{
- constexpr int colorPosition = 1;
- constexpr int positionPosition = 2; // 2..4
- constexpr int transformPosition = 5; // 5..13
- constexpr int namePosition = 14;
- if (tokens.size() != 15)
- {
- throw BodyParseError{"wrong amount of tokens in a type-1 line"};
- }
- const ldraw::Color color = colorFromString(tokens[colorPosition]);
- const glm::mat4 transform = matrixFromStrings(tokens, transformPosition, positionPosition);
- const QString& name = tokens[namePosition];
- static QRegExp re{R"((?:(\d+)\\)?(\d+)-(\d)+([a-z]+)\.dat)"};
- if (re.exactMatch(name)) {
- const auto p = std::find(std::begin(circularPrimitiveStems), std::end(circularPrimitiveStems), re.cap(4));
- const unsigned int divisions = (re.cap(1).isEmpty()) ? 16 : re.cap(1).toUInt();
- const unsigned int segments = re.cap(2).toUInt() * divisions / re.cap(3).toUInt();
- if (p != std::end(circularPrimitiveStems)) {
- const auto type = static_cast(p - std::begin(circularPrimitiveStems));
- return Colored{
- CircularPrimitive{
- .type = type,
- .fraction = {segments, divisions},
- .transformation = transform,
- },
- color,
- };
- }
- }
- return Colored{
- {
- .name = name,
- .transformation = transform,
- },
- color,
- };
-}
-
-template
-static auto parsePolygon(const QStringList& tokens)
-{
- 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 ldraw::Color color = colorFromString(tokens[colorPosition]);
- std::array vertices;
- for (int i = 0; i < NumVertices; i += 1)
- {
- vertices[unsigned_cast(i)] = vertexFromStrings(tokens, vertexPosition(i));
- }
- return std::make_pair(vertices, color);
-}
-
-ModelElement parseLDrawLine(QString line)
-{
- try
- {
- const QStringList tokens = line.simplified().split(" ");
- if (tokens.empty() or tokens == QStringList{{""}})
- {
- return Empty{};
- }
- bool ok_code;
- const int code = tokens[0].toInt(&ok_code);
- if (not ok_code)
- {
- throw BodyParseError{QObject::tr("line type was not an integer")};
- }
- switch (code)
- {
- case 0:
- return parseType0Line(line);
- case 1:
- return parseType1Line(tokens);
- case 2:
- {
- const auto pair = parsePolygon<2>(tokens);
- return Colored{{pair.first[0], pair.first[1]}, pair.second};
- }
- case 3:
- {
- const auto pair = parsePolygon<3>(tokens);
- return Colored{{pair.first[0], pair.first[1], pair.first[2]}, pair.second
- };
- }
- case 4:
- {
- const auto pair = parsePolygon<4>(tokens);
- const Quadrilateral quad{pair.first[0], pair.first[1], pair.first[2], pair.first[3]};
- return Colored{quad, pair.second};
- }
- case 5:
- {
- const auto pair = parsePolygon<4>(tokens);
- const ConditionalEdge cedge{pair.first[0], pair.first[1], pair.first[2], pair.first[3]};
- return Colored{cedge, pair.second};
- }
- default:
- throw BodyParseError{QObject::tr("bad line type '%1'").arg(code)};
- }
- }
- catch(const BodyParseError& error)
- {
- return ParseError{line};
+#if 0
+static QRegExp re{R"((?:(\d+)\\)?(\d+)-(\d)+([a-z]+)\.dat)"};
+if (re.exactMatch(name)) {
+ const auto p = std::find(std::begin(circularPrimitiveStems), std::end(circularPrimitiveStems), re.cap(4));
+ const unsigned int divisions = (re.cap(1).isEmpty()) ? 16 : re.cap(1).toUInt();
+ const unsigned int segments = re.cap(2).toUInt() * divisions / re.cap(3).toUInt();
+ if (p != std::end(circularPrimitiveStems)) {
+ const auto type = static_cast(p - std::begin(circularPrimitiveStems));
+ return Colored{
+ CircularPrimitive{
+ .type = type,
+ .fraction = {segments, divisions},
+ .transformation = transform,
+ },
+ color,
+ };
}
}
+#endif
+