1 module json; 2 3 import pry; 4 5 import std.stdio, std.conv, std.uni, std.variant, std.typecons, 6 std.json, std.datetime;; 7 import file = std.file; 8 import stdx.data.json; 9 10 import taggedalgebraic; 11 12 struct JSValue { 13 union Base { 14 typeof(null) null_; 15 bool boolean; 16 double num; 17 string str; 18 JSValue[] array; 19 JSValue[string] object; 20 } 21 alias Payload = TaggedAlgebraic!Base; 22 Payload payload; 23 alias payload this; 24 this(T)(T value){ payload = Payload(value); } 25 } 26 27 alias S = SimpleStream!string; 28 29 enum allowedChars = 30 unicode.Cc.add('"', '"'+1).add('\\', '\\'+1).inverted; 31 enum hex = CodepointSet('0', '9'+1, 'a', 'f'+1, 'A', 'F'+1); 32 33 auto jsString(){ 34 with(parsers!S) { 35 auto unescaped_string = set!allowedChars.rep!0; 36 auto escaped_string = any( 37 set!allowedChars, 38 seq(tk!'\\', any( 39 tk!'"', 40 tk!'\\', 41 tk!'/', 42 tk!'b'.map!(_ => cast(dchar)'\b'), 43 tk!'f'.map!(_ => cast(dchar)'\f'), 44 tk!'n'.map!(_ => cast(dchar)'\n'), 45 tk!'r'.map!(_ => cast(dchar)'\r'), 46 tk!'t'.map!(_ => cast(dchar)'\t'), 47 seq(tk!'u', set!hex.rep!(4,4)).map!(x => cast(dchar)to!int(x[1], 16)) 48 )).map!(x => x[1]) 49 ).utfString!(char, 0); 50 auto full = seq(tk!'"', any(unescaped_string, escaped_string), tk!'"'); 51 return full.skipWs.map!(x => x[1]); 52 } 53 } 54 55 unittest { 56 assert(`""`.parse(jsString) == ""); 57 assert(`"abc"`.parse(jsString) == "abc"); 58 } 59 60 auto jsNumber(){ 61 with(parsers!S) { 62 auto digit = range!('0','9'); 63 return seq( 64 tk!'-'.optional, 65 any( 66 tk!'0', 67 seq(range!('1', '9'), digit.rep!0) 68 ), // got to skip whitespace in front of tokens 69 // optional fraction 70 seq( 71 tk!'.', 72 digit.rep!1 73 ).optional, 74 // optional exponent 75 seq( 76 any(tk!'e', tk!'E'), 77 any(tk!'+', tk!'-').optional, 78 digit.rep!1 79 ).optional 80 ).slice.skipWs.map!(x => to!double(x)); 81 } 82 } 83 84 unittest{ 85 assert("0".parse(jsNumber) == 0); 86 assert(" -1e2".parse(jsNumber) == -100); 87 assert("3.1415".parse(jsNumber) == 3.1415); 88 } 89 90 auto jsonParser(){ 91 with(parsers!S) { 92 auto jsValue = dynamic!JSValue; 93 auto pair = seq(jsString, stk!':', jsValue).map!(x => tuple(x[0], x[2])); 94 auto jsObject = seq( 95 tk!'{', 96 seq( 97 pair, seq(stk!',', pair).map!(x => x[1]).aa!0 98 ).optional.map!((x){ 99 if(x.isNull) return null; 100 auto head = x[0]; 101 auto aa = x[1]; 102 aa[head[0]] = head[1]; 103 return aa; 104 }), 105 stk!'}' 106 ).skipWs.map!(x => x[1]); 107 auto jsArray = seq( 108 tk!'[', 109 delimited(jsValue, stk!','), 110 stk!']' 111 ).skipWs.map!(x => x[1]); 112 jsValue = any( 113 jsString.map!(x => JSValue(x)), 114 jsNumber.map!(x => JSValue(x)), 115 jsObject.map!(x => JSValue(x)), 116 jsArray.map!(x => JSValue(x)), 117 literal!"true".map!(x => JSValue(true)), 118 literal!"false".map!(x => JSValue(false)), 119 literal!"null".map!(x => JSValue(null)) 120 ); 121 return jsValue; 122 } 123 } 124 125 unittest { 126 auto v = `{ "a": 12, "b": [1,2,3 ], "c" : true, "null" : null }`.parse(jsonParser); 127 assert(v["a"] == 12); 128 assert(v["b"] == [ 1, 2, 3 ]); 129 assert(v["c"] == true); 130 assert(v["null"] == null); 131 } 132 133 void parseStd(string data){ 134 parseJSON(data); 135 } 136 137 void parsePry(string data){ 138 data.parse(jsonParser); 139 } 140 141 void parseDataJson(string data){ 142 toJSONValue(data); 143 } 144 145 void main(string[] argv){ 146 void usage(){ 147 writeln("Usage:\n./json <file>"); 148 } 149 if(argv.length != 2) return usage(); 150 const iterations = 1_000; 151 string data = cast(string)file.read(argv[1]); 152 version(std_json){ 153 string name = "std.json"; 154 auto dg = () => parseStd(data); 155 } 156 else version(pry){ 157 string name = "pry"; 158 auto dg = () => parsePry(data); 159 } 160 else version(stdx_data_json){ 161 string name = "stdx.data.json"; 162 auto dg = () => parseDataJson(data); 163 } 164 else { 165 static assert(false, "define one version of std_json, pry, stdx_data_json"); 166 } 167 auto results = benchmark!(dg)(iterations); 168 writefln("%s: %s us", name, 169 results[0].usecs / cast(double)iterations); 170 }