1 /* 2 * Copyright 2014, Augustin Cavalier (waddlesplash) 3 * Copyright 2014, Stephan Aßmus <superstippi@gmx.de> 4 * Distributed under the terms of the MIT License. 5 */ 6 7 8 #include <Json.h> 9 10 #include <stdio.h> 11 #include <stdlib.h> 12 13 #include <MessageBuilder.h> 14 #include <UnicodeChar.h> 15 16 17 // #pragma mark - Public methods 18 19 namespace BPrivate { 20 21 22 class ParseException { 23 public: 24 ParseException(int32 position, BString error) 25 : 26 fPosition(position), 27 fError(error), 28 fReturnCode(B_BAD_DATA) 29 { 30 } 31 32 ParseException(int32 position, status_t returnCode) 33 : 34 fPosition(position), 35 fError(""), 36 fReturnCode(returnCode) 37 { 38 } 39 40 void PrintToStream() const { 41 const char* error; 42 if (fError.Length() > 0) 43 error = fError.String(); 44 else 45 error = strerror(fReturnCode); 46 printf("Parse error at %" B_PRIi32 ": %s\n", fPosition, error); 47 } 48 49 status_t ReturnCode() const 50 { 51 return fReturnCode; 52 } 53 54 private: 55 int32 fPosition; 56 BString fError; 57 status_t fReturnCode; 58 }; 59 60 61 status_t 62 BJson::Parse(BMessage& message, const char* JSON) 63 { 64 BString temp(JSON); 65 return Parse(message, temp); 66 } 67 68 69 status_t 70 BJson::Parse(BMessage& message, BString& JSON) 71 { 72 try { 73 _Parse(message, JSON); 74 return B_OK; 75 } catch (ParseException e) { 76 e.PrintToStream(); 77 return e.ReturnCode(); 78 } 79 return B_ERROR; 80 } 81 82 83 // #pragma mark - Private methods 84 85 86 void 87 BJson::_Parse(BMessage& message, BString& JSON) 88 { 89 BMessageBuilder builder(message); 90 int32 pos = 0; 91 int32 length = JSON.Length(); 92 93 /* Locals used by the parser. */ 94 // Keeps track of the hierarchy (e.g. "{[{{") that has 95 // been read in. Allows the parser to verify that openbraces 96 // match up to closebraces and so on and so forth. 97 BString hierarchy(""); 98 // Stores the key that was just read by the string parser, 99 // in the case that we are parsing a map. 100 BString key(""); 101 102 // TODO: Check builder return codes and throw exception, or 103 // change builder implementation/interface to throw exceptions 104 // instead of returning errors. 105 // TODO: Elimitate more duplicated code, for example by moving 106 // more code into _ParseConstant(). 107 108 while (pos < length) { 109 switch (JSON[pos]) { 110 case '{': 111 hierarchy += "{"; 112 113 if (hierarchy != "{") { 114 if (builder.What() == JSON_TYPE_ARRAY) 115 builder.PushObject(builder.CountNames()); 116 else { 117 builder.PushObject(key.String()); 118 key = ""; 119 } 120 } 121 122 builder.SetWhat(JSON_TYPE_MAP); 123 break; 124 125 case '}': 126 if (hierarchy.EndsWith("{") && hierarchy.Length() != 1) { 127 hierarchy.Truncate(hierarchy.Length() - 1); 128 builder.PopObject(); 129 } else if (hierarchy.Length() == 1) 130 return; // End of the JSON data 131 else 132 throw ParseException(pos, "Unmatched closebrace }"); 133 134 break; 135 136 case '[': 137 hierarchy += "["; 138 139 if (builder.What() == JSON_TYPE_ARRAY) 140 builder.PushObject(builder.CountNames()); 141 else { 142 builder.PushObject(key.String()); 143 key = ""; 144 } 145 146 builder.SetWhat(JSON_TYPE_ARRAY); 147 break; 148 149 case ']': 150 if (hierarchy.EndsWith("[")) { 151 hierarchy.Truncate(hierarchy.Length() - 1); 152 builder.PopObject(); 153 } else { 154 BString error("Unmatched closebrace ] hierarchy: "); 155 error << hierarchy; 156 throw ParseException(pos, error); 157 } 158 159 break; 160 161 case 't': 162 { 163 if (builder.What() != JSON_TYPE_ARRAY && key.Length() == 0) { 164 throw ParseException(pos, 165 "'true' cannot be a key, it can only be a value"); 166 } 167 168 if (_ParseConstant(JSON, pos, "true")) { 169 if (builder.What() == JSON_TYPE_ARRAY) 170 key.SetToFormat("%" B_PRIu32, builder.CountNames()); 171 builder.AddBool(key.String(), true); 172 key = ""; 173 } else 174 throw ParseException(pos, "Unexpected 't'"); 175 176 break; 177 } 178 179 case 'f': 180 { 181 if (builder.What() != JSON_TYPE_ARRAY && key.Length() == 0) { 182 throw ParseException(pos, 183 "'false' cannot be a key, it can only be a value"); 184 } 185 186 if (_ParseConstant(JSON, pos, "false")) { 187 if (builder.What() == JSON_TYPE_ARRAY) 188 key.SetToFormat("%" B_PRIu32, builder.CountNames()); 189 builder.AddBool(key.String(), false); 190 key = ""; 191 } else 192 throw ParseException(pos, "Unexpected 'f'"); 193 194 break; 195 } 196 197 case 'n': 198 { 199 if (builder.What() != JSON_TYPE_ARRAY && key.Length() == 0) { 200 throw ParseException(pos, 201 "'null' cannot be a key, it can only be a value"); 202 } 203 204 if (_ParseConstant(JSON, pos, "null")) { 205 if (builder.What() == JSON_TYPE_ARRAY) 206 key.SetToFormat("%" B_PRIu32, builder.CountNames()); 207 builder.AddPointer(key.String(), (void*)NULL); 208 key = ""; 209 } else 210 throw ParseException(pos, "Unexpected 'n'"); 211 212 break; 213 } 214 215 case '"': 216 if (builder.What() != JSON_TYPE_ARRAY && key.Length() == 0) 217 key = _ParseString(JSON, pos); 218 else if (builder.What() != JSON_TYPE_ARRAY && key.Length() > 0) { 219 builder.AddString(key, _ParseString(JSON, pos)); 220 key = ""; 221 } else if (builder.What() == JSON_TYPE_ARRAY) { 222 key << builder.CountNames(); 223 builder.AddString(key, _ParseString(JSON, pos)); 224 key = ""; 225 } else 226 throw ParseException(pos, "Internal error at encountering \""); 227 228 break; 229 230 case '+': 231 case '-': 232 case '0': 233 case '1': 234 case '2': 235 case '3': 236 case '4': 237 case '5': 238 case '6': 239 case '7': 240 case '8': 241 case '9': 242 { 243 if (builder.What() != JSON_TYPE_ARRAY && key.Length() == 0) { 244 throw ParseException(pos, 245 "Numbers cannot be keys, they can only be values"); 246 } 247 248 if (builder.What() == JSON_TYPE_ARRAY) 249 key << builder.CountNames(); 250 251 double number = _ParseNumber(JSON, pos); 252 builder.AddDouble(key.String(), number); 253 254 key = ""; 255 break; 256 } 257 258 case ':': 259 case ',': 260 default: 261 // No need to do anything here. 262 break; 263 } 264 pos++; 265 } 266 267 throw ParseException(pos, "Unexpected end of document"); 268 } 269 270 271 BString 272 BJson::_ParseString(BString& JSON, int32& pos) 273 { 274 if (JSON[pos] != '"') // Verify we're at the start of a string. 275 return BString(""); 276 pos++; 277 278 BString str; 279 while (JSON[pos] != '"') { 280 if (JSON[pos] == '\\') { 281 pos++; 282 switch (JSON[pos]) { 283 case 'b': 284 str += "\b"; 285 break; 286 287 case 'f': 288 str += "\f"; 289 break; 290 291 case 'n': 292 str += "\n"; 293 break; 294 295 case 'r': 296 str += "\r"; 297 break; 298 299 case 't': 300 str += "\t"; 301 break; 302 303 case 'u': // 4-byte hexadecimal Unicode char (e.g. "\uffff") 304 { 305 uint intValue; 306 BString substr; 307 JSON.CopyInto(substr, pos + 1, 4); 308 if (sscanf(substr.String(), "%4x", &intValue) != 1) 309 return str; 310 // We probably hit the end of the string. 311 // This probably should be counted as an error, 312 // but for now let's soft-fail instead of hard-fail. 313 314 char character[20]; 315 char* ptr = character; 316 BUnicodeChar::ToUTF8(intValue, &ptr); 317 str.AppendChars(character, 1); 318 pos += 4; 319 break; 320 } 321 322 default: 323 str += JSON[pos]; 324 break; 325 } 326 } else 327 str += JSON[pos]; 328 pos++; 329 } 330 331 return str; 332 } 333 334 335 double 336 BJson::_ParseNumber(BString& JSON, int32& pos) 337 { 338 BString value; 339 340 while (true) { 341 switch (JSON[pos]) { 342 case '+': 343 case '-': 344 case 'e': 345 case 'E': 346 case '0': 347 case '1': 348 case '2': 349 case '3': 350 case '4': 351 case '5': 352 case '6': 353 case '7': 354 case '8': 355 case '9': 356 case '.': 357 value += JSON[pos]; 358 pos++; 359 continue; 360 361 default: 362 // We've reached the end of the number, so decrement the 363 // "pos" value so that the parser picks up on the next char. 364 pos--; 365 break; 366 } 367 break; 368 } 369 370 return strtod(value.String(), NULL); 371 } 372 373 374 bool 375 BJson::_ParseConstant(BString& JSON, int32& pos, const char* constant) 376 { 377 BString value; 378 JSON.CopyInto(value, pos, strlen(constant)); 379 if (value == constant) { 380 // Increase pos by the remainder of the constant, pos will be 381 // increased for the first letter in the main parse loop. 382 pos += strlen(constant) - 1; 383 return true; 384 } else 385 return false; 386 } 387 388 389 } // namespace BPrivate 390 391