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 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 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         with (JSONType) final switch (value.type)
130         {
131             case null_:
132                 this.currentValue.literal = JSONToken(null);
133                 break;
134             case string:
135                 this.currentValue.literal = JSONToken(value.str);
136                 break;
137             case integer:
138                 this.currentValue.literal = JSONToken(value.integer);
139                 break;
140             case uinteger:
141                 this.currentValue.literal = JSONToken(value.uinteger);
142                 break;
143             case float_:
144                 this.currentValue.literal = JSONToken(value.floating);
145                 break;
146             case array:
147                 this.currentValue.kind = JSONParserNodeKind.arrayStart;
148                 pushState(value);
149                 break;
150             case object:
151                 this.currentValue.kind = JSONParserNodeKind.objectStart;
152                 pushState(value);
153                 break;
154             case true_:
155                 this.currentValue.literal = JSONToken(true);
156                 break;
157             case false_:
158                 this.currentValue.literal = JSONToken(false);
159                 break;
160         }
161     }
162 
163     private void pushState(JSONValue value)
164     {
165         if (this.level >= 0)
166         {
167             if (this.level == this.iterators.length)
168             {
169                 this.iterators ~= this.previous;
170             }
171             else
172             {
173                 this.iterators[this.level] = this.previous;
174             }
175         }
176         this.previous = this.current;
177         this.current = ValueIterator(value);
178         this.level++;
179     }
180 
181     private void popState()
182     {
183         this.level--;
184         if (!outOfValues)
185         {
186             this.current = this.previous;
187             if (this.level >= 0) // handle -1 case
188             {
189                 this.previous = this.iterators[this.level];
190             }
191         }
192     }
193 
194     public bool outOfValues() const
195     {
196         return this.level == -2;
197     }
198 }
199 
200 private struct ValueIterator
201 {
202     JSONValue value;
203 
204     invariant(value.type == JSONType.array || value.type == JSONType.object);
205 
206     // if `value` is an array or object, indicates the next element to be selected.
207     size_t nextIndex = 0;
208 
209     bool usedKey; // objects are key, value, key, value => false, true, false, true...
210 }