Merge remote-tracking branch 'origin/trunk' into builtins-in-roc-delayed-alias

This commit is contained in:
Folkert 2022-03-18 21:25:52 +01:00
commit 4e1197165b
No known key found for this signature in database
GPG key ID: 1F17F6FFD112B97C
181 changed files with 9495 additions and 2273 deletions

View file

@ -23,6 +23,20 @@ pub enum Spaced<'a, T> {
SpaceAfter(&'a Spaced<'a, T>, &'a [CommentOrNewline<'a>]),
}
impl<'a, T> Spaced<'a, T> {
/// A `Spaced` is multiline if it has newlines or comments before or after the item, since
/// comments induce newlines!
pub fn is_multiline(&self) -> bool {
match self {
Spaced::Item(_) => false,
Spaced::SpaceBefore(_, spaces) | Spaced::SpaceAfter(_, spaces) => {
debug_assert!(!spaces.is_empty());
true
}
}
}
}
impl<'a, T: Debug> Debug for Spaced<'a, T> {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
match self {
@ -248,6 +262,22 @@ impl<'a> TypeHeader<'a> {
}
}
/// The `has` keyword associated with ability definitions.
#[derive(Debug, Clone, Copy, PartialEq)]
pub enum Has<'a> {
Has,
SpaceBefore(&'a Has<'a>, &'a [CommentOrNewline<'a>]),
SpaceAfter(&'a Has<'a>, &'a [CommentOrNewline<'a>]),
}
/// An ability demand is a value defining the ability; for example `hash : a -> U64 | a has Hash`
/// for a `Hash` ability.
#[derive(Debug, Clone, Copy, PartialEq)]
pub struct AbilityDemand<'a> {
pub name: Loc<Spaced<'a, &'a str>>,
pub typ: Loc<TypeAnnotation<'a>>,
}
#[derive(Debug, Clone, Copy, PartialEq)]
pub enum Def<'a> {
// TODO in canonicalization, validate the pattern; only certain patterns
@ -269,6 +299,15 @@ pub enum Def<'a> {
typ: Loc<TypeAnnotation<'a>>,
},
/// An ability definition. E.g.
/// Hash has
/// hash : a -> U64 | a has Hash
Ability {
header: TypeHeader<'a>,
loc_has: Loc<Has<'a>>,
demands: &'a [AbilityDemand<'a>],
},
// TODO in canonicalization, check to see if there are any newlines after the
// annotation; if not, and if it's followed by a Body, then the annotation
// applies to that expr! (TODO: verify that the pattern for both annotation and body match.)
@ -304,6 +343,13 @@ impl<'a> Def<'a> {
}
}
#[derive(Debug, Copy, Clone, PartialEq)]
pub struct HasClause<'a> {
pub var: Loc<Spaced<'a, &'a str>>,
// Should always be a zero-argument `Apply`; we'll check this in canonicalization
pub ability: Loc<TypeAnnotation<'a>>,
}
#[derive(Debug, Copy, Clone, PartialEq)]
pub enum TypeAnnotation<'a> {
/// A function. The types of its arguments, then the type of its return value.
@ -343,6 +389,9 @@ pub enum TypeAnnotation<'a> {
/// The `*` type variable, e.g. in (List *)
Wildcard,
/// A "where" clause demanding abilities designated by a `|`, e.g. `a -> U64 | a has Hash`
Where(&'a Loc<TypeAnnotation<'a>>, &'a [Loc<HasClause<'a>>]),
// We preserve this for the formatter; canonicalization ignores it.
SpaceBefore(&'a TypeAnnotation<'a>, &'a [CommentOrNewline<'a>]),
SpaceAfter(&'a TypeAnnotation<'a>, &'a [CommentOrNewline<'a>]),
@ -814,6 +863,15 @@ impl<'a> Spaceable<'a> for Def<'a> {
}
}
impl<'a> Spaceable<'a> for Has<'a> {
fn before(&'a self, spaces: &'a [CommentOrNewline<'a>]) -> Self {
Has::SpaceBefore(self, spaces)
}
fn after(&'a self, spaces: &'a [CommentOrNewline<'a>]) -> Self {
Has::SpaceAfter(self, spaces)
}
}
impl<'a> Expr<'a> {
pub fn loc_ref(&'a self, region: Region) -> Loc<&'a Self> {
Loc {

View file

@ -1,5 +1,5 @@
use crate::ast::{
AssignedField, Collection, CommentOrNewline, Def, Expr, ExtractSpaces, Pattern, Spaceable,
AssignedField, Collection, CommentOrNewline, Def, Expr, ExtractSpaces, Has, Pattern, Spaceable,
TypeAnnotation, TypeHeader,
};
use crate::blankspace::{space0_after_e, space0_around_ee, space0_before_e, space0_e};
@ -1071,6 +1071,187 @@ fn finish_parsing_alias_or_opaque<'a>(
parse_defs_expr(options, start_column, def_state, arena, state)
}
mod ability {
use super::*;
use crate::{
ast::{AbilityDemand, Spaceable, Spaced},
parser::EAbility,
};
/// Parses a single ability demand line; see `parse_demand`.
fn parse_demand_help<'a>(
start_column: u32,
) -> impl Parser<'a, AbilityDemand<'a>, EAbility<'a>> {
map!(
and!(
specialize(|_, pos| EAbility::DemandName(pos), loc!(lowercase_ident())),
skip_first!(
and!(
// TODO: do we get anything from picking up spaces here?
space0_e(start_column, EAbility::DemandName),
word1(b':', EAbility::DemandColon)
),
specialize(
EAbility::Type,
// Require the type to be more indented than the name
type_annotation::located_help(start_column + 1, true)
)
)
),
|(name, typ): (Loc<&'a str>, Loc<TypeAnnotation<'a>>)| {
AbilityDemand {
name: name.map_owned(Spaced::Item),
typ,
}
}
)
}
pub enum IndentLevel {
PendingMin(u32),
Exact(u32),
}
/// Parses an ability demand like `hash : a -> U64 | a has Hash`, in the context of a larger
/// ability definition.
/// This is basically the same as parsing a free-floating annotation, but with stricter rules.
pub fn parse_demand<'a>(
indent: IndentLevel,
) -> impl Parser<'a, (u32, AbilityDemand<'a>), EAbility<'a>> {
move |arena, state: State<'a>| {
let initial = state.clone();
// Put no restrictions on the indent after the spaces; we'll check it manually.
match space0_e(0, EAbility::DemandName).parse(arena, state) {
Err((MadeProgress, fail, _)) => Err((NoProgress, fail, initial)),
Err((NoProgress, fail, _)) => Err((NoProgress, fail, initial)),
Ok((_progress, spaces, state)) => {
match indent {
IndentLevel::PendingMin(min_indent) if state.column() < min_indent => {
let indent_difference = state.column() as i32 - min_indent as i32;
Err((
MadeProgress,
EAbility::DemandAlignment(indent_difference, state.pos()),
initial,
))
}
IndentLevel::Exact(wanted) if state.column() < wanted => {
// This demand is not indented correctly
let indent_difference = state.column() as i32 - wanted as i32;
Err((
// Rollback because the deindent may be because there is a next
// expression
NoProgress,
EAbility::DemandAlignment(indent_difference, state.pos()),
initial,
))
}
IndentLevel::Exact(wanted) if state.column() > wanted => {
// This demand is not indented correctly
let indent_difference = state.column() as i32 - wanted as i32;
Err((
MadeProgress,
EAbility::DemandAlignment(indent_difference, state.pos()),
initial,
))
}
_ => {
let indent_column = state.column();
let parser = parse_demand_help(indent_column);
match parser.parse(arena, state) {
Err((MadeProgress, fail, state)) => {
Err((MadeProgress, fail, state))
}
Err((NoProgress, fail, _)) => {
// We made progress relative to the entire ability definition,
// so this is an error.
Err((MadeProgress, fail, initial))
}
Ok((_, mut demand, state)) => {
// Tag spaces onto the parsed demand name
if !spaces.is_empty() {
demand.name = arena
.alloc(demand.name.value)
.with_spaces_before(spaces, demand.name.region);
}
Ok((MadeProgress, (indent_column, demand), state))
}
}
}
}
}
}
}
}
}
fn finish_parsing_ability<'a>(
start_column: u32,
options: ExprParseOptions,
name: Loc<&'a str>,
args: &'a [Loc<Pattern<'a>>],
loc_has: Loc<Has<'a>>,
arena: &'a Bump,
state: State<'a>,
) -> ParseResult<'a, Expr<'a>, EExpr<'a>> {
let mut demands = Vec::with_capacity_in(2, arena);
let min_indent_for_demand = start_column + 1;
// Parse the first demand. This will determine the indentation level all the
// other demands must observe.
let (_, (demand_indent_level, first_demand), mut state) =
ability::parse_demand(ability::IndentLevel::PendingMin(min_indent_for_demand))
.parse(arena, state)
.map_err(|(progress, err, state)| {
(progress, EExpr::Ability(err, state.pos()), state)
})?;
demands.push(first_demand);
let demand_indent = ability::IndentLevel::Exact(demand_indent_level);
let demand_parser = ability::parse_demand(demand_indent);
loop {
match demand_parser.parse(arena, state.clone()) {
Ok((_, (_indent, demand), next_state)) => {
state = next_state;
demands.push(demand);
}
Err((MadeProgress, problem, old_state)) => {
return Err((
MadeProgress,
EExpr::Ability(problem, old_state.pos()),
old_state,
));
}
Err((NoProgress, _, old_state)) => {
state = old_state;
break;
}
}
}
let def_region = Region::span_across(&name.region, &demands.last().unwrap().typ.region);
let def = Def::Ability {
header: TypeHeader { name, vars: args },
loc_has,
demands: demands.into_bump_slice(),
};
let loc_def = &*(arena.alloc(Loc::at(def_region, def)));
let def_state = DefState {
defs: bumpalo::vec![in arena; loc_def],
spaces_after: &[],
};
parse_defs_expr(options, start_column, def_state, arena, state)
}
fn parse_expr_operator<'a>(
min_indent: u32,
options: ExprParseOptions,
@ -1290,6 +1471,62 @@ fn parse_expr_end<'a>(
match parser.parse(arena, state.clone()) {
Err((MadeProgress, f, s)) => Err((MadeProgress, f, s)),
Ok((
_,
has @ Loc {
value:
Expr::Var {
module_name: "",
ident: "has",
},
..
},
state,
)) if matches!(expr_state.expr.value, Expr::GlobalTag(..)) => {
// This is an ability definition, `Ability arg1 ... has ...`.
let name = expr_state.expr.map_owned(|e| match e {
Expr::GlobalTag(name) => name,
_ => unreachable!(),
});
let mut arguments = Vec::with_capacity_in(expr_state.arguments.len(), arena);
for argument in expr_state.arguments {
match expr_to_pattern_help(arena, &argument.value) {
Ok(good) => {
arguments.push(Loc::at(argument.region, good));
}
Err(_) => {
let start = argument.region.start();
let err = &*arena.alloc(EPattern::Start(start));
return Err((
MadeProgress,
EExpr::Pattern(err, argument.region.start()),
state,
));
}
}
}
// Attach any spaces to the `has` keyword
let has = if !expr_state.spaces_after.is_empty() {
arena
.alloc(Has::Has)
.with_spaces_before(expr_state.spaces_after, has.region)
} else {
Loc::at(has.region, Has::Has)
};
finish_parsing_ability(
start_column,
options,
name,
arguments.into_bump_slice(),
has,
arena,
state,
)
}
Ok((_, mut arg, state)) => {
let new_end = state.pos();
@ -1762,6 +1999,7 @@ mod when {
((_, _), _),
State<'a>,
) = branch_alternatives(min_indent, options, None).parse(arena, state)?;
let original_indent = pattern_indent_level;
state.indent_column = pattern_indent_level;

View file

@ -104,6 +104,7 @@ impl_space_problem! {
ETypeTagUnion<'a>,
ETypedIdent<'a>,
EWhen<'a>,
EAbility<'a>,
PInParens<'a>,
PRecord<'a>
}
@ -331,6 +332,7 @@ pub enum EExpr<'a> {
DefMissingFinalExpr2(&'a EExpr<'a>, Position),
Type(EType<'a>, Position),
Pattern(&'a EPattern<'a>, Position),
Ability(EAbility<'a>, Position),
IndentDefBody(Position),
IndentEquals(Position),
IndentAnnotation(Position),
@ -472,6 +474,16 @@ pub enum EWhen<'a> {
PatternAlignment(u32, Position),
}
#[derive(Debug, Clone, PartialEq, Eq)]
pub enum EAbility<'a> {
Space(BadInputError, Position),
Type(EType<'a>, Position),
DemandAlignment(i32, Position),
DemandName(Position),
DemandColon(Position),
}
#[derive(Debug, Clone, PartialEq, Eq)]
pub enum EIf<'a> {
Space(BadInputError, Position),
@ -564,6 +576,8 @@ pub enum EType<'a> {
TStart(Position),
TEnd(Position),
TFunctionArgument(Position),
TWhereBar(Position),
THasClause(Position),
///
TIndentStart(Position),
TIndentEnd(Position),
@ -1406,6 +1420,32 @@ where
}
}
pub fn word3<'a, ToError, E>(
word_1: u8,
word_2: u8,
word_3: u8,
to_error: ToError,
) -> impl Parser<'a, (), E>
where
ToError: Fn(Position) -> E,
E: 'a,
{
debug_assert_ne!(word_1, b'\n');
debug_assert_ne!(word_2, b'\n');
debug_assert_ne!(word_3, b'\n');
let needle = [word_1, word_2, word_3];
move |_arena: &'a Bump, state: State<'a>| {
if state.bytes().starts_with(&needle) {
let state = state.advance(3);
Ok((MadeProgress, (), state))
} else {
Err((NoProgress, to_error(state.pos()), state))
}
}
}
#[macro_export]
macro_rules! word1_check_indent {
($word:expr, $word_problem:expr, $min_indent:expr, $indent_problem:expr) => {

View file

@ -1,8 +1,12 @@
use crate::ast::{AssignedField, Pattern, Tag, TypeAnnotation, TypeHeader};
use crate::ast::{
AssignedField, CommentOrNewline, HasClause, Pattern, Spaced, Tag, TypeAnnotation, TypeHeader,
};
use crate::blankspace::{space0_around_ee, space0_before_e, space0_e};
use crate::ident::lowercase_ident;
use crate::keyword;
use crate::parser::then;
use crate::parser::{
allocated, backtrackable, optional, specialize, specialize_ref, word1, word2, EType,
allocated, backtrackable, optional, specialize, specialize_ref, word1, word2, word3, EType,
ETypeApply, ETypeInParens, ETypeInlineAlias, ETypeRecord, ETypeTagUnion, ParseResult, Parser,
Progress::{self, *},
};
@ -240,7 +244,6 @@ where
fn record_type_field<'a>(
min_indent: u32,
) -> impl Parser<'a, AssignedField<'a, TypeAnnotation<'a>>, ETypeRecord<'a>> {
use crate::ident::lowercase_ident;
use crate::parser::Either::*;
use AssignedField::*;
@ -368,6 +371,75 @@ fn loc_applied_args_e<'a>(
zero_or_more!(loc_applied_arg(min_indent))
}
fn has_clause<'a>(min_indent: u32) -> impl Parser<'a, Loc<HasClause<'a>>, EType<'a>> {
map!(
// Suppose we are trying to parse "a has Hash"
and!(
space0_around_ee(
// Parse "a", with appropriate spaces
specialize(
|_, pos| EType::TBadTypeVariable(pos),
loc!(map!(lowercase_ident(), Spaced::Item)),
),
min_indent,
EType::TIndentStart,
EType::TIndentEnd
),
then(
// Parse "has"; we don't care about this keyword
word3(b'h', b'a', b's', EType::THasClause),
// Parse "Hash"; this may be qualified from another module like "Hash.Hash"
|arena, state, _progress, _output| {
space0_before_e(
specialize(EType::TApply, loc!(parse_concrete_type)),
state.column() + 1,
EType::TIndentStart,
)
.parse(arena, state)
}
)
),
|(var, ability): (Loc<Spaced<'a, &'a str>>, Loc<TypeAnnotation<'a>>)| {
let region = Region::span_across(&var.region, &ability.region);
let has_clause = HasClause { var, ability };
Loc::at(region, has_clause)
}
)
}
/// Parse a chain of `has` clauses, e.g. " | a has Hash, b has Eq".
/// Returns the clauses and spaces before the starting "|", if there were any.
fn has_clause_chain<'a>(
min_indent: u32,
) -> impl Parser<'a, (&'a [CommentOrNewline<'a>], &'a [Loc<HasClause<'a>>]), EType<'a>> {
move |arena, state: State<'a>| {
let (_, (spaces_before, ()), state) = and!(
space0_e(min_indent, EType::TIndentStart),
word1(b'|', EType::TWhereBar)
)
.parse(arena, state)?;
let min_demand_indent = state.column() + 1;
// Parse the first clause (there must be one), then the rest
let (_, first_clause, state) = has_clause(min_demand_indent).parse(arena, state)?;
let (_, mut clauses, state) = zero_or_more!(skip_first!(
word1(b',', EType::THasClause),
has_clause(min_demand_indent)
))
.parse(arena, state)?;
// Usually the number of clauses shouldn't be too large, so this is okay
clauses.insert(0, first_clause);
Ok((
MadeProgress,
(spaces_before, clauses.into_bump_slice()),
state,
))
}
}
fn expression<'a>(
min_indent: u32,
is_trailing_comma_valid: bool,
@ -404,7 +476,7 @@ fn expression<'a>(
]
.parse(arena, state.clone());
match result {
let (progress, annot, state) = match result {
Ok((p2, (rest, _dropped_spaces), state)) => {
let (p3, return_type, state) =
space0_before_e(term(min_indent), min_indent, EType::TIndentStart)
@ -421,7 +493,7 @@ fn expression<'a>(
value: TypeAnnotation::Function(output, arena.alloc(return_type)),
};
let progress = p1.or(p2).or(p3);
Ok((progress, result, state))
(progress, result, state)
}
Err(err) => {
if !is_trailing_comma_valid {
@ -442,7 +514,36 @@ fn expression<'a>(
}
// We ran into trouble parsing the function bits; just return the single term
Ok((p1, first, state))
(p1, first, state)
}
};
// Finally, try to parse a where clause if there is one.
// The where clause must be at least as deep as where the type annotation started.
let min_where_clause_indent = min_indent;
match has_clause_chain(min_where_clause_indent).parse(arena, state.clone()) {
Ok((where_progress, (spaces_before, has_chain), state)) => {
use crate::ast::Spaceable;
let region = Region::span_across(&annot.region, &has_chain.last().unwrap().region);
let type_annot = if !spaces_before.is_empty() {
let spaced = arena
.alloc(annot.value)
.with_spaces_before(spaces_before, annot.region);
&*arena.alloc(spaced)
} else {
&*arena.alloc(annot)
};
let where_annot = TypeAnnotation::Where(type_annot, has_chain);
Ok((
where_progress.or(progress),
Loc::at(region, where_annot),
state,
))
}
Err(_) => {
// Ran into a problem parsing a where clause; don't suppose there is one.
Ok((progress, annot, state))
}
}
})