Code Examples 🔗
Example programs for the scanner and parser are available in the examples directory of the Judo source distribution. The two examples provided are scanner.c and parser.c which demonstrate how to use the scanner and parser, respectively. The most noteworthy parts of their source code are explored in the following subsections.
The example programs are automatically built as part of Judo’s compilation process unless explicitly excluded at configuration time.
Scanner Example 🔗
The first example program uses the scanner API to print each semantic element, e.g. notable marker points within the JSON source text, on its own line.
The program begins by reading JSON source text from stdin
using the judo_readstdin
function. Note that the judo_readstdin
function is not part of the Judo API. It is an internal function used by the examples and command-line interface to read from stdin
in a portable way. Its source code is available here.
size_t json_len = 0;
const char *json = judo_readstdin(&json_len);
The program finds semantic elements incrementally using the judo_scan function. This function scans the JSON source text for the next semantic element. The element can be accessed on the judo_stream structure which is responsible for maintaining the scanners state.
The judo_scan function is repeatedly called until either an error occurs or until all semantic elements have been read. The latter is determined by the JUDO_EOF element.
struct judo_stream stream = {0};
enum judo_result result;
for (;;)
{
result = judo_scan(&stream, json, json_len);
if (result == JUDO_SUCCESS)
{
if (stream.element == JUDO_EOF)
{
break;
}
process_element(stream, json);
}
else
{
fprintf(stderr, "error: %s\n", stream.error);
return 1;
}
}
Each semantic element is printed to stdout
. If an error occurs, the semantic elements printed before the error will have already been processed. This is important to note because, in your own application, you may encounter an error after processing some semantic elements, which may require halting or rolling back work performed from previously processed semantic elements. If this is a concern, it is recommended to first iterate through all semantic elements to check for errors, and then iterate through them again to process them, knowing that no errors are present.
switch (stream.element)
{
case JUDO_NULL: puts("null"); break;
case JUDO_TRUE: puts("true"); break;
case JUDO_FALSE: puts("false"); break;
case JUDO_ARRAY_PUSH: puts("[push]"); break;
case JUDO_ARRAY_POP: puts("[pop]"); break;
case JUDO_OBJECT_PUSH: puts("{push}"); break;
case JUDO_OBJECT_POP: puts("{pop}"); break;
case JUDO_NUMBER:
printf("number: %.*s\n", stream.where.length, &json[stream.where.offset]);
break;
case JUDO_STRING:
printf("string: %.*s\n", stream.where.length, &json[stream.where.offset]);
break;
case JUDO_OBJECT_NAME:
printf("{name: %.*s}\n", stream.where.length, &json[stream.where.offset]);
break;
default:
break;
}
Parser Example 🔗
The second example program uses the parser API to print the JSON structure to stdout
in a compact form.
The program begins by reading JSON source text from stdin
. Like the scanner example, it uses the judo_readstdin
function to read from stdin
in a portable way.
size_t json_len = 0;
const char *json = judo_readstdin(&json_len);
The judo_parse function is used to parse the JSON source text into an in-memory tree structure. Each node of the tree represents a JSON value with the root being the top-level JSON value (often an object or array). Once processing is complete, the tree must be freed by passing the root value to the judo_free function.
struct judo_error error = {0};
struct judo_value *root;
enum judo_result result = judo_parse(json, json_len, &root, &error, NULL, memfunc);
if (result == JUDO_SUCCESS)
{
print_tree(json, root);
judo_free(root, NULL, memfunc);
}
else
{
fprintf(stderr, "error: %s\n", error.description);
return 1;
}
Both judo_parse and judo_free require you to provide your own dynamic memory allocator implementation. In Judo, this is a single function that behaves like malloc
or free
depending on its arguments. The parser example implements the function using the C standard memory management routines.
void *memfunc(void *user_data, void *ptr, size_t size)
{
if (ptr == NULL)
{
return malloc(size);
}
else
{
free(ptr);
return NULL;
}
}
Now that the tree is in memory, it’s time to traverse it. In the example, the traversal is handled by the print_tree
function. Traversal is implemented recursively.
The way a JSON value is printed depends on its type (null, boolean, number, string, array, or object). For primitive types, their lexeme is printed, while compound types require more specific processing.
switch (judo_gettype(value))
{
case JUDO_TYPE_NULL:
case JUDO_TYPE_BOOL:
case JUDO_TYPE_NUMBER:
case JUDO_TYPE_STRING:
span = judo_value2span(value);
printf("%.*s", span.length, &source[span.offset]);
break;
// [cont...]
Arrays are processed with the judo_first and judo_next functions, which iterate over each element of the array. In the example, the program passes each element of the array to the print_tree
function for recursive processing.
case JUDO_TYPE_ARRAY:
putchar('[');
elem = judo_first(value);
while (elem != NULL)
{
print_tree(source, elem);
if (judo_next(elem) != NULL)
{
putchar(',');
}
elem = judo_next(elem);
}
putchar(']');
break;
// [cont...]
Objects are processed with the judo_membfirst and judo_membnext functions, which iterate over each member of the object.
Each member (or “property” in JavaScript terminology) consists of two parts: the name and the value. The name, or “key” in JavaScript, is the string used to look up the member’s value. The value is a JSON value, represented as a judo_value, which, in this example, is passed to print_tree
for recursive processing.
case JUDO_TYPE_OBJECT:
putchar('{');
member = judo_membfirst(value);
while (member != NULL)
{
span = judo_name2span(member);
printf("%.*s:", span.length, &source[span.offset]);
print_tree(source, judo_membvalue(member));
if (judo_membnext(member) != NULL)
{
putchar(',');
}
member = judo_membnext(member);
}
putchar('}');
break;
}