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 }