use crate::alias::Alias;
use std::cell::RefCell;
use std::num::NonZeroU64;
use std::ops::Range;
use std::rc::Rc;
#[derive(Clone, Debug, Eq, PartialEq)]
#[non_exhaustive]
pub enum Source {
Unknown,
Stdin,
CommandString,
CommandFile { path: String },
Alias {
original: Location,
alias: Rc<Alias>,
},
CommandSubst { original: Location },
Arith { original: Location },
Eval { original: Location },
DotScript {
name: String,
origin: Location,
},
Trap {
condition: String,
origin: Location,
},
VariableValue {
name: String,
},
InitFile { path: String },
Other {
label: String,
},
}
impl Source {
pub fn is_alias_for(&self, name: &str) -> bool {
if let Source::Alias { original, alias } = self {
alias.name == name || original.code.source.is_alias_for(name)
} else {
false
}
}
pub fn label(&self) -> &str {
use Source::*;
match self {
Unknown => "<?>",
Stdin => "<stdin>",
CommandString => "<command_string>",
CommandFile { path } => path,
Alias { .. } => "<alias>",
CommandSubst { .. } => "<command_substitution>",
Arith { .. } => "<arithmetic_expansion>",
Eval { .. } => "<eval>",
DotScript { name, .. } => name,
Trap { condition, .. } => condition,
VariableValue { name } => name,
InitFile { path } => path,
Other { label } => label,
}
}
}
#[derive(Clone, Debug, Eq, PartialEq)]
pub struct Code {
pub value: RefCell<String>,
pub start_line_number: NonZeroU64,
pub source: Rc<Source>,
}
impl Code {
#[must_use]
pub fn line_number(&self, char_index: usize) -> NonZeroU64 {
let newlines = self
.value
.borrow()
.chars()
.take(char_index)
.filter(|c| *c == '\n')
.count()
.try_into()
.unwrap_or(u64::MAX);
self.start_line_number.saturating_add(newlines)
}
}
#[derive(Clone, Debug, Eq, PartialEq)]
pub struct Location {
pub code: Rc<Code>,
pub range: Range<usize>,
}
impl Location {
#[inline]
pub fn dummy<S: Into<String>>(value: S) -> Location {
fn with_line(value: String) -> Location {
let range = 0..value.chars().count();
let code = Rc::new(Code {
value: RefCell::new(value),
start_line_number: NonZeroU64::new(1).unwrap(),
source: Rc::new(Source::Unknown),
});
Location { code, range }
}
with_line(value.into())
}
pub fn byte_range(&self) -> Range<usize> {
let s = self.code.value.borrow();
let mut chars = s.char_indices();
let start = chars
.nth(self.range.start)
.map(|(i, _)| i)
.unwrap_or(s.len());
let end = if self.range.is_empty() {
start
} else {
chars
.nth(self.range.end - self.range.start - 1)
.map(|(i, _)| i)
.unwrap_or(s.len())
};
start..end
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn line_number() {
let code = Code {
value: RefCell::new("a\nbc\nd".to_string()),
start_line_number: NonZeroU64::new(1).unwrap(),
source: Rc::new(Source::Unknown),
};
assert_eq!(code.line_number(0).get(), 1);
assert_eq!(code.line_number(1).get(), 1);
assert_eq!(code.line_number(2).get(), 2);
assert_eq!(code.line_number(3).get(), 2);
assert_eq!(code.line_number(4).get(), 2);
assert_eq!(code.line_number(5).get(), 3);
assert_eq!(code.line_number(6).get(), 3);
assert_eq!(code.line_number(7).get(), 3);
assert_eq!(code.line_number(usize::MAX).get(), 3);
let code = Code {
start_line_number: NonZeroU64::new(3).unwrap(),
..code
};
assert_eq!(code.line_number(0).get(), 3);
assert_eq!(code.line_number(1).get(), 3);
assert_eq!(code.line_number(2).get(), 4);
assert_eq!(code.line_number(3).get(), 4);
assert_eq!(code.line_number(4).get(), 4);
assert_eq!(code.line_number(5).get(), 5);
assert_eq!(code.line_number(6).get(), 5);
assert_eq!(code.line_number(7).get(), 5);
assert_eq!(code.line_number(usize::MAX).get(), 5);
}
#[test]
fn byte_range() {
let code = Rc::new(Code {
value: RefCell::new("aℝ🦀bc".to_string()),
start_line_number: NonZeroU64::new(1).unwrap(),
source: Rc::new(Source::Unknown),
});
let location = Location {
code: Rc::clone(&code),
range: 0..1, };
assert_eq!(location.byte_range(), 0..1);
let location = Location {
code: Rc::clone(&code),
range: 1..2, };
assert_eq!(location.byte_range(), 1..4);
let location = Location {
code: Rc::clone(&code),
range: 2..3, };
assert_eq!(location.byte_range(), 4..8);
let location = Location {
code: Rc::clone(&code),
range: 1..4, };
assert_eq!(location.byte_range(), 1..9);
let location = Location {
code: Rc::clone(&code),
range: 2..2, };
assert_eq!(location.byte_range(), 4..4);
let location = Location {
code: Rc::clone(&code),
range: 4..5, };
assert_eq!(location.byte_range(), 9..10);
let location = Location {
code: Rc::clone(&code),
range: 5..6, };
assert_eq!(location.byte_range(), 10..10);
let location = Location {
code: Rc::clone(&code),
range: 0..5, };
assert_eq!(location.byte_range(), 0..10);
}
}
pub mod pretty;