use diag_lang::{Diagnostic, Severity};
use lexer_lang::Cursor;
use source_lang::{BytePos, Span};
#[derive(Clone, Copy, Debug, PartialEq, Eq)]
pub struct Input<'a> {
text: &'a str,
base: u32,
number: u32,
}
impl<'a> Input<'a> {
#[inline]
pub(crate) const fn new(text: &'a str, base: u32, number: u32) -> Self {
Self { text, base, number }
}
#[inline]
#[must_use]
pub const fn text(&self) -> &'a str {
self.text
}
#[inline]
#[must_use]
pub const fn base(&self) -> BytePos {
BytePos::new(self.base)
}
#[inline]
#[must_use]
pub const fn span(&self) -> Span {
Span::new(self.base, self.base + self.text.len() as u32)
}
#[inline]
#[must_use]
pub const fn number(&self) -> u32 {
self.number
}
#[inline]
#[must_use]
pub fn cursor(&self) -> Cursor<'a> {
Cursor::with_base(self.text, self.base)
}
#[must_use]
pub fn is_incomplete(&self, errors: &[Diagnostic]) -> bool {
errors_at_end(errors, self.base + self.meaningful_len())
}
#[must_use]
pub fn is_incomplete_relative(&self, errors: &[Diagnostic]) -> bool {
errors_at_end(errors, self.meaningful_len())
}
#[inline]
fn meaningful_len(&self) -> u32 {
self.text.trim_end().len() as u32
}
}
fn errors_at_end(errors: &[Diagnostic], end: u32) -> bool {
let mut any = false;
for error in errors.iter().filter(|d| d.severity() == Severity::Error) {
if error.primary().span().start().to_u32() < end {
return false;
}
any = true;
}
any
}
#[cfg(test)]
mod tests {
use diag_lang::Label;
use super::*;
fn diag(severity: Severity, at: u32) -> Diagnostic {
Diagnostic::new(severity, "m", Label::new(Span::empty(at), "l"))
}
#[test]
fn test_span_covers_text_from_base() {
let input = Input::new("ab\n", 10, 1);
assert_eq!(input.span(), Span::new(10, 13));
assert_eq!(input.base(), BytePos::new(10));
}
#[test]
fn test_cursor_positions_are_global() {
let input = Input::new("xy\n", 40, 1);
let mut cursor = input.cursor();
assert_eq!(cursor.pos(), BytePos::new(40));
assert_eq!(cursor.bump(), Some('x'));
assert_eq!(cursor.pos(), BytePos::new(41));
}
#[test]
fn test_is_incomplete_no_errors_is_false() {
assert!(!Input::new("x\n", 0, 1).is_incomplete(&[]));
}
#[test]
fn test_is_incomplete_error_at_trimmed_end_is_true() {
let input = Input::new("1 + \n", 0, 1);
assert!(input.is_incomplete(&[diag(Severity::Error, 3)]));
assert!(input.is_incomplete(&[diag(Severity::Error, 7)]));
assert!(!input.is_incomplete(&[diag(Severity::Error, 2)]));
}
#[test]
fn test_is_incomplete_respects_base() {
let input = Input::new("1 +\n", 100, 1);
assert!(input.is_incomplete(&[diag(Severity::Error, 103)]));
assert!(!input.is_incomplete(&[diag(Severity::Error, 3)]));
}
#[test]
fn test_is_incomplete_ignores_non_errors() {
let input = Input::new("1 +\n", 0, 1);
assert!(!input.is_incomplete(&[diag(Severity::Warning, 3)]));
assert!(input.is_incomplete(&[diag(Severity::Warning, 0), diag(Severity::Error, 3)]));
assert!(!input.is_incomplete(&[diag(Severity::Note, 0), diag(Severity::Help, 1)]));
}
#[test]
fn test_is_incomplete_any_early_error_wins() {
let input = Input::new("a b c\n", 0, 1);
let errors = [diag(Severity::Error, 5), diag(Severity::Error, 1)];
assert!(!input.is_incomplete(&errors));
}
#[test]
fn test_is_incomplete_relative_ignores_base() {
let input = Input::new("1 + \n", 100, 2);
assert!(input.is_incomplete_relative(&[diag(Severity::Error, 3)]));
assert!(input.is_incomplete_relative(&[diag(Severity::Error, 7)]));
assert!(!input.is_incomplete_relative(&[diag(Severity::Error, 2)]));
assert!(input.is_incomplete(&[diag(Severity::Error, 103)]));
assert!(!input.is_incomplete(&[diag(Severity::Error, 3)]));
}
#[test]
fn test_is_incomplete_relative_same_rules_as_global() {
let input = Input::new("a b c\n", 40, 3);
assert!(!input.is_incomplete_relative(&[]));
assert!(!input.is_incomplete_relative(&[diag(Severity::Warning, 5)]));
let errors = [diag(Severity::Error, 5), diag(Severity::Error, 1)];
assert!(!input.is_incomplete_relative(&errors));
assert!(input.is_incomplete_relative(&[diag(Severity::Note, 0), diag(Severity::Error, 5)]));
}
#[test]
fn test_both_methods_agree_at_base_zero() {
let input = Input::new("x *\n", 0, 1);
for at in 0..6 {
let errors = [diag(Severity::Error, at)];
assert_eq!(
input.is_incomplete(&errors),
input.is_incomplete_relative(&errors)
);
}
}
}