aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorMike Buland <eichlan@xagasoft.com>2019-12-14 21:18:59 -0800
committerMike Buland <eichlan@xagasoft.com>2019-12-14 21:18:59 -0800
commit2ff101097feedf85b0ab0163983159200fc146a2 (patch)
treeb766200d04833e866a65e91945950bbb31f5e969
parent8f9337568befa81e82e97abd4d2d75e3a5c5fbe9 (diff)
downloadlibbu++-2ff101097feedf85b0ab0163983159200fc146a2.tar.gz
libbu++-2ff101097feedf85b0ab0163983159200fc146a2.tar.bz2
libbu++-2ff101097feedf85b0ab0163983159200fc146a2.tar.xz
libbu++-2ff101097feedf85b0ab0163983159200fc146a2.zip
Json provides line/char in errors now.
All errors are also rewoked and the parser state is tracked much better. Also fixed a parser issue where it would error correctly, but report it poorly when an object started with something other than a string.
-rw-r--r--src/unstable/json.cpp208
-rw-r--r--src/unstable/json.h39
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
11Bu::Json::Json() : 12Bu::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
78Bu::Json::Json( Bu::UtfChar &c, Bu::Stream &sInput ) : 79Bu::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
84Bu::Json::Json( const Json &rSrc ) : 85Bu::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
303void Bu::Json::parse( const Bu::String &sInput ) 304void 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
309void Bu::Json::parse( Bu::UtfChar &c, Bu::Stream &sInput ) 310void 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
540void Bu::Json::parseString( Bu::UtfChar &c, Bu::Stream &sInput, 543void 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
605void Bu::Json::parseString( Bu::UtfChar &c, Bu::Stream &sInput ) 608void 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
612void Bu::Json::parseObject( Bu::UtfChar &c, Bu::Stream &sInput ) 615void 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
656void Bu::Json::parseArray( Bu::UtfChar &c, Bu::Stream &sInput ) 669void 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
695void Bu::Json::parseNumber( Bu::UtfChar &c, Bu::Stream &sInput ) 709void 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
730void Bu::Json::parseLiteral( Bu::UtfChar &c, Bu::Stream &sInput ) 747void 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
764bool Bu::Json::readChar( Bu::UtfChar &c, Bu::Stream &sInput ) 784bool 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
771void Bu::Json::readChar( Bu::UtfChar &c, Bu::Stream &sInput, const char *sSection ) 805void 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
784void Bu::Json::skipWs( Bu::UtfChar &c, Bu::Stream &sInput ) 818void 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
887void 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: