4
4
import json
5
5
import re
6
6
import sys
7
- from typing import Any , Callable , Dict , List , Optional , Set , Tuple , Union
8
-
7
+ from typing import Any , List , Optional , Set , Tuple , Union
9
8
10
9
def _build_repetition (item_rule , min_items , max_items , separator_rule = None ):
11
10
@@ -276,6 +275,51 @@ def recurse(i: int):
276
275
277
276
return '' .join (('(' , * recurse (0 ), ')' ))
278
277
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
+
279
323
def _add_rule (self , name , rule ):
280
324
esc_name = INVALID_RULE_CHARS_RE .sub ('-' , name )
281
325
if esc_name not in self ._rules or self ._rules [esc_name ] == rule :
@@ -524,10 +568,10 @@ def visit(self, schema, name):
524
568
return self ._add_rule (rule_name , self ._generate_union_rule (name , [{'type' : t } for t in schema_type ]))
525
569
526
570
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' )
528
572
529
573
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'
531
575
return self ._add_rule (rule_name , rule )
532
576
533
577
elif schema_type in (None , 'object' ) and \
@@ -632,7 +676,7 @@ def _add_primitive(self, name: str, rule: BuiltinRule):
632
676
self ._add_primitive (dep , dep_rule )
633
677
return n
634
678
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 ] ]):
636
680
prop_order = self ._prop_order
637
681
# sort by position in prop_order (if specified) then by original order
638
682
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
647
691
required_props = [k for k in sorted_props if k in required ]
648
692
optional_props = [k for k in sorted_props if k not in required ]
649
693
650
- if additional_properties == True or isinstance ( additional_properties , dict ) :
694
+ if additional_properties != False :
651
695
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
+
653
701
prop_kv_rule_names ["*" ] = self ._add_rule (
654
702
f'{ sub_name } -kv' ,
655
- self . _add_primitive ( 'string' , PRIMITIVE_RULES [ 'string' ]) + f' ":" space { value_rule } '
703
+ f' { key_rule } ":" space { value_rule } '
656
704
)
657
705
optional_props .append ("*" )
658
706
@@ -667,15 +715,11 @@ def _build_object_rule(self, properties: List[Tuple[str, Any]], required: Set[st
667
715
def get_recursive_refs (ks , first_is_optional ):
668
716
[k , * rest ] = ks
669
717
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 '?' )
677
721
else :
678
- res = kv_rule_name
722
+ res = kv_rule_name + ( ' ' + comma_ref + "*" if k == '*' else '' )
679
723
if len (rest ) > 0 :
680
724
res += ' ' + self ._add_rule (
681
725
f'{ name } { "-" if name else "" } { k } -rest' ,
0 commit comments