OPAL (Object Oriented Parallel Accelerator Library) 2024.2
OPAL
simple_parser.hpp
Go to the documentation of this file.
1//
2// Class SimpleParser
3// Simple recursive descent parser for SDDS format.
4//
5// Copyright (c) 2026, Paul Scherrer Institute, Villigen PSI, Switzerland
6// All rights reserved
7//
8// This file is part of OPAL.
9//
10// OPAL is free software: you can redistribute it and/or modify
11// it under the terms of the GNU General Public License as published by
12// the Free Software Foundation, either version 3 of the License, or
13// (at your option) any later version.
14//
15// You should have received a copy of the GNU General Public License
16// along with OPAL. If not, see <https://www.gnu.org/licenses/>.
17//
18#ifndef SIMPLE_PARSER_HPP_
19#define SIMPLE_PARSER_HPP_
20
32
33#include <cctype>
34#include <cstddef>
35#include <stdexcept>
36#include <string>
37#include <string_view>
38
39namespace SDDS {
40 namespace parser {
41
43 public:
44 SimpleParser(std::string_view input)
45 : input_(input), pos_(0), dataStartPos_(std::string::npos) {}
46
48 file result;
49 dataStartPos_ = std::string::npos;
50
51 // Parse version
52 result.sddsVersion_m = parseVersion();
53
54 // Parse optional description
55 if (match("&description", true)) {
57 }
58
59 // Parse parameters, columns, associates, arrays, includes
60 while (pos_ < input_.size()) {
61 if (match("&parameter", true)) {
62 result.sddsParameters_m.push_back(parseParameter());
63 } else if (match("&column", true)) {
64 result.sddsColumns_m.push_back(parseColumn());
65 } else if (match("&associate", true)) {
66 result.sddsAssociates_m.push_back(parseAssociate());
67 } else if (match("&array", true)) {
68 result.sddsArrays_m.push_back(parseArray());
69 } else if (match("&include", true)) {
70 result.sddsIncludes_m.push_back(parseInclude());
71 } else if (match("&data", true)) {
72 result.sddsData_m = parseData();
73 break;
74 } else {
75 break;
76 }
77 }
78
79 return result;
80 }
81
82 std::size_t dataStartPos() const {
83 return dataStartPos_;
84 }
85
86 private:
87 std::string_view input_;
88 std::size_t pos_;
89 std::size_t dataStartPos_;
90
94
95 bool match(std::string_view str, bool ignoreCase = false) {
97
98 if (pos_ + str.size() > input_.size())
99 return false;
100
101 const char* data = input_.data() + pos_;
102 if (!ignoreCase) {
103 if (std::char_traits<char>::compare(data, str.data(), str.size()) == 0) {
104 pos_ += str.size();
105 return true;
106 }
107 } else {
108 for (std::size_t i = 0; i < str.size(); ++i) {
109 if (std::tolower(static_cast<unsigned char>(data[i]))
110 != std::tolower(static_cast<unsigned char>(str[i]))) {
111 return false;
112 }
113 }
114 pos_ += str.size();
115 return true;
116 }
117
118 return false;
119 }
120
121 bool match(char c) {
123 if (pos_ < input_.size() && input_[pos_] == c) {
124 pos_++;
125 return true;
126 }
127 return false;
128 }
129
130 void expect(std::string_view str) {
131 if (!match(str, true)) { // case-insensitive expect
132 throw std::runtime_error("Expected: " + std::string(str));
133 }
134 }
135
136 void expect(char c) {
137 if (!match(c)) {
138 throw std::runtime_error(std::string("Expected: ") + c);
139 }
140 }
141
142 std::string parseQuotedString() {
143 if (pos_ >= input_.size() || input_[pos_] != '"') {
144 return "";
145 }
146 pos_++; // skip opening quote
147 std::string result;
148 while (pos_ < input_.size() && input_[pos_] != '"') {
149 if (input_[pos_] == '\\' && pos_ + 1 < input_.size()) {
150 pos_++;
151 result += input_[pos_];
152 } else {
153 result += input_[pos_];
154 }
155 pos_++;
156 }
157 if (pos_ < input_.size() && input_[pos_] == '"') {
158 pos_++; // skip closing quote
159 }
160 return result;
161 }
162
163 std::string parseIdentifier() {
165 std::size_t start = pos_;
166 while (pos_ < input_.size()) {
167 char c = input_[pos_];
168 if (std::isalnum(static_cast<unsigned char>(c)) ||
169 c == '@' || c == '#' || c == ':' || c == '+' ||
170 c == '-' || c == '%' || c == '.' || c == '_' ||
171 c == '$' || c == '&' || c == '/') {
172 pos_++;
173 } else {
174 break;
175 }
176 }
177 return std::string(input_.substr(start, pos_ - start));
178 }
179
180 std::string parseString() {
182 if (pos_ < input_.size() && input_[pos_] == '"') {
183 return parseQuotedString();
184 }
185 return parseIdentifier();
186 }
187
189 version v;
190 expect("SDDS");
192 std::string numStr;
193 while (pos_ < input_.size()
194 && std::isdigit(static_cast<unsigned char>(input_[pos_]))) {
195 numStr += input_[pos_];
196 pos_++;
197 }
198 if (numStr.empty()) {
199 throw std::runtime_error("Expected version number");
200 }
201 v.layoutVersion_m = static_cast<short>(std::stoi(numStr));
202 return v;
203 }
204
206 description desc;
207
208 while (true) {
209 if (match("&end", true)) {
210 break;
211 } else if (match("name", true)) {
212 expect('=');
213 desc.text_m = parseString();
214 } else if (match("text", true)) {
215 expect('=');
216 desc.text_m = parseString();
217 } else if (match("contents", true)) {
218 expect('=');
219 desc.content_m = parseString();
220 } else {
221 while (pos_ < input_.size()
222 && input_[pos_] != ',' && input_[pos_] != '\n') {
223 pos_++;
224 }
225 if (match(',')) continue;
226 }
227 }
228
229 return desc;
230 }
231
233 parameter param;
234
235 while (pos_ < input_.size()) {
236 if (match("&end", true)) {
237 break;
238 } else if (match("name", true)) {
239 expect('=');
240 param.name_m = parseString();
241 } else if (match("type", true)) {
242 expect('=');
243 std::string typeStr = parseString();
244 auto type = ast::parseDataType(typeStr);
245 if (!type) {
246 throw std::runtime_error("Unknown parameter type: " + typeStr);
247 }
248 param.type_m = *type;
249 } else if (match("units", true)) {
250 expect('=');
251 param.units_m = parseString();
252 } else if (match("description", true)) {
253 expect('=');
254 param.description_m = parseString();
255 } else {
256 while (pos_ < input_.size()
257 && input_[pos_] != ',' && input_[pos_] != '\n') {
258 pos_++;
259 }
260 if (match(',')) continue;
261 }
262
263 }
264
265 if (!param.checkMandatories()) {
266 throw std::runtime_error("Parameter declaration missing mandatory attributes");
267 }
268
269 return param;
270 }
271
273 column col;
274
275 while (true) {
276 if (match("&end", true)) {
277 break;
278 } else if (match("name", true)) {
279 expect('=');
280 col.name_m = parseString();
281 } else if (match("type", true)) {
282 expect('=');
283 std::string typeStr = parseString();
284 auto type = ast::parseDataType(typeStr);
285 if (!type) {
286 throw std::runtime_error("Unknown column type: " + typeStr);
287 }
288 col.type_m = *type;
289 } else if (match("units", true)) {
290 expect('=');
291 col.units_m = parseString();
292 } else if (match("description", true)) {
293 expect('=');
295 } else {
296 while (pos_ < input_.size()
297 && input_[pos_] != ',' && input_[pos_] != '\n') {
298 pos_++;
299 }
300 if (match(',')) continue;
301 }
302 }
303
304 if (!col.checkMandatories()) {
305 throw std::runtime_error("Column declaration missing mandatory attributes");
306 }
307
308 return col;
309 }
310
312 associate assoc;
313
314 while (true) {
315 if (match("&end", true)) break;
316 parseString();
317 if (match(',')) continue;
318 }
319
320 return assoc;
321 }
322
324 array arr;
325
326 while (true) {
327 if (match("&end", true)) break;
328 parseString();
329 if (match(',')) continue;
330 }
331
332 return arr;
333 }
334
336 include inc;
337
338 while (true) {
339 if (match("&end", true)) break;
340 parseString();
341 if (match(',')) continue;
342 }
343
344 return inc;
345 }
346
348 data d;
349
350 bool modeSet = false;
351 while (pos_ < input_.size()) {
352 if (match("&end", true)) {
353 break;
354 }
355
356 auto key = parseIdentifier();
357 if (key.empty()) {
358 throw std::runtime_error("Malformed &data entry");
359 }
360 expect('=');
361
362 if (ast::equalsIgnoreCase(key, "mode")) {
363 auto modeStr = parseString();
364 if (ast::equalsIgnoreCase(modeStr, "ascii")) {
366 modeSet = true;
367 } else if (ast::equalsIgnoreCase(modeStr, "binary")) {
369 modeSet = true;
370 } else {
371 throw std::runtime_error("Unknown data mode: " + modeStr);
372 }
373 } else {
374 // Parse and ignore unsupported attributes while preserving cursor position.
375 parseString();
376 }
377
378 if (match(',')) {
379 continue;
380 }
381 }
382
383 if (!modeSet) {
384 throw std::runtime_error("Missing mode attribute in &data");
385 }
387
388 return d;
389 }
390
391 };
392
393 } // namespace parser
394} // namespace SDDS
395
396#endif /* SIMPLE_PARSER_HPP_ */
constexpr std::optional< dataType > parseDataType(std::string_view typeName)
Definition ast.hpp:78
constexpr bool equalsIgnoreCase(std::string_view lhs, std::string_view rhs)
Definition ast.hpp:66
void skipWhitespaceAndComments(std::string_view input, std::size_t &pos)
Definition skipper.hpp:28
std::optional< std::string > name_m
Definition column.hpp:46
std::optional< ast::dataType > type_m
Definition column.hpp:49
bool checkMandatories() const
Definition column.hpp:53
std::optional< std::string > units_m
Definition column.hpp:47
std::optional< std::string > description_m
Definition column.hpp:48
ast::dataMode mode_m
Definition data.hpp:40
std::optional< std::string > content_m
std::optional< std::string > text_m
version sddsVersion_m
Definition file.hpp:34
std::optional< description > sddsDescription_m
Definition file.hpp:35
columnList sddsColumns_m
Definition file.hpp:37
arrayList sddsArrays_m
Definition file.hpp:40
data sddsData_m
Definition file.hpp:38
includeList sddsIncludes_m
Definition file.hpp:41
parameterList sddsParameters_m
Definition file.hpp:36
associateList sddsAssociates_m
Definition file.hpp:39
std::optional< std::string > name_m
Definition parameter.hpp:46
std::optional< std::string > description_m
Definition parameter.hpp:48
std::optional< std::string > units_m
Definition parameter.hpp:47
std::optional< ast::dataType > type_m
Definition parameter.hpp:49
bool checkMandatories() const
Definition parameter.hpp:53
std::size_t dataStartPos() const
SimpleParser(std::string_view input)
void expect(std::string_view str)
bool match(std::string_view str, bool ignoreCase=false)
short layoutVersion_m
Definition version.hpp:24