@@ -409,7 +409,7 @@ def is_unbounded(self) -> bool:
409409 """
410410 Returns true if the grammar produces unbounded programs.
411411 """
412- reachable_from : dict [U , set [V ]] = defaultdict (set )
412+ reachable_from : dict [U , set [U ]] = defaultdict (set )
413413 for (P , args ), dst in self .rules .items ():
414414 reachable_from [dst ].update (args )
415415
@@ -428,22 +428,8 @@ def is_unbounded(self) -> bool:
428428
429429 def compute_max_size_and_depth (self ) -> tuple [int , int ]:
430430 """
431- Return max size and max depth
431+ Return max size and max depth, this will loop endlessly if this grammar is unbounded.
432432 """
433- # Compute transitive closure
434- reachable_from : dict [U , set [V ]] = defaultdict (set )
435- for (P , args ), dst in self .rules .items ():
436- reachable_from [dst ].update (args )
437-
438- updated = True
439- while updated :
440- updated = False
441- for dst , reachables in reachable_from .copy ().items ():
442- before = len (reachables )
443- for S in reachables .copy ():
444- reachables .update (reachable_from [S ])
445- if len (reachables ) != before :
446- updated = True
447433 max_size_by_state : dict [U , int ] = {state : 0 for state in self .states }
448434 max_depth_by_state : dict [U , int ] = {state : 0 for state in self .states }
449435 for (P , args ), dst in self .rules .items ():
@@ -481,3 +467,58 @@ def __str__(self) -> str:
481467 lines .append (f"{ dst } <- '{ P } ' { add } " )
482468
483469 return s + "\n " .join (sorted (lines ))
470+
471+ def has_unproductive_rules (self ) -> bool :
472+ """
473+ Returns true iff a rule is unproductive (including unreachable).
474+ """
475+ new_states = self .states
476+ new_rules = {
477+ (letter , args ): dst
478+ for (letter , args ), dst in self .rules .items ()
479+ if dst in new_states and all (s in new_states for s in args )
480+ }
481+ if new_rules != self .rules :
482+ return True
483+ consumed = self .__get_consumed__ ()
484+ for _ , dst in list (self .rules .items ()):
485+ if dst not in consumed :
486+ return True
487+
488+ return False
489+
490+ def __has_cloning_derivation__ (self ) -> bool :
491+ # Compute transitive closure
492+ reachable_from : dict [U , set [U ]] = defaultdict (set )
493+ for (P , args ), dst in self .rules .items ():
494+ reachable_from [dst ].update (args )
495+
496+ updated = True
497+ while updated :
498+ updated = False
499+ for dst , reachables in reachable_from .copy ().items ():
500+ before = len (reachables )
501+ for S in reachables .copy ():
502+ reachables .update (reachable_from [S ])
503+ if len (reachables ) != before :
504+ updated = True
505+ if dst in reachables and any (
506+ len (args ) > 1
507+ for S in reachables
508+ for _ , args in self .reversed_rules [S ]
509+ ):
510+ return True
511+ return False
512+
513+ def is_tree_like (self ) -> bool :
514+ """
515+ Returns true iff all of the following are true:
516+ - |derivations| >= |states| + 2
517+ - there is no unusable production rules
518+ - there is a derivation by transitive closure S ->* w S S
519+ """
520+ return (
521+ self .size () >= len (self .all_states ) + 2
522+ and not self .has_unproductive_rules ()
523+ and self .__has_cloning_derivation__ ()
524+ )
0 commit comments