use std::rc::Rc;
use super::ast::{Annotation, Dashed, Id, Quoted, SourcePos, Token};
use super::hchar::is_alpha;
pub use super::interface::Severity;
use super::interface::Shell;
#[derive(Clone, Debug)]
pub struct ParseError {
pub pos: SourcePos,
pub has_messages: bool,
pub last_message: Option<Rc<str>>,
}
impl ParseError {
pub const fn unknown(pos: SourcePos) -> Self {
Self {
pos,
has_messages: false,
last_message: None,
}
}
pub const fn unexpected(pos: SourcePos) -> Self {
Self {
pos,
has_messages: true,
last_message: None,
}
}
pub fn message(pos: SourcePos, msg: &str) -> Self {
Self {
pos,
has_messages: true,
last_message: (!msg.is_empty()).then(|| Rc::from(msg)),
}
}
pub fn merge(e1: &Self, e2: &Self) -> Self {
if !e2.has_messages && e1.has_messages {
return e1.clone();
}
if !e1.has_messages && e2.has_messages {
return e2.clone();
}
match e1.pos.cmp(&e2.pos) {
std::cmp::Ordering::Equal => Self {
pos: e1.pos,
has_messages: e1.has_messages || e2.has_messages,
last_message: e2.last_message.clone().or_else(|| e1.last_message.clone()),
},
std::cmp::Ordering::Greater => e1.clone(),
std::cmp::Ordering::Less => e2.clone(),
}
}
}
#[derive(Clone, Debug)]
pub struct Fail {
pub consumed: bool,
pub err: ParseError,
}
pub type R<T> = Result<T, Fail>;
pub type Alternative<'a, T> = dyn FnMut(&mut P) -> R<T> + 'a;
#[derive(Clone, Debug, PartialEq, Eq)]
pub struct ParseNote {
pub start: SourcePos,
pub end: SourcePos,
pub severity: Severity,
pub code: i64,
pub msg: String,
}
#[derive(Clone, Debug)]
pub enum Context {
Name(SourcePos, String),
Annotation(Vec<Annotation>),
Source(String),
}
#[derive(Clone, Debug)]
pub struct HereDocPending {
pub id: Id,
pub dashed: Dashed,
pub quoted: Quoted,
pub end_token: String,
pub contexts: Vec<Context>,
}
pub trait SystemInterface {
fn read_file(&self, external_sources: Option<bool>, path: &str) -> Result<String, String>;
fn find_source(
&self,
current_script: &str,
external_sources: Option<bool>,
paths: &[String],
name: &str,
) -> String;
fn get_config(&self, filename: &str) -> Option<(String, String)>;
}
pub struct Environment {
pub system: Rc<dyn SystemInterface>,
pub check_sourced: bool,
pub ignore_rc: bool,
pub current_filename: String,
pub shell_type_override: Option<Shell>,
}
#[derive(Default)]
pub struct UserState {
pub last_id: Option<usize>,
pub positions: Vec<(SourcePos, SourcePos)>,
pub notes: Vec<ParseNote>,
pub here_docs: Vec<(Id, Vec<Token>)>,
pub pending_here_docs: Vec<HereDocPending>,
}
pub struct UserMark {
last_id: Option<usize>,
notes: usize,
here_docs: usize,
pending: Vec<HereDocPending>,
}
#[derive(Default, Clone)]
pub struct SystemState {
pub contexts: Vec<Context>,
pub problems: Vec<ParseNote>,
}
pub struct Mark {
input: Rc<Vec<char>>,
idx: usize,
pos: SourcePos,
pub counter: u64,
err: ParseError,
user: UserMark,
}
pub struct P {
pub input: Rc<Vec<char>>,
pub idx: usize,
pub pos: SourcePos,
pub counter: u64,
pub err: ParseError,
pub user: UserState,
pub sys: SystemState,
pub env: Environment,
pub files: Vec<String>,
}
pub const fn update_pos_char(mut pos: SourcePos, c: char) -> SourcePos {
match c {
'\n' => {
pos.line += 1;
pos.column = 1;
}
'\t' => pos.column = pos.column + 8 - ((pos.column - 1) % 8),
_ => pos.column += 1,
}
pos
}
impl P {
pub fn new(env: Environment, file: &str, contents: &str) -> Self {
let pos = SourcePos {
file: 0,
line: 1,
column: 1,
};
Self {
input: Rc::new(contents.chars().collect()),
idx: 0,
pos,
counter: 0,
err: ParseError::unknown(pos),
user: UserState::default(),
sys: SystemState::default(),
env,
files: vec![file.to_string()],
}
}
pub fn file_index(&mut self, name: &str) -> u32 {
if let Some(i) = self.files.iter().position(|f| f == name) {
return i as u32;
}
self.files.push(name.to_string());
(self.files.len() - 1) as u32
}
pub fn mark(&self) -> Mark {
Mark {
input: self.input.clone(),
idx: self.idx,
pos: self.pos,
counter: self.counter,
err: self.err.clone(),
user: UserMark {
last_id: self.user.last_id,
notes: self.user.notes.len(),
here_docs: self.user.here_docs.len(),
pending: self.user.pending_here_docs.clone(),
},
}
}
pub fn restore(&mut self, m: &Mark) {
self.input = m.input.clone();
self.idx = m.idx;
self.pos = m.pos;
self.counter = m.counter;
self.err = m.err.clone();
self.user.last_id = m.user.last_id;
self.user.notes.truncate(m.user.notes);
self.user.here_docs.truncate(m.user.here_docs);
self.user.pending_here_docs.clone_from(&m.user.pending);
}
pub const fn consumed_since(&self, f: &Fail, m: &Mark) -> bool {
f.consumed || self.counter > m.counter
}
pub fn empty_fail<T>(&self, err: &ParseError) -> R<T> {
Err(Fail {
consumed: false,
err: ParseError::merge(&self.err, err),
})
}
pub fn fail<T>(&self, msg: &str) -> R<T> {
self.empty_fail(&ParseError::message(self.pos, msg))
}
pub fn zero<T>(&self) -> R<T> {
self.empty_fail(&ParseError::unknown(self.pos))
}
pub fn guard(&self, cond: bool) -> R<()> {
if cond { self.ok(()) } else { self.zero() }
}
#[allow(
clippy::unnecessary_wraps,
reason = "Parsec's `return`, a parser that always succeeds: callers use it where an `R` is expected"
)]
pub const fn ok<T>(&self, x: T) -> R<T> {
Ok(x)
}
pub fn peek(&self) -> Option<char> {
self.input.get(self.idx).copied()
}
pub fn at_eof(&self) -> bool {
self.idx >= self.input.len()
}
fn advance(&mut self, c: char) {
self.idx += 1;
self.pos = update_pos_char(self.pos, c);
self.counter += 1;
self.err = ParseError::unknown(self.pos);
}
pub fn satisfy(&mut self, f: impl Fn(char) -> bool) -> R<char> {
match self.peek() {
Some(c) if f(c) => {
self.advance(c);
Ok(c)
}
_ => self.empty_fail(&ParseError::unexpected(self.pos)),
}
}
pub fn char_(&mut self, c: char) -> R<char> {
self.satisfy(|x| x == c)
}
pub fn one_of(&mut self, cs: &str) -> R<char> {
self.satisfy(|x| cs.contains(x))
}
pub fn none_of(&mut self, cs: &str) -> R<char> {
self.satisfy(|x| !cs.contains(x))
}
pub fn any_char(&mut self) -> R<char> {
self.satisfy(|_| true)
}
pub fn digit(&mut self) -> R<char> {
self.satisfy(|c| c.is_ascii_digit())
}
pub fn letter(&mut self) -> R<char> {
self.satisfy(is_alpha)
}
pub fn string(&mut self, s: &str) -> R<String> {
let chars: Vec<char> = s.chars().collect();
if chars.is_empty() {
return Ok(String::new());
}
let start_pos = self.pos;
let err = ParseError::unexpected(start_pos);
match self.peek() {
Some(c) if c == chars[0] => {}
_ => return self.empty_fail(&err),
}
for (i, &want) in chars.iter().enumerate() {
match self.input.get(self.idx + i) {
Some(&got) if got == want => {}
_ => {
self.counter += 1;
return Err(Fail {
consumed: true,
err,
});
}
}
}
for &c in &chars {
self.idx += 1;
self.pos = update_pos_char(self.pos, c);
self.counter += 1;
}
self.err = ParseError::unknown(self.pos);
Ok(s.to_string())
}
pub fn eof(&mut self) -> R<()> {
if self.at_eof() {
self.err = ParseError::merge(&self.err, &ParseError::unknown(self.pos));
Ok(())
} else {
self.empty_fail(&ParseError::unexpected(self.pos))
}
}
pub fn alt<T>(
&mut self,
a: impl FnOnce(&mut Self) -> R<T>,
b: impl FnOnce(&mut Self) -> R<T>,
) -> R<T> {
let m = self.mark();
match a(self) {
Ok(v) => Ok(v),
Err(f) if self.consumed_since(&f, &m) => Err(f),
Err(f) => {
self.restore(&m);
self.err = f.err;
b(self)
}
}
}
pub fn choice<T>(&mut self, ps: &mut [&mut Alternative<'_, T>]) -> R<T> {
let m = self.mark();
for p in ps.iter_mut() {
match p(self) {
Ok(v) => return Ok(v),
Err(f) if self.consumed_since(&f, &m) => return Err(f),
Err(f) => {
self.restore(&m);
self.err = f.err;
}
}
}
self.zero()
}
pub fn try_<T>(&mut self, p: impl FnOnce(&mut Self) -> R<T>) -> R<T> {
let m = self.mark();
match p(self) {
Ok(v) => Ok(v),
Err(f) => {
let consumed = self.consumed_since(&f, &m);
let err = if consumed {
ParseError::merge(&m.err, &f.err)
} else {
f.err
};
self.restore(&m);
Err(Fail {
consumed: false,
err,
})
}
}
}
pub fn look_ahead<T>(&mut self, p: impl FnOnce(&mut Self) -> R<T>) -> R<T> {
let m = self.mark();
let v = p(self)?;
let pos = m.pos;
self.restore(&m);
self.err = ParseError::merge(&m.err, &ParseError::unknown(pos));
Ok(v)
}
pub fn many<T>(&mut self, mut p: impl FnMut(&mut Self) -> R<T>) -> R<Vec<T>> {
let mut out = Vec::new();
loop {
let m = self.mark();
if !out.is_empty() {
self.err = ParseError::unknown(self.pos);
}
match p(self) {
Ok(v) => {
assert!(
self.counter > m.counter,
"ShellCheck parser: 'many' applied to a parser that accepts an empty string"
);
out.push(v);
}
Err(f) if self.consumed_since(&f, &m) => return Err(f),
Err(f) => {
self.restore(&m);
self.err = f.err;
return Ok(out);
}
}
}
}
pub fn many1<T>(&mut self, mut p: impl FnMut(&mut Self) -> R<T>) -> R<Vec<T>> {
let first = p(self)?;
let mut rest = self.many(p)?;
rest.insert(0, first);
Ok(rest)
}
pub fn option<T>(&mut self, x: T, p: impl FnOnce(&mut Self) -> R<T>) -> R<T> {
self.alt(p, |s| s.ok(x))
}
pub fn option_maybe<T>(&mut self, p: impl FnOnce(&mut Self) -> R<T>) -> R<Option<T>> {
self.alt(|s| p(s).map(Some), |s| s.ok(None))
}
pub fn optional<T>(&mut self, p: impl FnOnce(&mut Self) -> R<T>) -> R<()> {
self.alt(|s| p(s).map(|_| ()), |s| s.ok(()))
}
pub fn sep_by1<T, S>(
&mut self,
mut p: impl FnMut(&mut Self) -> R<T>,
mut sep: impl FnMut(&mut Self) -> R<S>,
) -> R<Vec<T>> {
let first = p(self)?;
let mut rest = self.many(|s| {
sep(s)?;
p(s)
})?;
rest.insert(0, first);
Ok(rest)
}
pub fn sep_by<T, S>(
&mut self,
p: impl FnMut(&mut Self) -> R<T>,
sep: impl FnMut(&mut Self) -> R<S>,
) -> R<Vec<T>> {
self.alt(|s| s.sep_by1(p, sep), |s| s.ok(Vec::new()))
}
pub fn chainl1<T, F: FnOnce(T, T) -> T>(
&mut self,
mut p: impl FnMut(&mut Self) -> R<T>,
mut op: impl FnMut(&mut Self) -> R<F>,
) -> R<T> {
let mut x = p(self)?;
loop {
let m = self.mark();
let step = (|| -> R<(F, T)> {
let combine = op(self)?;
let y = p(self)?;
Ok((combine, y))
})();
match step {
Ok((combine, y)) => x = combine(x, y),
Err(fl) if self.consumed_since(&fl, &m) => return Err(fl),
Err(fl) => {
self.restore(&m);
self.err = fl.err;
return Ok(x);
}
}
}
}
pub fn chainr1<T, F: FnOnce(T, T) -> T>(
&mut self,
p: &mut dyn FnMut(&mut Self) -> R<T>,
op: &mut dyn FnMut(&mut Self) -> R<F>,
) -> R<T> {
let x = p(self)?;
let m = self.mark();
let step = (|| -> R<(F, T)> {
let combine = op(self)?;
let y = self.chainr1(p, op)?;
Ok((combine, y))
})();
match step {
Ok((combine, y)) => Ok(combine(x, y)),
Err(fl) if self.consumed_since(&fl, &m) => Err(fl),
Err(fl) => {
self.restore(&m);
self.err = fl.err;
Ok(x)
}
}
}
pub fn next_id_between(&mut self, start: SourcePos, end: SourcePos) -> Id {
let n = self.user.last_id.map_or(0, |n| n + 1);
self.user.last_id = Some(n);
if self.user.positions.len() <= n {
self.user.positions.resize(n + 1, (start, end));
}
self.user.positions[n] = (start, end);
Id(n)
}
pub fn span_for_id(&self, id: Id) -> (SourcePos, SourcePos) {
self.user.positions[id.0]
}
pub fn new_id_for(&mut self, id: Id) -> Id {
let (s, e) = self.span_for_id(id);
self.next_id_between(s, e)
}
pub const fn start_span(&self) -> SourcePos {
self.pos
}
pub fn end_span(&mut self, start: SourcePos) -> Id {
let end = self.pos;
self.next_id_between(start, end)
}
pub fn id_spanning(&mut self, first: Id, last: Id) -> Id {
let (s, _) = self.span_for_id(first);
let (_, e) = self.span_for_id(last);
self.next_id_between(s, e)
}
pub fn id_spanning_list(&mut self, tokens: &[&Token]) -> Id {
if let (Some(first), Some(last)) = (tokens.first(), tokens.last()) {
self.id_spanning(first.id, last.id)
} else {
let pos = self.pos;
self.next_id_between(pos, pos)
}
}
fn should_ignore_code(&self, code: i64) -> bool {
let check_sourced = self.env.check_sourced;
self.sys
.contexts
.iter()
.any(|c| context_disables_code(check_sourced, code, c))
}
pub fn add_parse_note(&mut self, note: ParseNote) {
if !self.should_ignore_code(note.code) {
self.user.notes.push(note);
}
}
pub fn parse_note(&mut self, severity: Severity, code: i64, msg: &str) {
let pos = self.pos;
self.parse_note_at(pos, severity, code, msg);
}
pub fn parse_note_at(&mut self, pos: SourcePos, severity: Severity, code: i64, msg: &str) {
self.add_parse_note(ParseNote {
start: pos,
end: pos,
severity,
code,
msg: msg.to_string(),
});
}
pub fn parse_note_at_id(&mut self, id: Id, severity: Severity, code: i64, msg: &str) {
let (start, end) = self.span_for_id(id);
self.add_parse_note(ParseNote {
start,
end,
severity,
code,
msg: msg.to_string(),
});
}
pub fn parse_problem_at_with_end(
&mut self,
start: SourcePos,
end: SourcePos,
severity: Severity,
code: i64,
msg: &str,
) {
if !self.should_ignore_code(code) {
self.sys.problems.push(ParseNote {
start,
end,
severity,
code,
msg: msg.to_string(),
});
}
}
pub fn parse_problem_at(&mut self, pos: SourcePos, severity: Severity, code: i64, msg: &str) {
self.parse_problem_at_with_end(pos, pos, severity, code, msg);
}
pub fn parse_problem(&mut self, severity: Severity, code: i64, msg: &str) {
let pos = self.pos;
self.parse_problem_at(pos, severity, code, msg);
}
pub fn parse_problem_at_id(&mut self, id: Id, severity: Severity, code: i64, msg: &str) {
let (start, end) = self.span_for_id(id);
self.parse_problem_at_with_end(start, end, severity, code, msg);
}
pub fn with_context<T>(&mut self, entry: Context, p: impl FnOnce(&mut Self) -> R<T>) -> R<T> {
self.sys.contexts.push(entry);
let m = self.mark();
match p(self) {
Ok(v) => {
self.sys.contexts.pop();
Ok(v)
}
Err(f) if self.consumed_since(&f, &m) => Err(f),
Err(f) => {
self.restore(&m);
self.err = f.err;
self.sys.contexts.pop();
self.fail("")
}
}
}
pub fn called<T>(&mut self, name: &str, p: impl FnOnce(&mut Self) -> R<T>) -> R<T> {
let pos = self.pos;
self.with_context(Context::Name(pos, name.to_string()), p)
}
pub fn with_annotations<T>(
&mut self,
anns: Vec<Annotation>,
p: impl FnOnce(&mut Self) -> R<T>,
) -> R<T> {
if anns.is_empty() {
p(self)
} else {
self.with_context(Context::Annotation(anns), p)
}
}
pub fn swap_context<T>(
&mut self,
contexts: Vec<Context>,
p: impl FnOnce(&mut Self) -> R<T>,
) -> R<T> {
let old = std::mem::replace(&mut self.sys.contexts, contexts);
let m = self.mark();
match p(self) {
Ok(v) => {
self.sys.contexts = old;
Ok(v)
}
Err(f) if self.consumed_since(&f, &m) => Err(f),
Err(f) => {
self.restore(&m);
self.err = f.err;
self.sys.contexts = old;
self.fail("")
}
}
}
pub fn sub_parse<T>(
&mut self,
pos: SourcePos,
input: &str,
p: impl FnOnce(&mut Self) -> R<T>,
) -> R<T> {
let last_pos = self.pos;
let last_input = self.input.clone();
let last_idx = self.idx;
self.pos = pos;
self.input = Rc::new(input.chars().collect());
self.idx = 0;
let v = p(self)?;
self.input = last_input;
self.idx = last_idx;
self.pos = last_pos;
Ok(v)
}
}
pub fn context_disables_code(check_sourced: bool, code: i64, item: &Context) -> bool {
match item {
Context::Annotation(list) => list.iter().any(
|a| matches!(a, Annotation::DisableComment(from, to) if code >= *from && code < *to),
),
Context::Source(_) => !check_sourced,
Context::Name(..) => false,
}
}