use std::sync::Arc;
use swiftlet::error::{GrammarError, SwiftletError};
use swiftlet::grammar::Algorithm;
use swiftlet::{Ambiguity, ParserConfig, Swiftlet};
fn earley(start: &str) -> Arc<ParserConfig> {
Arc::new(ParserConfig {
algorithm: Algorithm::Earley,
start: start.to_string(),
..Default::default()
})
}
fn clr(start: &str) -> Arc<ParserConfig> {
Arc::new(ParserConfig {
algorithm: Algorithm::CLR,
start: start.to_string(),
..Default::default()
})
}
#[test]
fn rule_compiler_maybe_in_rule_earley() {
let grammar = r#"
start: greeting
greeting: "hi" [name]
name: WORD
WORD: /\w+/
%import WS
%ignore WS
"#;
let s = Swiftlet::from_str(grammar).unwrap();
assert!(s.parser(earley("start")).parse("hi").is_ok());
assert!(s.parser(earley("start")).parse("hi Alice").is_ok());
}
#[test]
fn rule_compiler_maybe_in_rule_clr() {
let grammar = r#"
start: greeting
greeting: "hi" [name]
name: WORD
WORD: /\w+/
%import WS
%ignore WS
"#;
let s = Swiftlet::from_str(grammar).unwrap();
assert!(s.parser(clr("start")).parse("hi").is_ok());
assert!(s.parser(clr("start")).parse("hi Alice").is_ok());
}
#[test]
fn rule_compiler_range_in_rule_is_exercised() {
let grammar = "start: letter\nletter: \"a\"..\"z\"\n";
let err = match Swiftlet::from_str(grammar) {
Ok(_) => panic!("range-in-rule with lowercase chars should fail to compile"),
Err(e) => e,
};
assert!(
matches!(err, SwiftletError::Grammar(_)),
"expected GrammarError, got: {:?}",
err
);
}
#[test]
fn rule_compiler_inline_regex_in_rule_earley() {
let grammar = r#"
start: token
token: /[a-z]+/
"#;
let s = Swiftlet::from_str(grammar).unwrap();
assert!(s.parser(earley("start")).parse("hello").is_ok());
}
#[test]
fn rule_compiler_inline_regex_in_rule_clr() {
let grammar = r#"
start: token
token: /[a-z]+/
"#;
let s = Swiftlet::from_str(grammar).unwrap();
assert!(s.parser(clr("start")).parse("hello").is_ok());
}
#[test]
fn rule_compiler_rule_priority_earley() {
let grammar = r#"
start: expr
expr.1: keyword
expr: NAME
keyword: "select"
NAME: /[a-z]+/
%import WS
%ignore WS
"#;
let s = Swiftlet::from_str(grammar).unwrap();
assert!(s.parser(earley("start")).parse("select").is_ok());
assert!(s.parser(earley("start")).parse("hello").is_ok());
}
#[test]
fn rule_compiler_or_expansion_cache_hit() {
let grammar = r#"
start: a | b
a: c | d
b: c | d
c: "x"
d: "y"
"#;
let s = Swiftlet::from_str(grammar).unwrap();
assert!(s.parser(earley("start")).parse("x").is_ok());
assert!(s.parser(earley("start")).parse("y").is_ok());
}
#[test]
fn rule_compiler_inline_string_case_insensitive_in_rule() {
let grammar = r#"
start: greeting
greeting: "hello"i
"#;
let s = Swiftlet::from_str(grammar).unwrap();
assert!(s.parser(earley("start")).parse("hello").is_ok());
assert!(s.parser(earley("start")).parse("HELLO").is_ok());
assert!(s.parser(earley("start")).parse("Hello").is_ok());
}
#[test]
fn rule_compiler_inline_string_case_insensitive_in_rule_clr() {
let grammar = r#"
start: greeting
greeting: "hello"i
"#;
let s = Swiftlet::from_str(grammar).unwrap();
assert!(s.parser(clr("start")).parse("HELLO").is_ok());
}
#[test]
fn load_grammar_returns_error_for_undefined_non_terminal() {
let grammar = "start: undefined_rule\n";
let err = match Swiftlet::from_str(grammar) {
Ok(_) => panic!("expected error for undefined non-terminal"),
Err(e) => e,
};
assert!(
matches!(
err,
SwiftletError::Grammar(GrammarError::RuleProductionNotFound(_))
),
"expected RuleProductionNotFound, got: {:?}",
err
);
}
#[test]
fn load_grammar_with_inline_literal_ignore_directive() {
let grammar = r#"
start: WORD+
WORD: /[a-zA-Z]+/
%ignore " "
"#;
let s = Swiftlet::from_str(grammar).unwrap();
assert!(s.parser(earley("start")).parse("hello world").is_ok());
}
#[test]
fn load_grammar_with_inline_literal_ignore_clr() {
let grammar = r#"
start: WORD+
WORD: /[a-zA-Z]+/
%ignore " "
"#;
let s = Swiftlet::from_str(grammar).unwrap();
assert!(s.parser(clr("start")).parse("foo bar").is_ok());
}
#[test]
fn terminal_compiler_multiline_regex_flag() {
let grammar = r#"
start: LINE+
LINE: /^\w+/m
%import (NEWLINE, WS_INLINE)
%ignore WS_INLINE
%ignore NEWLINE
"#;
let result = Swiftlet::from_str(grammar);
assert!(result.is_ok(), "multiline regex grammar should compile: {:?}", result.err());
}
#[test]
fn earley_explicit_ambiguity_returns_ambiguity_tree() {
let grammar = r#"
start: expr
expr: expr "+" expr
| INT
%import (WS, INT)
%ignore WS
"#;
let config = Arc::new(ParserConfig {
algorithm: Algorithm::Earley,
ambiguity: Ambiguity::Explicit,
start: "start".to_string(),
debug: false,
});
let swiftlet = Swiftlet::from_str(grammar).unwrap();
let result = swiftlet.parser(config).parse("1 + 2 + 3");
assert!(result.is_ok(), "explicit ambiguity parse should succeed");
let tree_str = result.unwrap().inline_text();
assert!(
tree_str.contains("_ambiguity") || tree_str.contains("start"),
"unexpected tree: {}",
tree_str
);
}
#[test]
fn clr_parse_fails_cleanly_on_incomplete_input() {
let grammar = r#"
start: A B
A: "x"
B: "y"
"#;
let s = Swiftlet::from_str(grammar).unwrap();
let err = s.parser(clr("start")).parse("x").unwrap_err();
assert!(
matches!(
err,
SwiftletError::Parse(_) | SwiftletError::Lexer(_)
),
"unexpected error type: {:?}",
err
);
}
#[test]
fn grammar_with_ignore_uses_string_literal_form() {
let grammar = r#"
start: NAME
NAME: /[a-zA-Z]+/
%ignore ","
"#;
let s = Swiftlet::from_str(grammar).unwrap();
assert!(s.parser(earley("start")).parse("hello").is_ok());
}
#[test]
fn grammar_with_multiple_terminals_deduplicates_correctly() {
let grammar = r#"
start: A B C
A: "aa"
B: "bb"
C: "cc"
"#;
let s = Swiftlet::from_str(grammar).unwrap();
assert!(s.parser(earley("start")).parse("aabbcc").is_ok());
assert!(s.parser(clr("start")).parse("aabbcc").is_ok());
}
#[test]
fn rule_compiler_expandable_rule_earley() {
let grammar = r#"
start: value
?value: number | word
number: INT
word: WORD
WORD: /[a-z]+/
%import (WS, INT)
%ignore WS
"#;
let s = Swiftlet::from_str(grammar).unwrap();
let result = s.parser(earley("start")).parse("42").unwrap();
let text = result.inline_text();
assert!(
!text.contains("Tree(\"value\""),
"?value should be expanded away, got: {}",
text
);
let result2 = s.parser(earley("start")).parse("hello").unwrap();
let text2 = result2.inline_text();
assert!(!text2.contains("Tree(\"value\""), "?value should expand: {}", text2);
}
#[test]
fn rule_compiler_expandable_rule_clr() {
let grammar = r#"
start: value
?value: number | word
number: INT
word: WORD
WORD: /[a-z]+/
%import (WS, INT)
%ignore WS
"#;
let s = Swiftlet::from_str(grammar).unwrap();
assert!(s.parser(clr("start")).parse("99").is_ok());
assert!(s.parser(clr("start")).parse("abc").is_ok());
}
#[test]
fn earley_explicit_ambiguity_produces_ambiguity_node_for_ambiguous_input() {
let grammar = r#"
start: expr
expr: expr "+" expr
| INT
%import (WS, INT)
%ignore WS
"#;
let config = Arc::new(ParserConfig {
algorithm: Algorithm::Earley,
ambiguity: Ambiguity::Explicit,
start: "start".to_string(),
debug: false,
});
let swiftlet = Swiftlet::from_str(grammar).unwrap();
let result = swiftlet.parser(config).parse("1 + 2 + 3").unwrap();
let text = result.inline_text();
assert!(
text.contains("_ambiguity"),
"expected _ambiguity wrapper for ambiguous parse, got: {}",
text
);
}
#[test]
fn earley_explicit_ambiguity_with_expandable_rule() {
let grammar = r#"
start: expr
expr: expr "+" expr | base
?base: INT
%import (WS, INT)
%ignore WS
"#;
let config = Arc::new(ParserConfig {
algorithm: Algorithm::Earley,
ambiguity: Ambiguity::Explicit,
start: "start".to_string(),
debug: false,
});
let swiftlet = Swiftlet::from_str(grammar).unwrap();
let result = swiftlet.parser(config).parse("1 + 2 + 3");
assert!(result.is_ok(), "explicit ambiguity with ?base should succeed: {:?}", result.err());
}
#[test]
fn clr_fails_on_unknown_token_at_start() {
let grammar = r#"
start: INT
%import INT
"#;
let err = Swiftlet::from_str(grammar)
.unwrap()
.parser(clr("start"))
.parse("abc")
.unwrap_err();
assert!(matches!(err, SwiftletError::Parse(_) | SwiftletError::Lexer(_)));
}