src/parser.cpp

changeset 8
44679e468ba9
parent 5
593a658cba8e
child 12
fe67489523b5
--- 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);
-}

mercurial