module.exports = grammar({
name: 'json',
extras: $ => [
/\s/,
$.comment,
],
supertypes: $ => [
$._value,
],
rules: {
document: $ => repeat($._value),
_value: $ => choice(
$.object,
$.array,
$.number,
$.string,
$.true,
$.false,
$.null,
),
object: $ => seq(
'{', commaSep($.pair), '}',
),
pair: $ => seq(
field('key', $.string),
':',
field('value', $._value),
),
array: $ => seq(
'[', commaSep($._value), ']',
),
string: $ => choice(
seq('"', '"'),
seq('"', $.string_content, '"'),
),
string_content: $ => repeat1(choice(
token.immediate(prec(1, /[^\\"\n]+/)),
$.escape_sequence,
)),
escape_sequence: _ => token.immediate(seq(
'\\',
/(\"|\\|\/|b|f|n|r|t|u)/,
)),
number: _ => {
const decimalDigits = /\d+/;
const signedInteger = seq(optional('-'), decimalDigits);
const exponentPart = seq(choice('e', 'E'), signedInteger);
const decimalIntegerLiteral = seq(
optional('-'),
choice(
'0',
seq(/[1-9]/, optional(decimalDigits)),
),
);
const decimalLiteral = choice(
seq(decimalIntegerLiteral, '.', optional(decimalDigits), optional(exponentPart)),
seq(decimalIntegerLiteral, optional(exponentPart)),
);
return token(decimalLiteral);
},
true: _ => 'true',
false: _ => 'false',
null: _ => 'null',
comment: _ => token(choice(
seq('//', /.*/),
seq(
'/*',
/[^*]*\*+([^/*][^*]*\*+)*/,
'/',
),
)),
},
});
function commaSep1(rule) {
return seq(rule, repeat(seq(',', rule)));
}
function commaSep(rule) {
return optional(commaSep1(rule));
}