|
@@ -13182,6 +13182,58 @@ static std::vector<llama_grammar_candidate> llama_grammar_reject_candidates(
|
|
|
return rejects;
|
|
return rejects;
|
|
|
}
|
|
}
|
|
|
|
|
|
|
|
|
|
+static bool llama_grammar_detect_left_recursion(
|
|
|
|
|
+ const std::vector<std::vector<llama_grammar_element>> & rules,
|
|
|
|
|
+ size_t rule_index,
|
|
|
|
|
+ std::vector<bool> * rules_visited,
|
|
|
|
|
+ std::vector<bool> * rules_in_progress,
|
|
|
|
|
+ std::vector<bool> * rules_may_be_empty) {
|
|
|
|
|
+ if ((*rules_in_progress)[rule_index]) {
|
|
|
|
|
+ return true;
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ (*rules_in_progress)[rule_index] = true;
|
|
|
|
|
+
|
|
|
|
|
+ const std::vector<llama_grammar_element> & rule = rules[rule_index];
|
|
|
|
|
+
|
|
|
|
|
+ // First check if the rule might produce the empty string. This could be done combined with the second
|
|
|
|
|
+ // step but it's more readable as two steps.
|
|
|
|
|
+ bool at_rule_start = true;
|
|
|
|
|
+ for (size_t i = 0; i < rule.size(); i++) {
|
|
|
|
|
+ if (llama_grammar_is_end_of_sequence(&rule[i])) {
|
|
|
|
|
+ if (at_rule_start) {
|
|
|
|
|
+ (*rules_may_be_empty)[rule_index] = true;
|
|
|
|
|
+ break;
|
|
|
|
|
+ }
|
|
|
|
|
+ at_rule_start = true;
|
|
|
|
|
+ } else {
|
|
|
|
|
+ at_rule_start = false;
|
|
|
|
|
+ }
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ // Second, recurse into leftmost nonterminals (or next-leftmost as long as the previous nonterminal may
|
|
|
|
|
+ // be empty)
|
|
|
|
|
+ bool recurse_into_nonterminal = true;
|
|
|
|
|
+ for (size_t i = 0; i < rule.size(); i++) {
|
|
|
|
|
+ if (rule[i].type == LLAMA_GRETYPE_RULE_REF && recurse_into_nonterminal) {
|
|
|
|
|
+ if (llama_grammar_detect_left_recursion(rules, (size_t)rule[i].value, rules_visited, rules_in_progress, rules_may_be_empty)) {
|
|
|
|
|
+ return true;
|
|
|
|
|
+ }
|
|
|
|
|
+ if (!((*rules_may_be_empty)[(size_t)rule[i].value])) {
|
|
|
|
|
+ recurse_into_nonterminal = false;
|
|
|
|
|
+ }
|
|
|
|
|
+ } else if (llama_grammar_is_end_of_sequence(&rule[i])) {
|
|
|
|
|
+ recurse_into_nonterminal = true;
|
|
|
|
|
+ } else {
|
|
|
|
|
+ recurse_into_nonterminal = false;
|
|
|
|
|
+ }
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ (*rules_in_progress)[rule_index] = false;
|
|
|
|
|
+ (*rules_visited)[rule_index] = true;
|
|
|
|
|
+ return false;
|
|
|
|
|
+}
|
|
|
|
|
+
|
|
|
//
|
|
//
|
|
|
// grammar - external
|
|
// grammar - external
|
|
|
//
|
|
//
|
|
@@ -13201,6 +13253,19 @@ struct llama_grammar * llama_grammar_init(
|
|
|
vec_rules[i].push_back({LLAMA_GRETYPE_END, 0});
|
|
vec_rules[i].push_back({LLAMA_GRETYPE_END, 0});
|
|
|
}
|
|
}
|
|
|
|
|
|
|
|
|
|
+ // Check for left recursion
|
|
|
|
|
+ std::vector<bool> rules_visited(n_rules);
|
|
|
|
|
+ std::vector<bool> rules_in_progress(n_rules);
|
|
|
|
|
+ std::vector<bool> rules_may_be_empty(n_rules);
|
|
|
|
|
+ for (size_t i = 0; i < n_rules; i++) {
|
|
|
|
|
+ if (rules_visited[i]) {
|
|
|
|
|
+ continue;
|
|
|
|
|
+ }
|
|
|
|
|
+ if (llama_grammar_detect_left_recursion(vec_rules, i, &rules_visited, &rules_in_progress, &rules_may_be_empty)) {
|
|
|
|
|
+ throw std::runtime_error(format("unsupported grammar, left recursion detected for nonterminal at index %zu", i));
|
|
|
|
|
+ }
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
// loop over alternates of start rule to build initial stacks
|
|
// loop over alternates of start rule to build initial stacks
|
|
|
std::vector<std::vector<const llama_grammar_element *>> stacks;
|
|
std::vector<std::vector<const llama_grammar_element *>> stacks;
|
|
|
pos = vec_rules[start_rule_index].data();
|
|
pos = vec_rules[start_rule_index].data();
|
|
@@ -13223,6 +13288,9 @@ struct llama_grammar * llama_grammar_init(
|
|
|
}
|
|
}
|
|
|
} while (true);
|
|
} while (true);
|
|
|
|
|
|
|
|
|
|
+ // Important: vec_rules has to be moved here, not copied, because stacks contains
|
|
|
|
|
+ // pointers to elements of vec_rules. If vec_rules were copied into llama_grammar
|
|
|
|
|
+ // then the pointers would be invalidated when the local vec_rules goes out of scope.
|
|
|
return new llama_grammar{ std::move(vec_rules), std::move(stacks), {} };
|
|
return new llama_grammar{ std::move(vec_rules), std::move(stacks), {} };
|
|
|
}
|
|
}
|
|
|
|
|
|