GCC Code Coverage Report


Directory: ./
File: mesh/mesh_CSG_compiler.cpp
Date: 2026-10-17 02:51:58
Exec Total Coverage
Lines: 230 301 76.4%
Functions: 21 24 87.5%
Branches: 231 523 44.2%

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 #pragma clang diagnostic ignored "-Wreserved-identifier"
68 #endif
69
70 // We need to change stb-c-lexer configuration
71 // because we need to undefined STB_C_LEX_DISCARD_PREPROCESSOR
72 // (we need to parse '#', it is an instruction/object 'modifier')
73 // Begin stb-c-lexer configuration (see geogram/third_party/stb_c_lexer.h)
74 #if defined(Y) || defined(N)
75 #error "'Y' or 'N' already defined, cannot use stb-c-lexer"
76 #endif
77
78 #define STB_C_LEX_C_DECIMAL_INTS Y
79 #define STB_C_LEX_C_HEX_INTS Y
80 #define STB_C_LEX_C_OCTAL_INTS Y
81 #define STB_C_LEX_C_DECIMAL_FLOATS Y
82 #define STB_C_LEX_C99_HEX_FLOATS N
83 #define STB_C_LEX_C_IDENTIFIERS Y
84 #define STB_C_LEX_C_DQ_STRINGS Y
85 #define STB_C_LEX_C_SQ_STRINGS N
86 #define STB_C_LEX_C_CHARS Y
87 #define STB_C_LEX_C_COMMENTS Y
88 #define STB_C_LEX_CPP_COMMENTS Y
89 #define STB_C_LEX_C_COMPARISONS Y
90 #define STB_C_LEX_C_LOGICAL Y
91 #define STB_C_LEX_C_SHIFTS Y
92 #define STB_C_LEX_C_INCREMENTS Y
93 #define STB_C_LEX_C_ARROW Y
94 #define STB_C_LEX_EQUAL_ARROW N
95 #define STB_C_LEX_C_BITWISEEQ Y
96 #define STB_C_LEX_C_ARITHEQ Y
97 #define STB_C_LEX_PARSE_SUFFIXES N
98 #define STB_C_LEX_DECIMAL_SUFFIXES ""
99 #define STB_C_LEX_HEX_SUFFIXES ""
100 #define STB_C_LEX_OCTAL_SUFFIXES ""
101 #define STB_C_LEX_FLOAT_SUFFIXES ""
102 #define STB_C_LEX_0_IS_EOF N
103 #define STB_C_LEX_INTEGERS_AS_DOUBLES N
104 #define STB_C_LEX_MULTILINE_DSTRINGS N
105 #define STB_C_LEX_MULTILINE_SSTRINGS N
106 #define STB_C_LEX_USE_STDLIB Y
107 #define STB_C_LEX_DOLLAR_IDENTIFIER Y
108 #define STB_C_LEX_FLOAT_NO_DECIMAL Y
109 #define STB_C_LEX_DEFINE_ALL_TOKEN_NAMES N
110 #define STB_C_LEX_DISCARD_PREPROCESSOR N // we just changed this one
111 #define STB_C_LEXER_DEFINITIONS
112 // end stb-c-lexer configuration
113
114 #define STB_C_LEXER_IMPLEMENTATION
115 #ifdef GEOGRAM_USE_BUILTIN_DEPS
116 #include <geogram/third_party/stb/stb_c_lexer.h>
117 #else
118 #include <stb/stb_c_lexer.h>
119 #endif
120
121 #ifdef GEO_COMPILER_GCC_FAMILY
122 #pragma GCC diagnostic pop
123 #endif
124
125 #ifdef GEO_COMPILER_MSVC
126 #pragma warning (pop)
127 #endif
128
129
130 namespace {
131 using namespace GEO;
132 static constexpr int CLEX_booleanlit = CLEX_first_unused_token;
133 299267 stb_lexer& getlex(void* lex_) {
134 299267 return *reinterpret_cast<stb_lexer*>(lex_);
135 }
136 }
137
138 /******************************************************************************/
139
140 namespace GEO {
141
1/2
✓ Branch 2 taken 20 times.
✗ Branch 3 not taken.
20 CSGCompiler::CSGCompiler() : lex_(nullptr), lines_(0) {
142 20 progress_ = nullptr;
143
1/2
✓ Branch 1 taken 20 times.
✗ Branch 2 not taken.
20 builder_ = std::make_shared<CSGBuilder>();
144 20 }
145
146 20 std::shared_ptr<Mesh> CSGCompiler::compile_file(
147 const std::filesystem::path& input_filename
148 ) {
149 // Add the directory that contains the file to the builder's file path,
150 // so that import() instructions are able to find files in the same
151 // directory.
152
2/4
✓ Branch 2 taken 20 times.
✗ Branch 3 not taken.
✓ Branch 5 taken 20 times.
✗ Branch 6 not taken.
20 builder_->push_file_path(input_filename.parent_path());
153
1/2
✓ Branch 1 taken 20 times.
✗ Branch 2 not taken.
20 std::string source = GEOCSG::load_OpenSCAD(input_filename);
154
1/2
✗ Branch 1 not taken.
✓ Branch 2 taken 20 times.
20 if(source.length() == 0) {
155 throw std::logic_error(
156 ✗ filename_.string() + ": could not open file (or file is empty)"
157 ✗ );
158 }
159
160
1/2
✓ Branch 1 taken 20 times.
✗ Branch 2 not taken.
20 std::shared_ptr<Mesh> result = compile_string(source);
161
162
6/8
✓ Branch 1 taken 20 times.
✗ Branch 2 not taken.
✓ Branch 5 taken 20 times.
✗ Branch 6 not taken.
✓ Branch 7 taken 1 times.
✓ Branch 8 taken 19 times.
✓ Branch 9 taken 1 times.
✓ Branch 10 taken 19 times.
20 if(result != nullptr && result->vertices.dimension() == 2) {
163
1/2
✓ Branch 2 taken 1 times.
✗ Branch 3 not taken.
1 result->vertices.set_dimension(3);
164 }
165
166
1/2
✓ Branch 2 taken 20 times.
✗ Branch 3 not taken.
20 builder_->pop_file_path();
167 20 return result;
168 20 }
169
170 20 std::shared_ptr<Mesh> CSGCompiler::compile_string(
171 const std::string& source
172 ) {
173 20 std::shared_ptr<Mesh> result;
174
175 static constexpr size_t BUFFER_SIZE = 0x10000;
176
1/2
✓ Branch 1 taken 20 times.
✗ Branch 2 not taken.
20 char* buffer = new char[BUFFER_SIZE];
177 stb_lexer lex;
178 20 lex_ = &lex;
179 20 lines_ = NO_INDEX;
180 20 line_ = NO_INDEX;
181 20 line_ptr_ = nullptr;
182
183 try {
184 20 stb_c_lexer_init(
185 &lex,
186 source.c_str(),
187 20 source.c_str()+source.length(),
188 buffer, BUFFER_SIZE
189 );
190
3/6
✓ Branch 3 taken 20 times.
✗ Branch 4 not taken.
✓ Branch 6 taken 20 times.
✗ Branch 7 not taken.
✓ Branch 9 taken 20 times.
✗ Branch 10 not taken.
40 ProgressTask progress("CSG", index_t(lines()), builder_->verbose());
191 20 progress_ = &progress;
192 20 CSGScope scope;
193
1/2
✓ Branch 2 taken 20 times.
✗ Branch 3 not taken.
20 builder().push_scope();
194
3/4
✓ Branch 1 taken 44 times.
✗ Branch 2 not taken.
✓ Branch 4 taken 24 times.
✓ Branch 5 taken 20 times.
44 while(lookahead_token().type != CLEX_eof) {
195
1/2
✓ Branch 1 taken 24 times.
✗ Branch 2 not taken.
24 parse_instruction_or_object();
196 }
197
2/4
✓ Branch 3 taken 20 times.
✗ Branch 4 not taken.
✓ Branch 6 taken 20 times.
✗ Branch 7 not taken.
20 result = builder().union_instr(builder().top_scope());
198
1/2
✓ Branch 2 taken 20 times.
✗ Branch 3 not taken.
20 builder().pop_scope();
199
0/3
✗ Branch 4 not taken.
✗ Branch 5 not taken.
✗ Branch 6 not taken.
20 } catch(std::shared_ptr<Mesh> reroot) {
200 ✗ Logger::out("CSG") << "Re-rooted (!) from line " << line()
201 ✗ << std::endl;
202 ✗ result = reroot;
203 ✗ } catch(const std::logic_error& e) {
204 ✗ Logger::err("CSG") << "Error while parsing file:"
205 ✗ << e.what()
206 ✗ << std::endl;
207 ✗ result = std::make_shared<Mesh>();
208 ✗ }
209
1/2
✓ Branch 0 taken 20 times.
✗ Branch 1 not taken.
20 delete[] buffer;
210 20 lex_ = nullptr;
211 20 progress_ = nullptr;
212 20 lines_ = 0;
213
214
215
3/4
✓ Branch 2 taken 20 times.
✗ Branch 3 not taken.
✓ Branch 4 taken 19 times.
✓ Branch 5 taken 1 times.
20 if(result->vertices.dimension() == 3) {
216
1/2
✓ Branch 2 taken 19 times.
✗ Branch 3 not taken.
19 reorient_connected_components(*result);
217 }
218
219 20 return result;
220 ✗ }
221
222 /********* Parser ********************************************************/
223
224 702 void CSGCompiler::parse_instruction_or_object() {
225
1/2
✓ Branch 1 taken 702 times.
✗ Branch 2 not taken.
702 Token lookahead = lookahead_token();
226
227 // get modifier, one of:
228 // '%', '*': ignore subtree (in OpenSCAD, '%' means transparent display)
229 // '#' : does not change anything (in OpenSCAD, transparent display)
230 // '!' : replace root with subtree
231
232 702 char modifier = ' ';
233
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)) {
234
1/2
✓ Branch 1 taken 3 times.
✗ Branch 2 not taken.
3 modifier = char(next_token().type);
235
1/2
✓ Branch 1 taken 3 times.
✗ Branch 2 not taken.
3 lookahead = lookahead_token();
236 }
237
238
1/2
✗ Branch 0 not taken.
✓ Branch 1 taken 702 times.
702 if(lookahead.type != CLEX_id) {
239 ✗ syntax_error("expected id (object or instruction)", lookahead);
240 }
241
242
1/2
✓ Branch 1 taken 702 times.
✗ Branch 2 not taken.
702 std::string instr_or_object_name = lookahead.str_val;
243
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)) {
244
1/2
✓ Branch 1 taken 248 times.
✗ Branch 2 not taken.
248 parse_object();
245
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)) {
246
1/2
✓ Branch 1 taken 454 times.
✗ Branch 2 not taken.
454 parse_instruction();
247 } else {
248 ✗ syntax_error("id is no known object or instruction", lookahead);
249 }
250
251 // '%': no effect on CSG tree, transparent rendering
252 // '*': no effect on CSG tree
253
3/4
✓ Branch 0 taken 699 times.
✓ Branch 1 taken 3 times.
✗ Branch 2 not taken.
✓ Branch 3 taken 699 times.
702 if(modifier == '%' || modifier == '*') {
254
1/2
✓ Branch 2 taken 3 times.
✗ Branch 3 not taken.
3 builder().top_scope().pop_back(); // remove latest generated object
255 }
256
257 // '!': replace root
258
1/2
✗ Branch 0 not taken.
✓ Branch 1 taken 702 times.
702 if(modifier == '!') {
259 // It is caught right after the main parsing loop.
260 ✗ std::shared_ptr<Mesh> new_root = *builder().top_scope().rbegin();
261 ✗ throw(new_root);
262 ✗ }
263
264
1/2
✓ Branch 0 taken 702 times.
✗ Branch 1 not taken.
702 if(progress_ != nullptr) {
265
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()) {
266 ✗ throw(std::logic_error("canceled"));
267 }
268
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()));
269 }
270
271
1/2
✗ Branch 2 not taken.
✓ Branch 3 taken 702 times.
702 if(builder_->verbose()) {
272 ✗ index_t cur_line = index_t(line());
273 ✗ Logger::out("CSG") << "Executed " << instr_or_object_name
274 ✗ << " at line " << cur_line << "/" << lines_
275 ✗ << " (" << index_t(cur_line*100) /
276 ✗ std::max(lines_,index_t(1)) << "%)"
277 ✗ << std::endl;
278 }
279 702 }
280
281 248 void CSGCompiler::parse_object() {
282
1/2
✓ Branch 1 taken 248 times.
✗ Branch 2 not taken.
248 Token tok = next_token();
283
4/8
✓ 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.
✗ Branch 7 not taken.
✓ Branch 8 taken 248 times.
248 if(tok.type != CLEX_id || !is_object(tok.str_val)) {
284 ✗ syntax_error("expected object");
285 }
286
1/2
✓ Branch 1 taken 248 times.
✗ Branch 2 not taken.
248 std::string object_name = tok.str_val;
287
288
1/2
✓ Branch 1 taken 248 times.
✗ Branch 2 not taken.
248 index_t object_line = index_t(line());
289
290
1/2
✓ Branch 1 taken 248 times.
✗ Branch 2 not taken.
248 ArgList args = parse_arg_list();
291
1/2
✓ Branch 1 taken 248 times.
✗ Branch 2 not taken.
248 next_token_check(';');
292
293
3/6
✓ Branch 2 taken 248 times.
✗ Branch 3 not taken.
✓ Branch 5 taken 248 times.
✗ Branch 6 not taken.
✓ Branch 8 taken 248 times.
✗ Branch 9 not taken.
496 builder_->set_fa(args.get_arg("$fa",CSGBuilder::DEFAULT_FA));
294
3/6
✓ Branch 2 taken 248 times.
✗ Branch 3 not taken.
✓ Branch 5 taken 248 times.
✗ Branch 6 not taken.
✓ Branch 8 taken 248 times.
✗ Branch 9 not taken.
496 builder_->set_fs(args.get_arg("$fs",CSGBuilder::DEFAULT_FS));
295
3/6
✓ Branch 2 taken 248 times.
✗ Branch 3 not taken.
✓ Branch 5 taken 248 times.
✗ Branch 6 not taken.
✓ Branch 8 taken 248 times.
✗ Branch 9 not taken.
496 builder_->set_fn(args.get_arg("$fn",CSGBuilder::DEFAULT_FN));
296
297
1/2
✗ Branch 2 not taken.
✓ Branch 3 taken 248 times.
248 if(builder_->verbose()) {
298 ✗ Logger::out("CSG") << object_name << " at line: "
299 ✗ << object_line << std::endl;
300 }
301
302
1/2
✓ Branch 2 taken 248 times.
✗ Branch 3 not taken.
248 builder_->add_object(object_name, args);
303
1/2
✓ Branch 2 taken 248 times.
✗ Branch 3 not taken.
248 builder_->reset_defaults();
304 248 }
305
306 454 void CSGCompiler::parse_instruction() {
307
1/2
✓ Branch 1 taken 454 times.
✗ Branch 2 not taken.
454 Token tok = next_token();
308
4/8
✓ 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.
✗ Branch 7 not taken.
✓ Branch 8 taken 454 times.
454 if(tok.type != CLEX_id || !is_instruction(tok.str_val)) {
309 ✗ syntax_error("expected instruction",tok);
310 }
311
1/2
✓ Branch 1 taken 454 times.
✗ Branch 2 not taken.
454 std::string instr_name = tok.str_val;
312
313
1/2
✓ Branch 1 taken 454 times.
✗ Branch 2 not taken.
454 index_t instruction_line = index_t(line());
314
315
1/2
✓ Branch 1 taken 454 times.
✗ Branch 2 not taken.
454 ArgList args = parse_arg_list();
316
317
318 // In .csg files produced by OpenSCAD it often happens that
319 // there are empty instructions without any scope.
320
2/4
✓ Branch 1 taken 454 times.
✗ Branch 2 not taken.
✗ Branch 4 not taken.
✓ Branch 5 taken 454 times.
454 if(lookahead_token().type == ';') {
321 ✗ next_token_check(';');
322 ✗ builder_->begin_instruction();
323 ✗ builder_->end_instruction(instr_name, args);
324 ✗ return;
325 }
326
327
1/2
✓ Branch 2 taken 454 times.
✗ Branch 3 not taken.
454 builder_->begin_instruction();
328
2/4
✓ Branch 1 taken 454 times.
✗ Branch 2 not taken.
✓ Branch 4 taken 454 times.
✗ Branch 5 not taken.
454 if(lookahead_token().type == '{') {
329
1/2
✓ Branch 1 taken 454 times.
✗ Branch 2 not taken.
454 next_token_check('{');
330 for(;;) {
331
3/4
✓ Branch 1 taken 1132 times.
✗ Branch 2 not taken.
✓ Branch 4 taken 454 times.
✓ Branch 5 taken 678 times.
1132 if(lookahead_token().type == '}') {
332 454 break;
333 }
334
1/2
✓ Branch 1 taken 678 times.
✗ Branch 2 not taken.
678 parse_instruction_or_object();
335 }
336
1/2
✓ Branch 1 taken 454 times.
✗ Branch 2 not taken.
454 next_token_check('}');
337 } else {
338 ✗ parse_instruction_or_object();
339 }
340
341
1/2
✗ Branch 2 not taken.
✓ Branch 3 taken 454 times.
454 if(builder_->verbose()) {
342 ✗ Logger::out("CSG") << instr_name << " at line: "
343 ✗ << instruction_line << std::endl;
344 }
345
346
3/6
✓ Branch 2 taken 454 times.
✗ Branch 3 not taken.
✓ Branch 5 taken 454 times.
✗ Branch 6 not taken.
✓ Branch 8 taken 454 times.
✗ Branch 9 not taken.
908 builder_->set_fa(args.get_arg("$fa",CSGBuilder::DEFAULT_FA));
347
3/6
✓ Branch 2 taken 454 times.
✗ Branch 3 not taken.
✓ Branch 5 taken 454 times.
✗ Branch 6 not taken.
✓ Branch 8 taken 454 times.
✗ Branch 9 not taken.
908 builder_->set_fs(args.get_arg("$fs",CSGBuilder::DEFAULT_FS));
348
3/6
✓ Branch 2 taken 454 times.
✗ Branch 3 not taken.
✓ Branch 5 taken 454 times.
✗ Branch 6 not taken.
✓ Branch 8 taken 454 times.
✗ Branch 9 not taken.
908 builder_->set_fn(args.get_arg("$fn",CSGBuilder::DEFAULT_FN));
349
350
1/2
✓ Branch 2 taken 454 times.
✗ Branch 3 not taken.
454 builder_->end_instruction(instr_name, args);
351
1/2
✓ Branch 2 taken 454 times.
✗ Branch 3 not taken.
454 builder_->reset_defaults();
352
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.
454 }
353
354 702 CSGCompiler::ArgList CSGCompiler::parse_arg_list() {
355 702 ArgList result;
356
1/2
✓ Branch 1 taken 702 times.
✗ Branch 2 not taken.
702 next_token_check('(');
357 for(;;) {
358
3/4
✓ Branch 1 taken 1731 times.
✗ Branch 2 not taken.
✓ Branch 4 taken 168 times.
✓ Branch 5 taken 1563 times.
1731 if(lookahead_token().type == ')') {
359 168 break;
360 }
361
1/2
✓ Branch 1 taken 1563 times.
✗ Branch 2 not taken.
1563 std::string arg_name = "";
362
3/4
✓ Branch 1 taken 1563 times.
✗ Branch 2 not taken.
✓ Branch 4 taken 1308 times.
✓ Branch 5 taken 255 times.
1563 if(lookahead_token().type == CLEX_id) {
363
1/2
✓ Branch 1 taken 1308 times.
✗ Branch 2 not taken.
1308 arg_name = next_token().str_val;
364
1/2
✓ Branch 1 taken 1308 times.
✗ Branch 2 not taken.
1308 next_token_check('=');
365 }
366
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());
367
3/4
✓ Branch 1 taken 1563 times.
✗ Branch 2 not taken.
✓ Branch 4 taken 534 times.
✓ Branch 5 taken 1029 times.
1563 if(lookahead_token().type == ')') {
368 534 break;
369 }
370
1/2
✓ Branch 1 taken 1029 times.
✗ Branch 2 not taken.
1029 next_token_check(',');
371
2/2
✓ Branch 1 taken 1029 times.
✓ Branch 2 taken 534 times.
2592 }
372
1/2
✓ Branch 1 taken 702 times.
✗ Branch 2 not taken.
702 next_token_check(')');
373 702 return result;
374 ✗ }
375
376 7596 CSGCompiler::Value CSGCompiler::parse_value() {
377
3/4
✓ Branch 1 taken 7596 times.
✗ Branch 2 not taken.
✓ Branch 4 taken 1642 times.
✓ Branch 5 taken 5954 times.
7596 if(lookahead_token().type == '[') {
378
1/2
✓ Branch 1 taken 1642 times.
✗ Branch 2 not taken.
1642 return parse_array();
379 }
380
1/2
✓ Branch 1 taken 5954 times.
✗ Branch 2 not taken.
5954 Token tok = next_token();
381
2/2
✓ Branch 0 taken 315 times.
✓ Branch 1 taken 5639 times.
5954 if(tok.type == '-') {
382
1/2
✓ Branch 1 taken 315 times.
✗ Branch 2 not taken.
315 tok = next_token();
383
2/2
✓ Branch 0 taken 173 times.
✓ Branch 1 taken 142 times.
315 if(tok.type == CLEX_intlit) {
384
1/2
✓ Branch 1 taken 173 times.
✗ Branch 2 not taken.
173 return Value(-tok.int_val);
385
1/2
✓ Branch 0 taken 142 times.
✗ Branch 1 not taken.
142 } else if(tok.type == CLEX_floatlit) {
386
1/2
✓ Branch 1 taken 142 times.
✗ Branch 2 not taken.
142 return Value(-tok.double_val);
387 } else {
388 ✗ syntax_error("Expected number", tok);
389 }
390 }
391
392
2/2
✓ Branch 0 taken 5092 times.
✓ Branch 1 taken 547 times.
5639 if(tok.type == CLEX_intlit) {
393
1/2
✓ Branch 1 taken 5092 times.
✗ Branch 2 not taken.
5092 return Value(tok.int_val);
394 }
395
396
2/2
✓ Branch 0 taken 308 times.
✓ Branch 1 taken 239 times.
547 if(tok.type == CLEX_floatlit) {
397
1/2
✓ Branch 1 taken 308 times.
✗ Branch 2 not taken.
308 return Value(tok.double_val);
398 }
399
400
2/2
✓ Branch 0 taken 156 times.
✓ Branch 1 taken 83 times.
239 if(tok.type == CLEX_booleanlit) {
401
1/2
✓ Branch 1 taken 156 times.
✗ Branch 2 not taken.
156 return Value(tok.boolean_val);
402 }
403
404
2/2
✓ Branch 0 taken 50 times.
✓ Branch 1 taken 33 times.
83 if(tok.type == CLEX_dqstring) {
405
1/2
✓ Branch 1 taken 50 times.
✗ Branch 2 not taken.
50 return Value(tok.str_val);
406 }
407
408
4/8
✓ Branch 0 taken 33 times.
✗ Branch 1 not taken.
✓ Branch 3 taken 33 times.
✗ Branch 4 not taken.
✓ Branch 5 taken 33 times.
✗ Branch 6 not taken.
✓ Branch 7 taken 33 times.
✗ Branch 8 not taken.
33 if(tok.type == CLEX_id && tok.str_val == "undef") {
409
1/2
✓ Branch 1 taken 33 times.
✗ Branch 2 not taken.
33 return Value();
410 }
411
412 ✗ syntax_error("Expected value", tok);
413 5954 }
414
415 1642 CSGCompiler::Value CSGCompiler::parse_array() {
416 1642 Value result;
417 1642 result.type = Value::ARRAY1D;
418
419
1/2
✓ Branch 1 taken 1642 times.
✗ Branch 2 not taken.
1642 next_token_check('[');
420 for(;;) {
421
2/4
✓ Branch 1 taken 6033 times.
✗ Branch 2 not taken.
✗ Branch 4 not taken.
✓ Branch 5 taken 6033 times.
6033 if(lookahead_token().type == ']') {
422 ✗ break;
423 }
424
1/2
✓ Branch 1 taken 6033 times.
✗ Branch 2 not taken.
6033 Value item = parse_value();
425
426
2/2
✓ Branch 0 taken 4798 times.
✓ Branch 1 taken 1235 times.
6033 if(item.type == Value::NUMBER) {
427
1/2
✓ Branch 1 taken 4798 times.
✗ Branch 2 not taken.
4798 result.array_val.resize(1);
428
2/4
✓ Branch 1 taken 4798 times.
✗ Branch 2 not taken.
✓ Branch 4 taken 4798 times.
✗ Branch 5 not taken.
4798 result.array_val[0].push_back(item.number_val);
429
1/2
✗ Branch 0 not taken.
✓ Branch 1 taken 1235 times.
1235 } else if(item.type == Value::BOOLEAN) {
430 ✗ result.array_val.resize(1);
431 ✗ result.array_val[0].push_back(double(item.boolean_val));
432
1/2
✓ Branch 0 taken 1235 times.
✗ Branch 1 not taken.
1235 } else if(item.type == Value::ARRAY1D) {
433 1235 result.type = Value::ARRAY2D;
434
1/2
✗ Branch 1 not taken.
✓ Branch 2 taken 1235 times.
1235 if(item.array_val.size() == 0) {
435 ✗ result.array_val.push_back(vector<double>());
436 } else {
437
2/4
✓ Branch 1 taken 1235 times.
✗ Branch 2 not taken.
✓ Branch 4 taken 1235 times.
✗ Branch 5 not taken.
1235 result.array_val.push_back(item.array_val[0]);
438 }
439 }
440
441
3/4
✓ Branch 1 taken 6033 times.
✗ Branch 2 not taken.
✓ Branch 4 taken 1642 times.
✓ Branch 5 taken 4391 times.
6033 if(lookahead_token().type == ']') {
442 1642 break;
443 }
444
1/2
✓ Branch 1 taken 4391 times.
✗ Branch 2 not taken.
4391 next_token_check(',');
445
2/2
✓ Branch 1 taken 4391 times.
✓ Branch 2 taken 1642 times.
10424 }
446
1/2
✓ Branch 1 taken 1642 times.
✗ Branch 2 not taken.
1642 next_token_check(']');
447
448 1642 return result;
449 ✗ }
450
451 950 bool CSGCompiler::is_object(const std::string& id) const {
452 950 return builder().is_object(id);
453 }
454
455 908 bool CSGCompiler::is_instruction(const std::string& id) const {
456 908 return builder().is_instruction(id);
457 }
458
459 702 bool CSGCompiler::is_modifier(int toktype) const {
460 return
461
1/2
✓ Branch 0 taken 699 times.
✗ Branch 1 not taken.
699 (toktype == int('%')) ||
462
1/2
✓ Branch 0 taken 699 times.
✗ Branch 1 not taken.
699 (toktype == int('#')) ||
463
3/4
✓ Branch 0 taken 699 times.
✓ Branch 1 taken 3 times.
✗ Branch 2 not taken.
✓ Branch 3 taken 699 times.
1401 (toktype == int('!')) ||
464 702 (toktype == int('*')) ;
465 }
466
467 /********* Parser utilities **********************************************/
468
469 12572 void CSGCompiler::next_token_check(char c) {
470
1/2
✓ Branch 1 taken 12572 times.
✗ Branch 2 not taken.
12572 Token tok = next_token();
471
1/2
✗ Branch 0 not taken.
✓ Branch 1 taken 12572 times.
12572 if(tok.type != int(c)) {
472 ✗ syntax_error(
473 ✗ String::format(
474 ✗ "Expected %c, got \"%s\"", c, tok.to_string().c_str()
475 ).c_str()
476 );
477 }
478 12572 }
479
480 20854 CSGCompiler::Token CSGCompiler::next_token() {
481
2/2
✓ Branch 0 taken 18281 times.
✓ Branch 1 taken 2573 times.
20854 if(lookahead_token_.type != -1) {
482 18281 Token result = lookahead_token_;
483 18281 lookahead_token_.type = -1;
484
1/2
✗ Branch 0 not taken.
✓ Branch 1 taken 18281 times.
18281 return result;
485 ✗ }
486 2573 return next_token_internal();
487 }
488
489 27308 CSGCompiler::Token CSGCompiler::lookahead_token() {
490
2/2
✓ Branch 0 taken 18301 times.
✓ Branch 1 taken 9007 times.
27308 if(lookahead_token_.type == -1) {
491
1/2
✓ Branch 1 taken 18301 times.
✗ Branch 2 not taken.
18301 lookahead_token_ = next_token_internal();
492 }
493 27308 return lookahead_token_;
494 }
495
496 20874 CSGCompiler::Token CSGCompiler::next_token_internal() {
497 20874 Token result;
498
2/2
✓ Branch 2 taken 20854 times.
✓ Branch 3 taken 20 times.
20874 if(stb_c_lexer_get_token(&getlex(lex_))) {
499 20854 result.type = int(getlex(lex_).token);
500
2/2
✓ Branch 1 taken 2199 times.
✓ Branch 2 taken 18655 times.
20854 if(getlex(lex_).token == CLEX_id) {
501
1/2
✓ Branch 2 taken 2199 times.
✗ Branch 3 not taken.
2199 result.str_val = getlex(lex_).string;
502
3/4
✓ Branch 1 taken 2199 times.
✗ Branch 2 not taken.
✓ Branch 3 taken 76 times.
✓ Branch 4 taken 2123 times.
2199 if(result.str_val == "true") {
503 76 result.type = CLEX_booleanlit;
504 76 result.boolean_val = true;
505
3/4
✓ Branch 1 taken 2123 times.
✗ Branch 2 not taken.
✓ Branch 3 taken 80 times.
✓ Branch 4 taken 2043 times.
2123 } else if(result.str_val == "false") {
506 80 result.type = CLEX_booleanlit;
507 80 result.boolean_val = false;
508 }
509 }
510
2/2
✓ Branch 1 taken 50 times.
✓ Branch 2 taken 20804 times.
20854 if(getlex(lex_).token == CLEX_dqstring) {
511
1/2
✓ Branch 2 taken 50 times.
✗ Branch 3 not taken.
50 result.str_val = getlex(lex_).string;
512 }
513 20854 result.int_val = int(getlex(lex_).int_number);
514 20854 result.double_val = getlex(lex_).real_number;
515 } else {
516 20 result.type = CLEX_eof;
517 }
518
1/2
✗ Branch 1 not taken.
✓ Branch 2 taken 20874 times.
20874 if(getlex(lex_).token == CLEX_parse_error) {
519 ✗ syntax_error("lexical error");
520 }
521 20874 return result;
522 ✗ }
523
524 20 int CSGCompiler::lines() const {
525
1/2
✓ Branch 0 taken 20 times.
✗ Branch 1 not taken.
20 if(lines_ == NO_INDEX) {
526 20 compute_lines();
527 }
528 20 return int(lines_);
529 }
530
531 20 void CSGCompiler::compute_lines() const {
532 20 index_t result=0;
533 49872 for(
534 20 const char* p = getlex(lex_).input_stream;
535
2/2
✓ Branch 1 taken 49872 times.
✓ Branch 2 taken 20 times.
49892 p != getlex(lex_).eof; ++p
536 ) {
537
2/2
✓ Branch 0 taken 1176 times.
✓ Branch 1 taken 48696 times.
49872 if(*p == '\n') {
538 1176 ++result;
539 }
540 }
541 20 lines_ = result;
542 20 }
543
544 1404 int CSGCompiler::line() const {
545
2/2
✓ Branch 0 taken 20 times.
✓ Branch 1 taken 1384 times.
1404 if(line_ == NO_INDEX) {
546 20 line_=1;
547 181 for(
548 20 line_ptr_ = getlex(lex_).input_stream;
549
4/4
✓ Branch 1 taken 161 times.
✓ Branch 2 taken 20 times.
✓ Branch 3 taken 161 times.
✓ Branch 4 taken 20 times.
342 ( line_ptr_ != getlex(lex_).parse_point &&
550
1/2
✓ Branch 1 taken 161 times.
✗ Branch 2 not taken.
161 line_ptr_ != getlex(lex_).eof );
551 161 ++line_ptr_
552 ) {
553
1/2
✗ Branch 0 not taken.
✓ Branch 1 taken 161 times.
161 if(*line_ptr_ == '\n') {
554 ✗ ++line_;
555 }
556 }
557 20 return int(line_);
558 }
559
4/4
✓ Branch 1 taken 49671 times.
✓ Branch 2 taken 1384 times.
✓ Branch 3 taken 49671 times.
✓ Branch 4 taken 1384 times.
100726 while(line_ptr_ != getlex(lex_).parse_point &&
560
1/2
✓ Branch 1 taken 49671 times.
✗ Branch 2 not taken.
49671 line_ptr_ != getlex(lex_).eof ) {
561
2/2
✓ Branch 0 taken 1136 times.
✓ Branch 1 taken 48535 times.
49671 if(*line_ptr_ == '\n') {
562 1136 ++line_;
563 }
564 49671 ++line_ptr_;
565 }
566 1384 return int(line_);
567 }
568
569 ✗ [[noreturn]] void CSGCompiler::syntax_error(const char* msg) {
570 throw(
571 std::logic_error(
572 ✗ String::format(
573 "%s:%d %s",
574 ✗ filename_.string().c_str(), line(), msg
575 )
576 )
577 ✗ );
578 }
579
580 ✗ [[noreturn]] void CSGCompiler::syntax_error(
581 const char* msg, const Token& tok
582 ) {
583 throw(
584 std::logic_error(
585 ✗ String::format(
586 "%s:%d %s (got \"%s\")",
587 ✗ filename_.string().c_str(), line(), msg,
588 ✗ tok.to_string().c_str()
589 )
590 )
591 ✗ );
592 }
593
594 /***** Token **********************************************************/
595
596 20894 CSGCompiler::Token::Token() :
597 20894 type(-1),
598 20894 int_val(0),
599 20894 double_val(0.0),
600 20894 boolean_val(false) {
601 20894 }
602
603 ✗ std::string CSGCompiler::Token::to_string() const {
604 ✗ if(type < 256) {
605 ✗ return String::format("\'%c\'",type);
606 }
607 ✗ if(type == CLEX_intlit) {
608 ✗ return String::to_string(int_val);
609 }
610 ✗ if(type == CLEX_floatlit) {
611 ✗ return String::to_string(double_val);
612 }
613 ✗ if(type == CLEX_booleanlit) {
614 ✗ return String::to_string(boolean_val);
615 }
616 ✗ if(type == CLEX_id) {
617 ✗ return str_val;
618 }
619 ✗ return "<unknown token>";
620 }
621
622 }
623