PLYwoot
Header-only C++17 library for parsing and writing PLY files
Loading...
Searching...
No Matches
header_parser.hpp
Go to the documentation of this file.
1/*
2 This file is part of PLYwoot, a header-only PLY parser.
3
4 Copyright (C) 2023-2025, Ton van den Heuvel
5
6 PLYwoot is free software: you can redistribute it and/or modify
7 it under the terms of the GNU General Public License as published by
8 the Free Software Foundation, either version 3 of the License, or
9 (at your option) any later version.
10
11 This program is distributed in the hope that it will be useful,
12 but WITHOUT ANY WARRANTY; without even the implied warranty of
13 MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
14 GNU General Public License for more details.
15
16 You should have received a copy of the GNU General Public License
17 along with this program. If not, see <http://www.gnu.org/licenses/>.
18*/
19
20#ifndef PLYWOOT_HEADER_PARSER_HPP
21#define PLYWOOT_HEADER_PARSER_HPP
22
24
25#include "exceptions.hpp"
26#include "header_scanner.hpp"
28#include "std.hpp"
29#include "types.hpp"
30
31#include <cassert>
32#include <cstdint>
33#include <istream>
34#include <numeric>
35#include <string>
36#include <utility>
37#include <vector>
38
39namespace plywoot {
40
43{
47 HeaderParserException(const std::string &message) : Exception("parser error: " + message) {}
48};
49
53{
58 InvalidFormat(const std::string &format) : HeaderParserException("invalid format found: " + format) {}
59};
60
63{
70 detail::HeaderScanner::Token expected,
71 detail::HeaderScanner::Token found,
72 const std::string &tokenString)
73 : UnexpectedToken(std::vector{expected}, found, tokenString)
74 {
75 }
76
84 std::vector<detail::HeaderScanner::Token> expected,
85 detail::HeaderScanner::Token found,
86 const std::string &tokenString)
88 "unexpected token '" + detail::to_string(found) + "' found, expected a token in the set {" +
89 std::accumulate(
90 std::next(expected.begin()),
91 expected.end(),
92 expected.empty() ? std::string() : detail::to_string(expected.front()),
93 [](auto &&x, auto &&y) {
94 return x + ", " + detail::to_string(std::forward<decltype(y)>(y));
95 }) +
96 "} instead"),
97 expected_{std::move(expected)},
98 found_{found}
99 {
100 }
101
105 const std::vector<detail::HeaderScanner::Token> &expected() const { return expected_; }
109 detail::HeaderScanner::Token found() const { return found_; }
110
111private:
113 std::vector<detail::HeaderScanner::Token> expected_;
115 detail::HeaderScanner::Token found_;
116};
117
118}
119
120namespace plywoot::detail {
121
124class HeaderParser
125{
126 using Token = detail::HeaderScanner::Token;
127
128public:
133 HeaderParser(std::istream &is) : scanner_{is}
134 {
135 accept(Token::MagicNumber);
136
137 // Parse the format section.
138 accept(Token::Format);
139 switch (scanner_.nextToken())
140 {
141 case Token::Ascii:
142 format_ = PlyFormat::Ascii;
143 break;
144 case Token::BinaryLittleEndian:
145 format_ = PlyFormat::BinaryLittleEndian;
146 break;
147 case Token::BinaryBigEndian:
148 format_ = PlyFormat::BinaryBigEndian;
149 break;
150 default:
151 throw InvalidFormat(scanner_.tokenString());
152 }
153 scanner_.nextToken(); // ignore the format version
154
155 // Ignore comment section for now.
156 while (scanner_.nextToken() == Token::Comment) { comments_.push_back(scanner_.comment()); }
157
158 // Parse elements.
159 do {
160 switch (scanner_.token())
161 {
162 case Token::EndHeader:
163 break;
164 case Token::Element:
165 elements_.push_back(parseElement());
166 break;
167 case Token::Comment:
168 comments_.push_back(scanner_.comment());
169 scanner_.nextToken();
170 break;
171 default:
172 throw UnexpectedToken(
173 {Token::EndHeader, Token::Element, Token::Comment}, scanner_.token(), scanner_.tokenString());
174 break;
175 }
176 } while (scanner_.token() != Token::EndHeader);
177 }
178
183 const std::vector<Comment> &comments() const { return comments_; }
187 const std::vector<PlyElement> &elements() const { return elements_; }
188
192 PlyFormat format() const { return format_; }
193
194private:
197 void accept(Token expected)
198 {
199 if (scanner_.nextToken() != expected)
200 {
201 // In case an identifier token is expected, all reserved keywords are
202 // acceptable as well.
203 if (!(expected == Token::Identifier && scanner_.isKeyword(scanner_.token())))
204 {
205 throw UnexpectedToken(expected, scanner_.token(), scanner_.tokenString());
206 }
207 }
208 }
209
212 PlyDataType tokenToDataType(Token t) const
213 {
214 switch (t)
215 {
216 case Token::Char:
217 return PlyDataType::Char;
218 case Token::UChar:
219 return PlyDataType::UChar;
220 case Token::Short:
221 return PlyDataType::Short;
222 case Token::UShort:
223 return PlyDataType::UShort;
224 case Token::Int:
225 return PlyDataType::Int;
226 case Token::UInt:
227 return PlyDataType::UInt;
228 case Token::Float:
229 return PlyDataType::Float;
230 case Token::Double:
231 return PlyDataType::Double;
232 default:
233 throw UnexpectedToken(
234 {Token::Char, Token::UChar, Token::Short, Token::UShort, Token::Int, Token::UInt, Token::Float,
235 Token::Double},
236 t, scanner_.tokenString());
237 }
238 }
239
241 PlyElement parseElement()
242 {
243 accept(Token::Identifier); // name of the elements
244 std::string name{scanner_.tokenString()};
245
246 accept(Token::Number);
247 std::size_t size{scanner_.tokenNumber()};
248
249 PlyElement result{std::move(name), size};
250
251 // Parse properties.
252 while (scanner_.nextToken() == Token::Property || scanner_.token() == Token::Comment)
253 {
254 if (scanner_.token() == Token::Property)
255 {
256 PlyDataType type;
257 PlyDataType sizeType;
258
259 // TODO(ton): probably reserved keywords may be used as names as well,
260 // just accept every token here, even numbers?
261 switch (scanner_.nextToken())
262 {
263 case Token::List:
264 sizeType = tokenToDataType(scanner_.nextToken());
265 type = tokenToDataType(scanner_.nextToken());
266 accept(Token::Identifier);
267 result.addProperty(scanner_.tokenString(), type, sizeType);
268 break;
269 default:
270 type = tokenToDataType(scanner_.token());
271 accept(Token::Identifier);
272 result.addProperty(scanner_.tokenString(), type);
273 break;
274 }
275 }
276 else
277 {
278 assert(scanner_.token() == Token::Comment);
279 comments_.push_back(scanner_.comment());
280 }
281 }
282
283 return result;
284 }
285
287 std::vector<Comment> comments_;
289 PlyFormat format_;
291 std::vector<PlyElement> elements_;
293 detail::HeaderScanner scanner_;
294};
295
296}
297
298#endif
Base class for all exceptions thrown by PLYwoot.
Base class for all header parser exceptions.
HeaderParserException(const std::string &message)
InvalidFormat(const std::string &format)
Exception thrown in case the input contains an unexpected token.
const std::vector< detail::HeaderScanner::Token > & expected() const
UnexpectedToken(std::vector< detail::HeaderScanner::Token > expected, detail::HeaderScanner::Token found, const std::string &tokenString)
UnexpectedToken(detail::HeaderScanner::Token expected, detail::HeaderScanner::Token found, const std::string &tokenString)
detail::HeaderScanner::Token found() const
PlyFormat
Definition types.hpp:72
PlyDataType
Enumeration of data types supported by the PLY format.
Definition types.hpp:36