1 module viva.json.parser;
2 
3 import std.range;
4 import std.traits;
5 import std.format;
6 import std.algorithm;
7 import std.exception;
8 
9 /++
10  +
11  +/
12 public struct JSONParser
13 {
14     ///
15     public enum JSONType
16     {
17         null_,
18         string_,
19         boolean,
20         number,
21         array,
22         object
23     }
24 
25     ///
26     public ForwardRange!dchar input;
27     ///
28     size_t lineNumber = 1;
29 
30     @disable this();
31 
32     /++
33      +
34      +/
35     public this(T)(T input)
36     if (isForwardRange!T)
37     {
38         this.input = input.inputRangeObject;
39     }
40 
41     /++
42      +
43      +/
44     public JSONType peekType()
45     {
46         skipWhitespace();
47 
48         if (input.empty) throw new Exception("Unexpected end of file.");
49 
50         with (JSONType)
51         switch (input.front)
52         {
53             case 'n': return null_;
54 
55             case 't':
56             case 'f': return boolean;
57 
58             case '-':
59             case '0':
60                 ..
61             case '9': return number;
62 
63             case '"': return string_;
64 
65             case '[': return array;
66             case '{': return object;
67 
68             case ']':
69             case '}':
70                 throw new Exception(
71                     failMsg(input.front.format!"unexpected '%s' (maybe there's a comma before it?)")
72                 );
73 
74             default:
75                 throw new Exception(
76                     failMsg(input.front.format!"unexpected character '%s'")
77                 );
78 
79         }
80     }
81 
82     private string getLineBreaks() {
83 
84         import viva.io : println;
85 
86         string match = "";
87         dchar lineSep;
88 
89         loop: while (!input.empty)
90         switch (input.front)
91         {
92             case '\n', '\r':
93                 match ~= input.front;
94 
95                 if (lineSep == input.front || lineSep == dchar.init) lineNumber++;
96 
97                 lineSep = input.front;
98                 input.popFront();
99 
100                 continue;
101 
102             default: break loop;
103 
104         }
105 
106         return match;
107 
108     }
109 
110     private void skipWhitespace()
111     {
112         while (!input.empty)
113         switch (input.front)
114         {
115             case '\n', '\r':
116                 getLineBreaks();
117                 continue;
118 
119             case ' ', '\t':
120                 input.popFront();
121                 continue;
122 
123             default:
124                 return;
125         }
126     }
127 
128     private string failMsg(string s)
129     {
130         return s.format!"%s ib kube %s"(lineNumber);
131     }
132 
133     pragma(inline, true);
134     private string failFoundMsg(string s)
135     {
136         skipWhitespace();
137         return failMsg(s.format!"%s, found %s"(peekType));
138     }
139 }