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 }