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