src/parser.cpp

changeset 333
07e65a4c6611
parent 328
3ea38fd469ca
child 335
c5830bce1c23
equal deleted inserted replaced
332:ae7f7fbb9cda 333:07e65a4c6611
14 * 14 *
15 * You should have received a copy of the GNU General Public License 15 * You should have received a copy of the GNU General Public License
16 * along with this program. If not, see <http://www.gnu.org/licenses/>. 16 * along with this program. If not, see <http://www.gnu.org/licenses/>.
17 */ 17 */
18 18
19 #include <QRegExp> 19 #include <QRegularExpression>
20 #include <QIODevice> 20 #include <QIODevice>
21 #include "src/ldrawalgorithm.h" 21 #include "src/ldrawalgorithm.h"
22 #include "src/model.h" 22 #include "src/model.h"
23 #include "src/parser.h" 23 #include "src/parser.h"
24 24
25 struct BodyParseError 25 #define NUMBER_REGEX R"([+-]?(?:(?:\d+\.?\d*)|(?:\.\d+)))"
26 #define SPACE_REGEX R"(\s+)"
27 #define VEC3_REGEX "(" NUMBER_REGEX SPACE_REGEX NUMBER_REGEX SPACE_REGEX NUMBER_REGEX ")"
28 #define TWO_VECTORS VEC3_REGEX SPACE_REGEX VEC3_REGEX
29 #define THREE_VECTORS TWO_VECTORS SPACE_REGEX VEC3_REGEX
30 #define FOUR_VECTORS THREE_VECTORS SPACE_REGEX VEC3_REGEX
31
32 static const auto& exprs()
26 { 33 {
27 QString message; 34 static const struct
28 }; 35 {
29 36 QRegularExpression subfileRe{QStringLiteral(
30 /* 37 R"(^\s*(1)\s+(\d+)\s+)" FOUR_VECTORS SPACE_REGEX R"(([^ ]+)\s*$)"
31 * Constructs an LDraw parser 38 )};
32 */ 39 QRegularExpression edgeRe{QStringLiteral(
33 Parser::Parser(QTextStream& stream, QObject* parent) : 40 R"(^\s*(2)\s+(\d+)\s+)" TWO_VECTORS R"(\s*$)"
34 QObject {parent}, 41 )};
35 stream {stream} {} 42 QRegularExpression triangleRe{QStringLiteral(
36 43 R"(^\s*(3)\s+(\d+)\s+)" THREE_VECTORS R"(\s*$)"
37 /* 44 )};
38 * Reads a single line from the device. 45 QRegularExpression quadRe{QStringLiteral(
39 */ 46 R"(^\s*(4)\s+(\d+)\s+)" FOUR_VECTORS R"(\s*$)"
40 QString Parser::readLine() 47 )};
48 QRegularExpression cedgeRe{QStringLiteral(
49 R"(^\s*(5)\s+(\d+)\s+)" FOUR_VECTORS R"(\s*$)"
50 )};
51 #if 0
52 QRegularExpression bfcRe{QStringLiteral(
53 R"(^\s*(0) (BFC (?:CERTIFY CCW|CERTIFY CW|NOCERTIFY|INVERTNEXT|CLIP|NOCLIP))\s*$)"
54 )};
55 #endif
56 QRegularExpression commentRe{QStringLiteral(
57 R"(^\s*(0)\s+(.+)$)"
58 )};
59 } result;
60 return result;
61 }
62 template<Attribute Attrib, typename T, Attribute... Attribs>
63 QString attrib(const LineType<T, Attribs...>& parsed)
41 { 64 {
42 return this->stream.readLine().trimmed(); 65 const int index = attribIndex<LineType<T, Attribs...>, Attrib>;
66 const TextRange& range = parsed.positions[index];
67 return parsed.content.mid(range.start, range.length);
43 } 68 }
44 69
45 /** 70 template<Attribute X, Attribute Y, Attribute Z, typename T, Attribute... Attribs>
46 * @brief Parses the model body into the given model. 71 glm::vec3 vectorAttrib(const LineType<T, Attribs...>& parsed)
47 * @param editor Handle to model edit context
48 */
49 void Parser::parseBody(Model& model)
50 { 72 {
51 bool invertNext = false; 73 return glm::vec3{
52 while (not this->stream.atEnd()) 74 attrib<X>(parsed).toFloat(),
53 { 75 attrib<Y>(parsed).toFloat(),
54 // Some LDraw parts such as 53588.dat can contain "BFC INVERTNEXT" with multiple inner whitespaces. 76 attrib<Z>(parsed).toFloat(),
55 // So we need to pass the string through QString::simplified to catch these cases. 77 };
56 const QString line = this->readLine().trimmed();
57 if (line == "0 BFC INVERTNEXT" or line == "0 BFC CERTIFY INVERTNEXT")
58 {
59 invertNext = true;
60 continue;
61 }
62 ModelElement element = parseLDrawLine(line);
63 if (invertNext)
64 {
65 element = inverted(element);
66 }
67 model.append(element);
68 invertNext = false;
69 }
70 } 78 }
71 79
72 static ldraw::Color colorFromString(const QString& colorString) 80 template<typename T>
81 ColorIndex colorAttrib(T* parsed)
73 { 82 {
74 bool colorSucceeded; 83 return ColorIndex{attrib<Attribute::Color>(*parsed).toInt()};
75 const ldraw::Color color = {colorString.toInt(&colorSucceeded)};
76 if (colorSucceeded)
77 {
78 return color;
79 }
80 else
81 {
82 throw BodyParseError{"colour was not an integer value"};
83 }
84 } 84 }
85 85
86 static glm::vec3 vertexFromStrings( 86 opt<ParsedLine> parse(const QString& line)
87 const QStringList& tokens,
88 const int startingPosition)
89 { 87 {
90 bool ok_x; 88 const auto tryRe = [&line](const QRegularExpression& re){
91 const float x = tokens[startingPosition].toFloat(&ok_x); 89 opt<QRegularExpressionMatch> result = re.match(line);
92 bool ok_y; 90 if (not result->hasMatch()) {
93 const float y = tokens[startingPosition + 1].toFloat(&ok_y); 91 result.reset();
94 bool ok_z; 92 }
95 const float z = tokens[startingPosition + 2].toFloat(&ok_z); 93 return result;
96 if (not ok_x or not ok_y or not ok_z) 94 };
97 { 95 opt<ParsedLine> result;
98 throw BodyParseError{"vertex contained illegal co-ordinates"}; 96 //! \brief Put value into result, add matched ranges to it and return a pointer
97 const auto init = [&]<typename T>(T&& value, const QRegularExpressionMatch& match){
98 result = value;
99 T* parsed = &std::get<T>(*result);
100 for (int i = 0; i < countof(T::attributes); ++i) {
101 parsed->positions[i] = {
102 .start = match.capturedStart(i + 1),
103 .length = match.capturedLength(i + 1),
104 };
105 }
106 return parsed;
107 };
108 if (auto line1Match = tryRe(exprs().subfileRe)) {
109 LineType1* const parsed = init(LineType1{}, *line1Match);
110 parsed->value = {
111 SubfileReference{
112 .name = attrib<Attribute::Name>(*parsed),
113 .transformation = glm::mat4{
114 glm::vec4{vectorAttrib<Attribute::X2, Attribute::Y2, Attribute::Z2>(*parsed), 0},
115 glm::vec4{vectorAttrib<Attribute::X3, Attribute::Y3, Attribute::Z3>(*parsed), 0},
116 glm::vec4{vectorAttrib<Attribute::X4, Attribute::Y4, Attribute::Z4>(*parsed), 0},
117 glm::vec4{vectorAttrib<Attribute::X1, Attribute::Y1, Attribute::Z1>(*parsed), 0}
118 },
119 },
120 colorAttrib(parsed),
121 };
99 } 122 }
100 return {x, y, z}; 123 else if (auto line2Match = tryRe(exprs().edgeRe)) {
101 } 124 LineType2* const parsed = init(LineType2{}, *line2Match);
102 125 parsed->value = {
103 static glm::mat4 matrixFromStrings( 126 LineSegment{
104 const QStringList& tokens, 127 .p1 = vectorAttrib<Attribute::X1, Attribute::Y1, Attribute::Z1>(*parsed),
105 const int startingPosition, 128 .p2 = vectorAttrib<Attribute::X2, Attribute::Y2, Attribute::Z2>(*parsed),
106 const int positionStartingIndex) 129 },
107 { 130 colorAttrib(parsed),
108 glm::mat4 result = glm::mat4{1}; 131 };
109 for (int i = 0; i < 9; i += 1)
110 {
111 const int row = i / 3;
112 const int column = i % 3;
113 const int index = i + startingPosition;
114 if (index >= tokens.size())
115 {
116 throw BodyParseError{"too few tokens available"};
117 }
118 bool ok;
119 // note that glm::mat4 is column-major
120 result[column][row] = tokens[index].toFloat(&ok);
121 if (not ok)
122 {
123 throw BodyParseError{"non-numeric values for matrix"};
124 }
125 } 132 }
126 for (int i = 0; i < 3; i += 1) 133 else if (auto line3Match = tryRe(exprs().triangleRe)) {
127 { 134 LineType3* const parsed = init(LineType3{}, *line3Match);
128 bool ok; 135 parsed->value = {
129 const auto value = tokens[i + positionStartingIndex].toFloat(&ok); 136 Triangle{
130 result[3][i] = value; 137 .p1 = vectorAttrib<Attribute::X1, Attribute::Y1, Attribute::Z1>(*parsed),
131 if (not ok) 138 .p2 = vectorAttrib<Attribute::X2, Attribute::Y2, Attribute::Z2>(*parsed),
132 { 139 .p3 = vectorAttrib<Attribute::X3, Attribute::Y3, Attribute::Z3>(*parsed),
133 throw BodyParseError{"non-numeric values for matrix"}; 140 },
134 } 141 colorAttrib(parsed),
142 };
143 }
144 else if (auto line4Match = tryRe(exprs().quadRe)) {
145 LineType4* const parsed = init(LineType4{}, *line4Match);
146 parsed->value = {
147 Quadrilateral{
148 .p1 = vectorAttrib<Attribute::X1, Attribute::Y1, Attribute::Z1>(*parsed),
149 .p2 = vectorAttrib<Attribute::X2, Attribute::Y2, Attribute::Z2>(*parsed),
150 .p3 = vectorAttrib<Attribute::X3, Attribute::Y3, Attribute::Z3>(*parsed),
151 .p4 = vectorAttrib<Attribute::X4, Attribute::Y4, Attribute::Z4>(*parsed),
152 },
153 colorAttrib(parsed),
154 };
155 }
156 else if (auto line5Match = tryRe(exprs().cedgeRe)) {
157 LineType5* const parsed = init(LineType5{}, *line5Match);
158 parsed->value = {
159 ConditionalEdge{
160 .p1 = vectorAttrib<Attribute::X1, Attribute::Y1, Attribute::Z1>(*parsed),
161 .p2 = vectorAttrib<Attribute::X2, Attribute::Y2, Attribute::Z2>(*parsed),
162 .c1 = vectorAttrib<Attribute::X3, Attribute::Y3, Attribute::Z3>(*parsed),
163 .c2 = vectorAttrib<Attribute::X4, Attribute::Y4, Attribute::Z4>(*parsed),
164 },
165 colorAttrib(parsed),
166 };
135 } 167 }
136 return result; 168 return result;
137 } 169 }
138 170
139 static Comment parseType0Line(const QString& line) 171 #if 0
140 { 172 static QRegExp re{R"((?:(\d+)\\)?(\d+)-(\d)+([a-z]+)\.dat)"};
141 return {line.mid(1).trimmed()}; 173 if (re.exactMatch(name)) {
142 } 174 const auto p = std::find(std::begin(circularPrimitiveStems), std::end(circularPrimitiveStems), re.cap(4));
143 175 const unsigned int divisions = (re.cap(1).isEmpty()) ? 16 : re.cap(1).toUInt();
144 static ModelElement parseType1Line(const QStringList& tokens) 176 const unsigned int segments = re.cap(2).toUInt() * divisions / re.cap(3).toUInt();
145 { 177 if (p != std::end(circularPrimitiveStems)) {
146 constexpr int colorPosition = 1; 178 const auto type = static_cast<CircularPrimitive::Type>(p - std::begin(circularPrimitiveStems));
147 constexpr int positionPosition = 2; // 2..4 179 return Colored<CircularPrimitive>{
148 constexpr int transformPosition = 5; // 5..13 180 CircularPrimitive{
149 constexpr int namePosition = 14; 181 .type = type,
150 if (tokens.size() != 15) 182 .fraction = {segments, divisions},
151 { 183 .transformation = transform,
152 throw BodyParseError{"wrong amount of tokens in a type-1 line"}; 184 },
153 } 185 color,
154 const ldraw::Color color = colorFromString(tokens[colorPosition]); 186 };
155 const glm::mat4 transform = matrixFromStrings(tokens, transformPosition, positionPosition);
156 const QString& name = tokens[namePosition];
157 static QRegExp re{R"((?:(\d+)\\)?(\d+)-(\d)+([a-z]+)\.dat)"};
158 if (re.exactMatch(name)) {
159 const auto p = std::find(std::begin(circularPrimitiveStems), std::end(circularPrimitiveStems), re.cap(4));
160 const unsigned int divisions = (re.cap(1).isEmpty()) ? 16 : re.cap(1).toUInt();
161 const unsigned int segments = re.cap(2).toUInt() * divisions / re.cap(3).toUInt();
162 if (p != std::end(circularPrimitiveStems)) {
163 const auto type = static_cast<CircularPrimitive::Type>(p - std::begin(circularPrimitiveStems));
164 return Colored<CircularPrimitive>{
165 CircularPrimitive{
166 .type = type,
167 .fraction = {segments, divisions},
168 .transformation = transform,
169 },
170 color,
171 };
172 }
173 }
174 return Colored<SubfileReference>{
175 {
176 .name = name,
177 .transformation = transform,
178 },
179 color,
180 };
181 }
182
183 template<int NumVertices>
184 static auto parsePolygon(const QStringList& tokens)
185 {
186 constexpr int colorPosition = 1;
187 auto vertexPosition = [](int n) { return 2 + 3*n; };
188 if (tokens.size() != 2 + 3 * NumVertices)
189 {
190 throw BodyParseError{"wrong amount of tokens"};
191 }
192 const ldraw::Color color = colorFromString(tokens[colorPosition]);
193 std::array<glm::vec3, NumVertices> vertices;
194 for (int i = 0; i < NumVertices; i += 1)
195 {
196 vertices[unsigned_cast(i)] = vertexFromStrings(tokens, vertexPosition(i));
197 }
198 return std::make_pair(vertices, color);
199 }
200
201 ModelElement parseLDrawLine(QString line)
202 {
203 try
204 {
205 const QStringList tokens = line.simplified().split(" ");
206 if (tokens.empty() or tokens == QStringList{{""}})
207 {
208 return Empty{};
209 }
210 bool ok_code;
211 const int code = tokens[0].toInt(&ok_code);
212 if (not ok_code)
213 {
214 throw BodyParseError{QObject::tr("line type was not an integer")};
215 }
216 switch (code)
217 {
218 case 0:
219 return parseType0Line(line);
220 case 1:
221 return parseType1Line(tokens);
222 case 2:
223 {
224 const auto pair = parsePolygon<2>(tokens);
225 return Colored<LineSegment>{{pair.first[0], pair.first[1]}, pair.second};
226 }
227 case 3:
228 {
229 const auto pair = parsePolygon<3>(tokens);
230 return Colored<Triangle>{{pair.first[0], pair.first[1], pair.first[2]}, pair.second
231 };
232 }
233 case 4:
234 {
235 const auto pair = parsePolygon<4>(tokens);
236 const Quadrilateral quad{pair.first[0], pair.first[1], pair.first[2], pair.first[3]};
237 return Colored<Quadrilateral>{quad, pair.second};
238 }
239 case 5:
240 {
241 const auto pair = parsePolygon<4>(tokens);
242 const ConditionalEdge cedge{pair.first[0], pair.first[1], pair.first[2], pair.first[3]};
243 return Colored<ConditionalEdge>{cedge, pair.second};
244 }
245 default:
246 throw BodyParseError{QObject::tr("bad line type '%1'").arg(code)};
247 }
248 }
249 catch(const BodyParseError& error)
250 {
251 return ParseError{line};
252 } 187 }
253 } 188 }
189 #endif
190

mercurial