@@ -13191,6 +13191,58 @@ static std::vector<llama_grammar_candidate> llama_grammar_reject_candidates(
13191
13191
return rejects;
13192
13192
}
13193
13193
13194
+ static bool llama_grammar_detect_left_recursion(
13195
+ const std::vector<std::vector<llama_grammar_element>> & rules,
13196
+ size_t rule_index,
13197
+ std::vector<bool> * rules_visited,
13198
+ std::vector<bool> * rules_in_progress,
13199
+ std::vector<bool> * rules_may_be_empty) {
13200
+ if ((*rules_in_progress)[rule_index]) {
13201
+ return true;
13202
+ }
13203
+
13204
+ (*rules_in_progress)[rule_index] = true;
13205
+
13206
+ const std::vector<llama_grammar_element> & rule = rules[rule_index];
13207
+
13208
+ // First check if the rule might produce the empty string. This could be done combined with the second
13209
+ // step but it's more readable as two steps.
13210
+ bool at_rule_start = true;
13211
+ for (size_t i = 0; i < rule.size(); i++) {
13212
+ if (llama_grammar_is_end_of_sequence(&rule[i])) {
13213
+ if (at_rule_start) {
13214
+ (*rules_may_be_empty)[rule_index] = true;
13215
+ break;
13216
+ }
13217
+ at_rule_start = true;
13218
+ } else {
13219
+ at_rule_start = false;
13220
+ }
13221
+ }
13222
+
13223
+ // Second, recurse into leftmost nonterminals (or next-leftmost as long as the previous nonterminal may
13224
+ // be empty)
13225
+ bool recurse_into_nonterminal = true;
13226
+ for (size_t i = 0; i < rule.size(); i++) {
13227
+ if (rule[i].type == LLAMA_GRETYPE_RULE_REF && recurse_into_nonterminal) {
13228
+ if (llama_grammar_detect_left_recursion(rules, (size_t)rule[i].value, rules_visited, rules_in_progress, rules_may_be_empty)) {
13229
+ return true;
13230
+ }
13231
+ if (!((*rules_may_be_empty)[(size_t)rule[i].value])) {
13232
+ recurse_into_nonterminal = false;
13233
+ }
13234
+ } else if (llama_grammar_is_end_of_sequence(&rule[i])) {
13235
+ recurse_into_nonterminal = true;
13236
+ } else {
13237
+ recurse_into_nonterminal = false;
13238
+ }
13239
+ }
13240
+
13241
+ (*rules_in_progress)[rule_index] = false;
13242
+ (*rules_visited)[rule_index] = true;
13243
+ return false;
13244
+ }
13245
+
13194
13246
//
13195
13247
// grammar - external
13196
13248
//
@@ -13210,6 +13262,19 @@ struct llama_grammar * llama_grammar_init(
13210
13262
vec_rules[i].push_back({LLAMA_GRETYPE_END, 0});
13211
13263
}
13212
13264
13265
+ // Check for left recursion
13266
+ std::vector<bool> rules_visited(n_rules);
13267
+ std::vector<bool> rules_in_progress(n_rules);
13268
+ std::vector<bool> rules_may_be_empty(n_rules);
13269
+ for (size_t i = 0; i < n_rules; i++) {
13270
+ if (rules_visited[i]) {
13271
+ continue;
13272
+ }
13273
+ if (llama_grammar_detect_left_recursion(vec_rules, i, &rules_visited, &rules_in_progress, &rules_may_be_empty)) {
13274
+ throw std::runtime_error(format("unsupported grammar, left recursion detected for nonterminal at index %zu", i));
13275
+ }
13276
+ }
13277
+
13213
13278
// loop over alternates of start rule to build initial stacks
13214
13279
std::vector<std::vector<const llama_grammar_element *>> stacks;
13215
13280
pos = vec_rules[start_rule_index].data();
@@ -13232,6 +13297,9 @@ struct llama_grammar * llama_grammar_init(
13232
13297
}
13233
13298
} while (true);
13234
13299
13300
+ // Important: vec_rules has to be moved here, not copied, because stacks contains
13301
+ // pointers to elements of vec_rules. If vec_rules were copied into llama_grammar
13302
+ // then the pointers would be invalidated when the local vec_rules goes out of scope.
13235
13303
return new llama_grammar{ std::move(vec_rules), std::move(stacks), {} };
13236
13304
}
13237
13305
0 commit comments