/jansson/src/variadic.c

http://github.com/nicolasff/webdis · C · 568 lines · 438 code · 95 blank · 35 comment · 70 complexity · 52ef6ec924eddbfb415cbc5051490968 MD5 · raw file

  1. /*
  2. * Copyright (c) 2009, 2010 Petri Lehtinen <petri@digip.org>
  3. * Copyright (c) 2010 Graeme Smecher <graeme.smecher@mail.mcgill.ca>
  4. *
  5. * Jansson is free software; you can redistribute it and/or modify
  6. * it under the terms of the MIT license. See LICENSE for details.
  7. */
  8. #include <stdarg.h>
  9. #include <string.h>
  10. #include <assert.h>
  11. #include <jansson.h>
  12. #include "jansson_private.h"
  13. json_t *json_pack(json_error_t *error, const char *fmt, ...) {
  14. int fmt_length = strlen(fmt);
  15. va_list ap;
  16. /* Keep a stack of containers (lists and objects) */
  17. int depth = 0;
  18. json_t **stack = NULL;
  19. /* Keep a list of objects we create in case of error */
  20. int free_count = 0;
  21. json_t **free_list = NULL;
  22. json_t *cur = NULL; /* Current container */
  23. json_t *root = NULL; /* root object */
  24. json_t *obj = NULL;
  25. char *key = NULL; /* Current key in an object */
  26. char *s;
  27. int line = 1;
  28. /* Allocation provisioned for worst case */
  29. stack = calloc(fmt_length, sizeof(json_t *));
  30. free_list = calloc(fmt_length, sizeof(json_t *));
  31. jsonp_error_init(error, "");
  32. if(!stack || !free_list)
  33. goto out;
  34. va_start(ap, fmt);
  35. while(*fmt) {
  36. switch(*fmt) {
  37. case '\n':
  38. line++;
  39. break;
  40. case ' ': /* Whitespace */
  41. break;
  42. case ',': /* Element spacer */
  43. if(!root)
  44. {
  45. jsonp_error_set(error, line, -1,
  46. "Unexpected COMMA precedes root element!");
  47. root = NULL;
  48. goto out;
  49. }
  50. if(!cur)
  51. {
  52. jsonp_error_set(error, line, -1,
  53. "Unexpected COMMA outside a list or object!");
  54. root = NULL;
  55. goto out;
  56. }
  57. if(key)
  58. {
  59. jsonp_error_set(error, line, -1,
  60. "Expected KEY, got COMMA!");
  61. root = NULL;
  62. goto out;
  63. }
  64. break;
  65. case ':': /* Key/value separator */
  66. if(!key)
  67. {
  68. jsonp_error_set(error, line, -1,
  69. "Got key/value separator without "
  70. "a key preceding it!");
  71. root = NULL;
  72. goto out;
  73. }
  74. if(!json_is_object(cur))
  75. {
  76. jsonp_error_set(error, line, -1,
  77. "Got a key/value separator "
  78. "(':') outside an object!");
  79. root = NULL;
  80. goto out;
  81. }
  82. break;
  83. case ']': /* Close array or object */
  84. case '}':
  85. if(key)
  86. {
  87. jsonp_error_set(error, line, -1,
  88. "OBJECT or ARRAY ended with an "
  89. "incomplete key/value pair!");
  90. root = NULL;
  91. goto out;
  92. }
  93. if(depth <= 0)
  94. {
  95. jsonp_error_set(error, line, -1,
  96. "Too many close-brackets '%c'", *fmt);
  97. root = NULL;
  98. goto out;
  99. }
  100. if(*fmt == ']' && !json_is_array(cur))
  101. {
  102. jsonp_error_set(error, line, -1,
  103. "Stray close-array ']' character");
  104. root = NULL;
  105. goto out;
  106. }
  107. if(*fmt == '}' && !json_is_object(cur))
  108. {
  109. jsonp_error_set(error, line, -1,
  110. "Stray close-object '}' character");
  111. root = NULL;
  112. goto out;
  113. }
  114. cur = stack[--depth];
  115. break;
  116. case '[':
  117. obj = json_array();
  118. goto obj_common;
  119. case '{':
  120. obj = json_object();
  121. goto obj_common;
  122. case 's': /* string */
  123. s = va_arg(ap, char*);
  124. if(!s)
  125. {
  126. jsonp_error_set(error, line, -1,
  127. "Refusing to handle a NULL string");
  128. root = NULL;
  129. goto out;
  130. }
  131. if(json_is_object(cur) && !key)
  132. {
  133. /* It's a key */
  134. key = s;
  135. break;
  136. }
  137. obj = json_string(s);
  138. goto obj_common;
  139. case 'n': /* null */
  140. obj = json_null();
  141. goto obj_common;
  142. case 'b': /* boolean */
  143. obj = va_arg(ap, int) ?
  144. json_true() : json_false();
  145. goto obj_common;
  146. case 'i': /* integer */
  147. obj = json_integer(va_arg(ap, int));
  148. goto obj_common;
  149. case 'f': /* double-precision float */
  150. obj = json_real(va_arg(ap, double));
  151. goto obj_common;
  152. case 'O': /* a json_t object; increments refcount */
  153. obj = va_arg(ap, json_t *);
  154. json_incref(obj);
  155. goto obj_common;
  156. case 'o': /* a json_t object; doesn't increment refcount */
  157. obj = va_arg(ap, json_t *);
  158. goto obj_common;
  159. obj_common: free_list[free_count++] = obj;
  160. /* Root this object to its parent */
  161. if(json_is_object(cur)) {
  162. if(!key)
  163. {
  164. jsonp_error_set(error, line, -1,
  165. "Expected key, got identifier '%c'!", *fmt);
  166. root = NULL;
  167. goto out;
  168. }
  169. json_object_set_new(cur, key, obj);
  170. key = NULL;
  171. }
  172. else if(json_is_array(cur))
  173. {
  174. json_array_append_new(cur, obj);
  175. }
  176. else if(!root)
  177. {
  178. printf("Rooting\n");
  179. root = obj;
  180. }
  181. else
  182. {
  183. jsonp_error_set(error, line, -1,
  184. "Can't figure out where to attach "
  185. "'%c' object!", *fmt);
  186. root = NULL;
  187. goto out;
  188. }
  189. /* If it was a container ('[' or '{'), descend on the stack */
  190. if(json_is_array(obj) || json_is_object(obj))
  191. {
  192. stack[depth++] = cur;
  193. cur = obj;
  194. }
  195. break;
  196. }
  197. fmt++;
  198. }
  199. va_end(ap);
  200. if(depth != 0) {
  201. jsonp_error_set(error, line, -1,
  202. "Missing object or array close-brackets in format string");
  203. root = NULL;
  204. goto out;
  205. }
  206. /* Success: don't free everything we just built! */
  207. free_count = 0;
  208. out:
  209. while(free_count)
  210. json_decref(free_list[--free_count]);
  211. if(free_list)
  212. free(free_list);
  213. if(stack)
  214. free(stack);
  215. return(root);
  216. }
  217. int json_unpack(json_t *root, json_error_t *error, const char *fmt, ...) {
  218. va_list ap;
  219. int rv=0; /* Return value */
  220. int line = 1; /* Line number */
  221. /* Keep a stack of containers (lists and objects) */
  222. int depth = 0;
  223. json_t **stack;
  224. int array_index = 0;
  225. char *key = NULL; /* Current key in an object */
  226. json_t *cur = NULL; /* Current container */
  227. json_t *obj = NULL;
  228. int fmt_length = strlen(fmt);
  229. jsonp_error_init(error, "");
  230. /* Allocation provisioned for worst case */
  231. stack = calloc(fmt_length, sizeof(json_t *));
  232. if(!stack)
  233. {
  234. jsonp_error_set(error, line, -1, "Out of memory!");
  235. rv = -1;
  236. goto out;
  237. }
  238. /* Even if we're successful, we need to know if the number of
  239. * arguments provided matches the number of JSON objects.
  240. * We can do this by counting the elements in every array or
  241. * object we open up, and decrementing the count as we visit
  242. * their children. */
  243. int unvisited = 0;
  244. va_start(ap, fmt);
  245. while(*fmt)
  246. {
  247. switch(*fmt)
  248. {
  249. case ' ': /* Whitespace */
  250. break;
  251. case '\n': /* Line break */
  252. line++;
  253. break;
  254. case ',': /* Element spacer */
  255. if(!cur)
  256. {
  257. jsonp_error_set(error, line, -1,
  258. "Unexpected COMMA outside a list or object!");
  259. rv = -1;
  260. goto out;
  261. }
  262. if(key)
  263. {
  264. jsonp_error_set(error, line, -1,
  265. "Expected KEY, got COMMA!");
  266. rv = -1;
  267. goto out;
  268. }
  269. break;
  270. case ':': /* Key/value separator */
  271. if(!json_is_object(cur) || !key)
  272. {
  273. jsonp_error_set(error, line, -1, "Unexpected ':'");
  274. rv = -1;
  275. goto out;
  276. }
  277. break;
  278. case '[':
  279. case '{':
  280. /* Fetch object */
  281. if(!cur)
  282. {
  283. obj = root;
  284. }
  285. else if(json_is_object(cur))
  286. {
  287. if(!key)
  288. {
  289. jsonp_error_set(error, line, -1,
  290. "Objects can't be keys");
  291. rv = -1;
  292. goto out;
  293. }
  294. obj = json_object_get(cur, key);
  295. unvisited--;
  296. key = NULL;
  297. }
  298. else if(json_is_array(cur))
  299. {
  300. obj = json_array_get(cur, array_index);
  301. unvisited--;
  302. array_index++;
  303. }
  304. else
  305. {
  306. assert(0);
  307. }
  308. /* Make sure we got what we expected */
  309. if(*fmt=='{' && !json_is_object(obj))
  310. {
  311. rv = -2;
  312. goto out;
  313. }
  314. if(*fmt=='[' && !json_is_array(obj))
  315. {
  316. rv = -2;
  317. goto out;
  318. }
  319. unvisited += json_is_object(obj) ?
  320. json_object_size(obj) :
  321. json_array_size(obj);
  322. /* Descend */
  323. stack[depth++] = cur;
  324. cur = obj;
  325. key = NULL;
  326. break;
  327. case ']':
  328. case '}':
  329. if(json_is_array(cur) && *fmt!=']')
  330. {
  331. jsonp_error_set(error, line, -1, "Missing ']'");
  332. rv = -1;
  333. goto out;
  334. }
  335. if(json_is_object(cur) && *fmt!='}')
  336. {
  337. jsonp_error_set(error, line, -1, "Missing '}'");
  338. rv = -1;
  339. goto out;
  340. }
  341. if(key)
  342. {
  343. jsonp_error_set(error, line, -1, "Unexpected '%c'", *fmt);
  344. rv = -1;
  345. goto out;
  346. }
  347. if(depth <= 0)
  348. {
  349. jsonp_error_set(error, line, -1, "Unexpected '%c'", *fmt);
  350. rv = -1;
  351. goto out;
  352. }
  353. cur = stack[--depth];
  354. break;
  355. case 's':
  356. if(!key && json_is_object(cur))
  357. {
  358. /* constant string for key */
  359. key = va_arg(ap, char*);
  360. break;
  361. }
  362. /* fall through */
  363. case 'i': /* integer */
  364. case 'f': /* double-precision float */
  365. case 'O': /* a json_t object; increments refcount */
  366. case 'o': /* a json_t object; borrowed reference */
  367. case 'b': /* boolean */
  368. case 'n': /* null */
  369. /* Fetch object */
  370. if(!cur)
  371. {
  372. obj = root;
  373. }
  374. else if(json_is_object(cur))
  375. {
  376. if(!key)
  377. {
  378. jsonp_error_set(error, line, -1,
  379. "Only strings may be used as keys!");
  380. rv = -1;
  381. goto out;
  382. }
  383. obj = json_object_get(cur, key);
  384. unvisited--;
  385. key = NULL;
  386. }
  387. else if(json_is_array(cur))
  388. {
  389. obj = json_array_get(cur, array_index);
  390. unvisited--;
  391. array_index++;
  392. }
  393. else
  394. {
  395. jsonp_error_set(error, line, -1,
  396. "Unsure how to retrieve JSON object '%c'",
  397. *fmt);
  398. rv = -1;
  399. goto out;
  400. }
  401. switch(*fmt)
  402. {
  403. case 's':
  404. if(!json_is_string(obj))
  405. {
  406. jsonp_error_set(error, line, -1,
  407. "Type mismatch! Object wasn't a string.");
  408. rv = -2;
  409. goto out;
  410. }
  411. *va_arg(ap, const char**) = json_string_value(obj);
  412. break;
  413. case 'i':
  414. if(!json_is_integer(obj))
  415. {
  416. jsonp_error_set(error, line, -1,
  417. "Type mismatch! Object wasn't an integer.");
  418. rv = -2;
  419. goto out;
  420. }
  421. *va_arg(ap, int*) = json_integer_value(obj);
  422. break;
  423. case 'b':
  424. if(!json_is_boolean(obj))
  425. {
  426. jsonp_error_set(error, line, -1,
  427. "Type mismatch! Object wasn't a boolean.");
  428. rv = -2;
  429. goto out;
  430. }
  431. *va_arg(ap, int*) = json_is_true(obj);
  432. break;
  433. case 'f':
  434. if(!json_is_number(obj))
  435. {
  436. jsonp_error_set(error, line, -1,
  437. "Type mismatch! Object wasn't a real.");
  438. rv = -2;
  439. goto out;
  440. }
  441. *va_arg(ap, double*) = json_number_value(obj);
  442. break;
  443. case 'O':
  444. json_incref(obj);
  445. /* Fall through */
  446. case 'o':
  447. *va_arg(ap, json_t**) = obj;
  448. break;
  449. case 'n':
  450. /* Don't actually assign anything; we're just happy
  451. * the null turned up as promised in the format
  452. * string. */
  453. break;
  454. default:
  455. jsonp_error_set(error, line, -1,
  456. "Unknown format character '%c'", *fmt);
  457. rv = -1;
  458. goto out;
  459. }
  460. }
  461. fmt++;
  462. }
  463. /* Return 0 if everything was matched; otherwise the number of JSON
  464. * objects we didn't get to. */
  465. rv = unvisited;
  466. out:
  467. va_end(ap);
  468. if(stack)
  469. free(stack);
  470. return(rv);
  471. }
  472. /* vim: ts=4:expandtab:sw=4
  473. */