@@ -58,6 +58,7 @@ extern int yyparse(void *scanner, jsgf_t * jsgf);
5858 * into Sphinx finite-state grammars.
5959 **/
6060
61+ static void detect_recursion_rule (jsgf_t * grammar , jsgf_rule_t * rule );
6162static int expand_rule (jsgf_t * grammar , jsgf_rule_t * rule ,
6263 int rule_entry , int rule_exit );
6364
@@ -283,6 +284,100 @@ importname2rulename(char *importname)
283284#define NO_NODE -1
284285#define RECURSIVE_NODE -2
285286
287+ /**
288+ *
289+ * Detect recursion; label each RHS as to whether it's right-recursive.
290+ * Just a cut-down version of expand_rhs() and expand_rule().
291+ */
292+ static void
293+ detect_recursion_rhs (jsgf_t * grammar , jsgf_rule_t * rule , jsgf_rhs_t * rhs )
294+ {
295+ gnode_t * gn ;
296+
297+ /* Iterate over atoms in rhs */
298+ for (gn = rhs -> atoms ; gn ; gn = gnode_next (gn )) {
299+ jsgf_atom_t * atom = (jsgf_atom_t * ) gnode_ptr (gn );
300+
301+ if (jsgf_atom_is_rule (atom )) {
302+ jsgf_rule_t * subrule ;
303+ char * fullname ;
304+ int32 subrule_lookup_result ;
305+ gnode_t * subnode ;
306+ jsgf_rule_stack_t * rule_stack_entry = NULL ;
307+
308+ if (0 == strcmp (atom -> name , "<NULL>" )) {
309+ continue ;
310+ }
311+ else if (0 == strcmp (atom -> name , "<VOID>" )) {
312+ /* This entire RHS is unspeakable */
313+ return ;
314+ }
315+
316+ fullname = jsgf_fullname_from_rule (rule , atom -> name );
317+ subrule_lookup_result = hash_table_lookup
318+ (grammar -> rules , fullname , (void * * ) & subrule );
319+ ckd_free (fullname );
320+ if (subrule_lookup_result == -1 ) {
321+ E_ERROR ("Undefined rule in RHS: %s\n" , fullname );
322+ return ;
323+ }
324+
325+ /* Look for this subrule in the stack of expanded rules */
326+ for (subnode = grammar -> rulestack ; subnode ;
327+ subnode = gnode_next (subnode )) {
328+ rule_stack_entry =
329+ (jsgf_rule_stack_t * ) gnode_ptr (subnode );
330+ if (rule_stack_entry -> rule == subrule )
331+ break ;
332+ }
333+
334+ if (subnode != NULL ) {
335+ /* Allow right-recursion only. */
336+ if (gnode_next (gn ) != NULL ) {
337+ E_ERROR
338+ ("Only right-recursion is permitted (in %s.%s)\n" ,
339+ grammar -> name , rule -> name );
340+ return ;
341+ }
342+
343+ /* Remember this RHS is right-recursive. */
344+ rhs -> is_recursive = 1 ;
345+ }
346+
347+ /* If right-recursion has already been checked on this rule,
348+ there's no need to check it again. */
349+ else if (!subrule -> ck_recursive ) {
350+ /* Expand the subrule */
351+ detect_recursion_rule (grammar , subrule );
352+ }
353+ }
354+ }
355+ }
356+
357+ static void
358+ detect_recursion_rule (jsgf_t * grammar , jsgf_rule_t * rule )
359+ {
360+ jsgf_rule_stack_t * rule_stack_entry ;
361+ jsgf_rhs_t * rhs ;
362+
363+ /* Push this rule onto the stack */
364+ rule_stack_entry =
365+ (jsgf_rule_stack_t * ) ckd_calloc (1 , sizeof (jsgf_rule_stack_t ));
366+ rule_stack_entry -> rule = rule ;
367+ rule_stack_entry -> entry = NO_NODE /* unused */ ;
368+ grammar -> rulestack = glist_add_ptr (grammar -> rulestack ,
369+ rule_stack_entry );
370+
371+ rule -> ck_recursive = 1 ;
372+ for (rhs = rule -> rhs ; rhs ; rhs = rhs -> alt ) {
373+ detect_recursion_rhs (grammar , rule , rhs );
374+ }
375+
376+ /* Pop this rule from the rule stack */
377+ ckd_free (gnode_ptr (grammar -> rulestack ));
378+ grammar -> rulestack = gnode_free (grammar -> rulestack , NULL );
379+ }
380+
286381/**
287382 *
288383 * Expand a right-hand-side of a rule (i.e. a single alternate).
@@ -368,7 +463,7 @@ expand_rhs(jsgf_t * grammar, jsgf_rule_t * rule, jsgf_rhs_t * rhs,
368463
369464 /* Let our caller know that this rhs didn't reach an
370465 end state. */
371- lastnode = RECURSIVE_NODE ;
466+ lastnode = - lastnode + RECURSIVE_NODE ;
372467 }
373468 else {
374469 /* If this is the last atom in this rhs, link its
@@ -415,6 +510,7 @@ expand_rule(jsgf_t * grammar, jsgf_rule_t * rule, int rule_entry,
415510 int rule_exit )
416511{
417512 jsgf_rule_stack_t * rule_stack_entry ;
513+ int multiple_alternates ;
418514 jsgf_rhs_t * rhs ;
419515
420516 /* Push this rule onto the stack */
@@ -425,18 +521,32 @@ expand_rule(jsgf_t * grammar, jsgf_rule_t * rule, int rule_entry,
425521 grammar -> rulestack = glist_add_ptr (grammar -> rulestack ,
426522 rule_stack_entry );
427523
524+ /* Remember whether this rule has more than one alternate.
525+ This determines whether a null-transition has to be added
526+ in order to to separate right-recursive sub-rules from
527+ its sibling alternates. */
528+ multiple_alternates = (rule -> rhs != NULL && rule -> rhs -> alt != NULL );
529+
530+ /* Deal with all non-right-recursive rules. (They will need
531+ the exit-node generated here.) */
428532 for (rhs = rule -> rhs ; rhs ; rhs = rhs -> alt ) {
429533 int lastnode ;
430534
535+ /* If this rule is right-recursive, deal with it in the
536+ next pass. */
537+ if (rhs -> is_recursive && multiple_alternates ) {
538+ continue ;
539+ }
540+
431541 lastnode = expand_rhs (grammar , rule , rhs , rule_entry , rule_exit );
432542
433543 if (lastnode == NO_NODE ) {
434544 return NO_NODE ;
435545 }
436- else if (lastnode = = RECURSIVE_NODE ) {
437- /* The rhs ended with right-recursion, i.e. a transition to
438- an earlier state. Nothing needs to happen at this level. */
439- ;
546+ else if (lastnode < = RECURSIVE_NODE ) {
547+ /* Shouldn't happen in this pass. */
548+ E_ERROR ( "Internal error" );
549+ return NO_NODE ;
440550 }
441551 else if (rule_exit == NO_NODE ) {
442552 /* If this rule doesn't have an exit state yet, use the exit
@@ -452,6 +562,37 @@ expand_rule(jsgf_t * grammar, jsgf_rule_t * rule, int rule_entry,
452562 rule_exit = rule_entry ;
453563 }
454564
565+ /* Now deal with all the right-recursive rules. */
566+ for (rhs = rule -> rhs ; rhs ; rhs = rhs -> alt ) {
567+ int this_rule_entry , lastnode ;
568+
569+ /* If this rule is right-recursive, insert a null-transition to
570+ give the right-recursion a unique destination, i.e. so that
571+ it doesn't interfere with any of its sibling alternates. */
572+ this_rule_entry = rule_entry ;
573+ if (!rhs -> is_recursive || !multiple_alternates ) {
574+ continue ;
575+ }
576+ jsgf_add_link (grammar , NULL ,
577+ rule_entry , grammar -> nstate );
578+ this_rule_entry = grammar -> nstate ;
579+ ++ grammar -> nstate ;
580+ rule_stack_entry -> entry = this_rule_entry ;
581+
582+ lastnode = expand_rhs (grammar , rule , rhs , this_rule_entry , rule_exit );
583+
584+ if (lastnode > RECURSIVE_NODE ) {
585+ /* Shouldn't happen in this pass. */
586+ E_ERROR ("Internal error" );
587+ return NO_NODE ;
588+ }
589+
590+ /* Allow the right-recursion to exit. */
591+ lastnode = - lastnode + RECURSIVE_NODE ;
592+ jsgf_add_link (grammar , NULL ,
593+ lastnode , rule_exit );
594+ }
595+
455596 /* Pop this rule from the rule stack */
456597 ckd_free (gnode_ptr (grammar -> rulestack ));
457598 grammar -> rulestack = gnode_free (grammar -> rulestack , NULL );
@@ -541,6 +682,9 @@ jsgf_build_fsg_internal(jsgf_t * grammar, jsgf_rule_t * rule,
541682 grammar -> links = NULL ;
542683 grammar -> nstate = 0 ;
543684
685+ /* Determine which rules are right-recursive. */
686+ detect_recursion_rule (grammar , rule );
687+
544688 /* Create the top-level entry state, and expand the
545689 top-level rule. */
546690 rule_entry = grammar -> nstate ++ ;
0 commit comments