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.