@@ -13182,6 +13182,58 @@ static std::vector<llama_grammar_candidate> llama_grammar_reject_candidates(
1318213182    return rejects;
1318313183}
1318413184
13185+ static bool llama_grammar_detect_left_recursion(
13186+         const std::vector<std::vector<llama_grammar_element>> & rules,
13187+         size_t                                                  rule_index,
13188+         std::vector<bool>                                     * rules_visited,
13189+         std::vector<bool>                                     * rules_in_progress,
13190+         std::vector<bool>                                     * rules_may_be_empty) {
13191+     if ((*rules_in_progress)[rule_index]) {
13192+         return true;
13193+     }
13194+ 
13195+     (*rules_in_progress)[rule_index] = true;
13196+ 
13197+     const std::vector<llama_grammar_element> & rule = rules[rule_index];
13198+ 
13199+     // First check if the rule might produce the empty string. This could be done combined with the second
13200+     // step but it's more readable as two steps.
13201+     bool at_rule_start = true;
13202+     for (size_t i = 0; i < rule.size(); i++) {
13203+         if (llama_grammar_is_end_of_sequence(&rule[i])) {
13204+             if (at_rule_start) {
13205+                 (*rules_may_be_empty)[rule_index] = true;
13206+                 break;
13207+             }
13208+             at_rule_start = true;
13209+         } else {
13210+             at_rule_start = false;
13211+         }
13212+     }
13213+ 
13214+     // Second, recurse into leftmost nonterminals (or next-leftmost as long as the previous nonterminal may
13215+     // be empty)
13216+     bool recurse_into_nonterminal = true;
13217+     for (size_t i = 0; i < rule.size(); i++) {
13218+         if (rule[i].type == LLAMA_GRETYPE_RULE_REF && recurse_into_nonterminal) {
13219+             if (llama_grammar_detect_left_recursion(rules, (size_t)rule[i].value, rules_visited, rules_in_progress, rules_may_be_empty)) {
13220+                 return true;
13221+             }
13222+             if (!((*rules_may_be_empty)[(size_t)rule[i].value])) {
13223+                 recurse_into_nonterminal = false;
13224+             }
13225+         } else if (llama_grammar_is_end_of_sequence(&rule[i])) {
13226+             recurse_into_nonterminal = true;
13227+         } else {
13228+             recurse_into_nonterminal = false;
13229+         }
13230+     }
13231+ 
13232+     (*rules_in_progress)[rule_index] = false;
13233+     (*rules_visited)[rule_index] = true;
13234+     return false;
13235+ }
13236+ 
1318513237//
1318613238// grammar - external
1318713239//
@@ -13201,6 +13253,19 @@ struct llama_grammar * llama_grammar_init(
1320113253        vec_rules[i].push_back({LLAMA_GRETYPE_END, 0});
1320213254    }
1320313255
13256+     // Check for left recursion
13257+     std::vector<bool> rules_visited(n_rules);
13258+     std::vector<bool> rules_in_progress(n_rules);
13259+     std::vector<bool> rules_may_be_empty(n_rules);
13260+     for (size_t i = 0; i < n_rules; i++) {
13261+         if (rules_visited[i]) {
13262+             continue;
13263+         }
13264+         if (llama_grammar_detect_left_recursion(vec_rules, i, &rules_visited, &rules_in_progress, &rules_may_be_empty)) {
13265+             throw std::runtime_error(format("unsupported grammar, left recursion detected for nonterminal at index %zu", i));
13266+         }
13267+     }
13268+ 
1320413269    // loop over alternates of start rule to build initial stacks
1320513270    std::vector<std::vector<const llama_grammar_element *>> stacks;
1320613271    pos = vec_rules[start_rule_index].data();
@@ -13223,6 +13288,9 @@ struct llama_grammar * llama_grammar_init(
1322313288        }
1322413289    } while (true);
1322513290
13291+     // Important: vec_rules has to be moved here, not copied, because stacks contains
13292+     // pointers to elements of vec_rules. If vec_rules were copied into llama_grammar
13293+     // then the pointers would be invalidated when the local vec_rules goes out of scope.
1322613294    return new llama_grammar{ std::move(vec_rules), std::move(stacks), {} };
1322713295}
1322813296
0 commit comments