57            line = [f
"{nonterminal} ::= ", 
"object_begin "]
 
   60            for field, _field_info 
in current.fields().items():
 
   61                field_name = f
"{nonterminal}_{field}" 
   62                fields.append(f
"'\"{field}\"' colon {field_name}")
 
   63                result.append((_field_info, field_name))
 
   64            line.append(
" comma ".join(fields))
 
   65            line.append(
" object_end;\n")
 
   66            return "".join(line), result
 
   69    def field_info(current: typing.Type, nonterminal: str):
 
   72                return "", [(current.annotation, nonterminal)]
 
   73            new_nonterminal = f
"{nonterminal}_required" 
   74            return f
"{nonterminal} ::= {new_nonterminal}?;\n", [(current.annotation, new_nonterminal)]
 
   77    def builtin_list(current: typing.Type, nonterminal: str):
 
   78        original = typing.get_origin(current)
 
   81        if original 
is typing.Sequence 
or isinstance(original, type) \
 
   82                and issubclass(original, collections.abc.Sequence):
 
   83            new_nonterminal = f
"{nonterminal}_value" 
   84            annotation = typing.get_args(current)
 
   86                annotation = typing.Any
 
   88                annotation = annotation[0]
 
   89            return f
"{nonterminal} ::= array_begin ({new_nonterminal} (comma {new_nonterminal})*)? array_end;\n", \
 
   90                [(annotation, new_nonterminal)]
 
   93    def builtin_dict(current: typing.Type, nonterminal: str):
 
   94        original = typing.get_origin(current)
 
   97        if original 
is typing.Mapping 
or isinstance(original, type) 
and issubclass(original,
 
   98                                                                                   collections.abc.Mapping):
 
   99            new_nonterminal = f
"{nonterminal}_value" 
  100            args = typing.get_args(current)
 
  105                    args[0], str), f
"{args[0]} is not string!" 
  107            return f
"{nonterminal} ::=" \
 
  108                f
" object_begin (string colon {new_nonterminal} (comma string colon {new_nonterminal})*)?" \
 
  110                [(value, new_nonterminal)]
 
  113    def builtin_tuple(current: typing.Type, nonterminal: str):
 
  114        if typing.get_origin(current) 
is tuple 
or isinstance(current, type) 
and issubclass(current, tuple):
 
  115            args = typing.get_args(current)
 
  116            new_nonterminals = []
 
  118            for i, arg 
in enumerate(args):
 
  120                new_nonterminals.append(f
"{nonterminal}_{i}")
 
  121            return f
"{nonterminal} ::=array_begin {' comma '.join(new_nonterminals)} array_end;\n", \
 
  122                zip(result, new_nonterminals)
 
  124    def builtin_union(current: typing.Type, nonterminal: str):
 
  125        if typing.get_origin(current) 
is typing.Union:
 
  126            args = typing.get_args(current)
 
  127            assert args, f
"{current} from {nonterminal} cannot be an empty union!" 
  128            new_nonterminals = []
 
  130            for i, arg 
in enumerate(args):
 
  132                new_nonterminals.append(f
"{nonterminal}_{i}")
 
  133            return f
"{nonterminal} ::= {' | '.join(new_nonterminals)};\n", zip(result, new_nonterminals)
 
  135    def builtin_literal(current: typing.Type, nonterminal: str):
 
  136        if typing.get_origin(current) 
is typing.Literal:
 
  137            args = typing.get_args(current)
 
  138            assert args, f
"{current} from {nonterminal} cannot be an empty literal!" 
  141            for i, arg 
in enumerate(args):
 
  142                if isinstance(arg, str):
 
  143                    new_items.append(f
'"{repr(arg)}"')
 
  144                elif isinstance(arg, bool):
 
  145                    new_items.append(f
'"{str(arg).lower()}"')
 
  146                elif isinstance(arg, int):
 
  147                    new_items.append(f
'"{str(arg)}"')
 
  148                elif isinstance(arg, float):
 
  149                    new_items.append(f
'"{str(arg)}"')
 
  151                    new_items.append(
"null")
 
  152                elif isinstance(arg, tuple):
 
  153                    for j,item 
in enumerate(arg):
 
  154                        new_nonterminal = f
"{nonterminal}_{i}_{j}" 
  155                        result.append((typing.Literal[item], new_nonterminal))
 
  156                    new_item = f
"(array_begin {' comma '.join(map(lambda x:x[1], result))} array_end)" 
  157                    new_items.append(new_item)
 
  158                elif isinstance(arg, frozendict):
 
  159                    for key, value 
in arg.items():
 
  160                        new_nonterminal = f
"{nonterminal}_{i}_{key}" 
  161                        result.append((typing.Literal[value], new_nonterminal))
 
  162                    new_item = f
"object_begin {' comma '.join(map(lambda x:x[1], result))} object_end" 
  163                    new_items.append(new_item)
 
  165                    new_nonterminal = f
"{nonterminal}_{i}" 
  166                    result.append((arg, new_nonterminal))
 
  167                    new_items.append(new_nonterminal)
 
  168            return f
"{nonterminal} ::= {' | '.join(new_items)};\n", result
 
  170    def builtin_simple_types(current: typing.Type, nonterminal: str):
 
  171        if isinstance(current, type) 
and issubclass(current, bool):
 
  172            return f
"{nonterminal} ::= boolean;\n", []
 
  173        elif isinstance(current, type) 
and issubclass(current, int):
 
  174            return f
"{nonterminal} ::= integer;\n", []
 
  175        elif isinstance(current, type) 
and issubclass(current, float):
 
  176            return f
"{nonterminal} ::= number;\n", []
 
  177        elif isinstance(current, type) 
and issubclass(current, decimal.Decimal):
 
  178            return f
"{nonterminal} ::= number;\n", []
 
  179        elif isinstance(current, type) 
and issubclass(current, str):
 
  180            return f
"{nonterminal} ::= string;\n", []
 
  181        elif isinstance(current, type) 
and issubclass(current, type(
None)):
 
  182            return f
"{nonterminal} ::= null;\n", []
 
  183        elif current 
is typing.Any:
 
  184            return f
"{nonterminal} ::= json_value;\n", []
 
  185        elif isinstance(current, typing.NewType):
 
  186            current: typing.NewType
 
  187            return "", [(current.__supertype__, nonterminal)]
 
  201    Generate a KBNF grammar string from a schema for JSON format.