Support unquoted bare string values in DOT parser (e.g., gpt-5.2)

Add a bare_string parser that accepts values containing hyphens and
dots like gpt-5.2-codex-spark and gemini-3-flash-preview. These are
common in kilroy DOT files for model names but were previously rejected
by arc's strict identifier parser.

All 14 kilroy DOT files now parse successfully.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
This commit is contained in:
Bryan Helmkamp 2026-02-28 18:09:38 -05:00
parent 6d869aa8da
commit 77b7bf7481
2 changed files with 71 additions and 22 deletions

View file

@ -238,7 +238,27 @@ pub mod combinators {
Ok((rest, AstValue::Str(format!("{num}{unit}"))))
}
/// Parse an AST value: duration, float, integer, boolean, quoted string, or bare identifier.
/// Parse a bare string value containing hyphens and dots (e.g., `gpt-5.2-codex`).
///
/// Must start with an alpha/underscore character, then may continue with
/// alphanumeric, underscore, hyphen, or dot characters. Must contain at
/// least one hyphen or dot (otherwise `identifier` handles it).
pub fn bare_string(input: &str) -> IResult<&str, String> {
let (rest, raw) = recognize(pair(
take_while1(|c: char| c.is_ascii_alphabetic() || c == '_'),
take_while(|c: char| c.is_ascii_alphanumeric() || c == '_' || c == '-' || c == '.'),
))(input)?;
if !raw.contains('-') && !raw.contains('.') {
return Err(nom::Err::Error(nom::error::Error::new(
input,
nom::error::ErrorKind::Verify,
)));
}
Ok((rest, raw.to_string()))
}
/// Parse an AST value: duration, float, integer, boolean, quoted string, bare identifier,
/// or bare string (e.g., `gpt-5.2-codex`).
pub fn value(input: &str) -> IResult<&str, AstValue> {
let input = input.trim_start();
alt((
@ -247,6 +267,7 @@ pub mod combinators {
map(float_value, AstValue::Float),
map(integer_value, AstValue::Int),
map(boolean, AstValue::Bool),
map(bare_string, AstValue::Str),
map(identifier, |s: &str| AstValue::Ident(s.to_string())),
))(input)
}
@ -403,4 +424,39 @@ mod tests {
assert_eq!(value("true"), Ok(("", AstValue::Bool(true))));
assert_eq!(value("LR"), Ok(("", AstValue::Ident("LR".into()))));
}
#[test]
fn parse_bare_string_with_hyphens_and_dots() {
assert_eq!(
bare_string("gpt-5.2 rest"),
Ok((" rest", "gpt-5.2".into()))
);
assert_eq!(
bare_string("gpt-5.2-codex"),
Ok(("", "gpt-5.2-codex".into()))
);
assert_eq!(
bare_string("gpt-5.3-codex-spark"),
Ok(("", "gpt-5.3-codex-spark".into()))
);
assert_eq!(
bare_string("gemini-3-flash-preview"),
Ok(("", "gemini-3-flash-preview".into()))
);
// Plain identifier without hyphens/dots should fail (identifier handles it)
assert!(bare_string("LR").is_err());
assert!(bare_string("openai").is_err());
}
#[test]
fn parse_value_bare_string() {
assert_eq!(
value("gpt-5.2"),
Ok(("", AstValue::Str("gpt-5.2".into())))
);
assert_eq!(
value("gpt-5.2-codex"),
Ok(("", AstValue::Str("gpt-5.2-codex".into())))
);
}
}

View file

@ -26,34 +26,27 @@ fn parse_kilroy_simple_example() {
assert!(graph.find_exit_node().is_some());
}
// The batch_*.dot files use unquoted values with hyphens/dots (e.g., `llm_model=gpt-5.2`)
// which is valid in kilroy's more lenient Go parser but not in arc's strict DOT parser.
// These tests document the parser gap: arc requires quoting such values.
#[test]
fn parse_kilroy_batch_clean_requires_quoted_model_values() {
let err = parse_kilroy_dot("batch_clean.dot").unwrap_err();
assert!(
err.contains("grammar error"),
"expected grammar error for unquoted `gpt-5.2`, got: {err}"
);
fn parse_kilroy_batch_clean() {
let graph = parse_kilroy_dot("batch_clean.dot").unwrap();
assert_eq!(graph.name, "G");
assert_eq!(graph.nodes.len(), 3);
assert!(graph.find_start_node().is_some());
assert!(graph.find_exit_node().is_some());
}
#[test]
fn parse_kilroy_batch_has_errors_requires_quoted_model_values() {
let err = parse_kilroy_dot("batch_has_errors.dot").unwrap_err();
assert!(
err.contains("grammar error"),
"expected grammar error for unquoted `gpt-5.2`, got: {err}"
);
fn parse_kilroy_batch_has_errors() {
// This file is intentionally missing llm_provider on the work node.
// It should still parse successfully — validation is separate from parsing.
let graph = parse_kilroy_dot("batch_has_errors.dot").unwrap();
assert_eq!(graph.nodes.len(), 3);
}
#[test]
fn parse_kilroy_batch_warnings_only_requires_quoted_model_values() {
let err = parse_kilroy_dot("batch_warnings_only.dot").unwrap_err();
assert!(
err.contains("grammar error"),
"expected grammar error for unquoted `gpt-5.2`, got: {err}"
);
fn parse_kilroy_batch_warnings_only() {
let graph = parse_kilroy_dot("batch_warnings_only.dot").unwrap();
assert_eq!(graph.nodes.len(), 3);
}
#[test]