:- use_module(library(lists)).
:- use_module(library(reif)).
-/* The DCGs are written to match the McKeeman Form presented on the right side of https://www.json.org/json-en.html
+/* The DCGs are written to match the McKeeman form presented on the right side of https://www.json.org/json-en.html
almost perfectly. Note that the McKeeman form conflicts some with the pictures on the left side. */
json_chars(Internal) --> json_element(Internal).
different types of values based on their principal functor. The principal functors match the types defined in
the JSON Schema spec here: https://json-schema.org/draft/2020-12/json-schema-validation.html#rfc.section.6.1.1
Down the line we'll incorporate more JSON Schema support, but this is it for now. */
-json_value(object(Assoc)) --> json_object(Assoc).
-json_value(array(List)) --> json_array(List).
-json_value(string(Chars)) --> json_string(Chars).
-json_value(number(Number)) --> json_number(Number).
-json_value(boolean(true)) --> "true".
-json_value(boolean(false)) --> "false".
-json_value(null) --> "null".
+json_value(object(Assoc)) --> json_object(Assoc).
+json_value(array(List)) --> json_array(List).
+json_value(string(Chars)) --> json_string(Chars).
+json_value(number(Number)) --> json_number(Number).
+json_value(boolean(true)) --> "true".
+json_value(boolean(false)) --> "false".
+json_value(null) --> "null".
/* Read Bob Kowalski's "Algorithm = Logic + Control":
https://www.doc.ic.ac.uk/~rak/papers/algorithm%20=%20logic%20+%20control.pdf
Maybe at some point in the future we'll have a library that takes a pure logic character parsing/generating DCG
and 'injects' control strategy into it. We aren't there yet... */
json_object(EmptyAssoc) --> {empty_assoc(EmptyAssoc)}, "{", json_ws, "}".
-json_object(Assoc) -->
- {
- nonvar(Assoc) ->
- \+ empty_assoc(Assoc),
- assoc_to_list(Assoc, [Pair|Pairs])
- ; true
- },
- "{",
- json_members([Pair|Pairs]),
- "}",
- {
- var(Assoc) ->
- list_to_assoc([Pair|Pairs], Assoc)
- ; true
- }.
-
-json_members([Key-Value]) --> json_member(Key, Value).
+json_object(Assoc) -->
+ { ( nonvar(Assoc) ->
+ \+ empty_assoc(Assoc),
+ assoc_to_list(Assoc, [Pair|Pairs])
+ ; true
+ ) },
+ "{",
+ json_members([Pair|Pairs]),
+ "}",
+ { ( var(Assoc) ->
+ list_to_assoc([Pair|Pairs], Assoc)
+ ; true
+ ) }.
+
+json_members([Key-Value]) --> json_member(Key, Value).
json_members([Key-Value | Pairs]) --> json_member(Key, Value), ",", json_members(Pairs).
json_member(Key, Value) --> json_ws, json_string(Key), json_ws, ":", json_element(Value).
-json_array([]) --> "[", json_ws, "]".
+json_array([]) --> "[", json_ws, "]".
json_array([Value|Values]) --> "[", json_elements([Value|Values]), "]".
-json_elements([Value]) --> json_element(Value).
+json_elements([Value]) --> json_element(Value).
json_elements([Value|Values]) --> json_element(Value), ",", json_elements(Values).
json_element(Value) --> json_ws, json_value(Value), json_ws.
json_string(Chars) --> "\"", json_characters(Chars), "\"".
-json_characters("") --> "".
+json_characters("") --> "".
json_characters([Char|Chars]) --> json_character(Char), json_characters(Chars).
/* A directly printable character is defined by the JSON spec as a character between 0020 and 10FFFF except the
If we moved the block containing `char_code/2` up before `[PrintChar]`, we would still be able to generate JSON,
but attempting to parse JSON would cause an instantiation error. */
escape_map([
- '"' - '"',
- ('\\') - ('\\'),
- ('/') - ('/'),
- '\b' - 'b',
- '\f' - 'f',
- '\n' - 'n',
- '\r' - 'r',
- '\t' - 't'
-]).
-
-json_character(PrintChar) -->
- [PrintChar],
- {
- escape_map(EscapeMap),
- \+ member(PrintChar-_, EscapeMap),
- char_code(PrintChar, PrintCharCode),
- PrintCharCode in 32..1114111 /* 20.10FFFF */
- }.
+ '"' - '"',
+ ('\\') - ('\\'),
+ ('/') - ('/'),
+ '\b' - 'b',
+ '\f' - 'f',
+ '\n' - 'n',
+ '\r' - 'r',
+ '\t' - 't' ]).
+
+json_character(PrintChar) -->
+ [PrintChar],
+ { escape_map(EscapeMap),
+ \+ member(PrintChar-_, EscapeMap),
+ char_code(PrintChar, PrintCharCode),
+ PrintCharCode in 32..1114111 /* 20.10FFFF */ }.
json_character(EscapeChar) --> "\\", json_escape(EscapeChar).
json_escape(EscapeChar) -->
- [PrintChar],
- {
- escape_map(EscapeMap),
- member(EscapeChar-PrintChar, EscapeMap)
- }.
+ [PrintChar],
+ { escape_map(EscapeMap),
+ member(EscapeChar-PrintChar, EscapeMap) }.
json_escape(EscapeChar) -->
- "u",
- { /* Logic: Define the domain of the escape character as well as the relationship between the escape character
+ "u",
+ /* Logic: Define the domain of the escape character as well as the relationship between the escape character
and the four hexes */
- [H1, H2, H3, H4] ins 0..15,
- EscapeCharCode in 0..65535,
- EscapeCharCode #= H1 * 16^3 + H2 * 16^2 + H3 * 16 + H4
- },
- { /* Control: Get the code of the escape character if we can. Otherwise we'll end up backtracking over 65,536
- possible hex values.
- Logic: Only the first 32 Unicode characters not escaped in the escape map are eligible for \u-escaping
- when generating. However, we want to be able to parse any of the 65,536 \u-escaped values when parsing. */
- nonvar(EscapeChar) ->
- char_code(EscapeChar, EscapeCharCode),
- EscapeCharCode in 0..31,
- escape_map(EscapeMap),
- \+ member(EscapeChar-_, EscapeMap)
- ; true
- },
- json_hex(H1),
- json_hex(H2),
- json_hex(H3),
- json_hex(H4),
- { /* Control + Logic: Get the escape character atom from the character code computed from the hexes. */
- var(EscapeChar) ->
- char_code(EscapeChar, EscapeCharCode)
- ; true
- }.
+ { [H1, H2, H3, H4] ins 0..15,
+ EscapeCharCode in 0..65535,
+ EscapeCharCode #= H1 * 16^3 + H2 * 16^2 + H3 * 16 + H4,
+ /* Control: Get the code of the escape character if we can. Otherwise we'll end up backtracking over 65,536
+ possible hex values.
+ Logic: Only the first 32 Unicode characters not escaped in the escape map are eligible for \u-escaping
+ when generating. However, we want to be able to parse any of the 65,536 \u-escaped values when parsing. */
+ ( nonvar(EscapeChar) ->
+ char_code(EscapeChar, EscapeCharCode),
+ EscapeCharCode in 0..31,
+ escape_map(EscapeMap),
+ \+ member(EscapeChar-_, EscapeMap)
+ ; true
+ )
+ },
+ json_hex(H1),
+ json_hex(H2),
+ json_hex(H3),
+ json_hex(H4),
+ /* Control + Logic: Get the escape character atom from the character code computed from the hexes. */
+ { ( var(EscapeChar) ->
+ char_code(EscapeChar, EscapeCharCode)
+ ; true
+ ) }.
json_hex(Digit) --> json_digit(Digit).
-json_hex(10) --> "a".
-json_hex(11) --> "b".
-json_hex(12) --> "c".
-json_hex(13) --> "d".
-json_hex(14) --> "e".
-json_hex(15) --> "f".
-json_hex(10) --> "A".
-json_hex(11) --> "B".
-json_hex(12) --> "C".
-json_hex(13) --> "D".
-json_hex(14) --> "E".
-json_hex(15) --> "F".
+json_hex(10) --> "a".
+json_hex(11) --> "b".
+json_hex(12) --> "c".
+json_hex(13) --> "d".
+json_hex(14) --> "e".
+json_hex(15) --> "f".
+json_hex(10) --> "A".
+json_hex(11) --> "B".
+json_hex(12) --> "C".
+json_hex(13) --> "D".
+json_hex(14) --> "E".
+json_hex(15) --> "F".
/* Here we are going to write completely different DCGs for parsing and generating, and rely on built-in
predicates. However, the underlying logic remains the same. */
json_number(Number) -->
- {
- nonvar(Number) ->
- number_chars(Number, NumberChars)
- ; false
- },
+ { ( nonvar(Number) ->
+ number_chars(Number, NumberChars)
+ ; false
+ ) },
NumberChars.
json_number(Number) -->
- {
- var(Number)
- },
+ { var(Number) },
json_sign_noplus(Sign),
json_integer(Integer),
json_fraction(Fraction),
json_exponent(Exponent),
- {
- Number is Sign * (Integer + Fraction) * 10.0 ^ Exponent
- }.
+ { Number is Sign * (Integer + Fraction) * 10.0 ^ Exponent }.
-json_integer(Digit) --> json_digit(Digit).
+json_integer(Digit) --> json_digit(Digit).
json_integer(TotalValue) -->
json_onenine(FirstDigit),
json_digits(RemainingValue, Power),
- {
- TotalValue #= FirstDigit * 10 ^ (Power + 1) + RemainingValue
- }.
+ { TotalValue #= FirstDigit * 10 ^ (Power + 1) + RemainingValue }.
-json_digits(Digit, 0) --> json_digit(Digit).
+json_digits(Digit, 0) --> json_digit(Digit).
json_digits(Value, Power) -->
json_digit(FirstDigit),
json_digits(RemainingValue, NextPower),
- {
- Power #= NextPower + 1,
- Value #= FirstDigit * 10^Power + RemainingValue
- }.
+ { Power #= NextPower + 1,
+ Value #= FirstDigit * 10^Power + RemainingValue }.
-json_digit(0) --> "0".
+json_digit(0) --> "0".
json_digit(Digit) --> json_onenine(Digit).
json_onenine(1) --> "1".
json_onenine(8) --> "8".
json_onenine(9) --> "9".
-json_fraction(0) --> "".
+json_fraction(0) --> "".
json_fraction(Fraction) -->
".",
json_digits(Value, Power),
- {
- Fraction is Value / 10 ^ (Power + 1)
- }.
+ { Fraction is Value / 10 ^ (Power + 1) }.
-json_exponent(0) --> "".
+json_exponent(0) --> "".
json_exponent(Exponent) -->
json_exponent_signifier,
json_sign(Sign),
json_digits(Value, _),
- {
- Exponent #= Sign * Value
- }.
+ { Exponent #= Sign * Value }.
json_exponent_signifier --> "E".
json_exponent_signifier --> "e".
-json_sign_noplus(1) --> "".
+json_sign_noplus(1) --> "".
json_sign_noplus(-1) --> "-".
json_sign(Sign) --> json_sign_noplus(Sign).
-json_sign(1) --> "+".
+json_sign(1) --> "+".
json_ws --> "".
json_ws --> " ", json_ws.