diff --git a/compiler/parse/src/header.rs b/compiler/parse/src/header.rs index e289e15179..3bd83988ab 100644 --- a/compiler/parse/src/header.rs +++ b/compiler/parse/src/header.rs @@ -2,13 +2,10 @@ use crate::ast::{CommentOrNewline, Spaceable, StrLiteral, TypeAnnotation}; use crate::blankspace::space0; use crate::ident::lowercase_ident; use crate::parser::{ - ascii_char, optional, peek_utf8_char, specialize, unexpected_eof, Either, ParseResult, Parser, - Progress, Progress::*, State, SyntaxError, + ascii_char, optional, specialize, Either, Parser, Progress, Progress::*, State, SyntaxError, }; use crate::string_literal; -use bumpalo::collections::String; use bumpalo::collections::Vec; -use bumpalo::Bump; use inlinable_string::InlinableString; use roc_region::all::Loc; @@ -274,24 +271,22 @@ pub fn package_entry<'a>() -> impl Parser<'a, PackageEntry<'a>, SyntaxError<'a>> } pub fn package_or_path<'a>() -> impl Parser<'a, PackageOrPath<'a>, SyntaxError<'a>> { - map!( - either!( + one_of![ + map!( specialize( |e, r, c| SyntaxError::Expr(crate::parser::EExpr::Str(e, r, c)), string_literal::parse() ), + PackageOrPath::Path + ), + map!( and!( package_name(), skip_first!(one_or_more!(ascii_char(b' ')), package_version()) - ) - ), - |answer| { - match answer { - Either::First(str_literal) => PackageOrPath::Path(str_literal), - Either::Second((name, version)) => PackageOrPath::Package(name, version), - } - } - ) + ), + |(name, version)| { PackageOrPath::Package(name, version) } + ) + ] } fn package_version<'a>() -> impl Parser<'a, Version<'a>, SyntaxError<'a>> { @@ -300,45 +295,57 @@ fn package_version<'a>() -> impl Parser<'a, Version<'a>, SyntaxError<'a>> { #[inline(always)] pub fn package_name<'a>() -> impl Parser<'a, PackageName<'a>, SyntaxError<'a>> { + use encode_unicode::CharExt; // e.g. rtfeldman/blah // // Package names and accounts can be capitalized and can contain dashes. // They cannot contain underscores or other special characters. // They must be ASCII. - map!( - and!( - parse_package_part, - skip_first!(ascii_char(b'/'), parse_package_part) - ), - |(account, pkg)| { PackageName { account, pkg } } - ) -} + |_, mut state: State<'a>| match chomp_package_part(state.bytes) { + Err(progress) => Err((progress, SyntaxError::ConditionFailed, state)), + Ok(account) => { + let mut chomped = account.len(); + if let Ok(('/', width)) = char::from_utf8_slice_start(&state.bytes[chomped..]) { + chomped += width; + match chomp_package_part(&state.bytes[chomped..]) { + Err(progress) => Err((progress, SyntaxError::ConditionFailed, state)), + Ok(pkg) => { + chomped += pkg.len(); -fn parse_package_part<'a>( - arena: &'a Bump, - mut state: State<'a>, -) -> ParseResult<'a, &'a str, SyntaxError<'a>> { - let mut part_buf = String::new_in(arena); // The current "part" (parts are dot-separated.) + state.column += chomped as u16; + state.bytes = &state.bytes[chomped..]; - while !state.bytes.is_empty() { - match peek_utf8_char(&state) { - Ok((ch, bytes_parsed)) => { - if ch == '-' || ch.is_ascii_alphanumeric() { - part_buf.push(ch); - - state = state.advance_without_indenting(bytes_parsed)?; - } else { - let progress = Progress::progress_when(!part_buf.is_empty()); - return Ok((progress, part_buf.into_bump_str(), state)); + let value = PackageName { account, pkg }; + Ok((MadeProgress, value, state)) + } } - } - Err(reason) => { - let progress = Progress::progress_when(!part_buf.is_empty()); - return state.fail(arena, progress, reason); + } else { + Err((MadeProgress, SyntaxError::ConditionFailed, state)) } } } - - Err(unexpected_eof(arena, state, 0)) +} + +fn chomp_package_part<'a>(buffer: &'a [u8]) -> Result<&'a str, Progress> { + use encode_unicode::CharExt; + + let mut chomped = 0; + + while let Ok((ch, width)) = char::from_utf8_slice_start(&buffer[chomped..]) { + if ch == '-' || ch.is_ascii_alphanumeric() { + chomped += width; + } else { + // we're done + break; + } + } + + if chomped == 0 { + Err(Progress::NoProgress) + } else { + let name = unsafe { std::str::from_utf8_unchecked(&buffer[..chomped]) }; + + Ok(name) + } }