src/parser.cpp

changeset 8
44679e468ba9
parent 5
593a658cba8e
child 12
fe67489523b5
equal deleted inserted replaced
7:68443f5be176 8:44679e468ba9
20 #include "parser.h" 20 #include "parser.h"
21 #include "objecttypes/comment.h" 21 #include "objecttypes/comment.h"
22 #include "objecttypes/conditionaledge.h" 22 #include "objecttypes/conditionaledge.h"
23 #include "objecttypes/edge.h" 23 #include "objecttypes/edge.h"
24 #include "objecttypes/errorline.h" 24 #include "objecttypes/errorline.h"
25 #include "objecttypes/metacommand.h"
25 #include "objecttypes/modelobject.h" 26 #include "objecttypes/modelobject.h"
26 #include "objecttypes/polygon.h" 27 #include "objecttypes/polygon.h"
27 #include "objecttypes/subfilereference.h" 28 #include "objecttypes/subfilereference.h"
28 29
30 struct BodyParseError
31 {
32 QString message;
33 };
34
29 /* 35 /*
30 * Constructs an LDraw parser 36 * Constructs an LDraw parser
31 */ 37 */
32 Parser::Parser(QIODevice& device, QObject* parent) : 38 Parser::Parser(QIODevice& device, QObject* parent) :
33 QObject {parent}, 39 QObject {parent},
39 QString Parser::readLine() 45 QString Parser::readLine()
40 { 46 {
41 return QString::fromUtf8(this->device.readLine()).trimmed(); 47 return QString::fromUtf8(this->device.readLine()).trimmed();
42 } 48 }
43 49
44 static const QMap<QString, decltype(LDHeader::type)> typeStrings { 50 static const QMap<QString, LDHeader::FileType> typeStrings {
45 {"Part", LDHeader::Part}, 51 {"Part", LDHeader::Part},
46 {"Subpart", LDHeader::Subpart}, 52 {"Subpart", LDHeader::Subpart},
47 {"Shortcut", LDHeader::Shortcut}, 53 {"Shortcut", LDHeader::Shortcut},
48 {"Primitive", LDHeader::Primitive}, 54 {"Primitive", LDHeader::Primitive},
49 {"8_Primitive", LDHeader::Primitive_8}, 55 {"8_Primitive", LDHeader::Primitive_8},
87 header.type = typeStrings.value(partTypeString, LDHeader::Part); 93 header.type = typeStrings.value(partTypeString, LDHeader::Part);
88 header.qualfiers = 0; 94 header.qualfiers = 0;
89 if (tokens.contains("Alias")) 95 if (tokens.contains("Alias"))
90 header.qualfiers |= LDHeader::Alias; 96 header.qualfiers |= LDHeader::Alias;
91 if (tokens.contains("Physical_Color")) 97 if (tokens.contains("Physical_Color"))
92 header.qualfiers |= LDHeader::Physical_Color; 98 header.qualfiers |= LDHeader::PhysicalColour;
93 if (tokens.contains("Flexible_Section")) 99 if (tokens.contains("Flexible_Section"))
94 header.qualfiers |= LDHeader::Flexible_Section; 100 header.qualfiers |= LDHeader::FlexibleSection;
95 return ParseSuccess; 101 return ParseSuccess;
96 } 102 }
97 else 103 else
98 { 104 {
99 return ParseFailure; 105 return ParseFailure;
100 } 106 }
101 } 107 }
102 else if (line == "0 BFC CERTIFY CCW") 108 else if (line == "0 BFC CERTIFY CCW")
103 { 109 {
104 winding = CounterClockwise; 110 winding = Anticlockwise;
105 return ParseSuccess; 111 return ParseSuccess;
106 } 112 }
107 else if (line == "0 BFC CERTIFY CW") 113 else if (line == "0 BFC CERTIFY CW")
108 { 114 {
109 winding = Clockwise; 115 winding = Clockwise;
246 if (line == "0 BFC INVERTNEXT" or line == "0 BFC CERTIFY INVERTNEXT") 252 if (line == "0 BFC INVERTNEXT" or line == "0 BFC CERTIFY INVERTNEXT")
247 { 253 {
248 invertNext = true; 254 invertNext = true;
249 continue; 255 continue;
250 } 256 }
251 modelobjects::BaseObject* object = parseFromString(editor, line); 257 std::unique_ptr<modelobjects::BaseObject> object = parseFromString(line);
252 if (invertNext) 258 if (invertNext)
253 { 259 {
254 editor.setObjectProperty(object, modelobjects::Property::IsInverted, true); 260 editor.setObjectProperty(object.get(), modelobjects::Property::IsInverted, true);
255 } 261 }
262 editor.append(std::move(object));
256 invertNext = false; 263 invertNext = false;
257 } 264 }
258 } 265 }
259 266
260 namespace 267 static Color colorFromString(const QString& colorString)
261 { 268 {
262 namespace regexes 269 bool colorSucceeded;
263 { 270 const Color color = {colorString.toInt(&colorSucceeded)};
264 static const QRegExp comment {R"(^\s*0\s*\/\/\s*(.+)$)"}; 271 if (colorSucceeded)
265 static const QRegExp metacommand {R"(^\s*0\s*(.+)$)"}; 272 {
266 static const QRegExp edgeline 273 return color;
267 {
268 R"(^\s*2)" // starting 2-token
269 R"(\s+(\d+))" // colour
270 R"(((?:\s+[^\s]+){3}))" // 1st vertex
271 R"(((?:\s+[^\s]+){3}))" // 2nd vertex
272 R"(\s*$)" // end
273 };
274 static const QRegExp triangle
275 {
276 R"(^\s*3)" // starting 3-token
277 R"(\s+(\d+))" // colour
278 R"(((?:\s+[^\s]+){3}))" // 1st vertex
279 R"(((?:\s+[^\s]+){3}))" // 2nd vertex
280 R"(((?:\s+[^\s]+){3}))" // 3rd vertex
281 R"(\s*$)" // end
282 };
283 static const QRegExp quadrilateral
284 {
285 R"(^\s*4)" // starting 4-token
286 R"(\s+(\d+))" // colour
287 R"(((?:\s+[^\s]+){3}))" // 1st vertex
288 R"(((?:\s+[^\s]+){3}))" // 2nd vertex
289 R"(((?:\s+[^\s]+){3}))" // 3rd vertex
290 R"(((?:\s+[^\s]+){3}))" // 4th vertex
291 R"(\s*$)" // end
292 };
293 static const QRegExp conditionaledge
294 {
295 R"(^\s*5)" // starting 5-token
296 R"(\s+(\d+))" // colour
297 R"(((?:\s+[^\s]+){3}))" // 1st vertex
298 R"(((?:\s+[^\s]+){3}))" // 2nd vertex
299 R"(((?:\s+[^\s]+){3}))" // 1st control point
300 R"(((?:\s+[^\s]+){3}))" // 2nd control point
301 R"(\s*$)" // end
302 };
303 }
304 }
305
306 static Vertex vertexFromString(const QString& vertex_string)
307 {
308 static const QRegExp pattern {R"(^\s*([^\s]+)\s+([^\s]+)\s+([^\s]+)\s*$)"};
309 const bool succeeded = pattern.exactMatch(vertex_string);
310 if (succeeded)
311 {
312 const float x = pattern.cap(1).toFloat(nullptr);
313 const float y = pattern.cap(2).toFloat(nullptr);
314 const float z = pattern.cap(3).toFloat(nullptr);
315 return {x, y, z};
316 } 274 }
317 else 275 else
318 { 276 {
319 return {}; 277 throw BodyParseError{"colour was not an integer value"};
320 } 278 }
321 } 279 }
322 280
323 static modelobjects::Edge* parseEdgeline( 281 static Vertex vertexFromStrings(
324 Model::EditContext& editor, 282 const QStringList& tokens,
325 const QString& line) 283 const int startingPosition)
326 { 284 {
327 const bool succeeded = regexes::edgeline.exactMatch(line); 285 bool ok_x;
328 if (succeeded) 286 const float x = tokens[startingPosition].toFloat(&ok_x);
329 { 287 bool ok_y;
330 const Color colour = {regexes::edgeline.cap(1).toInt(nullptr)}; 288 const float y = tokens[startingPosition + 1].toFloat(&ok_y);
331 const Vertex v_1 = vertexFromString(regexes::edgeline.cap(2)); 289 bool ok_z;
332 const Vertex v_2 = vertexFromString(regexes::edgeline.cap(3)); 290 const float z = tokens[startingPosition + 2].toFloat(&ok_z);
333 return editor.append<modelobjects::Edge>(v_1, v_2, colour); 291 if (not ok_x or not ok_y or not ok_z)
292 {
293 throw BodyParseError{"vertex contained illegal co-ordinates"};
294 }
295 return {x, y, z};
296 }
297
298 static Matrix3x3 matrixFromStrings(const QStringList& tokens, const int startingPosition)
299 {
300 Matrix3x3 result;
301 for (int i = 0; i < 9; i += 1)
302 {
303 const int row = i / 3;
304 const int column = i % 3;
305 const int index = i + startingPosition;
306 if (index >= tokens.size())
307 {
308 throw BodyParseError{"too few tokens available"};
309 }
310 bool ok;
311 result(row, column) = tokens[index].toFloat(&ok);
312 if (not ok)
313 {
314 throw BodyParseError{"non-numeric values for matrix"};
315 }
316 }
317 return result;
318 }
319
320 static std::unique_ptr<modelobjects::BaseObject> parseType0Line(
321 const QString& line,
322 const QStringList& tokens)
323 {
324 Q_UNUSED(tokens)
325 if (line.startsWith("0 //"))
326 {
327 return std::make_unique<modelobjects::Comment>(line.mid(std::strlen("0 //")).simplified());
334 } 328 }
335 else 329 else
336 { 330 {
337 return nullptr; 331 return std::make_unique<modelobjects::MetaCommand>(line.mid(1).simplified());
338 } 332 }
339 } 333 }
340 334
341 modelobjects::BaseObject* Parser::parseFromString( 335 static std::unique_ptr<modelobjects::SubfileReference> parseType1Line(
342 Model::EditContext& editor, 336 const QString& line,
343 const QString& line) 337 const QStringList& tokens)
344 { 338 {
345 modelobjects::Edge* edge = parseEdgeline(editor, line); 339 Q_UNUSED(line)
346 if (edge) 340 constexpr int colorPosition = 1;
347 { 341 constexpr int transformPosition = 2; // 2..10
348 return edge; 342 constexpr int positionPosition = 11; // 11..13
349 } 343 constexpr int namePosition = 14;
350 return editor.append<modelobjects::ErrorLine>(line); 344 if (tokens.size() != 15)
351 } 345 {
346 throw BodyParseError{"wrong amount of tokens in a type-1 line"};
347 }
348 const Color color = colorFromString(tokens[colorPosition]);
349 const Vertex position = vertexFromStrings(tokens, positionPosition);
350 const Matrix3x3 transform = matrixFromStrings(tokens, transformPosition);
351 const QString& name = tokens[namePosition];
352 return std::make_unique<modelobjects::SubfileReference>(position, transform, name, color);
353 }
354
355 template<typename T, int NumVertices>
356 static std::unique_ptr<T> parsePolygon(
357 const QString& line,
358 const QStringList& tokens)
359 {
360 Q_UNUSED(line)
361 constexpr int colorPosition = 1;
362 auto vertexPosition = [](int n) { return 2 + 3*n; };
363 if (tokens.size() != 2 + 3 * NumVertices)
364 {
365 throw BodyParseError{"wrong amount of tokens"};
366 }
367 const Color color = colorFromString(tokens[colorPosition]);
368 QVector<Vertex> vertices;
369 vertices.reserve(NumVertices);
370 for (int i = 0; i < NumVertices; i += 1)
371 {
372 vertices.append(vertexFromStrings(tokens, vertexPosition(i)));
373 }
374 return std::make_unique<T>(vertices, color);
375 }
376
377 std::unique_ptr<modelobjects::BaseObject> Parser::parseFromString(QString line)
378 {
379 line = line.simplified();
380 try
381 {
382 const QStringList tokens = line.split(QRegExp{R"(\s+)"});
383 if (tokens.empty())
384 {
385 return std::make_unique<modelobjects::Empty>();
386 }
387 bool ok_code;
388 const int code = tokens[0].toInt(&ok_code);
389 if (not ok_code)
390 {
391 throw BodyParseError{"line type was not an integer"};
392 }
393 switch (code)
394 {
395 case 0:
396 return parseType0Line(line, tokens);
397 case 1:
398 return parseType1Line(line, tokens);
399 case 2:
400 return parsePolygon<modelobjects::Edge, 2>(line, tokens);
401 case 3:
402 return parsePolygon<modelobjects::Triangle, 3>(line, tokens);
403 case 4:
404 return parsePolygon<modelobjects::Quadrilateral, 4>(line, tokens);
405 case 5:
406 return parsePolygon<modelobjects::ConditionalEdge, 4>(line, tokens);
407 default:
408 throw BodyParseError{utility::format("bad line type '%1'", code)};
409 }
410 }
411 catch(const BodyParseError& error)
412 {
413 return std::make_unique<modelobjects::ErrorLine>(line, error.message);
414 }
415 }

mercurial