1 /**
2  * This module takes a `std.json` JSONValue and generates a `stdx.data.json` token stream.
3  * It provides Phobos backwards compatibility for `std_data_json`.
4  */
5 module text.json.JsonValueRange;
6 
7 import funkwerk.stdx.data.json.lexer;
8 import funkwerk.stdx.data.json.parser;
9 import std.algorithm : count;
10 import std.json;
11 import std.range : drop;
12 
13 static assert(isJSONParserNodeInputRange!JsonValueRange);
14 
15 struct JsonValueRange
16 {
17     public bool empty;
18 
19     private JSONParserNode!string currentValue;
20 
21     private ValueIterator[] iterators;
22 
23     /**
24      * In order to allow us to be save()-less, it is not enough to just have the current iterator as array state!
25      * Consider the following case: [ A, B ], where A and B are objects.
26      * When decoding A, we are set to objectStart, but the current iterator is already A: that is, the iteration
27      * state on the level of [] is the previous state and thus must be saved also.
28      * Otherwise, when we finish parsing A, we will *consume* objectEnd and position ourselves at objectStart
29      * for B, advancing the state for the array iterator - which is one up from where we started.
30      */
31     private ValueIterator current, previous;
32 
33     private int level;
34 
35     invariant(this.level <= cast(int) this.iterators.length);
36 
37     public this(JSONValue value)
38     {
39         this.empty = false;
40         // current = 0, previous = -1
41         this.level = -2;
42         stepInto(value);
43     }
44 
45     // For debugging.
46     string toString()
47     {
48         import std.format : format;
49         import std.algorithm : max;
50 
51         return format!"JsonValueRange(%s, %s, %s: %s > %s > %s)"(
52             empty, currentValue, level, current, previous, iterators[0 .. max(0, level)]);
53     }
54 
55     public @property ref JSONParserNode!string front() return
56     in (!empty)
57     {
58         return this.currentValue;
59     }
60 
61     public JsonValueRange dup() const
62     {
63         JsonValueRange result;
64 
65         result.empty = this.empty;
66         result.currentValue = this.currentValue;
67         result.iterators = this.iterators.dup;
68         result.current = current;
69         result.previous = previous;
70         result.level = level;
71         return result;
72     }
73 
74     public void popFront()
75     in (!empty)
76     {
77         if (outOfValues)
78         {
79             empty = true;
80             return;
81         }
82 
83         if (current.value.type == JSONType.object)
84         {
85             if (current.nextIndex == current.value.objectNoRef.length)
86             {
87                 this.currentValue.kind = JSONParserNodeKind.objectEnd;
88                 popState;
89                 return;
90             }
91 
92             if (!current.usedKey)
93             {
94                 this.currentValue.key = current.value.objectNoRef.byKeyValue.drop(current.nextIndex).front.key;
95                 current.usedKey = true;
96                 return;
97             }
98 
99             auto value = current.value.objectNoRef.byKeyValue.drop(current.nextIndex).front.value;
100 
101             current.usedKey = false;
102             current.nextIndex++;
103             stepInto(value);
104             return;
105         }
106         else if (current.value.type == JSONType.array)
107         {
108             if (current.nextIndex == current.value.arrayNoRef.length)
109             {
110                 this.currentValue.kind = JSONParserNodeKind.arrayEnd;
111                 popState;
112                 return;
113             }
114             auto value = current.value.arrayNoRef[current.nextIndex];
115 
116             current.nextIndex++;
117             stepInto(value);
118         }
119         else
120         {
121             import std.format : format;
122 
123             assert(false, format!"unexpected value type: %s in %s"(current.value.type, this));
124         }
125     }
126 
127     private void stepInto(JSONValue value)
128     {
129         alias Token = JSONToken!string;
130 
131         with (JSONType) final switch (value.type)
132         {
133             case null_:
134                 this.currentValue.literal = Token(null);
135                 break;
136             case string:
137                 this.currentValue.literal = Token(value.str);
138                 break;
139             case integer:
140                 this.currentValue.literal = Token(value.integer);
141                 break;
142             case uinteger:
143                 this.currentValue.literal = Token(value.uinteger);
144                 break;
145             case float_:
146                 this.currentValue.literal = Token(value.floating);
147                 break;
148             case array:
149                 this.currentValue.kind = JSONParserNodeKind.arrayStart;
150                 pushState(value);
151                 break;
152             case object:
153                 this.currentValue.kind = JSONParserNodeKind.objectStart;
154                 pushState(value);
155                 break;
156             case true_:
157                 this.currentValue.literal = Token(true);
158                 break;
159             case false_:
160                 this.currentValue.literal = Token(false);
161                 break;
162         }
163     }
164 
165     private void pushState(JSONValue value)
166     {
167         if (this.level >= 0)
168         {
169             if (this.level == this.iterators.length)
170             {
171                 this.iterators ~= this.previous;
172             }
173             else
174             {
175                 this.iterators[this.level] = this.previous;
176             }
177         }
178         this.previous = this.current;
179         this.current = ValueIterator(value);
180         this.level++;
181     }
182 
183     private void popState()
184     {
185         this.level--;
186         if (!outOfValues)
187         {
188             this.current = this.previous;
189             if (this.level >= 0) // handle -1 case
190             {
191                 this.previous = this.iterators[this.level];
192             }
193         }
194     }
195 
196     public bool outOfValues() const
197     {
198         return this.level == -2;
199     }
200 }
201 
202 private struct ValueIterator
203 {
204     JSONValue value;
205 
206     invariant(value.type == JSONType.array || value.type == JSONType.object);
207 
208     // if `value` is an array or object, indicates the next element to be selected.
209     size_t nextIndex = 0;
210 
211     bool usedKey; // objects are key, value, key, value => false, true, false, true...
212 }