44import json
55import re
66import sys
7- from typing import Any , Callable , Dict , List , Optional , Set , Tuple , Union
8-
7+ from typing import Any , List , Optional , Set , Tuple , Union
98
109def _build_repetition (item_rule , min_items , max_items , separator_rule = None ):
1110
@@ -276,6 +275,51 @@ def recurse(i: int):
276275
277276 return '' .join (('(' , * recurse (0 ), ')' ))
278277
278+ def _not_strings (self , strings ):
279+ class TrieNode :
280+ def __init__ (self ):
281+ self .children = {}
282+ self .is_end_of_string = False
283+
284+ def insert (self , string ):
285+ node = self
286+ for c in string :
287+ node = node .children .setdefault (c , TrieNode ())
288+ node .is_end_of_string = True
289+
290+ trie = TrieNode ()
291+ for s in strings :
292+ trie .insert (s )
293+
294+ char_rule = self ._add_primitive ('char' , PRIMITIVE_RULES ['char' ])
295+ out = ['["] ( ' ]
296+
297+ def visit (node ):
298+ rejects = []
299+ first = True
300+ for c in sorted (node .children .keys ()):
301+ child = node .children [c ]
302+ rejects .append (c )
303+ if first :
304+ first = False
305+ else :
306+ out .append (' | ' )
307+ out .append (f'[{ c } ]' )
308+ if child .children :
309+ out .append (f' (' )
310+ visit (child )
311+ out .append (')' )
312+ elif child .is_end_of_string :
313+ out .append (f' { char_rule } +' )
314+ if node .children :
315+ if not first :
316+ out .append (' | ' )
317+ out .append (f'[^"{ "" .join (rejects )} ] { char_rule } *' )
318+ visit (trie )
319+
320+ out .append (f' ){ "" if trie .is_end_of_string else "?" } ["] space' )
321+ return '' .join (out )
322+
279323 def _add_rule (self , name , rule ):
280324 esc_name = INVALID_RULE_CHARS_RE .sub ('-' , name )
281325 if esc_name not in self ._rules or self ._rules [esc_name ] == rule :
@@ -524,10 +568,10 @@ def visit(self, schema, name):
524568 return self ._add_rule (rule_name , self ._generate_union_rule (name , [{'type' : t } for t in schema_type ]))
525569
526570 elif 'const' in schema :
527- return self ._add_rule (rule_name , self ._generate_constant_rule (schema ['const' ]))
571+ return self ._add_rule (rule_name , self ._generate_constant_rule (schema ['const' ]) + ' space' )
528572
529573 elif 'enum' in schema :
530- rule = ' | ' .join ((self ._generate_constant_rule (v ) for v in schema ['enum' ]))
574+ rule = '(' + ' | ' .join ((self ._generate_constant_rule (v ) for v in schema ['enum' ])) + ') space'
531575 return self ._add_rule (rule_name , rule )
532576
533577 elif schema_type in (None , 'object' ) and \
@@ -632,7 +676,7 @@ def _add_primitive(self, name: str, rule: BuiltinRule):
632676 self ._add_primitive (dep , dep_rule )
633677 return n
634678
635- def _build_object_rule (self , properties : List [Tuple [str , Any ]], required : Set [str ], name : str , additional_properties : Union [bool , Any ]):
679+ def _build_object_rule (self , properties : List [Tuple [str , Any ]], required : Set [str ], name : str , additional_properties : Optional [ Union [bool , Any ] ]):
636680 prop_order = self ._prop_order
637681 # sort by position in prop_order (if specified) then by original order
638682 sorted_props = [kv [0 ] for _ , kv in sorted (enumerate (properties ), key = lambda ikv : (prop_order .get (ikv [1 ][0 ], len (prop_order )), ikv [0 ]))]
@@ -647,12 +691,16 @@ def _build_object_rule(self, properties: List[Tuple[str, Any]], required: Set[st
647691 required_props = [k for k in sorted_props if k in required ]
648692 optional_props = [k for k in sorted_props if k not in required ]
649693
650- if additional_properties == True or isinstance ( additional_properties , dict ) :
694+ if additional_properties != False :
651695 sub_name = f'{ name } { "-" if name else "" } additional'
652- value_rule = self .visit ({} if additional_properties == True else additional_properties , f'{ sub_name } -value' )
696+ value_rule = self .visit (additional_properties , f'{ sub_name } -value' ) if isinstance (additional_properties , dict ) else \
697+ self ._add_primitive ('value' , PRIMITIVE_RULES ['value' ])
698+ key_rule = self ._add_primitive ('string' , PRIMITIVE_RULES ['string' ]) if not sorted_props \
699+ else self ._add_rule (f'{ sub_name } -k' , self ._not_strings (sorted_props ))
700+
653701 prop_kv_rule_names ["*" ] = self ._add_rule (
654702 f'{ sub_name } -kv' ,
655- self . _add_primitive ( 'string' , PRIMITIVE_RULES [ 'string' ]) + f' ":" space { value_rule } '
703+ f' { key_rule } ":" space { value_rule } '
656704 )
657705 optional_props .append ("*" )
658706
@@ -667,15 +715,11 @@ def _build_object_rule(self, properties: List[Tuple[str, Any]], required: Set[st
667715 def get_recursive_refs (ks , first_is_optional ):
668716 [k , * rest ] = ks
669717 kv_rule_name = prop_kv_rule_names [k ]
670- if k == '*' :
671- res = self ._add_rule (
672- f'{ name } { "-" if name else "" } additional-kvs' ,
673- f'{ kv_rule_name } ( "," space ' + kv_rule_name + ' )*'
674- )
675- elif first_is_optional :
676- res = f'( "," space { kv_rule_name } )?'
718+ comma_ref = f'( "," space { kv_rule_name } )'
719+ if first_is_optional :
720+ res = comma_ref + ('*' if k == '*' else '?' )
677721 else :
678- res = kv_rule_name
722+ res = kv_rule_name + ( ' ' + comma_ref + "*" if k == '*' else '' )
679723 if len (rest ) > 0 :
680724 res += ' ' + self ._add_rule (
681725 f'{ name } { "-" if name else "" } { k } -rest' ,
0 commit comments