src/parser.cpp

changeset 333
07e65a4c6611
parent 328
3ea38fd469ca
child 335
c5830bce1c23
--- 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 <http://www.gnu.org/licenses/>.
  */
 
-#include <QRegExp>
+#include <QRegularExpression>
 #include <QIODevice>
 #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<Attribute Attrib, typename T, Attribute... Attribs>
+QString attrib(const LineType<T, Attribs...>& parsed)
+{
+	const int index = attribIndex<LineType<T, Attribs...>, 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<Attribute X, Attribute Y, Attribute Z, typename T, Attribute... Attribs>
+glm::vec3 vectorAttrib(const LineType<T, Attribs...>& 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<X>(parsed).toFloat(),
+		attrib<Y>(parsed).toFloat(),
+		attrib<Z>(parsed).toFloat(),
+	};
+}
+
+template<typename T>
+ColorIndex colorAttrib(T* parsed)
+{
+	return ColorIndex{attrib<Attribute::Color>(*parsed).toInt()};
 }
 
-static ldraw::Color colorFromString(const QString& colorString)
+opt<ParsedLine> 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<QRegularExpressionMatch> result = re.match(line);
+		if (not result->hasMatch()) {
+			result.reset();
+		}
+		return result;
+	};
+	opt<ParsedLine> result;
+	//! \brief Put value into result, add matched ranges to it and return a pointer
+	const auto init = [&]<typename T>(T&& value, const QRegularExpressionMatch& match){
+		result = value;
+		T* parsed = &std::get<T>(*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<Attribute::Name>(*parsed),
+				.transformation = glm::mat4{
+					glm::vec4{vectorAttrib<Attribute::X2, Attribute::Y2, Attribute::Z2>(*parsed), 0},
+					glm::vec4{vectorAttrib<Attribute::X3, Attribute::Y3, Attribute::Z3>(*parsed), 0},
+					glm::vec4{vectorAttrib<Attribute::X4, Attribute::Y4, Attribute::Z4>(*parsed), 0},
+					glm::vec4{vectorAttrib<Attribute::X1, Attribute::Y1, Attribute::Z1>(*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<Attribute::X1, Attribute::Y1, Attribute::Z1>(*parsed),
+				.p2 = vectorAttrib<Attribute::X2, Attribute::Y2, Attribute::Z2>(*parsed),
+			},
+			colorAttrib(parsed),
+		};
+	}
+	else if (auto line3Match = tryRe(exprs().triangleRe)) {
+		LineType3* const parsed = init(LineType3{}, *line3Match);
+		parsed->value = {
+			Triangle{
+				.p1 = vectorAttrib<Attribute::X1, Attribute::Y1, Attribute::Z1>(*parsed),
+				.p2 = vectorAttrib<Attribute::X2, Attribute::Y2, Attribute::Z2>(*parsed),
+				.p3 = vectorAttrib<Attribute::X3, Attribute::Y3, Attribute::Z3>(*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<Attribute::X1, Attribute::Y1, Attribute::Z1>(*parsed),
+				.p2 = vectorAttrib<Attribute::X2, Attribute::Y2, Attribute::Z2>(*parsed),
+				.p3 = vectorAttrib<Attribute::X3, Attribute::Y3, Attribute::Z3>(*parsed),
+				.p4 = vectorAttrib<Attribute::X4, Attribute::Y4, Attribute::Z4>(*parsed),
+			},
+			colorAttrib(parsed),
+		};
+	}
+	else if (auto line5Match = tryRe(exprs().cedgeRe)) {
+		LineType5* const parsed = init(LineType5{}, *line5Match);
+		parsed->value = {
+			ConditionalEdge{
+				.p1 = vectorAttrib<Attribute::X1, Attribute::Y1, Attribute::Z1>(*parsed),
+				.p2 = vectorAttrib<Attribute::X2, Attribute::Y2, Attribute::Z2>(*parsed),
+				.c1 = vectorAttrib<Attribute::X3, Attribute::Y3, Attribute::Z3>(*parsed),
+				.c2 = vectorAttrib<Attribute::X4, Attribute::Y4, Attribute::Z4>(*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<CircularPrimitive::Type>(p - std::begin(circularPrimitiveStems));
-			return Colored<CircularPrimitive>{
-				CircularPrimitive{
-					.type = type,
-					.fraction = {segments, divisions},
-					.transformation = transform,
-				},
-				color,
-			};
-		}
-	}
-	return Colored<SubfileReference>{
-		{
-			.name = name,
-			.transformation = transform,
-		},
-		color,
-	};
-}
-
-template<int NumVertices>
-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<glm::vec3, NumVertices> 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<LineSegment>{{pair.first[0], pair.first[1]}, pair.second};
-		}
-		case 3:
-		{
-			const auto pair = parsePolygon<3>(tokens);
-			return Colored<Triangle>{{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<Quadrilateral>{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<ConditionalEdge>{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<CircularPrimitive::Type>(p - std::begin(circularPrimitiveStems));
+		return Colored<CircularPrimitive>{
+			CircularPrimitive{
+				.type = type,
+				.fraction = {segments, divisions},
+				.transformation = transform,
+			},
+			color,
+		};
 	}
 }
+#endif
+

mercurial