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 void popFront()
62     in (!empty)
63     {
64         if (outOfValues)
65         {
66             empty = true;
67             return;
68         }
69 
70         if (current.value.type == JSONType.object)
71         {
72             if (current.nextIndex == current.value.objectNoRef.length)
73             {
74                 this.currentValue.kind = JSONParserNodeKind.objectEnd;
75                 popState;
76                 return;
77             }
78 
79             if (!current.usedKey)
80             {
81                 this.currentValue.key = current.value.objectNoRef.byKeyValue.drop(current.nextIndex).front.key;
82                 current.usedKey = true;
83                 return;
84             }
85 
86             auto value = current.value.objectNoRef.byKeyValue.drop(current.nextIndex).front.value;
87 
88             current.usedKey = false;
89             current.nextIndex++;
90             stepInto(value);
91             return;
92         }
93         else if (current.value.type == JSONType.array)
94         {
95             if (current.nextIndex == current.value.arrayNoRef.length)
96             {
97                 this.currentValue.kind = JSONParserNodeKind.arrayEnd;
98                 popState;
99                 return;
100             }
101             auto value = current.value.arrayNoRef[current.nextIndex];
102 
103             current.nextIndex++;
104             stepInto(value);
105         }
106         else
107         {
108             import std.format : format;
109 
110             assert(false, format!"unexpected value type: %s"(current.value.type));
111         }
112     }
113 
114     private void stepInto(JSONValue value)
115     {
116         alias Token = JSONToken!string;
117 
118         with (JSONType) final switch (value.type)
119         {
120             case null_:
121                 this.currentValue.literal = Token(null);
122                 break;
123             case string:
124                 this.currentValue.literal = Token(value.str);
125                 break;
126             case integer:
127                 this.currentValue.literal = Token(value.integer);
128                 break;
129             case uinteger:
130                 this.currentValue.literal = Token(value.uinteger);
131                 break;
132             case float_:
133                 this.currentValue.literal = Token(value.floating);
134                 break;
135             case array:
136                 this.currentValue.kind = JSONParserNodeKind.arrayStart;
137                 pushState(value);
138                 break;
139             case object:
140                 this.currentValue.kind = JSONParserNodeKind.objectStart;
141                 pushState(value);
142                 break;
143             case true_:
144                 this.currentValue.literal = Token(true);
145                 break;
146             case false_:
147                 this.currentValue.literal = Token(false);
148                 break;
149         }
150     }
151 
152     private void pushState(JSONValue value)
153     {
154         if (this.level >= 0)
155         {
156             if (this.level == this.iterators.length)
157             {
158                 this.iterators ~= this.previous;
159             }
160             else
161             {
162                 this.iterators[this.level] = this.previous;
163             }
164         }
165         this.previous = this.current;
166         this.current = ValueIterator(value);
167         this.level++;
168     }
169 
170     private void popState()
171     {
172         this.level--;
173         if (!outOfValues)
174         {
175             this.current = this.previous;
176             if (this.level >= 0) // handle -1 case
177             {
178                 this.previous = this.iterators[this.level];
179             }
180         }
181     }
182 
183     public bool outOfValues() const
184     {
185         return this.level == -2;
186     }
187 }
188 
189 private struct ValueIterator
190 {
191     JSONValue value;
192 
193     invariant(value.type == JSONType.array || value.type == JSONType.object);
194 
195     // if `value` is an array or object, indicates the next element to be selected.
196     size_t nextIndex = 0;
197 
198     bool usedKey; // objects are key, value, key, value => false, true, false, true...
199 }