text: /*-
   * SPDX-License-Identifier: BSD-2-Clause-FreeBSD
   *
   * Copyright (c) 2021 Tobias Kortkamp <tobik@FreeBSD.org>
   * All rights reserved.
   *
   * Redistribution and use in source and binary forms, with or without
   * modification, are permitted provided that the following conditions
   * are met:
   * 1. Redistributions of source code must retain the above copyright
   *    notice, this list of conditions and the following disclaimer.
   * 2. Redistributions in binary form must reproduce the above copyright
   *    notice, this list of conditions and the following disclaimer in the
   *    documentation and/or other materials provided with the distribution.
   *
   * THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS ``AS IS'' AND
   * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
   * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
   * ARE DISCLAIMED.  IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE
   * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
   * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
   * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
   * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
   * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
   * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
   * SUCH DAMAGE.
   */
  #include "config.h"
  
  #include <sys/types.h>
  #include <inttypes.h>
  #include <stdbool.h>
  
  #include "peg.h"
  #include "peg/json.h"
  #include "peg/grammar.h"
  
  // See https://tools.ietf.org/html/rfc7159
  
  // Prototypes
  static RULE(JSON);
  static RULE(array);
  static RULE(array_0);
  static RULE(array_1);
  static RULE(array_values);
  static RULE(array_values_0);
  static RULE(begin_array);
  static RULE(begin_object);
  static RULE(character);
  static RULE(decimal_point);
  static RULE(digit);
  static RULE(e);
  static RULE(end_array);
  static RULE(end_object);
  static RULE(escape);
  static RULE(escaped);
  static RULE(escaped_0);
  static RULE(escaped_1);
  static RULE(exponent);
  static RULE(fraction);
  static RULE(hexdigit);
  static RULE(integer);
  static RULE(member);
  static RULE(minus);
  static RULE(minus_plus);
  static RULE(name_separator);
  static RULE(number);
  static RULE(object);
  static RULE(object_0);
  static RULE(object_1);
  static RULE(object_members);
  static RULE(object_members_0);
  static RULE(plus);
  static RULE(quotation_mark);
  static RULE(string);
  static RULE(unescaped);
  static RULE(value);
  static RULE(value_separator);
  static RULE(ws);
  static RULE(zero);
  
  RULE(JSON) {
  	if (ANY(ws))
  	if (MATCH(value))
  	if (ANY(ws))
  	return ACCEPT;
  	return REJECT;
  }
  
  RULE(ws) { return CHAR(0x20, 0x09, 0x0A, 0x0D); }
  
  RULE(begin_array) {
  	if (CHAR('['))
  	return ACCEPT;
  	return ERROR("expected beginning of array '['");
  }
  
  RULE(begin_object) {
  	if (CHAR('{'))
  	return ACCEPT;
  	return ERROR("expected beginning of object '{'");
  }
  
  RULE(end_array) {
  	if (CHAR(']'))
  	return ACCEPT;
  	return ERROR("expected end of array ']'");
  }
  
  RULE(end_object) {
  	if (CHAR('}'))
  	return ACCEPT;
  	return ERROR("expected end of object '}'");
  }
  
  RULE(name_separator) {
  	if (CHAR(':'))
  	return ACCEPT;
  	return ERROR("expected name separator ':'");
  }
  
  RULE(value_separator) {
  	if (CHAR(','))
  	return ACCEPT;
  	return ERROR("expected value separator ','");
  }
  
  RULE(escape) {
  	if (CHAR('\\'))
  	return ACCEPT;
  	return ERROR("expected escape '\\'");
  }
  
  RULE(quotation_mark) {
  	if (CHAR('"'))
  	return ACCEPT;
  	return ERROR("expected '\"'");
  }
  
  RULE(decimal_point) {
  	if (CHAR('.'))
  	return ACCEPT;
  	return ERROR("expected '.'");
  }
  
  RULE(minus) {
  	if (CHAR('-'))
  	return ACCEPT;
  	return ERROR("expected '-'");
  }
  
  RULE(plus) {
  	if (CHAR('+'))
  	return ACCEPT;
  	return ERROR("expected '+'");
  }
  
  RULE(zero) {
  	if (CHAR('0'))
  	return ACCEPT;
  	return ERROR("expected '0'");
  }
  
  RULE(digit) {
  	if (RANGE(0x30, 0x39))
  	return ACCEPT;
  	return ERROR("expected decimal digit");
  }
  
  RULE(hexdigit) {
  	if (!RANGE(0x30, 0x39)) // 0-9
  	if (!RANGE(0x41, 0x46)) // A-F
  	if (!RANGE(0x61, 0x66)) // a-f
  	return ERROR("expected hexadecimal digit");
  	return ACCEPT;
  }
  
  RULE(value) {
  	if (!CAPTURE(STRING("false"), 0, PEG_JSON_FALSE))
  	if (!CAPTURE(STRING("null"), 0, PEG_JSON_NULL))
  	if (!CAPTURE(STRING("true"), 0, PEG_JSON_TRUE))
  	if (!MATCH(object))
  	if (!MATCH(array))
  	if (!CAPTURE(MATCH(number), 0, PEG_JSON_NUMBER))
  	if (!MATCH(string))
  	return ERROR("");
  	return ACCEPT;
  }
  
  RULE(object_members_0) {
  	if (MATCH(value_separator))
  	if (ANY(ws))
  	if (MATCH(end_object))
  	return ACCEPT;
  	return REJECT;
  }
  
  RULE(object_members) {
  	if (ANY(ws))
  	if (!LOOKAHEAD(object_members_0))
  	if (MATCH(value_separator))
  	if (ANY(ws))
  	if (MATCH(member))
  	return ACCEPT;
  	return REJECT;
  }
  
  RULE(object_0) {
  	if (CAPTURE(MATCH(begin_object), 0, PEG_JSON_OBJECT_BEGIN))
  	if (ANY(ws))
  	if (MATCH(member))
  	if (ANY(object_members))
  	if (ANY(ws))
  	if (CAPTURE(MATCH(end_object), 0, PEG_JSON_OBJECT_END))
  	return ACCEPT;
  	return REJECT;
  }
  
  RULE(object_1) {
  	if (CAPTURE(MATCH(begin_object), 0, PEG_JSON_OBJECT_BEGIN))
  	if (ANY(ws))
  	if (CAPTURE(MATCH(end_object), 0, PEG_JSON_OBJECT_END))
  	return ACCEPT;
  	return REJECT;
  }
  
  RULE(object) {
  	if (!MATCH(object_0))
  	if (!MATCH(object_1))
  	return ERROR("");
  	return ACCEPT;
  }
  
  RULE(member) {
  	if (MATCH(string))
  	if (ANY(ws))
  	if (MATCH(name_separator))
  	if (ANY(ws))
  	if (CAPTURE(MATCH(value), 0, PEG_JSON_OBJECT_VALUE))
  	return ACCEPT;
  	return REJECT;
  }
  
  RULE(array_values_0) {
  	if (MATCH(value_separator))
  	if (ANY(ws))
  	if (MATCH(end_array))
  	return ACCEPT;
  	return REJECT;
  }
  
  RULE(array_values) {
  	if (ANY(ws))
  	if (!LOOKAHEAD(array_values_0))
  	if (MATCH(value_separator))
  	if (ANY(ws))
  	if (CAPTURE(MATCH(value), 0, PEG_JSON_ARRAY_VALUE))
  	return ACCEPT;
  	return REJECT;
  }
  
  RULE(array_0) {
  	if (CAPTURE(MATCH(begin_array), 0, PEG_JSON_ARRAY_BEGIN))
  	if (ANY(ws))
  	if (CAPTURE(MATCH(value), 0, PEG_JSON_ARRAY_VALUE))
  	if (ANY(array_values))
  	if (ANY(ws))
  	if (CAPTURE(MATCH(end_array), 0, PEG_JSON_ARRAY_END))
  	return ACCEPT;
  	return REJECT;
  }
  
  RULE(array_1) {
  	if (CAPTURE(MATCH(begin_array), 0, PEG_JSON_ARRAY_BEGIN))
  	if (ANY(ws))
  	if (CAPTURE(MATCH(end_array), 0, PEG_JSON_ARRAY_END))
  	return ACCEPT;
  	return REJECT;
  }
  
  RULE(array) {
  	if (!MATCH(array_0))
  	if (!MATCH(array_1))
  	return ERROR("");
  	return ACCEPT;
  }
  
  RULE(integer) {
  	if (MATCH(zero)) return ACCEPT;
  	if (RANGE('1', '9'))
  	if (ANY(digit))
  	return ACCEPT;
  	return REJECT;
  }
  
  RULE(e) {
  	if (!CHAR('e'))
  	if (!CHAR('E'))
  	return REJECT;
  	return ACCEPT;
  }
  
  RULE(minus_plus) {
  	if (!MATCH(minus))
  	if (!MATCH(plus))
  	return REJECT;
  	return ACCEPT;
  }
  
  RULE(exponent) {
  	if (MATCH(e))
  	if (OPT(MATCH(minus_plus)))
  	if (SOME(digit))
  	return ACCEPT;
  	return REJECT;
  }
  
  RULE(fraction) {
  	if (MATCH(decimal_point))
  	if (SOME(digit))
  	return ACCEPT;
  	return REJECT;
  }
  
  RULE(number) {
  	if (OPT(MATCH(minus)))
  	if (MATCH(integer))
  	if (OPT(MATCH(fraction)))
  	if (OPT(MATCH(exponent)))
  	return ACCEPT;
  	return ERROR("");
  }
  
  RULE(unescaped) {
  	if (!CHAR(0x20, 0x21))
  	if (!RANGE(0x23, 0x5b))
  	if (!RANGE(0x5d, 0x10ffff))
  	return REJECT;
  	return ACCEPT;
  }
  
  RULE(escaped_1) {
  	if (CHAR(0x75))
  	if (REPEAT(hexdigit, 4))
  	return ACCEPT;
  	return ERROR("invalid unicode escape");
  }
  
  RULE(escaped_0) {
  	if (!CHAR(0x22, 0x5c, 0x2f, 0x62, 0x66, 0x6e, 0x72, 0x74))
  	if (!MATCH(escaped_1))
  	return REJECT;
  	return ACCEPT;
  }
  
  RULE(escaped) {
  	if (MATCH(escape))
  	if (MATCH(escaped_0))
  	return ACCEPT;
  	return REJECT;
  }
  
  RULE(character) {
  	if (!MATCH(unescaped))
  	if (!MATCH(escaped))
  	return REJECT;
  	return ACCEPT;
  }
  
  RULE(string) {
  	if (MATCH(quotation_mark))
  	if (CAPTURE(ANY(character), 0, PEG_JSON_STRING))
  	if (MATCH(quotation_mark))
  	return ACCEPT;
  	return ERROR("invalid string");
  }
  
  RULE(peg_json_decode) {
  	if (MATCH(JSON))
  	if (EOS())
  	return ACCEPT;
  	return REJECT;
  }
  