| 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 |