Skip to content

Commit c6f1a03

Browse files
committed
fix(lark): treat max_tokens=0 as empty string
A rule with max_tokens=0 should be allowed to emit no tokens, i.e. match only the empty string. It was instead compiled to a token-limited lexeme that never terminates at runtime (emits an opening token then loops). Special-case max_tokens=0 to compile to "", uniformly across terminal, subgrammar and json bodies. Adds a regression test. Fixes #236
1 parent 0384f3f commit c6f1a03

2 files changed

Lines changed: 35 additions & 0 deletions

File tree

parser/src/lark/compiler.rs

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -524,6 +524,16 @@ impl Compiler {
524524
bail!("max_tokens= is not supported for parametric rules");
525525
}
526526

527+
if rule.max_tokens == Some(0) {
528+
// max_tokens=0 means the rule is allowed to emit no tokens at all,
529+
// i.e. it matches only the empty string. Compiling it as a
530+
// token-limited lexeme (or subgrammar) instead produces broken
531+
// runtime output -- the matcher emits an opening token and then
532+
// never terminates. Treat it the same as an empty-string body `""`.
533+
// See https://github.com/guidance-ai/llguidance/issues/236
534+
return Ok(self.builder.string(""));
535+
}
536+
527537
let id = if let Some(stop) = rule.stop_like() {
528538
let is_suffix = rule.suffix.is_some();
529539
let is_empty = matches!(stop, Value::LiteralString(s, _) if s.is_empty());

parser/tests/test_ll.rs

Lines changed: 25 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -680,6 +680,31 @@ fn test_ll_max_tokens() {
680680
);
681681
}
682682

683+
#[test]
684+
fn test_ll_max_tokens_zero() {
685+
// max_tokens=0 means the rule emits no tokens, i.e. it matches the empty
686+
// string -- so `name` contributes nothing and we go straight to "xy".
687+
// See issue #236.
688+
689+
// baseline: an explicitly empty rule works
690+
check_lark_grammar(
691+
r#"start: "Foo " num " x" name "y"
692+
num: /[0-9]+/
693+
name: ""
694+
"#,
695+
&["Foo‧ ", "5‧6‧ ", "xy"],
696+
);
697+
698+
// the bug: max_tokens=0 should behave identically to name: ""
699+
check_lark_grammar(
700+
r#"start: "Foo " num " x" name "y"
701+
num: /[0-9]+/
702+
name[max_tokens=0]: /.*/
703+
"#,
704+
&["Foo‧ ", "5‧6‧ ", "xy"],
705+
);
706+
}
707+
683708
#[test]
684709
fn test_ll_special_token() {
685710
check_lark_grammar(

0 commit comments

Comments
 (0)