diff options
-rw-r--r-- | src/unstable/json.cpp | 208 | ||||
-rw-r--r-- | src/unstable/json.h | 39 |
2 files changed, 151 insertions, 96 deletions
diff --git a/src/unstable/json.cpp b/src/unstable/json.cpp index f6a8d52..9745a7f 100644 --- a/src/unstable/json.cpp +++ b/src/unstable/json.cpp | |||
@@ -1,12 +1,13 @@ | |||
1 | #include "bu/json.h" | 1 | #include "bu/json.h" |
2 | #include "bu/staticmembuf.h" | 2 | #include "bu/staticmembuf.h" |
3 | #include "bu/membuf.h" | 3 | #include "bu/membuf.h" |
4 | #include "bu/exceptionparse.h" | ||
4 | 5 | ||
5 | #include "bu/sio.h" | 6 | #include "bu/sio.h" |
6 | 7 | ||
7 | #include <stdlib.h> | 8 | #include <stdlib.h> |
8 | 9 | ||
9 | #define next( txt ) readChar( c, sInput, "Unexpected end of stream while reading " txt "." ) | 10 | #define next( txt ) readChar( ps, "Unexpected end of stream while reading " txt "." ) |
10 | 11 | ||
11 | Bu::Json::Json() : | 12 | Bu::Json::Json() : |
12 | eType( Null ) | 13 | eType( Null ) |
@@ -75,10 +76,10 @@ Bu::Json::Json( Bu::Stream &sInput ) : | |||
75 | parse( sInput ); | 76 | parse( sInput ); |
76 | } | 77 | } |
77 | 78 | ||
78 | Bu::Json::Json( Bu::UtfChar &c, Bu::Stream &sInput ) : | 79 | Bu::Json::Json( Bu::Json::ParseState &ps ) : |
79 | eType( Invalid ) | 80 | eType( Invalid ) |
80 | { | 81 | { |
81 | parse( c, sInput ); | 82 | parse( ps ); |
82 | } | 83 | } |
83 | 84 | ||
84 | Bu::Json::Json( const Json &rSrc ) : | 85 | Bu::Json::Json( const Json &rSrc ) : |
@@ -294,10 +295,10 @@ void Bu::Json::parse( Bu::Stream &sInput ) | |||
294 | { | 295 | { |
295 | reset(); | 296 | reset(); |
296 | 297 | ||
297 | Bu::UtfChar c; | 298 | ParseState ps( sInput ); |
298 | next("json"); | 299 | next("json"); |
299 | 300 | ||
300 | parse( c, sInput ); | 301 | parse( ps ); |
301 | } | 302 | } |
302 | 303 | ||
303 | void Bu::Json::parse( const Bu::String &sInput ) | 304 | void Bu::Json::parse( const Bu::String &sInput ) |
@@ -306,40 +307,42 @@ void Bu::Json::parse( const Bu::String &sInput ) | |||
306 | parse( mb ); | 307 | parse( mb ); |
307 | } | 308 | } |
308 | 309 | ||
309 | void Bu::Json::parse( Bu::UtfChar &c, Bu::Stream &sInput ) | 310 | void Bu::Json::parse( ParseState &ps ) |
310 | { | 311 | { |
311 | while( c == ' ' || c == '\t' || c == '\r' || c == '\n' ) | 312 | while( ps.c == ' ' || ps.c == '\t' || ps.c == '\r' || ps.c == '\n' ) |
312 | { | 313 | { |
313 | next( "json" ); | 314 | next( "json" ); |
314 | } | 315 | } |
315 | if( c == '"' ) | 316 | if( ps.c == '"' ) |
316 | { | 317 | { |
317 | // String | 318 | // String |
318 | parseString( c, sInput ); | 319 | parseString( ps ); |
319 | } | 320 | } |
320 | else if( c == '{' ) | 321 | else if( ps.c == '{' ) |
321 | { | 322 | { |
322 | // Object | 323 | // Object |
323 | parseObject( c, sInput ); | 324 | parseObject( ps ); |
324 | } | 325 | } |
325 | else if( c == '[' ) | 326 | else if( ps.c == '[' ) |
326 | { | 327 | { |
327 | // Array | 328 | // Array |
328 | parseArray( c, sInput ); | 329 | parseArray( ps ); |
329 | } | 330 | } |
330 | else if( c == '-' || (c >= '0' && c <= '9') ) | 331 | else if( ps.c == '-' || (ps.c >= '0' && ps.c <= '9') ) |
331 | { | 332 | { |
332 | // Number -- apparently they can't start with a period | 333 | // Number -- apparently they can't start with a period |
333 | parseNumber( c, sInput ); | 334 | parseNumber( ps ); |
334 | } | 335 | } |
335 | else if( c == 't' || c == 'f' || c == 'n' ) | 336 | else if( ps.c == 't' || ps.c == 'f' || ps.c == 'n' ) |
336 | { | 337 | { |
337 | // True / false / null | 338 | // True / false / null |
338 | parseLiteral( c, sInput ); | 339 | parseLiteral( ps ); |
339 | } | 340 | } |
340 | else | 341 | else |
341 | { | 342 | { |
342 | throw Bu::ExceptionBase("Invalid characters in json stream."); | 343 | ps.error( |
344 | Bu::String("Invalid json: Invalid character: '%1'.").arg( (char)ps.c ) | ||
345 | ); | ||
343 | } | 346 | } |
344 | } | 347 | } |
345 | 348 | ||
@@ -382,7 +385,7 @@ void Bu::Json::write( Bu::Stream &sOutput ) const | |||
382 | switch( eType ) | 385 | switch( eType ) |
383 | { | 386 | { |
384 | case Invalid: | 387 | case Invalid: |
385 | throw Bu::ExceptionBase("Invalid type in json"); | 388 | throw Bu::ExceptionBase("Invalid type in json."); |
386 | break; | 389 | break; |
387 | 390 | ||
388 | case Object: | 391 | case Object: |
@@ -537,22 +540,21 @@ bool Bu::Json::operator==( const Bu::String &rRhs ) | |||
537 | return (*uDat.pString) == rRhs; | 540 | return (*uDat.pString) == rRhs; |
538 | } | 541 | } |
539 | 542 | ||
540 | void Bu::Json::parseString( Bu::UtfChar &c, Bu::Stream &sInput, | 543 | void Bu::Json::parseString( Bu::Json::ParseState &ps, Bu::UtfString &sOut ) |
541 | Bu::UtfString &sOut ) | ||
542 | { | 544 | { |
543 | skipWs( c, sInput ); | 545 | skipWs( ps ); |
544 | bool bEscape = false; | 546 | bool bEscape = false; |
545 | for(;;) | 547 | for(;;) |
546 | { | 548 | { |
547 | next( "string" ); | 549 | next( "string" ); |
548 | if( bEscape ) | 550 | if( bEscape ) |
549 | { | 551 | { |
550 | switch( c ) | 552 | switch( ps.c ) |
551 | { | 553 | { |
552 | case '"': | 554 | case '"': |
553 | case '\\': | 555 | case '\\': |
554 | case '/': | 556 | case '/': |
555 | sOut += c; | 557 | sOut += ps.c; |
556 | break; | 558 | break; |
557 | 559 | ||
558 | case 'b': | 560 | case 'b': |
@@ -580,8 +582,9 @@ void Bu::Json::parseString( Bu::UtfChar &c, Bu::Stream &sInput, | |||
580 | break; | 582 | break; |
581 | 583 | ||
582 | default: | 584 | default: |
583 | throw Bu::ExceptionBase( | 585 | ps.error( |
584 | "Invalid escape sequence encountered in string." | 586 | Bu::String("Invalid json: Invalid escape sequence: " |
587 | " '\\%1'.").arg( (char)ps.c ) | ||
585 | ); | 588 | ); |
586 | break; | 589 | break; |
587 | } | 590 | } |
@@ -589,37 +592,37 @@ void Bu::Json::parseString( Bu::UtfChar &c, Bu::Stream &sInput, | |||
589 | } | 592 | } |
590 | else | 593 | else |
591 | { | 594 | { |
592 | if( c == '\\' ) | 595 | if( ps.c == '\\' ) |
593 | bEscape = true; | 596 | bEscape = true; |
594 | else if( c == '"' ) | 597 | else if( ps.c == '"' ) |
595 | { | 598 | { |
596 | readChar( c, sInput ); | 599 | readChar( ps ); |
597 | break; | 600 | break; |
598 | } | 601 | } |
599 | else | 602 | else |
600 | sOut += c; | 603 | sOut += ps.c; |
601 | } | 604 | } |
602 | } | 605 | } |
603 | } | 606 | } |
604 | 607 | ||
605 | void Bu::Json::parseString( Bu::UtfChar &c, Bu::Stream &sInput ) | 608 | void Bu::Json::parseString( Bu::Json::ParseState &ps ) |
606 | { | 609 | { |
607 | eType = String; | 610 | eType = String; |
608 | uDat.pString = new Bu::UtfString(); | 611 | uDat.pString = new Bu::UtfString(); |
609 | parseString( c, sInput, *uDat.pString ); | 612 | parseString( ps, *uDat.pString ); |
610 | } | 613 | } |
611 | 614 | ||
612 | void Bu::Json::parseObject( Bu::UtfChar &c, Bu::Stream &sInput ) | 615 | void Bu::Json::parseObject( Bu::Json::ParseState &ps ) |
613 | { | 616 | { |
614 | skipWs( c, sInput ); | 617 | skipWs( ps ); |
615 | eType = Object; | 618 | eType = Object; |
616 | uDat.pObject = new JsonHash(); | 619 | uDat.pObject = new JsonHash(); |
617 | 620 | ||
618 | next( "object" ); | 621 | next( "object" ); |
619 | skipWs( c, sInput ); | 622 | skipWs( ps ); |
620 | 623 | ||
621 | // Check to see if it's an empty object. | 624 | // Check to see if it's an empty object. |
622 | if( c == '}' ) | 625 | if( ps.c == '}' ) |
623 | { | 626 | { |
624 | next("object"); | 627 | next("object"); |
625 | return; | 628 | return; |
@@ -627,35 +630,45 @@ void Bu::Json::parseObject( Bu::UtfChar &c, Bu::Stream &sInput ) | |||
627 | 630 | ||
628 | for(;;) | 631 | for(;;) |
629 | { | 632 | { |
633 | skipWs( ps ); | ||
634 | if( ps.c != '"' ) | ||
635 | { | ||
636 | ps.error( | ||
637 | Bu::String("Invalid json: expected string as key in object, " | ||
638 | "found '%1'.").arg( (char)ps.c ) | ||
639 | ); | ||
640 | } | ||
630 | Bu::UtfString sKey; | 641 | Bu::UtfString sKey; |
631 | parseString( c, sInput, sKey ); | 642 | parseString( ps, sKey ); |
632 | skipWs( c, sInput ); | 643 | skipWs( ps ); |
633 | if( c != ':' ) | 644 | if( ps.c != ':' ) |
634 | { | 645 | { |
635 | throw Bu::ExceptionBase( | 646 | ps.error( |
636 | "Invalid json, expected colon after key in object." | 647 | Bu::String("Invalid json: expected colon after key in object, " |
648 | "found '%1'.").arg( (char)ps.c ) | ||
637 | ); | 649 | ); |
638 | } | 650 | } |
639 | next("object"); | 651 | next("object"); |
640 | uDat.pObject->insert( sKey, new Json( c, sInput ) ); | 652 | uDat.pObject->insert( sKey, new Json( ps ) ); |
641 | skipWs( c, sInput ); | 653 | skipWs( ps ); |
642 | if( c == '}' ) | 654 | if( ps.c == '}' ) |
643 | { | 655 | { |
644 | readChar( c, sInput ); | 656 | readChar( ps ); |
645 | break; | 657 | break; |
646 | } | 658 | } |
647 | else if( c == ',' ) | 659 | else if( ps.c == ',' ) |
648 | next( "object" ); | 660 | next( "object" ); |
649 | else | 661 | else |
650 | throw Bu::ExceptionBase( | 662 | ps.error( |
651 | "Invalid json, expected comma or } after value in object." | 663 | Bu::String("Invalid json: expected comma or } after value " |
664 | "in object, found '%1'.").arg( (char)ps.c ) | ||
652 | ); | 665 | ); |
653 | } | 666 | } |
654 | } | 667 | } |
655 | 668 | ||
656 | void Bu::Json::parseArray( Bu::UtfChar &c, Bu::Stream &sInput ) | 669 | void Bu::Json::parseArray( Bu::Json::ParseState &ps ) |
657 | { | 670 | { |
658 | skipWs( c, sInput ); | 671 | skipWs( ps ); |
659 | 672 | ||
660 | eType = Array; | 673 | eType = Array; |
661 | uDat.pArray = new JsonList(); | 674 | uDat.pArray = new JsonList(); |
@@ -663,7 +676,7 @@ void Bu::Json::parseArray( Bu::UtfChar &c, Bu::Stream &sInput ) | |||
663 | next("array"); | 676 | next("array"); |
664 | 677 | ||
665 | // Check to see if it's an empty array. | 678 | // Check to see if it's an empty array. |
666 | if( c == ']' ) | 679 | if( ps.c == ']' ) |
667 | { | 680 | { |
668 | next("array"); | 681 | next("array"); |
669 | return; | 682 | return; |
@@ -671,74 +684,78 @@ void Bu::Json::parseArray( Bu::UtfChar &c, Bu::Stream &sInput ) | |||
671 | 684 | ||
672 | for(;;) | 685 | for(;;) |
673 | { | 686 | { |
674 | uDat.pArray->append( new Json( c, sInput ) ); | 687 | uDat.pArray->append( new Json( ps ) ); |
675 | skipWs( c, sInput ); | 688 | skipWs( ps ); |
676 | if( c == ']' ) | 689 | if( ps.c == ']' ) |
677 | { | 690 | { |
678 | readChar( c, sInput ); | 691 | readChar( ps ); |
679 | break; | 692 | break; |
680 | } | 693 | } |
681 | else if( c == ',' ) | 694 | else if( ps.c == ',' ) |
682 | { | 695 | { |
683 | next("array"); | 696 | next("array"); |
684 | continue; | 697 | continue; |
685 | } | 698 | } |
686 | else | 699 | else |
687 | { | 700 | { |
688 | throw Bu::ExceptionBase( | 701 | ps.error( |
689 | "Invalid json, expected comma or ] after value in array." | 702 | Bu::String("Invalid json: expected comma or ] after value " |
703 | "in array, found '%1'.").arg( (char)ps.c ) | ||
690 | ); | 704 | ); |
691 | } | 705 | } |
692 | } | 706 | } |
693 | } | 707 | } |
694 | 708 | ||
695 | void Bu::Json::parseNumber( Bu::UtfChar &c, Bu::Stream &sInput ) | 709 | void Bu::Json::parseNumber( Bu::Json::ParseState &ps ) |
696 | { | 710 | { |
697 | skipWs( c, sInput ); | 711 | skipWs( ps ); |
698 | 712 | ||
699 | Bu::String sBuf; | 713 | Bu::String sBuf; |
700 | if( c == '-' ) | 714 | if( ps.c == '-' ) |
701 | { | 715 | { |
702 | sBuf += c; | 716 | sBuf += ps.c; |
703 | next( "number" ); | 717 | next( "number" ); |
704 | } | 718 | } |
705 | bool bIntPart = true; | 719 | bool bIntPart = true; |
706 | do | 720 | do |
707 | { | 721 | { |
708 | if( c >= '0' && c <= '9' ) | 722 | if( ps.c >= '0' && ps.c <= '9' ) |
709 | sBuf += c; | 723 | sBuf += ps.c; |
710 | else if( c == '.' && bIntPart == true ) | 724 | else if( ps.c == '.' && bIntPart == true ) |
711 | { | 725 | { |
712 | bIntPart = false; | 726 | bIntPart = false; |
713 | sBuf += c; | 727 | sBuf += ps.c; |
714 | } | 728 | } |
715 | else if( c == ' ' || c == '\t' || c == '\n' || c == '\r' || | 729 | else if( ps.c == ' ' || ps.c == '\t' || ps.c == '\n' || ps.c == '\r' || |
716 | c == '}' || c == ']' || c == ',' ) | 730 | ps.c == '}' || ps.c == ']' || ps.c == ',' ) |
717 | { | 731 | { |
718 | break; | 732 | break; |
719 | } | 733 | } |
720 | else | 734 | else |
721 | { | 735 | { |
722 | throw Bu::ExceptionBase("Invalid character in number."); | 736 | ps.error( |
737 | Bu::String("Invalid json: Invalid character in number: '%1'."). | ||
738 | arg( (char)ps.c ) | ||
739 | ); | ||
723 | } | 740 | } |
724 | } while( readChar( c, sInput ) ); | 741 | } while( readChar( ps ) ); |
725 | 742 | ||
726 | eType = Number; | 743 | eType = Number; |
727 | uDat.dNumber = atof( sBuf.getStr() ); | 744 | uDat.dNumber = atof( sBuf.getStr() ); |
728 | } | 745 | } |
729 | 746 | ||
730 | void Bu::Json::parseLiteral( Bu::UtfChar &c, Bu::Stream &sInput ) | 747 | void Bu::Json::parseLiteral( Bu::Json::ParseState &ps ) |
731 | { | 748 | { |
732 | skipWs( c, sInput ); | 749 | skipWs( ps ); |
733 | 750 | ||
734 | Bu::String s; | 751 | Bu::String s; |
735 | do | 752 | do |
736 | { | 753 | { |
737 | if( isWs( c ) || c == ',' || c == '}' || c == ']' ) | 754 | if( isWs( ps.c ) || ps.c == ',' || ps.c == '}' || ps.c == ']' ) |
738 | break; | 755 | break; |
739 | else | 756 | else |
740 | s += c; | 757 | s += ps.c; |
741 | } while( readChar( c, sInput ) ); | 758 | } while( readChar( ps ) ); |
742 | 759 | ||
743 | if( s == "true" ) | 760 | if( s == "true" ) |
744 | { | 761 | { |
@@ -757,22 +774,39 @@ void Bu::Json::parseLiteral( Bu::UtfChar &c, Bu::Stream &sInput ) | |||
757 | } | 774 | } |
758 | else | 775 | else |
759 | { | 776 | { |
760 | throw Bu::ExceptionBase("Invalid literal token found."); | 777 | ps.error( |
778 | Bu::String("Invalid json: Invalid literal token found, '%1'."). | ||
779 | arg( s ) | ||
780 | ); | ||
761 | } | 781 | } |
762 | } | 782 | } |
763 | 783 | ||
764 | bool Bu::Json::readChar( Bu::UtfChar &c, Bu::Stream &sInput ) | 784 | bool Bu::Json::readChar( Bu::Json::ParseState &ps ) |
765 | { | 785 | { |
766 | if( Bu::UtfString::readPoint( sInput, c ) == 0 && sInput.isEos() ) | 786 | if( Bu::UtfString::readPoint( ps.sInput, ps.c ) == 0 && ps.sInput.isEos() ) |
767 | return false; | 787 | return false; |
788 | |||
789 | if( ps.c == '\n' ) | ||
790 | { | ||
791 | // Increment the line and set iChar to zero. This makes sense only | ||
792 | // beacuse we only complain after a charecter has been read, so this | ||
793 | // will be too large by one unless we start at zero. | ||
794 | ps.iLine++; | ||
795 | ps.iChar = 0; | ||
796 | } | ||
797 | else | ||
798 | { | ||
799 | ps.iChar++; | ||
800 | } | ||
801 | |||
768 | return true; | 802 | return true; |
769 | } | 803 | } |
770 | 804 | ||
771 | void Bu::Json::readChar( Bu::UtfChar &c, Bu::Stream &sInput, const char *sSection ) | 805 | void Bu::Json::readChar( Bu::Json::ParseState &ps, const char *sSection ) |
772 | { | 806 | { |
773 | if( Bu::UtfString::readPoint( sInput, c ) == 0 && sInput.isEos() ) | 807 | if( !readChar( ps ) ) |
774 | { | 808 | { |
775 | throw Bu::ExceptionBase( sSection ); | 809 | ps.error( sSection ); |
776 | } | 810 | } |
777 | } | 811 | } |
778 | 812 | ||
@@ -781,9 +815,9 @@ bool Bu::Json::isWs( Bu::UtfChar c ) | |||
781 | return c == ' ' || c == '\t' || c == '\r' || c == '\n'; | 815 | return c == ' ' || c == '\t' || c == '\r' || c == '\n'; |
782 | } | 816 | } |
783 | 817 | ||
784 | void Bu::Json::skipWs( Bu::UtfChar &c, Bu::Stream &sInput ) | 818 | void Bu::Json::skipWs( Bu::Json::ParseState &ps ) |
785 | { | 819 | { |
786 | while( isWs( c ) ) | 820 | while( isWs( ps.c ) ) |
787 | { | 821 | { |
788 | next("whitespace"); | 822 | next("whitespace"); |
789 | } | 823 | } |
@@ -850,3 +884,11 @@ Bu::Formatter &Bu::operator<<( Bu::Formatter &f, const Bu::Json &j ) | |||
850 | return f; | 884 | return f; |
851 | } | 885 | } |
852 | 886 | ||
887 | void Bu::Json::ParseState::error( const Bu::String &sTxt ) | ||
888 | { | ||
889 | throw Bu::ExceptionParse( | ||
890 | Bu::String("%1:%2: %3"). | ||
891 | arg( iLine ).arg( iChar ).arg( sTxt ).end().getStr() | ||
892 | ); | ||
893 | } | ||
894 | |||
diff --git a/src/unstable/json.h b/src/unstable/json.h index 5373bcf..a973f74 100644 --- a/src/unstable/json.h +++ b/src/unstable/json.h | |||
@@ -16,7 +16,22 @@ namespace Bu | |||
16 | class Json | 16 | class Json |
17 | { | 17 | { |
18 | private: | 18 | private: |
19 | Json( Bu::UtfChar &c, Bu::Stream &sInput ); | 19 | class ParseState |
20 | { | ||
21 | public: | ||
22 | ParseState( Bu::Stream &sInput ) : | ||
23 | c( 0 ), sInput( sInput ), iLine( 1 ), iChar( 0 ) | ||
24 | { | ||
25 | } | ||
26 | |||
27 | void error( const Bu::String &sTxt ); | ||
28 | |||
29 | Bu::UtfChar c; | ||
30 | Bu::Stream &sInput; | ||
31 | int iLine; | ||
32 | int iChar; | ||
33 | }; | ||
34 | Json( ParseState &ps ); | ||
20 | typedef Bu::Hash<Bu::UtfString, Json *> JsonHash; | 35 | typedef Bu::Hash<Bu::UtfString, Json *> JsonHash; |
21 | typedef Bu::Array<Json *> JsonList; | 36 | typedef Bu::Array<Json *> JsonList; |
22 | 37 | ||
@@ -90,19 +105,17 @@ namespace Bu | |||
90 | bool operator==( const Bu::String &rRhs ); | 105 | bool operator==( const Bu::String &rRhs ); |
91 | 106 | ||
92 | private: | 107 | private: |
93 | void parse( Bu::UtfChar &c, Bu::Stream &sInput ); | 108 | void parse( ParseState &ps ); |
94 | void parseString( Bu::UtfChar &c, Bu::Stream &sInput, | 109 | void parseString( ParseState &ps, Bu::UtfString &sOut ); |
95 | Bu::UtfString &sOut ); | 110 | void parseString( ParseState &ps ); |
96 | void parseString( Bu::UtfChar &c, Bu::Stream &sInput ); | 111 | void parseObject( ParseState &ps ); |
97 | void parseObject( Bu::UtfChar &c, Bu::Stream &sInput ); | 112 | void parseArray( ParseState &ps ); |
98 | void parseArray( Bu::UtfChar &c, Bu::Stream &sInput ); | 113 | void parseNumber( ParseState &ps ); |
99 | void parseNumber( Bu::UtfChar &c, Bu::Stream &sInput ); | 114 | void parseLiteral( ParseState &ps ); |
100 | void parseLiteral( Bu::UtfChar &c, Bu::Stream &sInput ); | 115 | bool readChar( ParseState &ps ); |
101 | bool readChar( Bu::UtfChar &c, Bu::Stream &sInput ); | 116 | void readChar( ParseState &ps, const char *sSection ); |
102 | void readChar( Bu::UtfChar &c, Bu::Stream &sInput, | ||
103 | const char *sSection ); | ||
104 | bool isWs( Bu::UtfChar c ); | 117 | bool isWs( Bu::UtfChar c ); |
105 | void skipWs( Bu::UtfChar &c, Bu::Stream &sInput ); | 118 | void skipWs( ParseState &ps ); |
106 | void writeStr( const Bu::UtfString &sStr, Bu::Stream &sOutput ) const; | 119 | void writeStr( const Bu::UtfString &sStr, Bu::Stream &sOutput ) const; |
107 | 120 | ||
108 | private: | 121 | private: |