improve panic reports
This commit is contained in:
parent
273267f61d
commit
ac4bd0fb55
|
@ -10,4 +10,6 @@ fn dec (n as :number) -> base :sub (n, 1)
|
||||||
|
|
||||||
fn sub (x as :number, y as :number) -> base :sub (x, y)
|
fn sub (x as :number, y as :number) -> base :sub (x, y)
|
||||||
|
|
||||||
#{add, dec, sub}
|
fn panics! () -> add ("foo", "bar")
|
||||||
|
|
||||||
|
#{add, dec, sub, panics!}
|
||||||
|
|
54
src/errors.rs
Normal file
54
src/errors.rs
Normal file
|
@ -0,0 +1,54 @@
|
||||||
|
use crate::process::{LErr, Trace};
|
||||||
|
use crate::validator::VErr;
|
||||||
|
use crate::value::Value;
|
||||||
|
use ariadne::{sources, Color, Label, Report, ReportKind};
|
||||||
|
use std::collections::HashSet;
|
||||||
|
|
||||||
|
pub fn report_panic(err: LErr) {
|
||||||
|
let mut srcs = HashSet::new();
|
||||||
|
let mut stack = vec![];
|
||||||
|
let mut order = 1;
|
||||||
|
for entry in err.trace.iter().rev() {
|
||||||
|
let Trace {
|
||||||
|
callee,
|
||||||
|
caller,
|
||||||
|
function,
|
||||||
|
arguments,
|
||||||
|
input,
|
||||||
|
src,
|
||||||
|
} = entry;
|
||||||
|
let (_, first_span) = callee;
|
||||||
|
let (_, second_span) = caller;
|
||||||
|
let Value::Fn(f) = function else {
|
||||||
|
unreachable!()
|
||||||
|
};
|
||||||
|
let fn_name = f.borrow().name.clone();
|
||||||
|
let i = first_span.start;
|
||||||
|
let j = second_span.end;
|
||||||
|
let label = Label::new((entry.input, i..j))
|
||||||
|
.with_message(format!("({order}) calling `{fn_name}` with `{arguments}`"));
|
||||||
|
order += 1;
|
||||||
|
stack.push(label);
|
||||||
|
srcs.insert((*input, *src));
|
||||||
|
}
|
||||||
|
|
||||||
|
Report::build(ReportKind::Error, (err.input, err.span.into_range()))
|
||||||
|
.with_message(format!("Ludus panicked! {}", err.msg))
|
||||||
|
.with_label(Label::new((err.input, err.span.into_range())).with_color(Color::Red))
|
||||||
|
.with_labels(stack)
|
||||||
|
.with_note(err.extra)
|
||||||
|
.finish()
|
||||||
|
.print(sources(srcs.iter().copied()))
|
||||||
|
.unwrap();
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn report_invalidation(errs: Vec<VErr>) {
|
||||||
|
for err in errs {
|
||||||
|
Report::build(ReportKind::Error, (err.input, err.span.into_range()))
|
||||||
|
.with_message(err.msg.to_string())
|
||||||
|
.with_label(Label::new((err.input, err.span.into_range())).with_color(Color::Cyan))
|
||||||
|
.finish()
|
||||||
|
.print(sources(vec![(err.input, err.src)]))
|
||||||
|
.unwrap();
|
||||||
|
}
|
||||||
|
}
|
44
src/main.rs
44
src/main.rs
|
@ -96,15 +96,24 @@ pub fn prelude<'src>() -> (
|
||||||
let prelude_parsed = Box::leak(Box::new(p_ast.unwrap()));
|
let prelude_parsed = Box::leak(Box::new(p_ast.unwrap()));
|
||||||
let base_pkg = base();
|
let base_pkg = base();
|
||||||
|
|
||||||
let base_names = base_pkg.iter().map(|binding| binding.0.clone()).collect();
|
let mut v6or = Validator::new(
|
||||||
|
&prelude_parsed.0,
|
||||||
let mut v6or = Validator::new(&prelude_parsed.0, prelude_parsed.1, &base_names);
|
prelude_parsed.1,
|
||||||
|
"prelude",
|
||||||
|
prelude,
|
||||||
|
&base_pkg,
|
||||||
|
);
|
||||||
v6or.validate();
|
v6or.validate();
|
||||||
|
|
||||||
// dbg!(&v6or);
|
if !v6or.errors.is_empty() {
|
||||||
|
report_invalidation(v6or.errors);
|
||||||
|
panic!("interal Ludus error: invalid prelude")
|
||||||
|
}
|
||||||
|
|
||||||
let mut base_ctx = Process::<'src> {
|
let mut base_ctx = Process::<'src> {
|
||||||
locals: base_pkg,
|
input: "prelude",
|
||||||
|
src: prelude,
|
||||||
|
locals: base_pkg.clone(),
|
||||||
ast: &prelude_parsed.0,
|
ast: &prelude_parsed.0,
|
||||||
span: prelude_parsed.1,
|
span: prelude_parsed.1,
|
||||||
prelude: vec![],
|
prelude: vec![],
|
||||||
|
@ -154,20 +163,17 @@ pub fn run(src: &'static str) {
|
||||||
|
|
||||||
let parsed = parse_result.unwrap();
|
let parsed = parse_result.unwrap();
|
||||||
|
|
||||||
let dummy_prelude = vec![];
|
let (prelude_ctx, mut prelude_fn_info) = prelude();
|
||||||
|
|
||||||
let mut v6or = Validator::new(&parsed.0, parsed.1, &dummy_prelude);
|
let mut v6or = Validator::new(&parsed.0, parsed.1, "script", src, &prelude_ctx);
|
||||||
|
|
||||||
v6or.validate();
|
v6or.validate();
|
||||||
|
|
||||||
// dbg!(&v6or);
|
|
||||||
// dbg!(&v6or.fn_info);
|
|
||||||
|
|
||||||
let (prelude_ctx, mut prelude_fn_info) = prelude();
|
|
||||||
|
|
||||||
prelude_fn_info.extend(&mut v6or.fn_info.into_iter());
|
prelude_fn_info.extend(&mut v6or.fn_info.into_iter());
|
||||||
|
|
||||||
let mut proc = Process {
|
let mut proc = Process {
|
||||||
|
input: "script",
|
||||||
|
src,
|
||||||
locals: vec![],
|
locals: vec![],
|
||||||
prelude: prelude_ctx,
|
prelude: prelude_ctx,
|
||||||
ast: &parsed.0,
|
ast: &parsed.0,
|
||||||
|
@ -179,22 +185,14 @@ pub fn run(src: &'static str) {
|
||||||
|
|
||||||
match result {
|
match result {
|
||||||
Ok(result) => println!("{}", result),
|
Ok(result) => println!("{}", result),
|
||||||
Err(err) => report_panic(err, src),
|
Err(err) => report_panic(err),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn main() {
|
pub fn main() {
|
||||||
let src = "
|
let src = "
|
||||||
fn bar
|
fn foo () -> panics! ()
|
||||||
fn foo () -> {:foo}
|
foo ()
|
||||||
|
|
||||||
let quux = foo
|
|
||||||
let fuzz = :asdf
|
|
||||||
|
|
||||||
fn baz (...) -> bar ()
|
|
||||||
|
|
||||||
fn bar () -> quux ()
|
|
||||||
baz (1, 2, 3)
|
|
||||||
";
|
";
|
||||||
run(src);
|
run(src);
|
||||||
// struct_scalpel::print_dissection_info::<value::Value>()
|
// struct_scalpel::print_dissection_info::<value::Value>()
|
||||||
|
|
|
@ -275,7 +275,7 @@ impl fmt::Display for Ast {
|
||||||
StringPattern(s) => write!(f, "{}", s),
|
StringPattern(s) => write!(f, "{}", s),
|
||||||
KeywordPattern(k) => write!(f, ":{}", k),
|
KeywordPattern(k) => write!(f, ":{}", k),
|
||||||
WordPattern(w) => write!(f, "{}", w),
|
WordPattern(w) => write!(f, "{}", w),
|
||||||
AsPattern(w, t) => write!(f, "{} as {}", w, t),
|
AsPattern(w, t) => write!(f, "{} as :{}", w, t),
|
||||||
Splattern(p) => write!(f, "...{}", p.0),
|
Splattern(p) => write!(f, "...{}", p.0),
|
||||||
PlaceholderPattern => write!(f, "_"),
|
PlaceholderPattern => write!(f, "_"),
|
||||||
TuplePattern(t) => write!(
|
TuplePattern(t) => write!(
|
||||||
|
|
103
src/process.rs
103
src/process.rs
|
@ -9,19 +9,40 @@ use imbl::Vector;
|
||||||
use std::cell::RefCell;
|
use std::cell::RefCell;
|
||||||
use std::rc::Rc;
|
use std::rc::Rc;
|
||||||
|
|
||||||
#[derive(Debug)]
|
#[derive(Debug, Clone, PartialEq)]
|
||||||
pub struct LErr<'src> {
|
pub struct LErr<'src> {
|
||||||
|
pub input: &'static str,
|
||||||
|
pub src: &'static str,
|
||||||
pub msg: String,
|
pub msg: String,
|
||||||
pub span: SimpleSpan,
|
pub span: SimpleSpan,
|
||||||
pub trace: Vec<(Spanned<Ast>, Spanned<Ast>, Value<'src>, Value<'src>)>,
|
pub trace: Vec<Trace<'src>>,
|
||||||
|
pub extra: String,
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Debug, Clone, PartialEq)]
|
||||||
|
pub struct Trace<'src> {
|
||||||
|
pub callee: Spanned<Ast>,
|
||||||
|
pub caller: Spanned<Ast>,
|
||||||
|
pub function: Value<'src>,
|
||||||
|
pub arguments: Value<'src>,
|
||||||
|
pub input: &'static str,
|
||||||
|
pub src: &'static str,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl<'src> LErr<'src> {
|
impl<'src> LErr<'src> {
|
||||||
pub fn new(msg: String, span: SimpleSpan) -> LErr<'src> {
|
pub fn new(
|
||||||
|
msg: String,
|
||||||
|
span: SimpleSpan,
|
||||||
|
input: &'static str,
|
||||||
|
src: &'static str,
|
||||||
|
) -> LErr<'src> {
|
||||||
LErr {
|
LErr {
|
||||||
msg,
|
msg,
|
||||||
span,
|
span,
|
||||||
|
input,
|
||||||
|
src,
|
||||||
trace: vec![],
|
trace: vec![],
|
||||||
|
extra: "".to_string(),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -30,6 +51,8 @@ type LResult<'src> = Result<Value<'src>, LErr<'src>>;
|
||||||
|
|
||||||
#[derive(Debug)]
|
#[derive(Debug)]
|
||||||
pub struct Process<'src> {
|
pub struct Process<'src> {
|
||||||
|
pub input: &'static str,
|
||||||
|
pub src: &'static str,
|
||||||
pub locals: Vec<(String, Value<'src>)>,
|
pub locals: Vec<(String, Value<'src>)>,
|
||||||
pub prelude: Vec<(String, Value<'src>)>,
|
pub prelude: Vec<(String, Value<'src>)>,
|
||||||
pub ast: &'src Ast,
|
pub ast: &'src Ast,
|
||||||
|
@ -47,14 +70,19 @@ impl<'src> Process<'src> {
|
||||||
let resolved_prelude = self.prelude.iter().rev().find(|(name, _)| word == name);
|
let resolved_prelude = self.prelude.iter().rev().find(|(name, _)| word == name);
|
||||||
match resolved_prelude {
|
match resolved_prelude {
|
||||||
Some((_, value)) => Ok(value.clone()),
|
Some((_, value)) => Ok(value.clone()),
|
||||||
None => Err(LErr::new(format!("unbound name `{word}`"), self.span)),
|
None => Err(LErr::new(
|
||||||
|
format!("unbound name `{word}`"),
|
||||||
|
self.span,
|
||||||
|
self.input,
|
||||||
|
self.src,
|
||||||
|
)),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn panic(&self, msg: String) -> LResult<'src> {
|
pub fn panic(&self, msg: String) -> LResult<'src> {
|
||||||
Err(LErr::new(msg, self.span))
|
Err(LErr::new(msg, self.span, self.input, self.src))
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn bind(&mut self, word: String, value: &Value<'src>) {
|
pub fn bind(&mut self, word: String, value: &Value<'src>) {
|
||||||
|
@ -207,8 +235,8 @@ impl<'src> Process<'src> {
|
||||||
{
|
{
|
||||||
let root = self.ast;
|
let root = self.ast;
|
||||||
let to = self.locals.len();
|
let to = self.locals.len();
|
||||||
let mut clauses = clauses.iter();
|
let mut clauses_iter = clauses.iter();
|
||||||
while let Some((Ast::MatchClause(patt, guard, body), _)) = clauses.next() {
|
while let Some((Ast::MatchClause(patt, guard, body), _)) = clauses_iter.next() {
|
||||||
if self.match_pattern(&patt.0, value).is_some() {
|
if self.match_pattern(&patt.0, value).is_some() {
|
||||||
let pass_guard = match guard.as_ref() {
|
let pass_guard = match guard.as_ref() {
|
||||||
None => true,
|
None => true,
|
||||||
|
@ -224,7 +252,26 @@ impl<'src> Process<'src> {
|
||||||
return result;
|
return result;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
self.panic("no match".to_string())
|
let patterns = clauses
|
||||||
|
.iter()
|
||||||
|
.map(|clause| {
|
||||||
|
let (Ast::MatchClause(patt, ..), _) = clause else {
|
||||||
|
unreachable!("internal Ludus error")
|
||||||
|
};
|
||||||
|
let patt = &patt.as_ref().0;
|
||||||
|
patt.to_string()
|
||||||
|
})
|
||||||
|
.collect::<Vec<_>>()
|
||||||
|
.join("\n");
|
||||||
|
dbg!(&patterns);
|
||||||
|
Err(LErr {
|
||||||
|
input: self.input,
|
||||||
|
src: self.src,
|
||||||
|
msg: "no match".to_string(),
|
||||||
|
span: self.span,
|
||||||
|
trace: vec![],
|
||||||
|
extra: format!("expected {value} to match one of\n{}", patterns),
|
||||||
|
})
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -252,7 +299,7 @@ impl<'src> Process<'src> {
|
||||||
let mut f = f.borrow_mut();
|
let mut f = f.borrow_mut();
|
||||||
for i in 0..f.enclosing.len() {
|
for i in 0..f.enclosing.len() {
|
||||||
let (name, value) = f.enclosing[i].clone();
|
let (name, value) = f.enclosing[i].clone();
|
||||||
if matches!(value, Value::FnDecl(_)) {
|
if !f.has_run && matches!(value, Value::FnDecl(_)) {
|
||||||
let defined = self.resolve(&name);
|
let defined = self.resolve(&name);
|
||||||
match defined {
|
match defined {
|
||||||
Ok(Value::Fn(defined)) => f.enclosing[i] = (name.clone(), Fn(defined)),
|
Ok(Value::Fn(defined)) => f.enclosing[i] = (name.clone(), Fn(defined)),
|
||||||
|
@ -265,20 +312,23 @@ impl<'src> Process<'src> {
|
||||||
_ => unreachable!("internal Ludus error"),
|
_ => unreachable!("internal Ludus error"),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
self.locals.push(f.enclosing[i].clone());
|
||||||
}
|
}
|
||||||
let mut enclosing = f.enclosing.clone();
|
f.has_run = true;
|
||||||
self.locals.append(&mut enclosing);
|
let input = self.input;
|
||||||
|
let src = self.src;
|
||||||
|
self.input = f.input;
|
||||||
|
self.src = f.src;
|
||||||
let result = self.match_clauses(&args, f.body);
|
let result = self.match_clauses(&args, f.body);
|
||||||
self.locals.truncate(to);
|
self.locals.truncate(to);
|
||||||
|
self.input = input;
|
||||||
|
self.src = src;
|
||||||
result
|
result
|
||||||
}
|
}
|
||||||
// TODO: partially applied functions shnould work! In #15
|
// TODO: partially applied functions shnould work! In #15
|
||||||
(Fn(_f), Args(_args)) => todo!(),
|
(Fn(_f), Args(_args)) => todo!(),
|
||||||
(_, Keyword(_)) => Ok(Nil),
|
(_, Keyword(_)) => Ok(Nil),
|
||||||
(_, Args(_)) => Err(LErr::new(
|
(_, Args(_)) => self.panic("only functions and keywords may be called".to_string()),
|
||||||
"you may only call a function".to_string(),
|
|
||||||
self.span,
|
|
||||||
)),
|
|
||||||
(Base(f), Tuple(args)) => match f {
|
(Base(f), Tuple(args)) => match f {
|
||||||
BaseFn::Nullary(f) => {
|
BaseFn::Nullary(f) => {
|
||||||
let num_args = args.len();
|
let num_args = args.len();
|
||||||
|
@ -440,8 +490,14 @@ impl<'src> Process<'src> {
|
||||||
let first_val = self.visit(first)?;
|
let first_val = self.visit(first)?;
|
||||||
let mut result = self.apply(root_val.clone(), first_val.clone());
|
let mut result = self.apply(root_val.clone(), first_val.clone());
|
||||||
if let Err(mut err) = result {
|
if let Err(mut err) = result {
|
||||||
err.trace
|
err.trace.push(Trace {
|
||||||
.push((*root.clone(), *first.clone(), root_val, first_val));
|
callee: *root.clone(),
|
||||||
|
caller: *first.clone(),
|
||||||
|
function: root_val,
|
||||||
|
arguments: first_val,
|
||||||
|
input: self.input,
|
||||||
|
src: self.src,
|
||||||
|
});
|
||||||
return Err(err);
|
return Err(err);
|
||||||
};
|
};
|
||||||
let mut prev_node;
|
let mut prev_node;
|
||||||
|
@ -454,8 +510,14 @@ impl<'src> Process<'src> {
|
||||||
result = self.apply(callee.clone(), caller.clone());
|
result = self.apply(callee.clone(), caller.clone());
|
||||||
|
|
||||||
if let Err(mut err) = result {
|
if let Err(mut err) = result {
|
||||||
err.trace
|
err.trace.push(Trace {
|
||||||
.push((prev_node.clone(), this_node.clone(), caller, callee));
|
callee: prev_node.clone(),
|
||||||
|
caller: this_node.clone(),
|
||||||
|
function: caller,
|
||||||
|
arguments: callee,
|
||||||
|
input: self.input,
|
||||||
|
src: self.src,
|
||||||
|
});
|
||||||
return Err(err);
|
return Err(err);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -493,6 +555,9 @@ impl<'src> Process<'src> {
|
||||||
body: clauses,
|
body: clauses,
|
||||||
doc,
|
doc,
|
||||||
enclosing,
|
enclosing,
|
||||||
|
has_run: false,
|
||||||
|
input: self.input,
|
||||||
|
src: self.src,
|
||||||
})));
|
})));
|
||||||
|
|
||||||
let maybe_decl_i = self.locals.iter().position(|(binding, _)| binding == name);
|
let maybe_decl_i = self.locals.iter().position(|(binding, _)| binding == name);
|
||||||
|
|
|
@ -1,16 +1,24 @@
|
||||||
use crate::parser::*;
|
use crate::parser::*;
|
||||||
use crate::spans::Span;
|
use crate::spans::Span;
|
||||||
|
use crate::value::Value;
|
||||||
use std::collections::{HashMap, HashSet};
|
use std::collections::{HashMap, HashSet};
|
||||||
|
|
||||||
#[derive(Clone, Debug, PartialEq)]
|
#[derive(Clone, Debug, PartialEq)]
|
||||||
pub struct VErr {
|
pub struct VErr {
|
||||||
msg: String,
|
pub msg: String,
|
||||||
span: Span,
|
pub span: Span,
|
||||||
|
pub input: &'static str,
|
||||||
|
pub src: &'static str,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl VErr {
|
impl VErr {
|
||||||
pub fn new(msg: String, span: Span) -> VErr {
|
pub fn new(msg: String, span: Span, input: &'static str, src: &'static str) -> VErr {
|
||||||
VErr { msg, span }
|
VErr {
|
||||||
|
msg,
|
||||||
|
span,
|
||||||
|
input,
|
||||||
|
src,
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -45,10 +53,11 @@ fn match_arities(arities: &HashSet<Arity>, num_args: u8) -> bool {
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Debug, PartialEq)]
|
#[derive(Debug, PartialEq)]
|
||||||
pub struct Validator<'a> {
|
pub struct Validator<'a, 'src> {
|
||||||
// TODO: add another term here: FnStatus. See Issue #18.
|
|
||||||
pub locals: Vec<(String, Span, FnInfo)>,
|
pub locals: Vec<(String, Span, FnInfo)>,
|
||||||
pub prelude: &'a Vec<String>,
|
pub prelude: &'a Vec<(String, Value<'src>)>,
|
||||||
|
pub input: &'static str,
|
||||||
|
pub src: &'static str,
|
||||||
pub ast: &'a Ast,
|
pub ast: &'a Ast,
|
||||||
pub span: Span,
|
pub span: Span,
|
||||||
pub errors: Vec<VErr>,
|
pub errors: Vec<VErr>,
|
||||||
|
@ -56,9 +65,17 @@ pub struct Validator<'a> {
|
||||||
status: VStatus,
|
status: VStatus,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl<'a> Validator<'a> {
|
impl<'a, 'src: 'a> Validator<'a, 'src> {
|
||||||
pub fn new(ast: &'a Ast, span: Span, prelude: &'a Vec<String>) -> Validator<'a> {
|
pub fn new(
|
||||||
|
ast: &'a Ast,
|
||||||
|
span: Span,
|
||||||
|
input: &'static str,
|
||||||
|
src: &'static str,
|
||||||
|
prelude: &'a Vec<(String, Value<'src>)>,
|
||||||
|
) -> Validator<'a, 'src> {
|
||||||
Validator {
|
Validator {
|
||||||
|
input,
|
||||||
|
src,
|
||||||
locals: vec![],
|
locals: vec![],
|
||||||
prelude,
|
prelude,
|
||||||
ast,
|
ast,
|
||||||
|
@ -92,7 +109,7 @@ impl<'a> Validator<'a> {
|
||||||
|
|
||||||
fn resolved(&self, name: &str) -> bool {
|
fn resolved(&self, name: &str) -> bool {
|
||||||
self.locals.iter().any(|(bound, ..)| name == bound.as_str())
|
self.locals.iter().any(|(bound, ..)| name == bound.as_str())
|
||||||
|| self.prelude.iter().any(|bound| name == bound.as_str())
|
|| self.prelude.iter().any(|(bound, _)| name == bound.as_str())
|
||||||
}
|
}
|
||||||
|
|
||||||
fn bound(&self, name: &str) -> Option<&(String, Span, FnInfo)> {
|
fn bound(&self, name: &str) -> Option<&(String, Span, FnInfo)> {
|
||||||
|
@ -103,7 +120,8 @@ impl<'a> Validator<'a> {
|
||||||
}
|
}
|
||||||
|
|
||||||
fn err(&mut self, msg: String) {
|
fn err(&mut self, msg: String) {
|
||||||
self.errors.push(VErr::new(msg, self.span))
|
self.errors
|
||||||
|
.push(VErr::new(msg, self.span, self.input, self.src))
|
||||||
}
|
}
|
||||||
|
|
||||||
fn use_name(&mut self, name: String) {
|
fn use_name(&mut self, name: String) {
|
||||||
|
|
|
@ -13,6 +13,9 @@ pub struct Fn<'src> {
|
||||||
pub body: &'src Vec<Spanned<Ast>>,
|
pub body: &'src Vec<Spanned<Ast>>,
|
||||||
pub doc: Option<String>,
|
pub doc: Option<String>,
|
||||||
pub enclosing: Vec<(String, Value<'src>)>,
|
pub enclosing: Vec<(String, Value<'src>)>,
|
||||||
|
pub has_run: bool,
|
||||||
|
pub input: &'static str,
|
||||||
|
pub src: &'static str,
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Debug, Dissectible)]
|
#[derive(Debug, Dissectible)]
|
||||||
|
|
Loading…
Reference in New Issue
Block a user