diff --git a/Cargo.toml b/Cargo.toml index a426954..a436f99 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -1,2 +1,2 @@ [workspace] -members = ["parser", "computations", "repl"] +members = ["parser", "computations", "repl", "utils"] diff --git a/computations/Cargo.toml b/computations/Cargo.toml index ef49a4b..03e7cd6 100644 --- a/computations/Cargo.toml +++ b/computations/Cargo.toml @@ -4,4 +4,6 @@ version = "0.1.0" edition = "2021" [dependencies] -parser = { path = "../parser" } \ No newline at end of file +parser = { path = "../parser" } +thiserror = "1.0.30" +utils = { path = "../utils" } diff --git a/computations/src/lib.rs b/computations/src/lib.rs index 2e901d4..0a31cf1 100644 --- a/computations/src/lib.rs +++ b/computations/src/lib.rs @@ -3,8 +3,24 @@ mod trees; use parser::Sym; use trees::{ComputationTree, Literal, UnOp}; +use utils::{Position, Positioned, PositionedExt}; use crate::trees::{BinOp, Combinator}; +use thiserror::Error; + +#[derive(Debug, Error)] +pub enum ComputationError { + #[error("Expected an operator")] + ExpectedOperator, + #[error("Expression is empty")] + Empty, + #[error("Expression could not fully be computed")] + SymbolsRemaining, + #[error("Unsupported feature: {0}")] + UnsupportedFeature(&'static str), +} + +pub type Error = Positioned; // States for our state machine // WaitForOp -> We are waiting for an operator as the next symbol @@ -27,12 +43,10 @@ struct Accumulator { impl Accumulator { // Build a new accumulator containing only the symbol "s" viewed // as a computation tree - fn new(s: &Sym) -> Result { - Self::to_tree(s).and_then(|acc| { - Ok(Accumulator { - state: State::WaitForOp, - acc, - }) + fn new(s: Positioned<&Sym>) -> Result { + Self::to_tree(s).map(|acc| Accumulator { + state: State::WaitForOp, + acc, }) } @@ -86,8 +100,8 @@ impl Accumulator { } /// TODO : captures in lambdas ?? - fn count_variables(prog: &Vec) -> u32 { - prog.iter() + fn count_variables<'a, I: IntoIterator>(prog: I) -> u32 { + prog.into_iter() .map(|s| match s { Sym::Var(_) => 1, _ => 0, @@ -96,91 +110,104 @@ impl Accumulator { } /// Convert an arbitrary symbol to a computation tree - fn to_tree<'a>(s: &Sym) -> Result { - match s { + fn to_tree<'a>(s: Positioned<&Sym>) -> Result { + match s.value { Sym::CombS | Sym::CombK | Sym::CombD | Sym::CombI => { - Ok(ComputationTree::CombOp(Self::to_comb(s))) + Ok(ComputationTree::CombOp(Self::to_comb(s.value))) } Sym::Map | Sym::Eq | Sym::Add | Sym::And | Sym::Or | Sym::Filter | Sym::Reduce => { - Ok(ComputationTree::BinOpSym(Self::to_binary(s))) + Ok(ComputationTree::BinOpSym(Self::to_binary(s.value))) + } + Sym::Iota | Sym::Len | Sym::Neg => { + Ok(ComputationTree::UnOpSym(Self::to_unary(s.value))) } - Sym::Iota | Sym::Len | Sym::Neg => Ok(ComputationTree::UnOpSym(Self::to_unary(s))), Sym::Literal(n) => Ok(ComputationTree::Lit(n.clone().into())), Sym::Var(v) => Ok(ComputationTree::Lit(Literal::Var(v.clone()))), - Sym::Lambda(prog) => non_linear_check(prog).and_then(|body| { - Ok(ComputationTree::Lambda { - vars: Self::count_variables(prog), - body: Box::new(body), - }) + Sym::Lambda(prog) => non_linear_check( + prog.iter().map(|s| &s.value).positioned( + prog.iter() + .map(|s| s.pos.clone()) + .fold(Position::default(), Position::merge), + ), + ) + .map(|body| ComputationTree::Lambda { + vars: Self::count_variables(prog.iter().map(|s| &s.value)), + body: Box::new(body), }), } } /// Given an accumulator and a symbol, next computes a new accumulator /// and consume the symbol to extend the current computation tree. - fn next(self, s: &Sym) -> Result { + fn next(self, s: Positioned<&Sym>) -> Result { match self.state { - State::WaitForOp => match s { + State::WaitForOp => match s.value { Sym::Map | Sym::Eq | Sym::Filter | Sym::Reduce | Sym::Add | Sym::And | Sym::Or => { Ok(Accumulator { - state: State::WaitForVal(Self::to_binary(s)), + state: State::WaitForVal(Self::to_binary(s.value)), ..self }) } Sym::Iota | Sym::Len => Ok(Accumulator { state: State::WaitForOp, acc: ComputationTree::UnaryOp { - op: Self::to_unary(s), + op: Self::to_unary(s.value), lhs: Box::new(self.acc), }, }), - _ => Err("Expected operator"), + _ => Err(ComputationError::ExpectedOperator.positioned(s.pos)), }, - State::WaitForVal(op) => Self::to_tree(s).and_then(|tree| { - Ok(Accumulator { - state: State::WaitForOp, - acc: ComputationTree::BinaryOp { - op, - lhs: Box::new(tree), - rhs: Box::new(self.acc), - }, - }) + State::WaitForVal(op) => Self::to_tree(s).map(|tree| Accumulator { + state: State::WaitForOp, + acc: ComputationTree::BinaryOp { + op, + lhs: Box::new(tree), + rhs: Box::new(self.acc), + }, }), } } } // Check that a sequence of symbols is well formed -fn linear_check(prog: &[Sym]) -> Result { - let first = Accumulator::new(&prog[0])?; - let next = &prog[1..]; - next.iter() - .try_fold(first, |acc, s| acc.next(s)) - .and_then(|a| { - // println!("debug {:?}", a); - a.finish().ok_or("Symbols remaining") - }) +fn linear_check<'a, I: IntoIterator>>( + prog: Positioned, +) -> Result { + let mut it = prog.value.into_iter(); + let acc = Accumulator::new( + it.next() + .ok_or(ComputationError::Empty.positioned(prog.pos.clone()))?, + )?; + let a = it.try_fold(acc, |acc, s| acc.next(s))?; + a.finish() + .ok_or(ComputationError::SymbolsRemaining.positioned(prog.pos)) } // Check that a sequence of symbols is well formed (in the context of a lambda) -fn non_linear_check(_prog: &[Sym]) -> Result { - Err("TODO: Lambdas not supported") +fn non_linear_check<'a, I: IntoIterator>( + prog: Positioned, +) -> Result { + Err(ComputationError::UnsupportedFeature("non_linear_check").positioned(prog.pos)) } /// Check that an ULP program is well formed and returns its associated /// computation tree -pub fn check(mut prog: Vec) -> Result { - if prog.len() == 0 { - Err("No symbols") +pub fn check(prog: Vec>) -> Result { + let pos = prog + .iter() + .map(|s| s.pos.clone()) + .fold(Position::default(), Position::merge); + if prog.is_empty() { + Err(ComputationError::Empty.positioned(pos)) } else { - prog.reverse(); - linear_check(&prog) + linear_check(prog.iter().map(|s| s.as_ref()).positioned(pos)) } } #[cfg(test)] mod test { use parser::*; + use utils::PositionedExt; use crate::check; @@ -194,7 +221,12 @@ mod test { Sym::Iota, Sym::Literal(Lit::Num("2".to_string())), ]; - let res = check(prog); + let res = check( + prog.into_iter() + .enumerate() + .map(|(i, s)| s.spanned(i..i + 1)) + .collect(), + ); println!("result: {:?}", res); assert!(res.is_ok()) } @@ -206,11 +238,16 @@ mod test { Sym::Add, Sym::Map, Sym::Iota, - Sym::Literal(Lit::Num("2".to_string())) + Sym::Literal(Lit::Num("2".to_string())), ]; - let err = check(prog); - println!("result: {:?}", err); - assert!(err.is_err()) + let res = check( + prog.into_iter() + .enumerate() + .map(|(i, s)| s.spanned(i..i + 1)) + .collect(), + ); + println!("result: {:?}", res); + assert!(res.is_err()) } #[test] @@ -223,8 +260,13 @@ mod test { Sym::Iota, Sym::Literal(Lit::Num("2".to_string())), ]; - let err = check(prog); - println!("result: {:?}", err); - assert!(err.is_ok()) + let res = check( + prog.into_iter() + .enumerate() + .map(|(i, s)| s.spanned(i..i + 1)) + .collect(), + ); + println!("result: {:?}", res); + assert!(res.is_err()) } } diff --git a/parser/Cargo.toml b/parser/Cargo.toml index e256f03..d435418 100644 --- a/parser/Cargo.toml +++ b/parser/Cargo.toml @@ -8,3 +8,7 @@ edition = "2021" [dependencies] ariadne = "0.1.3" chumsky = "0.5.0" +utils = { path = "../utils" } + +[dev-dependencies] +insta = "1.8.0" diff --git a/parser/src/lib.rs b/parser/src/lib.rs index 7761f22..9fdfd74 100644 --- a/parser/src/lib.rs +++ b/parser/src/lib.rs @@ -1,12 +1,11 @@ mod report; -mod spanned; mod token; use crate::token::Token; use chumsky::{prelude::*, Stream}; -use report::Report; use report::{report_of_char_error, report_of_token_error}; use token::lexer; +use utils::{report::Report, Positioned, PositionedExt, Reference}; #[derive(Clone, Debug, PartialEq)] pub enum Lit { @@ -32,26 +31,32 @@ pub enum Sym { Add, Literal(Lit), Var(u32), - Lambda(Vec), + Lambda(Vec>), } impl Sym { - pub fn lambda>>(inner: I) -> Option { + pub fn lambda>>>(inner: I) -> Option { Some(Self::Lambda(inner.into_iter().collect::>>()?)) } } fn literal() -> impl Parser> { - use Token::*; use token::Dir::*; + use Token::*; let int = filter_map(|span, tok| match tok { Num(n) => Ok(Lit::Num(n)), t => Err(Simple::expected_input_found(span, vec![], Some(t))), }); - recursive(|lit| lit.repeated().at_least(1).delimited_by(Bracket(L), Bracket(R)).map(Lit::List).or(int)) + recursive(|lit| { + lit.repeated() + .at_least(1) + .delimited_by(Bracket(L), Bracket(R)) + .map(Lit::List) + .or(int) + }) } -fn parser() -> impl Parser>, Error = Simple> { +fn parser() -> impl Parser>>, Error = Simple> { use token::Dir::*; use Token::*; let var = filter_map(|span, tok| match tok { @@ -62,7 +67,7 @@ fn parser() -> impl Parser>, Error = Simple> { recursive(|instr| { instr .delimited_by(Brace(L), Brace(R)) - .map(|v| Sym::lambda(v)) + .map_with_span(|v, span| Sym::lambda(v).map(|l| l.spanned(span))) .or(just(Ident("K".to_string())) .to(Sym::CombK) .or(just(Ident("S".to_string())).to(Sym::CombS)) @@ -80,14 +85,17 @@ fn parser() -> impl Parser>, Error = Simple> { .or(just(Op("\\".to_string())).to(Sym::Filter)) .or(lit) .or(var) - .map(Some)) + .map_with_span(|s, span| Some(s.spanned(span)))) .recover_with(nested_delimiters(Brace(L), Brace(R), [], |_| None)) .repeated() }) .map(|v| v.into_iter().collect::>>()) } -pub fn parse(src_id: impl Into, input: &str) -> (Option>, Vec) { +pub fn parse( + src_id: impl Into, + input: &str, +) -> (Option>>, Vec) { let src_id = src_id.into(); let slen = input.len(); let (tokens, tokerr) = lexer().then_ignore(end()).parse_recovery(input); @@ -100,16 +108,26 @@ pub fn parse(src_id: impl Into, input: &str) -> (Option>, Vec, input: &str) -> (Option>, Vec { - { - use ariadne::Source; - let input = $input; - let (res, err) = parse("", input); - for report in err { - report.eprint(("".into(), Source::from(input))).unwrap(); - } - assert_eq!(res, Some(vec![$($e),*])); - } - }; + ($input:expr, [$($e:expr),*]) => {{ + let input = $input; + let (res, err) = parse("", input); + insta::assert_display_snapshot!(err + .into_iter() + .map(|r| { + use ariadne::Source; + let mut s = ::std::io::Cursor::new(Vec::new()); + r.write(("".into(), Source::from(input)), &mut s) + .unwrap(); + String::from_utf8_lossy(&s.into_inner()).to_string() + }) + .collect::>() + .join("\n")); + insta::assert_debug_snapshot!(res); + }}; } #[test] fn test_ski() { diff --git a/parser/src/report.rs b/parser/src/report.rs index 414ca81..44cebd9 100644 --- a/parser/src/report.rs +++ b/parser/src/report.rs @@ -1,79 +1,81 @@ -use ariadne::{Color, Fmt}; +use ariadne::{Color, Fmt, ReportKind}; use chumsky::{error::SimpleReason, prelude::Simple}; -use std::ops::Range; +use std::rc::Rc; use crate::token::Token; +use utils::{report::*, Position, PositionedExt, Reference}; -pub type SrcId = (String, Range); -pub type Report = ariadne::Report; -pub type Label = ariadne::Label; - -pub(crate) fn report_of_char_error(src_id: impl Into, err: Simple) -> Report { - let id = (src_id.into(), err.span()); - let report = Report::build(ariadne::ReportKind::Error, id.0.clone(), err.span().start); +pub(crate) fn report_of_char_error(src_id: impl Into, err: Simple) -> Report { + let pos = Position::spanned(err.span()).with_reference(src_id); match err.reason() { - SimpleReason::Unclosed { span, delimiter } => report - .with_message(format!( - "Unclosed delimiter {}", - delimiter.fg(Color::Yellow) - )) - .with_label( - Label::new((id.0.clone(), span.clone())) - .with_color(Color::Blue) - .with_message(format!("Unclosed delimiter is here")), - ) - .finish(), - SimpleReason::Unexpected => report - .with_message(if let Some(found) = err.found() { - format!("Unexpected input {}", found.fg(Color::Red)) - } else { - format!("Unexpected input") - }) - .with_label(Label::new(id).with_color(Color::Blue).with_message(format!( + SimpleReason::Unclosed { span, delimiter } => { + format!("Unclosed delimiter {}", delimiter.fg(Color::Yellow)) + .positioned(pos.clone()) + .into_report(ReportKind::Error) + .with_label( + "Unclosed delimiter is here" + .positioned(pos) + .into_label() + .with_color(Color::Blue), + ) + .finish() + } + SimpleReason::Unexpected => if let Some(found) = err.found() { + format!("Unexpected input {}", found.fg(Color::Red)) + } else { + format!("Unexpected input") + } + .positioned(pos.clone()) + .into_report(ReportKind::Error) + .with_label( + Label::new(pos) + .with_color(Color::Blue) + .with_message(format!( "Unexpected token {}", err.expected() .map(|t| t.fg(Color::Cyan).to_string()) .collect::>() .join(", ") - ))) - .finish(), - SimpleReason::Custom(msg) => report - .with_message(msg) - .finish(), + )), + ) + .finish(), + SimpleReason::Custom(msg) => msg.positioned(pos).into_report(ReportKind::Error).finish(), } } -pub(crate) fn report_of_token_error(src_id: impl Into, err: Simple) -> Report { - let id = (src_id.into(), err.span()); - let report = Report::build(ariadne::ReportKind::Error, id.0.clone(), err.span().start); +pub(crate) fn report_of_token_error(src_id: impl Into, err: Simple) -> Report { + let pos = Position::spanned(err.span()).with_reference(src_id); match err.reason() { - SimpleReason::Unclosed { span, delimiter } => report - .with_message(format!( - "Unclosed delimiter {}", - delimiter.fg(Color::Yellow) - )) - .with_label( - Label::new((id.0.clone(), span.clone())) - .with_color(Color::Blue) - .with_message(format!("Unclosed delimiter is here")), - ) - .finish(), - SimpleReason::Unexpected => report - .with_message(if let Some(found) = err.found() { - format!("Unexpected input {}", found.fg(Color::Red)) - } else { - format!("Unexpected input") - }) - .with_label(Label::new(id).with_color(Color::Blue).with_message(format!( + SimpleReason::Unclosed { span, delimiter } => { + format!("Unclosed delimiter {}", delimiter.fg(Color::Yellow)) + .positioned(pos.clone()) + .into_report(ReportKind::Error) + .with_label( + Label::new(pos) + .with_color(Color::Blue) + .with_message(format!("Unclosed delimiter is here")), + ) + .finish() + } + SimpleReason::Unexpected => (if let Some(found) = err.found() { + format!("Unexpected input {}", found.fg(Color::Red)) + } else { + format!("Unexpected input") + }) + .positioned(pos.clone()) + .into_report(ReportKind::Error) + .with_label( + Label::new(pos) + .with_color(Color::Blue) + .with_message(format!( "Expecting one of {}", err.expected() .map(|v| v.fg(Color::Cyan).to_string()) .collect::>() .join(", ") - ))) - .finish(), - SimpleReason::Custom(msg) => report - .with_message(msg) - .finish(), + )), + ) + .finish(), + SimpleReason::Custom(msg) => msg.positioned(pos).into_report(ReportKind::Error).finish(), } } diff --git a/parser/src/spanned.rs b/parser/src/spanned.rs deleted file mode 100644 index e53bb4a..0000000 --- a/parser/src/spanned.rs +++ /dev/null @@ -1,31 +0,0 @@ -use std::ops::{Range, Deref}; - -pub type Span = Range; - -#[derive(Debug, Clone, PartialEq, Eq, Hash)] -pub struct Spanned { - pub span: Span, - pub value: T, -} - -impl Deref for Spanned { - type Target = T; - - fn deref(&self) -> &Self::Target { - &self.value - } -} - -impl From> for (T, Span) { - fn from(s: Spanned) -> Self { - (s.value, s.span) - } -} - -pub trait SpannedExt: Sized { - fn spanned(self, span: Span) -> Spanned { - Spanned { span, value: self } - } -} - -impl SpannedExt for T {} diff --git a/parser/src/token.rs b/parser/src/token.rs index d9c4b98..36ef6ac 100644 --- a/parser/src/token.rs +++ b/parser/src/token.rs @@ -10,7 +10,7 @@ use chumsky::{ pub use Dir::*; pub use Token::*; -use crate::spanned::{Spanned, SpannedExt}; +use utils::{Positioned, PositionedExt}; #[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)] pub enum Dir { @@ -44,9 +44,8 @@ impl fmt::Display for Token { } } -pub(crate) fn lexer() -> impl Parser>, Error = Simple> { - let op = filter(char::is_ascii_punctuation) - .map(|c| Op(c.to_string())); +pub(crate) fn lexer() -> impl Parser>, Error = Simple> { + let op = filter(char::is_ascii_punctuation).map(|c| Op(c.to_string())); let ident = ident().map(Ident); let num = int(10).map(Num); let parens = just('(').to(Paren(L)).or(just(')').to(Paren(R))); diff --git a/repl/Cargo.toml b/repl/Cargo.toml index 0cf2d2b..bcc4c89 100644 --- a/repl/Cargo.toml +++ b/repl/Cargo.toml @@ -10,3 +10,4 @@ ariadne = "*" computations = { path = "../computations" } parser = { path = "../parser" } rustyline = "9" +utils = { path = "../utils" } diff --git a/repl/src/main.rs b/repl/src/main.rs index f984b08..05520cb 100644 --- a/repl/src/main.rs +++ b/repl/src/main.rs @@ -1,15 +1,13 @@ -use ariadne::{ReportKind, Source}; +use ariadne::{Color, ReportKind, Source}; use computations::check; use parser::parse; -use rustyline::{Editor, error::ReadlineError}; -use std::ops::Range; +use rustyline::{error::ReadlineError, Editor}; +use utils::{PositionedExt, Reference}; -const HISTORYFILE: &'static str = "/tmp/ulp-repl.history"; - -type SrcId = (String, Range); -type Report = ariadne::Report; +const HISTORYFILE: &str = "/tmp/ulp-repl.history"; fn main() { + let reference = Reference::from(""); let mut rl = Editor::<()>::new(); rl.load_history(HISTORYFILE).unwrap_or(()); @@ -17,23 +15,25 @@ fn main() { match rl.readline("ULP> ") { Ok(line) => { rl.add_history_entry(&line); - let (ast, errors) = parse("", &line); + let (ast, errors) = parse(reference.clone(), &line); for err in errors { - err.eprint(("".to_string(), Source::from(&line))).unwrap(); + err.eprint(("".into(), Source::from(&line))).unwrap(); } if let Some(ast) = ast { match check(ast) { Ok(comp) => println!("Computation {:#?}", comp), - Err(err) => Report::build(ReportKind::Error, "", 0) - .with_message(format!("Check error: {}", err)) + Err(err) => "Check error" + .positioned(err.pos.clone().with_reference("")) + .into_report(ReportKind::Error) + .with_label(err.into_label().with_color(Color::Red)) .with_note("The structure is correct, however ULP could not figure out how to compute the expression.") .finish() - .eprint(("".to_string(), Source::from(&line))) + .eprint((reference.clone(), Source::from(&line))) .unwrap(), } } - }, - Err(ReadlineError::Interrupted) => {}, + } + Err(ReadlineError::Interrupted) => {} Err(ReadlineError::Eof) => break, Err(err) => eprintln!("Fatal error: {}", err), } diff --git a/utils/Cargo.toml b/utils/Cargo.toml new file mode 100644 index 0000000..639df54 --- /dev/null +++ b/utils/Cargo.toml @@ -0,0 +1,9 @@ +[package] +name = "utils" +version = "0.1.0" +edition = "2021" + +# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html + +[dependencies] +ariadne = "*" diff --git a/utils/src/lib.rs b/utils/src/lib.rs new file mode 100644 index 0000000..02cf12a --- /dev/null +++ b/utils/src/lib.rs @@ -0,0 +1,223 @@ +use std::{ + error::Error, + fmt, + ops::{Deref, Range}, + rc::Rc, +}; + +pub mod report; + +#[derive(Debug, Default, Clone, Copy, PartialEq, Eq)] +pub struct Span { + pub start: usize, + pub end: usize, +} + +impl From> for Span { + fn from(r: Range) -> Self { + Self { + start: r.start, + end: r.end, + } + } +} + +impl Into> for Span { + fn into(self) -> Range { + self.start..self.end + } +} + +impl fmt::Display for Span { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + write!(f, "{}:{}", self.start, self.end) + } +} + +impl Span { + pub fn merge(self, other: Self) -> Self { + Self { + start: self.start.min(other.start), + end: self.end.max(other.end), + } + } +} + +#[derive(Debug, Default, Clone, PartialEq, Eq)] +pub struct Reference(Option>); + +impl<'a> From<&'a str> for Reference { + fn from(s: &'a str) -> Self { + Self(Some(s.into())) + } +} + +impl From for Reference { + fn from(s: String) -> Self { + Self(Some(s.into())) + } +} + +impl fmt::Display for Reference { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + if let Some(s) = self.0.as_deref() { + write!(f, "{}", s) + } else { + write!(f, "") + } + } +} + +impl Reference { + pub fn or(self, other: Self) -> Self { + Self(self.0.or(other.0)) + } +} + +#[derive(Debug, Default, Clone, PartialEq, Eq)] +pub struct Position { + pub reference: Reference, + pub span: Span, +} + +impl fmt::Display for Position { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + write!(f, "{}:{}", self.reference, self.span) + } +} + +impl Position { + pub fn spanned>(span: S) -> Self { + Self { + span: span.into(), + ..Default::default() + } + } + pub fn with_reference>(mut self, reference: S) -> Self { + self.reference = reference.into(); + self + } + + pub fn with_span>(mut self, span: S) -> Self { + self.span = span.into(); + self + } +} + +#[derive(Debug, Clone, PartialEq, Eq)] +pub struct Positioned { + pub pos: Position, + pub value: T, +} + +impl fmt::Display for Positioned { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + write!(f, "{}: {}", self.pos, self.value) + } +} + +impl Error for Positioned { + fn source(&self) -> Option<&(dyn Error + 'static)> { + self.value.source() + } +} + +impl Position { + pub fn merge(self, other: Self) -> Self { + Self { + reference: self.reference.or(other.reference), + span: self.span.merge(other.span), + } + } +} + +impl Deref for Positioned { + type Target = T; + fn deref(&self) -> &Self::Target { + &self.value + } +} + +impl Positioned { + pub fn map(self, map: impl FnOnce(T) -> U) -> Positioned { + Positioned { + pos: self.pos, + value: map(self.value), + } + } + + pub fn as_ref(&self) -> Positioned<&T> { + Positioned { + pos: self.pos.clone(), + value: &self.value, + } + } + + pub fn as_deref(&self) -> Positioned<&T::Target> + where + T: Deref, + { + self.as_ref().map(|r| r.deref()) + } + + pub fn into_inner(self) -> T { + self.value + } + + pub fn with_reference>(self, reference: S) -> Self { + Self { + pos: self.pos.with_reference(reference), + value: self.value, + } + } + + pub fn with_span>(self, span: S) -> Self { + Self { + pos: self.pos.with_span(span), + value: self.value, + } + } +} + +impl Positioned> { + pub fn from_positioned(values: Vec>) -> Self { + let (pos, values): (Vec<_>, Vec<_>) = values.into_iter().map(|p| (p.pos, p.value)).unzip(); + let pos = pos.into_iter().fold(Position::default(), Position::merge); + Self { pos, value: values } + } +} + +impl Positioned> { + pub fn transpose(self) -> Result, Positioned> { + let Self { pos, value } = self; + value + .map(|t| Positioned { + pos: pos.clone(), + value: t, + }) + .map_err(|e| Positioned { pos, value: e }) + } +} + +impl Positioned> { + pub fn transpose(self) -> Option> { + let Self { pos, value } = self; + value.map(|value| Positioned { pos, value }) + } +} + +pub trait PositionedExt: Sized { + fn positioned(self, pos: Position) -> Positioned { + Positioned { pos, value: self } + } + + fn spanned>(self, span: S) -> Positioned { + self.positioned(Position::spanned(span)) + } + + fn referenced>(self, reference: S) -> Positioned { + self.positioned(Position::default().with_reference(reference)) + } +} + +impl PositionedExt for T {} diff --git a/utils/src/report.rs b/utils/src/report.rs new file mode 100644 index 0000000..53f129c --- /dev/null +++ b/utils/src/report.rs @@ -0,0 +1,35 @@ +use ariadne::ReportKind; + +use crate::{Position, Positioned, Reference}; + +pub type SourceId = Position; +pub type Label = ariadne::Label; +pub type ReportBuilder = ariadne::ReportBuilder; +pub type Report = ariadne::Report; + +impl ariadne::Span for Position { + type SourceId = Reference; + + fn source(&self) -> &Self::SourceId { + &self.reference + } + + fn start(&self) -> usize { + self.span.start + } + + fn end(&self) -> usize { + self.span.end + } +} + +impl Positioned { + pub fn into_report(self, kind: ReportKind) -> ReportBuilder { + Report::build(kind, self.pos.reference, self.pos.span.start) + .with_message(self.value.to_string()) + } + + pub fn into_label(self) -> Label { + Label::new(self.pos).with_message(self.value.to_string()) + } +}