GCC Code Coverage Report


Directory: ./
File: lib/geogram/mesh/mesh_CSG_compiler.cpp
Date: 2026-09-07 02:28:19
Exec Total Coverage
Lines: 203 259 78.4%
Functions: 20 23 87.0%
Branches: 197 434 45.4%

Line Branch Exec Source
1 /*
2 * Copyright (c) 2000-2025 Inria
3 * All rights reserved.
4 *
5 * Redistribution and use in source and binary forms, with or without
6 * modification, are permitted provided that the following conditions are met:
7 *
8 * * Redistributions of source code must retain the above copyright notice,
9 * this list of conditions and the following disclaimer.
10 * * Redistributions in binary form must reproduce the above copyright notice,
11 * this list of conditions and the following disclaimer in the documentation
12 * and/or other materials provided with the distribution.
13 * * Neither the name of the ALICE Project-Team nor the names of its
14 * contributors may be used to endorse or promote products derived from this
15 * software without specific prior written permission.
16 *
17 * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
18 * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
19 * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
20 * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE
21 * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
22 * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
23 * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
24 * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
25 * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
26 * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
27 * POSSIBILITY OF SUCH DAMAGE.
28 *
29 * Contact: Bruno Levy
30 *
31 * https://www.inria.fr/en/bruno-levy-1
32 *
33 * Inria,
34 * Domaine de Voluceau,
35 * 78150 Le Chesnay - Rocquencourt
36 * FRANCE
37 *
38 */
39
40 #include <geogram/mesh/mesh_CSG_compiler.h>
41 #include <geogram/mesh/mesh_fill_holes.h>
42 #include <geogram/mesh/mesh_topology.h>
43 #include <geogram/basic/progress.h>
44
45 // Silence some warnings in stb_c_lexer.h
46
47 #ifdef GEO_COMPILER_MSVC
48 #pragma warning (push)
49 #pragma warning (disable: 4505) // stb__strchr unreferenced function
50 #endif
51
52 #ifdef GEO_COMPILER_GCC_FAMILY
53 #pragma GCC diagnostic push
54 #pragma GCC diagnostic ignored "-Wconversion"
55 #pragma GCC diagnostic ignored "-Wunused-function"
56 #endif
57
58 #ifdef GEO_COMPILER_CLANG
59 #pragma clang diagnostic ignored "-Wreserved-id-macro"
60 #pragma clang diagnostic ignored "-Wzero-as-null-pointer-constant"
61 #pragma clang diagnostic ignored "-Wself-assign"
62 #pragma clang diagnostic ignored "-Wmissing-noreturn"
63 #pragma clang diagnostic ignored "-Wunused-member-function"
64 #pragma clang diagnostic ignored "-Wcast-qual"
65 #pragma clang diagnostic ignored "-Wunused-macros"
66 #pragma clang diagnostic ignored "-Wimplicit-fallthrough"
67 #endif
68
69 // We need to change stb-c-lexer configuration
70 // because we need to undefined STB_C_LEX_DISCARD_PREPROCESSOR
71 // (we need to parse '#', it is an instruction/object 'modifier')
72 // Begin stb-c-lexer configuration (see geogram/third_party/stb_c_lexer.h)
73 #if defined(Y) || defined(N)
74 #error "'Y' or 'N' already defined, cannot use stb-c-lexer"
75 #endif
76
77 #define STB_C_LEX_C_DECIMAL_INTS Y
78 #define STB_C_LEX_C_HEX_INTS Y
79 #define STB_C_LEX_C_OCTAL_INTS Y
80 #define STB_C_LEX_C_DECIMAL_FLOATS Y
81 #define STB_C_LEX_C99_HEX_FLOATS N
82 #define STB_C_LEX_C_IDENTIFIERS Y
83 #define STB_C_LEX_C_DQ_STRINGS Y
84 #define STB_C_LEX_C_SQ_STRINGS N
85 #define STB_C_LEX_C_CHARS Y
86 #define STB_C_LEX_C_COMMENTS Y
87 #define STB_C_LEX_CPP_COMMENTS Y
88 #define STB_C_LEX_C_COMPARISONS Y
89 #define STB_C_LEX_C_LOGICAL Y
90 #define STB_C_LEX_C_SHIFTS Y
91 #define STB_C_LEX_C_INCREMENTS Y
92 #define STB_C_LEX_C_ARROW Y
93 #define STB_C_LEX_EQUAL_ARROW N
94 #define STB_C_LEX_C_BITWISEEQ Y
95 #define STB_C_LEX_C_ARITHEQ Y
96 #define STB_C_LEX_PARSE_SUFFIXES N
97 #define STB_C_LEX_DECIMAL_SUFFIXES ""
98 #define STB_C_LEX_HEX_SUFFIXES ""
99 #define STB_C_LEX_OCTAL_SUFFIXES ""
100 #define STB_C_LEX_FLOAT_SUFFIXES ""
101 #define STB_C_LEX_0_IS_EOF N
102 #define STB_C_LEX_INTEGERS_AS_DOUBLES N
103 #define STB_C_LEX_MULTILINE_DSTRINGS N
104 #define STB_C_LEX_MULTILINE_SSTRINGS N
105 #define STB_C_LEX_USE_STDLIB Y
106 #define STB_C_LEX_DOLLAR_IDENTIFIER Y
107 #define STB_C_LEX_FLOAT_NO_DECIMAL Y
108 #define STB_C_LEX_DEFINE_ALL_TOKEN_NAMES N
109 #define STB_C_LEX_DISCARD_PREPROCESSOR N // we just changed this one
110 #define STB_C_LEXER_DEFINITIONS
111 // end stb-c-lexer configuration
112
113 #define STB_C_LEXER_IMPLEMENTATION
114 #ifdef GEOGRAM_USE_BUILTIN_DEPS
115 #include <geogram/third_party/stb/stb_c_lexer.h>
116 #else
117 #include <stb/stb_c_lexer.h>
118 #endif
119
120 #ifdef GEO_COMPILER_GCC_FAMILY
121 #pragma GCC diagnostic pop
122 #endif
123
124 #ifdef GEO_COMPILER_MSVC
125 #pragma warning (pop)
126 #endif
127
128
129 namespace {
130 using namespace GEO;
131 static constexpr int CLEX_booleanlit = CLEX_first_unused_token;
132 stb_lexer& getlex(void* lex_) {
133 return *reinterpret_cast<stb_lexer*>(lex_);
134 }
135 }
136
137 /******************************************************************************/
138
139 namespace GEO {
140
1/2
✓ Branch 2 taken 20 times.
✗ Branch 3 not taken.
20 CSGCompiler::CSGCompiler() : lex_(nullptr), lines_(0) {
141
1/2
✓ Branch 1 taken 20 times.
✗ Branch 2 not taken.
20 progress_ = nullptr;
142 builder_ = std::make_shared<CSGBuilder>();
143 20 }
144
145 20 std::shared_ptr<Mesh> CSGCompiler::compile_file(
146 const std::filesystem::path& input_filename
147 ) {
148 // Add the directory that contains the file to the builder's file path,
149 // so that import() instructions are able to find files in the same
150 // directory.
151 40 builder_->push_file_path(input_filename.parent_path());
152 20 std::string source = GEOCSG::load_OpenSCAD(input_filename);
153
1/2
✗ Branch 0 not taken.
✓ Branch 1 taken 20 times.
20 if(source.length() == 0) {
154 throw std::logic_error(
155 filename_.string() + ": could not open file (or file is empty)"
156 );
157 }
158
159
1/2
✓ Branch 1 taken 20 times.
✗ Branch 2 not taken.
20 std::shared_ptr<Mesh> result = compile_string(source);
160
161
3/4
✓ Branch 0 taken 20 times.
✗ Branch 1 not taken.
✓ Branch 2 taken 1 times.
✓ Branch 3 taken 19 times.
40 if(result != nullptr && result->vertices.dimension() == 2) {
162
1/2
✓ Branch 1 taken 1 times.
✗ Branch 2 not taken.
1 result->vertices.set_dimension(3);
163 }
164
165
1/2
✓ Branch 1 taken 20 times.
✗ Branch 2 not taken.
20 builder_->pop_file_path();
166 20 return result;
167 }
168
169 20 std::shared_ptr<Mesh> CSGCompiler::compile_string(
170 const std::string& source
171 ) {
172 20 std::shared_ptr<Mesh> result;
173
174 static constexpr size_t BUFFER_SIZE = 0x10000;
175
1/2
✓ Branch 1 taken 20 times.
✗ Branch 2 not taken.
20 char* buffer = new char[BUFFER_SIZE];
176 stb_lexer lex;
177 20 lex_ = &lex;
178 20 lines_ = NO_INDEX;
179 20 line_ = NO_INDEX;
180
1/2
✗ Branch 0 not taken.
✓ Branch 1 taken 20 times.
20 line_ptr_ = nullptr;
181
182 try {
183
1/2
✓ Branch 1 taken 20 times.
✗ Branch 2 not taken.
20 stb_c_lexer_init(
184 &lex,
185 source.c_str(),
186 source.c_str()+source.length(),
187 buffer, BUFFER_SIZE
188 );
189
3/6
✓ Branch 1 taken 20 times.
✗ Branch 2 not taken.
✓ Branch 4 taken 20 times.
✗ Branch 5 not taken.
✓ Branch 7 taken 20 times.
✗ Branch 8 not taken.
20 ProgressTask progress("CSG", index_t(lines()), builder_->verbose());
190
1/2
✓ Branch 1 taken 20 times.
✗ Branch 2 not taken.
20 progress_ = &progress;
191 CSGScope scope;
192 builder().push_scope();
193
4/6
✓ Branch 1 taken 44 times.
✗ Branch 2 not taken.
✓ Branch 3 taken 44 times.
✗ Branch 4 not taken.
✓ Branch 5 taken 24 times.
✓ Branch 6 taken 20 times.
88 while(lookahead_token().type != CLEX_eof) {
194
1/2
✓ Branch 1 taken 24 times.
✗ Branch 2 not taken.
24 parse_instruction_or_object();
195 }
196
2/4
✗ Branch 0 not taken.
✓ Branch 1 taken 20 times.
✓ Branch 3 taken 20 times.
✗ Branch 4 not taken.
40 result = builder().union_instr(builder().top_scope());
197 builder().pop_scope();
198
0/3
✗ Branch 2 not taken.
✗ Branch 3 not taken.
✗ Branch 4 not taken.
20 } catch(std::shared_ptr<Mesh> reroot) {
199 Logger::out("CSG") << "Re-rooted (!) from line " << line()
200 << std::endl;
201 result = reroot;
202 } catch(const std::logic_error& e) {
203 Logger::err("CSG") << "Error while parsing file:"
204 << e.what()
205 << std::endl;
206 result = std::make_shared<Mesh>();
207 }
208 20 delete[] buffer;
209 20 lex_ = nullptr;
210 20 progress_ = nullptr;
211
1/2
✗ Branch 0 not taken.
✓ Branch 1 taken 20 times.
20 lines_ = 0;
212
213
214
2/2
✓ Branch 0 taken 19 times.
✓ Branch 1 taken 1 times.
20 if(result->vertices.dimension() == 3) {
215
1/2
✓ Branch 1 taken 19 times.
✗ Branch 2 not taken.
19 reorient_connected_components(*result);
216 }
217
218 20 return result;
219 }
220
221 /********* Parser ********************************************************/
222
223 702 void CSGCompiler::parse_instruction_or_object() {
224 702 Token lookahead = lookahead_token();
225
226 // get modifier, one of:
227 // '%', '*': ignore subtree (in OpenSCAD, '%' means transparent display)
228 // '#' : does not change anything (in OpenSCAD, transparent display)
229 // '!' : replace root with subtree
230
231 char modifier = ' ';
232
3/4
✓ Branch 1 taken 702 times.
✗ Branch 2 not taken.
✓ Branch 3 taken 3 times.
✓ Branch 4 taken 699 times.
702 if(is_modifier(lookahead.type)) {
233
2/4
✓ Branch 1 taken 3 times.
✗ Branch 2 not taken.
✓ Branch 3 taken 3 times.
✗ Branch 4 not taken.
3 modifier = char(next_token().type);
234
1/4
✓ Branch 1 taken 3 times.
✗ Branch 2 not taken.
✗ Branch 3 not taken.
✗ Branch 4 not taken.
6 lookahead = lookahead_token();
235 }
236
237
1/2
✗ Branch 0 not taken.
✓ Branch 1 taken 702 times.
702 if(lookahead.type != CLEX_id) {
238 syntax_error("expected id (object or instruction)", lookahead);
239 }
240
241 std::string instr_or_object_name = lookahead.str_val;
242
3/4
✓ Branch 1 taken 702 times.
✗ Branch 2 not taken.
✓ Branch 3 taken 248 times.
✓ Branch 4 taken 454 times.
702 if(is_object(instr_or_object_name)) {
243
1/2
✓ Branch 1 taken 248 times.
✗ Branch 2 not taken.
248 parse_object();
244
2/4
✓ Branch 1 taken 454 times.
✗ Branch 2 not taken.
✓ Branch 3 taken 454 times.
✗ Branch 4 not taken.
454 } else if(is_instruction(instr_or_object_name)) {
245
1/2
✓ Branch 1 taken 454 times.
✗ Branch 2 not taken.
454 parse_instruction();
246 } else {
247 syntax_error("id is no known object or instruction", lookahead);
248 }
249
250 // '%': no effect on CSG tree, transparent rendering
251 // '*': no effect on CSG tree
252
2/2
✓ Branch 0 taken 3 times.
✓ Branch 1 taken 699 times.
702 if(modifier == '%' || modifier == '*') {
253 builder().top_scope().pop_back(); // remove latest generated object
254 }
255
256 // '!': replace root
257
1/2
✗ Branch 0 not taken.
✓ Branch 1 taken 702 times.
702 if(modifier == '!') {
258 // It is caught right after the main parsing loop.
259 std::shared_ptr<Mesh> new_root = *builder().top_scope().rbegin();
260 throw(new_root);
261 }
262
263
1/2
✓ Branch 0 taken 702 times.
✗ Branch 1 not taken.
702 if(progress_ != nullptr) {
264
2/4
✓ Branch 1 taken 702 times.
✗ Branch 2 not taken.
✗ Branch 3 not taken.
✓ Branch 4 taken 702 times.
702 if(progress_->is_canceled()) {
265 throw(std::logic_error("canceled"));
266 }
267
2/4
✓ Branch 1 taken 702 times.
✗ Branch 2 not taken.
✓ Branch 4 taken 702 times.
✗ Branch 5 not taken.
702 progress_->progress(index_t(line()));
268 }
269
270
1/2
✗ Branch 0 not taken.
✓ Branch 1 taken 702 times.
702 if(builder_->verbose()) {
271 index_t cur_line = index_t(line());
272 Logger::out("CSG") << "Executed " << instr_or_object_name
273 << " at line " << cur_line << "/" << lines_
274 << " (" << index_t(cur_line*100) /
275 std::max(lines_,index_t(1)) << "%)"
276 << std::endl;
277 }
278 702 }
279
280 248 void CSGCompiler::parse_object() {
281 248 Token tok = next_token();
282
3/6
✓ Branch 0 taken 248 times.
✗ Branch 1 not taken.
✓ Branch 3 taken 248 times.
✗ Branch 4 not taken.
✗ Branch 5 not taken.
✓ Branch 6 taken 248 times.
248 if(tok.type != CLEX_id || !is_object(tok.str_val)) {
283 syntax_error("expected object");
284 }
285 std::string object_name = tok.str_val;
286
287
1/2
✓ Branch 1 taken 248 times.
✗ Branch 2 not taken.
248 index_t object_line = index_t(line());
288
289
1/2
✓ Branch 1 taken 248 times.
✗ Branch 2 not taken.
248 ArgList args = parse_arg_list();
290
1/2
✓ Branch 1 taken 248 times.
✗ Branch 2 not taken.
248 next_token_check(';');
291
292
3/6
✓ Branch 1 taken 248 times.
✗ Branch 2 not taken.
✓ Branch 4 taken 248 times.
✗ Branch 5 not taken.
✓ Branch 7 taken 248 times.
✗ Branch 8 not taken.
496 builder_->set_fa(args.get_arg("$fa",CSGBuilder::DEFAULT_FA));
293
3/6
✓ Branch 1 taken 248 times.
✗ Branch 2 not taken.
✓ Branch 4 taken 248 times.
✗ Branch 5 not taken.
✓ Branch 7 taken 248 times.
✗ Branch 8 not taken.
496 builder_->set_fs(args.get_arg("$fs",CSGBuilder::DEFAULT_FS));
294
3/6
✓ Branch 1 taken 248 times.
✗ Branch 2 not taken.
✓ Branch 4 taken 248 times.
✗ Branch 5 not taken.
✗ Branch 6 not taken.
✓ Branch 7 taken 248 times.
496 builder_->set_fn(args.get_arg("$fn",CSGBuilder::DEFAULT_FN));
295
296
1/2
✗ Branch 0 not taken.
✓ Branch 1 taken 248 times.
248 if(builder_->verbose()) {
297 Logger::out("CSG") << object_name << " at line: "
298 << object_line << std::endl;
299 }
300
301
1/2
✓ Branch 1 taken 248 times.
✗ Branch 2 not taken.
248 builder_->add_object(object_name, args);
302
1/2
✓ Branch 1 taken 248 times.
✗ Branch 2 not taken.
248 builder_->reset_defaults();
303 248 }
304
305 454 void CSGCompiler::parse_instruction() {
306 454 Token tok = next_token();
307
3/6
✓ Branch 0 taken 454 times.
✗ Branch 1 not taken.
✓ Branch 3 taken 454 times.
✗ Branch 4 not taken.
✗ Branch 5 not taken.
✓ Branch 6 taken 454 times.
454 if(tok.type != CLEX_id || !is_instruction(tok.str_val)) {
308 syntax_error("expected instruction",tok);
309 }
310 std::string instr_name = tok.str_val;
311
312
1/2
✓ Branch 1 taken 454 times.
✗ Branch 2 not taken.
454 index_t instruction_line = index_t(line());
313
314
1/2
✓ Branch 1 taken 454 times.
✗ Branch 2 not taken.
454 ArgList args = parse_arg_list();
315
316
317 // In .csg files produced by OpenSCAD it often happens that
318 // there are empty instructions without any scope.
319
3/6
✓ Branch 1 taken 454 times.
✗ Branch 2 not taken.
✓ Branch 3 taken 454 times.
✗ Branch 4 not taken.
✗ Branch 5 not taken.
✓ Branch 6 taken 454 times.
908 if(lookahead_token().type == ';') {
320 next_token_check(';');
321 builder_->begin_instruction();
322 builder_->end_instruction(instr_name, args);
323 return;
324 }
325
326
1/2
✓ Branch 1 taken 454 times.
✗ Branch 2 not taken.
454 builder_->begin_instruction();
327
3/6
✓ Branch 1 taken 454 times.
✗ Branch 2 not taken.
✓ Branch 3 taken 454 times.
✗ Branch 4 not taken.
✓ Branch 5 taken 454 times.
✗ Branch 6 not taken.
908 if(lookahead_token().type == '{') {
328
1/2
✓ Branch 1 taken 454 times.
✗ Branch 2 not taken.
454 next_token_check('{');
329 for(;;) {
330
4/6
✓ Branch 1 taken 1132 times.
✗ Branch 2 not taken.
✓ Branch 3 taken 1132 times.
✗ Branch 4 not taken.
✓ Branch 5 taken 678 times.
✓ Branch 6 taken 454 times.
2264 if(lookahead_token().type == '}') {
331 break;
332 }
333
1/2
✓ Branch 1 taken 678 times.
✗ Branch 2 not taken.
678 parse_instruction_or_object();
334 }
335
1/2
✓ Branch 1 taken 454 times.
✗ Branch 2 not taken.
454 next_token_check('}');
336 } else {
337 parse_instruction_or_object();
338 }
339
340
1/2
✗ Branch 0 not taken.
✓ Branch 1 taken 454 times.
454 if(builder_->verbose()) {
341 Logger::out("CSG") << instr_name << " at line: "
342 << instruction_line << std::endl;
343 }
344
345
3/6
✓ Branch 1 taken 454 times.
✗ Branch 2 not taken.
✓ Branch 4 taken 454 times.
✗ Branch 5 not taken.
✓ Branch 7 taken 454 times.
✗ Branch 8 not taken.
908 builder_->set_fa(args.get_arg("$fa",CSGBuilder::DEFAULT_FA));
346
3/6
✓ Branch 1 taken 454 times.
✗ Branch 2 not taken.
✓ Branch 4 taken 454 times.
✗ Branch 5 not taken.
✓ Branch 7 taken 454 times.
✗ Branch 8 not taken.
908 builder_->set_fs(args.get_arg("$fs",CSGBuilder::DEFAULT_FS));
347
3/6
✓ Branch 1 taken 454 times.
✗ Branch 2 not taken.
✓ Branch 4 taken 454 times.
✗ Branch 5 not taken.
✓ Branch 7 taken 454 times.
✗ Branch 8 not taken.
908 builder_->set_fn(args.get_arg("$fn",CSGBuilder::DEFAULT_FN));
348
349
1/2
✓ Branch 1 taken 454 times.
✗ Branch 2 not taken.
454 builder_->end_instruction(instr_name, args);
350
1/2
✓ Branch 1 taken 454 times.
✗ Branch 2 not taken.
454 builder_->reset_defaults();
351 }
352
353 702 CSGCompiler::ArgList CSGCompiler::parse_arg_list() {
354 702 ArgList result;
355
1/2
✓ Branch 1 taken 702 times.
✗ Branch 2 not taken.
702 next_token_check('(');
356 for(;;) {
357
4/6
✓ Branch 1 taken 1731 times.
✗ Branch 2 not taken.
✓ Branch 3 taken 1731 times.
✗ Branch 4 not taken.
✓ Branch 5 taken 1563 times.
✓ Branch 6 taken 168 times.
3462 if(lookahead_token().type == ')') {
358 break;
359 }
360
1/2
✓ Branch 1 taken 1563 times.
✗ Branch 2 not taken.
1563 std::string arg_name = "";
361
4/6
✓ Branch 1 taken 1563 times.
✗ Branch 2 not taken.
✓ Branch 3 taken 1563 times.
✗ Branch 4 not taken.
✓ Branch 5 taken 1308 times.
✓ Branch 6 taken 255 times.
3126 if(lookahead_token().type == CLEX_id) {
362
1/2
✓ Branch 1 taken 1308 times.
✗ Branch 2 not taken.
1308 arg_name = next_token().str_val;
363
1/2
✓ Branch 1 taken 1308 times.
✗ Branch 2 not taken.
1308 next_token_check('=');
364 }
365
2/4
✓ Branch 1 taken 1563 times.
✗ Branch 2 not taken.
✓ Branch 4 taken 1563 times.
✗ Branch 5 not taken.
1563 result.add_arg(arg_name, parse_value());
366
4/8
✓ Branch 1 taken 1563 times.
✗ Branch 2 not taken.
✓ Branch 3 taken 1563 times.
✗ Branch 4 not taken.
✓ Branch 5 taken 1029 times.
✓ Branch 6 taken 534 times.
✗ Branch 7 not taken.
✗ Branch 8 not taken.
3126 if(lookahead_token().type == ')') {
367 break;
368 }
369
1/2
✓ Branch 1 taken 1029 times.
✗ Branch 2 not taken.
1029 next_token_check(',');
370 }
371
1/2
✓ Branch 1 taken 702 times.
✗ Branch 2 not taken.
702 next_token_check(')');
372 702 return result;
373 }
374
375 7596 CSGCompiler::Value CSGCompiler::parse_value() {
376
3/4
✓ Branch 1 taken 7596 times.
✗ Branch 2 not taken.
✓ Branch 3 taken 1642 times.
✓ Branch 4 taken 5954 times.
15192 if(lookahead_token().type == '[') {
377 1642 return parse_array();
378 }
379 5954 Token tok = next_token();
380
6/7
✓ Branch 0 taken 315 times.
✓ Branch 1 taken 5092 times.
✓ Branch 2 taken 308 times.
✓ Branch 3 taken 156 times.
✓ Branch 4 taken 50 times.
✓ Branch 5 taken 33 times.
✗ Branch 6 not taken.
5954 if(tok.type == '-') {
381
1/4
✓ Branch 1 taken 315 times.
✗ Branch 2 not taken.
✗ Branch 3 not taken.
✗ Branch 4 not taken.
315 tok = next_token();
382
2/2
✓ Branch 0 taken 173 times.
✓ Branch 1 taken 142 times.
315 if(tok.type == CLEX_intlit) {
383
1/2
✓ Branch 1 taken 173 times.
✗ Branch 2 not taken.
173 return Value(-tok.int_val);
384
1/2
✓ Branch 0 taken 142 times.
✗ Branch 1 not taken.
142 } else if(tok.type == CLEX_floatlit) {
385
1/2
✓ Branch 1 taken 142 times.
✗ Branch 2 not taken.
142 return Value(-tok.double_val);
386 } else {
387 syntax_error("Expected number", tok);
388 }
389 }
390
391 if(tok.type == CLEX_intlit) {
392
1/2
✓ Branch 1 taken 5092 times.
✗ Branch 2 not taken.
5092 return Value(tok.int_val);
393 }
394
395 if(tok.type == CLEX_floatlit) {
396
1/2
✓ Branch 1 taken 308 times.
✗ Branch 2 not taken.
308 return Value(tok.double_val);
397 }
398
399 if(tok.type == CLEX_booleanlit) {
400
1/2
✓ Branch 1 taken 156 times.
✗ Branch 2 not taken.
156 return Value(tok.boolean_val);
401 }
402
403 if(tok.type == CLEX_dqstring) {
404
1/2
✓ Branch 1 taken 50 times.
✗ Branch 2 not taken.
50 return Value(tok.str_val);
405 }
406
407
1/2
✓ Branch 0 taken 33 times.
✗ Branch 1 not taken.
33 if(tok.type == CLEX_id && tok.str_val == "undef") {
408
1/2
✓ Branch 1 taken 33 times.
✗ Branch 2 not taken.
33 return Value();
409 }
410
411 syntax_error("Expected value", tok);
412 }
413
414 1642 CSGCompiler::Value CSGCompiler::parse_array() {
415 1642 Value result;
416 1642 result.type = Value::ARRAY1D;
417
418
1/2
✓ Branch 1 taken 1642 times.
✗ Branch 2 not taken.
1642 next_token_check('[');
419 for(;;) {
420
3/6
✓ Branch 1 taken 6033 times.
✗ Branch 2 not taken.
✓ Branch 3 taken 6033 times.
✗ Branch 4 not taken.
✓ Branch 5 taken 6033 times.
✗ Branch 6 not taken.
12066 if(lookahead_token().type == ']') {
421 break;
422 }
423
1/2
✓ Branch 1 taken 6033 times.
✗ Branch 2 not taken.
6033 Value item = parse_value();
424
425
2/2
✓ Branch 0 taken 4798 times.
✓ Branch 1 taken 1235 times.
6033 if(item.type == Value::NUMBER) {
426
1/2
✓ Branch 1 taken 4798 times.
✗ Branch 2 not taken.
4798 result.array_val.resize(1);
427
2/2
✓ Branch 0 taken 1018 times.
✓ Branch 1 taken 3780 times.
4798 result.array_val[0].push_back(item.number_val);
428
1/2
✗ Branch 0 not taken.
✓ Branch 1 taken 1235 times.
1235 } else if(item.type == Value::BOOLEAN) {
429 result.array_val.resize(1);
430 result.array_val[0].push_back(double(item.boolean_val));
431
1/2
✓ Branch 0 taken 1235 times.
✗ Branch 1 not taken.
1235 } else if(item.type == Value::ARRAY1D) {
432
1/2
✗ Branch 0 not taken.
✓ Branch 1 taken 1235 times.
1235 result.type = Value::ARRAY2D;
433
1/2
✗ Branch 0 not taken.
✓ Branch 1 taken 1235 times.
1235 if(item.array_val.size() == 0) {
434 result.array_val.push_back(vector<double>());
435 } else {
436
1/2
✓ Branch 1 taken 1235 times.
✗ Branch 2 not taken.
1235 result.array_val.push_back(item.array_val[0]);
437 }
438 }
439
440
4/6
✓ Branch 1 taken 6033 times.
✗ Branch 2 not taken.
✓ Branch 3 taken 6033 times.
✗ Branch 4 not taken.
✓ Branch 5 taken 4391 times.
✓ Branch 6 taken 1642 times.
12066 if(lookahead_token().type == ']') {
441 break;
442 }
443
1/2
✓ Branch 1 taken 4391 times.
✗ Branch 2 not taken.
4391 next_token_check(',');
444 6033 }
445
1/2
✓ Branch 1 taken 1642 times.
✗ Branch 2 not taken.
1642 next_token_check(']');
446
447 1642 return result;
448 }
449
450 950 bool CSGCompiler::is_object(const std::string& id) const {
451 950 return builder().is_object(id);
452 }
453
454 908 bool CSGCompiler::is_instruction(const std::string& id) const {
455 908 return builder().is_instruction(id);
456 }
457
458 702 bool CSGCompiler::is_modifier(int toktype) const {
459 return
460 702 (toktype == int('%')) ||
461 702 (toktype == int('#')) ||
462
2/2
✓ Branch 0 taken 699 times.
✓ Branch 1 taken 3 times.
702 (toktype == int('!')) ||
463 699 (toktype == int('*')) ;
464 }
465
466 /********* Parser utilities **********************************************/
467
468 12572 void CSGCompiler::next_token_check(char c) {
469 12572 Token tok = next_token();
470
1/2
✗ Branch 0 not taken.
✓ Branch 1 taken 12572 times.
12572 if(tok.type != int(c)) {
471 syntax_error(
472 String::format(
473 "Expected %c, got \"%s\"", c, tok.to_string().c_str()
474 ).c_str()
475 );
476 }
477 12572 }
478
479 20854 CSGCompiler::Token CSGCompiler::next_token() {
480
2/2
✓ Branch 0 taken 18281 times.
✓ Branch 1 taken 2573 times.
20854 if(lookahead_token_.type != -1) {
481 18281 Token result = lookahead_token_;
482 18281 lookahead_token_.type = -1;
483 return result;
484 }
485 2573 return next_token_internal();
486 }
487
488 27308 CSGCompiler::Token CSGCompiler::lookahead_token() {
489
2/2
✓ Branch 0 taken 18301 times.
✓ Branch 1 taken 9007 times.
27308 if(lookahead_token_.type == -1) {
490 36602 lookahead_token_ = next_token_internal();
491 }
492 27308 return lookahead_token_;
493 }
494
495 20874 CSGCompiler::Token CSGCompiler::next_token_internal() {
496 20874 Token result;
497
2/2
✓ Branch 1 taken 20854 times.
✓ Branch 2 taken 20 times.
20874 if(stb_c_lexer_get_token(&getlex(lex_))) {
498 20854 result.type = int(getlex(lex_).token);
499
2/2
✓ Branch 0 taken 2199 times.
✓ Branch 1 taken 18655 times.
20854 if(getlex(lex_).token == CLEX_id) {
500
1/2
✓ Branch 1 taken 2199 times.
✗ Branch 2 not taken.
2199 result.str_val = getlex(lex_).string;
501
2/2
✓ Branch 0 taken 76 times.
✓ Branch 1 taken 2123 times.
2199 if(result.str_val == "true") {
502 76 result.type = CLEX_booleanlit;
503 76 result.boolean_val = true;
504
2/2
✓ Branch 0 taken 80 times.
✓ Branch 1 taken 2043 times.
2123 } else if(result.str_val == "false") {
505 80 result.type = CLEX_booleanlit;
506 80 result.boolean_val = false;
507 }
508 }
509
2/2
✓ Branch 0 taken 50 times.
✓ Branch 1 taken 20804 times.
20854 if(getlex(lex_).token == CLEX_dqstring) {
510
1/2
✓ Branch 1 taken 50 times.
✗ Branch 2 not taken.
50 result.str_val = getlex(lex_).string;
511 }
512 20854 result.int_val = int(getlex(lex_).int_number);
513 20854 result.double_val = getlex(lex_).real_number;
514 } else {
515 20 result.type = CLEX_eof;
516 }
517
1/2
✗ Branch 0 not taken.
✓ Branch 1 taken 20874 times.
20874 if(getlex(lex_).token == CLEX_parse_error) {
518 syntax_error("lexical error");
519 }
520 20874 return result;
521 }
522
523 20 int CSGCompiler::lines() const {
524
1/2
✓ Branch 0 taken 20 times.
✗ Branch 1 not taken.
20 if(lines_ == NO_INDEX) {
525 20 compute_lines();
526 }
527 20 return int(lines_);
528 }
529
530 20 void CSGCompiler::compute_lines() const {
531 index_t result=0;
532 49872 for(
533 20 const char* p = getlex(lex_).input_stream;
534
2/2
✓ Branch 0 taken 49872 times.
✓ Branch 1 taken 20 times.
49892 p != getlex(lex_).eof; ++p
535 ) {
536
2/2
✓ Branch 0 taken 1176 times.
✓ Branch 1 taken 48696 times.
49872 if(*p == '\n') {
537 1176 ++result;
538 }
539 }
540 20 lines_ = result;
541 20 }
542
543 1404 int CSGCompiler::line() const {
544
2/2
✓ Branch 0 taken 20 times.
✓ Branch 1 taken 1384 times.
1404 if(line_ == NO_INDEX) {
545 20 line_=1;
546 161 for(
547 20 line_ptr_ = getlex(lex_).input_stream;
548
2/2
✓ Branch 0 taken 161 times.
✓ Branch 1 taken 20 times.
181 ( line_ptr_ != getlex(lex_).parse_point &&
549
1/2
✓ Branch 0 taken 161 times.
✗ Branch 1 not taken.
161 line_ptr_ != getlex(lex_).eof );
550 ++line_ptr_
551 ) {
552
1/2
✗ Branch 0 not taken.
✓ Branch 1 taken 161 times.
161 if(*line_ptr_ == '\n') {
553 ++line_;
554 }
555 }
556 20 return int(line_);
557 }
558
2/2
✓ Branch 0 taken 49671 times.
✓ Branch 1 taken 1384 times.
51055 while(line_ptr_ != getlex(lex_).parse_point &&
559
1/2
✗ Branch 0 not taken.
✓ Branch 1 taken 49671 times.
49671 line_ptr_ != getlex(lex_).eof ) {
560
2/2
✓ Branch 0 taken 1136 times.
✓ Branch 1 taken 48535 times.
49671 if(*line_ptr_ == '\n') {
561 1136 ++line_;
562 }
563 49671 ++line_ptr_;
564 }
565 1384 return int(line_);
566 }
567
568 [[noreturn]] void CSGCompiler::syntax_error(const char* msg) {
569 throw(
570 std::logic_error(
571 String::format(
572 "%s:%d %s",
573 filename_.string().c_str(), line(), msg
574 )
575 )
576 );
577 }
578
579 [[noreturn]] void CSGCompiler::syntax_error(
580 const char* msg, const Token& tok
581 ) {
582 throw(
583 std::logic_error(
584 String::format(
585 "%s:%d %s (got \"%s\")",
586 filename_.string().c_str(), line(), msg,
587 tok.to_string().c_str()
588 )
589 )
590 );
591 }
592
593 /***** Token **********************************************************/
594
595 20894 CSGCompiler::Token::Token() :
596 20894 type(-1),
597 20894 int_val(0),
598 20894 double_val(0.0),
599 20894 boolean_val(false) {
600 20894 }
601
602 std::string CSGCompiler::Token::to_string() const {
603 if(type < 256) {
604 return String::format("\'%c\'",type);
605 }
606 if(type == CLEX_intlit) {
607 return String::to_string(int_val);
608 }
609 if(type == CLEX_floatlit) {
610 return String::to_string(double_val);
611 }
612 if(type == CLEX_booleanlit) {
613 return String::to_string(boolean_val);
614 }
615 if(type == CLEX_id) {
616 return str_val;
617 }
618 return "<unknown token>";
619 }
620
621 }
622