summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorJoey Adams2011-03-28 05:18:04 +0000
committerJoey Adams2011-03-28 14:51:33 +0000
commitefe9481e0a005efae7ad54648af1327fdc09ad01 (patch)
tree25474bea1a9d42e142831884f285db47397c6b0e
parentade7ee9de18622821f6fa79bd309fe938669a255 (diff)
json.c: Added explanation of parsing functions/macros
-rw-r--r--json.c83
1 files changed, 61 insertions, 22 deletions
diff --git a/json.c b/json.c
index d05fc45..601fe82 100644
--- a/json.c
+++ b/json.c
@@ -31,15 +31,51 @@ static void append_indent(StringInfo buf, const char *space, size_t space_len
int indent);
/*
-In all of these parsers, s is the parsing cursor, and also used to indicate failure.
-e is the pointer referring to the end of the text (or "slice", if you will).
-
-Functions and macros starting with expect_ return a pointer to the end of the accepted
-fragment. The other macros update s statefully.
-*/
+ * Parsing functions and macros
+ *
+ * The functions and macros that follow are used to simplify the implementation
+ * of the recursive descent parser used for JSON validation. See the
+ * implementation of expect_object() for a good example of them in action.
+ *
+ * These functions/macros use a few unifying concepts:
+ *
+ * * const char *s and const char *e form a "slice" within a string,
+ * where s points to the first character and e points after the last
+ * character. Hence, the length of a slice is e - s. Although it would be
+ * simpler to just get rid of const char *e and rely on strings being
+ * null-terminated, varlena texts are not guaranteed to be null-terminated,
+ * meaning we would have to copy them to parse them.
+ *
+ * * Every expect_* function sees if the beginning of a slice matches what it
+ * expects, and returns the end of the slice on success. To illustrate:
+ *
+ * s expect_number(s, e) e
+ * | | |
+ * {"pi": 3.14159265, "e": 2.71828183, "phi": 1.61803399}
+ *
+ * * When a parse error occurs, s is set to NULL. Moreover, the parser
+ * functions and macros check to see if s is NULL before using it.
+ * This means parser functions built entirely of parser functions can proceed
+ * with the illusion that the input will always be valid, rather than having
+ * to do a NULL check on every line (see expect_number, which has no explicit
+ * checks). However, one must ensure that the parser will always halt,
+ * even in the NULL case.
+ *
+ * Bear in mind that while pop_*, optional_*, and skip_* update s,
+ * the expect_* functions do not. Example:
+ *
+ * s = expect_char(s, e, '{');
+ * s = expect_space(s, e);
+ *
+ * if (optional_char(s, e, '}'))
+ * return s;
+ *
+ * Also, note that functions traversing an already-validated JSON text
+ * can take advantage of the assumption that the input is valid.
+ * For example, stringify_value does not perform NULL checks, nor does it
+ * check if s < e before dereferencing s.
+ */
-/* Every expect_ function returns NULL if s == NULL.
- * Normally called like this: s = expect_*(s, e); */
static const char *expect_value(const char *s, const char *e);
static const char *expect_object(const char *s, const char *e);
static const char *expect_array(const char *s, const char *e);
@@ -60,6 +96,21 @@ static const char *expect_space(const char *s, const char *e);
*/
/*
+ * expect_char: Expect the next character to be @c, and consume it.
+ * expect_char_pred: Expect pred(next character) to hold, and consume it.
+ * expect_char_cond: Expect a character to be available and cond to hold, and consume.
+ * expect_eof: Expect there to be no more input left.
+ *
+ * These macros, like any expect_ macros/functions, return a new pointer
+ * rather than updating @s.
+ */
+#define expect_char(s, e, c) expect_char_cond(s, e, *(s) == (c))
+#define expect_char_pred(s, e, pred) expect_char_cond(s, e, pred(*(s)))
+#define expect_char_cond(s, e, cond) \
+ ((s) != NULL && (s) < (e) && (cond) ? (s) + 1 : NULL)
+#define expect_eof(s, e) ((s) != NULL && (s) == (e) ? (s) : NULL)
+
+/*
* next_char: Get the next character, but do not consume it.
* next_char_pred: Apply pred to the next character.
* next_char_cond: Evaluate cond if a character is available.
@@ -85,19 +136,6 @@ static const char *expect_space(const char *s, const char *e);
((s) != NULL && (s) < (e) ? (s)++, pred((s)[-1]) : false)
/*
- * expect_char: Expect the next character to be @c, and consume it.
- * expect_char_pred: Expect pred(next character) to hold, and consume it.
- * expect_char_cond: Expect a character to be available and cond to hold, and consume.
- *
- * These macros, like any expect_ macros/functions, return a new pointer
- * rather than updating @s.
- */
-#define expect_char(s, e, c) expect_char_cond(s, e, *(s) == (c))
-#define expect_char_pred(s, e, pred) expect_char_cond(s, e, pred(*(s)))
-#define expect_char_cond(s, e, cond) \
- ((s) != NULL && (s) < (e) && (cond) ? (s) + 1 : NULL)
-
-/*
* optional_char: If the next character is @c, consume it.
* optional_char_pred: If pred(next character) holds, consume it.
* optional_char_cond: If a character is available, and cond holds, consume.
@@ -147,8 +185,9 @@ json_validate(const char *str, size_t length)
s = expect_space(s, e);
s = expect_value(s, e);
s = expect_space(s, e);
+ s = expect_eof(s, e);
- return s != NULL && s == e;
+ return s != NULL;
}
/*