1 
2 module ws.decode;
3 
4 import std.stdio, std.conv, ws.io, ws.string;
5 
6 
7 private {
8 
9 	alias void delegate(string, string, bool) CallbackText;
10 	alias void delegate(double) CallbackNum;
11 	alias void delegate(string) Callback;
12 	
13 	bool isCommandChar(char c){
14 	    // return find(" \t\n;{}", c).empty;
15 		return c != ' ' && c != '\t' && c != '\n' && c != ';' && c != '{' && c != '}';
16 	}
17 
18 	bool isContentChar(char c){
19 	    // return find("\n\;{}", c).empty;
20 		return c != '\n' && c != ';' && c != '{' && c != '}';
21 	}
22 
23 }
24 
25 
26 bool isNumberChar(char c){
27 	return (c>=cast(int)'0' && c<=cast(int)'9') || c=='-' || c=='+' || c=='.';
28 }
29 
30 
31 class Decode {
32 
33 
34 	private {
35 		void error(long line, string s){
36 			throw new Exception("[" ~ tostring(line) ~ "]: " ~ s);
37 		}
38 
39 		string queue;
40 		string comments;
41 		CallbackText callback;
42 		bool wholeBlock = true;
43 		File* myFile;
44 	}
45 
46 	static void text(string text, CallbackText callback){
47 		new Decode(false, text ~ '\n', callback);
48 	}
49 
50 
51 	static void file(string path, CallbackText callback){
52 		try
53 			new Decode(true, path, callback);
54 		catch(Exception e){
55 			throw new Exception("Error in file \"" ~ path ~ "\"", e);
56 		}
57 	}
58  
59 
60     static void file(string path, Callback[string] list, Callback[string] blist = Callback[string].init){
61         file(path, (name, value, block){
62             if(!block){
63 				if(name !in list)
64 					throw new Exception("No handler for \"" ~ name ~ "\"");
65 				list[name](value);
66             }else{
67             	if(name !in blist)
68             		throw new Exception("No handler for \"" ~ name ~ "\" as block");
69             	blist[name](value);
70             }
71         });
72     }
73     
74 
75 	this(bool isFile, string what, CallbackText cb){
76 		callback = cb;
77 		if(isFile){
78 			myFile = new File(what, "r");
79 			getSome();
80 		}else
81 			queue = what;
82 		process();
83 		scope(exit)
84 			if(myFile)
85 				myFile.close();
86 	}
87 
88 
89 	bool getSome(){
90 		if(!myFile || myFile.eof())
91 			return false;
92 
93 		size_t i = 0;
94 		char[1] buf;
95 		while(!myFile.eof() && i++ < 200){
96 			myFile.rawRead(buf);
97 			if(buf.length){
98 				auto c = buf[0];
99 				if(c == '\r')
100 					continue;
101 				queue ~= c;
102 			}else{
103 				queue ~= '\n';
104 				break;
105 			}
106 		}
107 		return true;
108 	}
109 
110 
111 	void process(){
112 		string[2] data;
113 		size_t state = 0;
114 		long currentLevel = 0;
115 		bool inString = false;
116 		bool inComment = false;
117 		int line = 1;
118 
119 		for(size_t i=0; i<queue.length; ++i){
120 			char c = queue[i];
121 
122 			if(c == '\n')
123 				line++;
124 
125 			if(inComment){
126 				if(c == '\n')
127 					inComment = false;
128 				else {
129 					if(i == queue.length-1 && !getSome())
130 						throw new Exception("something failed hard");
131 					continue;
132 				}
133 			}else if(c == '#'){
134 				inComment = true;
135 			}else if(c == '\"'){
136 				inString = !inString;
137 
138 			}else if((state ? c.isContentChar() : c.isCommandChar()) || inString || currentLevel>1 || (currentLevel==1 && c != '}')){
139 				data[state] ~= c;
140 
141 			}else if(data[0].strip.length && !state){
142 				state = 1;
143 				
144 			}else if(state){
145 				if(!currentLevel && data[1].length){
146 					callback(data[0].strip, data[1].strip, false);
147 					state = 0;
148 					data = ["", ""];
149 				}
150 			}
151 
152 			if(!inComment && !inString){
153 				if(c == '{'){
154 					state = 1;
155 					++currentLevel;
156 				}else if(c == '}'){
157 					--currentLevel;
158 					if(!currentLevel){
159 						callback(data[0].strip, data[1].strip, true);
160 						state = 0;
161 						data = ["", ""];
162 					}else if(currentLevel < 0)
163 						error(line, "Too many }");
164 				}
165 			}
166 
167 			if(i == queue.length-1 && !getSome()){
168 				if(currentLevel)
169 					error(line, "Unfinished {} block in text");
170 				else if(data[0].length)
171 					error(line, "Unfinished command, queue: %s".format(queue));
172 				else if(data[1].length)
173 					error(line, "Unfinished argument");
174 			}
175 
176 		}
177 	}
178 
179 };
180 
181 
182 
183 unittest {
184 	string t =
185 			"level1 {\n" ~
186 			"	level2item1 cookies\n" ~
187 			"	level2item2 hurr\n" ~
188 			"#	level2comment awesomeness\n" ~
189 			"	level3block {\n" ~
190 			"		a 1\n" ~
191 			"	}\n" ~
192 			"}";
193 			
194 	Decode.text(t, delegate(string cmd, string args, bool b){
195 		assert(b && cmd == "level1");
196 		Decode.text(args, delegate(string cmd2, string args2, bool b2){
197 			assert(b2 == (cmd2 == "level3block"));
198 			if(cmd2 == "level2item1")
199 				assert(args2 == "cookies");
200 			else if(cmd2 == "level2item2")
201 				assert(args2 == "hurr");
202 			else if(cmd2 == "level3block"){
203 				assert(args2.strip() == "a 1");
204 			}else
205 				assert(0);
206 		});
207 	});
208 
209 	string anonblocks =
210 			"level1 {\n" ~
211 			"	{\n" ~
212 			"		a 1\n" ~
213 			"	 }\n" ~
214 			"	{ b 2 }\n" ~
215 			"}\n";
216 
217 	Decode.text(anonblocks, delegate(string cmd, string args, bool b){
218 		assert(b && cmd == "level1");
219 		Decode.text(args, delegate(string cmd2, string args2, bool b2){
220 			assert(b2);
221 			Decode.text(args2, delegate(string cmd3, string args3, bool b3){
222 				if(cmd3 == "a")
223 					assert(args3 == "1");
224 				else if(cmd3 == "b")
225 					assert(args3 == "2");
226 				else
227 					assert(false);
228 			});
229 		});
230 	});
231 }
232 
233 
234 void toNumbers(string text, CallbackNum callback){
235 	string n;
236 	foreach(i, c; text){
237 		if(isNumberChar(c)){
238 			n ~= c;
239 			if(i == text.length-1)
240 				callback(to!real(n));
241 		}else if(n.length){
242 			callback(to!real(n));
243 			n = "";
244 		}
245 	}
246 }
247 
248 
249 double[] toNumbers(string text){
250 	size_t current;
251 	double[] numbers;
252 	string n;
253 	foreach(i, c; text){
254 		if(isNumberChar(c)){
255 			n ~= c;
256 			if(i == text.length-1)
257 				numbers ~= to!real(n);
258 		}else if(n.length){
259 			numbers ~= to!real(n);
260 			n = "";
261 		}
262 	}
263 	return numbers;
264 }
265