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 inQuote = 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 				inQuote = !inQuote;
137 
138 			}else if((state ? c.isContentChar() : c.isCommandChar()) || inQuote || currentLevel>1 || (currentLevel==1 && c != '}')){
139 				data[state] ~= c;
140 
141 			}else if(data[0].length && !state){
142 				state = 1;
143 				
144 			}else if(state){
145 				if(!currentLevel && data[1].length){
146 					callback(data[0], data[1], false);
147 					state = 0;
148 					data = ["", ""];
149 				}
150 			}
151 
152 			if(!inComment && !inQuote){
153 				if(c == '{'){
154 					++currentLevel;
155 				}else if(c == '}'){
156 					--currentLevel;
157 					if(!currentLevel){
158 						callback(data[0], data[1], true);
159 						state = 0;
160 						data = ["", ""];
161 					}else if(currentLevel < 0)
162 						error(line, "Too many }");
163 				}
164 			}
165 
166 			if(i == queue.length-1 && !getSome()){
167 				if(currentLevel)
168 					error(line, "Unfinished {} block in text");
169 				else if(data[0].length)
170 					error(line, "Unfinished command, queue: %s".format(queue));
171 				else if(data[1].length)
172 					error(line, "Unfinished argument");
173 			}
174 
175 		}
176 	}
177 
178 };
179 
180 
181 
182 unittest {
183 	string t =
184 			"level1 {\n"
185 			"	level2item1 cookies\n"
186 			"	level2item2 hurr\n"
187 			"#	level2comment awesomeness\n"
188 			"}";
189 			
190 	Decode.text(t, delegate(string cmd, string args, bool b){
191 		assert(b && cmd == "level1");
192 		Decode.text(args, delegate(string cmd2, string args2, bool b2){
193 			assert(!b2);
194 			if(cmd2 == "level2item1")
195 				assert(args2 == "cookies");
196 			else if(cmd2 == "level2item2")
197 				assert(args2 == "hurr");
198 			else
199 				assert(0);
200 		});
201 	});
202 }
203 
204 
205 void toNumbers(string text, CallbackNum callback){
206 	string n;
207 	foreach(i, c; text){
208 		if(isNumberChar(c)){
209 			n ~= c;
210 			if(i == text.length-1)
211 				callback(to!real(n));
212 		}else if(n.length){
213 			callback(to!real(n));
214 			n = "";
215 		}
216 	}
217 }
218 
219 
220 double[] toNumbers(string text){
221 	size_t current;
222 	double[] numbers;
223 	string n;
224 	foreach(i, c; text){
225 		if(isNumberChar(c)){
226 			n ~= c;
227 			if(i == text.length-1)
228 				numbers ~= to!real(n);
229 		}else if(n.length){
230 			numbers ~= to!real(n);
231 			n = "";
232 		}
233 	}
234 	return numbers;
235 }
236