1 module toml.parser;
2 
3 import toml.grammar;
4 import pegged.grammar;
5 import std.array: split;
6 import std.exception;
7 import std.file : readText;
8 
9 enum TOMLType {
10     String,
11     Integer,
12     Float,
13     Boolean,
14     Datetime,
15     Array,
16     Group
17 };
18 
19 class TOMLException: Exception {
20     this(string msg, string file="parser", ulong line=111) {
21         super(msg, file, line);
22     }
23 }
24 
25 alias enforceTOML = enforceEx!(TOMLException);
26 
27 struct TOMLValue {
28 
29     union Store {
30         string stringv;
31         long intv;
32         float floatv;
33         bool boolv;
34         TOMLValue[] arrayv;
35         TOMLValue[string] keygroups;
36     }
37 
38     private {
39         Store _store;
40         TOMLType _type;
41     }
42 
43     private void assign(T)(T val) {
44         static if( is(T: string) ) {
45             _store.stringv = val;
46             _type = TOMLType.String;
47         } 
48         else static if ( is(T: long) ) {
49             _store.intv = val;
50             _type = TOMLType.Integer;
51         }
52         else static if ( is(T: bool) ) {
53             _store.boolv = val;
54             _type = TOMLType.Boolean;
55         }
56         else static if ( is(T: float) ) {
57             _store.floatv = val;
58             _type = TOMLType.Float;
59         }
60         else 
61             static assert(0, "Unknown type");
62         
63     }
64 
65     private void assign(T)(T val, string key) {
66         static if ( is(T: TOMLValue) ) 
67             _store.keygroups[key] = val;
68         else
69             _store.keygroups[key] = TOMLValue(val);
70     }
71 
72     //
73     // Constructors
74     // -----------------------------------------
75 
76     this(T)(T v) {
77         static if ( is(T: TOMLType) ) 
78             _type = v;
79         else
80             assign(v);
81     }
82 
83     this(T)(ref T v) {
84         static if ( is(T: TOMLValue[]) ) {
85             _store.arrayv = v;
86             _type = TOMLType.Array;
87         }
88         else static if ( is(T: TOMLValue[string]) ) {
89             _store.keygroups = v;
90             _type = TOMLType.Group;
91         }
92     }
93  
94     // 
95     // Operators
96     // ---------------------------------------
97 
98     // Index assign
99     void opIndexAssign(T)(T v, string key) {
100         enforceTOML(_type==TOMLType.Group);
101         assign(v, key);
102     }
103 
104     TOMLValue opIndexAssign(TOMLValue  v, string key) {
105         enforceTOML(_type==TOMLType.Group);
106         _store.keygroups[key] = v;
107         return v;
108     }
109 
110     ref inout(TOMLValue) opIndex(string v) inout {
111         enforceTOML(_type==TOMLType.Group);
112         return _store.keygroups[v];
113     }
114 
115     auto opBinaryRight(string op : "in")(string k) const
116     {
117         enforceTOML(_type==TOMLType.Group);
118         return k in _store.keygroups;
119     }
120 
121 
122     //
123     // Value accessors
124     // ---------------------------------------
125     string str(){
126         enforceTOML(_type==TOMLType.String);
127         return _store.stringv;
128     }
129 
130     long integer() {
131         enforceTOML(_type==TOMLType.Integer);
132         return _store.intv;
133     }
134 
135     TOMLValue[] array() {
136         enforceTOML(_type==TOMLType.Array);
137         return _store.arrayv;
138     }
139 
140     TOMLValue[string] group() {
141         enforceTOML(_type==TOMLType.Group);
142         return _store.keygroups;
143     }
144 
145     auto keys() { 
146         enforceTOML(_type==TOMLType.Group);
147         return _store.keygroups.keys;
148     }
149 
150 }
151 
152 void _toTOMLDictionary(ParseTree p, ref TOMLValue root, string current_header=null) {
153 
154     TOMLValue __valueLine(ParseTree valueNode){ 
155         auto v = valueNode.matches[0];
156         switch (valueNode.name) {
157             case "TOML.IntegerValue": 
158                 return TOMLValue(v.to!int);
159             case "TOML.StringValue": 
160                 return TOMLValue(v.to!string);
161             case "TOML.FloatValue": 
162                 return TOMLValue(v.to!float);
163             case "TOML.BooleanValue":
164                 return TOMLValue(v.to!bool);
165             case "TOML.Array":
166                 TOMLValue[] vals;
167                 //first children is the typed array match
168                 foreach (pc; valueNode.children[0].children) {
169                     vals ~= __valueLine(pc);
170                 }
171                 return TOMLValue(vals);
172             default:
173                 break;
174         }
175         assert(0);
176     }
177 
178     switch (p.name) {
179         case "TOML.ValueLine": 
180             auto name = p.children[0].matches[0];   //Name node
181             auto value = p.children[1].children[0]; //Value node
182             if (current_header==null)
183                 root[name] = __valueLine(value);
184             else {
185                 auto k = current_header.split('.');
186                 if (!(k[0] in root)) root[k[0]] = TOMLValue(TOMLType.Group);
187                 TOMLValue * v = &root[k[0]];
188                 
189                 foreach (t; k[1..$]) {
190                     if (!(t in v._store.keygroups))
191                         v.assign(TOMLValue(TOMLType.Group), t);
192                     v = &(v._store.keygroups[t]);
193                 }
194                 v._store.keygroups[name] = __valueLine(value);
195                 
196             }
197             break;
198         case "TOML.KeyGroup":
199             auto header = p.children[0].children[0].matches[0]; //HeaderName node
200             auto vals = p.children[1..$];
201             foreach (v; vals) {
202                 _toTOMLDictionary(v, root, header);
203             }
204             break;
205         default:
206             break;
207     }
208 }
209 
210 TOMLValue parse(string data) {
211     auto parseTree = TOML(data);
212     TOMLValue dict = TOMLValue(TOMLType.Group);
213     foreach(p; parseTree.children[0].children) {
214         _toTOMLDictionary(p, dict);
215     }
216     return dict;
217 }
218 
219 TOMLValue parseFile(string filename) {
220     return parse(readText(filename));
221 }
222 
223 unittest {
224     import std.stdio;
225 
226     enum TEST1 = `
227         key_string = "string_value"
228         key_array = ["string_1", "string_2"]
229 
230         [servers]
231         a = 12
232 
233         [servers.test]
234         a = 12
235         ports = [1,2,3]
236         `;
237 
238     auto d = parse(TEST1);
239 
240     assert(d["key_string"].str == "string_value");
241     writefln("Servers: %s", d["servers"].keys);
242     writefln("All: %s", d.keys);
243     assert(d["servers"]["a"].integer == 12);
244     assert(d["servers"]["test"]["a"].integer == 12);
245     assert(d["servers"]["test"]["ports"].array[2].integer == 3);
246 
247 }