// Copyright (c) 2021 Dr. Colin Hirsch and Daniel Frey
// Distributed under the Boost Software License, Version 1.0.
// (See accompanying file LICENSE_1_0.txt or copy at https://www.boost.org/LICENSE_1_0.txt)

#ifndef TAO_PEGTL_CONTRIB_PROTO3_HPP
#define TAO_PEGTL_CONTRIB_PROTO3_HPP

#include "../ascii.hpp"
#include "../config.hpp"
#include "../rules.hpp"

namespace TAO_PEGTL_NAMESPACE::proto3
{
   // protocol buffer v3
   // https://developers.google.com/protocol-buffers/docs/reference/proto3-spec

   // clang-format off
   struct comment_sl : seq< two< '/' >, until< eolf > > {};
   struct comment_ml : if_must< string< '/', '*' >, until< string< '*', '/' > > > {};
   struct sp : sor< space, comment_sl, comment_ml > {};
   struct sps : star< sp > {};

   struct comma : one< ',' > {};
   struct dot : one< '.' > {};
   struct equ : one< '=' > {};
   struct semi : one< ';' > {};

   struct option;
   struct message;
   struct extend;

   struct ident_first : ranges< 'a', 'z', 'A', 'Z' > {};  // NOTE: Yes, no '_'.
   struct ident_other : ranges< 'a', 'z', 'A', 'Z', '0', '9', '_' > {};
   struct ident : seq< ident_first, star< ident_other > > {};
   struct full_ident : list_must< ident, dot > {};

   struct hex_lit : seq< one< '0' >, one< 'x', 'X' >, plus< xdigit > > {};
   struct oct_lit : seq< one< '0' >, plus< odigit > > {};
   struct dec_lit : seq< range< '1', '9' >, star< digit > >  {};
   struct int_lit : sor< hex_lit, oct_lit, dec_lit > {};

   struct sign : one< '+', '-' > {};
   struct exp : seq< one< 'E', 'e' >, opt< sign >, plus< digit > > {};
   struct float_lit : sor<
      seq< plus< digit >, dot, exp >,
      seq< plus< digit >, dot, star< digit >, opt< exp > >,
      seq< dot, plus< digit >, opt< exp > >,
      keyword< 'i', 'n', 'f' >,
      keyword< 'n', 'a', 'n' > > {};

   struct bool_lit : sor< keyword< 't', 'r', 'u', 'e' >,
                          keyword< 'f', 'a', 'l', 's', 'e' > > {};

   struct hex_escape : if_must< one< 'x', 'X' >, xdigit, xdigit > {};
   struct oct_escape : if_must< odigit, odigit, odigit > {};
   struct char_escape : one< 'a', 'b', 'f', 'n', 'r', 't', 'v', '\\', '\'', '"' > {};
   struct escape : if_must< one< '\\' >, hex_escape, oct_escape, char_escape > {};
   struct char_value : sor< escape, not_one< '\n', '\0' > > {};  // NOTE: No need to exclude '\' from not_one<>, see escape rule.
   template< char Q >
   struct str_impl : if_must< one< Q >, until< one< Q >, char_value > > {};
   struct str_lit : sor< str_impl< '\'' >, str_impl< '"' > > {};

   struct constant : sor< bool_lit, seq< opt< sign >, float_lit >, seq< opt< sign >, int_lit >, str_lit, full_ident > {};

   struct option_name : seq< sor< ident, if_must< one< '(' >, full_ident, one< ')' > > >, star_must< dot, ident > > {};
   struct option : if_must< keyword< 'o', 'p', 't', 'i', 'o', 'n' >, sps, option_name, sps, equ, sps, constant, sps, semi > {};

   struct bool_type : keyword< 'b', 'o', 'o', 'l' > {};
   struct bytes_type : keyword< 'b', 'y', 't', 'e', 's' > {};
   struct double_type : keyword< 'd', 'o', 'u', 'b', 'l', 'e' > {};
   struct float_type : keyword< 'f', 'l', 'o', 'a', 't' > {};
   struct string_type : keyword< 's', 't', 'r', 'i', 'n', 'g' > {};

   struct int32_type : keyword< 'i', 'n', 't', '3', '2' > {};
   struct int64_type : keyword< 'i', 'n', 't', '6', '4' > {};
   struct sint32_type : keyword< 's', 'i', 'n', 't', '3', '2' > {};
   struct sint64_type : keyword< 's', 'i', 'n', 't', '6', '4' > {};
   struct uint32_type : keyword< 'u', 'i', 'n', 't', '3', '2' > {};
   struct uint64_type : keyword< 'u', 'i', 'n', 't', '6', '4' > {};
   struct fixed32_type : keyword< 'f', 'i', 'x', 'e', 'd', '3', '2' > {};
   struct fixed64_type : keyword< 'f', 'i', 'x', 'e', 'd', '6', '4' > {};
   struct sfixed32_type : keyword< 's', 'f', 'i', 'x', 'e', 'd', '3', '2' > {};
   struct sfixed64_type : keyword< 's', 'f', 'i', 'x', 'e', 'd', '6', '4' > {};

   struct builtin_type : sor< bool_type, bytes_type, double_type, float_type, string_type, int32_type, int64_type, sint32_type, sint64_type, uint32_type, uint64_type, fixed32_type, fixed64_type, sfixed32_type, sfixed64_type > {};

   struct defined_type : seq< opt< dot >, full_ident > {};  // NOTE: This replaces both message_type and enum_type -- they have the same syntax.

   struct type : sor< builtin_type, defined_type > {};

   struct field_option : if_must< option_name, sps, equ, sps, constant > {};
   struct field_options : if_must< one< '[' >, sps, list< field_option, comma, sp >, sps, one< ']' > > {};
   struct field_name : ident {};
   struct field_number : int_lit {};
   struct field : seq< opt< sor< keyword< 'o', 'p', 't', 'i', 'o', 'n', 'a', 'l' >, keyword< 'r', 'e', 'p', 'e', 'a', 't', 'e', 'd' > >, sps >, type, sps, field_name, sps, equ, sps, field_number, sps, opt< field_options, sps >, semi > {};

   struct oneof_name : ident {};
   struct oneof_field : if_must< type, sps, field_name, sps, equ, sps, field_number, sps, opt< field_options, sps >, semi > {};
   struct oneof_body : sor< oneof_field, semi > {};
   struct oneof : if_must< keyword< 'o', 'n', 'e', 'o', 'f' >, sps, oneof_name, sps, one< '{' >, sps, until< one< '}' >, oneof_body, sps >, sps > {};

   struct key_type : seq< sor< bool_type, string_type, int32_type, int64_type, sint32_type, sint64_type, uint32_type, uint64_type, fixed32_type, fixed64_type, sfixed32_type, sfixed64_type >, not_at< ident_other > > {};
   struct map_name : ident {};
   struct map_field : if_must< keyword< 'm', 'a', 'p' >, sps, one< '<' >, sps, key_type, sps, comma, sps, type, sps, one< '>' >, sps, map_name, sps, equ, sps, field_number, sps, opt< field_options, sps >, semi > {};

   struct range : if_must< int_lit, sps, keyword< 't', 'o' >, sps, sor< int_lit, keyword< 'm', 'a', 'x' > > > {};
   struct ranges : list_must< range, comma, sp > {};
   struct field_names : list_must< field_name, comma, sp > {};
   struct reserved : if_must< keyword< 'r', 'e', 's', 'e', 'r', 'v', 'e', 'd' >, sps, sor< ranges, field_names >, sps, semi > {};

   struct enum_name : ident {};
   struct enum_value_option : seq< option_name, sps, equ, sps, constant > {};
   struct enum_field : seq< ident, sps, equ, sps, int_lit, sps, opt_must< one< '[' >, sps, list_must< enum_value_option, comma, sp >, sps, one< ']' >, sps >, semi > {};
   struct enum_body : if_must< one< '{' >, sps, star< sor< option, enum_field, semi >, sps >, one< '}' > > {};
   struct enum_def : if_must< keyword< 'e', 'n', 'u', 'm' >, sps, enum_name, sps, enum_body > {};

   struct message_thing : sor< field, enum_def, message, option, oneof, map_field, reserved, extend, semi > {};
   struct message_body : seq< one<'{'>, sps, star< message_thing, sps >, one<'}'> > {};
   struct message : if_must< keyword< 'm', 'e', 's', 's', 'a', 'g', 'e' >, sps, defined_type, sps, message_body > {};
   struct extend : if_must< keyword< 'e', 'x', 't', 'e', 'n', 'd' >, sps, defined_type, sps, message_body > {};

   struct package : if_must< keyword< 'p', 'a', 'c', 'k', 'a', 'g', 'e' >, sps, full_ident, sps, semi > {};

   struct import_option : opt< sor< keyword< 'w', 'e', 'a', 'k' >, keyword< 'p', 'u', 'b', 'l', 'i', 'c' > > > {};
   struct import : if_must< keyword< 'i', 'm', 'p', 'o', 'r', 't' >, sps, import_option, sps, str_lit, sps, semi > {};

   struct rpc_name : ident {};
   struct rpc_type : if_must< one< '(' >, sps, opt< keyword< 's', 't', 'r', 'e', 'a', 'm' >, sps >, defined_type, sps, one< ')' > > {};
   struct rpc_options : if_must< one< '{' >, sps, star< sor< option, semi >, sps >, one< '}' > > {};
   struct rpc : if_must< keyword< 'r', 'p', 'c' >, sps, rpc_name, sps, rpc_type, sps, keyword< 'r', 'e', 't', 'u', 'r', 'n', 's' >, sps, rpc_type, sps, sor< semi, rpc_options > > {};
   struct service_name : ident {};
   struct service : if_must< keyword< 's', 'e', 'r', 'v', 'i', 'c', 'e' >, sps, service_name, sps, one< '{' >, sps, star< sor< option, rpc, semi >, sps >, one< '}' > > {};

   struct body : sor< import, package, option, message, enum_def, service, extend, semi > {};

   struct quote : one< '\'', '"' > {};
   struct head : if_must< keyword< 's', 'y', 'n', 't', 'a', 'x' >, sps, equ, sps, quote, string< 'p', 'r', 'o', 't', 'o', '3' >, quote, sps, semi > {};
   struct proto : must< sps, head, sps, star< body, sps >, eof > {};
   // clang-format on

}  // namespace TAO_PEGTL_NAMESPACE::proto3

#endif
