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