library(json) describes JSON as a definite clause grammar, a set of rules
that relate characters to a Prolog term. Because a grammar is a relation, the
same rules parse JSON text into a term and generate JSON text from a term.
Run it in the playground · Source
How does a JSON document look as a Prolog term, and can that term be turned back into JSON text? The example parses a small object, including an emoji written as a pair of UTF-16 escapes, and generates a second object.
JSON values map to tagged Prolog terms:
pairs([Key-Value, ...]);list([...]);string(Chars) and numbers are number(N);boolean(true) or boolean(false), and null is null.The tags keep JSON’s types visible. A JSON string and a JSON number never get confused, even when they print alike.
json_example(parsed, JSON) :-
phrase(json_chars(JSON), "{\"name\":\"Ada\",\"active\":true,\"scores\":[3,5,8],\"emoji\":\"\\uD83D\\uDE00\"}").
json_example(generated, Chars) :-
once(phrase(json_chars(pairs([
string("project")-string("EyeProlog"),
string("ok")-boolean(true)
])), Chars)).
phrase/2 runs a grammar over a character list. The first clause supplies
the text and asks for the term; the second supplies the term and asks for the
text. once/1 keeps the first rendering, since a grammar may allow several
spellings of the same value.
eyeprolog examples/json.pl
json_example(parsed, pairs([string("name") - string("Ada"), string("active") - boolean(true), string("scores") - list([number(3), number(5), number(8)]), string("emoji") - string("😀")])).
json_example(generated, "{\"project\":\"EyeProlog\",\"ok\":true}").
The surrogate pair 😀 is combined into the single character 😀,
and the generated object comes out as compact JSON text.
eyeprolog --proof examples/json.pl
step(once(phrase(json_chars(pairs([string("project") - string("EyeProlog"), string("ok") - boolean(true)])), "{\"project\":\"EyeProlog\",\"ok\":true}")),
control,
[],
[phrase(json_chars(pairs([string("project") - string("EyeProlog"), string("ok") - boolean(true)])), "{\"project\":\"EyeProlog\",\"ok\":true}")]).
The generated answer rests on once/1, recorded as a control step whose
only use is the grammar call it committed to, with the text already filled in.
condition('C1', resolution, ok, 2).
condition('C5', re_decision, ok, 1).
obligation(builtin, theory_scoped, phrase(json_chars(pairs([string("project") - string("EyeProlog"), string("ok") - boolean(true)])), "{\"project\":\"EyeProlog\",\"ok\":true}")).
verdict(checked_with_obligations).
The two json_example/2 rules replay, and the once/1 step is confirmed to
use one of its allowed alternatives. The two grammar calls are recorded as
theory-scoped obligations: they depend on the bundled JSON library rather
than on this program’s clauses, so the checker names them as trusted premises.
[1, "two", null] and predict the term before running it.list([number(1), boolean(false)]).Learn more in The Art of EyeProlog, Chapter 39.