use pest::iterators::Pair; use pest::Parser; use std::collections::BTreeMap; use std::path::PathBuf; use std::rc::Rc; use dhall_parser::{DhallParser, Rule}; use crate::core; use crate::core::*; // This file consumes the parse tree generated by pest and turns it into // our own AST. All those custom macros should eventually moved into // their own crate because they are quite general and useful. For now they // are here and hopefully you can figure out how they work. pub type ParsedExpr = Expr; pub type ParsedText = InterpolatedText; pub type ParsedTextContents<'a> = InterpolatedTextContents<'a, X, Import>; pub type RcExpr = Rc; pub type ParseError = pest::error::Error; pub type ParseResult = Result; pub fn custom_parse_error(pair: &Pair, msg: String) -> ParseError { let msg = format!("{} while matching on:\n{}", msg, debug_pair(pair.clone())); let e = pest::error::ErrorVariant::CustomError { message: msg }; pest::error::Error::new_from_span(e, pair.as_span()) } fn debug_pair(pair: Pair) -> String { use std::fmt::Write; let mut s = String::new(); fn aux(s: &mut String, indent: usize, prefix: String, pair: Pair) { let indent_str = "| ".repeat(indent); let rule = pair.as_rule(); let contents = pair.as_str(); let mut inner = pair.into_inner(); let mut first = true; while let Some(p) = inner.next() { if first { first = false; let last = inner.peek().is_none(); if last && p.as_str() == contents { let prefix = format!("{}{:?} > ", prefix, rule); aux(s, indent, prefix, p); continue; } else { writeln!( s, r#"{}{}{:?}: "{}""#, indent_str, prefix, rule, contents ) .unwrap(); } } aux(s, indent + 1, "".into(), p); } if first { writeln!( s, r#"{}{}{:?}: "{}""#, indent_str, prefix, rule, contents ) .unwrap(); } } aux(&mut s, 0, "".into(), pair); s } #[derive(Debug)] enum IterMatchError { NoMatchFound, Other(T), // Allow other macros to inject their own errors } /* Extends match_iter with typed matches. Takes a callback that determines * when a capture matches. * Returns `Result<_, IterMatchError<_>>`; errors returned by the callback will * get propagated using IterMatchError::Other. * * Example: * ``` * macro_rules! callback { * (@type_callback, positive, $x:expr) => { * if $x >= 0 { Ok($x) } else { Err(()) } * }; * (@type_callback, negative, $x:expr) => { * if $x <= 0 { Ok($x) } else { Err(()) } * }; * (@type_callback, any, $x:expr) => { * Ok($x) * }; * } * * let vec = vec![-1, 2, 3]; * * match_iter_typed!(callback; vec.into_iter(); * (x: positive, y?: negative, z: any) => { ... }, * ) * ``` * */ macro_rules! match_iter_typed { // Collect untyped arguments to pass to match_iter! (@collect, ($($vars:tt)*), ($($args:tt)*), ($($acc:tt)*), ($x:ident : $ty:ident, $($rest:tt)*)) => { match_iter_typed!(@collect, ($($vars)*), ($($args)*), ($($acc)*, $x), ($($rest)*)) }; (@collect, ($($vars:tt)*), ($($args:tt)*), ($($acc:tt)*), ($x:ident.. : $ty:ident, $($rest:tt)*)) => { match_iter_typed!(@collect, ($($vars)*), ($($args)*), ($($acc)*, $x..), ($($rest)*)) }; // Catch extra comma if exists (@collect, ($($vars:tt)*), ($($args:tt)*), (,$($acc:tt)*), ($(,)*)) => { match_iter_typed!(@collect, ($($vars)*), ($($args)*), ($($acc)*), ()) }; (@collect, ($iter:expr, $body:expr, $callback:ident, $error:ident), ($($args:tt)*), ($($acc:tt)*), ($(,)*)) => { { let res = iter_patterns::destructure_iter!($iter; [$($acc)*] => { match_iter_typed!(@callback, $callback, $iter, $($args)*); $body }); res.ok_or(IterMatchError::NoMatchFound) } }; // Pass the matches through the callback (@callback, $callback:ident, $iter:expr, $x:ident : $ty:ident $($rest:tt)*) => { let $x = $callback!(@type_callback, $ty, $x); #[allow(unused_mut)] let mut $x = match $x { Ok(x) => x, Err(e) => break Err(IterMatchError::Other(e)), }; match_iter_typed!(@callback, $callback, $iter $($rest)*); }; (@callback, $callback: ident, $iter:expr, $x:ident.. : $ty:ident $($rest:tt)*) => { let $x = $x.map(|x| $callback!(@type_callback, $ty, x)).collect(); let $x: Vec<_> = match $x { Ok(x) => x, Err(e) => break Err(IterMatchError::Other(e)), }; #[allow(unused_mut)] let mut $x = $x.into_iter(); match_iter_typed!(@callback, $callback, $iter $($rest)*); }; (@callback, $callback:ident, $iter:expr $(,)*) => {}; ($callback:ident; $iter:expr; ($($args:tt)*) => $body:expr) => { { #[allow(unused_mut)] let mut iter = $iter; let res: Result<_, IterMatchError<_>> = loop { break match_iter_typed!(@collect, (iter, $body, $callback, last_error), ($($args)*), (), ($($args)*,) ) }; res } }; } /* Extends match_iter and match_iter_typed with branching. * Returns `Result<_, IterMatchError<_>>`; errors returned by the callback will * get propagated using IterMatchError::Other. * Allows multiple branches. The passed iterator must be Clone. * Will check the branches in order, testing each branch using the callback macro provided. * * Example: * ``` * macro_rules! callback { * (@type_callback, positive, $x:expr) => { * if $x >= 0 { Ok($x) } else { Err(()) } * }; * (@type_callback, negative, $x:expr) => { * if $x <= 0 { Ok($x) } else { Err(()) } * }; * (@type_callback, any, $x:expr) => { * Ok($x) * }; * (@branch_callback, typed, $($args:tt)*) => { * match_iter_typed!(callback; $($args)*) * }; * (@branch_callback, untyped, $($args:tt)*) => { * match_iter!($($args)*) * }; * } * * let vec = vec![-1, 2, 3]; * * match_iter_branching!(branch_callback; vec.into_iter(); * typed!(x: positive, y?: negative, z: any) => { ... }, * untyped!(x, y, z) => { ... }, * ) * ``` * */ macro_rules! match_iter_branching { (@noclone, $callback:ident; $arg:expr; $( $submac:ident!($($args:tt)*) => $body:expr ),* $(,)*) => { { #[allow(unused_assignments)] let mut last_error = IterMatchError::NoMatchFound; // Not a real loop; used for error handling // Would use loop labels but they create warnings #[allow(unreachable_code)] loop { $( let matched: Result<_, IterMatchError<_>> = $callback!(@branch_callback, $submac, $arg; ($($args)*) => $body); #[allow(unused_assignments)] match matched { Ok(v) => break Ok(v), Err(e) => last_error = e, }; )* break Err(last_error); } } }; ($callback:ident; $iter:expr; $($args:tt)*) => { { #[allow(unused_mut)] let mut iter = $iter; match_iter_branching!(@noclone, $callback; iter.clone(); $($args)*) } }; } macro_rules! match_pair { (@type_callback, $ty:ident, $x:expr) => { $ty($x) }; (@branch_callback, children, $pair:expr; $($args:tt)*) => { { #[allow(unused_mut)] let mut pairs = $pair.clone().into_inner(); match_iter_typed!(match_pair; pairs; $($args)*) } }; (@branch_callback, self, $pair:expr; ($x:ident : $ty:ident) => $body:expr) => { { let $x = match_pair!(@type_callback, $ty, $pair.clone()); match $x { Ok($x) => Ok($body), Err(e) => Err(IterMatchError::Other(e)), } } }; (@branch_callback, raw_pair, $pair:expr; ($x:ident) => $body:expr) => { { let $x = $pair.clone(); Ok($body) } }; (@branch_callback, captured_str, $pair:expr; ($x:ident) => $body:expr) => { { let $x = $pair.as_str(); Ok($body) } }; ($pair:expr; $($args:tt)*) => { { let pair = $pair; let result = match_iter_branching!(@noclone, match_pair; pair; $($args)*); result.map_err(|e| match e { IterMatchError::Other(e) => e, _ => custom_parse_error(&pair, "No match found".to_owned()), }) } }; } macro_rules! make_pest_parse_function { ($name:ident<$o:ty>; $submac:ident!( $($args:tt)* )) => ( #[allow(unused_variables)] #[allow(non_snake_case)] #[allow(clippy::all)] fn $name<'a>(pair: Pair<'a, Rule>) -> ParseResult<$o> { $submac!(pair; $($args)*) } ); } macro_rules! named { ($name:ident<$o:ty>; $($args:tt)*) => ( make_pest_parse_function!($name<$o>; match_pair!( $($args)* )); ); } macro_rules! rule { ($name:ident<$o:ty>; $($args:tt)*) => ( make_pest_parse_function!($name<$o>; match_rule!( Rule::$name => match_pair!( $($args)* ), )); ); } macro_rules! rule_group { ($name:ident<$o:ty>; $($ty:ident),*) => ( make_pest_parse_function!($name<$o>; match_rule!( $( Rule::$ty => match_pair!(raw_pair!(p) => $ty(p)?), )* )); ); } macro_rules! match_rule { ($pair:expr; $($pat:pat => $submac:ident!( $($args:tt)* ),)*) => { { #[allow(unreachable_patterns)] match $pair.as_rule() { $( $pat => $submac!($pair; $($args)*), )* r => Err(custom_parse_error(&$pair, format!("Unexpected {:?}", r))), } } }; } rule!(EOI<()>; children!() => ()); named!(str<&'a str>; captured_str!(s) => s.trim()); named!(raw_str<&'a str>; captured_str!(s) => s); named!(label