use crate::state::State; use bumpalo::collections::vec::Vec; use bumpalo::Bump; use roc_region::all::{Loc, Position, Region}; use Progress::*; #[derive(Debug, Clone, PartialEq, Eq)] pub enum Either { First(First), Second(Second), } impl Copy for Either {} pub type ParseResult<'a, Output, Error> = Result<(Progress, Output, State<'a>), (Progress, Error)>; #[derive(Debug, Clone, Copy, PartialEq, Eq)] pub enum Progress { MadeProgress, NoProgress, } impl Progress { pub fn from_lengths(before: usize, after: usize) -> Self { Self::from_consumed(before - after) } pub fn from_consumed(chars_consumed: usize) -> Self { Self::progress_when(chars_consumed != 0) } pub fn progress_when(made_progress: bool) -> Self { if made_progress { Progress::MadeProgress } else { Progress::NoProgress } } pub fn or(&self, other: Self) -> Self { if (*self == MadeProgress) || (other == MadeProgress) { MadeProgress } else { NoProgress } } } #[derive(Debug, Clone, PartialEq, Eq)] pub enum SyntaxError<'a> { Unexpected(Region), OutdentedTooFar, Eof(Region), InvalidPattern, BadUtf8, ReservedKeyword(Region), ArgumentsBeforeEquals(Region), NotYetImplemented(String), Todo, Type(EType<'a>), Pattern(EPattern<'a>), Expr(EExpr<'a>, Position), Header(EHeader<'a>), Space(BadInputError), NotEndOfFile(Position), } impl<'a> SyntaxError<'a> { pub fn get_region(&self) -> Option { match self { SyntaxError::Unexpected(r) => Some(*r), SyntaxError::Eof(r) => Some(*r), SyntaxError::ReservedKeyword(r) => Some(*r), SyntaxError::ArgumentsBeforeEquals(r) => Some(*r), SyntaxError::Type(e_type) => Some(e_type.get_region()), SyntaxError::Pattern(e_pattern) => Some(e_pattern.get_region()), SyntaxError::NotEndOfFile(pos) => Some(Region::from_pos(*pos)), SyntaxError::Expr(e_expr, _) => Some(e_expr.get_region()), SyntaxError::Header(e_header) => Some(e_header.get_region()), SyntaxError::NotYetImplemented(_) => None, SyntaxError::OutdentedTooFar => None, SyntaxError::Todo => None, SyntaxError::InvalidPattern => None, SyntaxError::BadUtf8 => None, SyntaxError::Space(_bad_input) => None, } } } pub trait SpaceProblem: std::fmt::Debug { fn space_problem(e: BadInputError, pos: Position) -> Self; } macro_rules! impl_space_problem { ($($name:ident $(< $lt:tt >)?),*) => { $( impl $(< $lt >)? SpaceProblem for $name $(< $lt >)? { fn space_problem(e: BadInputError, pos: Position) -> Self { Self::Space(e, pos) } } )* }; } impl_space_problem! { EExpect<'a>, EExposes, EExpr<'a>, EHeader<'a>, EIf<'a>, EImport<'a>, EParams<'a>, EImports, EImportParams<'a>, EInParens<'a>, EClosure<'a>, EList<'a>, EPackageEntry<'a>, EPackages<'a>, EPattern<'a>, EProvides<'a>, ERecord<'a>, EReturn<'a>, ERequires<'a>, EString<'a>, EType<'a>, ETypeInParens<'a>, ETypeRecord<'a>, ETypeTagUnion<'a>, ETypedIdent<'a>, ETypeAbilityImpl<'a>, EWhen<'a>, EAbility<'a>, PInParens<'a>, PRecord<'a>, PList<'a> } #[derive(Debug, Clone, PartialEq, Eq)] pub enum EHeader<'a> { Provides(EProvides<'a>, Position), Params(EParams<'a>, Position), Exposes(EExposes, Position), Imports(EImports, Position), Requires(ERequires<'a>, Position), Packages(EPackages<'a>, Position), Space(BadInputError, Position), Start(Position), ModuleName(Position), AppName(EString<'a>, Position), PackageName(EPackageName<'a>, Position), PlatformName(EPackageName<'a>, Position), IndentStart(Position), InconsistentModuleName(Region), } impl<'a> EHeader<'a> { pub fn get_region(&self) -> Region { match self { EHeader::Provides(provides, _pos) => provides.get_region(), EHeader::Params(params, _pos) => params.get_region(), EHeader::Exposes(_, pos) => Region::from_pos(*pos), EHeader::Imports(_, pos) => Region::from_pos(*pos), EHeader::Requires(requires, _pos) => requires.get_region(), EHeader::Packages(packages, _pos) => packages.get_region(), EHeader::Space(_, pos) => Region::from_pos(*pos), EHeader::Start(pos) => Region::from_pos(*pos), EHeader::ModuleName(pos) => Region::from_pos(*pos), EHeader::AppName(app_name, _pos) => app_name.get_region(), EHeader::PackageName(package_name, _pos) => package_name.get_region(), EHeader::PlatformName(platform_name, _pos) => platform_name.get_region(), EHeader::IndentStart(pos) => Region::from_pos(*pos), EHeader::InconsistentModuleName(region) => *region, } } } #[derive(Debug, Clone, PartialEq, Eq)] pub enum EProvides<'a> { Provides(Position), Open(Position), To(Position), IndentProvides(Position), IndentTo(Position), IndentListStart(Position), IndentPackage(Position), ListStart(Position), ListEnd(Position), Identifier(Position), Package(EPackageName<'a>, Position), Space(BadInputError, Position), } impl<'a> EProvides<'a> { pub fn get_region(&self) -> Region { let pos = match self { EProvides::Provides(p) | EProvides::Open(p) | EProvides::To(p) | EProvides::IndentProvides(p) | EProvides::IndentTo(p) | EProvides::IndentListStart(p) | EProvides::IndentPackage(p) | EProvides::ListStart(p) | EProvides::ListEnd(p) | EProvides::Identifier(p) | EProvides::Package(_, p) | EProvides::Space(_, p) => p, }; Region::from_pos(*pos) } } #[derive(Debug, Clone, PartialEq, Eq)] pub enum EParams<'a> { Pattern(PRecord<'a>, Position), BeforeArrow(Position), Arrow(Position), AfterArrow(Position), Space(BadInputError, Position), } impl<'a> EParams<'a> { pub fn get_region(&self) -> Region { let pos = match self { EParams::Pattern(_, p) | EParams::BeforeArrow(p) | EParams::Arrow(p) | EParams::AfterArrow(p) | EParams::Space(_, p) => p, }; Region::from_pos(*pos) } } #[derive(Debug, Clone, Copy, PartialEq, Eq)] pub enum EExposes { Exposes(Position), Open(Position), IndentExposes(Position), IndentListStart(Position), ListStart(Position), ListEnd(Position), Identifier(Position), Space(BadInputError, Position), } impl EExposes { pub fn get_region(&self) -> Region { let pos = match self { EExposes::Exposes(p) | EExposes::Open(p) | EExposes::IndentExposes(p) | EExposes::IndentListStart(p) | EExposes::ListStart(p) | EExposes::ListEnd(p) | EExposes::Identifier(p) | EExposes::Space(_, p) => p, }; Region::from_pos(*pos) } } #[derive(Debug, Clone, PartialEq, Eq)] pub enum ERequires<'a> { Requires(Position), Open(Position), IndentRequires(Position), IndentListStart(Position), ListStart(Position), ListEnd(Position), TypedIdent(ETypedIdent<'a>, Position), Rigid(Position), Space(BadInputError, Position), } impl<'a> ERequires<'a> { pub fn get_region(&self) -> Region { let pos = match self { ERequires::Requires(p) | ERequires::Open(p) | ERequires::IndentRequires(p) | ERequires::IndentListStart(p) | ERequires::ListStart(p) | ERequires::ListEnd(p) | ERequires::TypedIdent(_, p) | ERequires::Rigid(p) | ERequires::Space(_, p) => p, }; Region::from_pos(*pos) } } #[derive(Debug, Clone, PartialEq, Eq)] pub enum ETypedIdent<'a> { Space(BadInputError, Position), HasType(Position), IndentHasType(Position), Name(Position), Type(EType<'a>, Position), IndentType(Position), Identifier(Position), } impl<'a> ETypedIdent<'a> { pub fn get_region(&self) -> Region { let pos = match self { ETypedIdent::Space(_, p) | ETypedIdent::HasType(p) | ETypedIdent::IndentHasType(p) | ETypedIdent::Name(p) | ETypedIdent::Type(_, p) | ETypedIdent::IndentType(p) | ETypedIdent::Identifier(p) => p, }; Region::from_pos(*pos) } } #[derive(Debug, Clone, PartialEq, Eq)] pub enum EPackages<'a> { Open(Position), Space(BadInputError, Position), Packages(Position), IndentPackages(Position), ListStart(Position), ListEnd(Position), IndentListStart(Position), IndentListEnd(Position), PackageEntry(EPackageEntry<'a>, Position), } impl<'a> EPackages<'a> { pub fn get_region(&self) -> Region { let pos = match self { EPackages::Open(p) | EPackages::Space(_, p) | EPackages::Packages(p) | EPackages::IndentPackages(p) | EPackages::ListStart(p) | EPackages::ListEnd(p) | EPackages::IndentListStart(p) | EPackages::IndentListEnd(p) | EPackages::PackageEntry(_, p) => p, }; Region::from_pos(*pos) } } #[derive(Debug, Clone, PartialEq, Eq)] pub enum EPackageName<'a> { BadPath(EString<'a>, Position), Escapes(Position), Multiline(Position), } impl<'a> EPackageName<'a> { pub fn get_region(&self) -> Region { let pos = match self { EPackageName::BadPath(_, p) | EPackageName::Escapes(p) | EPackageName::Multiline(p) => { p } }; Region::from_pos(*pos) } } #[derive(Debug, Clone, PartialEq, Eq)] pub enum EPackageEntry<'a> { BadPackage(EPackageName<'a>, Position), Shorthand(Position), Colon(Position), IndentPackage(Position), IndentPlatform(Position), Platform(Position), Space(BadInputError, Position), } impl<'a> EPackageEntry<'a> { pub fn get_region(&self) -> Region { let pos = match self { EPackageEntry::BadPackage(_, p) | EPackageEntry::Shorthand(p) | EPackageEntry::Colon(p) | EPackageEntry::IndentPackage(p) | EPackageEntry::IndentPlatform(p) | EPackageEntry::Platform(p) | EPackageEntry::Space(_, p) => p, }; Region::from_pos(*pos) } } #[derive(Debug, Clone, Copy, PartialEq, Eq)] pub enum EImports { Open(Position), Imports(Position), IndentImports(Position), IndentListStart(Position), IndentListEnd(Position), ListStart(Position), ListEnd(Position), Identifier(Position), ExposingDot(Position), ShorthandDot(Position), Shorthand(Position), ModuleName(Position), Space(BadInputError, Position), IndentSetStart(Position), SetStart(Position), SetEnd(Position), TypedIdent(Position), AsKeyword(Position), StrLiteral(Position), } impl EImports { pub fn get_region(&self) -> Region { let pos = match self { EImports::Open(p) | EImports::Imports(p) | EImports::IndentImports(p) | EImports::IndentListStart(p) | EImports::IndentListEnd(p) | EImports::ListStart(p) | EImports::ListEnd(p) | EImports::Identifier(p) | EImports::ExposingDot(p) | EImports::ShorthandDot(p) | EImports::Shorthand(p) | EImports::ModuleName(p) | EImports::Space(_, p) | EImports::IndentSetStart(p) | EImports::SetStart(p) | EImports::SetEnd(p) | EImports::TypedIdent(p) | EImports::AsKeyword(p) | EImports::StrLiteral(p) => p, }; Region::from_pos(*pos) } } #[derive(Debug, Clone, Copy, PartialEq, Eq)] pub enum BadInputError { HasTab, HasMisplacedCarriageReturn, HasAsciiControl, BadUtf8, } impl<'a, T> SourceError<'a, T> { pub fn new(problem: T, state: &State<'a>) -> Self { Self { problem, bytes: state.original_bytes(), } } pub fn map_problem(self, f: impl FnOnce(T) -> E) -> SourceError<'a, E> { SourceError { problem: f(self.problem), bytes: self.bytes, } } pub fn into_file_error(self, filename: std::path::PathBuf) -> FileError<'a, T> { FileError { problem: self, filename, } } } impl<'a> SyntaxError<'a> { pub fn into_source_error(self, state: &State<'a>) -> SourceError<'a, SyntaxError<'a>> { SourceError { problem: self, bytes: state.original_bytes(), } } pub fn into_file_error( self, filename: std::path::PathBuf, state: &State<'a>, ) -> FileError<'a, SyntaxError<'a>> { self.into_source_error(state).into_file_error(filename) } } #[derive(Debug, Clone, PartialEq, Eq)] pub enum EExpr<'a> { TrailingOperator(Position), Start(Position), End(Position), BadExprEnd(Position), Space(BadInputError, Position), Dot(Position), Access(Position), UnaryNot(Position), UnaryNegate(Position), BadOperator(&'a str, Position), DefMissingFinalExpr(Position), DefMissingFinalExpr2(&'a EExpr<'a>, Position), Type(EType<'a>, Position), Pattern(&'a EPattern<'a>, Position), Ability(EAbility<'a>, Position), IndentDefBody(Position), IndentEquals(Position), IndentAnnotation(Position), Equals(Position), Colon(Position), DoubleColon(Position), Ident(Position), ElmStyleFunction(Region, Position), MalformedPattern(Position), QualifiedTag(Position), BackpassComma(Position), BackpassArrow(Position), BackpassContinue(Position), DbgContinue(Position), When(EWhen<'a>, Position), If(EIf<'a>, Position), Expect(EExpect<'a>, Position), Dbg(EExpect<'a>, Position), Import(EImport<'a>, Position), Return(EReturn<'a>, Position), Closure(EClosure<'a>, Position), Underscore(Position), Crash(Position), Try(Position), InParens(EInParens<'a>, Position), Record(ERecord<'a>, Position), RecordUpdateOldBuilderField(Region), RecordUpdateIgnoredField(Region), RecordBuilderOldBuilderField(Region), // SingleQuote errors are folded into the EString Str(EString<'a>, Position), Number(ENumber, Position), List(EList<'a>, Position), IndentStart(Position), IndentEnd(Position), UnexpectedComma(Position), UnexpectedTopLevelExpr(Position), } impl<'a> EExpr<'a> { pub fn get_region(&self) -> Region { match self { // Cases with child nodes that have get_region() EExpr::Type(e_type, _) => e_type.get_region(), EExpr::Pattern(e_pattern, _) => e_pattern.get_region(), EExpr::Ability(e_ability, _) => e_ability.get_region(), EExpr::When(e_when, _) => e_when.get_region(), EExpr::If(e_if, _) => e_if.get_region(), EExpr::Expect(e_expect, _) => e_expect.get_region(), EExpr::Dbg(e_expect, _) => e_expect.get_region(), EExpr::Import(e_import, _) => e_import.get_region(), EExpr::Return(e_return, _) => e_return.get_region(), EExpr::Closure(e_closure, _) => e_closure.get_region(), EExpr::InParens(e_in_parens, _) => e_in_parens.get_region(), EExpr::Record(e_record, _) => e_record.get_region(), EExpr::Str(e_string, _) => e_string.get_region(), EExpr::List(e_list, _) => e_list.get_region(), // Cases with direct Region values EExpr::RecordUpdateOldBuilderField(r) | EExpr::RecordUpdateIgnoredField(r) | EExpr::RecordBuilderOldBuilderField(r) => *r, // Cases with Position values EExpr::TrailingOperator(p) | EExpr::Start(p) | EExpr::End(p) | EExpr::BadExprEnd(p) | EExpr::Space(_, p) | EExpr::Dot(p) | EExpr::Access(p) | EExpr::UnaryNot(p) | EExpr::UnaryNegate(p) | EExpr::BadOperator(_, p) | EExpr::DefMissingFinalExpr(p) | EExpr::DefMissingFinalExpr2(_, p) | EExpr::IndentDefBody(p) | EExpr::IndentEquals(p) | EExpr::IndentAnnotation(p) | EExpr::Equals(p) | EExpr::Colon(p) | EExpr::DoubleColon(p) | EExpr::Ident(p) | EExpr::ElmStyleFunction(_, p) | EExpr::MalformedPattern(p) | EExpr::QualifiedTag(p) | EExpr::BackpassComma(p) | EExpr::BackpassArrow(p) | EExpr::BackpassContinue(p) | EExpr::DbgContinue(p) | EExpr::Underscore(p) | EExpr::Crash(p) | EExpr::Try(p) | EExpr::Number(_, p) | EExpr::IndentStart(p) | EExpr::IndentEnd(p) | EExpr::UnexpectedComma(p) | EExpr::UnexpectedTopLevelExpr(p) => Region::from_pos(*p), } } } #[derive(Debug, Clone, PartialEq, Eq)] pub enum ENumber { End, } #[derive(Debug, Clone, PartialEq, Eq)] pub enum EString<'a> { Open(Position), CodePtOpen(Position), CodePtEnd(Position), InvalidSingleQuote(ESingleQuote, Position), Space(BadInputError, Position), EndlessSingleLine(Position), EndlessMultiLine(Position), EndlessSingleQuote(Position), UnknownEscape(Position), Format(&'a EExpr<'a>, Position), FormatEnd(Position), MultilineInsufficientIndent(Position), ExpectedDoubleQuoteGotSingleQuote(Position), } impl<'a> EString<'a> { pub fn get_region(&self) -> Region { match self { // Case with child node that has get_region() EString::Format(expr, _) => expr.get_region(), // Cases with Position values EString::Open(p) | EString::CodePtOpen(p) | EString::CodePtEnd(p) | EString::InvalidSingleQuote(_, p) | EString::Space(_, p) | EString::EndlessSingleLine(p) | EString::EndlessMultiLine(p) | EString::EndlessSingleQuote(p) | EString::UnknownEscape(p) | EString::FormatEnd(p) | EString::MultilineInsufficientIndent(p) | EString::ExpectedDoubleQuoteGotSingleQuote(p) => Region::from_pos(*p), } } } #[derive(Debug, Clone, Copy, PartialEq, Eq)] pub enum ESingleQuote { Empty, TooLong, InterpolationNotAllowed, } #[derive(Debug, Clone, PartialEq, Eq)] pub enum ERecord<'a> { End(Position), Open(Position), Prefix(Position), Field(Position), UnderscoreField(Position), Colon(Position), QuestionMark(Position), Arrow(Position), Ampersand(Position), // TODO remove Expr(&'a EExpr<'a>, Position), Space(BadInputError, Position), } impl<'a> ERecord<'a> { pub fn get_region(&self) -> Region { match self { // Cases with child node that has get_region() ERecord::Expr(expr, _) => expr.get_region(), // Cases with Position values ERecord::End(p) | ERecord::Open(p) | ERecord::Prefix(p) | ERecord::Field(p) | ERecord::UnderscoreField(p) | ERecord::Colon(p) | ERecord::QuestionMark(p) | ERecord::Arrow(p) | ERecord::Ampersand(p) | ERecord::Space(_, p) => Region::from_pos(*p), } } } #[derive(Debug, Clone, PartialEq, Eq)] pub enum EInParens<'a> { End(Position), Open(Position), /// Empty parens, e.g. () is not allowed Empty(Position), /// Expr(&'a EExpr<'a>, Position), /// Space(BadInputError, Position), } impl<'a> EInParens<'a> { pub fn get_region(&self) -> Region { match self { // Cases with child node that has get_region() EInParens::Expr(expr, _) => expr.get_region(), // Cases with Position values EInParens::End(p) | EInParens::Open(p) | EInParens::Empty(p) | EInParens::Space(_, p) => Region::from_pos(*p), } } } #[derive(Debug, Clone, PartialEq, Eq)] pub enum EClosure<'a> { Space(BadInputError, Position), Start(Position), Arrow(Position), Comma(Position), Arg(Position), // TODO make EEXpr Pattern(EPattern<'a>, Position), Body(&'a EExpr<'a>, Position), IndentArrow(Position), IndentBody(Position), IndentArg(Position), } impl<'a> EClosure<'a> { pub fn get_region(&self) -> Region { match self { // Cases with child nodes that have get_region() EClosure::Pattern(pattern, _) => pattern.get_region(), EClosure::Body(expr, _) => expr.get_region(), // Cases with Position values EClosure::Space(_, p) | EClosure::Start(p) | EClosure::Arrow(p) | EClosure::Comma(p) | EClosure::Arg(p) | EClosure::IndentArrow(p) | EClosure::IndentBody(p) | EClosure::IndentArg(p) => Region::from_pos(*p), } } } #[derive(Debug, Clone, PartialEq, Eq)] pub enum EList<'a> { Open(Position), End(Position), Space(BadInputError, Position), Expr(&'a EExpr<'a>, Position), } impl<'a> EList<'a> { pub fn get_region(&self) -> Region { match self { // Case with child node that has get_region() EList::Expr(expr, _) => expr.get_region(), // Cases with Position values EList::Open(p) | EList::End(p) | EList::Space(_, p) => Region::from_pos(*p), } } } #[derive(Debug, Clone, PartialEq, Eq)] pub enum EWhen<'a> { Space(BadInputError, Position), When(Position), Is(Position), Pattern(EPattern<'a>, Position), Arrow(Position), Bar(Position), IfToken(Position), IfGuard(&'a EExpr<'a>, Position), Condition(&'a EExpr<'a>, Position), Branch(&'a EExpr<'a>, Position), IndentCondition(Position), IndentPattern(Position), IndentArrow(Position), IndentBranch(Position), IndentIfGuard(Position), PatternAlignment(u32, Position), } impl<'a> EWhen<'a> { pub fn get_region(&self) -> Region { match self { // Cases with child nodes that have get_region() EWhen::Pattern(pattern, _) => pattern.get_region(), EWhen::IfGuard(expr, _) => expr.get_region(), EWhen::Condition(expr, _) => expr.get_region(), EWhen::Branch(expr, _) => expr.get_region(), // Cases with Position values EWhen::Space(_, p) | EWhen::When(p) | EWhen::Is(p) | EWhen::Arrow(p) | EWhen::Bar(p) | EWhen::IfToken(p) | EWhen::IndentCondition(p) | EWhen::IndentPattern(p) | EWhen::IndentArrow(p) | EWhen::IndentBranch(p) | EWhen::IndentIfGuard(p) | EWhen::PatternAlignment(_, p) => Region::from_pos(*p), } } } #[derive(Debug, Clone, PartialEq, Eq)] pub enum EAbility<'a> { Space(BadInputError, Position), Type(EType<'a>, Position), DemandAlignment(i32, Position), DemandName(Position), DemandColon(Position), } impl<'a> EAbility<'a> { pub fn get_region(&self) -> Region { match self { // Case with child node that has get_region() EAbility::Type(e_type, _) => e_type.get_region(), // Cases with Position values EAbility::Space(_, p) | EAbility::DemandAlignment(_, p) | EAbility::DemandName(p) | EAbility::DemandColon(p) => Region::from_pos(*p), } } } #[derive(Debug, Clone, PartialEq, Eq)] pub enum EIf<'a> { Space(BadInputError, Position), If(Position), Then(Position), Else(Position), // TODO make EEXpr Condition(&'a EExpr<'a>, Position), ThenBranch(&'a EExpr<'a>, Position), ElseBranch(&'a EExpr<'a>, Position), IndentCondition(Position), IndentIf(Position), IndentThenToken(Position), IndentElseToken(Position), IndentThenBranch(Position), IndentElseBranch(Position), } impl<'a> EIf<'a> { pub fn get_region(&self) -> Region { match self { EIf::Condition(expr, _) | EIf::ThenBranch(expr, _) | EIf::ElseBranch(expr, _) => { expr.get_region() } EIf::Space(_, p) | EIf::If(p) | EIf::Then(p) | EIf::Else(p) | EIf::IndentCondition(p) | EIf::IndentIf(p) | EIf::IndentThenToken(p) | EIf::IndentElseToken(p) | EIf::IndentThenBranch(p) | EIf::IndentElseBranch(p) => Region::from_pos(*p), } } } #[derive(Debug, Clone, PartialEq, Eq)] pub enum EExpect<'a> { Space(BadInputError, Position), Dbg(Position), Expect(Position), Condition(&'a EExpr<'a>, Position), Continuation(&'a EExpr<'a>, Position), IndentCondition(Position), } impl<'a> EExpect<'a> { pub fn get_region(&self) -> Region { match self { EExpect::Condition(expr, _) | EExpect::Continuation(expr, _) => expr.get_region(), EExpect::Space(_, p) | EExpect::Dbg(p) | EExpect::Expect(p) | EExpect::IndentCondition(p) => Region::from_pos(*p), } } } #[derive(Debug, Clone, PartialEq, Eq)] pub enum EReturn<'a> { Space(BadInputError, Position), Return(Position), ReturnValue(&'a EExpr<'a>, Position), IndentReturnValue(Position), } impl<'a> EReturn<'a> { pub fn get_region(&self) -> Region { match self { EReturn::ReturnValue(expr, _) => expr.get_region(), EReturn::Space(_, p) | EReturn::Return(p) | EReturn::IndentReturnValue(p) => { Region::from_pos(*p) } } } } #[derive(Debug, Clone, PartialEq, Eq)] pub enum EImport<'a> { Import(Position), IndentStart(Position), PackageShorthand(Position), PackageShorthandDot(Position), ModuleName(Position), Params(EImportParams<'a>, Position), IndentAs(Position), As(Position), IndentAlias(Position), Alias(Position), LowercaseAlias(Region), IndentExposing(Position), Exposing(Position), ExposingListStart(Position), ExposedName(Position), ExposingListEnd(Position), IndentIngestedPath(Position), IngestedPath(Position), IndentIngestedName(Position), IngestedName(Position), IndentColon(Position), Colon(Position), IndentAnnotation(Position), Annotation(EType<'a>, Position), Space(BadInputError, Position), EndNewline(Position), } impl<'a> EImport<'a> { pub fn get_region(&self) -> Region { match self { // Cases with child nodes that have get_region() EImport::Params(params, _) => params.get_region(), EImport::Annotation(e_type, _) => e_type.get_region(), // Case with direct Region value EImport::LowercaseAlias(r) => *r, // Cases with Position values EImport::Import(p) | EImport::IndentStart(p) | EImport::PackageShorthand(p) | EImport::PackageShorthandDot(p) | EImport::ModuleName(p) | EImport::IndentAs(p) | EImport::As(p) | EImport::IndentAlias(p) | EImport::Alias(p) | EImport::IndentExposing(p) | EImport::Exposing(p) | EImport::ExposingListStart(p) | EImport::ExposedName(p) | EImport::ExposingListEnd(p) | EImport::IndentIngestedPath(p) | EImport::IngestedPath(p) | EImport::IndentIngestedName(p) | EImport::IngestedName(p) | EImport::IndentColon(p) | EImport::Colon(p) | EImport::IndentAnnotation(p) | EImport::Space(_, p) | EImport::EndNewline(p) => Region::from_pos(*p), } } } #[derive(Debug, Clone, PartialEq, Eq)] pub enum EImportParams<'a> { Indent(Position), Record(ERecord<'a>, Position), RecordUpdateFound(Region), RecordBuilderFound(Region), RecordIgnoredFieldFound(Region), Space(BadInputError, Position), } impl<'a> EImportParams<'a> { pub fn get_region(&self) -> Region { match self { EImportParams::Indent(p) | EImportParams::Record(_, p) | EImportParams::Space(_, p) => { Region::from_pos(*p) } EImportParams::RecordUpdateFound(r) | EImportParams::RecordBuilderFound(r) | EImportParams::RecordIgnoredFieldFound(r) => *r, } } } #[derive(Debug, Clone, PartialEq, Eq)] pub enum EPattern<'a> { Record(PRecord<'a>, Position), List(PList<'a>, Position), AsKeyword(Position), AsIdentifier(Position), Underscore(Position), NotAPattern(Position), Start(Position), End(Position), Space(BadInputError, Position), PInParens(PInParens<'a>, Position), NumLiteral(ENumber, Position), IndentStart(Position), IndentEnd(Position), AsIndentStart(Position), AccessorFunction(Position), RecordUpdaterFunction(Position), } impl<'a> EPattern<'a> { pub fn get_region(&self) -> Region { match self { // Cases with child nodes that have get_region() EPattern::Record(expr, _) => expr.get_region(), EPattern::List(expr, _) => expr.get_region(), EPattern::PInParens(expr, _) => expr.get_region(), // Cases with Position values EPattern::AsKeyword(position) | EPattern::AsIdentifier(position) | EPattern::Underscore(position) | EPattern::NotAPattern(position) | EPattern::Start(position) | EPattern::End(position) | EPattern::Space(_, position) | EPattern::NumLiteral(_, position) | EPattern::IndentStart(position) | EPattern::IndentEnd(position) | EPattern::AsIndentStart(position) | EPattern::AccessorFunction(position) | EPattern::RecordUpdaterFunction(position) => Region::from_pos(*position), } } } #[derive(Debug, Clone, PartialEq, Eq)] pub enum PRecord<'a> { End(Position), Open(Position), Field(Position), Colon(Position), Optional(Position), Pattern(&'a EPattern<'a>, Position), Expr(&'a EExpr<'a>, Position), Space(BadInputError, Position), } impl<'a> PRecord<'a> { pub fn get_region(&self) -> Region { match self { // Cases with child nodes that have get_region() PRecord::Pattern(pattern, _) => pattern.get_region(), PRecord::Expr(expr, _) => expr.get_region(), // Cases with Position values PRecord::End(p) | PRecord::Open(p) | PRecord::Field(p) | PRecord::Colon(p) | PRecord::Optional(p) | PRecord::Space(_, p) => Region::from_pos(*p), } } } #[derive(Debug, Clone, PartialEq, Eq)] pub enum PList<'a> { End(Position), Open(Position), Rest(Position), Pattern(&'a EPattern<'a>, Position), Space(BadInputError, Position), } impl<'a> PList<'a> { pub fn get_region(&self) -> Region { match self { // Case with child node that has get_region() PList::Pattern(pattern, _) => pattern.get_region(), // Cases with Position values PList::End(p) | PList::Open(p) | PList::Rest(p) | PList::Space(_, p) => { Region::from_pos(*p) } } } } #[derive(Debug, Clone, PartialEq, Eq)] pub enum PInParens<'a> { Empty(Position), End(Position), Open(Position), Pattern(&'a EPattern<'a>, Position), Space(BadInputError, Position), } impl<'a> PInParens<'a> { pub fn get_region(&self) -> Region { match self { // Case with child node that has get_region() PInParens::Pattern(pattern, _) => pattern.get_region(), // Cases with Position values PInParens::Empty(p) | PInParens::End(p) | PInParens::Open(p) | PInParens::Space(_, p) => Region::from_pos(*p), } } } #[derive(Debug, Clone, PartialEq, Eq)] pub enum EType<'a> { Space(BadInputError, Position), UnderscoreSpacing(Position), TRecord(ETypeRecord<'a>, Position), TTagUnion(ETypeTagUnion<'a>, Position), TInParens(ETypeInParens<'a>, Position), TApply(ETypeApply, Position), TInlineAlias(ETypeInlineAlias, Position), TBadTypeVariable(Position), TWildcard(Position), TInferred(Position), /// TStart(Position), TEnd(Position), TFunctionArgument(Position), TWhereBar(Position), TImplementsClause(Position), TAbilityImpl(ETypeAbilityImpl<'a>, Position), /// TIndentStart(Position), TIndentEnd(Position), TAsIndentStart(Position), } impl<'a> EType<'a> { pub fn get_region(&self) -> Region { match self { // Cases with child nodes that have get_region() EType::TRecord(expr, _) => expr.get_region(), EType::TTagUnion(expr, _) => expr.get_region(), EType::TInParens(expr, _) => expr.get_region(), EType::TApply(eapply, _) => eapply.get_region(), EType::TInlineAlias(einline, _) => einline.get_region(), EType::TAbilityImpl(eability, _) => eability.get_region(), // Cases with Position values EType::Space(_, p) | EType::UnderscoreSpacing(p) | EType::TBadTypeVariable(p) | EType::TWildcard(p) | EType::TInferred(p) | EType::TStart(p) | EType::TEnd(p) | EType::TFunctionArgument(p) | EType::TWhereBar(p) | EType::TImplementsClause(p) | EType::TIndentStart(p) | EType::TIndentEnd(p) | EType::TAsIndentStart(p) => Region::from_pos(*p), } } } #[derive(Debug, Clone, PartialEq, Eq)] pub enum ETypeRecord<'a> { End(Position), Open(Position), Field(Position), Colon(Position), Optional(Position), Type(&'a EType<'a>, Position), Space(BadInputError, Position), IndentOpen(Position), IndentColon(Position), IndentOptional(Position), IndentEnd(Position), } impl<'a> ETypeRecord<'a> { pub fn get_region(&self) -> Region { match self { // Case with child node that has get_region() ETypeRecord::Type(type_expr, _) => type_expr.get_region(), // Cases with Position values ETypeRecord::End(p) | ETypeRecord::Open(p) | ETypeRecord::Field(p) | ETypeRecord::Colon(p) | ETypeRecord::Optional(p) | ETypeRecord::Space(_, p) | ETypeRecord::IndentOpen(p) | ETypeRecord::IndentColon(p) | ETypeRecord::IndentOptional(p) | ETypeRecord::IndentEnd(p) => Region::from_pos(*p), } } } #[derive(Debug, Clone, PartialEq, Eq)] pub enum ETypeTagUnion<'a> { End(Position), Open(Position), Type(&'a EType<'a>, Position), Space(BadInputError, Position), } impl<'a> ETypeTagUnion<'a> { pub fn get_region(&self) -> Region { match self { // Case with child node that has get_region() ETypeTagUnion::Type(type_expr, _) => type_expr.get_region(), // Cases with Position values ETypeTagUnion::End(p) | ETypeTagUnion::Open(p) | ETypeTagUnion::Space(_, p) => { Region::from_pos(*p) } } } } #[derive(Debug, Clone, PartialEq, Eq)] pub enum ETypeInParens<'a> { /// e.g. (), which isn't a valid type Empty(Position), End(Position), Open(Position), /// Type(&'a EType<'a>, Position), /// Space(BadInputError, Position), /// IndentOpen(Position), IndentEnd(Position), } impl<'a> ETypeInParens<'a> { pub fn get_region(&self) -> Region { match self { // Cases with child nodes that have get_region() ETypeInParens::Type(type_expr, _) => type_expr.get_region(), // Cases with Position values ETypeInParens::Empty(p) | ETypeInParens::End(p) | ETypeInParens::Open(p) | ETypeInParens::Space(_, p) | ETypeInParens::IndentOpen(p) | ETypeInParens::IndentEnd(p) => Region::from_pos(*p), } } } #[derive(Debug, Clone, PartialEq, Eq)] pub enum ETypeApply { /// StartNotUppercase(Position), End(Position), Space(BadInputError, Position), /// DoubleDot(Position), TrailingDot(Position), StartIsNumber(Position), } impl ETypeApply { pub fn get_region(&self) -> Region { let pos = match self { ETypeApply::StartNotUppercase(p) | ETypeApply::End(p) | ETypeApply::Space(_, p) | ETypeApply::DoubleDot(p) | ETypeApply::TrailingDot(p) | ETypeApply::StartIsNumber(p) => p, }; Region::from_pos(*pos) } } #[derive(Debug, Clone, PartialEq, Eq)] pub enum ETypeInlineAlias { NotAnAlias(Position), Qualified(Position), ArgumentNotLowercase(Position), } impl ETypeInlineAlias { pub fn get_region(&self) -> Region { let pos = match self { ETypeInlineAlias::NotAnAlias(p) | ETypeInlineAlias::Qualified(p) | ETypeInlineAlias::ArgumentNotLowercase(p) => p, }; Region::from_pos(*pos) } } #[derive(Debug, Clone, PartialEq, Eq)] pub enum ETypeAbilityImpl<'a> { End(Position), Open(Position), Field(Position), UnderscoreField(Position), Colon(Position), Arrow(Position), Optional(Position), Type(&'a EType<'a>, Position), Space(BadInputError, Position), Prefix(Position), QuestionMark(Position), Ampersand(Position), Expr(&'a EExpr<'a>, Position), IndentBar(Position), IndentAmpersand(Position), } impl<'a> ETypeAbilityImpl<'a> { pub fn get_region(&self) -> Region { match self { // Case with child node that has get_region() ETypeAbilityImpl::Type(type_expr, _) => type_expr.get_region(), ETypeAbilityImpl::Expr(expr, _) => expr.get_region(), // Cases with Position values ETypeAbilityImpl::End(p) | ETypeAbilityImpl::Open(p) | ETypeAbilityImpl::Field(p) | ETypeAbilityImpl::UnderscoreField(p) | ETypeAbilityImpl::Colon(p) | ETypeAbilityImpl::Arrow(p) | ETypeAbilityImpl::Optional(p) | ETypeAbilityImpl::Space(_, p) | ETypeAbilityImpl::Prefix(p) | ETypeAbilityImpl::QuestionMark(p) | ETypeAbilityImpl::Ampersand(p) | ETypeAbilityImpl::IndentBar(p) | ETypeAbilityImpl::IndentAmpersand(p) => Region::from_pos(*p), } } } impl<'a> From> for ETypeAbilityImpl<'a> { fn from(e: ERecord<'a>) -> Self { match e { ERecord::End(p) => ETypeAbilityImpl::End(p), ERecord::Open(p) => ETypeAbilityImpl::Open(p), ERecord::Field(p) => ETypeAbilityImpl::Field(p), ERecord::UnderscoreField(p) => ETypeAbilityImpl::UnderscoreField(p), ERecord::Colon(p) => ETypeAbilityImpl::Colon(p), ERecord::Arrow(p) => ETypeAbilityImpl::Arrow(p), ERecord::Space(s, p) => ETypeAbilityImpl::Space(s, p), ERecord::Prefix(p) => ETypeAbilityImpl::Prefix(p), ERecord::QuestionMark(p) => ETypeAbilityImpl::QuestionMark(p), ERecord::Ampersand(p) => ETypeAbilityImpl::Ampersand(p), ERecord::Expr(e, p) => ETypeAbilityImpl::Expr(e, p), } } } #[derive(Debug)] pub struct SourceError<'a, T> { pub problem: T, pub bytes: &'a [u8], } #[derive(Debug)] pub struct FileError<'a, T> { pub problem: SourceError<'a, T>, pub filename: std::path::PathBuf, } pub trait Parser<'a, Output, Error> { fn parse( &self, arena: &'a Bump, state: State<'a>, min_indent: u32, ) -> ParseResult<'a, Output, Error>; #[cfg(not(feature = "parse_debug_trace"))] #[inline(always)] fn trace(self, _message: &'static str) -> Self where Self: Sized, Output: std::fmt::Debug, Error: std::fmt::Debug, { self } #[cfg(feature = "parse_debug_trace")] fn trace(self, message: &'static str) -> Traced<'a, Output, Error, Self> where Self: Sized, Output: std::fmt::Debug, Error: std::fmt::Debug, { Traced { parser: self, message, _phantom: Default::default(), } } } impl<'a, F, Output, Error> Parser<'a, Output, Error> for F where Error: 'a, F: Fn(&'a Bump, State<'a>, u32) -> ParseResult<'a, Output, Error>, { fn parse( &self, arena: &'a Bump, state: State<'a>, min_indent: u32, ) -> ParseResult<'a, Output, Error> { self(arena, state, min_indent) } } #[cfg(feature = "parse_debug_trace")] pub struct Traced<'a, O, E, P: Parser<'a, O, E>> { parser: P, message: &'static str, _phantom: std::marker::PhantomData<&'a (O, E)>, } #[cfg(feature = "parse_debug_trace")] impl<'a, O: std::fmt::Debug, E: std::fmt::Debug, P: Parser<'a, O, E>> Parser<'a, O, E> for Traced<'a, O, E, P> where E: 'a, { fn parse(&self, arena: &'a Bump, state: State<'a>, min_indent: u32) -> ParseResult<'a, O, E> { use std::cell::RefCell; thread_local! { pub static INDENT: RefCell = RefCell::new(0); } // This should be enough for anyone. Right? RIGHT? let indent_text = "| ; : ! ".repeat(100); let cur_indent = INDENT.with(|i| *i.borrow()); println!( "{:<5?}:{:<2} {}{:<50}", state.pos(), min_indent, &indent_text[..cur_indent * 2], self.message ); let previous_state = state.clone(); INDENT.with(|i| *i.borrow_mut() += 1); let res = self.parser.parse(arena, state, min_indent); INDENT.with(|i| *i.borrow_mut() = cur_indent); let (progress, value, state) = match &res { Ok((progress, result, state)) => (progress, Ok(result), state), Err((progress, error)) => (progress, Err(error), &previous_state), }; println!( "{:<5?}:{:<2} {}{:<50} {:<15} {:?}", state.pos(), min_indent, &indent_text[..cur_indent * 2], self.message, format!("{:?}", progress), value ); res } } /// Allocates the output of the given parser and returns a reference to it. /// This also happens if the given parser fails. /// /// # Examples /// /// ``` /// # #![forbid(unused_imports)] /// # use roc_parse::state::State; /// # use crate::roc_parse::parser::{Parser, Progress, allocated, word}; /// # use roc_region::all::Position; /// # use bumpalo::Bump; /// # #[derive(Debug, PartialEq)] /// # enum Problem { /// # NotFound(Position), /// # } /// # let arena = Bump::new(); /// let parser_inner = word("hello", Problem::NotFound); /// let alloc_parser = allocated(parser_inner); /// /// // Success case /// let (progress, output, state) = alloc_parser.parse(&arena, State::new("hello, world".as_bytes()), 0).unwrap(); /// assert_eq!(progress, Progress::MadeProgress); /// assert_eq!(output, &()); /// assert_eq!(state.pos(), Position::new(5)); /// /// // Error case /// let (progress, err) = alloc_parser.parse(&arena, State::new("bye, world".as_bytes()), 0).unwrap_err(); /// assert_eq!(progress, Progress::NoProgress); /// assert_eq!(err, Problem::NotFound(Position::zero())); /// ``` pub fn allocated<'a, P, Val, Error>(parser: P) -> impl Parser<'a, &'a Val, Error> where Error: 'a, P: Parser<'a, Val, Error>, Val: 'a, { move |arena, state: State<'a>, min_indent: u32| { let (progress, answer, state) = parser.parse(arena, state, min_indent)?; Ok((progress, &*arena.alloc(answer), state)) } } /// Apply transform function to turn output of given parser into another parser. /// Can be used to chain two parsers. /// /// # Examples /// /// ``` /// # #![forbid(unused_imports)] /// # use roc_parse::state::State; /// # use crate::roc_parse::parser::{Parser, Progress, and_then, word}; /// # use roc_region::all::Position; /// # use bumpalo::Bump; /// # #[derive(Debug, PartialEq)] /// # enum Problem { /// # NotFound(Position), /// # } /// # let arena = Bump::new(); /// let parser1 = word("hello", Problem::NotFound); /// let parser = and_then(parser1, move |p,b| word(", ", Problem::NotFound)); /// /// // Success case /// let (progress, output, state) = parser.parse(&arena, State::new("hello, world".as_bytes()), 0).unwrap(); /// assert_eq!(progress, Progress::MadeProgress); /// assert_eq!(output, ()); /// assert_eq!(state.pos(), Position::new(7)); /// /// // Error case /// let (progress, problem) = parser.parse(&arena, State::new("hello!! world".as_bytes()), 0).unwrap_err(); /// assert_eq!(progress, Progress::NoProgress); /// assert_eq!(problem, Problem::NotFound(Position::new(5))); /// ``` pub fn and_then<'a, P1, P2, F, Before, After, Error>( parser: P1, transform: F, ) -> impl Parser<'a, After, Error> where P1: Parser<'a, Before, Error>, P2: Parser<'a, After, Error>, F: Fn(Progress, Before) -> P2, Error: 'a, { move |arena, state, min_indent| { parser .parse(arena, state, min_indent) .and_then(|(progress, output, next_state)| { transform(progress, output).parse(arena, next_state, min_indent) }) } } /// Creates a new parser that can change its output based on a function. /// /// # Examples /// ``` /// # #![forbid(unused_imports)] /// # use roc_parse::state::State; /// # use crate::roc_parse::parser::{Parser, Progress, then, word}; /// # use roc_region::all::Position; /// # use bumpalo::Bump; /// # #[derive(Debug, PartialEq)] /// # enum Problem { /// # NotFound(Position), /// # OddChar(Position), /// # } /// # let arena = Bump::new(); /// let parser_inner = word("hello", Problem::NotFound); /// /// let parser = then(parser_inner, /// |arena, new_state, progress, output| { /// // arbitrary check /// if new_state.pos().offset % 2 == 0 { /// Ok((progress, output, new_state)) /// } else { /// Err((Progress::NoProgress, Problem::OddChar(new_state.pos()))) /// } /// } /// ); /// /// let actual = parser.parse(&arena, State::new("hello, world".as_bytes()), 0); /// assert!(actual.is_err()); /// ``` pub fn then<'a, P1, F, Before, After, E>(parser: P1, transform: F) -> impl Parser<'a, After, E> where P1: Parser<'a, Before, E>, After: 'a, E: 'a, F: Fn(&'a Bump, State<'a>, Progress, Before) -> ParseResult<'a, After, E>, { move |arena, state, min_indent| { parser .parse(arena, state, min_indent) .and_then(|(progress, output, next_state)| { transform(arena, next_state, progress, output) }) } } /// Matches a word/string exactly, useful when finding a keyword. /// This only matches if the next char is whitespace, the start of a comment, or the end of a line. /// /// # Examples /// ``` /// # #![forbid(unused_imports)] /// # use roc_parse::state::State; /// # use crate::roc_parse::parser::{Parser, Progress, keyword}; /// # use roc_region::all::Position; /// # use bumpalo::Bump; /// # #[derive(Debug, PartialEq)] /// # enum Problem { /// # NotFound(Position), /// # } /// # let arena = Bump::new(); /// let parser = keyword("when", Problem::NotFound); /// /// // Success case /// let (progress, output, state) = parser.parse(&arena, State::new("when".as_bytes()), 0).unwrap(); /// assert_eq!(progress, Progress::MadeProgress); /// assert_eq!(output, ()); /// assert_eq!(state.pos().offset, 4); /// /// // Error case /// let (progress, err) = parser.parse(&arena, State::new("whence".as_bytes()), 0).unwrap_err(); /// assert_eq!(progress, Progress::NoProgress); /// assert_eq!(err, Problem::NotFound(Position::zero())); /// ``` pub fn keyword<'a, ToError, E>( keyword_str: &'static str, if_error: ToError, ) -> impl Parser<'a, (), E> where ToError: Fn(Position) -> E, E: 'a, { move |_, mut state: State<'a>, _min_indent| { let width = keyword_str.len(); if !state.bytes().starts_with(keyword_str.as_bytes()) { return Err((NoProgress, if_error(state.pos()))); } // the next character should not be an identifier character // to prevent treating `whence` or `iffy` as keywords match state.bytes().get(width) { Some( b' ' | b'#' | b'\n' | b'\r' | b'\t' | b',' | b'(' | b')' | b'[' | b']' | b'{' | b'}' | b'"' | b'\'' | b'/' | b'\\' | b'+' | b'*' | b'%' | b'^' | b'&' | b'|' | b'<' | b'>' | b'=' | b'!' | b'~' | b'`' | b';' | b':' | b'?' | b'.' | b'@' | b'-', ) => { state = state.advance(width); Ok((MadeProgress, (), state)) } None => { state = state.advance(width); Ok((MadeProgress, (), state)) } Some(_) => Err((NoProgress, if_error(state.pos()))), } } } /// Parse zero or more values separated by a delimiter (e.g. a comma) whose /// values are discarded pub fn sep_by0<'a, P, D, Val, Error>( delimiter: D, parser: P, ) -> impl Parser<'a, Vec<'a, Val>, Error> where D: Parser<'a, (), Error>, P: Parser<'a, Val, Error>, Error: 'a, { move |arena, state: State<'a>, min_indent: u32| { let original_state = state.clone(); let start_bytes_len = state.bytes().len(); match parser.parse(arena, state, min_indent) { Ok((elem_progress, first_output, next_state)) => { // in practice, we want elements to make progress debug_assert_eq!(elem_progress, MadeProgress); let mut state = next_state; let mut buf = Vec::with_capacity_in(1, arena); buf.push(first_output); loop { match delimiter.parse(arena, state.clone(), min_indent) { Ok((_, (), next_state)) => { // If the delimiter passed, check the element parser. match parser.parse(arena, next_state.clone(), min_indent) { Ok((element_progress, next_output, next_state)) => { // in practice, we want elements to make progress debug_assert_eq!(element_progress, MadeProgress); state = next_state; buf.push(next_output); } Err((_, fail)) => { // If the delimiter parsed, but the following // element did not, that's a fatal error. let progress = Progress::from_lengths( start_bytes_len, next_state.bytes().len(), ); return Err((progress, fail)); } } } Err((delim_progress, fail)) => match delim_progress { MadeProgress => return Err((MadeProgress, fail)), NoProgress => return Ok((NoProgress, buf, state)), }, } } } Err((element_progress, fail)) => match element_progress { MadeProgress => Err((MadeProgress, fail)), NoProgress => Ok((NoProgress, Vec::new_in(arena), original_state)), }, } } } /// Parse zero or more values separated by a delimiter (e.g. a comma) /// with an optional trailing delimiter whose values are discarded pub fn trailing_sep_by0<'a, P, D, Val, Error>( delimiter: D, parser: P, ) -> impl Parser<'a, Vec<'a, Val>, Error> where D: Parser<'a, (), Error>, P: Parser<'a, Val, Error>, Error: 'a, { move |arena, state: State<'a>, min_indent: u32| { let original_state = state.clone(); let start_bytes_len = state.bytes().len(); match parser.parse(arena, state, min_indent) { Ok((progress, first_output, next_state)) => { // in practice, we want elements to make progress debug_assert_eq!(progress, MadeProgress); let mut state = next_state; let mut buf = Vec::with_capacity_in(1, arena); buf.push(first_output); loop { match delimiter.parse(arena, state.clone(), min_indent) { Ok((_, (), next_state)) => { // If the delimiter passed, check the element parser. match parser.parse(arena, next_state.clone(), min_indent) { Ok((element_progress, next_output, next_state)) => { // in practice, we want elements to make progress debug_assert_eq!(element_progress, MadeProgress); state = next_state; buf.push(next_output); } Err((_, _fail)) => { // If the delimiter parsed, but the following // element did not, that means we saw a trailing comma let progress = Progress::from_lengths( start_bytes_len, next_state.bytes().len(), ); return Ok((progress, buf, next_state)); } } } Err((delim_progress, fail)) => match delim_progress { MadeProgress => return Err((MadeProgress, fail)), NoProgress => return Ok((NoProgress, buf, state)), }, } } } Err((element_progress, fail)) => match element_progress { MadeProgress => Err((MadeProgress, fail)), NoProgress => Ok((NoProgress, Vec::new_in(arena), original_state)), }, } } } /// Parse one or more values separated by a delimiter (e.g. a comma) whose /// values are discarded pub fn sep_by1<'a, P, D, Val, Error>( delimiter: D, parser: P, ) -> impl Parser<'a, Vec<'a, Val>, Error> where D: Parser<'a, (), Error>, P: Parser<'a, Val, Error>, Error: 'a, { move |arena, state: State<'a>, min_indent: u32| { let start_bytes_len = state.bytes().len(); match parser.parse(arena, state, min_indent) { Ok((progress, first_output, next_state)) => { debug_assert_eq!(progress, MadeProgress); let mut state = next_state; let mut buf = Vec::with_capacity_in(1, arena); buf.push(first_output); loop { let old_state = state.clone(); match delimiter.parse(arena, state, min_indent) { Ok((_, (), next_state)) => { // If the delimiter passed, check the element parser. match parser.parse(arena, next_state, min_indent) { Ok((_, next_output, next_state)) => { state = next_state; buf.push(next_output); } Err((_, fail)) => { return Err((MadeProgress, fail)); } } } Err((delim_progress, fail)) => { match delim_progress { MadeProgress => { // fail if the delimiter made progress return Err((MadeProgress, fail)); } NoProgress => { let progress = Progress::from_lengths( start_bytes_len, old_state.bytes().len(), ); return Ok((progress, buf, old_state)); } } } } } } Err((fail_progress, fail)) => Err((fail_progress, fail)), } } } /// Parse one or more values separated by a delimiter (e.g. a comma) whose /// values are discarded pub fn sep_by1_e<'a, P, V, D, Val, Error>( delimiter: D, parser: P, to_element_error: V, ) -> impl Parser<'a, Vec<'a, Val>, Error> where D: Parser<'a, (), Error>, P: Parser<'a, Val, Error>, V: Fn(Position) -> Error, Error: 'a, { move |arena, state: State<'a>, min_indent: u32| { let original_state = state.clone(); let start_bytes_len = state.bytes().len(); match parser.parse(arena, state, min_indent) { Ok((progress, first_output, next_state)) => { debug_assert_eq!(progress, MadeProgress); let mut state = next_state; let mut buf = Vec::with_capacity_in(1, arena); buf.push(first_output); loop { let old_state = state.clone(); match delimiter.parse(arena, state, min_indent) { Ok((_, (), next_state)) => { // If the delimiter passed, check the element parser. match parser.parse(arena, next_state.clone(), min_indent) { Ok((_, next_output, next_state)) => { state = next_state; buf.push(next_output); } Err((MadeProgress, fail)) => { return Err((MadeProgress, fail)); } Err((NoProgress, _fail)) => { return Err((NoProgress, to_element_error(next_state.pos()))); } } } Err((delim_progress, fail)) => { match delim_progress { MadeProgress => { // fail if the delimiter made progress return Err((MadeProgress, fail)); } NoProgress => { let progress = Progress::from_lengths( start_bytes_len, old_state.bytes().len(), ); return Ok((progress, buf, old_state)); } } } } } } Err((MadeProgress, fail)) => Err((MadeProgress, fail)), Err((NoProgress, _fail)) => Err((NoProgress, to_element_error(original_state.pos()))), } } } /// Make the given parser optional, it can complete or not consume anything, /// but it can't error with progress made. /// /// # Examples /// ``` /// # #![forbid(unused_imports)] /// # use roc_parse::state::State; /// # use crate::roc_parse::parser::{Parser, Progress, optional, word}; /// # use roc_region::all::Position; /// # use bumpalo::Bump; /// # #[derive(Debug, PartialEq)] /// # enum Problem { /// # NotFound(Position), /// # } /// # let arena = Bump::new(); /// let parser = optional(word("hello", Problem::NotFound)); /// /// // Parser completed case /// let (progress, output, state) = parser.parse(&arena, State::new("hello, world".as_bytes()), 0).unwrap(); /// assert_eq!(progress, Progress::MadeProgress); /// assert_eq!(output, Some(())); /// assert_eq!(state.pos().offset, 5); /// /// // No progress case /// let (progress, output, state) = parser.parse(&arena, State::new("bye, world".as_bytes()), 0).unwrap(); /// assert_eq!(progress, Progress::NoProgress); /// assert_eq!(output, None); /// assert_eq!(state.pos().offset, 0); /// ``` pub fn optional<'a, P, T, E>(parser: P) -> impl Parser<'a, Option, E> where P: Parser<'a, T, E>, E: 'a, { move |arena: &'a Bump, state: State<'a>, min_indent: u32| { // We have to clone this because if the optional parser fails, // we need to revert back to the original state. let original_state = state.clone(); match parser.parse(arena, state, min_indent) { Ok((progress, out1, state)) => Ok((progress, Some(out1), state)), Err((MadeProgress, e)) => Err((MadeProgress, e)), Err((NoProgress, _)) => Ok((NoProgress, None, original_state)), } } } // MACRO COMBINATORS // // Using some combinators together results in combinatorial type explosion, // this takes forever to compile. Using macros instead avoids this! /// Wraps the output of the given parser in a [`Loc`](../roc_region/all/struct.Loc.html) struct, /// to provide location information. /// /// # Examples /// ``` /// # #![forbid(unused_imports)] /// # use roc_parse::state::State; /// # use crate::roc_parse::parser::{Parser, Progress, word, loc}; /// # use roc_region::all::{Loc, Position}; /// # use bumpalo::Bump; /// # #[derive(Debug, PartialEq)] /// # enum Problem { /// # NotFound(Position), /// # } /// # let arena = Bump::new(); /// # fn foo<'a>(arena: &'a Bump) { /// let parser = loc(word("hello", Problem::NotFound)); /// /// let (progress, output, state) = parser.parse(&arena, State::new("hello, world".as_bytes()), 0).unwrap(); /// assert_eq!(progress, Progress::MadeProgress); /// assert_eq!(output, Loc::new(0, 5, ())); /// assert_eq!(state.pos().offset, 5); /// # } /// # foo(&arena); /// ``` pub fn loc<'a, Output, E: 'a>( parser: impl Parser<'a, Output, E>, ) -> impl Parser<'a, Loc, E> { move |arena, state: crate::state::State<'a>, min_indent: u32| { let start = state.pos(); match parser.parse(arena, state, min_indent) { Ok((progress, value, state)) => { let end = state.pos(); let region = Region::new(start, end); Ok((progress, Loc { region, value }, state)) } Err(err) => Err(err), } } } /// If the first one parses, ignore its output and move on to parse with the second one. /// /// # Examples /// ``` /// # #![forbid(unused_imports)] /// # use roc_parse::state::State; /// # use crate::roc_parse::parser::{Parser, Progress, word, skip_first}; /// # use roc_parse::ident::lowercase_ident; /// # use bumpalo::Bump; /// # let arena = Bump::new(); /// # fn foo<'a>(arena: &'a Bump) { /// let parser = skip_first( /// word("hello, ", |_| ()), /// lowercase_ident() /// ); /// /// let (progress, output, state) = parser.parse(&arena, State::new("hello, world".as_bytes()), 0).unwrap(); /// assert_eq!(progress, Progress::MadeProgress); /// assert_eq!(output, "world"); /// assert_eq!(state.pos().offset, 12); /// # } /// # foo(&arena); /// ``` pub fn skip_first<'a, P1, First, P2, Second, E>(p1: P1, p2: P2) -> impl Parser<'a, Second, E> where P1: Parser<'a, First, E>, P2: Parser<'a, Second, E>, E: 'a, { move |arena, state: crate::state::State<'a>, min_indent: u32| match p1 .parse(arena, state, min_indent) { Ok((p1, _, state)) => match p2.parse(arena, state, min_indent) { Ok((p2, out2, state)) => Ok((p1.or(p2), out2, state)), Err((p2, fail)) => Err((p1.or(p2), fail)), }, Err((progress, fail)) => Err((progress, fail)), } } /// If the first one parses, parse the second one; if it also parses, use the /// output from the first one. /// /// # Examples /// ``` /// # #![forbid(unused_imports)] /// # use roc_parse::state::State; /// # use crate::roc_parse::parser::{Parser, Progress, word, skip_second}; /// # use roc_parse::ident::lowercase_ident; /// # use bumpalo::Bump; /// # let arena = Bump::new(); /// # fn foo<'a>(arena: &'a Bump) { /// let parser = skip_second( /// lowercase_ident(), /// word(", world", |_| ()) /// ); /// /// let (progress, output, state) = parser.parse(&arena, State::new("hello, world".as_bytes()), 0).unwrap(); /// assert_eq!(progress, Progress::MadeProgress); /// assert_eq!(output, "hello"); /// assert_eq!(state.pos().offset, 12); /// # } /// # foo(&arena); /// ``` pub fn skip_second<'a, P1, First, P2, Second, E>(p1: P1, p2: P2) -> impl Parser<'a, First, E> where E: 'a, P1: Parser<'a, First, E>, P2: Parser<'a, Second, E>, { move |arena, state: crate::state::State<'a>, min_indent: u32| match p1 .parse(arena, state, min_indent) { Ok((p1, out1, state)) => match p2.parse(arena, state, min_indent) { Ok((p2, _, state)) => Ok((p1.or(p2), out1, state)), Err((p2, fail)) => Err((p1.or(p2), fail)), }, Err((progress, fail)) => Err((progress, fail)), } } pub fn collection_inner<'a, Elem: 'a + crate::ast::Spaceable<'a> + Clone, E: 'a + SpaceProblem>( elem: impl Parser<'a, Loc, E> + 'a, delimiter: impl Parser<'a, (), E>, space_before: impl Fn(&'a Elem, &'a [crate::ast::CommentOrNewline<'a>]) -> Elem, ) -> impl Parser<'a, crate::ast::Collection<'a, Loc>, E> { map_with_arena( and( and( crate::blankspace::spaces(), trailing_sep_by0( delimiter, crate::blankspace::spaces_before_optional_after(elem), ), ), crate::blankspace::spaces(), ), #[allow(clippy::type_complexity)] move |arena: &'a bumpalo::Bump, out: ( ( &'a [crate::ast::CommentOrNewline<'a>], bumpalo::collections::Vec<'a, Loc>, ), &'a [crate::ast::CommentOrNewline<'a>], )| { let ((spaces, mut parsed_elems), mut final_comments) = out; if !spaces.is_empty() { if let Some(first) = parsed_elems.first_mut() { first.value = space_before(arena.alloc(first.value.clone()), spaces); } else { debug_assert!(final_comments.is_empty()); final_comments = spaces; } } crate::ast::Collection::with_items_and_comments( arena, parsed_elems.into_bump_slice(), final_comments, ) }, ) } pub fn collection_trailing_sep_e< 'a, Elem: 'a + crate::ast::Spaceable<'a> + Clone, E: 'a + SpaceProblem, >( opening_brace: impl Parser<'a, (), E>, elem: impl Parser<'a, Loc, E> + 'a, delimiter: impl Parser<'a, (), E>, closing_brace: impl Parser<'a, (), E>, space_before: impl Fn(&'a Elem, &'a [crate::ast::CommentOrNewline<'a>]) -> Elem, ) -> impl Parser<'a, crate::ast::Collection<'a, Loc>, E> { between( opening_brace, reset_min_indent(collection_inner(elem, delimiter, space_before)), closing_brace, ) } /// Creates a parser that always succeeds with the given argument as output. /// /// # Examples /// ``` /// # #![forbid(unused_imports)] /// # use roc_parse::state::State; /// # use crate::roc_parse::parser::{Parser, Progress, succeed}; /// # use bumpalo::Bump; /// # let arena = Bump::new(); /// # fn foo<'a>(arena: &'a Bump) { /// let parser = succeed("different"); /// /// let (progress, output, state) = Parser::<&'a str,()>::parse(&parser, &arena, State::new("hello, world".as_bytes()), 0).unwrap(); /// assert_eq!(progress, Progress::NoProgress); /// assert_eq!(output, "different"); /// assert_eq!(state.pos().offset, 0); /// # } /// # foo(&arena); /// ``` pub fn succeed<'a, T: Clone, E: 'a>(value: T) -> impl Parser<'a, T, E> { move |_arena: &'a bumpalo::Bump, state: crate::state::State<'a>, _min_indent: u32| { Ok((NoProgress, value.clone(), state)) } } /// Creates a parser that always fails. /// If the given parser succeeds, the error is customized with the given function. /// /// # Examples /// ``` /// # #![forbid(unused_imports)] /// # use roc_parse::state::State; /// # use crate::roc_parse::parser::{Parser, Progress, word, fail_when}; /// # use roc_region::all::Position; /// # use bumpalo::Bump; /// # #[derive(Debug, PartialEq)] /// # enum Problem { /// # NotFound(Position), /// # OtherProblem(Position), /// # } /// # let arena = Bump::new(); /// let parser = fail_when(Problem::OtherProblem, word("hello", Problem::NotFound)); /// /// // When given parser succeeds: /// let (progress, err) = Parser::<(), Problem>::parse(&parser, &arena, State::new("hello, world".as_bytes()), 0).unwrap_err(); /// assert_eq!(progress, Progress::MadeProgress); /// assert_eq!(err, Problem::OtherProblem(Position::new(0))); /// /// // When given parser errors: /// let (progress, err) = Parser::<(), Problem>::parse(&parser, &arena, State::new("bye, world".as_bytes()), 0).unwrap_err(); /// assert_eq!(progress, Progress::NoProgress); /// assert_eq!(err, Problem::NotFound(Position::new(0))); /// ``` pub fn fail_when<'a, T, T2, E, F, P>(f: F, p: P) -> impl Parser<'a, T, E> where T: 'a, T2: 'a, E: 'a, F: Fn(Position) -> E, P: Parser<'a, T2, E>, { move |arena: &'a bumpalo::Bump, state: State<'a>, min_indent: u32| { let original_state = state.clone(); match p.parse(arena, state, min_indent) { Ok((_, _, _)) => Err((MadeProgress, f(original_state.pos()))), Err((progress, err)) => Err((progress, err)), } } } /// Creates a parser that always fails using the given error function. /// /// # Examples /// ``` /// # #![forbid(unused_imports)] /// # use roc_parse::state::State; /// # use crate::roc_parse::parser::{Parser, Progress, fail}; /// # use roc_region::all::Position; /// # use bumpalo::Bump; /// # #[derive(Debug, PartialEq)] /// # enum Problem { /// # NotFound(Position), /// # } /// # let arena = Bump::new(); /// let parser = fail(Problem::NotFound); /// /// let (progress, err) = Parser::<(), Problem>::parse(&parser, &arena, State::new("hello, world".as_bytes()), 0).unwrap_err(); /// assert_eq!(progress, Progress::NoProgress); /// assert_eq!(err, Problem::NotFound(Position::new(0))); /// ``` pub fn fail<'a, T, E, F>(f: F) -> impl Parser<'a, T, E> where T: 'a, E: 'a, F: Fn(Position) -> E, { move |_arena: &'a bumpalo::Bump, state: State<'a>, _min_indent: u32| { Err((NoProgress, f(state.pos()))) } } /// Creates a parser that fails if the next byte is the given byte. pub fn error_on_byte<'a, T, E, F>(byte_to_match: u8, to_error: F) -> impl Parser<'a, T, E> where T: 'a, E: 'a, F: Fn(Position) -> E, { debug_assert_ne!(byte_to_match, b'\n'); move |_arena: &'a Bump, state: State<'a>, _min_indent: u32| match state.bytes().first() { Some(x) if *x == byte_to_match => Err((MadeProgress, to_error(state.pos()))), _ => Err((NoProgress, to_error(state.pos()))), } } /// Runs two parsers in succession. If both parsers succeed, the output is a tuple of both outputs. /// Both parsers must have the same error type. /// /// # Example /// /// ``` /// # #![forbid(unused_imports)] /// # use roc_parse::state::State; /// # use crate::roc_parse::parser::{Parser, Progress, and, word}; /// # use roc_region::all::Position; /// # use bumpalo::Bump; /// # #[derive(Debug, PartialEq)] /// # enum Problem { /// # NotFound(Position), /// # } /// # let arena = Bump::new(); /// let parser1 = word("hello", Problem::NotFound); /// let parser2 = word(", ", Problem::NotFound); /// let parser = and(parser1, parser2); /// /// // Success case /// let (progress, output, state) = parser.parse(&arena, State::new("hello, world".as_bytes()), 0).unwrap(); /// assert_eq!(progress, Progress::MadeProgress); /// assert_eq!(output, ((),())); /// assert_eq!(state.pos(), Position::new(7)); /// /// // Error case /// let (progress, err) = parser.parse(&arena, State::new("hello!! world".as_bytes()), 0).unwrap_err(); /// assert_eq!(progress, Progress::MadeProgress); /// assert_eq!(err, Problem::NotFound(Position::new(5))); /// ``` pub fn and<'a, Output1, Output2, E: 'a>( p1: impl Parser<'a, Output1, E>, p2: impl Parser<'a, Output2, E>, ) -> impl Parser<'a, (Output1, Output2), E> { move |arena: &'a bumpalo::Bump, state: crate::state::State<'a>, min_indent: u32| match p1 .parse(arena, state, min_indent) { Ok((p1, out1, state)) => match p2.parse(arena, state, min_indent) { Ok((p2, out2, state)) => Ok((p1.or(p2), (out1, out2), state)), Err((p2, fail)) => Err((p1.or(p2), fail)), }, Err((progress, fail)) => Err((progress, fail)), } } /// Take as input something that looks like a struct literal where values are parsers /// and return a parser that runs each parser and returns a struct literal with the /// results. #[macro_export] macro_rules! record { ($name:ident $(:: $name_ext:ident)* { $($field:ident: $parser:expr),* $(,)? }) => { move |arena: &'a bumpalo::Bump, state: $crate::state::State<'a>, min_indent: u32| { let mut state = state; let mut progress = NoProgress; $( let (new_progress, $field, new_state) = $parser.parse(arena, state, min_indent)?; state = new_state; progress = progress.or(new_progress); )* Ok((progress, $name $(:: $name_ext)* { $($field),* }, state)) } }; } /// Similar to [`skip_first`], but we modify the `min_indent` of the second /// parser (`parser`) to be 1 greater than the `line_indent()` at the start of /// the first parser (`before`). pub fn indented_seq_skip_first<'a, O, E: 'a>( before: impl Parser<'a, (), E>, parser: impl Parser<'a, O, E>, ) -> impl Parser<'a, O, E> { move |arena: &'a bumpalo::Bump, state: crate::state::State<'a>, _min_indent: u32| { let start_indent = state.line_indent(); // TODO: we should account for min_indent here, but this doesn't currently work // because min_indent is sometimes larger than it really should be, which is in turn // due to uses of `increment_indent`. // // let p1_indent = std::cmp::max(start_indent, min_indent); let p1_indent = start_indent; let p2_indent = p1_indent + 1; match before.parse(arena, state, p1_indent) { Ok((p1, (), state)) => match parser.parse(arena, state, p2_indent) { Ok((p2, out2, state)) => Ok((p1.or(p2), out2, state)), Err((p2, fail)) => Err((p1.or(p2), fail)), }, Err((progress, fail)) => Err((progress, fail)), } } } /// Similar to `and`, but we modify the min_indent of the second parser to be /// 1 greater than the line_indent() at the start of the first parser. pub fn indented_seq<'a, Output1, Output2, E: 'a>( p1: impl Parser<'a, Output1, E>, p2: impl Parser<'a, Output2, E>, ) -> impl Parser<'a, (Output1, Output2), E> { move |arena: &'a bumpalo::Bump, state: crate::state::State<'a>, _min_indent: u32| { let start_indent = state.line_indent(); // TODO: we should account for min_indent here, but this doesn't currently work // because min_indent is sometimes larger than it really should be, which is in turn // due to uses of `increment_indent`. // // let p1_indent = std::cmp::max(start_indent, min_indent); let p1_indent = start_indent; let p2_indent = p1_indent + 1; match p1.parse(arena, state, p1_indent) { Ok((p1, out1, state)) => match p2.parse(arena, state, p2_indent) { Ok((p2, out2, state)) => Ok((p1.or(p2), (out1, out2), state)), Err((p2, fail)) => Err((p1.or(p2), fail)), }, Err((progress, fail)) => Err((progress, fail)), } } } /// Similar to [`and`], but we modify the `min_indent` of the second parser to be /// 1 greater than the `column()` at the start of the first parser. pub fn absolute_indented_seq<'a, Output1, Output2, E: 'a>( p1: impl Parser<'a, Output1, E>, p2: impl Parser<'a, Output2, E>, ) -> impl Parser<'a, (Output1, Output2), E> { move |arena: &'a bumpalo::Bump, state: crate::state::State<'a>, _min_indent: u32| { let start_indent = state.column(); let p1_indent = start_indent; let p2_indent = p1_indent + 1; match p1.parse(arena, state, p1_indent) { Ok((p1, out1, state)) => match p2.parse(arena, state, p2_indent) { Ok((p2, out2, state)) => Ok((p1.or(p2), (out1, out2), state)), Err((p2, fail)) => Err((p1.or(p2), fail)), }, Err((progress, fail)) => Err((progress, fail)), } } } /// Returns the result of the first parser that makes progress, even if it failed. /// If no parsers make progress, the last parser's result is returned. /// /// # Examples /// ``` /// # #![forbid(unused_imports)] /// # use roc_parse::state::State; /// # use crate::roc_parse::parser::{Parser, Progress, word, byte}; /// # use roc_region::all::Position; /// # use roc_parse::one_of; /// # use bumpalo::Bump; /// # #[derive(Debug, PartialEq)] /// # enum Problem { /// # NotFound(Position), /// # } /// # let arena = Bump::new(); /// # fn foo<'a>(arena: &'a Bump) { /// let parser1 = one_of!( /// word("hello", Problem::NotFound), /// byte(b'h', Problem::NotFound) /// ); /// let (progress, output, state) = parser1.parse(&arena, State::new("hello, world".as_bytes()), 0).unwrap(); /// assert_eq!(progress, Progress::MadeProgress); /// assert_eq!(output, ()); /// assert_eq!(state.pos().offset, 5); /// /// let parser2 = one_of!( /// // swapped the order of the parsers /// byte(b'h', Problem::NotFound), /// word("hello", Problem::NotFound) /// ); /// let (progress, output, state) = parser2.parse(&arena, State::new("hello! world".as_bytes()), 0).unwrap(); /// assert_eq!(progress, Progress::MadeProgress); /// assert_eq!(output, ()); /// assert_eq!(state.pos().offset, 1); /// # } /// # foo(&arena); /// ``` #[macro_export] macro_rules! one_of { ($p1:expr, $p2:expr) => { move |arena: &'a bumpalo::Bump, state: $crate::state::State<'a>, min_indent: u32| { let original_state = state.clone(); match $p1.parse(arena, state, min_indent) { valid @ Ok(_) => valid, Err((MadeProgress, fail)) => Err((MadeProgress, fail)), Err((NoProgress, _)) => $p2.parse(arena, original_state, min_indent), } } }; ($p1:expr, $($others:expr),+) => { one_of!($p1, one_of!($($others),+)) }; ($p1:expr, $($others:expr),+ $(,)?) => { one_of!($p1, $($others),+) }; } pub fn reset_min_indent<'a, P, T, X: 'a>(parser: P) -> impl Parser<'a, T, X> where P: Parser<'a, T, X>, { move |arena, state, _min_indent| parser.parse(arena, state, 0) } pub fn set_min_indent<'a, P, T, X: 'a>(min_indent: u32, parser: P) -> impl Parser<'a, T, X> where P: Parser<'a, T, X>, { move |arena, state, _m| parser.parse(arena, state, min_indent) } pub fn increment_min_indent<'a, P, T, X: 'a>(parser: P) -> impl Parser<'a, T, X> where P: Parser<'a, T, X>, { move |arena, state, min_indent| parser.parse(arena, state, min_indent + 1) } pub fn line_min_indent<'a, P, T, X: 'a>(parser: P) -> impl Parser<'a, T, X> where P: Parser<'a, T, X>, { move |arena, state: State<'a>, min_indent| { let min_indent = std::cmp::max(state.line_indent(), min_indent); parser.parse(arena, state, min_indent) } } pub fn absolute_column_min_indent<'a, P, T, X: 'a>(parser: P) -> impl Parser<'a, T, X> where P: Parser<'a, T, X>, { move |arena, state: State<'a>, _min_indent| { let min_indent = state.column() + 1; parser.parse(arena, state, min_indent) } } /// Transforms a possible error, like `map_err` in Rust. /// It has no effect if the given parser succeeds. /// /// # Examples /// ``` /// # #![forbid(unused_imports)] /// # use roc_parse::state::State; /// # use crate::roc_parse::parser::{Parser, Progress, word, specialize_err}; /// # use roc_region::all::Position; /// # use bumpalo::Bump; /// # #[derive(Debug, PartialEq)] /// # enum Problem { /// # NotFound(Position), /// # Other(Position), /// # } /// # let arena = Bump::new(); /// let parser = specialize_err( /// |_prev_err, pos| Problem::Other(pos), /// word("bye", Problem::NotFound) /// ); /// /// let (progress, err) = parser.parse(&arena, State::new("hello, world".as_bytes()), 0).unwrap_err(); /// assert_eq!(progress, Progress::NoProgress); /// assert_eq!(err, Problem::Other(Position::new(0))); /// ``` pub fn specialize_err<'a, F, P, T, X, Y>(map_error: F, parser: P) -> impl Parser<'a, T, Y> where F: Fn(X, Position) -> Y, P: Parser<'a, T, X>, Y: 'a, { move |a, state: State<'a>, min_indent| { let original_state = state.clone(); match parser.parse(a, state, min_indent) { Ok(t) => Ok(t), Err((p, error)) => Err((p, map_error(error, original_state.pos()))), } } } /// Transforms a possible error, like `map_err` in Rust. /// Similar to [`specialize_err`], but the error is arena allocated, and the /// mapping function receives a reference to the error. /// It has no effect if the inner parser succeeds. /// /// # Examples /// ``` /// # #![forbid(unused_imports)] /// # use roc_parse::state::State; /// # use crate::roc_parse::parser::{Parser, Progress, word, specialize_err_ref}; /// # use roc_region::all::Position; /// # use bumpalo::Bump; /// # #[derive(Debug, PartialEq)] /// # enum Problem { /// # NotFound(Position), /// # Other(Position), /// # } /// # let arena = Bump::new(); /// let parser = specialize_err_ref( /// |_prev_err, pos| Problem::Other(pos), /// word("bye", Problem::NotFound) /// ); /// /// let (progress, err) = parser.parse(&arena, State::new("hello, world".as_bytes()), 0).unwrap_err(); /// assert_eq!(progress, Progress::NoProgress); /// assert_eq!(err, Problem::Other(Position::new(0))); /// ``` pub fn specialize_err_ref<'a, F, P, T, X, Y>(map_error: F, parser: P) -> impl Parser<'a, T, Y> where F: Fn(&'a X, Position) -> Y, P: Parser<'a, T, X>, Y: 'a, X: 'a, { move |a, state: State<'a>, min_indent| { let original_state = state.clone(); match parser.parse(a, state, min_indent) { Ok(t) => Ok(t), Err((p, error)) => Err((p, map_error(a.alloc(error), original_state.pos()))), } } } /// Matches an entire `str` and moves the state's position forward if it succeeds. /// /// # Example /// /// ``` /// # #![forbid(unused_imports)] /// # use roc_parse::state::State; /// # use crate::roc_parse::parser::{Parser, Progress, word}; /// # use roc_region::all::Position; /// # use bumpalo::Bump; /// # #[derive(Debug, PartialEq)] /// # enum Problem { /// # NotFound(Position), /// # } /// # let arena = Bump::new(); /// let parser = word("hello", Problem::NotFound); /// /// // Success case /// let (progress, output, state) = parser.parse(&arena, State::new("hello, world".as_bytes()), 0).unwrap(); /// assert_eq!(progress, Progress::MadeProgress); /// assert_eq!(output, ()); /// assert_eq!(state.pos(), Position::new(5)); /// /// // Error case /// let (progress, problem) = parser.parse(&arena, State::new("bye, world".as_bytes()), 0).unwrap_err(); /// assert_eq!(progress, Progress::NoProgress); /// assert_eq!(problem, Problem::NotFound(Position::zero())); /// ``` pub fn word<'a, ToError, E>(word: &'static str, to_error: ToError) -> impl Parser<'a, (), E> where ToError: Fn(Position) -> E, E: 'a, { debug_assert!(!word.contains('\n')); move |_arena: &'a Bump, state: State<'a>, _min_indent: u32| { if state.bytes().starts_with(word.as_bytes()) { let state = state.advance(word.len()); Ok((MadeProgress, (), state)) } else { Err((NoProgress, to_error(state.pos()))) } } } /// Matches a single `u8` and moves the state's position forward if it succeeds. /// /// # Example /// /// ``` /// # #![forbid(unused_imports)] /// # use roc_parse::state::State; /// # use crate::roc_parse::parser::{Parser, Progress, byte}; /// # use roc_region::all::Position; /// # use bumpalo::Bump; /// # #[derive(Debug, PartialEq)] /// # enum Problem { /// # NotFound(Position), /// # } /// # let arena = Bump::new(); /// let parser = byte(b'h', Problem::NotFound); /// /// // Success case /// let (progress, output, state) = parser.parse(&arena, State::new("hello, world".as_bytes()), 0).unwrap(); /// assert_eq!(progress, Progress::MadeProgress); /// assert_eq!(output, ()); /// assert_eq!(state.pos(), Position::new(1)); /// /// // Error case /// let (progress, problem) = parser.parse(&arena, State::new("bye, world".as_bytes()), 0).unwrap_err(); /// assert_eq!(progress, Progress::NoProgress); /// assert_eq!(problem, Problem::NotFound(Position::zero())); /// ``` pub fn byte<'a, ToError, E>(byte_to_match: u8, to_error: ToError) -> impl Parser<'a, (), E> where ToError: Fn(Position) -> E, E: 'a, { debug_assert_ne!(byte_to_match, b'\n'); move |_arena: &'a Bump, state: State<'a>, _min_indent: u32| match state.bytes().first() { Some(x) if *x == byte_to_match => { let state = state.advance(1); Ok((MadeProgress, (), state)) } _ => Err((NoProgress, to_error(state.pos()))), } } /// Matches a single `u8` and moves the state's position forward if it succeeds. /// This parser will fail if it is a lower indentation level than it should be. /// /// # Example /// /// ``` /// # #![forbid(unused_imports)] /// # use roc_parse::state::State; /// # use crate::roc_parse::parser::{Parser, Progress, byte, byte_indent}; /// # use roc_region::all::Position; /// # use bumpalo::Bump; /// # #[derive(Debug, PartialEq)] /// # enum Problem { /// # NotFound(Position), /// # WrongIndentLevel(Position), /// # } /// # let arena = Bump::new(); /// let parser = byte_indent(b'h', Problem::WrongIndentLevel); /// /// // Success case /// let (progress, output, state) = parser.parse(&arena, State::new("hello, world".as_bytes()), 0).unwrap(); /// assert_eq!(progress, Progress::MadeProgress); /// assert_eq!(output, ()); /// assert_eq!(state.pos(), Position::new(1)); /// /// // Error case /// let state = State::new(" hello, world".as_bytes()); /// let _ = byte(b' ', Problem::NotFound).parse(&arena, state.clone(), 0).unwrap(); /// let (progress, problem) = parser.parse(&arena, state, 0).unwrap_err(); /// assert_eq!(progress, Progress::NoProgress); /// assert_eq!(problem, Problem::WrongIndentLevel(Position::zero())); /// ``` pub fn byte_indent<'a, ToError, E>(byte_to_match: u8, to_error: ToError) -> impl Parser<'a, (), E> where ToError: Fn(Position) -> E, E: 'a, { debug_assert_ne!(byte_to_match, b'\n'); move |_arena: &'a Bump, state: State<'a>, min_indent: u32| { if min_indent > state.column() { return Err((NoProgress, to_error(state.pos()))); } match state.bytes().first() { Some(x) if *x == byte_to_match => { let state = state.advance(1); Ok((MadeProgress, (), state)) } _ => Err((NoProgress, to_error(state.pos()))), } } } /// Matches two `u8` in a row. /// /// # Example /// /// ``` /// # #![forbid(unused_imports)] /// # use roc_parse::state::State; /// # use crate::roc_parse::parser::{Parser, Progress, two_bytes}; /// # use roc_region::all::Position; /// # use bumpalo::Bump; /// # #[derive(Debug, PartialEq)] /// # enum Problem { /// # NotFound(Position), /// # } /// # let arena = Bump::new(); /// let parser = two_bytes(b'h', b'e', Problem::NotFound); /// /// // Success case /// let (progress, output, state) = parser.parse(&arena, State::new("hello, world".as_bytes()), 0).unwrap(); /// assert_eq!(progress, Progress::MadeProgress); /// assert_eq!(output, ()); /// assert_eq!(state.pos(), Position::new(2)); /// /// // Error case /// let (progress, problem) = parser.parse(&arena, State::new("hi, world".as_bytes()), 0).unwrap_err(); /// assert_eq!(progress, Progress::NoProgress); /// assert_eq!(problem, Problem::NotFound(Position::zero())); /// ``` pub fn two_bytes<'a, ToError, E>( byte_1: u8, byte_2: u8, to_error: ToError, ) -> impl Parser<'a, (), E> where ToError: Fn(Position) -> E, E: 'a, { debug_assert_ne!(byte_1, b'\n'); debug_assert_ne!(byte_2, b'\n'); let needle = [byte_1, byte_2]; move |_arena: &'a Bump, state: State<'a>, _min_indent: u32| { if state.bytes().starts_with(&needle) { let state = state.advance(2); Ok((MadeProgress, (), state)) } else { Err((NoProgress, to_error(state.pos()))) } } } /// Matches three `u8` in a row. /// /// # Example /// /// ``` /// # #![forbid(unused_imports)] /// # use roc_parse::state::State; /// # use crate::roc_parse::parser::{Parser, Progress, three_bytes}; /// # use roc_region::all::Position; /// # use bumpalo::Bump; /// # #[derive(Debug, PartialEq)] /// # enum Problem { /// # NotFound(Position), /// # } /// # let arena = Bump::new(); /// let parser = three_bytes(b'h', b'e', b'l', Problem::NotFound); /// /// // Success case /// let (progress, output, state) = parser.parse(&arena, State::new("hello, world".as_bytes()), 0).unwrap(); /// assert_eq!(progress, Progress::MadeProgress); /// assert_eq!(output, ()); /// assert_eq!(state.pos(), Position::new(3)); /// /// // Error case /// let (progress, err) = parser.parse(&arena, State::new("hi, world".as_bytes()), 0).unwrap_err(); /// assert_eq!(progress, Progress::NoProgress); /// assert_eq!(err, Problem::NotFound(Position::zero())); /// ``` pub fn three_bytes<'a, ToError, E>( byte_1: u8, byte_2: u8, byte_3: u8, to_error: ToError, ) -> impl Parser<'a, (), E> where ToError: Fn(Position) -> E, E: 'a, { debug_assert_ne!(byte_1, b'\n'); debug_assert_ne!(byte_2, b'\n'); debug_assert_ne!(byte_3, b'\n'); let needle = [byte_1, byte_2, byte_3]; move |_arena: &'a Bump, state: State<'a>, _min_indent: u32| { if state.bytes().starts_with(&needle) { let state = state.advance(3); Ok((MadeProgress, (), state)) } else { Err((NoProgress, to_error(state.pos()))) } } } #[macro_export] macro_rules! byte_check_indent { ($byte_to_match:expr, $problem:expr, $min_indent:expr, $indent_problem:expr) => { $crate::parser::and( byte($byte_to_match, $problem), $crate::parser::check_indent($min_indent, $indent_problem), ) }; } /// transform the `Ok` result of a parser /// /// # Example /// /// ``` /// # #![forbid(unused_imports)] /// # use roc_parse::state::State; /// # use crate::roc_parse::parser::{Parser, Progress, word, map}; /// # use roc_region::all::Position; /// # use bumpalo::Bump; /// # #[derive(Debug, PartialEq)] /// # enum Problem { /// # NotFound(Position), /// # } /// # let arena = Bump::new(); /// let parser = map( /// word("hello", Problem::NotFound), /// |_output| "new output!" /// ); /// /// // Success case /// let (progress, output, state) = parser.parse(&arena, State::new("hello, world".as_bytes()), 0).unwrap(); /// assert_eq!(progress, Progress::MadeProgress); /// assert_eq!(output, "new output!"); /// assert_eq!(state.pos(), Position::new(5)); /// /// // Error case /// let (progress, err) = parser.parse(&arena, State::new("bye, world".as_bytes()), 0).unwrap_err(); /// assert_eq!(progress, Progress::NoProgress); /// assert_eq!(err, Problem::NotFound(Position::zero())); /// ``` pub fn map<'a, Output, MappedOutput, E: 'a>( parser: impl Parser<'a, Output, E>, transform: impl Fn(Output) -> MappedOutput, ) -> impl Parser<'a, MappedOutput, E> { move |arena, state, min_indent| { parser .parse(arena, state, min_indent) .map(|(progress, output, next_state)| (progress, transform(output), next_state)) } } /// Applies the parser as many times as possible. /// This parser will only fail if the given parser makes partial progress. pub fn zero_or_more<'a, Output, E: 'a>( parser: impl Parser<'a, Output, E>, ) -> impl Parser<'a, bumpalo::collections::Vec<'a, Output>, E> { move |arena, state: State<'a>, min_indent: u32| { let original_state = state.clone(); let start_bytes_len = state.bytes().len(); match parser.parse(arena, state, min_indent) { Ok((_, first_output, next_state)) => { let mut state = next_state; let mut buf = Vec::with_capacity_in(1, arena); buf.push(first_output); loop { let old_state = state.clone(); match parser.parse(arena, state, min_indent) { Ok((_, next_output, next_state)) => { state = next_state; buf.push(next_output); } Err((fail_progress, fail)) => { match fail_progress { MadeProgress => { // made progress on an element and then failed; that's an error return Err((MadeProgress, fail)); } NoProgress => { // the next element failed with no progress // report whether we made progress before let progress = Progress::from_lengths( start_bytes_len, old_state.bytes().len(), ); return Ok((progress, buf, old_state)); } } } } } } Err((fail_progress, fail)) => { match fail_progress { MadeProgress => { // made progress on an element and then failed; that's an error Err((MadeProgress, fail)) } NoProgress => { // the first element failed (with no progress), but that's OK // because we only need to parse 0 elements Ok((NoProgress, Vec::new_in(arena), original_state)) } } } } } } /// Creates a parser that matches one or more times. /// Will fail if the given parser fails to match or matches partially. /// /// # Example /// /// ``` /// # #![forbid(unused_imports)] /// # use roc_parse::state::State; /// # use crate::roc_parse::parser::{Parser, Progress, word, one_or_more}; /// # use roc_region::all::Position; /// # use bumpalo::Bump; /// # #[derive(Debug, PartialEq)] /// # enum Problem { /// # NotFound(Position), /// # } /// # let arena = Bump::new(); /// # fn foo<'a>(arena: &'a Bump) { /// let parser = one_or_more( /// word("hello, ", Problem::NotFound), /// Problem::NotFound /// ); /// /// // Success case /// let (progress, output, state) = parser.parse(&arena, State::new("hello, hello, world".as_bytes()), 0).unwrap(); /// assert_eq!(progress, Progress::MadeProgress); /// assert_eq!(output, bumpalo::vec![in &arena; (),()]); /// assert_eq!(state.pos(), Position::new(14)); /// /// // Error case /// let (progress, err) = parser.parse(&arena, State::new("bye, world".as_bytes()), 0).unwrap_err(); /// assert_eq!(progress, Progress::NoProgress); /// assert_eq!(err, Problem::NotFound(Position::zero())); /// # } /// # foo(&arena); /// ``` pub fn one_or_more<'a, Output, E: 'a>( parser: impl Parser<'a, Output, E>, to_error: impl Fn(Position) -> E, ) -> impl Parser<'a, bumpalo::collections::Vec<'a, Output>, E> { move |arena, state: State<'a>, min_indent: u32| match parser.parse( arena, state.clone(), min_indent, ) { Ok((_, first_output, next_state)) => { let mut state = next_state; let mut buf = Vec::with_capacity_in(1, arena); buf.push(first_output); loop { let old_state = state.clone(); match parser.parse(arena, state, min_indent) { Ok((_, next_output, next_state)) => { state = next_state; buf.push(next_output); } Err((NoProgress, _)) => { return Ok((MadeProgress, buf, old_state)); } Err((MadeProgress, fail)) => { return Err((MadeProgress, fail)); } } } } Err((progress, _)) => Err((progress, to_error(state.pos()))), } } /// Creates a parser that debug prints the result of parsing. /// It doesn't change the given parser at all, /// useful for inspecting a parser during development. /// /// # Example /// /// ``` /// # #![forbid(unused_imports)] /// # use roc_parse::state::State; /// # use crate::roc_parse::parser::{Parser, Progress, word}; /// # use roc_region::all::Position; /// # use roc_parse::debug; /// # use bumpalo::Bump; /// # #[derive(Debug, PartialEq)] /// # enum Problem { /// # NotFound(Position), /// # } /// # let arena = Bump::new(); /// # fn foo<'a>(arena: &'a Bump) { /// let parser = debug!( /// word("hello", Problem::NotFound) /// ); /// let (progress, output, state) = parser.parse(&arena, State::new("hello, world".as_bytes()), 0).unwrap(); /// assert_eq!(progress, Progress::MadeProgress); /// assert_eq!(output, ()); /// assert_eq!(state.pos().offset, 5); /// # } /// # foo(&arena); /// ``` #[macro_export] macro_rules! debug { ($parser:expr) => { move |arena, state: $crate::state::State<'a>, min_indent: u32| { dbg!($parser.parse(arena, state, min_indent)) } }; } /// Matches either of the two given parsers. /// If the first parser succeeds, its result is used, /// otherwise, the second parser's result is used. /// /// # Example /// /// ``` /// # #![forbid(unused_imports)] /// # use roc_parse::state::State; /// # use crate::roc_parse::parser::{Parser, Progress, word, either, Either}; /// # use roc_region::all::Position; /// # use bumpalo::Bump; /// # #[derive(Debug, PartialEq)] /// # enum Problem { /// # NotFound(Position), /// # } /// # let arena = Bump::new(); /// # fn foo<'a>(arena: &'a Bump) { /// let parser = either( /// word("hello", Problem::NotFound), /// word("bye", Problem::NotFound) /// ); /// /// // Success cases /// let (progress, output, state) = parser.parse(&arena, State::new("hello, world".as_bytes()), 0).unwrap(); /// assert_eq!(progress, Progress::MadeProgress); /// assert_eq!(output, Either::First(())); /// assert_eq!(state.pos().offset, 5); /// /// let (progress, output, state) = parser.parse(&arena, State::new("bye, world".as_bytes()), 0).unwrap(); /// assert_eq!(progress, Progress::MadeProgress); /// assert_eq!(output, Either::Second(())); /// assert_eq!(state.pos().offset, 3); /// /// // Error case /// let (progress, err) = parser.parse(&arena, State::new("later, world".as_bytes()), 0).unwrap_err(); /// assert_eq!(progress, Progress::NoProgress); /// assert_eq!(err, Problem::NotFound(Position::zero())); /// # } /// # foo(&arena); /// ``` pub fn either<'a, OutputLeft, OutputRight, E: 'a>( p1: impl Parser<'a, OutputLeft, E>, p2: impl Parser<'a, OutputRight, E>, ) -> impl Parser<'a, Either, E> { move |arena: &'a bumpalo::Bump, state: crate::state::State<'a>, min_indent: u32| { let original_state = state.clone(); match p1.parse(arena, state, min_indent) { Ok((progress, output, state)) => { Ok((progress, crate::parser::Either::First(output), state)) } Err((NoProgress, _)) => match p2.parse(arena, original_state.clone(), min_indent) { Ok((progress, output, state)) => { Ok((progress, crate::parser::Either::Second(output), state)) } Err((progress, fail)) => Err((progress, fail)), }, Err((MadeProgress, fail)) => Err((MadeProgress, fail)), } } } /// Given three parsers, parse them all but ignore the output of the first and last one. /// Useful for parsing things between two braces (e.g. parentheses). /// /// If any of the three parsers error, this will error. /// /// # Example /// ``` /// # #![forbid(unused_imports)] /// # use roc_parse::state::State; /// # use crate::roc_parse::parser::{Parser, Progress, word, byte, between}; /// # use roc_region::all::Position; /// # use bumpalo::Bump; /// # #[derive(Debug, PartialEq)] /// # enum Problem { /// # NotFound(Position), /// # } /// # let arena = Bump::new(); /// # fn foo<'a>(arena: &'a Bump) { /// let parser = between( /// byte(b'(', Problem::NotFound), /// word("hello", Problem::NotFound), /// byte(b')', Problem::NotFound) /// ); /// let (progress, output, state) = parser.parse(&arena, State::new("(hello), world".as_bytes()), 0).unwrap(); /// assert_eq!(progress, Progress::MadeProgress); /// assert_eq!(output, ()); /// assert_eq!(state.pos().offset, 7); /// # } /// # foo(&arena); /// ``` pub fn between<'a, Before, Inner, After, Err: 'a>( opening_brace: impl Parser<'a, Before, Err>, inner: impl Parser<'a, Inner, Err>, closing_brace: impl Parser<'a, After, Err>, ) -> impl Parser<'a, Inner, Err> { skip_first(opening_brace, skip_second(inner, closing_brace)) } /// Maps/transforms the `Ok` result of parsing using the given function. /// Similar to [`map`], but the transform function also takes a bump allocator. /// /// # Example /// /// ``` /// # #![forbid(unused_imports)] /// # use roc_parse::state::State; /// # use crate::roc_parse::parser::{Parser, Progress, word, map_with_arena}; /// # use roc_region::all::Position; /// # use bumpalo::Bump; /// # #[derive(Debug, PartialEq)] /// # enum Problem { /// # NotFound(Position), /// # } /// # let arena = Bump::new(); /// let parser = map_with_arena( /// word("hello", Problem::NotFound), /// |_arena, _output| "new output!" /// ); /// /// // Success case /// let (progress, output, state) = parser.parse(&arena, State::new("hello, world".as_bytes()), 0).unwrap(); /// assert_eq!(progress, Progress::MadeProgress); /// assert_eq!(output, "new output!"); /// assert_eq!(state.pos(), Position::new(5)); /// /// // Error Case /// let (progress, err) = parser.parse(&arena, State::new("bye, world".as_bytes()), 0).unwrap_err(); /// assert_eq!(progress, Progress::NoProgress); /// assert_eq!(err, Problem::NotFound(Position::zero())); /// ``` #[inline(always)] pub fn map_with_arena<'a, P, F, Before, After, E>( parser: P, transform: F, ) -> impl Parser<'a, After, E> where P: Parser<'a, Before, E>, F: Fn(&'a Bump, Before) -> After, E: 'a, { move |arena, state, min_indent| { parser .parse(arena, state, min_indent) .map(|(progress, output, next_state)| (progress, transform(arena, output), next_state)) } } /// Creates a new parser that does not progress but still forwards the output of /// the given parser if it succeeds. /// /// # Example /// /// ``` /// # #![forbid(unused_imports)] /// # use roc_parse::state::State; /// # use crate::roc_parse::parser::{Parser, Progress, word, backtrackable}; /// # use roc_region::all::Position; /// # use bumpalo::Bump; /// # #[derive(Debug, PartialEq)] /// # enum Problem { /// # NotFound(Position), /// # } /// # let arena = Bump::new(); /// let parser = backtrackable( /// word("hello", Problem::NotFound), /// ); /// /// // Success case /// let (progress, output, state) = parser.parse(&arena, State::new("hello, world".as_bytes()), 0).unwrap(); /// assert_eq!(progress, Progress::NoProgress); /// assert_eq!(output, ()); /// assert_eq!(state.pos().offset, 5); /// /// // Error Case /// let (progress, err) = parser.parse(&arena, State::new("bye, world".as_bytes()), 0).unwrap_err(); /// assert_eq!(progress, Progress::NoProgress); /// assert_eq!(err, Problem::NotFound(Position::zero())); /// ``` pub fn backtrackable<'a, P, Val, Error>(parser: P) -> impl Parser<'a, Val, Error> where P: Parser<'a, Val, Error>, Error: 'a, { move |arena: &'a Bump, state: State<'a>, min_indent: u32| match parser .parse(arena, state, min_indent) { Ok((_, a, s1)) => Ok((NoProgress, a, s1)), Err((_, f)) => Err((NoProgress, f)), } }