1 module json.parser.parser; 2 3 private { 4 import json.parser.lexer; 5 import json.value; 6 7 import std.conv; 8 import std.string; 9 import std.utf; 10 11 static immutable dstring[JsonToken.Type] keywords; 12 static immutable dchar[JsonToken.Type] punctuation; 13 } 14 15 shared static this() 16 { 17 dstring[JsonToken.Type] _kwTemp; 18 foreach( k, v; json.parser.lexer.Keywords ) 19 _kwTemp[v] = k; 20 21 dchar[JsonToken.Type] _punctTemp; 22 foreach( k, v; json.parser.lexer.Punctuation ) 23 _punctTemp[v] = k; 24 25 keywords = cast(immutable)_kwTemp; 26 punctuation = cast(immutable)_punctTemp; 27 } 28 29 final package class Parser 30 { 31 private: 32 JsonToken[] tokens; 33 immutable size_t length; 34 size_t index; 35 StandardCompliant standard; 36 37 public this( Lexer lexer, StandardCompliant standardCompliant ) 38 { 39 this.tokens = lexer.tokenize(); 40 this.length = this.tokens.length; 41 this.standard = standardCompliant; 42 } 43 44 public JsonValue parse() 45 { 46 this.index = 0; 47 auto value = this.parseValue(); 48 this.take( JsonToken.Type.EndOfInput ); 49 50 return value; 51 } 52 53 JsonValue parseValue() 54 { 55 auto token = this.take(); 56 with( JsonToken.Type ) 57 switch( token.type ) 58 { 59 case String: return JsonValue( token.text ); 60 61 case Number: 62 { 63 with( JsonToken.NumberType ) 64 final switch( token.numberType ) 65 { 66 case signed: return JsonValue( token.text.to!long ); 67 case unsigned: return JsonValue( token.text.to!ulong ); 68 case floating: return JsonValue( token.text.to!real ); 69 } 70 } 71 72 case True: return jtrue; 73 case False: return jfalse; 74 case Null: return jnull; 75 case LeftBrace: return this.parseObject(); 76 case LeftSquare: return this.parseArray(); 77 78 default: 79 throw new JsonParserException( token, "Unexpected '%s', expecting value".format( token.identify() ) ); 80 } 81 } 82 83 JsonValue parseObject() 84 { 85 with( JsonToken.Type ) 86 { 87 JsonValue[dstring] object; 88 while( !this.match( RightBrace ) ) 89 { 90 auto key = this.standard ? this.take( String ) : this.takeFirstOf( String, Identifier ); 91 this.take( Colon ); 92 93 object[key.text] = this.parseValue(); 94 95 if( !this.match( Comma ) ) 96 break; 97 98 this.take( Comma ); 99 100 // allow trailing comma in non-standard mode 101 if( !this.standard && this.match( RightBrace ) ) 102 break; 103 } 104 105 this.take( RightBrace ); 106 return JsonValue( object ); 107 } 108 } 109 110 JsonValue parseArray() 111 { 112 with( JsonToken.Type ) 113 { 114 JsonValue[] array; 115 while( !this.match( RightSquare ) ) 116 { 117 array ~= this.parseValue(); 118 119 if( !this.match( Comma ) ) 120 break; 121 122 this.take( Comma ); 123 124 // allow trailing comma in non-standard mode 125 if( !this.standard && this.match( RightSquare ) ) 126 break; 127 } 128 129 this.take( RightSquare ); 130 return JsonValue( array ); 131 } 132 } 133 134 JsonToken take() 135 { 136 debug assert( this.index < this.length ); 137 return this.tokens[this.index++]; 138 } 139 140 JsonToken take( JsonToken.Type type ) 141 { 142 auto token = this.take(); 143 if( token.type != type ) 144 throw new JsonParserException( 145 token, 146 "Unexpected '%s', expecting '%s'".format( 147 token.identify(), 148 this.identify( type ) 149 ) 150 ); 151 152 return token; 153 } 154 155 JsonToken takeFirstOf( JsonToken.Type[] types... ) 156 { 157 import std.array : join; 158 import std.algorithm.iteration : map; 159 import std.algorithm.searching : canFind; 160 161 auto token = this.take(); 162 163 if( !types.canFind( token.type ) ) 164 throw new JsonParserException( 165 token, 166 "Unexpected '%s', expecting one of: %s".format( 167 token.identify(), 168 types.map!( t => this.identify( t ) ).join( ", " ) 169 ) 170 ); 171 172 return token; 173 } 174 175 bool match( JsonToken.Type type ) 176 { 177 return this.tokens[this.index].type == type; 178 } 179 180 bool matchAndTake( JsonToken.Type type ) 181 { 182 if( this.match( type ) ) 183 { 184 this.take( type ); 185 return true; 186 } 187 188 return false; 189 } 190 191 string identify( JsonToken.Type type ) 192 { 193 if( auto word = type in keywords ) 194 return ( *word ).toUTF8(); 195 196 if( auto mark = type in punctuation ) 197 return [ *mark ].toUTF8(); 198 199 with( JsonToken.Type ) 200 switch( type ) 201 { 202 case EndOfInput: 203 return "end-of-input"; 204 205 case String: 206 case Number: 207 return to!( string )( type ).toLower(); 208 209 default: 210 assert( false, "unhandled type '%s'".format( type ) ); 211 } 212 } 213 }