use itertools::Itertools; use pest::iterators::Pair; use pest::prec_climber as pcl; use pest::prec_climber::PrecClimber; use pest::Parser; use std::borrow::Cow; use std::collections::HashMap; use std::rc::Rc; use dhall_generated_parser::{DhallParser, Rule}; use crate::map::{DupTreeMap, DupTreeSet}; use crate::ExprF::*; use crate::*; // 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(crate) type ParsedRawExpr = RawExpr; pub(crate) type ParsedExpr = Expr; type ParsedText = InterpolatedText; type ParsedTextContents = InterpolatedTextContents; pub type ParseError = pest::error::Error; pub type ParseResult = Result; #[derive(Debug)] enum Either { Left(A), Right(B), } impl crate::Builtin { pub fn parse(s: &str) -> Option { use crate::Builtin::*; match s { "Bool" => Some(Bool), "Natural" => Some(Natural), "Integer" => Some(Integer), "Double" => Some(Double), "Text" => Some(Text), "List" => Some(List), "Optional" => Some(Optional), "None" => Some(OptionalNone), "Natural/build" => Some(NaturalBuild), "Natural/fold" => Some(NaturalFold), "Natural/isZero" => Some(NaturalIsZero), "Natural/even" => Some(NaturalEven), "Natural/odd" => Some(NaturalOdd), "Natural/toInteger" => Some(NaturalToInteger), "Natural/show" => Some(NaturalShow), "Natural/subtract" => Some(NaturalSubtract), "Integer/toDouble" => Some(IntegerToDouble), "Integer/show" => Some(IntegerShow), "Double/show" => Some(DoubleShow), "List/build" => Some(ListBuild), "List/fold" => Some(ListFold), "List/length" => Some(ListLength), "List/head" => Some(ListHead), "List/last" => Some(ListLast), "List/indexed" => Some(ListIndexed), "List/reverse" => Some(ListReverse), "Optional/fold" => Some(OptionalFold), "Optional/build" => Some(OptionalBuild), "Text/show" => Some(TextShow), _ => None, } } } 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 } macro_rules! parse_children { // Variable length pattern with a common unary variant (@match_forwards, $parse_args:expr, $iter:expr, ($body:expr), $variant:ident ($x:ident).., $($rest:tt)* ) => { parse_children!(@match_backwards, $parse_args, $iter, ({ let $x = $iter .map(|x| Parsers::$variant($parse_args, x)) .collect::, _>>()? .into_iter(); $body }), $($rest)* ) }; // Single item pattern (@match_forwards, $parse_args:expr, $iter:expr, ($body:expr), $variant:ident ($x:pat), $($rest:tt)* ) => {{ let p = $iter.next().unwrap(); let $x = Parsers::$variant($parse_args, p)?; parse_children!(@match_forwards, $parse_args, $iter, ($body), $($rest)* ) }}; // Single item pattern after a variable length one: declare reversed and take from the end (@match_backwards, $parse_args:expr, $iter:expr, ($body:expr), $variant:ident ($x:pat), $($rest:tt)* ) => { parse_children!(@match_backwards, $parse_args, $iter, ({ let p = $iter.next_back().unwrap(); let $x = Parsers::$variant($parse_args, p)?; $body }), $($rest)*) }; // Check no elements remain (@match_forwards, $parse_args:expr, $iter:expr, ($body:expr) $(,)*) => { $body }; // After a variable length pattern, everything has already been consumed (@match_backwards, $parse_args:expr, $iter:expr, ($body:expr) $(,)*) => { $body }; ($parse_args:expr, $iter:expr; [$($args:tt)*] => $body:expr) => { parse_children!(@match_forwards, $parse_args, $iter, ($body), $($args)*, ) }; } macro_rules! make_parser { (@children_pattern, $varpat:ident, ($($acc:tt)*), [$variant:ident ($x:pat), $($rest:tt)*] ) => ( make_parser!(@children_pattern, $varpat, ($($acc)* , Rule::$variant), [$($rest)*] ) ); (@children_pattern, $varpat:ident, ($($acc:tt)*), [$variant:ident ($x:ident).., $($rest:tt)*] ) => ( make_parser!(@children_pattern, $varpat, ($($acc)* , $varpat..), [$($rest)*] ) ); (@children_pattern, $varpat:ident, (, $($acc:tt)*), [$(,)*] ) => ([$($acc)*]); (@children_pattern, $varpat:ident, ($($acc:tt)*), [$(,)*] ) => ([$($acc)*]); (@children_filter, $varpat:ident, [$variant:ident ($x:pat), $($rest:tt)*] ) => ( make_parser!(@children_filter, $varpat, [$($rest)*]) ); (@children_filter, $varpat:ident, [$variant:ident ($x:ident).., $($rest:tt)*] ) => ( $varpat.iter().all(|r| r == &Rule::$variant) && make_parser!(@children_filter, $varpat, [$($rest)*]) ); (@children_filter, $varpat:ident, [$(,)*]) => (true); (@body, ($climbers:expr, $input:expr, $pair:expr), rule!( $name:ident<$o:ty>; $span:ident; captured_str!($x:pat) => $body:expr ) ) => ({ let $span = Span::make($input.clone(), $pair.as_span()); let $x = $pair.as_str(); let res: Result<_, String> = try { $body }; res.map_err(|msg| custom_parse_error(&$pair, msg)) }); (@body, ($climbers:expr, $input:expr, $pair:expr), rule!( $name:ident<$o:ty>; $span:ident; children!( $( [$($args:tt)*] => $body:expr ),* $(,)* ) ) ) => ({ let children_rules: Vec = $pair .clone() .into_inner() .map(|p| p.as_rule()) .collect(); let $span = Span::make($input.clone(), $pair.as_span()); #[allow(unused_mut)] let mut iter = $pair.clone().into_inner(); #[allow(unreachable_code)] match children_rules.as_slice() { $( make_parser!(@children_pattern, x, (), [$($args)*,]) if make_parser!(@children_filter, x, [$($args)*,]) => { parse_children!(($climbers, $input.clone()), iter; [$($args)*] => { let res: Result<_, String> = try { $body }; res.map_err(|msg| custom_parse_error(&$pair, msg)) } ) } , )* [..] => Err(custom_parse_error( &$pair, format!("Unexpected children: {:?}", children_rules) )), } }); (@body, ($climbers:expr, $input:expr, $pair:expr), rule!( $name:ident<$o:ty>; prec_climb!( $other_rule:ident, $_climber:expr, $args:pat => $body:expr $(,)* ) ) ) => ({ let climber = $climbers.get(&Rule::$name).unwrap(); climber.climb( $pair.clone().into_inner(), |p| Parsers::$other_rule(($climbers, $input.clone()), p), |l, op, r| { let $args = (l?, op, r?); let res: Result<_, String> = try { $body }; res.map_err(|msg| custom_parse_error(&$pair, msg)) }, ) }); (@body, ($($things:tt)*), rule!( $name:ident<$o:ty>; $($args:tt)* ) ) => ({ make_parser!(@body, ($($things)*), rule!( $name<$o>; _span; $($args)* ) ) }); (@body, ($($things:tt)*), rule!($name:ident<$o:ty>) ) => ({ Ok(()) }); (@construct_climber, ($map:expr), rule!( $name:ident<$o:ty>; prec_climb!($other_rule:ident, $climber:expr, $($_rest:tt)* ) ) ) => ({ $map.insert(Rule::$name, $climber) }); (@construct_climber, ($($things:tt)*), $($args:tt)*) => (()); ($( $submac:ident!( $name:ident<$o:ty> $($args:tt)* ); )*) => ( struct Parsers; impl Parsers { $( #[allow(non_snake_case, unused_variables, clippy::let_unit_value)] fn $name<'a>( (climbers, input): (&HashMap>, Rc), pair: Pair<'a, Rule>, ) -> ParseResult<$o> { make_parser!(@body, (climbers, input, pair), $submac!( $name<$o> $($args)* )) } )* } fn construct_precclimbers() -> HashMap> { let mut map = HashMap::new(); $( make_parser!(@construct_climber, (map), $submac!( $name<$o> $($args)* )); )* map } struct EntryPoint; impl EntryPoint { $( #[allow(non_snake_case, dead_code)] fn $name<'a>( input: Rc, pair: Pair<'a, Rule>, ) -> ParseResult<$o> { let climbers = construct_precclimbers(); Parsers::$name((&climbers, input), pair) } )* } ); } // Trim the shared indent off of a vec of lines, as defined by the Dhall semantics of multiline // literals. fn trim_indent(lines: &mut Vec) { let is_indent = |c: char| c == ' ' || c == '\t'; // There is at least one line so this is safe let last_line_head = lines.last().unwrap().head(); let indent_chars = last_line_head .char_indices() .take_while(|(_, c)| is_indent(*c)); let mut min_indent_idx = match indent_chars.last() { Some((i, _)) => i, // If there is no indent char, then no indent needs to be stripped None => return, }; for line in lines.iter() { // Ignore empty lines if line.is_empty() { continue; } // Take chars from line while they match the current minimum indent. let indent_chars = last_line_head[0..=min_indent_idx] .char_indices() .zip(line.head().chars()) .take_while(|((_, c1), c2)| c1 == c2); match indent_chars.last() { Some(((i, _), _)) => min_indent_idx = i, // If there is no indent char, then no indent needs to be stripped None => return, }; } // Remove the shared indent from non-empty lines for line in lines.iter_mut() { if !line.is_empty() { line.head_mut().replace_range(0..=min_indent_idx, ""); } } } make_parser! { rule!(EOI<()>); rule!(simple_label