| 323 | | free(found__ATTR__); found__ATTR__ = 0; |
| 324 | | } |
| | 341 | free(found__ATTR__); found__ATTR__ = 0; |
| | 342 | } |
| | 343 | |
| | 344 | const char *nextNonSpace(const char* ptr) { |
| | 345 | while (isspace(*ptr)) ++ptr; |
| | 346 | return ptr; |
| | 347 | } |
| | 348 | const char *nextNonWord(const char* ptr) { |
| | 349 | while (isalnum(*ptr) || *ptr == '_') ++ptr; |
| | 350 | return ptr; |
| | 351 | } |
| | 352 | |
| | 353 | inline const char *matchingParen(const char *from) { |
| | 354 | int open = 1; |
| | 355 | int i; |
| | 356 | for (i = 1; from[i] && open>0; ++i) { |
| | 357 | switch (from[i]) { |
| | 358 | case '(': open++; break; |
| | 359 | case ')': open--; break; |
| | 360 | } |
| | 361 | } |
| | 362 | if (open) return NULL; // no matching closing paren |
| | 363 | return from+i-1; |
| | 364 | } |
| | 365 | |
| | 366 | // ----------------- |
| | 367 | // LinePart |
| | 368 | |
| | 369 | class LinePart { |
| | 370 | const char *line; |
| | 371 | size_t linelen; // of line |
| | 372 | |
| | 373 | size_t pos; |
| | 374 | size_t size; |
| | 375 | |
| | 376 | bool part_is_legal() { return (pos <= linelen) && ((pos+size) <= linelen); } |
| | 377 | |
| | 378 | void set(size_t newPos, size_t newSize) { |
| | 379 | pos = newPos; |
| | 380 | size = newSize; |
| | 381 | mp_assert(part_is_legal()); |
| | 382 | } |
| | 383 | |
| | 384 | public: |
| | 385 | |
| | 386 | LinePart(const char *wholeLine, size_t len = -1U) |
| | 387 | : line(wholeLine), |
| | 388 | linelen(len != -1U ? len : strlen(line)) |
| | 389 | { |
| | 390 | mp_assert(line[linelen] == 0); |
| | 391 | undefine(); |
| | 392 | } |
| | 393 | |
| | 394 | size_t get_size() const { return size; } |
| | 395 | bool is_empty() const { return !size; } |
| | 396 | |
| | 397 | const char *whole_line() const { return line; } |
| | 398 | |
| | 399 | void define(const char *start, const char *end) { set(start-line, end-start+1); } |
| | 400 | void undefine() { set(0, 0); } |
| | 401 | |
| | 402 | void copyTo(char *buffer) const { |
| | 403 | mp_assert(!is_empty()); |
| | 404 | memcpy(buffer, line+pos, size); |
| | 405 | buffer[size] = 0; |
| | 406 | } |
| | 407 | |
| | 408 | LinePart behind() const { |
| | 409 | mp_assert(!is_empty()); |
| | 410 | size_t behind_offset = pos+size; |
| | 411 | mp_assert(linelen >= behind_offset); |
| | 412 | return LinePart(line+behind_offset, linelen-behind_offset); |
| | 413 | } |
| | 414 | |
| | 415 | void error(const char *format) { |
| | 416 | // 'format' has to contain one '%s' |
| | 417 | mp_assert(!is_empty()); |
| | 418 | char part[get_size()+1]; |
| | 419 | copyTo(part); |
| | 420 | errorf(format, part); |
| | 421 | undefine(); |
| | 422 | } |
| | 423 | }; |
| | 424 | |
| | 425 | |
| | 426 | // ------------------ |
| | 427 | // PartQueue |
| | 428 | |
| | 429 | class PartQueue { |
| | 430 | LinePart first; |
| | 431 | PartQueue *next; |
| | 432 | size_t size; |
| | 433 | |
| | 434 | bool is_empty() const { return !size; } |
| | 435 | size_t get_size() const { return size; } |
| | 436 | |
| | 437 | void copyPartsTo(char *buffer) const { |
| | 438 | mp_assert(!first.is_empty()); |
| | 439 | first.copyTo(buffer); |
| | 440 | buffer += first.get_size(); |
| | 441 | if (next) { |
| | 442 | *buffer++ = ' '; |
| | 443 | next->copyPartsTo(buffer); |
| | 444 | } |
| | 445 | } |
| | 446 | |
| | 447 | public: |
| | 448 | PartQueue(LinePart first_) |
| | 449 | : first(first_), |
| | 450 | next(0), |
| | 451 | size(first.get_size()) |
| | 452 | {} |
| | 453 | ~PartQueue() { delete next; } |
| | 454 | |
| | 455 | void append(PartQueue *mp) { |
| | 456 | mp_assert(!next); |
| | 457 | next = mp; |
| | 458 | size += 1+mp->get_size(); // add pos for space |
| | 459 | } |
| | 460 | |
| | 461 | char *to_string() { |
| | 462 | char *result = (char *)malloc(get_size()+1); |
| | 463 | copyPartsTo(result); |
| | 464 | mp_assert(get_size() == strlen(result)); |
| | 465 | return result; |
| | 466 | } |
| | 467 | }; |
| | 468 | |
| | 469 | // ------------------------ |
| | 470 | // AttributeParser |
| | 471 | |
| | 472 | class AttributeParser { |
| | 473 | const char *attrName; |
| | 474 | size_t attrNameLen; |
| | 475 | bool expandName; // try to expand 'attrName' |
| | 476 | bool expectParens; // otherwise parens are optional |
| | 477 | |
| | 478 | public: |
| | 479 | AttributeParser(const char *attrName_, bool expandName_, bool expectParens_) |
| | 480 | : attrName(attrName_), |
| | 481 | attrNameLen(strlen(attrName)), |
| | 482 | expandName(expandName_), |
| | 483 | expectParens(expectParens_) |
| | 484 | {} |
| | 485 | |
| | 486 | private: |
| | 487 | void parse_one_attr(LinePart& part) const { |
| | 488 | const char *found = strstr(part.whole_line(), attrName); |
| | 489 | if (found) { |
| | 490 | const char *behind = found+attrNameLen; |
| | 491 | if (expandName) behind = nextNonWord(behind); |
| | 492 | const char *openParen = nextNonSpace(behind); |
| | 493 | |
| | 494 | if (*openParen == '(') { |
| | 495 | const char *closeParen = matchingParen(openParen); |
| | 496 | if (closeParen) part.define(found, closeParen); |
| | 497 | else { |
| | 498 | part.define(found, openParen); |
| | 499 | part.error("Could not find matching paren after '%s'"); |
| | 500 | } |
| | 501 | } |
| | 502 | else { |
| | 503 | part.define(found, behind-1); |
| | 504 | if (expectParens) part.error("Expected to see '(' after '%s'"); |
| | 505 | } |
| | 506 | } |
| | 507 | } |
| | 508 | |
| | 509 | // rest is not really part of this class - may go to abstract base class if needed |
| | 510 | |
| | 511 | PartQueue *parse_all(LinePart from) const { |
| | 512 | parse_one_attr(from); |
| | 513 | if (from.is_empty()) return NULL; |
| | 514 | |
| | 515 | PartQueue *head = new PartQueue(from); |
| | 516 | PartQueue *rest = parse_all(from.behind()); |
| | 517 | if (rest) head->append(rest); |
| | 518 | |
| | 519 | return head; |
| | 520 | } |
| | 521 | |
| | 522 | public: |
| | 523 | char *parse(const char *toParse, size_t toParseSize) const { |
| | 524 | char *result = NULL; |
| | 525 | PartQueue *found_attributes = parse_all(LinePart(toParse, toParseSize)); |
| | 526 | |
| | 527 | if (found_attributes) { |
| | 528 | result = found_attributes->to_string(); |
| | 529 | delete found_attributes; |
| | 530 | } |
| | 531 | return result; |
| | 532 | } |
| | 533 | }; |
| | 534 | |
| | 535 | static AttributeParser attribute_parser("__attribute__", false, true); |
| | 536 | static AttributeParser ATTR_parser("__ATTR__", true, false); |
| 333 | | att = strstr(last_comment, "__attribute__"); |
| 334 | | if (att != 0) { |
| 335 | | char *a = att+13; |
| 336 | | int parens = 1; |
| 337 | | |
| 338 | | while (*a && *a != '(') ++a; // search '(' |
| 339 | | if (*a++ == '(') { // if '(' found |
| 340 | | while (parens && *a) { |
| 341 | | switch (*a++) { |
| 342 | | case '(': parens++; break; |
| 343 | | case ')': parens--; break; |
| 344 | | } |
| 345 | | } |
| 346 | | *a = 0; |
| 347 | | DEBUG_PRINT("__attribute__ found!\n"); |
| 348 | | found__attribute__ = strdup(att); |
| 349 | | if (search__ATTR__) { error("found '__attribute__' but expected '__ATTR__..'"); } |
| 350 | | } |
| 351 | | } |
| 352 | | |
| 353 | | att = strstr(last_comment, "__ATTR__"); |
| 354 | | if (att != 0) { |
| 355 | | char *a = att+8; |
| 356 | | |
| 357 | | while (*a && (isalnum(*a) || *a == '_')) ++a; // goto end of name |
| 358 | | |
| 359 | | if (*a == '(') { |
| 360 | | int parens = 1; |
| 361 | | a++; |
| 362 | | |
| 363 | | while (parens && *a) { |
| 364 | | switch (*a++) { |
| 365 | | case '(': parens++; break; |
| 366 | | case ')': parens--; break; |
| 367 | | } |
| 368 | | } |
| 369 | | *a = 0; |
| 370 | | DEBUG_PRINT("__ATTR__ with parameters found!\n"); |
| 371 | | found__ATTR__ = strdup(att); |
| 372 | | } |
| 373 | | else { |
| 374 | | *a = 0; |
| 375 | | DEBUG_PRINT("__ATTR__ w/o parameters found!\n"); |
| 376 | | found__ATTR__ = strdup(att); |
| 377 | | } |
| 378 | | if (search__attribute__) { error("found '__ATTR__..' but expected '__attribute__'"); } |
| | 542 | char *seen_attribute = attribute_parser.parse(last_comment, lc_size); |
| | 543 | char *seen_ATTR = ATTR_parser.parse(last_comment, lc_size); |
| | 544 | |
| | 545 | if (search__attribute__) { |
| | 546 | mp_assert(!search__ATTR__); |
| | 547 | found__attribute__ = seen_attribute; |
| | 548 | if (seen_ATTR) { |
| | 549 | error("Expected not to see '__ATTR__..' (when looking for '__attribute__(...)')"); |
| | 550 | } |
| | 551 | } |
| | 552 | |
| | 553 | if (search__ATTR__) { |
| | 554 | mp_assert(!search__attribute__); |
| | 555 | found__ATTR__ = seen_ATTR; |
| | 556 | if (seen_attribute) { |
| | 557 | error("Expected not to see '__attribute__(...)' (when looking for '__ATTR__..')"); |
| | 558 | } |
| | 1712 | |
| | 1713 | // -------------------------------------------------------------------------------- |
| | 1714 | |
| | 1715 | #if (UNIT_TESTS == 1) |
| | 1716 | |
| | 1717 | #include <test_unit.h> |
| | 1718 | |
| | 1719 | inline char*& searchResult(bool ATTR) { return ATTR ? found__ATTR__ : found__attribute__; } |
| | 1720 | inline int& doSearchFor(bool ATTR) { return ATTR ? search__ATTR__ : search__attribute__; } |
| | 1721 | |
| | 1722 | inline const char *test_extract(bool ATTR, const char *str) { |
| | 1723 | doSearchFor(ATTR) = true; |
| | 1724 | doSearchFor(!ATTR) = false; |
| | 1725 | |
| | 1726 | clear_found_attribute(); |
| | 1727 | |
| | 1728 | strcpy(last_comment, str); |
| | 1729 | lc_size = strlen(last_comment); |
| | 1730 | |
| | 1731 | search_comment_for_attribute(); |
| | 1732 | |
| | 1733 | TEST_ASSERT(!searchResult(!ATTR)); |
| | 1734 | return searchResult(ATTR); |
| | 1735 | } |
| | 1736 | |
| | 1737 | #define TEST_ATTR_____(comment,extracted) TEST_ASSERT_EQUAL(test_extract(true, comment), extracted) |
| | 1738 | #define TEST_attribute(comment,extracted) TEST_ASSERT_EQUAL(test_extract(false, comment), extracted) |
| | 1739 | |
| | 1740 | #define TEST_both(c,e) do { TEST_attribute(c,e); TEST_ATTR_____(c,e); } while(0) |
| | 1741 | |
| | 1742 | void TEST_attribute_parser() { |
| | 1743 | TEST_both("", NULL); |
| | 1744 | TEST_both("nothing here", NULL); |
| | 1745 | |
| | 1746 | TEST_attribute("bla bla __attribute__(whatever) more content", "__attribute__(whatever)"); |
| | 1747 | TEST_ATTR_____("bla bla __ATTR__DEPRECATED more content", "__ATTR__DEPRECATED"); |
| | 1748 | TEST_ATTR_____("bla bla __ATTR__FORMAT(pos) more content", "__ATTR__FORMAT(pos)"); |
| | 1749 | |
| | 1750 | TEST_ATTR_____("__ATTR__DEPRECATED", "__ATTR__DEPRECATED"); |
| | 1751 | TEST_ATTR_____("__ATTR__FORMAT(pos)", "__ATTR__FORMAT(pos)"); |
| | 1752 | TEST_ATTR_____("bla __ATTR__FORMAT(pos)", "__ATTR__FORMAT(pos)"); |
| | 1753 | TEST_ATTR_____("__ATTR__FORMAT(pos) bla", "__ATTR__FORMAT(pos)"); |
| | 1754 | TEST_ATTR_____(" __ATTR__FORMAT(pos) ", "__ATTR__FORMAT(pos)"); |
| | 1755 | |
| | 1756 | TEST_ATTR_____("__ATTR__FORMAT((pos)", NULL); |
| | 1757 | TEST_ATTR_____("__attribute__(pos", NULL); |
| | 1758 | TEST_ATTR_____("__ATTR__FORMAT(pos))", "__ATTR__FORMAT(pos)"); |
| | 1759 | TEST_ATTR_____("__ATTR__FORMAT((pos))", "__ATTR__FORMAT((pos))"); |
| | 1760 | TEST_ATTR_____("__ATTR__FORMAT((pos)+((sop)))", "__ATTR__FORMAT((pos)+((sop)))"); |
| | 1761 | TEST_ATTR_____("__ATTR__FORMAT(((pos)))+(sop))", "__ATTR__FORMAT(((pos)))"); |
| | 1762 | |
| | 1763 | TEST_ATTR_____("bla bla __ATTR__DEPRECATED __ATTR__FORMAT(pos) more content", "__ATTR__DEPRECATED __ATTR__FORMAT(pos)"); |
| | 1764 | TEST_ATTR_____("bla __ATTR__DEPRECATED bla more__ATTR__FORMAT(pos)content", "__ATTR__DEPRECATED __ATTR__FORMAT(pos)"); |
| | 1765 | |
| | 1766 | TEST_ATTR_____(" goes to header: __ATTR__NORETURN */", "__ATTR__NORETURN"); |
| | 1767 | |
| | 1768 | clear_found_attribute(); |
| | 1769 | } |
| | 1770 | |
| | 1771 | #endif |