Merge branch 'trunk' of github.com:rtfeldman/roc into editor-comments

This commit is contained in:
Anton-4 2022-01-01 11:10:54 +01:00
commit f941e30b86
271 changed files with 15287 additions and 8370 deletions

View file

@ -1,12 +1,70 @@
use std::fmt::Debug;
use crate::header::{AppHeader, ImportsEntry, InterfaceHeader, PlatformHeader, TypedIdent};
use crate::header::{AppHeader, InterfaceHeader, PlatformHeader};
use crate::ident::Ident;
use bumpalo::collections::{String, Vec};
use bumpalo::Bump;
use roc_module::called_via::{BinOp, CalledVia, UnaryOp};
use roc_region::all::{Loc, Position, Region};
#[derive(Debug)]
pub struct Spaces<'a, T> {
pub before: &'a [CommentOrNewline<'a>],
pub item: T,
pub after: &'a [CommentOrNewline<'a>],
}
#[derive(Copy, Clone, PartialEq)]
pub enum Spaced<'a, T> {
Item(T),
// Spaces
SpaceBefore(&'a Spaced<'a, T>, &'a [CommentOrNewline<'a>]),
SpaceAfter(&'a Spaced<'a, T>, &'a [CommentOrNewline<'a>]),
}
impl<'a, T: Debug> Debug for Spaced<'a, T> {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
match self {
Self::Item(item) => item.fmt(f),
Self::SpaceBefore(item, space) => f
.debug_tuple("SpaceBefore")
.field(item)
.field(space)
.finish(),
Self::SpaceAfter(item, space) => f
.debug_tuple("SpaceAfter")
.field(item)
.field(space)
.finish(),
}
}
}
pub trait ExtractSpaces<'a>: Sized + Copy {
type Item;
fn extract_spaces(&self) -> Spaces<'a, Self::Item>;
}
impl<'a, T: ExtractSpaces<'a>> ExtractSpaces<'a> for &'a T {
type Item = T::Item;
fn extract_spaces(&self) -> Spaces<'a, Self::Item> {
(*self).extract_spaces()
}
}
impl<'a, T: ExtractSpaces<'a>> ExtractSpaces<'a> for Loc<T> {
type Item = T::Item;
fn extract_spaces(&self) -> Spaces<'a, Self::Item> {
let spaces = self.value.extract_spaces();
Spaces {
before: spaces.before,
item: spaces.item,
after: spaces.after,
}
}
}
#[derive(Clone, Debug, PartialEq)]
pub enum Module<'a> {
Interface { header: InterfaceHeader<'a> },
@ -167,6 +225,12 @@ pub struct PrecedenceConflict<'a> {
pub expr: &'a Loc<Expr<'a>>,
}
#[derive(Debug, Clone, Copy, PartialEq)]
pub struct AliasHeader<'a> {
pub name: Loc<&'a str>,
pub vars: &'a [Loc<Pattern<'a>>],
}
#[derive(Debug, Clone, Copy, PartialEq)]
pub enum Def<'a> {
// TODO in canonicalization, validate the pattern; only certain patterns
@ -178,8 +242,7 @@ pub enum Def<'a> {
///
/// Foo : Bar Baz
Alias {
name: Loc<&'a str>,
vars: &'a [Loc<Pattern<'a>>],
header: AliasHeader<'a>,
ann: Loc<TypeAnnotation<'a>>,
},
@ -207,6 +270,17 @@ pub enum Def<'a> {
NotYetImplemented(&'static str),
}
impl<'a> Def<'a> {
pub fn unroll_spaces_before(&self) -> (&'a [CommentOrNewline<'a>], &Def) {
let (spaces, def): (&'a [_], &Def) = match self {
Def::SpaceBefore(def, spaces) => (spaces, def),
def => (&[], def),
};
debug_assert!(!matches!(def, Def::SpaceBefore(_, _)));
(spaces, def)
}
}
#[derive(Debug, Copy, Clone, PartialEq)]
pub enum TypeAnnotation<'a> {
/// A function. The types of its arguments, then the type of its return value.
@ -222,7 +296,7 @@ pub enum TypeAnnotation<'a> {
As(
&'a Loc<TypeAnnotation<'a>>,
&'a [CommentOrNewline<'a>],
&'a Loc<TypeAnnotation<'a>>,
AliasHeader<'a>,
),
Record {
@ -341,7 +415,7 @@ pub enum Pattern<'a> {
PrivateTag(&'a str),
Apply(&'a Loc<Pattern<'a>>, &'a [Loc<Pattern<'a>>]),
/// This is Loc<Pattern> rather than Loc<str> so we can record comments
/// This is Located<Pattern> rather than Located<str> so we can record comments
/// around the destructured names, e.g. { x ### x does stuff ###, y }
/// In practice, these patterns will always be Identifier
RecordDestructure(Collection<'a, Loc<Pattern<'a>>>),
@ -649,6 +723,15 @@ pub trait Spaceable<'a> {
}
}
impl<'a, T> Spaceable<'a> for Spaced<'a, T> {
fn before(&'a self, spaces: &'a [CommentOrNewline<'a>]) -> Self {
Spaced::SpaceBefore(self, spaces)
}
fn after(&'a self, spaces: &'a [CommentOrNewline<'a>]) -> Self {
Spaced::SpaceAfter(self, spaces)
}
}
impl<'a> Spaceable<'a> for Expr<'a> {
fn before(&'a self, spaces: &'a [CommentOrNewline<'a>]) -> Self {
Expr::SpaceBefore(self, spaces)
@ -676,24 +759,6 @@ impl<'a> Spaceable<'a> for TypeAnnotation<'a> {
}
}
impl<'a> Spaceable<'a> for ImportsEntry<'a> {
fn before(&'a self, spaces: &'a [CommentOrNewline<'a>]) -> Self {
ImportsEntry::SpaceBefore(self, spaces)
}
fn after(&'a self, spaces: &'a [CommentOrNewline<'a>]) -> Self {
ImportsEntry::SpaceAfter(self, spaces)
}
}
impl<'a> Spaceable<'a> for TypedIdent<'a> {
fn before(&'a self, spaces: &'a [CommentOrNewline<'a>]) -> Self {
TypedIdent::SpaceBefore(self, spaces)
}
fn after(&'a self, spaces: &'a [CommentOrNewline<'a>]) -> Self {
TypedIdent::SpaceAfter(self, spaces)
}
}
impl<'a, Val> Spaceable<'a> for AssignedField<'a, Val> {
fn before(&'a self, spaces: &'a [CommentOrNewline<'a>]) -> Self {
AssignedField::SpaceBefore(self, spaces)
@ -735,4 +800,122 @@ impl<'a> Expr<'a> {
value: self,
}
}
pub fn is_tag(&self) -> bool {
matches!(self, Expr::GlobalTag(_) | Expr::PrivateTag(_))
}
}
macro_rules! impl_extract_spaces {
($t:ident $(< $($generic_args:ident),* >)?) => {
impl<'a, $($($generic_args: Copy),*)?> ExtractSpaces<'a> for $t<'a, $($($generic_args),*)?> {
type Item = Self;
fn extract_spaces(&self) -> Spaces<'a, Self::Item> {
match self {
$t::SpaceBefore(item, before) => {
match item {
$t::SpaceBefore(_, _) => todo!(),
$t::SpaceAfter(item, after) => {
Spaces {
before,
item: **item,
after,
}
}
_ => {
Spaces {
before,
item: **item,
after: &[],
}
}
}
},
$t::SpaceAfter(item, after) => {
match item {
$t::SpaceBefore(item, before) => {
Spaces {
before,
item: **item,
after,
}
}
$t::SpaceAfter(_, _) => todo!(),
_ => {
Spaces {
before: &[],
item: **item,
after,
}
}
}
},
_ => {
Spaces {
before: &[],
item: *self,
after: &[],
}
}
}
}
}
};
}
impl_extract_spaces!(Expr);
impl_extract_spaces!(Tag);
impl_extract_spaces!(AssignedField<T>);
impl<'a, T: Copy> ExtractSpaces<'a> for Spaced<'a, T> {
type Item = T;
fn extract_spaces(&self) -> Spaces<'a, T> {
match self {
Spaced::SpaceBefore(item, before) => match item {
Spaced::SpaceBefore(_, _) => todo!(),
Spaced::SpaceAfter(item, after) => {
if let Spaced::Item(item) = item {
Spaces {
before,
item: *item,
after,
}
} else {
todo!();
}
}
Spaced::Item(item) => Spaces {
before,
item: *item,
after: &[],
},
},
Spaced::SpaceAfter(item, after) => match item {
Spaced::SpaceBefore(item, before) => {
if let Spaced::Item(item) = item {
Spaces {
before,
item: *item,
after,
}
} else {
todo!();
}
}
Spaced::SpaceAfter(_, _) => todo!(),
Spaced::Item(item) => Spaces {
before: &[],
item: *item,
after,
},
},
Spaced::Item(item) => Spaces {
before: &[],
item: *item,
after: &[],
},
}
}
}

View file

@ -1,23 +1,23 @@
use crate::ast::CommentOrNewline;
use crate::ast::Spaceable;
use crate::parser::{
self, and, backtrackable, BadInputError, Col, Parser, Progress::*, Row, State,
};
use crate::parser::{self, and, backtrackable, BadInputError, Parser, Progress::*};
use crate::state::State;
use bumpalo::collections::vec::Vec;
use bumpalo::Bump;
use roc_region::all::Located;
use roc_region::all::Loc;
use roc_region::all::Position;
pub fn space0_around_ee<'a, P, S, E>(
parser: P,
min_indent: u16,
space_problem: fn(BadInputError, Row, Col) -> E,
indent_before_problem: fn(Row, Col) -> E,
indent_after_problem: fn(Row, Col) -> E,
) -> impl Parser<'a, Located<S>, E>
space_problem: fn(BadInputError, Position) -> E,
indent_before_problem: fn(Position) -> E,
indent_after_problem: fn(Position) -> E,
) -> impl Parser<'a, Loc<S>, E>
where
S: Spaceable<'a>,
S: 'a,
P: Parser<'a, Located<S>, E>,
P: Parser<'a, Loc<S>, E>,
P: 'a,
E: 'a,
{
@ -36,14 +36,14 @@ where
pub fn space0_before_optional_after<'a, P, S, E>(
parser: P,
min_indent: u16,
space_problem: fn(BadInputError, Row, Col) -> E,
indent_before_problem: fn(Row, Col) -> E,
indent_after_problem: fn(Row, Col) -> E,
) -> impl Parser<'a, Located<S>, E>
space_problem: fn(BadInputError, Position) -> E,
indent_before_problem: fn(Position) -> E,
indent_after_problem: fn(Position) -> E,
) -> impl Parser<'a, Loc<S>, E>
where
S: Spaceable<'a>,
S: 'a,
P: Parser<'a, Located<S>, E>,
P: Parser<'a, Loc<S>, E>,
P: 'a,
E: 'a,
{
@ -66,9 +66,9 @@ fn spaces_around_help<'a, S>(
arena: &'a Bump,
tuples: (
&'a [CommentOrNewline<'a>],
(Located<S>, &'a [CommentOrNewline<'a>]),
(Loc<S>, &'a [CommentOrNewline<'a>]),
),
) -> Located<S>
) -> Loc<S>
where
S: Spaceable<'a>,
S: 'a,
@ -101,19 +101,19 @@ where
pub fn space0_before_e<'a, P, S, E>(
parser: P,
min_indent: u16,
space_problem: fn(BadInputError, Row, Col) -> E,
indent_problem: fn(Row, Col) -> E,
) -> impl Parser<'a, Located<S>, E>
space_problem: fn(BadInputError, Position) -> E,
indent_problem: fn(Position) -> E,
) -> impl Parser<'a, Loc<S>, E>
where
S: Spaceable<'a>,
S: 'a,
P: Parser<'a, Located<S>, E>,
P: Parser<'a, Loc<S>, E>,
P: 'a,
E: 'a,
{
parser::map_with_arena(
and!(space0_e(min_indent, space_problem, indent_problem), parser),
|arena: &'a Bump, (space_list, loc_expr): (&'a [CommentOrNewline<'a>], Located<S>)| {
|arena: &'a Bump, (space_list, loc_expr): (&'a [CommentOrNewline<'a>], Loc<S>)| {
if space_list.is_empty() {
loc_expr
} else {
@ -128,19 +128,19 @@ where
pub fn space0_after_e<'a, P, S, E>(
parser: P,
min_indent: u16,
space_problem: fn(BadInputError, Row, Col) -> E,
indent_problem: fn(Row, Col) -> E,
) -> impl Parser<'a, Located<S>, E>
space_problem: fn(BadInputError, Position) -> E,
indent_problem: fn(Position) -> E,
) -> impl Parser<'a, Loc<S>, E>
where
S: Spaceable<'a>,
S: 'a,
P: Parser<'a, Located<S>, E>,
P: Parser<'a, Loc<S>, E>,
P: 'a,
E: 'a,
{
parser::map_with_arena(
and!(parser, space0_e(min_indent, space_problem, indent_problem)),
|arena: &'a Bump, (loc_expr, space_list): (Located<S>, &'a [CommentOrNewline<'a>])| {
|arena: &'a Bump, (loc_expr, space_list): (Loc<S>, &'a [CommentOrNewline<'a>])| {
if space_list.is_empty() {
loc_expr
} else {
@ -154,24 +154,24 @@ where
pub fn check_indent<'a, E>(
min_indent: u16,
indent_problem: fn(Row, Col) -> E,
indent_problem: fn(Position) -> E,
) -> impl Parser<'a, (), E>
where
E: 'a,
{
move |_, state: State<'a>| {
if state.column >= min_indent {
if state.pos.column >= min_indent {
Ok((NoProgress, (), state))
} else {
Err((NoProgress, indent_problem(state.line, state.column), state))
Err((NoProgress, indent_problem(state.pos), state))
}
}
}
pub fn space0_e<'a, E>(
min_indent: u16,
space_problem: fn(BadInputError, Row, Col) -> E,
indent_problem: fn(Row, Col) -> E,
space_problem: fn(BadInputError, Position) -> E,
indent_problem: fn(Position) -> E,
) -> impl Parser<'a, &'a [CommentOrNewline<'a>], E>
where
E: 'a,
@ -182,8 +182,8 @@ where
#[inline(always)]
fn spaces_help_help<'a, E>(
min_indent: u16,
space_problem: fn(BadInputError, Row, Col) -> E,
indent_problem: fn(Row, Col) -> E,
space_problem: fn(BadInputError, Position) -> E,
indent_problem: fn(Position) -> E,
) -> impl Parser<'a, &'a [CommentOrNewline<'a>], E>
where
E: 'a,
@ -193,48 +193,44 @@ where
move |arena, mut state: State<'a>| {
let comments_and_newlines = Vec::new_in(arena);
match eat_spaces(state.bytes, state.line, state.column, comments_and_newlines) {
HasTab { row, col } => {
match eat_spaces(state.bytes(), state.pos, comments_and_newlines) {
HasTab(pos) => {
// there was a tab character
let mut state = state;
state.pos = pos;
// TODO: it _seems_ like if we're changing the line/column, we should also be
// advancing the state by the corresponding number of bytes.
// Not doing this is likely a bug!
// state = state.advance(<something>);
Err((
MadeProgress,
space_problem(BadInputError::HasTab, row, col),
State {
line: row,
column: col,
..state
},
space_problem(BadInputError::HasTab, pos),
state,
))
}
Good {
row,
col,
pos,
bytes,
comments_and_newlines,
} => {
if bytes == state.bytes {
if bytes == state.bytes() {
Ok((NoProgress, &[] as &[_], state))
} else if state.line != row {
} else if state.pos.line != pos.line {
// we parsed at least one newline
state.indent_col = col;
state.indent_column = pos.column;
if col >= min_indent {
state.line = row;
state.column = col;
state.bytes = bytes;
if pos.column >= min_indent {
state.pos = pos;
state = state.advance(state.bytes().len() - bytes.len());
Ok((MadeProgress, comments_and_newlines.into_bump_slice(), state))
} else {
Err((
MadeProgress,
indent_problem(state.line, state.column),
state,
))
Err((MadeProgress, indent_problem(state.pos), state))
}
} else {
state.column = col;
state.bytes = bytes;
state.pos.column = pos.column;
state = state.advance(state.bytes().len() - bytes.len());
Ok((MadeProgress, comments_and_newlines.into_bump_slice(), state))
}
@ -245,21 +241,16 @@ where
enum SpaceState<'a> {
Good {
row: Row,
col: Col,
pos: Position,
bytes: &'a [u8],
comments_and_newlines: Vec<'a, CommentOrNewline<'a>>,
},
HasTab {
row: Row,
col: Col,
},
HasTab(Position),
}
fn eat_spaces<'a>(
mut bytes: &'a [u8],
mut row: Row,
mut col: Col,
mut pos: Position,
mut comments_and_newlines: Vec<'a, CommentOrNewline<'a>>,
) -> SpaceState<'a> {
use SpaceState::*;
@ -268,30 +259,30 @@ fn eat_spaces<'a>(
match c {
b' ' => {
bytes = &bytes[1..];
col += 1;
pos.column += 1;
}
b'\n' => {
bytes = &bytes[1..];
row += 1;
col = 0;
pos.line += 1;
pos.column = 0;
comments_and_newlines.push(CommentOrNewline::Newline);
}
b'\r' => {
bytes = &bytes[1..];
}
b'\t' => {
return HasTab { row, col };
return HasTab(pos);
}
b'#' => {
return eat_line_comment(&bytes[1..], row, col + 1, comments_and_newlines);
pos.column += 1;
return eat_line_comment(&bytes[1..], pos, comments_and_newlines);
}
_ => break,
}
}
Good {
row,
col,
pos,
bytes,
comments_and_newlines,
}
@ -299,8 +290,7 @@ fn eat_spaces<'a>(
fn eat_line_comment<'a>(
mut bytes: &'a [u8],
row: Row,
mut col: Col,
mut pos: Position,
mut comments_and_newlines: Vec<'a, CommentOrNewline<'a>>,
) -> SpaceState<'a> {
use SpaceState::*;
@ -309,7 +299,7 @@ fn eat_line_comment<'a>(
match bytes.get(1) {
Some(b' ') => {
bytes = &bytes[2..];
col += 2;
pos.column += 2;
true
}
@ -318,16 +308,17 @@ fn eat_line_comment<'a>(
bytes = &bytes[2..];
comments_and_newlines.push(CommentOrNewline::DocComment(""));
return eat_spaces(bytes, row + 1, 0, comments_and_newlines);
pos.line += 1;
pos.column = 0;
return eat_spaces(bytes, pos, comments_and_newlines);
}
None => {
// consume the second #
col += 1;
pos.column += 1;
bytes = &bytes[1..];
return Good {
row,
col,
pos,
bytes,
comments_and_newlines,
};
@ -340,13 +331,13 @@ fn eat_line_comment<'a>(
};
let initial = bytes;
let initial_col = col;
let initial_column = pos.column;
for c in bytes {
match c {
b'\t' => return HasTab { row, col },
b'\t' => return HasTab(pos),
b'\n' => {
let delta = (col - initial_col) as usize;
let delta = (pos.column - initial_column) as usize;
let comment = unsafe { std::str::from_utf8_unchecked(&initial[..delta]) };
if is_doc_comment {
@ -354,17 +345,19 @@ fn eat_line_comment<'a>(
} else {
comments_and_newlines.push(CommentOrNewline::LineComment(comment));
}
return eat_spaces(&bytes[1..], row + 1, 0, comments_and_newlines);
pos.line += 1;
pos.column = 0;
return eat_spaces(&bytes[1..], pos, comments_and_newlines);
}
_ => {
bytes = &bytes[1..];
col += 1;
pos.column += 1;
}
}
}
// We made it to the end of the bytes. This means there's a comment without a trailing newline.
let delta = (col - initial_col) as usize;
let delta = (pos.column - initial_column) as usize;
let comment = unsafe { std::str::from_utf8_unchecked(&initial[..delta]) };
if is_doc_comment {
@ -374,8 +367,7 @@ fn eat_line_comment<'a>(
}
Good {
row,
col,
pos,
bytes,
comments_and_newlines,
}

File diff suppressed because it is too large Load diff

View file

@ -1,20 +1,13 @@
use crate::ast::{Collection, CommentOrNewline, Spaceable, StrLiteral, TypeAnnotation};
use crate::ast::{Collection, CommentOrNewline, Spaced, StrLiteral, TypeAnnotation};
use crate::blankspace::space0_e;
use crate::ident::lowercase_ident;
use crate::parser::Progress::{self, *};
use crate::parser::{
specialize, word1, EPackageEntry, EPackageName, EPackageOrPath, Parser, State,
};
use crate::parser::Progress::*;
use crate::parser::{specialize, word1, EPackageEntry, EPackageName, Parser};
use crate::state::State;
use crate::string_literal;
use bumpalo::collections::Vec;
use roc_region::all::Loc;
#[derive(Copy, Clone, PartialEq, Eq, Debug, Hash)]
pub struct PackageName<'a> {
pub account: &'a str,
pub pkg: &'a str,
}
#[derive(Copy, Clone, PartialEq, Eq, Debug, Hash)]
pub enum Version<'a> {
Exact(&'a str),
@ -33,10 +26,7 @@ pub enum VersionComparison {
}
#[derive(Copy, Clone, PartialEq, Debug)]
pub enum PackageOrPath<'a> {
Package(PackageName<'a>, Version<'a>),
Path(StrLiteral<'a>),
}
pub struct PackageName<'a>(pub &'a str);
#[derive(Copy, Clone, PartialEq, Eq, Debug, Hash)]
pub struct ModuleName<'a>(&'a str);
@ -57,11 +47,30 @@ impl<'a> ModuleName<'a> {
}
}
#[derive(Copy, Clone, PartialEq, Eq, Debug, Hash)]
pub struct ExposedName<'a>(&'a str);
impl<'a> From<ExposedName<'a>> for &'a str {
fn from(name: ExposedName<'a>) -> Self {
name.0
}
}
impl<'a> ExposedName<'a> {
pub fn new(name: &'a str) -> Self {
ExposedName(name)
}
pub fn as_str(&'a self) -> &'a str {
self.0
}
}
#[derive(Clone, Debug, PartialEq)]
pub struct InterfaceHeader<'a> {
pub name: Loc<ModuleName<'a>>,
pub exposes: Collection<'a, Loc<ExposesEntry<'a, &'a str>>>,
pub imports: Collection<'a, Loc<ImportsEntry<'a>>>,
pub exposes: Collection<'a, Loc<Spaced<'a, ExposedName<'a>>>>,
pub imports: Collection<'a, Loc<Spaced<'a, ImportsEntry<'a>>>>,
// Potential comments and newlines - these will typically all be empty.
pub before_header: &'a [CommentOrNewline<'a>],
@ -75,15 +84,15 @@ pub struct InterfaceHeader<'a> {
#[derive(Copy, Clone, Debug, PartialEq)]
pub enum To<'a> {
ExistingPackage(&'a str),
NewPackage(PackageOrPath<'a>),
NewPackage(PackageName<'a>),
}
#[derive(Clone, Debug, PartialEq)]
pub struct AppHeader<'a> {
pub name: Loc<StrLiteral<'a>>,
pub packages: Collection<'a, Loc<PackageEntry<'a>>>,
pub imports: Collection<'a, Loc<ImportsEntry<'a>>>,
pub provides: Collection<'a, Loc<ExposesEntry<'a, &'a str>>>,
pub packages: Collection<'a, Loc<Spaced<'a, PackageEntry<'a>>>>,
pub imports: Collection<'a, Loc<Spaced<'a, ImportsEntry<'a>>>>,
pub provides: Collection<'a, Loc<Spaced<'a, ExposedName<'a>>>>,
pub to: Loc<To<'a>>,
// Potential comments and newlines - these will typically all be empty.
@ -102,8 +111,8 @@ pub struct AppHeader<'a> {
#[derive(Clone, Debug, PartialEq)]
pub struct PackageHeader<'a> {
pub name: Loc<PackageName<'a>>,
pub exposes: Vec<'a, Loc<ExposesEntry<'a, &'a str>>>,
pub packages: Vec<'a, (Loc<&'a str>, Loc<PackageOrPath<'a>>)>,
pub exposes: Vec<'a, Loc<Spaced<'a, ExposedName<'a>>>>,
pub packages: Vec<'a, (Loc<&'a str>, Loc<PackageName<'a>>)>,
pub imports: Vec<'a, Loc<ImportsEntry<'a>>>,
// Potential comments and newlines - these will typically all be empty.
@ -118,37 +127,25 @@ pub struct PackageHeader<'a> {
}
#[derive(Copy, Clone, Debug, PartialEq)]
pub enum PlatformRigid<'a> {
Entry { rigid: &'a str, alias: &'a str },
// Spaces
SpaceBefore(&'a PlatformRigid<'a>, &'a [CommentOrNewline<'a>]),
SpaceAfter(&'a PlatformRigid<'a>, &'a [CommentOrNewline<'a>]),
}
impl<'a> Spaceable<'a> for PlatformRigid<'a> {
fn before(&'a self, spaces: &'a [CommentOrNewline<'a>]) -> Self {
PlatformRigid::SpaceBefore(self, spaces)
}
fn after(&'a self, spaces: &'a [CommentOrNewline<'a>]) -> Self {
PlatformRigid::SpaceAfter(self, spaces)
}
pub struct PlatformRigid<'a> {
pub rigid: &'a str,
pub alias: &'a str,
}
#[derive(Clone, Debug, PartialEq)]
pub struct PlatformRequires<'a> {
pub rigids: Collection<'a, Loc<PlatformRigid<'a>>>,
pub signature: Loc<TypedIdent<'a>>,
pub rigids: Collection<'a, Loc<Spaced<'a, PlatformRigid<'a>>>>,
pub signature: Loc<Spaced<'a, TypedIdent<'a>>>,
}
#[derive(Clone, Debug, PartialEq)]
pub struct PlatformHeader<'a> {
pub name: Loc<PackageName<'a>>,
pub requires: PlatformRequires<'a>,
pub exposes: Collection<'a, Loc<ExposesEntry<'a, ModuleName<'a>>>>,
pub packages: Collection<'a, Loc<PackageEntry<'a>>>,
pub imports: Collection<'a, Loc<ImportsEntry<'a>>>,
pub provides: Collection<'a, Loc<ExposesEntry<'a, &'a str>>>,
pub exposes: Collection<'a, Loc<Spaced<'a, ModuleName<'a>>>>,
pub packages: Collection<'a, Loc<Spaced<'a, PackageEntry<'a>>>>,
pub imports: Collection<'a, Loc<Spaced<'a, ImportsEntry<'a>>>>,
pub provides: Collection<'a, Loc<Spaced<'a, ExposedName<'a>>>>,
pub effects: Effects<'a>,
// Potential comments and newlines - these will typically all be empty.
@ -174,26 +171,7 @@ pub struct Effects<'a> {
pub spaces_after_type_name: &'a [CommentOrNewline<'a>],
pub effect_shortname: &'a str,
pub effect_type_name: &'a str,
pub entries: Collection<'a, Loc<TypedIdent<'a>>>,
}
#[derive(Copy, Clone, Debug, PartialEq)]
pub enum ExposesEntry<'a, T> {
/// e.g. `Task`
Exposed(T),
// Spaces
SpaceBefore(&'a ExposesEntry<'a, T>, &'a [CommentOrNewline<'a>]),
SpaceAfter(&'a ExposesEntry<'a, T>, &'a [CommentOrNewline<'a>]),
}
impl<'a, T> Spaceable<'a> for ExposesEntry<'a, T> {
fn before(&'a self, spaces: &'a [CommentOrNewline<'a>]) -> Self {
ExposesEntry::SpaceBefore(self, spaces)
}
fn after(&'a self, spaces: &'a [CommentOrNewline<'a>]) -> Self {
ExposesEntry::SpaceAfter(self, spaces)
}
pub entries: Collection<'a, Loc<Spaced<'a, TypedIdent<'a>>>>,
}
#[derive(Copy, Clone, Debug, PartialEq)]
@ -201,71 +179,35 @@ pub enum ImportsEntry<'a> {
/// e.g. `Task` or `Task.{ Task, after }`
Module(
ModuleName<'a>,
Collection<'a, Loc<ExposesEntry<'a, &'a str>>>,
Collection<'a, Loc<Spaced<'a, ExposedName<'a>>>>,
),
/// e.g. `base.Task` or `base.Task.{ after }` or `base.{ Task.{ Task, after } }`
/// e.g. `pf.Task` or `pf.Task.{ after }` or `pf.{ Task.{ Task, after } }`
Package(
&'a str,
ModuleName<'a>,
Collection<'a, Loc<ExposesEntry<'a, &'a str>>>,
Collection<'a, Loc<Spaced<'a, ExposedName<'a>>>>,
),
// Spaces
SpaceBefore(&'a ImportsEntry<'a>, &'a [CommentOrNewline<'a>]),
SpaceAfter(&'a ImportsEntry<'a>, &'a [CommentOrNewline<'a>]),
}
impl<'a> ExposesEntry<'a, &'a str> {
pub fn as_str(&'a self) -> &'a str {
use ExposesEntry::*;
match self {
Exposed(string) => string,
SpaceBefore(sub_entry, _) | SpaceAfter(sub_entry, _) => sub_entry.as_str(),
}
}
/// e.g.
///
/// printLine : Str -> Effect {}
#[derive(Copy, Clone, Debug, PartialEq)]
pub struct TypedIdent<'a> {
pub ident: Loc<&'a str>,
pub spaces_before_colon: &'a [CommentOrNewline<'a>],
pub ann: Loc<TypeAnnotation<'a>>,
}
#[derive(Copy, Clone, Debug, PartialEq)]
pub enum TypedIdent<'a> {
/// e.g.
///
/// printLine : Str -> Effect {}
Entry {
ident: Loc<&'a str>,
spaces_before_colon: &'a [CommentOrNewline<'a>],
ann: Loc<TypeAnnotation<'a>>,
},
// Spaces
SpaceBefore(&'a TypedIdent<'a>, &'a [CommentOrNewline<'a>]),
SpaceAfter(&'a TypedIdent<'a>, &'a [CommentOrNewline<'a>]),
pub struct PackageEntry<'a> {
pub shorthand: &'a str,
pub spaces_after_shorthand: &'a [CommentOrNewline<'a>],
pub package_name: Loc<PackageName<'a>>,
}
#[derive(Copy, Clone, Debug, PartialEq)]
pub enum PackageEntry<'a> {
Entry {
shorthand: &'a str,
spaces_after_shorthand: &'a [CommentOrNewline<'a>],
package_or_path: Loc<PackageOrPath<'a>>,
},
// Spaces
SpaceBefore(&'a PackageEntry<'a>, &'a [CommentOrNewline<'a>]),
SpaceAfter(&'a PackageEntry<'a>, &'a [CommentOrNewline<'a>]),
}
impl<'a> Spaceable<'a> for PackageEntry<'a> {
fn before(&'a self, spaces: &'a [CommentOrNewline<'a>]) -> Self {
PackageEntry::SpaceBefore(self, spaces)
}
fn after(&'a self, spaces: &'a [CommentOrNewline<'a>]) -> Self {
PackageEntry::SpaceAfter(self, spaces)
}
}
pub fn package_entry<'a>() -> impl Parser<'a, PackageEntry<'a>, EPackageEntry<'a>> {
pub fn package_entry<'a>() -> impl Parser<'a, Spaced<'a, PackageEntry<'a>>, EPackageEntry<'a>> {
move |arena, state| {
// You may optionally have a package shorthand,
// e.g. "uc" in `uc: roc/unicode 1.0.0`
@ -275,151 +217,46 @@ pub fn package_entry<'a>() -> impl Parser<'a, PackageEntry<'a>, EPackageEntry<'a
let (_, opt_shorthand, state) = maybe!(and!(
skip_second!(
specialize(|_, r, c| EPackageEntry::Shorthand(r, c), lowercase_ident()),
specialize(|_, pos| EPackageEntry::Shorthand(pos), lowercase_ident()),
word1(b':', EPackageEntry::Colon)
),
space0_e(
min_indent,
EPackageEntry::Space,
EPackageEntry::IndentPackageOrPath
EPackageEntry::IndentPackage
)
))
.parse(arena, state)?;
let (_, package_or_path, state) = loc!(specialize(
EPackageEntry::BadPackageOrPath,
package_or_path()
))
.parse(arena, state)?;
let (_, package_or_path, state) =
loc!(specialize(EPackageEntry::BadPackage, package_name())).parse(arena, state)?;
let entry = match opt_shorthand {
Some((shorthand, spaces_after_shorthand)) => PackageEntry::Entry {
Some((shorthand, spaces_after_shorthand)) => PackageEntry {
shorthand,
spaces_after_shorthand,
package_or_path,
package_name: package_or_path,
},
None => PackageEntry::Entry {
None => PackageEntry {
shorthand: "",
spaces_after_shorthand: &[],
package_or_path,
package_name: package_or_path,
},
};
Ok((MadeProgress, entry, state))
Ok((MadeProgress, Spaced::Item(entry), state))
}
}
pub fn package_or_path<'a>() -> impl Parser<'a, PackageOrPath<'a>, EPackageOrPath<'a>> {
one_of![
map!(
specialize(EPackageOrPath::BadPath, string_literal::parse()),
PackageOrPath::Path
),
map!(
and!(
specialize(EPackageOrPath::BadPackage, package_name()),
skip_first!(skip_spaces(), package_version())
),
|(name, version)| { PackageOrPath::Package(name, version) }
)
]
}
fn skip_spaces<'a, T>() -> impl Parser<'a, (), T>
where
T: 'a,
{
|_, mut state: State<'a>| {
let mut chomped = 0;
let mut it = state.bytes.iter();
while let Some(b' ') = it.next() {
chomped += 1;
}
if chomped == 0 {
Ok((NoProgress, (), state))
} else {
state.column += chomped;
state.bytes = it.as_slice();
Ok((MadeProgress, (), state))
}
}
}
fn package_version<'a, T>() -> impl Parser<'a, Version<'a>, T>
where
T: 'a,
{
move |_, _| todo!("TODO parse package version")
}
#[inline(always)]
pub fn package_name<'a>() -> impl Parser<'a, PackageName<'a>, EPackageName> {
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.
|_, mut state: State<'a>| match chomp_package_part(state.bytes) {
Err(progress) => Err((
progress,
EPackageName::Account(state.line, state.column),
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,
EPackageName::Pkg(state.line, state.column + chomped as u16),
state,
)),
Ok(pkg) => {
chomped += pkg.len();
state.column += chomped as u16;
state.bytes = &state.bytes[chomped..];
let value = PackageName { account, pkg };
Ok((MadeProgress, value, state))
}
}
} else {
Err((
MadeProgress,
EPackageName::MissingSlash(state.line, state.column + chomped as u16),
state,
))
}
}
}
}
fn chomp_package_part(buffer: &[u8]) -> Result<&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)
pub fn package_name<'a>() -> impl Parser<'a, PackageName<'a>, EPackageName<'a>> {
move |arena, state: State<'a>| {
let pos = state.pos;
specialize(EPackageName::BadPath, string_literal::parse())
.parse(arena, state)
.and_then(|(progress, text, next_state)| match text {
StrLiteral::PlainLine(text) => Ok((progress, PackageName(text), next_state)),
StrLiteral::Line(_) => Err((progress, EPackageName::Escapes(pos), next_state)),
StrLiteral::Block(_) => Err((progress, EPackageName::Multiline(pos), next_state)),
})
}
}

View file

@ -1,7 +1,9 @@
use crate::parser::Progress::{self, *};
use crate::parser::{BadInputError, Col, EExpr, ParseResult, Parser, Row, State};
use crate::parser::{BadInputError, EExpr, ParseResult, Parser};
use crate::state::State;
use bumpalo::collections::vec::Vec;
use bumpalo::Bump;
use roc_region::all::Position;
/// The parser accepts all of these in any position where any one of them could
/// appear. This way, canonicalization can give more helpful error messages like
@ -59,14 +61,14 @@ impl<'a> Ident<'a> {
/// * A record field, e.g. "email" in `.email` or in `email:`
/// * A named pattern match, e.g. "foo" in `foo =` or `foo ->` or `\foo ->`
pub fn lowercase_ident<'a>() -> impl Parser<'a, &'a str, ()> {
move |_, state: State<'a>| match chomp_lowercase_part(state.bytes) {
move |_, state: State<'a>| match chomp_lowercase_part(state.bytes()) {
Err(progress) => Err((progress, (), state)),
Ok(ident) => {
if crate::keyword::KEYWORDS.iter().any(|kw| &ident == kw) {
Err((NoProgress, (), state))
} else {
let width = ident.len();
match state.advance_without_indenting_ee(width, |_, _| ()) {
match state.advance_without_indenting_ee(width, |_| ()) {
Ok(state) => Ok((MadeProgress, ident, state)),
Err(bad) => Err(bad),
}
@ -77,13 +79,13 @@ pub fn lowercase_ident<'a>() -> impl Parser<'a, &'a str, ()> {
pub fn tag_name<'a>() -> impl Parser<'a, &'a str, ()> {
move |arena, state: State<'a>| {
if state.bytes.starts_with(b"@") {
match chomp_private_tag(state.bytes, state.line, state.column) {
Err(BadIdent::Start(_, _)) => Err((NoProgress, (), state)),
if state.bytes().starts_with(b"@") {
match chomp_private_tag(state.bytes(), state.pos) {
Err(BadIdent::Start(_)) => Err((NoProgress, (), state)),
Err(_) => Err((MadeProgress, (), state)),
Ok(ident) => {
let width = ident.len();
match state.advance_without_indenting_ee(width, |_, _| ()) {
match state.advance_without_indenting_ee(width, |_| ()) {
Ok(state) => Ok((MadeProgress, ident, state)),
Err(bad) => Err(bad),
}
@ -101,11 +103,11 @@ pub fn tag_name<'a>() -> impl Parser<'a, &'a str, ()> {
/// * A type name
/// * A global tag
pub fn uppercase_ident<'a>() -> impl Parser<'a, &'a str, ()> {
move |_, state: State<'a>| match chomp_uppercase_part(state.bytes) {
move |_, state: State<'a>| match chomp_uppercase_part(state.bytes()) {
Err(progress) => Err((progress, (), state)),
Ok(ident) => {
let width = ident.len();
match state.advance_without_indenting_ee(width, |_, _| ()) {
match state.advance_without_indenting_ee(width, |_| ()) {
Ok(state) => Ok((MadeProgress, ident, state)),
Err(bad) => Err(bad),
}
@ -114,14 +116,14 @@ pub fn uppercase_ident<'a>() -> impl Parser<'a, &'a str, ()> {
}
pub fn unqualified_ident<'a>() -> impl Parser<'a, &'a str, ()> {
move |_, state: State<'a>| match chomp_part(|c| c.is_alphabetic(), state.bytes) {
move |_, state: State<'a>| match chomp_part(|c| c.is_alphabetic(), state.bytes()) {
Err(progress) => Err((progress, (), state)),
Ok(ident) => {
if crate::keyword::KEYWORDS.iter().any(|kw| &ident == kw) {
Err((MadeProgress, (), state))
} else {
let width = ident.len();
match state.advance_without_indenting_ee(width, |_, _| ()) {
match state.advance_without_indenting_ee(width, |_| ()) {
Ok(state) => Ok((MadeProgress, ident, state)),
Err(bad) => Err(bad),
}
@ -132,14 +134,14 @@ pub fn unqualified_ident<'a>() -> impl Parser<'a, &'a str, ()> {
macro_rules! advance_state {
($state:expr, $n:expr) => {
$state.advance_without_indenting_ee($n, |r, c| {
BadIdent::Space(crate::parser::BadInputError::LineTooLong, r, c)
$state.advance_without_indenting_ee($n, |pos| {
BadIdent::Space(crate::parser::BadInputError::LineTooLong, pos)
})
};
}
pub fn parse_ident<'a>(arena: &'a Bump, state: State<'a>) -> ParseResult<'a, Ident<'a>, EExpr<'a>> {
let initial = state;
let initial = state.clone();
match parse_ident_help(arena, state) {
Ok((progress, ident, state)) => {
@ -148,11 +150,7 @@ pub fn parse_ident<'a>(arena: &'a Bump, state: State<'a>) -> ParseResult<'a, Ide
if let Some(first) = parts.first() {
for keyword in crate::keyword::KEYWORDS.iter() {
if first == keyword {
return Err((
NoProgress,
EExpr::Start(initial.line, initial.column),
initial,
));
return Err((NoProgress, EExpr::Start(initial.pos), initial));
}
}
}
@ -161,13 +159,11 @@ pub fn parse_ident<'a>(arena: &'a Bump, state: State<'a>) -> ParseResult<'a, Ide
Ok((progress, ident, state))
}
Err((NoProgress, _, state)) => {
Err((NoProgress, EExpr::Start(state.line, state.column), state))
}
Err((NoProgress, _, state)) => Err((NoProgress, EExpr::Start(state.pos), state)),
Err((MadeProgress, fail, state)) => match fail {
BadIdent::Start(r, c) => Err((NoProgress, EExpr::Start(r, c), state)),
BadIdent::Space(e, r, c) => Err((NoProgress, EExpr::Space(e, r, c), state)),
_ => malformed_identifier(initial.bytes, fail, state),
BadIdent::Start(pos) => Err((NoProgress, EExpr::Start(pos), state)),
BadIdent::Space(e, pos) => Err((NoProgress, EExpr::Space(e, pos), state)),
_ => malformed_identifier(initial.bytes(), fail, state),
},
}
}
@ -177,12 +173,12 @@ fn malformed_identifier<'a>(
problem: BadIdent,
mut state: State<'a>,
) -> ParseResult<'a, Ident<'a>, EExpr<'a>> {
let chomped = chomp_malformed(state.bytes);
let delta = initial_bytes.len() - state.bytes.len();
let chomped = chomp_malformed(state.bytes());
let delta = initial_bytes.len() - state.bytes().len();
let parsed_str = unsafe { std::str::from_utf8_unchecked(&initial_bytes[..chomped + delta]) };
state = state.advance_without_indenting_ee(chomped, |r, c| {
EExpr::Space(crate::parser::BadInputError::LineTooLong, r, c)
state = state.advance_without_indenting_ee(chomped, |pos| {
EExpr::Space(crate::parser::BadInputError::LineTooLong, pos)
})?;
Ok((MadeProgress, Ident::Malformed(parsed_str, problem), state))
@ -208,16 +204,16 @@ pub fn chomp_malformed(bytes: &[u8]) -> usize {
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum BadIdent {
Start(Row, Col),
Space(BadInputError, Row, Col),
Start(Position),
Space(BadInputError, Position),
Underscore(Row, Col),
QualifiedTag(Row, Col),
WeirdAccessor(Row, Col),
WeirdDotAccess(Row, Col),
WeirdDotQualified(Row, Col),
StrayDot(Row, Col),
BadPrivateTag(Row, Col),
Underscore(Position),
QualifiedTag(Position),
WeirdAccessor(Position),
WeirdDotAccess(Position),
WeirdDotQualified(Position),
StrayDot(Position),
BadPrivateTag(Position),
}
fn chomp_lowercase_part(buffer: &[u8]) -> Result<&str, Progress> {
@ -264,7 +260,7 @@ where
}
/// a `.foo` accessor function
fn chomp_accessor(buffer: &[u8], row: Row, col: Col) -> Result<&str, BadIdent> {
fn chomp_accessor(buffer: &[u8], pos: Position) -> Result<&str, BadIdent> {
// assumes the leading `.` has been chomped already
use encode_unicode::CharExt;
@ -273,20 +269,23 @@ fn chomp_accessor(buffer: &[u8], row: Row, col: Col) -> Result<&str, BadIdent> {
let chomped = name.len();
if let Ok(('.', _)) = char::from_utf8_slice_start(&buffer[chomped..]) {
Err(BadIdent::WeirdAccessor(row, col))
Err(BadIdent::WeirdAccessor(pos))
} else {
Ok(name)
}
}
Err(_) => {
// we've already made progress with the initial `.`
Err(BadIdent::StrayDot(row, col + 1))
Err(BadIdent::StrayDot(Position {
line: pos.line,
column: pos.column + 1,
}))
}
}
}
/// a `@Token` private tag
fn chomp_private_tag(buffer: &[u8], row: Row, col: Col) -> Result<&str, BadIdent> {
fn chomp_private_tag(buffer: &[u8], pos: Position) -> Result<&str, BadIdent> {
// assumes the leading `@` has NOT been chomped already
debug_assert_eq!(buffer.get(0), Some(&b'@'));
use encode_unicode::CharExt;
@ -296,21 +295,23 @@ fn chomp_private_tag(buffer: &[u8], row: Row, col: Col) -> Result<&str, BadIdent
let width = 1 + name.len();
if let Ok(('.', _)) = char::from_utf8_slice_start(&buffer[width..]) {
Err(BadIdent::BadPrivateTag(row, col + width as u16))
Err(BadIdent::BadPrivateTag(pos.bump_column(width as u16)))
} else {
let value = unsafe { std::str::from_utf8_unchecked(&buffer[..width]) };
Ok(value)
}
}
Err(_) => Err(BadIdent::BadPrivateTag(row, col + 1)),
Err(_) => Err(BadIdent::BadPrivateTag(Position {
line: pos.line,
column: pos.column + 1,
})),
}
}
fn chomp_identifier_chain<'a>(
arena: &'a Bump,
buffer: &'a [u8],
row: Row,
col: Col,
pos: Position,
) -> Result<(u16, Ident<'a>), (u16, BadIdent)> {
use encode_unicode::CharExt;
@ -319,7 +320,7 @@ fn chomp_identifier_chain<'a>(
match char::from_utf8_slice_start(&buffer[chomped..]) {
Ok((ch, width)) => match ch {
'.' => match chomp_accessor(&buffer[1..], row, col) {
'.' => match chomp_accessor(&buffer[1..], pos) {
Ok(accessor) => {
let bytes_parsed = 1 + accessor.len();
@ -327,7 +328,7 @@ fn chomp_identifier_chain<'a>(
}
Err(fail) => return Err((1, fail)),
},
'@' => match chomp_private_tag(buffer, row, col) {
'@' => match chomp_private_tag(buffer, pos) {
Ok(tagname) => {
let bytes_parsed = tagname.len();
@ -341,10 +342,10 @@ fn chomp_identifier_chain<'a>(
first_is_uppercase = c.is_uppercase();
}
_ => {
return Err((0, BadIdent::Start(row, col)));
return Err((0, BadIdent::Start(pos)));
}
},
Err(_) => return Err((0, BadIdent::Start(row, col))),
Err(_) => return Err((0, BadIdent::Start(pos))),
}
while let Ok((ch, width)) = char::from_utf8_slice_start(&buffer[chomped..]) {
@ -390,15 +391,15 @@ fn chomp_identifier_chain<'a>(
}
Err(0) if !module_name.is_empty() => Err((
chomped as u16,
BadIdent::QualifiedTag(row, chomped as u16 + col),
BadIdent::QualifiedTag(pos.bump_column(chomped as u16)),
)),
Err(1) if parts.is_empty() => Err((
chomped as u16 + 1,
BadIdent::WeirdDotQualified(row, chomped as u16 + col + 1),
BadIdent::WeirdDotQualified(pos.bump_column(chomped as u16 + 1)),
)),
Err(width) => Err((
chomped as u16 + width,
BadIdent::WeirdDotAccess(row, chomped as u16 + col + width),
BadIdent::WeirdDotAccess(pos.bump_column(chomped as u16 + width)),
)),
}
} else if let Ok(('_', _)) = char::from_utf8_slice_start(&buffer[chomped..]) {
@ -407,7 +408,7 @@ fn chomp_identifier_chain<'a>(
// to give good error messages for this case
Err((
chomped as u16 + 1,
BadIdent::Underscore(row, col + chomped as u16 + 1),
BadIdent::Underscore(pos.bump_column(chomped as u16 + 1)),
))
} else if first_is_uppercase {
// just one segment, starting with an uppercase letter; that's a global tag
@ -448,10 +449,10 @@ fn chomp_module_chain(buffer: &[u8]) -> Result<u16, Progress> {
}
pub fn concrete_type<'a>() -> impl Parser<'a, (&'a str, &'a str), ()> {
move |_, state: State<'a>| match chomp_concrete_type(state.bytes) {
move |_, state: State<'a>| match chomp_concrete_type(state.bytes()) {
Err(progress) => Err((progress, (), state)),
Ok((module_name, type_name, width)) => {
match state.advance_without_indenting_ee(width, |_, _| ()) {
match state.advance_without_indenting_ee(width, |_| ()) {
Ok(state) => Ok((MadeProgress, (module_name, type_name), state)),
Err(bad) => Err(bad),
}
@ -527,7 +528,7 @@ fn parse_ident_help<'a>(
arena: &'a Bump,
mut state: State<'a>,
) -> ParseResult<'a, Ident<'a>, BadIdent> {
match chomp_identifier_chain(arena, state.bytes, state.line, state.column) {
match chomp_identifier_chain(arena, state.bytes(), state.pos) {
Ok((width, ident)) => {
state = advance_state!(state, width as usize)?;
Ok((MadeProgress, ident, state))

View file

@ -14,6 +14,7 @@ pub mod module;
pub mod number_literal;
pub mod pattern;
pub mod problems;
pub mod state;
pub mod string_literal;
pub mod test_helpers;
pub mod type_annotation;

View file

@ -1,44 +1,37 @@
use crate::ast::{Collection, CommentOrNewline, Def, Module};
use crate::ast::{Collection, CommentOrNewline, Def, Module, Spaced};
use crate::blankspace::{space0_around_ee, space0_before_e, space0_e};
use crate::header::{
package_entry, package_name, package_or_path, AppHeader, Effects, ExposesEntry, ImportsEntry,
InterfaceHeader, ModuleName, PackageEntry, PlatformHeader, PlatformRequires, PlatformRigid, To,
TypedIdent,
package_entry, package_name, AppHeader, Effects, ExposedName, ImportsEntry, InterfaceHeader,
ModuleName, PackageEntry, PlatformHeader, PlatformRequires, PlatformRigid, To, TypedIdent,
};
use crate::ident::{lowercase_ident, unqualified_ident, uppercase_ident};
use crate::parser::Progress::{self, *};
use crate::parser::{
backtrackable, specialize, word1, word2, Col, EEffects, EExposes, EHeader, EImports, EPackages,
EProvides, ERequires, ETypedIdent, Parser, Row, State, SyntaxError,
backtrackable, specialize, word1, word2, EEffects, EExposes, EHeader, EImports, EPackages,
EProvides, ERequires, ETypedIdent, Parser, SyntaxError,
};
use crate::state::State;
use crate::string_literal;
use crate::type_annotation;
use bumpalo::collections::Vec;
use roc_region::all::Located;
use roc_region::all::{Loc, Position};
fn end_of_file<'a>() -> impl Parser<'a, (), SyntaxError<'a>> {
|_arena, state: State<'a>| {
if state.has_reached_end() {
Ok((NoProgress, (), state))
} else {
Err((
NoProgress,
SyntaxError::NotEndOfFile(state.line, state.column),
state,
))
Err((NoProgress, SyntaxError::NotEndOfFile(state.pos), state))
}
}
}
#[inline(always)]
pub fn module_defs<'a>() -> impl Parser<'a, Vec<'a, Located<Def<'a>>>, SyntaxError<'a>> {
pub fn module_defs<'a>() -> impl Parser<'a, Vec<'a, Loc<Def<'a>>>, SyntaxError<'a>> {
// force that we parse until the end of the input
let min_indent = 0;
skip_second!(
specialize(
|e, _, _| SyntaxError::Expr(e),
crate::expr::defs(min_indent),
),
specialize(|e, _| SyntaxError::Expr(e), crate::expr::defs(min_indent),),
end_of_file()
)
}
@ -171,11 +164,11 @@ fn chomp_module_name(buffer: &[u8]) -> Result<&str, Progress> {
#[inline(always)]
fn module_name<'a>() -> impl Parser<'a, ModuleName<'a>, ()> {
|_, mut state: State<'a>| match chomp_module_name(state.bytes) {
|_, mut state: State<'a>| match chomp_module_name(state.bytes()) {
Ok(name) => {
let width = name.len();
state.column += width as u16;
state.bytes = &state.bytes[width..];
state.pos.column += width as u16;
state = state.advance(width);
Ok((MadeProgress, ModuleName::new(name), state))
}
@ -220,7 +213,7 @@ fn app_header<'a>() -> impl Parser<'a, AppHeader<'a>, EHeader<'a>> {
#[allow(clippy::type_complexity)]
let opt_imports: Option<(
(&'a [CommentOrNewline<'a>], &'a [CommentOrNewline<'a>]),
Collection<'a, Located<ImportsEntry<'a>>>,
Collection<'a, Loc<Spaced<'a, ImportsEntry<'a>>>>,
)> = opt_imports;
let ((before_imports, after_imports), imports) =
@ -303,8 +296,8 @@ fn platform_header<'a>() -> impl Parser<'a, PlatformHeader<'a>, EHeader<'a>> {
#[derive(Debug)]
struct ProvidesTo<'a> {
entries: Collection<'a, Located<ExposesEntry<'a, &'a str>>>,
to: Located<To<'a>>,
entries: Collection<'a, Loc<Spaced<'a, ExposedName<'a>>>>,
to: Loc<To<'a>>,
before_provides_keyword: &'a [CommentOrNewline<'a>],
after_provides_keyword: &'a [CommentOrNewline<'a>],
@ -315,10 +308,10 @@ struct ProvidesTo<'a> {
fn provides_to_package<'a>() -> impl Parser<'a, To<'a>, EProvides<'a>> {
one_of![
specialize(
|_, r, c| EProvides::Identifier(r, c),
|_, pos| EProvides::Identifier(pos),
map!(lowercase_ident(), To::ExistingPackage)
),
specialize(EProvides::Package, map!(package_or_path(), To::NewPackage))
specialize(EProvides::Package, map!(package_name(), To::NewPackage))
]
}
@ -362,7 +355,7 @@ fn provides_without_to<'a>() -> impl Parser<
'a,
(
(&'a [CommentOrNewline<'a>], &'a [CommentOrNewline<'a>]),
Collection<'a, Located<ExposesEntry<'a, &'a str>>>,
Collection<'a, Loc<Spaced<'a, ExposedName<'a>>>>,
),
EProvides<'a>,
> {
@ -385,22 +378,22 @@ fn provides_without_to<'a>() -> impl Parser<
EProvides::Open,
EProvides::Space,
EProvides::IndentListEnd,
ExposesEntry::SpaceBefore
Spaced::SpaceBefore
)
)
}
fn exposes_entry<'a, F, E>(
to_expectation: F,
) -> impl Parser<'a, Located<ExposesEntry<'a, &'a str>>, E>
) -> impl Parser<'a, Loc<Spaced<'a, ExposedName<'a>>>, E>
where
F: Fn(crate::parser::Row, crate::parser::Col) -> E,
F: Fn(Position) -> E,
F: Copy,
E: 'a,
{
loc!(map!(
specialize(|_, r, c| to_expectation(r, c), unqualified_ident()),
ExposesEntry::Exposed
specialize(|_, pos| to_expectation(pos), unqualified_ident()),
|n| Spaced::Item(ExposedName::new(n))
))
}
@ -444,33 +437,33 @@ fn platform_requires<'a>() -> impl Parser<'a, PlatformRequires<'a>, ERequires<'a
#[inline(always)]
fn requires_rigids<'a>(
min_indent: u16,
) -> impl Parser<'a, Collection<'a, Located<PlatformRigid<'a>>>, ERequires<'a>> {
) -> impl Parser<'a, Collection<'a, Loc<Spaced<'a, PlatformRigid<'a>>>>, ERequires<'a>> {
collection_trailing_sep_e!(
word1(b'{', ERequires::ListStart),
specialize(|_, r, c| ERequires::Rigid(r, c), loc!(requires_rigid())),
specialize(|_, pos| ERequires::Rigid(pos), loc!(requires_rigid())),
word1(b',', ERequires::ListEnd),
word1(b'}', ERequires::ListEnd),
min_indent,
ERequires::Open,
ERequires::Space,
ERequires::IndentListEnd,
PlatformRigid::SpaceBefore
Spaced::SpaceBefore
)
}
#[inline(always)]
fn requires_rigid<'a>() -> impl Parser<'a, PlatformRigid<'a>, ()> {
fn requires_rigid<'a>() -> impl Parser<'a, Spaced<'a, PlatformRigid<'a>>, ()> {
map!(
and!(
lowercase_ident(),
skip_first!(word2(b'=', b'>', |_, _| ()), uppercase_ident())
skip_first!(word2(b'=', b'>', |_| ()), uppercase_ident())
),
|(rigid, alias)| PlatformRigid::Entry { rigid, alias }
|(rigid, alias)| Spaced::Item(PlatformRigid { rigid, alias })
)
}
#[inline(always)]
fn requires_typed_ident<'a>() -> impl Parser<'a, Located<TypedIdent<'a>>, ERequires<'a>> {
fn requires_typed_ident<'a>() -> impl Parser<'a, Loc<Spaced<'a, TypedIdent<'a>>>, ERequires<'a>> {
skip_first!(
word1(b'{', ERequires::ListStart),
skip_second!(
@ -491,7 +484,7 @@ fn exposes_values<'a>() -> impl Parser<
'a,
(
(&'a [CommentOrNewline<'a>], &'a [CommentOrNewline<'a>]),
Collection<'a, Located<ExposesEntry<'a, &'a str>>>,
Collection<'a, Loc<Spaced<'a, ExposedName<'a>>>>,
),
EExposes,
> {
@ -515,7 +508,7 @@ fn exposes_values<'a>() -> impl Parser<
EExposes::Open,
EExposes::Space,
EExposes::IndentListEnd,
ExposesEntry::SpaceBefore
Spaced::SpaceBefore
)
)
}
@ -523,10 +516,10 @@ fn exposes_values<'a>() -> impl Parser<
fn spaces_around_keyword<'a, E>(
min_indent: u16,
keyword: &'static str,
expectation: fn(Row, Col) -> E,
space_problem: fn(crate::parser::BadInputError, Row, Col) -> E,
indent_problem1: fn(Row, Col) -> E,
indent_problem2: fn(Row, Col) -> E,
expectation: fn(Position) -> E,
space_problem: fn(crate::parser::BadInputError, Position) -> E,
indent_problem1: fn(Position) -> E,
indent_problem2: fn(Position) -> E,
) -> impl Parser<'a, (&'a [CommentOrNewline<'a>], &'a [CommentOrNewline<'a>]), E>
where
E: 'a,
@ -545,7 +538,7 @@ fn exposes_modules<'a>() -> impl Parser<
'a,
(
(&'a [CommentOrNewline<'a>], &'a [CommentOrNewline<'a>]),
Collection<'a, Located<ExposesEntry<'a, ModuleName<'a>>>>,
Collection<'a, Loc<Spaced<'a, ModuleName<'a>>>>,
),
EExposes,
> {
@ -569,28 +562,28 @@ fn exposes_modules<'a>() -> impl Parser<
EExposes::Open,
EExposes::Space,
EExposes::IndentListEnd,
ExposesEntry::SpaceBefore
Spaced::SpaceBefore
)
)
}
fn exposes_module<'a, F, E>(
to_expectation: F,
) -> impl Parser<'a, Located<ExposesEntry<'a, ModuleName<'a>>>, E>
) -> impl Parser<'a, Loc<Spaced<'a, ModuleName<'a>>>, E>
where
F: Fn(crate::parser::Row, crate::parser::Col) -> E,
F: Fn(Position) -> E,
F: Copy,
E: 'a,
{
loc!(map!(
specialize(|_, r, c| to_expectation(r, c), module_name()),
ExposesEntry::Exposed
specialize(|_, pos| to_expectation(pos), module_name()),
Spaced::Item
))
}
#[derive(Debug)]
struct Packages<'a> {
entries: Collection<'a, Located<PackageEntry<'a>>>,
entries: Collection<'a, Loc<Spaced<'a, PackageEntry<'a>>>>,
before_packages_keyword: &'a [CommentOrNewline<'a>],
after_packages_keyword: &'a [CommentOrNewline<'a>],
}
@ -618,7 +611,7 @@ fn packages<'a>() -> impl Parser<'a, Packages<'a>, EPackages<'a>> {
EPackages::Open,
EPackages::Space,
EPackages::IndentListEnd,
PackageEntry::SpaceBefore
Spaced::SpaceBefore
)
),
|((before_packages_keyword, after_packages_keyword), entries): (
@ -639,7 +632,7 @@ fn imports<'a>() -> impl Parser<
'a,
(
(&'a [CommentOrNewline<'a>], &'a [CommentOrNewline<'a>]),
Collection<'a, Located<ImportsEntry<'a>>>,
Collection<'a, Loc<Spaced<'a, ImportsEntry<'a>>>>,
),
EImports,
> {
@ -663,7 +656,7 @@ fn imports<'a>() -> impl Parser<
EImports::Open,
EImports::Space,
EImports::IndentListEnd,
ImportsEntry::SpaceBefore
Spaced::SpaceBefore
)
)
}
@ -686,14 +679,14 @@ fn effects<'a>() -> impl Parser<'a, Effects<'a>, EEffects<'a>> {
// e.g. `fx.`
let (_, type_shortname, state) = skip_second!(
specialize(|_, r, c| EEffects::Shorthand(r, c), lowercase_ident()),
specialize(|_, pos| EEffects::Shorthand(pos), lowercase_ident()),
word1(b'.', EEffects::ShorthandDot)
)
.parse(arena, state)?;
// the type name, e.g. Effects
let (_, (type_name, spaces_after_type_name), state) = and!(
specialize(|_, r, c| EEffects::TypeName(r, c), uppercase_ident()),
specialize(|_, pos| EEffects::TypeName(pos), uppercase_ident()),
space0_e(min_indent, EEffects::Space, EEffects::IndentListStart)
)
.parse(arena, state)?;
@ -706,7 +699,7 @@ fn effects<'a>() -> impl Parser<'a, Effects<'a>, EEffects<'a>> {
EEffects::Open,
EEffects::Space,
EEffects::IndentListEnd,
TypedIdent::SpaceBefore
Spaced::SpaceBefore
)
.parse(arena, state)?;
@ -726,7 +719,7 @@ fn effects<'a>() -> impl Parser<'a, Effects<'a>, EEffects<'a>> {
}
#[inline(always)]
fn typed_ident<'a>() -> impl Parser<'a, TypedIdent<'a>, ETypedIdent<'a>> {
fn typed_ident<'a>() -> impl Parser<'a, Spaced<'a, TypedIdent<'a>>, ETypedIdent<'a>> {
// e.g.
//
// printLine : Str -> Effect {}
@ -736,7 +729,7 @@ fn typed_ident<'a>() -> impl Parser<'a, TypedIdent<'a>, ETypedIdent<'a>> {
and!(
and!(
loc!(specialize(
|_, r, c| ETypedIdent::Identifier(r, c),
|_, pos| ETypedIdent::Identifier(pos),
lowercase_ident()
)),
space0_e(min_indent, ETypedIdent::Space, ETypedIdent::IndentHasType)
@ -744,7 +737,10 @@ fn typed_ident<'a>() -> impl Parser<'a, TypedIdent<'a>, ETypedIdent<'a>> {
skip_first!(
word1(b':', ETypedIdent::HasType),
space0_before_e(
specialize(ETypedIdent::Type, type_annotation::located_help(min_indent)),
specialize(
ETypedIdent::Type,
type_annotation::located_help(min_indent, true)
),
min_indent,
ETypedIdent::Space,
ETypedIdent::IndentType,
@ -752,41 +748,41 @@ fn typed_ident<'a>() -> impl Parser<'a, TypedIdent<'a>, ETypedIdent<'a>> {
)
),
|((ident, spaces_before_colon), ann)| {
TypedIdent::Entry {
Spaced::Item(TypedIdent {
ident,
spaces_before_colon,
ann,
}
})
}
)
}
fn shortname<'a>() -> impl Parser<'a, &'a str, EImports> {
specialize(|_, r, c| EImports::Shorthand(r, c), lowercase_ident())
specialize(|_, pos| EImports::Shorthand(pos), lowercase_ident())
}
fn module_name_help<'a, F, E>(to_expectation: F) -> impl Parser<'a, ModuleName<'a>, E>
where
F: Fn(crate::parser::Row, crate::parser::Col) -> E,
F: Fn(Position) -> E,
E: 'a,
F: 'a,
{
specialize(move |_, r, c| to_expectation(r, c), module_name())
specialize(move |_, pos| to_expectation(pos), module_name())
}
#[inline(always)]
fn imports_entry<'a>() -> impl Parser<'a, ImportsEntry<'a>, EImports> {
fn imports_entry<'a>() -> impl Parser<'a, Spaced<'a, ImportsEntry<'a>>, EImports> {
let min_indent = 1;
type Temp<'a> = (
(Option<&'a str>, ModuleName<'a>),
Option<Collection<'a, Located<ExposesEntry<'a, &'a str>>>>,
Option<Collection<'a, Loc<Spaced<'a, ExposedName<'a>>>>>,
);
map_with_arena!(
and!(
and!(
// e.g. `base.`
// e.g. `pf.`
maybe!(skip_second!(
shortname(),
word1(b'.', EImports::ShorthandDot)
@ -806,18 +802,20 @@ fn imports_entry<'a>() -> impl Parser<'a, ImportsEntry<'a>, EImports> {
EImports::Open,
EImports::Space,
EImports::IndentSetEnd,
ExposesEntry::SpaceBefore
Spaced::SpaceBefore
)
))
),
|_arena, ((opt_shortname, module_name), opt_values): Temp<'a>| {
let exposed_values = opt_values.unwrap_or_else(Collection::empty);
match opt_shortname {
let entry = match opt_shortname {
Some(shortname) => ImportsEntry::Package(shortname, module_name, exposed_values),
None => ImportsEntry::Module(module_name, exposed_values),
}
};
Spaced::Item(entry)
}
)
}

View file

@ -1,5 +1,6 @@
use crate::ast::Base;
use crate::parser::{ENumber, ParseResult, Parser, Progress, State};
use crate::parser::{ENumber, ParseResult, Parser, Progress};
use crate::state::State;
pub enum NumLiteral<'a> {
Float(&'a str),
@ -13,9 +14,9 @@ pub enum NumLiteral<'a> {
pub fn positive_number_literal<'a>() -> impl Parser<'a, NumLiteral<'a>, ENumber> {
move |_arena, state: State<'a>| {
match state.bytes.get(0) {
match state.bytes().get(0) {
Some(first_byte) if (*first_byte as char).is_ascii_digit() => {
parse_number_base(false, state.bytes, state)
parse_number_base(false, state.bytes(), state)
}
_ => {
// this is not a number at all
@ -27,13 +28,13 @@ pub fn positive_number_literal<'a>() -> impl Parser<'a, NumLiteral<'a>, ENumber>
pub fn number_literal<'a>() -> impl Parser<'a, NumLiteral<'a>, ENumber> {
move |_arena, state: State<'a>| {
match state.bytes.get(0) {
match state.bytes().get(0) {
Some(first_byte) if *first_byte == b'-' => {
// drop the minus
parse_number_base(true, &state.bytes[1..], state)
parse_number_base(true, &state.bytes()[1..], state)
}
Some(first_byte) if (*first_byte as char).is_ascii_digit() => {
parse_number_base(false, state.bytes, state)
parse_number_base(false, state.bytes(), state)
}
_ => {
// this is not a number at all
@ -66,7 +67,7 @@ fn chomp_number_base<'a>(
let string = unsafe { std::str::from_utf8_unchecked(&bytes[..chomped]) };
let new = state.advance_without_indenting_ee(chomped + 2 + is_negative as usize, |_, _| {
let new = state.advance_without_indenting_ee(chomped + 2 + is_negative as usize, |_| {
ENumber::LineTooLong
})?;
@ -99,11 +100,10 @@ fn chomp_number_dec<'a>(
}
let string =
unsafe { std::str::from_utf8_unchecked(&state.bytes[0..chomped + is_negative as usize]) };
unsafe { std::str::from_utf8_unchecked(&state.bytes()[0..chomped + is_negative as usize]) };
let new = state.advance_without_indenting_ee(chomped + is_negative as usize, |_, _| {
ENumber::LineTooLong
})?;
let new = state
.advance_without_indenting_ee(chomped + is_negative as usize, |_| ENumber::LineTooLong)?;
Ok((
Progress::MadeProgress,

File diff suppressed because it is too large Load diff

View file

@ -4,12 +4,13 @@ use crate::ident::{lowercase_ident, parse_ident, Ident};
use crate::parser::Progress::{self, *};
use crate::parser::{
backtrackable, optional, specialize, specialize_ref, word1, EPattern, PInParens, PRecord,
ParseResult, Parser, State,
ParseResult, Parser,
};
use crate::state::State;
use bumpalo::collections::string::String;
use bumpalo::collections::Vec;
use bumpalo::Bump;
use roc_region::all::{Located, Region};
use roc_region::all::{Loc, Region};
/// Different patterns are supported in different circumstances.
/// For example, when branches can pattern match on number literals, but
@ -23,9 +24,7 @@ pub enum PatternType {
WhenBranch,
}
pub fn loc_closure_param<'a>(
min_indent: u16,
) -> impl Parser<'a, Located<Pattern<'a>>, EPattern<'a>> {
pub fn loc_closure_param<'a>(min_indent: u16) -> impl Parser<'a, Loc<Pattern<'a>>, EPattern<'a>> {
move |arena, state| parse_closure_param(arena, state, min_indent)
}
@ -33,7 +32,7 @@ fn parse_closure_param<'a>(
arena: &'a Bump,
state: State<'a>,
min_indent: u16,
) -> ParseResult<'a, Located<Pattern<'a>>, EPattern<'a>> {
) -> ParseResult<'a, Loc<Pattern<'a>>, EPattern<'a>> {
one_of!(
// An ident is the most common param, e.g. \foo -> ...
loc_ident_pattern_help(min_indent, true),
@ -51,9 +50,7 @@ fn parse_closure_param<'a>(
.parse(arena, state)
}
pub fn loc_pattern_help<'a>(
min_indent: u16,
) -> impl Parser<'a, Located<Pattern<'a>>, EPattern<'a>> {
pub fn loc_pattern_help<'a>(min_indent: u16) -> impl Parser<'a, Loc<Pattern<'a>>, EPattern<'a>> {
one_of!(
specialize(EPattern::PInParens, loc_pattern_in_parens_help(min_indent)),
loc!(underscore_pattern_help()),
@ -69,11 +66,11 @@ pub fn loc_pattern_help<'a>(
fn loc_tag_pattern_args_help<'a>(
min_indent: u16,
) -> impl Parser<'a, Vec<'a, Located<Pattern<'a>>>, EPattern<'a>> {
) -> impl Parser<'a, Vec<'a, Loc<Pattern<'a>>>, EPattern<'a>> {
zero_or_more!(loc_tag_pattern_arg(min_indent))
}
fn loc_tag_pattern_arg<'a>(min_indent: u16) -> impl Parser<'a, Located<Pattern<'a>>, EPattern<'a>> {
fn loc_tag_pattern_arg<'a>(min_indent: u16) -> impl Parser<'a, Loc<Pattern<'a>>, EPattern<'a>> {
// Don't parse operators, because they have a higher precedence than function application.
// If we encounter one, we're done parsing function args!
move |arena, state| {
@ -83,14 +80,14 @@ fn loc_tag_pattern_arg<'a>(min_indent: u16) -> impl Parser<'a, Located<Pattern<'
let (_, loc_pat, state) = loc_parse_tag_pattern_arg(min_indent, arena, state)?;
let Located { region, value } = loc_pat;
let Loc { region, value } = loc_pat;
Ok((
MadeProgress,
if spaces.is_empty() {
Located::at(region, value)
Loc::at(region, value)
} else {
Located::at(region, Pattern::SpaceBefore(arena.alloc(value), spaces))
Loc::at(region, Pattern::SpaceBefore(arena.alloc(value), spaces))
},
state,
))
@ -101,7 +98,7 @@ fn loc_parse_tag_pattern_arg<'a>(
min_indent: u16,
arena: &'a Bump,
state: State<'a>,
) -> ParseResult<'a, Located<Pattern<'a>>, EPattern<'a>> {
) -> ParseResult<'a, Loc<Pattern<'a>>, EPattern<'a>> {
one_of!(
specialize(EPattern::PInParens, loc_pattern_in_parens_help(min_indent)),
loc!(underscore_pattern_help()),
@ -119,7 +116,7 @@ fn loc_parse_tag_pattern_arg<'a>(
fn loc_pattern_in_parens_help<'a>(
min_indent: u16,
) -> impl Parser<'a, Located<Pattern<'a>>, PInParens<'a>> {
) -> impl Parser<'a, Loc<Pattern<'a>>, PInParens<'a>> {
between!(
word1(b'(', PInParens::Open),
space0_around_ee(
@ -159,7 +156,7 @@ fn number_pattern_help<'a>() -> impl Parser<'a, Pattern<'a>, EPattern<'a>> {
fn string_pattern_help<'a>() -> impl Parser<'a, Pattern<'a>, EPattern<'a>> {
specialize(
|_, r, c| EPattern::Start(r, c),
|_, pos| EPattern::Start(pos),
map!(crate::string_literal::parse(), Pattern::StrLiteral),
)
}
@ -167,16 +164,16 @@ fn string_pattern_help<'a>() -> impl Parser<'a, Pattern<'a>, EPattern<'a>> {
fn loc_ident_pattern_help<'a>(
min_indent: u16,
can_have_arguments: bool,
) -> impl Parser<'a, Located<Pattern<'a>>, EPattern<'a>> {
) -> impl Parser<'a, Loc<Pattern<'a>>, EPattern<'a>> {
move |arena: &'a Bump, state: State<'a>| {
let original_state = state;
let original_state = state.clone();
let (_, loc_ident, state) =
specialize(|_, r, c| EPattern::Start(r, c), loc!(parse_ident)).parse(arena, state)?;
specialize(|_, pos| EPattern::Start(pos), loc!(parse_ident)).parse(arena, state)?;
match loc_ident.value {
Ident::GlobalTag(tag) => {
let loc_tag = Located {
let loc_tag = Loc {
region: loc_ident.region,
value: Pattern::GlobalTag(tag),
};
@ -196,14 +193,14 @@ fn loc_ident_pattern_help<'a>(
let value =
Pattern::Apply(&*arena.alloc(loc_tag), loc_args.into_bump_slice());
Ok((MadeProgress, Located { region, value }, state))
Ok((MadeProgress, Loc { region, value }, state))
}
} else {
Ok((MadeProgress, loc_tag, state))
}
}
Ident::PrivateTag(tag) => {
let loc_tag = Located {
let loc_tag = Loc {
region: loc_ident.region,
value: Pattern::PrivateTag(tag),
};
@ -223,7 +220,7 @@ fn loc_ident_pattern_help<'a>(
let value =
Pattern::Apply(&*arena.alloc(loc_tag), loc_args.into_bump_slice());
Ok((MadeProgress, Located { region, value }, state))
Ok((MadeProgress, Loc { region, value }, state))
}
} else {
Ok((MadeProgress, loc_tag, state))
@ -235,13 +232,13 @@ fn loc_ident_pattern_help<'a>(
if crate::keyword::KEYWORDS.contains(&parts[0]) {
Err((
NoProgress,
EPattern::End(original_state.line, original_state.column),
EPattern::End(original_state.pos),
original_state,
))
} else if module_name.is_empty() && parts.len() == 1 {
Ok((
MadeProgress,
Located {
Loc {
region: loc_ident.region,
value: Pattern::Identifier(parts[0]),
},
@ -255,7 +252,7 @@ fn loc_ident_pattern_help<'a>(
};
Ok((
MadeProgress,
Located {
Loc {
region: loc_ident.region,
value: Pattern::Malformed(
String::from_str_in(&malformed_str, arena).into_bump_str(),
@ -267,7 +264,7 @@ fn loc_ident_pattern_help<'a>(
}
Ident::AccessorFunction(string) => Ok((
MadeProgress,
Located {
Loc {
region: loc_ident.region,
value: Pattern::Malformed(string),
},
@ -278,7 +275,7 @@ fn loc_ident_pattern_help<'a>(
Ok((
MadeProgress,
Located {
Loc {
region: loc_ident.region,
value: Pattern::MalformedIdent(malformed, problem),
},
@ -307,10 +304,9 @@ fn lowercase_ident_pattern<'a>(
arena: &'a Bump,
state: State<'a>,
) -> ParseResult<'a, &'a str, EPattern<'a>> {
let row = state.line;
let col = state.column;
let pos = state.pos;
specialize(move |_, _, _| EPattern::End(row, col), lowercase_ident()).parse(arena, state)
specialize(move |_, _| EPattern::End(pos), lowercase_ident()).parse(arena, state)
}
#[inline(always)]
@ -337,16 +333,15 @@ fn record_pattern_help<'a>(min_indent: u16) -> impl Parser<'a, Pattern<'a>, PRec
}
}
fn record_pattern_field<'a>(min_indent: u16) -> impl Parser<'a, Located<Pattern<'a>>, PRecord<'a>> {
fn record_pattern_field<'a>(min_indent: u16) -> impl Parser<'a, Loc<Pattern<'a>>, PRecord<'a>> {
use crate::parser::Either::*;
move |arena, state: State<'a>| {
// You must have a field name, e.g. "email"
// using the initial row/col is important for error reporting
let row = state.line;
let col = state.column;
// using the initial pos is important for error reporting
let pos = state.pos;
let (progress, loc_label, state) = loc!(specialize(
move |_, _, _| PRecord::Field(row, col),
move |_, _| PRecord::Field(pos),
lowercase_ident()
))
.parse(arena, state)?;
@ -370,7 +365,7 @@ fn record_pattern_field<'a>(min_indent: u16) -> impl Parser<'a, Located<Pattern<
space0_before_e(val_parser, min_indent, PRecord::Space, PRecord::IndentColon)
.parse(arena, state)?;
let Located {
let Loc {
value: label,
region,
} = loc_label;
@ -379,7 +374,7 @@ fn record_pattern_field<'a>(min_indent: u16) -> impl Parser<'a, Located<Pattern<
Ok((
MadeProgress,
Located::at(
Loc::at(
region,
Pattern::RequiredField(
label,
@ -400,7 +395,7 @@ fn record_pattern_field<'a>(min_indent: u16) -> impl Parser<'a, Located<Pattern<
space0_before_e(val_parser, min_indent, PRecord::Space, PRecord::IndentColon)
.parse(arena, state)?;
let Located {
let Loc {
value: label,
region,
} = loc_label;
@ -409,7 +404,7 @@ fn record_pattern_field<'a>(min_indent: u16) -> impl Parser<'a, Located<Pattern<
Ok((
MadeProgress,
Located::at(
Loc::at(
region,
Pattern::OptionalField(
label,
@ -424,14 +419,14 @@ fn record_pattern_field<'a>(min_indent: u16) -> impl Parser<'a, Located<Pattern<
// If no value was provided, record it as a Var.
// Canonicalize will know what to do with a Var later.
None => {
let Located { value, region } = loc_label;
let Loc { value, region } = loc_label;
let value = if !spaces.is_empty() {
Pattern::SpaceAfter(arena.alloc(Pattern::Identifier(value)), spaces)
} else {
Pattern::Identifier(value)
};
Ok((MadeProgress, Located::at(region, value), state))
Ok((MadeProgress, Loc::at(region, value), state))
}
}
}

146
compiler/parse/src/state.rs Normal file
View file

@ -0,0 +1,146 @@
use crate::parser::Progress::*;
use crate::parser::{BadInputError, Progress};
use bumpalo::Bump;
use roc_region::all::{Position, Region};
use std::fmt;
/// A position in a source file.
#[derive(Clone)]
pub struct State<'a> {
/// The raw input bytes from the file.
bytes: &'a [u8],
/// Current position within the input (line/column)
pub pos: Position,
/// Current indentation level, in columns
/// (so no indent is col 1 - this saves an arithmetic operation.)
pub indent_column: u16,
}
impl<'a> State<'a> {
pub fn new(bytes: &'a [u8]) -> State<'a> {
State {
bytes,
pos: Position::default(),
indent_column: 0,
}
}
pub fn bytes(&self) -> &'a [u8] {
self.bytes
}
#[must_use]
pub fn advance(&self, offset: usize) -> State<'a> {
let mut state = self.clone();
state.bytes = &state.bytes[offset..];
state
}
/// Returns the current position
// TODO: replace this with just accessing the field
pub const fn get_position(&self) -> Position {
self.pos
}
/// Returns whether the parser has reached the end of the input
pub const fn has_reached_end(&self) -> bool {
self.bytes.is_empty()
}
/// Use advance_spaces to advance with indenting.
/// This assumes we are *not* advancing with spaces, or at least that
/// any spaces on the line were preceded by non-spaces - which would mean
/// they weren't eligible to indent anyway.
pub fn advance_without_indenting_e<TE, E>(
self,
quantity: usize,
to_error: TE,
) -> Result<Self, (Progress, E, Self)>
where
TE: Fn(BadInputError, Position) -> E,
{
self.advance_without_indenting_ee(quantity, |p| to_error(BadInputError::LineTooLong, p))
}
pub fn advance_without_indenting_ee<TE, E>(
self,
quantity: usize,
to_error: TE,
) -> Result<Self, (Progress, E, Self)>
where
TE: Fn(Position) -> E,
{
match (self.pos.column as usize).checked_add(quantity) {
Some(column_usize) if column_usize <= u16::MAX as usize => {
Ok(State {
bytes: &self.bytes[quantity..],
pos: Position {
line: self.pos.line,
column: column_usize as u16,
},
// Once we hit a nonspace character, we are no longer indenting.
..self
})
}
_ => Err((NoProgress, to_error(self.pos), self)),
}
}
/// Returns a Region corresponding to the current state, but
/// with the the end column advanced by the given amount. This is
/// useful when parsing something "manually" (using input.chars())
/// and thus wanting a Region while not having access to loc().
pub fn len_region(&self, length: u16) -> Region {
Region::new(
self.pos,
Position {
line: self.pos.line,
column: self
.pos
.column
.checked_add(length)
.unwrap_or_else(|| panic!("len_region overflowed")),
},
)
}
/// Return a failing ParseResult for the given FailReason
pub fn fail<T, X>(
self,
_arena: &'a Bump,
progress: Progress,
reason: X,
) -> Result<(Progress, T, Self), (Progress, X, Self)> {
Err((progress, reason, self))
}
}
impl<'a> fmt::Debug for State<'a> {
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
write!(f, "State {{")?;
match std::str::from_utf8(self.bytes) {
Ok(string) => write!(f, "\n\tbytes: [utf8] {:?}", string)?,
Err(_) => write!(f, "\n\tbytes: [invalid utf8] {:?}", self.bytes)?,
}
write!(
f,
"\n\t(line, col): ({}, {}),",
self.pos.line, self.pos.column
)?;
write!(f, "\n\tindent_column: {}", self.indent_column)?;
write!(f, "\n}}")
}
}
#[test]
fn state_size() {
// State should always be under 8 machine words, so it fits in a typical
// cache line.
let state_size = std::mem::size_of::<State>();
let maximum = std::mem::size_of::<usize>() * 8;
assert!(state_size <= maximum, "{:?} <= {:?}", state_size, maximum);
}

View file

@ -1,7 +1,8 @@
use crate::ast::{EscapedChar, StrLiteral, StrSegment};
use crate::expr;
use crate::parser::Progress::*;
use crate::parser::{allocated, loc, specialize_ref, word1, BadInputError, EString, Parser, State};
use crate::parser::{allocated, loc, specialize_ref, word1, BadInputError, EString, Parser};
use crate::state::State;
use bumpalo::collections::vec::Vec;
use bumpalo::Bump;
@ -11,38 +12,29 @@ fn ascii_hex_digits<'a>() -> impl Parser<'a, &'a str, EString<'a>> {
move |arena, state: State<'a>| {
let mut buf = bumpalo::collections::String::new_in(arena);
for &byte in state.bytes.iter() {
for &byte in state.bytes().iter() {
if (byte as char).is_ascii_hexdigit() {
buf.push(byte as char);
} else if buf.is_empty() {
// We didn't find any hex digits!
return Err((
NoProgress,
EString::CodePtEnd(state.line, state.column),
state,
));
return Err((NoProgress, EString::CodePtEnd(state.pos), state));
} else {
let state = state.advance_without_indenting_ee(buf.len(), |r, c| {
EString::Space(BadInputError::LineTooLong, r, c)
let state = state.advance_without_indenting_ee(buf.len(), |pos| {
EString::Space(BadInputError::LineTooLong, pos)
})?;
return Ok((MadeProgress, buf.into_bump_str(), state));
}
}
Err((
NoProgress,
EString::CodePtEnd(state.line, state.column),
state,
))
Err((NoProgress, EString::CodePtEnd(state.pos), state))
}
}
macro_rules! advance_state {
($state:expr, $n:expr) => {
$state.advance_without_indenting_ee($n, |r, c| {
EString::Space(BadInputError::LineTooLong, r, c)
})
$state
.advance_without_indenting_ee($n, |pos| EString::Space(BadInputError::LineTooLong, pos))
};
}
@ -53,18 +45,18 @@ pub fn parse<'a>() -> impl Parser<'a, StrLiteral<'a>, EString<'a>> {
let is_multiline;
let mut bytes;
if state.bytes.starts_with(b"\"\"\"") {
if state.bytes().starts_with(b"\"\"\"") {
// we will be parsing a multi-string
is_multiline = true;
bytes = state.bytes[3..].iter();
bytes = state.bytes()[3..].iter();
state = advance_state!(state, 3)?;
} else if state.bytes.starts_with(b"\"") {
} else if state.bytes().starts_with(b"\"") {
// we will be parsing a single-string
is_multiline = false;
bytes = state.bytes[1..].iter();
bytes = state.bytes()[1..].iter();
state = advance_state!(state, 1)?;
} else {
return Err((NoProgress, EString::Open(state.line, state.column), state));
return Err((NoProgress, EString::Open(state.pos), state));
}
// At the parsing stage we keep the entire raw string, because the formatter
@ -97,7 +89,7 @@ pub fn parse<'a>() -> impl Parser<'a, StrLiteral<'a>, EString<'a>> {
// something which signalled that we should end the
// current segment - so use segment_parsed_bytes - 1 here,
// to exclude that char we just parsed.
let string_bytes = &state.bytes[0..(segment_parsed_bytes - 1)];
let string_bytes = &state.bytes()[0..(segment_parsed_bytes - 1)];
match std::str::from_utf8(string_bytes) {
Ok(string) => {
@ -108,7 +100,7 @@ pub fn parse<'a>() -> impl Parser<'a, StrLiteral<'a>, EString<'a>> {
Err(_) => {
return Err((
MadeProgress,
EString::Space(BadInputError::BadUtf8, state.line, state.column),
EString::Space(BadInputError::BadUtf8, state.pos),
state,
));
}
@ -200,11 +192,7 @@ pub fn parse<'a>() -> impl Parser<'a, StrLiteral<'a>, EString<'a>> {
// all remaining chars. This will mask all other errors, but
// it should make it easiest to debug; the file will be a giant
// error starting from where the open quote appeared.
return Err((
MadeProgress,
EString::EndlessSingle(state.line, state.column),
state,
));
return Err((MadeProgress, EString::EndlessSingle(state.pos), state));
}
}
b'\\' => {
@ -224,7 +212,7 @@ pub fn parse<'a>() -> impl Parser<'a, StrLiteral<'a>, EString<'a>> {
// Advance past the `\(` before using the expr parser
state = advance_state!(state, 2)?;
let original_byte_count = state.bytes.len();
let original_byte_count = state.bytes().len();
// This is an interpolated variable.
// Parse an arbitrary expression, then give a
@ -237,7 +225,7 @@ pub fn parse<'a>() -> impl Parser<'a, StrLiteral<'a>, EString<'a>> {
.parse(arena, state)?;
// Advance the iterator past the expr we just parsed.
for _ in 0..(original_byte_count - new_state.bytes.len()) {
for _ in 0..(original_byte_count - new_state.bytes().len()) {
bytes.next();
}
@ -251,7 +239,7 @@ pub fn parse<'a>() -> impl Parser<'a, StrLiteral<'a>, EString<'a>> {
// Advance past the `\u` before using the expr parser
state = advance_state!(state, 2)?;
let original_byte_count = state.bytes.len();
let original_byte_count = state.bytes().len();
// Parse the hex digits, surrounded by parens, then
// give a canonicalization error if the digits form
@ -264,7 +252,7 @@ pub fn parse<'a>() -> impl Parser<'a, StrLiteral<'a>, EString<'a>> {
.parse(arena, state)?;
// Advance the iterator past the expr we just parsed.
for _ in 0..(original_byte_count - new_state.bytes.len()) {
for _ in 0..(original_byte_count - new_state.bytes().len()) {
bytes.next();
}
@ -293,11 +281,7 @@ pub fn parse<'a>() -> impl Parser<'a, StrLiteral<'a>, EString<'a>> {
// Invalid escape! A backslash must be followed
// by either an open paren or else one of the
// escapable characters (\n, \t, \", \\, etc)
return Err((
MadeProgress,
EString::UnknownEscape(state.line, state.column),
state,
));
return Err((MadeProgress, EString::UnknownEscape(state.pos), state));
}
}
}
@ -311,9 +295,9 @@ pub fn parse<'a>() -> impl Parser<'a, StrLiteral<'a>, EString<'a>> {
Err((
MadeProgress,
if is_multiline {
EString::EndlessMulti(state.line, state.column)
EString::EndlessMulti(state.pos)
} else {
EString::EndlessSingle(state.line, state.column)
EString::EndlessSingle(state.pos)
},
state,
))

View file

@ -2,10 +2,11 @@ use crate::ast;
use crate::module::module_defs;
// use crate::module::module_defs;
use crate::parser::Parser;
use crate::parser::{State, SyntaxError};
use crate::parser::SyntaxError;
use crate::state::State;
use bumpalo::collections::Vec as BumpVec;
use bumpalo::Bump;
use roc_region::all::Located;
use roc_region::all::Loc;
pub fn parse_expr_with<'a>(
arena: &'a Bump,
@ -18,7 +19,7 @@ pub fn parse_expr_with<'a>(
pub fn parse_loc_with<'a>(
arena: &'a Bump,
input: &'a str,
) -> Result<Located<ast::Expr<'a>>, SyntaxError<'a>> {
) -> Result<Loc<ast::Expr<'a>>, SyntaxError<'a>> {
let state = State::new(input.trim().as_bytes());
match crate::expr::test_parse_expr(0, arena, state) {
@ -30,7 +31,7 @@ pub fn parse_loc_with<'a>(
pub fn parse_defs_with<'a>(
arena: &'a Bump,
input: &'a str,
) -> Result<BumpVec<'a, Located<ast::Def<'a>>>, SyntaxError<'a>> {
) -> Result<BumpVec<'a, Loc<ast::Def<'a>>>, SyntaxError<'a>> {
let state = State::new(input.trim().as_bytes());
match module_defs().parse(arena, state) {

View file

@ -1,20 +1,21 @@
use crate::ast::{AssignedField, Tag, TypeAnnotation};
use crate::ast::{AliasHeader, AssignedField, Pattern, Tag, TypeAnnotation};
use crate::blankspace::{space0_around_ee, space0_before_e, space0_e};
use crate::keyword;
use crate::parser::{
allocated, backtrackable, optional, specialize, specialize_ref, word1, word2, EType,
ETypeApply, ETypeInParens, ETypeRecord, ETypeTagUnion, ParseResult, Parser,
ETypeApply, ETypeInParens, ETypeInlineAlias, ETypeRecord, ETypeTagUnion, ParseResult, Parser,
Progress::{self, *},
State,
};
use crate::state::State;
use bumpalo::collections::vec::Vec;
use bumpalo::Bump;
use roc_region::all::{Located, Region};
use roc_region::all::{Loc, Position, Region};
pub fn located_help<'a>(
min_indent: u16,
) -> impl Parser<'a, Located<TypeAnnotation<'a>>, EType<'a>> {
expression(min_indent)
is_trailing_comma_valid: bool,
) -> impl Parser<'a, Loc<TypeAnnotation<'a>>, EType<'a>> {
expression(min_indent, is_trailing_comma_valid)
}
#[inline(always)]
@ -46,11 +47,64 @@ fn tag_union_type<'a>(min_indent: u16) -> impl Parser<'a, TypeAnnotation<'a>, ET
}
}
fn fail_type_start<'a, T: 'a>() -> impl Parser<'a, T, EType<'a>> {
|_arena, state: State<'a>| Err((NoProgress, EType::TStart(state.line, state.column), state))
fn check_type_alias(
p: Progress,
annot: Loc<TypeAnnotation>,
) -> impl Parser<AliasHeader, ETypeInlineAlias> {
move |arena, state| match annot.value {
TypeAnnotation::Apply("", tag_name, vars) => {
let mut var_names = Vec::new_in(arena);
var_names.reserve(vars.len());
for var in vars {
if let TypeAnnotation::BoundVariable(v) = var.value {
var_names.push(Loc::at(var.region, Pattern::Identifier(v)));
} else {
return Err((
p,
ETypeInlineAlias::ArgumentNotLowercase(var.region.start()),
state,
));
}
}
let name_start = annot.region.start();
let name_region =
Region::between(name_start, name_start.bump_column(tag_name.len() as u16));
let header = AliasHeader {
name: Loc::at(name_region, tag_name),
vars: var_names.into_bump_slice(),
};
Ok((p, header, state))
}
TypeAnnotation::Apply(_, _, _) => {
Err((p, ETypeInlineAlias::Qualified(annot.region.start()), state))
}
_ => Err((p, ETypeInlineAlias::NotAnAlias(annot.region.start()), state)),
}
}
fn term<'a>(min_indent: u16) -> impl Parser<'a, Located<TypeAnnotation<'a>>, EType<'a>> {
fn parse_type_alias_after_as<'a>(min_indent: u16) -> impl Parser<'a, AliasHeader<'a>, EType<'a>> {
move |arena, state| {
space0_before_e(
term(min_indent),
min_indent,
EType::TSpace,
EType::TAsIndentStart,
)
.parse(arena, state)
.and_then(|(p, annot, state)| {
specialize(EType::TInlineAlias, check_type_alias(p, annot)).parse(arena, state)
})
}
}
fn fail_type_start<'a, T: 'a>() -> impl Parser<'a, T, EType<'a>> {
|_arena, state: State<'a>| Err((NoProgress, EType::TStart(state.pos), state))
}
fn term<'a>(min_indent: u16) -> impl Parser<'a, Loc<TypeAnnotation<'a>>, EType<'a>> {
map_with_arena!(
and!(
one_of!(
@ -71,12 +125,7 @@ fn term<'a>(min_indent: u16) -> impl Parser<'a, Located<TypeAnnotation<'a>>, ETy
backtrackable(space0_e(min_indent, EType::TSpace, EType::TIndentEnd)),
crate::parser::keyword_e(keyword::AS, EType::TEnd)
),
space0_before_e(
term(min_indent),
min_indent,
EType::TSpace,
EType::TAsIndentStart
)
parse_type_alias_after_as(min_indent)
),
Some
),
@ -84,44 +133,39 @@ fn term<'a>(min_indent: u16) -> impl Parser<'a, Located<TypeAnnotation<'a>>, ETy
]
),
|arena: &'a Bump,
(loc_ann, opt_as): (
Located<TypeAnnotation<'a>>,
Option<(&'a [_], Located<TypeAnnotation<'a>>)>
)| {
(loc_ann, opt_as): (Loc<TypeAnnotation<'a>>, Option<(&'a [_], AliasHeader<'a>)>)| {
match opt_as {
Some((spaces, loc_as)) => {
let region = Region::span_across(&loc_ann.region, &loc_as.region);
let value =
TypeAnnotation::As(arena.alloc(loc_ann), spaces, arena.alloc(loc_as));
Some((spaces, alias)) => {
let alias_vars_region =
Region::across_all(alias.vars.iter().map(|v| &v.region));
let region = Region::span_across(&loc_ann.region, &alias_vars_region);
let value = TypeAnnotation::As(arena.alloc(loc_ann), spaces, alias);
Located { region, value }
Loc { region, value }
}
None => loc_ann,
}
}
)
.trace("type_annotation:term")
}
/// The `*` type variable, e.g. in (List *) Wildcard,
fn loc_wildcard<'a>() -> impl Parser<'a, Located<TypeAnnotation<'a>>, EType<'a>> {
map!(loc!(word1(b'*', EType::TWildcard)), |loc_val: Located<
(),
>| {
fn loc_wildcard<'a>() -> impl Parser<'a, Loc<TypeAnnotation<'a>>, EType<'a>> {
map!(loc!(word1(b'*', EType::TWildcard)), |loc_val: Loc<()>| {
loc_val.map(|_| TypeAnnotation::Wildcard)
})
}
/// The `_` indicating an inferred type, e.g. in (List _)
fn loc_inferred<'a>() -> impl Parser<'a, Located<TypeAnnotation<'a>>, EType<'a>> {
map!(loc!(word1(b'_', EType::TInferred)), |loc_val: Located<
(),
>| {
fn loc_inferred<'a>() -> impl Parser<'a, Loc<TypeAnnotation<'a>>, EType<'a>> {
map!(loc!(word1(b'_', EType::TInferred)), |loc_val: Loc<()>| {
loc_val.map(|_| TypeAnnotation::Inferred)
})
}
fn loc_applied_arg<'a>(min_indent: u16) -> impl Parser<'a, Located<TypeAnnotation<'a>>, EType<'a>> {
fn loc_applied_arg<'a>(min_indent: u16) -> impl Parser<'a, Loc<TypeAnnotation<'a>>, EType<'a>> {
use crate::ast::Spaceable;
map_with_arena!(
@ -137,11 +181,11 @@ fn loc_applied_arg<'a>(min_indent: u16) -> impl Parser<'a, Located<TypeAnnotatio
loc!(parse_type_variable)
)
),
|arena: &'a Bump, (spaces, argument): (&'a [_], Located<TypeAnnotation<'a>>)| {
|arena: &'a Bump, (spaces, argument): (&'a [_], Loc<TypeAnnotation<'a>>)| {
if spaces.is_empty() {
argument
} else {
let Located { region, value } = argument;
let Loc { region, value } = argument;
arena.alloc(value).with_spaces_before(spaces, region)
}
}
@ -150,11 +194,11 @@ fn loc_applied_arg<'a>(min_indent: u16) -> impl Parser<'a, Located<TypeAnnotatio
fn loc_type_in_parens<'a>(
min_indent: u16,
) -> impl Parser<'a, Located<TypeAnnotation<'a>>, ETypeInParens<'a>> {
) -> impl Parser<'a, Loc<TypeAnnotation<'a>>, ETypeInParens<'a>> {
between!(
word1(b'(', ETypeInParens::Open),
space0_around_ee(
move |arena, state| specialize_ref(ETypeInParens::Type, expression(min_indent))
move |arena, state| specialize_ref(ETypeInParens::Type, expression(min_indent, true))
.parse(arena, state),
min_indent,
ETypeInParens::Space,
@ -189,15 +233,14 @@ fn tag_type<'a>(min_indent: u16) -> impl Parser<'a, Tag<'a>, ETypeTagUnion<'a>>
}
}
use crate::parser::{Col, Row};
fn parse_tag_name<'a, F, E>(to_problem: F) -> impl Parser<'a, &'a str, E>
where
F: Fn(Row, Col) -> E,
F: Fn(Position) -> E,
E: 'a,
{
move |arena, state: State<'a>| match crate::ident::tag_name().parse(arena, state) {
Ok(good) => Ok(good),
Err((progress, _, state)) => Err((progress, to_problem(state.line, state.column), state)),
Err((progress, _, state)) => Err((progress, to_problem(state.pos), state)),
}
}
@ -208,13 +251,12 @@ fn record_type_field<'a>(
use crate::parser::Either::*;
use AssignedField::*;
move |arena, state: State<'a>| {
(move |arena, state: State<'a>| {
// You must have a field name, e.g. "email"
// using the initial row/col is important for error reporting
let row = state.line;
let col = state.column;
// using the initial pos is important for error reporting
let pos = state.pos;
let (progress, loc_label, state) = loc!(specialize(
move |_, _, _| ETypeRecord::Field(row, col),
move |_, _| ETypeRecord::Field(pos),
lowercase_ident()
))
.parse(arena, state)?;
@ -231,7 +273,7 @@ fn record_type_field<'a>(
))
.parse(arena, state)?;
let val_parser = specialize_ref(ETypeRecord::Type, term(min_indent));
let val_parser = specialize_ref(ETypeRecord::Type, expression(min_indent, true));
match opt_loc_val {
Some(First(_)) => {
@ -276,14 +318,15 @@ fn record_type_field<'a>(
Ok((MadeProgress, value, state))
}
}
}
})
.trace("type_annotation:record_type_field")
}
#[inline(always)]
fn record_type<'a>(min_indent: u16) -> impl Parser<'a, TypeAnnotation<'a>, ETypeRecord<'a>> {
use crate::type_annotation::TypeAnnotation::*;
move |arena, state| {
(move |arena, state| {
let (_, fields, state) = collection_trailing_sep_e!(
// word1_check_indent!(b'{', TRecord::Open, min_indent, TRecord::IndentOpen),
word1(b'{', ETypeRecord::Open),
@ -305,7 +348,8 @@ fn record_type<'a>(min_indent: u16) -> impl Parser<'a, TypeAnnotation<'a>, EType
let result = Record { fields, ext };
Ok((MadeProgress, result, state))
}
})
.trace("type_annotation:record_type")
}
fn applied_type<'a>(min_indent: u16) -> impl Parser<'a, TypeAnnotation<'a>, EType<'a>> {
@ -316,7 +360,7 @@ fn applied_type<'a>(min_indent: u16) -> impl Parser<'a, TypeAnnotation<'a>, ETyp
// e.g. `Str Float` in `Map Str Float`
loc_applied_args_e(min_indent)
),
|(ctor, args): (TypeAnnotation<'a>, Vec<'a, Located<TypeAnnotation<'a>>>)| {
|(ctor, args): (TypeAnnotation<'a>, Vec<'a, Loc<TypeAnnotation<'a>>>)| {
match &ctor {
TypeAnnotation::Apply(module_name, name, _) => {
if args.is_empty() {
@ -331,16 +375,20 @@ fn applied_type<'a>(min_indent: u16) -> impl Parser<'a, TypeAnnotation<'a>, ETyp
}
}
)
.trace("type_annotation:applied_type")
}
fn loc_applied_args_e<'a>(
min_indent: u16,
) -> impl Parser<'a, Vec<'a, Located<TypeAnnotation<'a>>>, EType<'a>> {
) -> impl Parser<'a, Vec<'a, Loc<TypeAnnotation<'a>>>, EType<'a>> {
zero_or_more!(loc_applied_arg(min_indent))
}
fn expression<'a>(min_indent: u16) -> impl Parser<'a, Located<TypeAnnotation<'a>>, EType<'a>> {
move |arena, state: State<'a>| {
fn expression<'a>(
min_indent: u16,
is_trailing_comma_valid: bool,
) -> impl Parser<'a, Loc<TypeAnnotation<'a>>, EType<'a>> {
(move |arena, state: State<'a>| {
let (p1, first, state) = space0_before_e(
term(min_indent),
min_indent,
@ -349,65 +397,82 @@ fn expression<'a>(min_indent: u16) -> impl Parser<'a, Located<TypeAnnotation<'a>
)
.parse(arena, state)?;
let (p2, rest, state) = zero_or_more!(skip_first!(
word1(b',', EType::TFunctionArgument),
one_of![
space0_around_ee(
let result = and![
zero_or_more!(skip_first!(
word1(b',', EType::TFunctionArgument),
one_of![
space0_around_ee(
term(min_indent),
min_indent,
EType::TSpace,
EType::TIndentStart,
EType::TIndentEnd
),
|_, state: State<'a>| Err((
NoProgress,
EType::TFunctionArgument(state.pos),
state
))
]
))
.trace("type_annotation:expression:rest_args"),
// TODO this space0 is dropped, so newlines just before the function arrow when there
// is only one argument are not seen by the formatter. Can we do better?
skip_second!(
space0_e(min_indent, EType::TSpace, EType::TIndentStart),
word2(b'-', b'>', EType::TStart)
)
.trace("type_annotation:expression:arrow")
]
.parse(arena, state.clone());
match result {
Ok((p2, (rest, _dropped_spaces), state)) => {
let (p3, return_type, state) = space0_before_e(
term(min_indent),
min_indent,
EType::TSpace,
EType::TIndentStart,
EType::TIndentEnd
),
|_, state: State<'a>| Err((
NoProgress,
EType::TFunctionArgument(state.line, state.column),
state
))
]
))
.parse(arena, state)?;
)
.parse(arena, state)?;
// TODO this space0 is dropped, so newlines just before the function arrow when there
// is only one argument are not seen by the formatter. Can we do better?
let (p3, is_function, state) = optional(skip_first!(
space0_e(min_indent, EType::TSpace, EType::TIndentStart),
word2(b'-', b'>', EType::TStart)
))
.parse(arena, state)?;
// prepare arguments
let mut arguments = Vec::with_capacity_in(rest.len() + 1, arena);
arguments.push(first);
arguments.extend(rest);
let output = arena.alloc(arguments);
if is_function.is_some() {
let (p4, return_type, state) = space0_before_e(
term(min_indent),
min_indent,
EType::TSpace,
EType::TIndentStart,
)
.parse(arena, state)?;
let result = Loc {
region: return_type.region,
value: TypeAnnotation::Function(output, arena.alloc(return_type)),
};
let progress = p1.or(p2).or(p3);
Ok((progress, result, state))
}
Err(err) => {
if !is_trailing_comma_valid {
let (_, comma, _) = optional(skip_first!(
space0_e(min_indent, EType::TSpace, EType::TIndentStart),
word1(b',', EType::TStart)
))
.trace("check trailing comma")
.parse(arena, state.clone())?;
// prepare arguments
let mut arguments = Vec::with_capacity_in(rest.len() + 1, arena);
arguments.push(first);
arguments.extend(rest);
let output = arena.alloc(arguments);
if comma.is_some() {
// If the surrounding scope has declared that a trailing comma is not a valid state
// for a type annotation - and we found one anyway - return an error so that we can
// produce a more useful error message, knowing that the user was probably writing a
// function type and messed up the syntax somehow.
return Err(err);
}
}
let result = Located {
region: return_type.region,
value: TypeAnnotation::Function(output, arena.alloc(return_type)),
};
let progress = p1.or(p2).or(p3).or(p4);
Ok((progress, result, state))
} else {
let progress = p1.or(p2).or(p3);
// if there is no function arrow, there cannot be more than 1 "argument"
if rest.is_empty() {
Ok((progress, first, state))
} else {
// e.g. `Int,Int` without an arrow and return type
panic!()
// We ran into trouble parsing the function bits; just return the single term
Ok((p1, first, state))
}
}
}
})
.trace("type_annotation:expression")
}
/// Parse a basic type annotation that's a combination of variables
@ -430,7 +495,7 @@ fn parse_concrete_type<'a>(
arena: &'a Bump,
state: State<'a>,
) -> ParseResult<'a, TypeAnnotation<'a>, ETypeApply> {
let initial_bytes = state.bytes;
let initial_bytes = state.bytes();
match crate::ident::concrete_type().parse(arena, state) {
Ok((_, (module_name, type_name), state)) => {
@ -438,19 +503,17 @@ fn parse_concrete_type<'a>(
Ok((MadeProgress, answer, state))
}
Err((NoProgress, _, state)) => {
Err((NoProgress, ETypeApply::End(state.line, state.column), state))
}
Err((NoProgress, _, state)) => Err((NoProgress, ETypeApply::End(state.pos), state)),
Err((MadeProgress, _, mut state)) => {
// we made some progress, but ultimately failed.
// that means a malformed type name
let chomped = crate::ident::chomp_malformed(state.bytes);
let delta = initial_bytes.len() - state.bytes.len();
let chomped = crate::ident::chomp_malformed(state.bytes());
let delta = initial_bytes.len() - state.bytes().len();
let parsed_str =
unsafe { std::str::from_utf8_unchecked(&initial_bytes[..chomped + delta]) };
state = state.advance_without_indenting_ee(chomped, |r, c| {
ETypeApply::Space(crate::parser::BadInputError::LineTooLong, r, c)
state = state.advance_without_indenting_ee(chomped, |pos| {
ETypeApply::Space(crate::parser::BadInputError::LineTooLong, pos)
})?;
Ok((MadeProgress, TypeAnnotation::Malformed(parsed_str), state))
@ -468,10 +531,6 @@ fn parse_type_variable<'a>(
Ok((MadeProgress, answer, state))
}
Err((progress, _, state)) => Err((
progress,
EType::TBadTypeVariable(state.line, state.column),
state,
)),
Err((progress, _, state)) => Err((progress, EType::TBadTypeVariable(state.pos), state)),
}
}