1 /**
2     JSON Reader
3 
4     Copyright: 
5         Copyright © 2020-2026, Inochi2D Project
6     
7     License:
8         $(LINK2 https://github.com/Inochi2D/inochi2d/blob/main/LICENSE, BSD 2-clause License)
9     
10     Authors:
11         Luna Nielsen
12 */
13 module inp.format.json.reader;
14 import inp.format.parse;
15 import inp.format.node;
16 import nulib.io.stream;
17 import nulib.io.stream.rw;
18 import nulib.string;
19 import nulib.math;
20 import nulib.conv;
21 import numem;
22 
23 @nogc:
24 
25 /**
26     Reads and parses a JSON string into a $(D DataNode).
27 
28     Params:
29         stream = The stream to read.
30         length = The amount of characters to read.
31     
32     Returns:
33         A result type containing either a $(D DataNode)
34         or an error message.
35 */
36 Result!DataNode readJson(Stream stream, size_t length = 0) @nogc {
37     DataNode result;
38     scope StreamReader reader = new StreamReader(stream);
39     if (auto err = reader.readJson(result, length))
40         return error!DataNode(err);
41     return ok(result.move());
42 }
43 
44 /**
45     Reads JSON data from the given existing stream.
46 
47     Params:
48         reader =    The stream reader to read from.
49         node =      The node to store the result in.
50         length =    The length of the buffer to read.
51 
52     Returns:
53         Any errors emitted.
54 */
55 ErrorString readJson(StreamReader reader, ref DataNode node, size_t length) {
56     if (length > 0) {
57         ubyte[] buffer = nu_malloca!ubyte(length);
58         if (reader.stream.read(buffer) < length)
59             return "Reached EOF!";
60 
61         scope MemoryStream mstream = new MemoryStream(buffer);
62         scope StreamReader mreader = new StreamReader(mstream);
63         return mreader.readJson(node);
64     }
65     return reader.readJson(node);
66 }
67 
68 
69 
70 
71 //
72 //              IMPLEMENTATION DETAILS
73 //
74 private:
75 
76 /**
77     Reads a JSON value from the stream.    
78 */
79 ErrorString readJson(StreamReader reader, ref DataNode node) {
80     reader.skipWhitespace();
81     switch(reader.peekChar()) {
82         default:
83             if (reader.peekString("true")) {
84                 reader.skip(4);
85                 node = DataNode(true);
86                 return null;
87             }
88 
89             if (reader.peekString("false")) {
90                 reader.skip(5);
91                 node = DataNode(false);
92                 return null;
93             }
94 
95             // Skip 'null'
96             if (reader.peekString("null")) {
97                 reader.skip(4);
98                 return null;
99             }
100 
101             // Okay, no idea what this character is.
102             return "Unexpected token";
103 
104         case '0', '1', '2', '3', '4', '5', '6', '7', '8', '9':
105         case '+', '-':
106             return reader.readJsonNumber(node);
107 
108         case '"':
109             return reader.readJsonString(node);
110 
111         case '{':
112             return reader.readJsonObject(node);
113 
114         case '[':
115             return reader.readJsonArray(node);
116     }
117 }
118 
119 ErrorString readJsonString(StreamReader reader, ref DataNode node) {
120     node = DataNode(reader.readStringJson().take());
121     return null;
122 }
123 
124 ErrorString readJsonNumber(StreamReader reader, ref DataNode node) {
125     bool isFloating;
126     nstring num = reader.readNumberJson(isFloating);
127 
128     if (isFloating)
129         node = DataNode(parseFloat!double(num[]));
130     else
131         node = DataNode(parseInt!long(num[]));
132     
133     return null;
134 }
135 
136 ErrorString readJsonArray(StreamReader reader, ref DataNode node) {
137     char c = cast(char)reader.readU8();
138     if (c != '[')
139         return "Not an array";
140 
141     node = DataNode.createArray();
142             
143     // Empty array.
144     if (reader.peekChar() == ']') {
145         reader.skip(1);
146         return null;
147     }
148 
149     DataNode value;
150     do {
151         
152         // Read value
153         reader.skipWhitespace();
154         if (auto error = reader.readJson(value)) {
155             return error;
156         }
157         reader.skipWhitespace();
158 
159         // Append value
160         node ~= value.move();
161 
162         // Consume comma if needed.
163         c = cast(char)reader.readU8();
164         if (c == ',') {
165             reader.skipWhitespace();
166             continue;
167         }
168 
169         // Consume array end.
170         if (c == ']')
171             break;
172 
173     } while(!reader.eof());
174     return null;
175 }
176 
177 ErrorString readJsonObject(StreamReader reader, ref DataNode node) {
178     char c = cast(char)reader.readU8();
179     if (c != '{')
180         return "Not an object";
181 
182     node = DataNode.createObject();
183             
184     // Empty object.
185     if (reader.peekChar() == '}') {
186         reader.skip(1);
187         return null;
188     }
189 
190     DataNode value;
191     do {
192 
193         // Read key
194         reader.skipWhitespace();
195         nstring key = reader.readStringJson();
196         reader.skipWhitespace();
197 
198         // Ensure it's a k-v pair.
199         c = cast(char)reader.readU8();
200         if (c != ':')
201             return "Invalid key-value pair!";
202         
203         // Read value
204         reader.skipWhitespace();
205         if (auto error = reader.readJson(value)) {
206             return error;
207         }
208         reader.skipWhitespace();
209 
210         // Assign key-value pair.
211         node[key[]] = value.move();
212 
213         // Consume comma if needed.
214         c = cast(char)reader.readU8();
215         if (c == ',') {
216             reader.skipWhitespace();
217             continue;
218         }
219 
220         // Comsume object end.
221         if (c == '}')
222             break;
223 
224     } while(!reader.eof());
225     return null;
226 }