├── .gitignore ├── License.txt ├── Readme.md └── ccpp.h /.gitignore: -------------------------------------------------------------------------------- 1 | # Temporary testing file 2 | main.cpp 3 | 4 | # Temporary Visual Studio project garbage 5 | *.vcxproj* 6 | [Dd]ebug/ 7 | [Rr]elease/ 8 | x64/ 9 | -------------------------------------------------------------------------------- /License.txt: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2018 codecat 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /Readme.md: -------------------------------------------------------------------------------- 1 | # Codecat Preprocessor 2 | A general purpose single-header preprocessor library. 3 | 4 | ## Supported directives 5 | The following directives are currently supported: 6 | 7 | * `#define ` 8 | * `#undef ` 9 | * `#if ` 10 | * `#elif ` 11 | * `#else` 12 | * `#endif` 13 | * `#include` (via `set_include_callback`) 14 | * Other arbitrary directives (via `set_command_callback`) 15 | 16 | ## Example usage: 17 | ```cpp 18 | static char* read_file(const char* path, size_t* out_size) { /* ... */ } 19 | 20 | int main() 21 | { 22 | // Read contents of file "SomeFile.txt" into "buffer" 23 | size_t size; 24 | char* buffer = read_file("SomeFile.txt", &size); 25 | 26 | // Create a preprocessor 27 | ccpp::processor p; 28 | 29 | // Add some definitions 30 | p.add_define("SOME_DEFINE"); 31 | 32 | // Begin processing 33 | p.process(buffer, size); 34 | 35 | // Dump output 36 | printf("%s\n", buffer); 37 | 38 | return 0; 39 | } 40 | ``` 41 | 42 | ## Motivation 43 | I couldn't find a good simple no-dependencies preprocessor library for general purpose use that was also permissively licensed, so I decided to make my own. 44 | 45 | This was made primarily as a preprocessor for [Openplanet](https://openplanet.nl/)'s scripts. 46 | 47 | ## License 48 | [MIT license](License.txt). 49 | -------------------------------------------------------------------------------- /ccpp.h: -------------------------------------------------------------------------------- 1 | /* Codecat Preprocessor 2 | * 3 | * A general purpose single-header preprocessor library. 4 | * 5 | * Example usage: 6 | * #define CCPP_IMPL 7 | * #include "ccpp.h" 8 | * 9 | * int main() 10 | * { 11 | * // Read contents of file "SomeFile.txt" into "buffer" 12 | * FILE* fh = fopen("SomeFile.txt", "rb"); 13 | * fseek(fh, 0, SEEK_END); 14 | * size_t size = ftell(fh); 15 | * fseek(fh, 0, SEEK_SET); 16 | * 17 | * char* buffer = (char*)malloc(size + 1); 18 | * fread(buffer, 1, size, fh); 19 | * fclose(fh); 20 | * buffer[size] = '\0'; 21 | * 22 | * // Create a preprocessor 23 | * ccpp::processor p; 24 | * 25 | * // Add some definitions 26 | * p.add_define("SOME_DEFINE"); 27 | * 28 | * // Begin processing 29 | * p.process(buffer, size); 30 | * 31 | * // Dump output 32 | * printf("%s\n", buffer); 33 | * 34 | * return 0; 35 | * } 36 | * 37 | * Supported directives: 38 | * #define 39 | * #undef 40 | * #if 41 | * #else 42 | * #elif 43 | * #endif 44 | * 45 | * 46 | * MIT License 47 | * 48 | * Copyright (c) 2018 codecat 49 | * 50 | * Permission is hereby granted, free of charge, to any person obtaining a copy 51 | * of this software and associated documentation files (the "Software"), to deal 52 | * in the Software without restriction, including without limitation the rights 53 | * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 54 | * copies of the Software, and to permit persons to whom the Software is 55 | * furnished to do so, subject to the following conditions: 56 | * 57 | * The above copyright notice and this permission notice shall be included in all 58 | * copies or substantial portions of the Software. 59 | * 60 | * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 61 | * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 62 | * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 63 | * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 64 | * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 65 | * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 66 | * SOFTWARE. 67 | */ 68 | 69 | #pragma once 70 | 71 | #include 72 | #include 73 | #include 74 | #include 75 | 76 | namespace ccpp 77 | { 78 | extern char character; 79 | 80 | class processor 81 | { 82 | typedef std::function include_callback_t; 83 | typedef std::function command_callback_t; 84 | 85 | private: 86 | char* m_p; 87 | char* m_pEnd; 88 | 89 | size_t m_line; 90 | size_t m_column; 91 | 92 | std::vector m_defines; 93 | std::stack m_stack; 94 | 95 | include_callback_t m_includeCallback; 96 | command_callback_t m_commandCallback; 97 | 98 | public: 99 | processor(); 100 | processor(const processor ©); 101 | ~processor(); 102 | 103 | void add_define(const char* name); 104 | void remove_define(const char* name); 105 | 106 | bool has_define(const char* name); 107 | 108 | void set_include_callback(const include_callback_t &callback); 109 | void set_command_callback(const command_callback_t &callback); 110 | 111 | void process(char* buffer); 112 | void process(char* buffer, size_t len); 113 | 114 | private: 115 | bool test_condition(); 116 | 117 | void expect_eol(); 118 | void consume_line(); 119 | 120 | void overwrite(char* p, size_t len); 121 | }; 122 | } 123 | 124 | #if defined(CCPP_IMPL) 125 | 126 | #include 127 | #include 128 | #include 129 | 130 | #ifndef CCPP_ERROR 131 | # define CCPP_ERROR(error, ...) printf("[CCPP ERROR] " error "\n", ##__VA_ARGS__) 132 | #endif 133 | 134 | #ifndef CCPP_ASSERT 135 | # if defined(_DEBUG) 136 | # define CCPP_ASSERT(cond) if (!(cond)) { CCPP_ERROR("Assertion failed: '%s' on line %d", #cond, __LINE__); } 137 | # else 138 | # define CCPP_ASSERT(cond) 139 | # endif 140 | #endif 141 | 142 | enum class ELexType 143 | { 144 | None, 145 | 146 | Whitespace, 147 | Newline, 148 | Word, 149 | Operator, 150 | String, 151 | }; 152 | 153 | static const char* lex_type_name(ELexType type) 154 | { 155 | switch (type) { 156 | case ELexType::Whitespace: return "WHITESPACE"; 157 | case ELexType::Newline: return "NEWLINE"; 158 | case ELexType::Word: return "WORD"; 159 | case ELexType::Operator: return "OPERATOR"; 160 | case ELexType::String: return "STRING"; 161 | } 162 | return "NONE"; 163 | } 164 | 165 | static void lex_char_text(char c, char* buffer, size_t size) 166 | { 167 | if (isalnum(c)) { 168 | snprintf(buffer, size, "%c", c); 169 | return; 170 | } 171 | snprintf(buffer, size, "\\x%02X", (int)c); 172 | } 173 | 174 | static size_t lex(char* p, char* pEnd, ELexType &type) 175 | { 176 | type = ELexType::None; 177 | 178 | char* pStart = p; 179 | 180 | if (*p == '"') { 181 | type = ELexType::String; 182 | 183 | while (p < pEnd) { 184 | p++; 185 | if (*p == '\\') { 186 | // Skip next character 187 | p++; 188 | } else if (*p == '"') { 189 | // End of string 190 | p++; 191 | break; 192 | } 193 | } 194 | 195 | } else { 196 | bool haveNewlineR = false; 197 | bool haveNewlineN = false; 198 | 199 | while (p < pEnd) { 200 | char c = *p; 201 | bool isWhitespace = (c == ' ' || c == '\t'); 202 | bool isNewline = (c == '\r' || c == '\n'); 203 | bool isAlphaNum = ((c >= 'a' && c <= 'z') || (c >= 'A' && c <= 'Z') || (c >= '0' && c <= '9') || c == '_'); 204 | bool isOperator = (c == '!' || c == '&' || c == '|' || c == '(' || c == ')'); 205 | 206 | if (isNewline) { 207 | // Only handle 1 newline at a time, which can be any combination of \r and \n 208 | if (c == '\r') { 209 | if (haveNewlineR) break; 210 | haveNewlineR = true; 211 | } 212 | if (c == '\n') { 213 | if (haveNewlineN) break; 214 | haveNewlineN = true; 215 | } 216 | } 217 | 218 | if (type == ELexType::None) { 219 | if (isWhitespace) { 220 | type = ELexType::Whitespace; 221 | } else if (isNewline) { 222 | type = ELexType::Newline; 223 | } else if (isAlphaNum) { 224 | type = ELexType::Word; 225 | } else if (isOperator) { 226 | type = ELexType::Operator; 227 | } 228 | 229 | } else { 230 | if (type == ELexType::Whitespace && !isWhitespace) { 231 | break; 232 | } else if (type == ELexType::Newline && !isNewline) { 233 | break; 234 | } else if (type == ELexType::Word && !isAlphaNum) { 235 | break; 236 | } else if (type == ELexType::Operator && !isOperator) { 237 | break; 238 | } 239 | } 240 | 241 | p++; 242 | } 243 | } 244 | 245 | return p - pStart; 246 | } 247 | 248 | static size_t lex_expect(char* p, char* pEnd, ELexType expected_type) 249 | { 250 | ELexType type; 251 | size_t len = lex(p, pEnd, type); 252 | 253 | if (type != expected_type) { 254 | char buffer[16]; 255 | lex_char_text(*p, buffer, sizeof(buffer)); 256 | CCPP_ERROR("Unexpected '%s' of type %s, was expecting a %s", buffer, lex_type_name(type), lex_type_name(expected_type)); 257 | return 0; 258 | } 259 | 260 | return len; 261 | } 262 | 263 | // Same as lex(), except skips whitespace and outputs start and length of the symbol 264 | static size_t lex_next(char* p, char* pEnd, ELexType &type, char** start, size_t* len) 265 | { 266 | ELexType t; 267 | size_t l = lex(p, pEnd, t); 268 | *len = l; 269 | 270 | if (t == ELexType::Whitespace) { 271 | p += l; 272 | size_t l2 = lex(p, pEnd, t); 273 | *len = l2; 274 | l += l2; 275 | } 276 | 277 | type = t; 278 | *start = p; 279 | return l; 280 | } 281 | 282 | char ccpp::character = '#'; 283 | 284 | enum 285 | { 286 | // Contents must pass 287 | Scope_Passing = (1 << 0), 288 | 289 | // Contents must be erased 290 | Scope_Erasing = (1 << 1), 291 | 292 | // Contents are inside of an else directive 293 | Scope_Else = (1 << 2), 294 | 295 | // Contents are inside of an else if directive 296 | Scope_ElseIf = (1 << 3), 297 | 298 | // Contents are deep and should be ignored 299 | Scope_Deep = (1 << 4), 300 | }; 301 | 302 | enum 303 | { 304 | // The match passed 305 | Match_Pass = (1 << 0), 306 | 307 | // Should be matched as AND with previous entry 308 | Match_OpAnd = (1 << 1), 309 | 310 | // Should be matched as OR with previous entry 311 | Match_OpOr = (1 << 2), 312 | }; 313 | 314 | ccpp::processor::processor() 315 | { 316 | m_p = nullptr; 317 | m_pEnd = nullptr; 318 | } 319 | 320 | ccpp::processor::processor(const processor ©) 321 | : processor() 322 | { 323 | for (const char* p : copy.m_defines) { 324 | add_define(p); 325 | } 326 | 327 | m_includeCallback = copy.m_includeCallback; 328 | m_commandCallback = copy.m_commandCallback; 329 | } 330 | 331 | ccpp::processor::~processor() 332 | { 333 | for (const char* p : m_defines) { 334 | free((void*)p); 335 | } 336 | } 337 | 338 | void ccpp::processor::add_define(const char* name) 339 | { 340 | if (has_define(name)) { 341 | CCPP_ERROR("Definition \"%s\" already exists!", name); 342 | return; 343 | } 344 | 345 | size_t len = strlen(name); 346 | char* p = (char*)malloc(len + 1); 347 | memcpy(p, name, len + 1); 348 | 349 | m_defines.emplace_back(p); 350 | } 351 | 352 | void ccpp::processor::remove_define(const char* name) 353 | { 354 | for (size_t i = 0; i < m_defines.size(); i++) { 355 | if (!strcmp(m_defines[i], name)) { 356 | m_defines.erase(m_defines.begin() + i); 357 | return; 358 | } 359 | } 360 | 361 | CCPP_ERROR("Couldn't undefine \"%s\" because it does not exist!", name); 362 | } 363 | 364 | bool ccpp::processor::has_define(const char* name) 365 | { 366 | for (const char* p : m_defines) { 367 | if (!strcmp(p, name)) { 368 | return true; 369 | } 370 | } 371 | return false; 372 | } 373 | 374 | void ccpp::processor::set_include_callback(const include_callback_t &callback) 375 | { 376 | m_includeCallback = callback; 377 | } 378 | 379 | void ccpp::processor::set_command_callback(const command_callback_t &callback) 380 | { 381 | m_commandCallback = callback; 382 | } 383 | 384 | void ccpp::processor::process(char* buffer) 385 | { 386 | process(buffer, strlen(buffer)); 387 | } 388 | 389 | void ccpp::processor::process(char* buffer, size_t len) 390 | { 391 | if (m_p != nullptr) { 392 | CCPP_ERROR("Illegal attempt of preprocessor usage while not finished preprocessing!"); 393 | return; 394 | } 395 | 396 | m_line = 1; 397 | m_column = 0; 398 | 399 | m_p = buffer; 400 | m_pEnd = buffer + len; 401 | 402 | while (m_p < m_pEnd) { 403 | bool isErasing = false; 404 | bool isDeep = false; 405 | 406 | if (m_stack.size() > 0) { 407 | const uint32_t &scope = m_stack.top(); 408 | 409 | isErasing = (scope & Scope_Erasing); 410 | isDeep = (scope & Scope_Deep); 411 | } 412 | 413 | if (*m_p == '\n') { 414 | m_column = 0; 415 | m_line++; 416 | m_p++; 417 | } else { 418 | if (m_column++ > 0 || *m_p != character) { 419 | if (isErasing) { 420 | *m_p = ' '; 421 | } 422 | 423 | m_p++; 424 | continue; 425 | } 426 | 427 | char* commandStart = m_p++; 428 | 429 | // Expect a command word 430 | size_t lenCommand = lex_expect(m_p, m_pEnd, ELexType::Word); 431 | if (lenCommand == 0) { 432 | continue; 433 | } 434 | 435 | char* wordCommand = (char*)alloca(lenCommand + 1); 436 | memcpy(wordCommand, m_p, lenCommand); 437 | wordCommand[lenCommand] = '\0'; 438 | 439 | m_p += lenCommand; 440 | 441 | if (!strcmp(wordCommand, "define")) { 442 | // #define 443 | 444 | if (isErasing) { 445 | // Just consume the line if we're erasing 446 | consume_line(); 447 | 448 | } else { 449 | // Expect some whitespace 450 | size_t lenCommandWhitespace = lex_expect(m_p, m_pEnd, ELexType::Whitespace); 451 | if (lenCommandWhitespace == 0) { 452 | continue; 453 | } 454 | m_p += lenCommandWhitespace; 455 | 456 | // Expect a define word 457 | size_t lenDefine = lex_expect(m_p, m_pEnd, ELexType::Word); 458 | if (lenDefine == 0) { 459 | continue; 460 | } 461 | 462 | char* wordDefine = (char*)alloca(lenDefine + 1); 463 | memcpy(wordDefine, m_p, lenDefine); 464 | wordDefine[lenDefine] = '\0'; 465 | 466 | m_p += lenDefine; 467 | 468 | // Add define 469 | add_define(wordDefine); 470 | 471 | // Expect end of line 472 | expect_eol(); 473 | } 474 | 475 | } else if (!strcmp(wordCommand, "undef")) { 476 | // #undef 477 | 478 | if (isErasing) { 479 | // Just consume the line if we're erasing 480 | consume_line(); 481 | 482 | } else { 483 | // Expect some whitespace 484 | size_t lenCommandWhitespace = lex_expect(m_p, m_pEnd, ELexType::Whitespace); 485 | if (lenCommandWhitespace == 0) { 486 | continue; 487 | } 488 | m_p += lenCommandWhitespace; 489 | 490 | // Expect a define word 491 | size_t lenDefine = lex_expect(m_p, m_pEnd, ELexType::Word); 492 | if (lenDefine == 0) { 493 | continue; 494 | } 495 | 496 | char* wordDefine = (char*)alloca(lenDefine + 1); 497 | memcpy(wordDefine, m_p, lenDefine); 498 | wordDefine[lenDefine] = '\0'; 499 | 500 | m_p += lenDefine; 501 | 502 | // Undefine 503 | remove_define(wordDefine); 504 | 505 | // Expect end of line 506 | expect_eol(); 507 | } 508 | 509 | } else if (!strcmp(wordCommand, "if")) { 510 | // #if 511 | 512 | if (isErasing) { 513 | // Just consume the line and push erasing at deep level 514 | m_stack.push(Scope_Erasing | Scope_Deep); 515 | consume_line(); 516 | 517 | } else { 518 | // Expect some whitespace 519 | size_t lenCommandWhitespace = lex_expect(m_p, m_pEnd, ELexType::Whitespace); 520 | if (lenCommandWhitespace == 0) { 521 | continue; 522 | } 523 | m_p += lenCommandWhitespace; 524 | 525 | // Expect a condition 526 | bool conditionPassed = test_condition(); 527 | 528 | // Push to the stack 529 | if (conditionPassed) { 530 | m_stack.push(Scope_Passing); 531 | } else { 532 | m_stack.push(Scope_Erasing); 533 | } 534 | } 535 | 536 | } else if (!strcmp(wordCommand, "else")) { 537 | // #else 538 | 539 | if (isErasing && isDeep) { 540 | // Just consume the line if we're deep 541 | consume_line(); 542 | 543 | } else if (m_stack.size() == 0) { 544 | // If the stack is empty, this is an invalid command 545 | CCPP_ERROR("Unexpected #else on line %d", (int)m_line); 546 | consume_line(); 547 | 548 | } else { 549 | // Get top of stack 550 | uint32_t &top = m_stack.top(); 551 | 552 | // Error out if we're already in an else directive 553 | if (top & Scope_Else) { 554 | CCPP_ERROR("Unexpected #else on line %d", (int)m_line); 555 | 556 | } else { 557 | if (top & Scope_Passing) { 558 | // If we're passing, set scope to erasing else 559 | top = Scope_Erasing | Scope_Else; 560 | 561 | } else if (top & Scope_Erasing) { 562 | // If we're erasing, set scope to passing else 563 | top = Scope_Passing | Scope_Else; 564 | } 565 | } 566 | 567 | // Expect end of line 568 | expect_eol(); 569 | } 570 | 571 | } else if (!strcmp(wordCommand, "elif")) { 572 | // #elif 573 | 574 | if (isErasing && isDeep) { 575 | // Just consume the line if we're deep 576 | consume_line(); 577 | 578 | } else if (m_stack.size() == 0) { 579 | // If the stack is empty, this is an invalid command 580 | CCPP_ERROR("Unexpected #elif on line %d", (int)m_line); 581 | consume_line(); 582 | 583 | } else { 584 | // Get top of stack 585 | uint32_t &top = m_stack.top(); 586 | 587 | // Error out if we're already in an else directive 588 | if (top & Scope_Else) { 589 | CCPP_ERROR("Unexpected #elif on line %d", (int)m_line); 590 | consume_line(); 591 | 592 | } else { 593 | if (top & Scope_Passing) { 594 | // If we're already passing, we'll erase anything below and set the deep flag to ignore the rest 595 | top = Scope_Erasing | Scope_ElseIf | Scope_Deep; 596 | consume_line(); 597 | 598 | } else { 599 | // Expect some whitespace 600 | size_t lenCommandWhitespace = lex_expect(m_p, m_pEnd, ELexType::Whitespace); 601 | if (lenCommandWhitespace == 0) { 602 | continue; 603 | } 604 | m_p += lenCommandWhitespace; 605 | 606 | // Expect a condition 607 | bool conditionPassed = test_condition(); 608 | 609 | // Update the scope 610 | if (conditionPassed) { 611 | top = Scope_Passing | Scope_ElseIf; 612 | } else { 613 | top = Scope_Erasing | Scope_ElseIf; 614 | } 615 | } 616 | } 617 | } 618 | 619 | } else if (!strcmp(wordCommand, "endif")) { 620 | // #endif 621 | 622 | if (m_stack.size() == 0) { 623 | // If the stack is empty, this is an invalid command 624 | CCPP_ERROR("Unexpected #endif on line %d", (int)m_line); 625 | consume_line(); 626 | 627 | } else { 628 | // Expect end of line 629 | expect_eol(); 630 | 631 | // Pop from stack 632 | m_stack.pop(); 633 | } 634 | 635 | } else if (!strcmp(wordCommand, "include")) { 636 | // #include 637 | 638 | if (isErasing) { 639 | // Just consume the line if we're erasing 640 | consume_line(); 641 | 642 | } else { 643 | if (m_includeCallback == nullptr) { 644 | // If no callback is set up, just consume the line 645 | CCPP_ERROR("No include callback set up for #include on line %d", (int)m_line); 646 | consume_line(); 647 | 648 | } else { 649 | // Expect some whitespace 650 | size_t lenCommandWhitespace = lex_expect(m_p, m_pEnd, ELexType::Whitespace); 651 | if (lenCommandWhitespace == 0) { 652 | continue; 653 | } 654 | m_p += lenCommandWhitespace; 655 | 656 | // Expect a string 657 | size_t lenPath = lex_expect(m_p, m_pEnd, ELexType::String); 658 | if (lenPath == 0) { 659 | continue; 660 | } 661 | 662 | char* path = (char*)alloca(lenPath); 663 | memcpy(path, m_p + 1, lenPath - 2); 664 | path[lenPath - 2] = '\0'; 665 | 666 | m_p += lenPath; 667 | 668 | // Run callback 669 | if (!m_includeCallback(path)) { 670 | CCPP_ERROR("Failed to include \"%s\" on line %d", path, (int)m_line); 671 | } 672 | 673 | // Expect end of line 674 | expect_eol(); 675 | } 676 | } 677 | 678 | } else { 679 | // Unknown command, it can be handled by the callback, or throw an error 680 | bool commandFound = false; 681 | int line = (int)m_line; 682 | 683 | char* commandValueStart = m_p; 684 | 685 | // Consume until end of line 686 | consume_line(); 687 | 688 | // Handle if not erasing 689 | if (!isErasing) { 690 | // See if there is a custom command callback 691 | if (m_commandCallback != nullptr) { 692 | ELexType typeCommandValue; 693 | size_t lenCommandValue = lex(commandValueStart, m_pEnd, typeCommandValue); 694 | 695 | if (typeCommandValue == ELexType::Whitespace) { 696 | // Handle potential whitespace 697 | commandValueStart += lenCommandValue; 698 | lenCommandValue = lex(commandValueStart, m_pEnd, typeCommandValue); 699 | } 700 | 701 | if (typeCommandValue == ELexType::Newline) { 702 | // If end of line, there's no command value 703 | commandFound = m_commandCallback(wordCommand, nullptr); 704 | 705 | } else { 706 | // If not end of line yet, there's some value 707 | char* commandValue = (char*)alloca(lenCommandValue + 1); 708 | memcpy(commandValue, commandValueStart, lenCommandValue); 709 | commandValue[lenCommandValue] = '\0'; 710 | 711 | commandFound = m_commandCallback(wordCommand, commandValue); 712 | } 713 | } 714 | 715 | if (!commandFound) { 716 | CCPP_ERROR("Unrecognized preprocessor command \"%s\" on line %d", wordCommand, line); 717 | } 718 | } 719 | } 720 | 721 | overwrite(commandStart, m_p - commandStart); 722 | } 723 | } 724 | 725 | // If there's something left in the stack, there are unclosed commands (missing #endif etc.) 726 | if (m_stack.size() > 0) { 727 | CCPP_ERROR("%d preprocessor scope(s) left unclosed at end of file (did you forget \"#endif\"?)", (int)m_stack.size()); 728 | } 729 | 730 | m_p = nullptr; 731 | m_pEnd = nullptr; 732 | } 733 | 734 | bool ccpp::processor::test_condition() 735 | { 736 | //TODO: 737 | // () 738 | 739 | std::vector conditions; 740 | 741 | uint32_t opFlag = 0; 742 | 743 | while (true) { 744 | ELexType type; 745 | char* symStart; 746 | size_t symLength; 747 | 748 | m_p += lex_next(m_p, m_pEnd, type, &symStart, &symLength); 749 | 750 | if (type == ELexType::Newline) { 751 | m_line++; 752 | m_column = 0; 753 | break; 754 | } 755 | 756 | uint32_t cond = 0; 757 | bool mustEqual = true; 758 | 759 | if (type == ELexType::Operator) { 760 | if (*symStart == '!') { 761 | mustEqual = false; 762 | 763 | } else if (symLength == 2 && !strncmp(symStart, "&&", symLength)) { 764 | opFlag = Match_OpAnd; 765 | continue; 766 | 767 | } else if (symLength == 2 && !strncmp(symStart, "||", symLength)) { 768 | opFlag = Match_OpOr; 769 | continue; 770 | 771 | } else { 772 | char* operatorText = (char*)alloca(symLength + 1); 773 | memcpy(operatorText, symStart, symLength); 774 | operatorText[symLength] = '\0'; 775 | CCPP_ERROR("Unexpected operator '%s' in condition on line %d", operatorText, (int)m_line); 776 | } 777 | m_p += lex_next(m_p, m_pEnd, type, &symStart, &symLength); 778 | } 779 | 780 | if (type != ELexType::Word) { 781 | CCPP_ERROR("Unexpected %s in condition on line %d", lex_type_name(type), (int)m_line); 782 | } else { 783 | std::string word(symStart, symLength); 784 | if (has_define(word.c_str()) == mustEqual) { 785 | cond = Match_Pass; 786 | } 787 | } 788 | 789 | conditions.emplace_back(cond | opFlag); 790 | } 791 | 792 | // Perform AND conditions first 793 | for (int i = (int)conditions.size() - 1; i >= 1; i--) { 794 | uint32_t &rhs = conditions[i]; 795 | uint32_t &lhs = conditions[i - 1]; 796 | 797 | if (rhs & Match_OpAnd) { 798 | if ((rhs & Match_Pass) && (lhs & Match_Pass)) { 799 | lhs |= Match_Pass; 800 | } else { 801 | lhs &= ~Match_Pass; 802 | } 803 | conditions.erase(conditions.begin() + i); 804 | } 805 | } 806 | 807 | // Perform OR conditions last 808 | for (int i = (int)conditions.size() - 1; i >= 1; i--) { 809 | uint32_t &rhs = conditions[i]; 810 | uint32_t &lhs = conditions[i - 1]; 811 | 812 | if (rhs & Match_OpOr) { 813 | if ((rhs & Match_Pass) || (lhs & Match_Pass)) { 814 | lhs |= Match_Pass; 815 | } else { 816 | lhs &= ~Match_Pass; 817 | } 818 | conditions.erase(conditions.begin() + i); 819 | } 820 | } 821 | 822 | // Now there should only be 1 condition left 823 | CCPP_ASSERT(conditions.size() == 1); 824 | if (conditions.size() != 1) { 825 | return false; 826 | } 827 | return (conditions[0] & Match_Pass); 828 | } 829 | 830 | void ccpp::processor::expect_eol() 831 | { 832 | // Consider the end of the string as end of line too 833 | if (m_p == m_pEnd) { 834 | return; 835 | } 836 | 837 | if (lex_expect(m_p, m_pEnd, ELexType::Newline) == 0) { 838 | return; 839 | } 840 | 841 | m_p++; 842 | 843 | m_line++; 844 | m_column = 0; 845 | } 846 | 847 | void ccpp::processor::consume_line() 848 | { 849 | ELexType type = ELexType::None; 850 | while (type != ELexType::Newline) { 851 | m_p += lex(m_p, m_pEnd, type); 852 | } 853 | 854 | m_line++; 855 | m_column = 0; 856 | } 857 | 858 | void ccpp::processor::overwrite(char* p, size_t len) 859 | { 860 | char* pEnd = p + len; 861 | for (; p < pEnd; p++) { 862 | if (*p != '\r' && *p != '\n') { 863 | *p = ' '; 864 | } 865 | } 866 | } 867 | 868 | #endif 869 | --------------------------------------------------------------------------------