use std::path::Path;
use crate::failure::{Doing, Failure};
use crate::rig::field;
use crate::shell::Shell;
use bash_strings::parse_array;
use super::{Frame, Site, Source, Stack};
pub struct Args<'a> {
pub argc: &'a str,
pub argv: &'a str,
}
pub struct Columns<'a> {
pub skip: usize,
pub pwd: &'a str,
pub funcs: &'a str,
pub sources: &'a str,
pub lines: &'a str,
pub args: Option<Args<'a>>,
}
impl<'a> Columns<'a> {
pub fn of(words: &'a [String]) -> Result<Self, Failure> {
let at = |key: &str| field(words, key).ok_or_else(|| broken(format!("no {key:?} section")));
let skip = at("skip")?;
Ok(Self {
skip: skip
.parse()
.map_err(|_| broken(format!("skip {skip:?} is not a count")))?,
pwd: at("pwd")?,
funcs: at("funcs")?,
sources: at("sources")?,
lines: at("lines")?,
args: match (
field(words, "argc"),
field(words, "argv"),
) {
(Some(argc), Some(argv)) => Some(Args { argc, argv }),
(None, None) => None,
_ => {
return Err(broken(
"one of \"argc\"/\"argv\" without the other",
));
}
},
})
}
pub fn frames(&self, shell: &Shell) -> Result<Stack, Failure> {
let column = |name: &str, text: &str| parse_array(text).doing(|| format!("reading the {name:?} column"));
let funcs = column("funcs", self.funcs)?;
let sources = column("sources", self.sources)?;
let lines = column("lines", self.lines)?;
if funcs.len() != sources.len() || funcs.len() != lines.len() {
return Err(broken(format!(
"columns of {} funcs, {} sources and {} lines",
funcs.len(),
sources.len(),
lines.len()
)));
}
if self.skip < 1 || self.skip > funcs.len() {
return Err(broken(format!(
"skip {} of {} frames",
self.skip,
funcs.len()
)));
}
let numbered = |what: &str, text: &str| {
text.parse::<u32>()
.map_err(|_| broken(format!("{what} {text:?}")))
};
let entered_at = numbered("entry line", &lines[funcs.len() - 1])?;
let arguments = match &self.args {
Some(args) => arguments(args, funcs.len())?,
None => None,
};
let pwd = Path::new(self.pwd);
let mut frames: Vec<Frame> = (self.skip..funcs.len())
.map(|at| {
Ok(Frame {
site: Site::of(&funcs[at]),
source: Source::of(&sources[at], pwd, shell),
lineno: numbered("line number", &lines[at - 1])?,
args: arguments.as_ref().map(|groups| groups[at].clone()),
})
})
.collect::<Result<_, Failure>>()?;
if entered_at != 0 {
frames.push(Frame {
site: Site::Shell,
source: Source::Shell,
lineno: entered_at,
args: None,
});
}
Stack::of(frames).ok_or_else(|| broken("a walk with no frames"))
}
}
fn arguments(args: &Args<'_>, frames: usize) -> Result<Option<Vec<Vec<String>>>, Failure> {
let widths = parse_array(args.argc).doing(|| "reading the \"argc\" column".to_string())?;
if widths.len() != frames {
return Ok(None);
}
let flat = parse_array(args.argv).doing(|| "reading the \"argv\" column".to_string())?;
let mut groups = Vec::with_capacity(frames);
let mut from = 0usize;
for width in &widths {
let width: usize = width
.parse()
.map_err(|_| broken(format!("argument count {width:?}")))?;
let upto = from
.checked_add(width)
.filter(|&upto| upto <= flat.len())
.ok_or_else(|| {
broken(format!(
"a group of {width} past {} arguments",
flat.len()
))
})?;
groups.push(flat[from..upto].iter().rev().cloned().collect());
from = upto;
}
if from != flat.len() {
return Err(broken(format!(
"{} arguments belong to no frame",
flat.len() - from
)));
}
Ok(Some(groups))
}
fn broken(what: impl Into<String>) -> Failure {
Failure::new("reading a call stack", what.into())
}