#![allow(clippy::unwrap_used, clippy::expect_used, clippy::panic)]
use lang_forge::Language;
use repl_lang::{Feed, Input, Session, Status};
fn calc() -> Language {
Language::from_lsf(
r##"
[language]
name = "calc"
[rules]
program = "stmt*"
stmt = "'let' IDENT '=' expr ';' | expr ';'"
group = "'(' expr ')'"
[rules.expr]
operand = "NUMBER | IDENT | group"
levels = [
{ left = ["+", "-"] },
{ left = ["*", "/"] },
]
"##,
)
.expect("the schematic forges")
}
#[derive(Debug, PartialEq, Eq)]
struct Done {
text: String,
errors: bool,
}
fn pipeline(language: &Language, input: Input<'_>) -> Status<Done> {
let parse = language.parse(input.text());
if input.is_incomplete_relative(parse.diagnostics()) {
Status::Incomplete
} else {
Status::Complete(Done {
text: input.text().to_owned(),
errors: parse.has_errors(),
})
}
}
fn run(session: &mut Session, language: &Language, lines: &[&str]) -> Vec<Option<Done>> {
lines
.iter()
.map(|line| {
match session
.feed(line, |input| pipeline(language, input))
.unwrap()
{
Feed::Complete { value, .. } => Some(value),
Feed::Incomplete => None,
Feed::Empty => panic!("unexpected blank line {line:?}"),
}
})
.collect()
}
fn done(text: &str, errors: bool) -> Option<Done> {
Some(Done {
text: text.to_owned(),
errors,
})
}
#[test]
fn test_relative_spans_continue_unfinished_entries_in_every_entry() {
let language = calc();
let mut session = Session::new();
let out = run(
&mut session,
&language,
&[
"let a = 1;", "let b = (2 +",
"3);", "a *",
"b",
";", "let total = (a + b) * (a - b) + (1 +", "2);",
],
);
assert_eq!(
out,
[
done("let a = 1;\n", false),
None,
done("let b = (2 +\n3);\n", false),
None,
None,
done("a *\nb\n;\n", false),
None,
done("let total = (a + b) * (a - b) + (1 +\n2);\n", false),
]
);
assert_eq!(session.len(), 4);
}
#[test]
fn test_relative_spans_report_mid_entry_errors_at_once() {
let language = calc();
let mut session = Session::new();
let out = run(
&mut session,
&language,
&[
"let a = 1;",
"let b = 1 + ) ;", "let c = (1 +", "2;", "a + ", "1;",
],
);
assert_eq!(
out,
[
done("let a = 1;\n", false),
done("let b = 1 + ) ;\n", true),
None,
done("let c = (1 +\n2;\n", true),
None,
done("a + \n1;\n", false),
]
);
}
#[test]
fn test_relative_and_global_methods_agree_on_every_entry() {
let language = calc();
let mut session = Session::new();
let lines = [
"let x = 10;",
"x * (2 +",
"3)",
";",
"let y = x -",
"1;",
"y +",
"x;",
];
for line in lines {
let feed = session
.feed(line, |input| {
let parse = language.parse(input.text());
let relative = input.is_incomplete_relative(parse.diagnostics());
let shifted: Vec<_> = parse
.diagnostics()
.iter()
.map(|d| {
let span = d.primary().span();
let base = input.base().to_u32();
let global = repl_lang::Span::new(
span.start().to_u32() + base,
span.end().to_u32() + base,
);
diag_lang::Diagnostic::new(
d.severity(),
d.message(),
diag_lang::Label::new(global, ""),
)
})
.collect();
assert_eq!(
input.is_incomplete(&shifted),
relative,
"entry {} line {line:?}",
input.number()
);
if relative {
Status::Incomplete
} else {
Status::Complete(())
}
})
.unwrap();
assert!(!matches!(feed, Feed::Empty));
}
assert_eq!(session.len(), 4);
}
#[test]
fn test_global_method_misjudges_relative_spans_after_the_first_entry() {
let language = calc();
let mut session = Session::new();
session
.feed("let a = 1;", |_| Status::Complete(()))
.unwrap();
session
.feed("let b = (2 +", |input| {
let parse = language.parse(input.text());
assert!(parse.has_errors());
assert!(input.is_incomplete_relative(parse.diagnostics()));
assert!(!input.is_incomplete(parse.diagnostics()));
Status::Complete(())
})
.unwrap();
}