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