| 644 return true; |
644 return true; |
| 645 } |
645 } |
| 646 |
646 |
| 647 // ============================================================================= |
647 // ============================================================================= |
| 648 // ----------------------------------------------------------------------------- |
648 // ----------------------------------------------------------------------------- |
| 649 #define CHECK_TOKEN_COUNT(N) \ |
649 class LDParseError : public std::exception |
| 650 if (tokens.size() != N) \ |
650 { PROPERTY (private, str, Error, STR_OPS, STOCK_WRITE) |
| 651 return new LDError (line, "Bad amount of tokens"); |
651 PROPERTY (private, str, Line, STR_OPS, STOCK_WRITE) |
| 652 |
652 |
| 653 #define CHECK_TOKEN_NUMBERS(MIN, MAX) \ |
653 public: |
| 654 for (int i = MIN; i <= MAX; ++i) \ |
654 LDParseError (str line, str a) : m_Error (a), m_Line (line) {} |
| 655 if (!numeric (tokens[i])) \ |
655 |
| 656 return new LDError (line, fmt ("Token #%1 was `%2`, expected a number", (i + 1), tokens[i])); |
656 const char* what() const throw() |
| |
657 { return getError().toLocal8Bit().constData(); |
| |
658 } |
| |
659 }; |
| |
660 |
| |
661 // ============================================================================= |
| |
662 // ----------------------------------------------------------------------------- |
| |
663 void checkTokenCount (str line, const QStringList& tokens, int num) |
| |
664 { if (tokens.size() != num) |
| |
665 throw LDParseError (line, fmt ("Bad amount of tokens, expected %1, got %2", num, tokens.size())); |
| |
666 } |
| |
667 |
| |
668 // ============================================================================= |
| |
669 // ----------------------------------------------------------------------------- |
| |
670 void checkTokenNumbers (str line, const QStringList& tokens, int min, int max) |
| |
671 { bool ok; |
| |
672 |
| |
673 // Check scientific notation, e.g. 7.99361e-15 |
| |
674 QRegExp scient ("\\-?[0-9]+\\.[0-9]+e\\-[0-9]+"); |
| |
675 |
| |
676 for (int i = min; i <= max; ++i) |
| |
677 { tokens[i].toDouble (&ok); |
| |
678 |
| |
679 if (!ok && !scient.exactMatch (tokens[i])) |
| |
680 throw LDParseError (line, fmt ("Token #%1 was `%2`, expected a number (matched length: %3)", (i + 1), tokens[i], scient.matchedLength())); |
| |
681 } |
| |
682 } |
| 657 |
683 |
| 658 // ============================================================================= |
684 // ============================================================================= |
| 659 // ----------------------------------------------------------------------------- |
685 // ----------------------------------------------------------------------------- |
| 660 static vertex parseVertex (QStringList& s, const int n) |
686 static vertex parseVertex (QStringList& s, const int n) |
| 661 { vertex v; |
687 { vertex v; |
| 670 // This is the LDraw code parser function. It takes in a string containing LDraw |
696 // This is the LDraw code parser function. It takes in a string containing LDraw |
| 671 // code and returns the object parsed from it. parseLine never returns null, |
697 // code and returns the object parsed from it. parseLine never returns null, |
| 672 // the object will be LDError if it could not be parsed properly. |
698 // the object will be LDError if it could not be parsed properly. |
| 673 // ----------------------------------------------------------------------------- |
699 // ----------------------------------------------------------------------------- |
| 674 LDObject* parseLine (str line) |
700 LDObject* parseLine (str line) |
| 675 { QStringList tokens = line.split (" ", str::SkipEmptyParts); |
701 { try |
| 676 |
702 { QStringList tokens = line.split (" ", str::SkipEmptyParts); |
| 677 if (tokens.size() <= 0) |
703 |
| 678 { // Line was empty, or only consisted of whitespace |
704 if (tokens.size() <= 0) |
| 679 return new LDEmpty; |
705 { // Line was empty, or only consisted of whitespace |
| 680 } |
706 return new LDEmpty; |
| 681 |
707 } |
| 682 if (tokens[0].length() != 1 || tokens[0][0].isDigit() == false) |
708 |
| 683 return new LDError (line, "Illogical line code"); |
709 if (tokens[0].length() != 1 || tokens[0][0].isDigit() == false) |
| 684 |
710 throw LDParseError (line, "Illogical line code"); |
| 685 int num = tokens[0][0].digitValue(); |
711 |
| 686 |
712 int num = tokens[0][0].digitValue(); |
| 687 switch (num) |
713 |
| 688 { case 0: |
714 switch (num) |
| 689 { // Comment |
715 { case 0: |
| 690 str comm = line.mid (line.indexOf ("0") + 1).simplified(); |
716 { // Comment |
| 691 |
717 str comm = line.mid (line.indexOf ("0") + 1).simplified(); |
| 692 // Handle BFC statements |
718 |
| 693 if (tokens.size() > 2 && tokens[1] == "BFC") |
719 // Handle BFC statements |
| 694 { for (int i = 0; i < LDBFC::NumStatements; ++i) |
720 if (tokens.size() > 2 && tokens[1] == "BFC") |
| 695 if (comm == fmt ("BFC %1", LDBFC::statements [i])) |
721 { for (int i = 0; i < LDBFC::NumStatements; ++i) |
| 696 return new LDBFC ( (LDBFC::Type) i); |
722 if (comm == fmt ("BFC %1", LDBFC::statements [i])) |
| 697 |
723 return new LDBFC ( (LDBFC::Type) i); |
| 698 // MLCAD is notorious for stuffing these statements in parts it |
724 |
| 699 // creates. The above block only handles valid statements, so we |
725 // MLCAD is notorious for stuffing these statements in parts it |
| 700 // need to handle MLCAD-style invertnext, clip and noclip separately. |
726 // creates. The above block only handles valid statements, so we |
| 701 struct |
727 // need to handle MLCAD-style invertnext, clip and noclip separately. |
| 702 { str a; |
728 struct |
| 703 LDBFC::Type b; |
729 { str a; |
| 704 } BFCData[] = |
730 LDBFC::Type b; |
| 705 { { "INVERTNEXT", LDBFC::InvertNext }, |
731 } BFCData[] = |
| 706 { "NOCLIP", LDBFC::NoClip }, |
732 { { "INVERTNEXT", LDBFC::InvertNext }, |
| 707 { "CLIP", LDBFC::Clip } |
733 { "NOCLIP", LDBFC::NoClip }, |
| 708 }; |
734 { "CLIP", LDBFC::Clip } |
| 709 |
735 }; |
| 710 for (const auto& i : BFCData) |
736 |
| 711 if (comm == "BFC CERTIFY " + i.a) |
737 for (const auto& i : BFCData) |
| 712 return new LDBFC (i.b); |
738 if (comm == "BFC CERTIFY " + i.a) |
| |
739 return new LDBFC (i.b); |
| |
740 } |
| |
741 |
| |
742 if (tokens.size() > 2 && tokens[1] == "!LDFORGE") |
| |
743 { // Handle LDForge-specific types, they're embedded into comments too |
| |
744 if (tokens[2] == "VERTEX") |
| |
745 { // Vertex (0 !LDFORGE VERTEX) |
| |
746 checkTokenCount (line, tokens, 7); |
| |
747 checkTokenNumbers (line, tokens, 3, 6); |
| |
748 |
| |
749 LDVertex* obj = new LDVertex; |
| |
750 obj->setColor (tokens[3].toLong()); |
| |
751 |
| |
752 for_axes (ax) |
| |
753 obj->pos[ax] = tokens[4 + ax].toDouble(); // 4 - 6 |
| |
754 |
| |
755 return obj; |
| |
756 } elif (tokens[2] == "OVERLAY") |
| |
757 { checkTokenCount (line, tokens, 9);; |
| |
758 checkTokenNumbers (line, tokens, 5, 8); |
| |
759 |
| |
760 LDOverlay* obj = new LDOverlay; |
| |
761 obj->setFileName (tokens[3]); |
| |
762 obj->setCamera (tokens[4].toLong()); |
| |
763 obj->setX (tokens[5].toLong()); |
| |
764 obj->setY (tokens[6].toLong()); |
| |
765 obj->setWidth (tokens[7].toLong()); |
| |
766 obj->setHeight (tokens[8].toLong()); |
| |
767 return obj; |
| |
768 } |
| |
769 } |
| |
770 |
| |
771 // Just a regular comment: |
| |
772 LDComment* obj = new LDComment; |
| |
773 obj->text = comm; |
| |
774 return obj; |
| 713 } |
775 } |
| 714 |
776 |
| 715 if (tokens.size() > 2 && tokens[1] == "!LDFORGE") |
777 case 1: |
| 716 { // Handle LDForge-specific types, they're embedded into comments too |
778 { // Subfile |
| 717 if (tokens[2] == "VERTEX") |
779 checkTokenCount (line, tokens, 15); |
| 718 { // Vertex (0 !LDFORGE VERTEX) |
780 checkTokenNumbers (line, tokens, 1, 13); |
| 719 CHECK_TOKEN_COUNT (7) |
781 |
| 720 CHECK_TOKEN_NUMBERS (3, 6) |
782 // Try open the file. Disable g_loadingMainFile temporarily since we're |
| 721 |
783 // not loading the main file now, but the subfile in question. |
| 722 LDVertex* obj = new LDVertex; |
784 bool tmp = g_loadingMainFile; |
| 723 obj->setColor (tokens[3].toLong()); |
785 g_loadingMainFile = false; |
| 724 |
786 LDDocument* load = getDocument (tokens[14]); |
| 725 for_axes (ax) |
787 g_loadingMainFile = tmp; |
| 726 obj->pos[ax] = tokens[4 + ax].toDouble(); // 4 - 6 |
788 |
| 727 |
789 // If we cannot open the file, mark it an error. Note we cannot use LDParseError |
| 728 return obj; |
790 // here because the error object needs the document reference. |
| 729 } elif (tokens[2] == "OVERLAY") |
791 if (!load) |
| 730 { CHECK_TOKEN_COUNT (9); |
792 { LDError* obj = new LDError (line, fmt ("Could not open %1", tokens[14])); |
| 731 CHECK_TOKEN_NUMBERS (5, 8) |
793 obj->setFileReferenced (tokens[14]); |
| 732 |
|
| 733 LDOverlay* obj = new LDOverlay; |
|
| 734 obj->setFileName (tokens[3]); |
|
| 735 obj->setCamera (tokens[4].toLong()); |
|
| 736 obj->setX (tokens[5].toLong()); |
|
| 737 obj->setY (tokens[6].toLong()); |
|
| 738 obj->setWidth (tokens[7].toLong()); |
|
| 739 obj->setHeight (tokens[8].toLong()); |
|
| 740 return obj; |
794 return obj; |
| 741 } |
795 } |
| 742 } |
796 |
| 743 |
797 LDSubfile* obj = new LDSubfile; |
| 744 // Just a regular comment: |
798 obj->setColor (tokens[1].toLong()); |
| 745 LDComment* obj = new LDComment; |
799 obj->setPosition (parseVertex (tokens, 2)); // 2 - 4 |
| 746 obj->text = comm; |
800 |
| 747 return obj; |
801 matrix transform; |
| 748 } |
802 |
| 749 |
803 for (int i = 0; i < 9; ++i) |
| 750 case 1: |
804 transform[i] = tokens[i + 5].toDouble(); // 5 - 13 |
| 751 { // Subfile |
805 |
| 752 CHECK_TOKEN_COUNT (15) |
806 obj->setTransform (transform); |
| 753 CHECK_TOKEN_NUMBERS (1, 13) |
807 obj->setFileInfo (load); |
| 754 |
|
| 755 // Try open the file. Disable g_loadingMainFile temporarily since we're |
|
| 756 // not loading the main file now, but the subfile in question. |
|
| 757 bool tmp = g_loadingMainFile; |
|
| 758 g_loadingMainFile = false; |
|
| 759 LDDocument* load = getDocument (tokens[14]); |
|
| 760 g_loadingMainFile = tmp; |
|
| 761 |
|
| 762 // If we cannot open the file, mark it an error |
|
| 763 if (!load) |
|
| 764 { LDError* obj = new LDError (line, fmt ("Could not open %1", tokens[14])); |
|
| 765 obj->setFileReferenced (tokens[14]); |
|
| 766 return obj; |
808 return obj; |
| 767 } |
809 } |
| 768 |
810 |
| 769 LDSubfile* obj = new LDSubfile; |
811 case 2: |
| 770 obj->setColor (tokens[1].toLong()); |
812 { checkTokenCount (line, tokens, 8); |
| 771 obj->setPosition (parseVertex (tokens, 2)); // 2 - 4 |
813 checkTokenNumbers (line, tokens, 1, 7); |
| 772 |
814 |
| 773 matrix transform; |
815 // Line |
| 774 |
816 LDLine* obj = new LDLine; |
| 775 for (int i = 0; i < 9; ++i) |
817 obj->setColor (tokens[1].toLong()); |
| 776 transform[i] = tokens[i + 5].toDouble(); // 5 - 13 |
818 |
| 777 |
819 for (int i = 0; i < 2; ++i) |
| 778 obj->setTransform (transform); |
820 obj->setVertex (i, parseVertex (tokens, 2 + (i * 3))); // 2 - 7 |
| 779 obj->setFileInfo (load); |
821 |
| 780 return obj; |
822 return obj; |
| 781 } |
823 } |
| 782 |
824 |
| 783 case 2: |
825 case 3: |
| 784 { CHECK_TOKEN_COUNT (8) |
826 { checkTokenCount (line, tokens, 11); |
| 785 CHECK_TOKEN_NUMBERS (1, 7) |
827 checkTokenNumbers (line, tokens, 1, 10); |
| 786 |
828 |
| 787 // Line |
829 // Triangle |
| 788 LDLine* obj = new LDLine; |
830 LDTriangle* obj = new LDTriangle; |
| 789 obj->setColor (tokens[1].toLong()); |
831 obj->setColor (tokens[1].toLong()); |
| 790 |
832 |
| 791 for (int i = 0; i < 2; ++i) |
833 for (int i = 0; i < 3; ++i) |
| 792 obj->setVertex (i, parseVertex (tokens, 2 + (i * 3))); // 2 - 7 |
834 obj->setVertex (i, parseVertex (tokens, 2 + (i * 3))); // 2 - 10 |
| 793 |
835 |
| 794 return obj; |
836 return obj; |
| 795 } |
837 } |
| 796 |
838 |
| 797 case 3: |
839 case 4: |
| 798 { CHECK_TOKEN_COUNT (11) |
840 case 5: |
| 799 CHECK_TOKEN_NUMBERS (1, 10) |
841 { checkTokenCount (line, tokens, 14); |
| 800 |
842 checkTokenNumbers (line, tokens, 1, 13); |
| 801 // Triangle |
843 |
| 802 LDTriangle* obj = new LDTriangle; |
844 // Quadrilateral / Conditional line |
| 803 obj->setColor (tokens[1].toLong()); |
845 LDObject* obj; |
| 804 |
846 |
| 805 for (int i = 0; i < 3; ++i) |
847 if (num == 4) |
| 806 obj->setVertex (i, parseVertex (tokens, 2 + (i * 3))); // 2 - 10 |
848 obj = new LDQuad; |
| 807 |
849 else |
| 808 return obj; |
850 obj = new LDCondLine; |
| 809 } |
851 |
| 810 |
852 obj->setColor (tokens[1].toLong()); |
| 811 case 4: |
853 |
| 812 case 5: |
854 for (int i = 0; i < 4; ++i) |
| 813 { CHECK_TOKEN_COUNT (14) |
855 obj->setVertex (i, parseVertex (tokens, 2 + (i * 3))); // 2 - 13 |
| 814 CHECK_TOKEN_NUMBERS (1, 13) |
856 |
| 815 |
857 return obj; |
| 816 // Quadrilateral / Conditional line |
858 } |
| 817 LDObject* obj; |
859 |
| 818 |
860 default: // Strange line we couldn't parse |
| 819 if (num == 4) |
861 throw LDError (line, "Unknown line code number"); |
| 820 obj = new LDQuad; |
862 } |
| 821 else |
863 } |
| 822 obj = new LDCondLine; |
864 catch (LDParseError& e) |
| 823 |
865 { return new LDError (e.getLine(), e.getError()); |
| 824 obj->setColor (tokens[1].toLong()); |
|
| 825 |
|
| 826 for (int i = 0; i < 4; ++i) |
|
| 827 obj->setVertex (i, parseVertex (tokens, 2 + (i * 3))); // 2 - 13 |
|
| 828 |
|
| 829 return obj; |
|
| 830 } |
|
| 831 |
|
| 832 default: // Strange line we couldn't parse |
|
| 833 return new LDError (line, "Unknown line code number"); |
|
| 834 } |
866 } |
| 835 } |
867 } |
| 836 |
868 |
| 837 // ============================================================================= |
869 // ============================================================================= |
| 838 // ----------------------------------------------------------------------------- |
870 // ----------------------------------------------------------------------------- |