Use private/global tags and Access over Field

This commit is contained in:
Richard Feldman 2019-12-02 18:32:05 -05:00
parent 468e285654
commit 1d18f4cc85
17 changed files with 333 additions and 314 deletions

View file

@ -1,21 +1,17 @@
use crate::can::problem::Problem;
use crate::can::procedure::References;
use crate::can::symbol::Symbol;
use crate::collections::{ImMap, MutMap};
use crate::region::Located;
use crate::collections::MutMap;
/// The canonicalization environment for a particular module.
pub struct Env {
/// The module's path. Unqualified references to identifiers and variant names are assumed
/// to be relative to this path.
/// The module's path. Private tags and unqualified references to identifiers
/// are assumed to be relative to this path.
pub home: Box<str>,
/// Problems we've encountered along the way, which will be reported to the user at the end.
pub problems: Vec<Problem>,
/// Variants either declared in this module, or imported.
pub variants: ImMap<Symbol, Located<Box<str>>>,
/// Closures
pub closures: MutMap<Symbol, References>,
@ -24,10 +20,9 @@ pub struct Env {
}
impl Env {
pub fn new(home: Box<str>, declared_variants: ImMap<Symbol, Located<Box<str>>>) -> Env {
pub fn new(home: Box<str>) -> Env {
Env {
home,
variants: declared_variants,
problems: Vec::new(),
closures: MutMap::default(),
tailcallable_symbol: None,

View file

@ -22,8 +22,9 @@ pub enum Expr {
FunctionPointer(Variable, Symbol),
/// Look up exactly one field on a record, e.g. (expr).foo.
/// Canonicalization will convert chains to single-access, e.g. foo.bar.baz to (foo.bar).baz.
Field(Box<Located<Expr>>, Box<str>),
Access(Box<Located<Expr>>, Box<str>),
Tag(Box<str>, Vec<Expr>),
// Pattern Matching
/// Case is guaranteed to be exhaustive at this point. (If it wasn't, then
@ -40,6 +41,8 @@ pub enum Expr {
Box<Located<Expr>>,
),
/// This is *only* for calling functions, not for tag application.
/// The Tag variant contains any applied values inside it.
Call(Box<Expr>, Vec<Located<Expr>>, CalledVia),
Closure(Symbol, Recursive, Vec<Located<Pattern>>, Box<Located<Expr>>),

View file

@ -86,7 +86,6 @@ fn canonicalize_def<'a>(
let variable = var_store.fresh();
let expected = Expected::NoExpectation(Type::Variable(variable));
let declared_idents = ImMap::default(); // TODO FIXME infer this from scope arg
let declared_variants = ImMap::default(); // TODO get rid of this
let name: Box<str> = "TODOfixme".into();
// Desugar operators (convert them to Apply calls, taking into account
@ -102,7 +101,7 @@ fn canonicalize_def<'a>(
// scope_prefix will be "Main.foo$" and its first closure will be named "Main.foo$0"
let scope_prefix = format!("{}.{}$", home, name).into();
let mut scope = Scope::new(scope_prefix, declared_idents.clone());
let mut env = Env::new(home, declared_variants.clone());
let mut env = Env::new(home);
let (loc_expr, _) = canonicalize_expr(
&ImMap::default(),
&mut env,
@ -165,7 +164,6 @@ pub fn canonicalize_declaration<'a>(
region: Region,
loc_expr: Located<ast::Expr<'a>>,
declared_idents: &ImMap<Ident, (Symbol, Region)>,
declared_variants: &ImMap<Symbol, Located<Box<str>>>,
expected: Expected<Type>,
) -> (Located<Expr>, Output, Vec<Problem>) {
// Desugar operators (convert them to Apply calls, taking into account
@ -181,7 +179,7 @@ pub fn canonicalize_declaration<'a>(
// scope_prefix will be "Main.foo$" and its first closure will be named "Main.foo$0"
let scope_prefix = format!("{}.{}$", home, name).into();
let mut scope = Scope::new(scope_prefix, declared_idents.clone());
let mut env = Env::new(home, declared_variants.clone());
let mut env = Env::new(home);
let (loc_expr, output) = canonicalize_expr(
&ImMap::default(),
&mut env,
@ -846,11 +844,11 @@ fn canonicalize_expr(
(expr, output)
}
ast::Expr::Field(_, _)
| ast::Expr::QualifiedField(_, _)
ast::Expr::Access(_, _)
| ast::Expr::AccessorFunction(_)
| ast::Expr::If(_)
| ast::Expr::Variant(_, _)
| ast::Expr::GlobalTag(_)
| ast::Expr::PrivateTag(_)
| ast::Expr::MalformedIdent(_)
| ast::Expr::MalformedClosure
| ast::Expr::PrecedenceConflict(_, _, _) => {
@ -1267,17 +1265,9 @@ fn add_idents_from_pattern<'a>(
// Ignore the newline/comment info; it doesn't matter in canonicalization.
add_idents_from_pattern(region, pattern, scope, answer)
}
Variant(_, _)
| IntLiteral(_)
| HexIntLiteral(_)
| OctalIntLiteral(_)
| BinaryIntLiteral(_)
| FloatLiteral(_)
| StrLiteral(_)
| BlockStrLiteral(_)
| EmptyRecordLiteral
| Malformed(_)
| Underscore => (),
GlobalTag(_) | PrivateTag(_) | IntLiteral(_) | HexIntLiteral(_) | OctalIntLiteral(_)
| BinaryIntLiteral(_) | FloatLiteral(_) | StrLiteral(_) | BlockStrLiteral(_)
| EmptyRecordLiteral | Malformed(_) | Underscore => (),
}
}
@ -1309,17 +1299,9 @@ fn remove_idents(pattern: &ast::Pattern, idents: &mut ImMap<Ident, (Symbol, Regi
// Ignore the newline/comment info; it doesn't matter in canonicalization.
remove_idents(pattern, idents)
}
Variant(_, _)
| IntLiteral(_)
| HexIntLiteral(_)
| BinaryIntLiteral(_)
| OctalIntLiteral(_)
| FloatLiteral(_)
| StrLiteral(_)
| BlockStrLiteral(_)
| EmptyRecordLiteral
| Malformed(_)
| Underscore => {}
GlobalTag(_) | PrivateTag(_) | IntLiteral(_) | HexIntLiteral(_) | BinaryIntLiteral(_)
| OctalIntLiteral(_) | FloatLiteral(_) | StrLiteral(_) | BlockStrLiteral(_)
| EmptyRecordLiteral | Malformed(_) | Underscore => {}
}
}
@ -1638,7 +1620,7 @@ fn can_defs<'a>(
// see below: a closure needs a fresh References!
let mut is_closure = false;
// First, make sure we are actually assigning an identifier instead of (for example) a variant.
// First, make sure we are actually assigning an identifier instead of (for example) a tag.
//
// If we're assigning (UserId userId) = ... then this is certainly not a closure declaration,
// which also implies it's not a self tail call!

View file

@ -49,8 +49,6 @@ pub fn desugar<'a>(arena: &'a Bump, loc_expr: &'a Located<Expr<'a>>) -> &'a Loca
| Nested(Str(_))
| BlockStr(_)
| Nested(BlockStr(_))
| QualifiedField(_, _)
| Nested(QualifiedField(_, _))
| AccessorFunction(_)
| Nested(AccessorFunction(_))
| Var(_, _)
@ -61,13 +59,24 @@ pub fn desugar<'a>(arena: &'a Bump, loc_expr: &'a Located<Expr<'a>>) -> &'a Loca
| Nested(MalformedClosure)
| PrecedenceConflict(_, _, _)
| Nested(PrecedenceConflict(_, _, _))
| Variant(_, _)
| Nested(Variant(_, _)) => loc_expr,
| GlobalTag(_)
| Nested(GlobalTag(_))
| PrivateTag(_)
| Nested(PrivateTag(_)) => loc_expr,
Field(sub_expr, paths) | Nested(Field(sub_expr, paths)) => arena.alloc(Located {
region: loc_expr.region,
value: Field(desugar(arena, sub_expr), paths.clone()),
}),
Access(sub_expr, paths) | Nested(Access(sub_expr, paths)) => {
let region = loc_expr.region;
let loc_sub_expr = Located {
region,
value: Nested(sub_expr),
};
let value = Access(
&desugar(arena, arena.alloc(loc_sub_expr)).value,
paths.clone(),
);
arena.alloc(Located { region, value })
}
List(elems) | Nested(List(elems)) => {
let mut new_elems = Vec::with_capacity_in(elems.len(), arena);
@ -221,7 +230,7 @@ pub fn desugar<'a>(arena: &'a Bump, loc_expr: &'a Located<Expr<'a>>) -> &'a Loca
branches.push(&*arena.alloc((
Located {
value: Pattern::Variant(&[], "False"),
value: Pattern::GlobalTag("False"),
region: pattern_region,
},
Located {

View file

@ -7,7 +7,7 @@ use crate::can::problem::Problem;
use crate::can::scope::Scope;
use crate::can::symbol::Symbol;
use crate::collections::ImMap;
use crate::ident::{Ident, VariantName};
use crate::ident::Ident;
use crate::parse::ast;
use crate::region::{Located, Region};
use crate::subs::VarStore;
@ -19,8 +19,9 @@ use crate::types::{Constraint, PExpected, PatternCategory, Type};
#[derive(Clone, Debug, PartialEq)]
pub enum Pattern {
Identifier(Variable, Symbol),
Variant(Variable, Symbol),
AppliedVariant(Variable, Symbol, Vec<Located<Pattern>>),
Tag(Variable, Symbol),
/// TODO replace regular Tag with this
AppliedTag(Variable, Symbol, Vec<Located<Pattern>>),
IntLiteral(i64),
FloatLiteral(f64),
ExactString(Box<str>),
@ -29,7 +30,6 @@ pub enum Pattern {
// Runtime Exceptions
Shadowed(Located<Ident>),
UnrecognizedVariant(Located<VariantName>),
// Example: (5 = 1 + 2) is an unsupported pattern in an assignment; Int patterns aren't allowed in assignments!
UnsupportedPattern(Region),
}
@ -118,9 +118,10 @@ pub fn canonicalize_pattern<'a>(
let symbol_and_region = (symbol.clone(), region);
// Add this to both scope.idents *and* shadowable_idents.
// The latter is relevant when recursively canonicalizing Variant patterns,
// which can bring multiple new idents into scope. For example, it's important
// that we catch (Blah foo foo) as being an example of shadowing.
// The latter is relevant when recursively canonicalizing
// tag application patterns, which can bring multiple
// new idents into scope. For example, it's important that
// we catch (Blah foo foo) as being an example of shadowing.
scope
.idents
.insert(new_ident.clone(), symbol_and_region.clone());
@ -132,56 +133,18 @@ pub fn canonicalize_pattern<'a>(
}
}
}
&GlobalTag(name) => {
// Canonicalize the tag's name.
let symbol = Symbol::from_global_tag(name);
// &AppliedVariant((ref loc_name, ref loc_args)) => {
// // Canonicalize the variant's arguments.
// let mut can_args: Vec<Located<Pattern>> = Vec::new();
// for loc_arg in loc_args {
// let loc_can_arg =
// canonicalize_pattern(env, subs, constraints, scope, pattern_type, &loc_arg, shadowable_idents);
// can_args.push(loc_can_arg);
// }
// // Canonicalize the variant's name.
// let symbol = Symbol::from_variant(&loc_name.value, &env.home);
// if env.variants.contains_key(&symbol) {
// // No problems; the qualified variant name was in scope!
// Pattern::AppliedVariant(symbol, can_args)
// } else {
// // We couldn't find the variant name in scope. NAMING PROBLEM!
// env.problem(Problem::UnrecognizedVariant(loc_name.clone()));
// Pattern::UnrecognizedVariant(loc_name.clone())
// }
// }
&Variant(module_parts, name) => {
// Canonicalize the variant's name.
let variant = if module_parts.is_empty() {
VariantName::Unqualified(name.to_string())
} else {
VariantName::Qualified(module_parts.to_vec().join("."), name.to_string())
};
let symbol = Symbol::from_variant(&variant, &env.home);
if env.variants.contains_key(&symbol) {
// No problems; the qualified variant name was in scope!
Pattern::Variant(var_store.fresh(), symbol)
} else {
let loc_name = Located {
region,
value: variant,
};
// We couldn't find the variant name in scope. NAMING PROBLEM!
env.problem(Problem::UnrecognizedVariant(loc_name.clone()));
Pattern::UnrecognizedVariant(loc_name.clone())
}
Pattern::Tag(var_store.fresh(), symbol)
}
&PrivateTag(name) => {
// Canonicalize the tag's name.
let symbol = Symbol::from_private_tag(&env.home, name);
Pattern::Tag(var_store.fresh(), symbol)
}
&FloatLiteral(ref string) => match pattern_type {
CaseBranch => {
let float = finish_parsing_float(string)
@ -361,7 +324,8 @@ fn add_constraints<'a>(
add_constraints(pattern, scope, region, expected, state)
}
Variant(_, _)
GlobalTag(_)
| PrivateTag(_)
| Apply(_, _)
| RecordDestructure(_)
| RecordField(_, _)

View file

@ -1,5 +1,5 @@
use crate::can::pattern::PatternType;
use crate::ident::{Ident, VariantName};
use crate::ident::Ident;
use crate::operator::BinOp;
use crate::region::{Located, Region};
@ -9,7 +9,6 @@ pub enum Problem {
Shadowing(Located<Ident>),
UnrecognizedFunctionName(Located<Ident>),
UnrecognizedConstant(Located<Ident>),
UnrecognizedVariant(Located<VariantName>),
UnusedAssignment(Located<Ident>),
UnusedArgument(Located<Ident>),
PrecedenceProblem(PrecedenceProblem),
@ -29,7 +28,6 @@ pub enum RuntimeError {
InvalidPrecedence(PrecedenceProblem, Region),
UnrecognizedFunctionName(Located<Ident>),
UnrecognizedConstant(Located<Ident>),
UnrecognizedVariant(Located<VariantName>),
FloatOutsideRange(Box<str>),
IntOutsideRange(Box<str>),
InvalidHex(std::num::ParseIntError, Box<str>),

View file

@ -46,7 +46,6 @@ impl Procedure {
pub struct References {
pub locals: ImSet<Symbol>,
pub globals: ImSet<Symbol>,
pub variants: ImSet<Symbol>,
pub calls: ImSet<Symbol>,
}
@ -55,7 +54,6 @@ impl References {
References {
locals: ImSet::default(),
globals: ImSet::default(),
variants: ImSet::default(),
calls: ImSet::default(),
}
}
@ -63,7 +61,6 @@ impl References {
pub fn union(mut self, other: References) -> Self {
self.locals = self.locals.union(other.locals);
self.globals = self.globals.union(other.globals);
self.variants = self.variants.union(other.variants);
self.calls = self.calls.union(other.calls);
self
@ -72,8 +69,4 @@ impl References {
pub fn has_local(&self, symbol: &Symbol) -> bool {
self.locals.contains(symbol)
}
pub fn has_variant(&self, symbol: &Symbol) -> bool {
self.variants.contains(symbol)
}
}

View file

@ -1,8 +1,8 @@
use crate::ident::{UnqualifiedIdent, VariantName};
use crate::ident::UnqualifiedIdent;
use crate::module::ModuleName;
use std::fmt;
/// A globally unique identifier, used for both vars and variants.
/// A globally unique identifier, used for both vars and tags.
/// It will be used directly in code gen.
#[derive(Clone, PartialEq, Eq, Hash, PartialOrd, Ord)]
pub struct Symbol(Box<str>);
@ -33,12 +33,12 @@ impl Symbol {
})
}
pub fn from_variant(variant_name: &VariantName, home: &str) -> Symbol {
match variant_name {
VariantName::Unqualified(name) => Symbol::new(home, name),
VariantName::Qualified(path, name) => Symbol::new(path, name),
pub fn from_global_tag(tag_name: &str) -> Symbol {
Symbol(tag_name.into())
}
pub fn from_private_tag(home: &str, tag_name: &str) -> Symbol {
Symbol(format!("{}.{}", home, tag_name).into())
}
pub fn from_module<'a>(

View file

@ -251,13 +251,13 @@ pub fn is_multiline_expr<'a>(expr: &'a Expr<'a>) -> bool {
| OctalInt(_)
| BinaryInt(_)
| Str(_)
| Field(_, _)
| QualifiedField(_, _)
| Access(_, _)
| AccessorFunction(_)
| Var(_, _)
| MalformedIdent(_)
| MalformedClosure
| Variant(_, _) => false,
| GlobalTag(_)
| PrivateTag(_) => false,
// These expressions always have newlines
Defs(_, _) | Case(_, _) => true,

View file

@ -12,12 +12,7 @@ pub fn fmt_pattern<'a>(
match pattern {
Identifier(string) => buf.push_str(string),
Variant(module_parts, name) => {
for part in module_parts.iter() {
buf.push_str(part);
buf.push('.');
}
GlobalTag(name) | PrivateTag(name) => {
buf.push_str(name);
}
Apply(loc_pattern, loc_arg_patterns) => {

View file

@ -40,15 +40,6 @@ impl<'a> UnqualifiedIdent<'a> {
}
}
/// A variant name, possibly fully-qualified with a module name
/// e.g. (Result.Ok)
/// Parameterized on a phantom marker for whether it has been canonicalized
#[derive(Clone, Debug, PartialEq, Eq, Hash)]
pub enum VariantName {
Unqualified(String),
Qualified(String, String),
}
/// An identifier, possibly fully-qualified with a module name
/// e.g. (Http.Request from http)
/// Parameterized on a phantom marker for whether it has been canonicalized
@ -89,12 +80,3 @@ impl Display for Ident {
}
}
}
impl Display for VariantName {
fn fmt(&self, f: &mut Formatter) -> fmt::Result {
match self {
VariantName::Unqualified(name) => write!(f, "{}", name),
VariantName::Qualified(path, name) => write!(f, "{}.{}", path, name),
}
}
}

View file

@ -127,13 +127,10 @@ pub enum Expr<'a> {
// String Literals
Str(&'a str),
BlockStr(&'a [&'a str]),
/// e.g. `(expr).foo.bar` - we rule out nested lookups in canonicalization,
/// but we want to keep the nesting here to give a nicer error message.
Field(&'a Loc<Expr<'a>>, Vec<'a, &'a str>),
/// e.g. `Foo.Bar.baz.qux`
QualifiedField(&'a [&'a str], &'a [&'a str]),
/// Look up exactly one field on a record, e.g. (expr).foo.
Access(&'a Expr<'a>, UnqualifiedIdent<'a>),
/// e.g. `.foo`
AccessorFunction(&'a str),
AccessorFunction(UnqualifiedIdent<'a>),
// Collection Literals
List(Vec<'a, &'a Loc<Expr<'a>>>),
@ -141,7 +138,10 @@ pub enum Expr<'a> {
// Lookups
Var(&'a [&'a str], &'a str),
Variant(&'a [&'a str], &'a str),
// Tags
GlobalTag(&'a str),
PrivateTag(&'a str),
// Pattern Matching
Closure(&'a Vec<'a, Loc<Pattern<'a>>>, &'a Loc<Expr<'a>>),
@ -150,7 +150,7 @@ pub enum Expr<'a> {
// Application
/// To apply by name, do Apply(Var(...), ...)
/// To apply a variant by name, do Apply(Variant(...), ...)
/// To apply a tag by name, do Apply(Tag(...), ...)
Apply(&'a Loc<Expr<'a>>, Vec<'a, &'a Loc<Expr<'a>>>, CalledVia),
BinOp(&'a (Loc<Expr<'a>>, Loc<BinOp>, Loc<Expr<'a>>)),
UnaryOp(&'a Loc<Expr<'a>>, Loc<UnaryOp>),
@ -272,8 +272,8 @@ pub enum Pattern<'a> {
// Identifier
Identifier(&'a str),
// Variant, optionally qualified
Variant(&'a [&'a str], &'a str),
GlobalTag(&'a str),
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
/// around the destructured names, e.g. { x ### x does stuff ###, y }
@ -310,17 +310,12 @@ pub enum Pattern<'a> {
impl<'a> Pattern<'a> {
pub fn from_ident(arena: &'a Bump, ident: Ident<'a>) -> Pattern<'a> {
match ident {
Ident::Var(maybe_qualified) => {
if maybe_qualified.module_parts.is_empty() {
Pattern::Identifier(maybe_qualified.value)
Ident::GlobalTag(string) => Pattern::GlobalTag(string),
Ident::PrivateTag(string) => Pattern::PrivateTag(string),
Ident::Access(maybe_qualified) => {
if maybe_qualified.value.len() == 1 {
Pattern::Identifier(maybe_qualified.value.iter().next().unwrap())
} else {
Pattern::Variant(maybe_qualified.module_parts, maybe_qualified.value)
}
}
Ident::Variant(maybe_qualified) => {
Pattern::Variant(maybe_qualified.module_parts, maybe_qualified.value)
}
Ident::Field(maybe_qualified) => {
let mut buf = String::with_capacity_in(
maybe_qualified.module_parts.len() + maybe_qualified.value.len(),
arena,
@ -344,6 +339,7 @@ impl<'a> Pattern<'a> {
Pattern::Malformed(buf.into_bump_str())
}
}
Ident::AccessorFunction(string) => Pattern::Malformed(string),
Ident::Malformed(string) => Pattern::Malformed(string),
}

View file

@ -8,16 +8,16 @@ use bumpalo::Bump;
/// 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
/// "you can't redefine this variant!" if you wrote `Foo = ...` or
/// "you can't redefine this tag!" if you wrote `Foo = ...` or
/// "you can only define unqualified constants" if you wrote `Foo.bar = ...`
#[derive(Debug, Clone, PartialEq, Eq)]
pub enum Ident<'a> {
/// foo or Bar.Baz.foo
Var(MaybeQualified<'a, &'a str>),
/// Foo or Bar.Baz.Foo
Variant(MaybeQualified<'a, &'a str>),
/// foo.bar or Foo.Bar.baz.qux
Field(MaybeQualified<'a, &'a [&'a str]>),
/// Foo or Bar
GlobalTag(&'a str),
/// @Foo or @Bar
PrivateTag(&'a str),
/// foo or foo.bar or Foo.Bar.baz.qux
Access(MaybeQualified<'a, &'a [&'a str]>),
/// .foo
AccessorFunction(&'a str),
/// .Foo or foo. or something like foo.Bar
@ -29,9 +29,8 @@ impl<'a> Ident<'a> {
use self::Ident::*;
match self {
Var(string) => string.len(),
Variant(string) => string.len(),
Field(string) => string.len(),
GlobalTag(string) | PrivateTag(string) => string.len(),
Access(string) => string.len(),
AccessorFunction(string) => string.len(),
Malformed(string) => string.len(),
}
@ -64,12 +63,31 @@ where
let mut noncapitalized_parts: Vec<&'a str> = Vec::new_in(arena);
let mut is_capitalized;
let is_accessor_fn;
let mut is_private_tag = false;
// Identifiers and accessor functions must start with either a letter or a dot.
// If this starts with neither, it must be something else!
match chars.next() {
Some(ch) => {
if ch.is_alphabetic() {
if ch == '@' {
// '@' must always be followed by a capital letter!
match chars.next() {
Some(ch) if ch.is_uppercase() => {
part_buf.push('@');
part_buf.push(ch);
is_private_tag = true;
is_capitalized = true;
is_accessor_fn = false;
}
Some(ch) => {
return Err(unexpected(ch, 0, state, Attempting::Identifier));
}
None => {
return Err(unexpected_eof(0, Attempting::Identifier, state));
}
}
} else if ch.is_alphabetic() {
part_buf.push(ch);
is_capitalized = ch.is_uppercase();
@ -178,7 +196,7 @@ where
let answer = if is_accessor_fn {
// Handle accessor functions first because they have the strictest requirements.
// Accessor functions may have exactly 1 noncapitalized part, and no capitalzed parts.
if capitalized_parts.is_empty() && noncapitalized_parts.len() == 1 {
if capitalized_parts.is_empty() && noncapitalized_parts.len() == 1 && !is_private_tag {
let value = noncapitalized_parts.iter().next().unwrap();
Ident::AccessorFunction(value)
@ -193,14 +211,28 @@ where
);
}
} else {
match noncapitalized_parts.len() {
0 => {
// We have capitalized parts only, so this must be a variant.
match capitalized_parts.pop() {
Some(value) => Ident::Variant(MaybeQualified {
module_parts: capitalized_parts.into_bump_slice(),
value,
}),
if noncapitalized_parts.is_empty() {
// We have capitalized parts only, so this must be a tag.
match capitalized_parts.first() {
Some(value) => {
if capitalized_parts.len() == 1 {
if is_private_tag {
Ident::PrivateTag(value)
} else {
Ident::GlobalTag(value)
}
} else {
// This is a qualified tag, which is not allowed!
return malformed(
None,
arena,
state,
chars,
capitalized_parts,
noncapitalized_parts,
);
}
}
None => {
// We had neither capitalized nor noncapitalized parts,
// yet we made it this far. The only explanation is that this was
@ -208,24 +240,23 @@ where
return Err(unexpected('.', 1, state, Attempting::Identifier));
}
}
}
1 => {
// We have exactly one noncapitalized part, so this must be a var.
let value = noncapitalized_parts.iter().next().unwrap();
Ident::Var(MaybeQualified {
module_parts: capitalized_parts.into_bump_slice(),
value,
})
}
_ => {
// We have multiple noncapitalized parts, so this must be a field.
Ident::Field(MaybeQualified {
} else if is_private_tag {
// This is qualified field access with an '@' in front, which does not make sense!
return malformed(
None,
arena,
state,
chars,
capitalized_parts,
noncapitalized_parts,
);
} else {
// We have multiple noncapitalized parts, so this must be field access.
Ident::Access(MaybeQualified {
module_parts: capitalized_parts.into_bump_slice(),
value: noncapitalized_parts.into_bump_slice(),
})
}
}
};
let state = state.advance_without_indenting(chars_parsed)?;
@ -288,14 +319,14 @@ pub fn ident<'a>() -> impl Parser<'a, Ident<'a>> {
}
}
pub fn variant_or_ident<'a, F>(pred: F) -> impl Parser<'a, &'a str>
pub fn global_tag_or_ident<'a, F>(pred: F) -> impl Parser<'a, &'a str>
where
F: Fn(char) -> bool,
{
move |arena, state: State<'a>| {
let mut chars = state.input.chars();
// pred will determine if this is a variant or ident (based on capitalization)
// pred will determine if this is a tag or ident (based on capitalization)
let first_letter = match chars.next() {
Some(first_char) => {
if pred(first_char) {
@ -346,12 +377,12 @@ where
/// * 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> {
variant_or_ident(|first_char| first_char.is_lowercase())
global_tag_or_ident(|first_char| first_char.is_lowercase())
}
pub fn unqualified_ident<'a>() -> impl Parser<'a, UnqualifiedIdent<'a>> {
map!(
variant_or_ident(|first_char| first_char.is_alphabetic()),
global_tag_or_ident(|first_char| first_char.is_alphabetic()),
UnqualifiedIdent::new
)
}

View file

@ -10,13 +10,14 @@ pub mod problems;
pub mod string_literal;
pub mod type_annotation;
use crate::ident::UnqualifiedIdent;
use crate::operator::{BinOp, CalledVia, UnaryOp};
use crate::parse::ast::{AssignedField, Attempting, Def, Expr, MaybeQualified, Pattern, Spaceable};
use crate::parse::blankspace::{
space0, space0_after, space0_around, space0_before, space1, space1_after, space1_around,
space1_before,
};
use crate::parse::ident::{ident, lowercase_ident, variant_or_ident, Ident};
use crate::parse::ident::{global_tag_or_ident, ident, lowercase_ident, Ident};
use crate::parse::number_literal::number_literal;
use crate::parse::parser::{
allocated, char, not, not_followed_by, optional, string, then, unexpected, unexpected_eof,
@ -216,13 +217,24 @@ pub fn loc_parenthetical_expr<'a>(min_indent: u16) -> impl Parser<'a, Located<Ex
Ok((Located { value, region }, state))
}
// '.' and a record field immediately after ')', no optional spaces
Some(Either::Second(Either::First(fields))) => Ok((
Some(Either::Second(Either::First(fields))) => {
let mut value = loc_expr.value;
for field in fields {
// Wrap the previous answer in the new one, so we end up
// with a nested Expr. That way, `foo.bar.baz` gets represented
// in the AST as if it had been written (foo.bar).baz all along.
value = Expr::Access(arena.alloc(value), UnqualifiedIdent::new(field));
}
Ok((
Located {
region: loc_expr_with_extras.region,
value: Expr::Field(arena.alloc(loc_expr), fields),
region: loc_expr.region,
value,
},
state,
)),
))
}
None => Ok((loc_expr, state)),
}
},
@ -243,7 +255,8 @@ fn expr_to_pattern<'a>(arena: &'a Bump, expr: &Expr<'a>) -> Result<Pattern<'a>,
}))
}
}
Expr::Variant(module_parts, value) => Ok(Pattern::Variant(module_parts, value)),
Expr::GlobalTag(value) => Ok(Pattern::GlobalTag(value)),
Expr::PrivateTag(value) => Ok(Pattern::PrivateTag(value)),
Expr::Apply(loc_val, loc_args, _) => {
let region = loc_val.region;
let value = expr_to_pattern(arena, &loc_val.value)?;
@ -298,7 +311,7 @@ fn expr_to_pattern<'a>(arena: &'a Bump, expr: &Expr<'a>) -> Result<Pattern<'a>,
// These would not have parsed as patterns
Expr::BlockStr(_)
| Expr::AccessorFunction(_)
| Expr::Field(_, _)
| Expr::Access(_, _)
| Expr::List(_)
| Expr::Closure(_, _)
| Expr::BinOp(_)
@ -307,8 +320,7 @@ fn expr_to_pattern<'a>(arena: &'a Bump, expr: &Expr<'a>) -> Result<Pattern<'a>,
| Expr::Case(_, _)
| Expr::MalformedClosure
| Expr::PrecedenceConflict(_, _, _)
| Expr::UnaryOp(_, _)
| Expr::QualifiedField(_, _) => Err(Fail {
| Expr::UnaryOp(_, _) => Err(Fail {
attempting: Attempting::Def,
reason: FailReason::InvalidPattern,
}),
@ -655,9 +667,10 @@ fn parse_closure_param<'a>(
char(')')
),
// The least common, but still allowed, e.g. \Foo -> ...
loc!(map!(unqualified_variant(), |name| {
Pattern::Variant(&[], name)
}))
loc!(one_of!(
map!(private_tag(), Pattern::PrivateTag),
map!(global_tag(), Pattern::GlobalTag)
))
)
.parse(arena, state)
}
@ -665,7 +678,7 @@ fn parse_closure_param<'a>(
fn pattern<'a>(min_indent: u16) -> impl Parser<'a, Pattern<'a>> {
one_of!(
underscore_pattern(),
variant_pattern(),
tag_pattern(),
ident_pattern(),
record_destructure(min_indent),
string_pattern(),
@ -708,8 +721,11 @@ fn record_destructure<'a>(min_indent: u16) -> impl Parser<'a, Pattern<'a>> {
)
}
fn variant_pattern<'a>() -> impl Parser<'a, Pattern<'a>> {
map!(unqualified_variant(), |name| Pattern::Variant(&[], name))
fn tag_pattern<'a>() -> impl Parser<'a, Pattern<'a>> {
one_of!(
map!(private_tag(), Pattern::PrivateTag),
map!(global_tag(), Pattern::GlobalTag)
)
}
fn ident_pattern<'a>() -> impl Parser<'a, Pattern<'a>> {
@ -958,7 +974,7 @@ pub fn ident_etc<'a>(min_indent: u16) -> impl Parser<'a, Expr<'a>> {
Some(Either::First(loc_args)) => {
let loc_expr = Located {
region: loc_ident.region,
value: ident_to_expr(loc_ident.value),
value: ident_to_expr(arena, loc_ident.value),
};
let mut allocated_args = Vec::with_capacity_in(loc_args.len(), arena);
@ -999,7 +1015,7 @@ pub fn ident_etc<'a>(min_indent: u16) -> impl Parser<'a, Expr<'a>> {
None => {
let ident = loc_ident.value.clone();
Ok((ident_to_expr(ident), state))
Ok((ident_to_expr(arena, ident), state))
}
}
},
@ -1007,8 +1023,8 @@ pub fn ident_etc<'a>(min_indent: u16) -> impl Parser<'a, Expr<'a>> {
}
pub fn ident_without_apply<'a>() -> impl Parser<'a, Expr<'a>> {
then(loc!(ident()), move |_arena, state, loc_ident| {
Ok((ident_to_expr(loc_ident.value), state))
then(loc!(ident()), move |arena, state, loc_ident| {
Ok((ident_to_expr(arena, loc_ident.value), state))
})
}
@ -1047,12 +1063,34 @@ pub fn case_with_indent<'a>() -> impl Parser<'a, u16> {
}
}
fn ident_to_expr(src: Ident<'_>) -> Expr<'_> {
fn ident_to_expr<'a>(arena: &'a Bump, src: Ident<'a>) -> Expr<'a> {
match src {
Ident::Var(info) => Expr::Var(info.module_parts, info.value),
Ident::Variant(info) => Expr::Variant(info.module_parts, info.value),
Ident::Field(info) => Expr::QualifiedField(info.module_parts, info.value),
Ident::AccessorFunction(string) => Expr::AccessorFunction(string),
Ident::GlobalTag(string) => Expr::GlobalTag(string),
Ident::PrivateTag(string) => Expr::PrivateTag(string),
Ident::Access(info) => {
let mut iter = info.value.into_iter();
// The first value in the iterator is the variable name,
// e.g. `foo` in `foo.bar.baz`
let mut answer = match iter.next() {
Some(var) => Expr::Var(info.module_parts, var),
None => {
panic!("Parsed an Ident::Access with no parts");
}
};
// The remaining items in the iterator are record field accesses,
// e.g. `bar` in `foo.bar.baz`, followed by `baz`
for field in iter {
// Wrap the previous answer in the new one, so we end up
// with a nested Expr. That way, `foo.bar.baz` gets represented
// in the AST as if it had been written (foo.bar).baz all along.
answer = Expr::Access(arena.alloc(answer), UnqualifiedIdent::new(field));
}
answer
}
Ident::AccessorFunction(string) => Expr::AccessorFunction(UnqualifiedIdent::new(string)),
Ident::Malformed(string) => Expr::MalformedIdent(string),
}
}
@ -1162,11 +1200,17 @@ pub fn record_literal<'a>(min_indent: u16) -> impl Parser<'a, Expr<'a>> {
)
}
/// This is mainly for matching variants in closure params, e.g. \Foo -> ...
///
/// TODO: this should absolutely support qualified variants. Need to change it and rename it!
fn unqualified_variant<'a>() -> impl Parser<'a, &'a str> {
variant_or_ident(|first_char| first_char.is_uppercase())
/// This is mainly for matching tags in closure params, e.g. \@Foo -> ...
fn private_tag<'a>() -> impl Parser<'a, &'a str> {
skip_first!(char('@'), global_tag())
}
/// This is mainly for matching tags in closure params, e.g. \Foo -> ...
fn global_tag<'a>() -> impl Parser<'a, &'a str> {
skip_first!(
char('@'),
global_tag_or_ident(|first_char| first_char.is_uppercase())
)
}
pub fn string_literal<'a>() -> impl Parser<'a, Expr<'a>> {

View file

@ -36,13 +36,7 @@ pub fn parse_loc_with<'a>(arena: &'a Bump, input: &'a str) -> Result<Located<ast
#[allow(dead_code)]
pub fn can_expr(expr_str: &str) -> (Expr, Output, Vec<Problem>, VarStore, Variable) {
can_expr_with(
&Bump::new(),
"blah",
expr_str,
&ImMap::default(),
&ImMap::default(),
)
can_expr_with(&Bump::new(), "blah", expr_str, &ImMap::default())
}
#[allow(dead_code)]
@ -51,7 +45,6 @@ pub fn can_expr_with(
name: &str,
expr_str: &str,
declared_idents: &ImMap<Ident, (Symbol, Region)>,
declared_variants: &ImMap<Symbol, Located<Box<str>>>,
) -> (Expr, Output, Vec<Problem>, VarStore, Variable) {
let loc_expr = parse_loc_with(&arena, expr_str).unwrap_or_else(|_| {
panic!(
@ -72,7 +65,6 @@ pub fn can_expr_with(
Region::zero(),
loc_expr,
declared_idents,
declared_variants,
expected,
);

View file

@ -48,7 +48,6 @@ mod test_canonicalize {
struct Out<'a> {
locals: Vec<&'a str>,
globals: Vec<&'a str>,
variants: Vec<&'a str>,
calls: Vec<&'a str>,
tail_call: Option<&'a str>,
}
@ -58,7 +57,6 @@ mod test_canonicalize {
let references = References {
locals: vec_to_set(self.locals),
globals: vec_to_set(self.globals),
variants: vec_to_set(self.variants),
calls: vec_to_set(self.calls),
};
@ -78,8 +76,7 @@ mod test_canonicalize {
fn assert_can(input: &str, expected: Expr) {
let arena = Bump::new();
let (actual, _, _, _, _) =
can_expr_with(&arena, "Blah", input, &ImMap::default(), &ImMap::default());
let (actual, _, _, _, _) = can_expr_with(&arena, "Blah", input, &ImMap::default());
assert_eq!(expected, actual);
}
@ -141,7 +138,7 @@ mod test_canonicalize {
"#
);
let (_actual, mut output, problems, _var_store, _vars) =
can_expr_with(&arena, "Blah", src, &ImMap::default(), &ImMap::default());
can_expr_with(&arena, "Blah", src, &ImMap::default());
assert_eq!(problems, vec![]);
@ -153,7 +150,6 @@ mod test_canonicalize {
Out {
locals: vec!["func"],
globals: vec![],
variants: vec![],
calls: vec!["func"],
tail_call: None
}
@ -177,7 +173,6 @@ mod test_canonicalize {
// references: References {
// locals: vec_to_set(vec![]),
// globals: vec_to_set(vec![]),
// variants: vec_to_set(vec![]),
// calls: vec_to_set(vec![]),
// }
// }
@ -199,7 +194,7 @@ mod test_canonicalize {
);
let arena = Bump::new();
let (_actual, mut output, problems, _var_store, _vars) =
can_expr_with(&arena, "Blah", src, &ImMap::default(), &ImMap::default());
can_expr_with(&arena, "Blah", src, &ImMap::default());
assert_eq!(problems, vec![]);
@ -211,7 +206,6 @@ mod test_canonicalize {
Out {
locals: vec!["identity", "apply"],
globals: vec![],
variants: vec![],
calls: vec!["f", "apply"],
tail_call: None
}
@ -257,7 +251,7 @@ mod test_canonicalize {
);
let arena = Bump::new();
let (actual, _output, _problems, _var_store, _vars) =
can_expr_with(&arena, "Blah", src, &ImMap::default(), &ImMap::default());
can_expr_with(&arena, "Blah", src, &ImMap::default());
let detected = get_closure(&actual, 0);
assert_eq!(detected, Recursive::TailRecursive);
@ -283,7 +277,7 @@ mod test_canonicalize {
);
let arena = Bump::new();
let (actual, _output, _problems, _var_store, _vars) =
can_expr_with(&arena, "Blah", src, &ImMap::default(), &ImMap::default());
can_expr_with(&arena, "Blah", src, &ImMap::default());
let detected = get_closure(&actual, 0);
assert_eq!(detected, Recursive::TailRecursive);
@ -300,7 +294,7 @@ mod test_canonicalize {
);
let arena = Bump::new();
let (actual, _output, _problems, _var_store, _vars) =
can_expr_with(&arena, "Blah", src, &ImMap::default(), &ImMap::default());
can_expr_with(&arena, "Blah", src, &ImMap::default());
let detected = get_closure(&actual, 0);
assert_eq!(detected, Recursive::TailRecursive);
@ -320,7 +314,7 @@ mod test_canonicalize {
);
let arena = Bump::new();
let (actual, _output, _problems, _var_store, _vars) =
can_expr_with(&arena, "Blah", src, &ImMap::default(), &ImMap::default());
can_expr_with(&arena, "Blah", src, &ImMap::default());
let detected = get_closure(&actual, 0);
assert_eq!(detected, Recursive::Recursive);
@ -346,7 +340,7 @@ mod test_canonicalize {
);
let arena = Bump::new();
let (actual, _output, _problems, _var_store, _vars) =
can_expr_with(&arena, "Blah", src, &ImMap::default(), &ImMap::default());
can_expr_with(&arena, "Blah", src, &ImMap::default());
let detected = get_closure(&actual, 0);
assert_eq!(detected, Recursive::Recursive);
@ -381,7 +375,6 @@ mod test_canonicalize {
// Out {
// locals: vec!["func", "local"],
// globals: vec![],
// variants: vec![],
// calls: vec!["func"],
// tail_call: None
// }
@ -415,7 +408,6 @@ mod test_canonicalize {
// Out {
// locals: vec!["local"],
// globals: vec![],
// variants: vec![],
// calls: vec![],
// tail_call: None
// }
@ -450,7 +442,6 @@ mod test_canonicalize {
// Out {
// locals: vec![],
// globals: vec![],
// variants: vec![],
// calls: vec![],
// tail_call: None
// }
@ -481,7 +472,6 @@ mod test_canonicalize {
// Out {
// locals: vec!["a", "b"],
// globals: vec![],
// variants: vec![],
// calls: vec![],
// tail_call: None
// }
@ -512,7 +502,6 @@ mod test_canonicalize {
// Out {
// locals: vec!["c"],
// globals: vec![],
// variants: vec![],
// calls: vec![],
// tail_call: None
// }
@ -541,7 +530,6 @@ mod test_canonicalize {
// Out {
// locals: vec!["fibonacci"],
// globals: vec![],
// variants: vec![],
// calls: vec!["fibonacci"],
// tail_call: None
// }
@ -576,7 +564,6 @@ mod test_canonicalize {
// Out {
// locals: vec!["factorial", "factorialHelp"],
// globals: vec![],
// variants: vec![],
// calls: vec!["factorial", "factorialHelp"],
// tail_call: None
// }
@ -604,7 +591,6 @@ mod test_canonicalize {
// Out {
// locals: vec!["a", "b"],
// globals: vec![],
// variants: vec![],
// calls: vec![],
// tail_call: None
// }
@ -634,7 +620,6 @@ mod test_canonicalize {
// Out {
// locals: vec!["increment", "x", "y", "z"],
// globals: vec![],
// variants: vec![],
// calls: vec!["increment"],
// tail_call: None
// }
@ -675,7 +660,6 @@ mod test_canonicalize {
// Out {
// locals: vec!["func1", "func2", "x", "y", "z"],
// globals: vec![],
// variants: vec![],
// calls: vec!["func1", "func2"],
// tail_call: None
// }

View file

@ -17,6 +17,7 @@ mod test_parse {
use crate::helpers::parse_with;
use bumpalo::collections::vec::Vec;
use bumpalo::{self, Bump};
use roc::ident::UnqualifiedIdent;
use roc::module::ModuleName;
use roc::operator::BinOp::*;
use roc::operator::CalledVia;
@ -467,25 +468,51 @@ mod test_parse {
// VARIANT
#[test]
fn basic_variant() {
fn basic_global_tag() {
let arena = Bump::new();
let module_parts = Vec::new_in(&arena).into_bump_slice();
let expected = Expr::Variant(module_parts, "Whee");
let expected = Expr::GlobalTag("Whee");
let actual = parse_with(&arena, "Whee");
assert_eq!(Ok(expected), actual);
}
#[test]
fn qualified_variant() {
fn basic_private_tag() {
let arena = Bump::new();
let module_parts = bumpalo::vec![in &arena; "One", "Two"].into_bump_slice();
let expected = Expr::Variant(module_parts, "Whee");
let expected = Expr::PrivateTag("@Whee");
let actual = parse_with(&arena, "@Whee");
assert_eq!(Ok(expected), actual);
}
#[test]
fn qualified_global_tag() {
let arena = Bump::new();
let expected = Expr::MalformedIdent("One.Two.Whee");
let actual = parse_with(&arena, "One.Two.Whee");
assert_eq!(Ok(expected), actual);
}
// TODO restore this test - it fails, but is not worth fixing right now.
// #[test]
// fn qualified_private_tag() {
// let arena = Bump::new();
// let expected = Expr::MalformedIdent("One.Two.@Whee");
// let actual = parse_with(&arena, "One.Two.@Whee");
// assert_eq!(Ok(expected), actual);
// }
#[test]
fn private_qualified_tag() {
let arena = Bump::new();
let expected = Expr::MalformedIdent("@One.Two.Whee");
let actual = parse_with(&arena, "@One.Two.Whee");
assert_eq!(Ok(expected), actual);
}
// LISTS
#[test]
@ -520,13 +547,25 @@ mod test_parse {
// FIELD ACCESS
#[test]
fn basic_field() {
let arena = Bump::new();
let module_parts = Vec::new_in(&arena).into_bump_slice();
let field = UnqualifiedIdent::new("field");
let var = Var(module_parts, "rec");
let expected = Access(arena.alloc(var), field);
let actual = parse_with(&arena, "rec.field");
assert_eq!(Ok(expected), actual);
}
#[test]
fn parenthetical_basic_field() {
let arena = Bump::new();
let module_parts = Vec::new_in(&arena).into_bump_slice();
let fields = bumpalo::vec![in &arena; "field"];
let field = UnqualifiedIdent::new("field");
let paren_var = ParensAround(arena.alloc(Var(module_parts, "rec")));
let expected = Field(arena.alloc(Located::new(0, 0, 1, 4, paren_var)), fields);
let expected = Access(arena.alloc(paren_var), field);
let actual = parse_with(&arena, "(rec).field");
assert_eq!(Ok(expected), actual);
@ -536,21 +575,27 @@ mod test_parse {
fn parenthetical_field_qualified_var() {
let arena = Bump::new();
let module_parts = bumpalo::vec![in &arena; "One", "Two"].into_bump_slice();
let fields = bumpalo::vec![in &arena; "field"];
let field = UnqualifiedIdent::new("field");
let paren_var = ParensAround(arena.alloc(Var(module_parts, "rec")));
let expected = Field(arena.alloc(Located::new(0, 0, 1, 12, paren_var)), fields);
let expected = Access(arena.alloc(paren_var), field);
let actual = parse_with(&arena, "(One.Two.rec).field");
assert_eq!(Ok(expected), actual);
}
#[test]
fn basic_field() {
fn multiple_fields() {
let arena = Bump::new();
let module_parts = Vec::new_in(&arena).into_bump_slice();
let fields = bumpalo::vec![in &arena; "rec", "field"].into_bump_slice();
let expected = QualifiedField(module_parts, fields);
let actual = parse_with(&arena, "rec.field");
let abc = UnqualifiedIdent::new("abc");
let def = UnqualifiedIdent::new("def");
let ghi = UnqualifiedIdent::new("ghi");
let var = Var(module_parts, "rec");
let expected = Access(
arena.alloc(Access(arena.alloc(Access(arena.alloc(var), abc)), def)),
ghi,
);
let actual = parse_with(&arena, "rec.abc.def.ghi");
assert_eq!(Ok(expected), actual);
}
@ -559,9 +604,15 @@ mod test_parse {
fn qualified_field() {
let arena = Bump::new();
let module_parts = bumpalo::vec![in &arena; "One", "Two"].into_bump_slice();
let fields = bumpalo::vec![in &arena; "rec", "field"].into_bump_slice();
let expected = QualifiedField(module_parts, fields);
let actual = parse_with(&arena, "One.Two.rec.field");
let abc = UnqualifiedIdent::new("abc");
let def = UnqualifiedIdent::new("def");
let ghi = UnqualifiedIdent::new("ghi");
let var = Var(module_parts, "rec");
let expected = Access(
arena.alloc(Access(arena.alloc(Access(arena.alloc(var), abc)), def)),
ghi,
);
let actual = parse_with(&arena, "One.Two.rec.abc.def.ghi");
assert_eq!(Ok(expected), actual);
}