Skip to content

Commit 0dbc7f0

Browse files
authored
Fix #236: treat max_tokens=0 as empty string (#356)
`max_tokens=0` was compiled into a token-limited lexeme that never terminates at runtime (the `{`-then-garbage symptom in #236). Since a rule capped at zero tokens can only match the empty string, this special-cases it to compile to `""`, placed before the stop/terminal/subgrammar dispatch so it's handled uniformly for all body types. Includes a regression test (`test_ll_max_tokens_zero`) with the issue's repro plus a `name: ""` equivalence baseline. Full parser suite + fmt + clippy clean.
1 parent c9bedc5 commit 0dbc7f0

2 files changed

Lines changed: 36 additions & 0 deletions

File tree

parser/src/lark/compiler.rs

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

527+
if rule.max_tokens == Some(0) {
528+
// max_tokens=N caps a rule at N emitted tokens, so max_tokens=0
529+
// forces zero emitted tokens: the rule can only match the empty
530+
// string (epsilon). Compiling it as a token-limited lexeme (or
531+
// subgrammar) instead produces broken runtime output -- the matcher
532+
// emits an opening token and then never terminates. Treat it the
533+
// same as an empty-string body `""`.
534+
// See https://github.com/guidance-ai/llguidance/issues/236
535+
return Ok(self.builder.string(""));
536+
}
537+
527538
let id = if let Some(stop) = rule.stop_like() {
528539
let is_suffix = rule.suffix.is_some();
529540
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 forces zero emitted tokens, i.e. the rule matches only the
686+
// empty string -- so `name` contributes nothing and we go straight to "xy".
687+
// See https://github.com/guidance-ai/llguidance/issues/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)