mirror of
https://github.com/roc-lang/roc.git
synced 2025-10-03 08:34:33 +00:00
Use private/global tags and Access over Field
This commit is contained in:
parent
468e285654
commit
1d18f4cc85
17 changed files with 333 additions and 314 deletions
|
@ -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,
|
||||
|
|
|
@ -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>>),
|
||||
|
|
|
@ -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!
|
||||
|
|
|
@ -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 {
|
||||
|
|
|
@ -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(_, _)
|
||||
|
|
|
@ -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>),
|
||||
|
|
|
@ -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)
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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),
|
||||
pub fn from_global_tag(tag_name: &str) -> Symbol {
|
||||
Symbol(tag_name.into())
|
||||
}
|
||||
|
||||
VariantName::Qualified(path, name) => Symbol::new(path, name),
|
||||
}
|
||||
pub fn from_private_tag(home: &str, tag_name: &str) -> Symbol {
|
||||
Symbol(format!("{}.{}", home, tag_name).into())
|
||||
}
|
||||
|
||||
pub fn from_module<'a>(
|
||||
|
|
|
@ -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,
|
||||
|
|
|
@ -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) => {
|
||||
|
|
18
src/ident.rs
18
src/ident.rs
|
@ -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),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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,39 +310,35 @@ 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,
|
||||
);
|
||||
let mut buf = String::with_capacity_in(
|
||||
maybe_qualified.module_parts.len() + maybe_qualified.value.len(),
|
||||
arena,
|
||||
);
|
||||
|
||||
for part in maybe_qualified.module_parts.iter() {
|
||||
buf.push_str(part);
|
||||
buf.push('.');
|
||||
}
|
||||
|
||||
let mut iter = maybe_qualified.value.iter().peekable();
|
||||
|
||||
while let Some(part) = iter.next() {
|
||||
buf.push_str(part);
|
||||
|
||||
// If there are more fields to come, add a "."
|
||||
if iter.peek().is_some() {
|
||||
for part in maybe_qualified.module_parts.iter() {
|
||||
buf.push_str(part);
|
||||
buf.push('.');
|
||||
}
|
||||
}
|
||||
|
||||
Pattern::Malformed(buf.into_bump_str())
|
||||
let mut iter = maybe_qualified.value.iter().peekable();
|
||||
|
||||
while let Some(part) = iter.next() {
|
||||
buf.push_str(part);
|
||||
|
||||
// If there are more fields to come, add a "."
|
||||
if iter.peek().is_some() {
|
||||
buf.push('.');
|
||||
}
|
||||
}
|
||||
|
||||
Pattern::Malformed(buf.into_bump_str())
|
||||
}
|
||||
}
|
||||
Ident::AccessorFunction(string) => Pattern::Malformed(string),
|
||||
Ident::Malformed(string) => Pattern::Malformed(string),
|
||||
|
|
|
@ -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,38 +211,51 @@ 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,
|
||||
}),
|
||||
None => {
|
||||
// We had neither capitalized nor noncapitalized parts,
|
||||
// yet we made it this far. The only explanation is that this was
|
||||
// a stray '.' drifting through the cosmos.
|
||||
return Err(unexpected('.', 1, state, Attempting::Identifier));
|
||||
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
|
||||
// a stray '.' drifting through the cosmos.
|
||||
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 {
|
||||
module_parts: capitalized_parts.into_bump_slice(),
|
||||
value: noncapitalized_parts.into_bump_slice(),
|
||||
})
|
||||
}
|
||||
} 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(),
|
||||
})
|
||||
}
|
||||
};
|
||||
|
||||
|
@ -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
|
||||
)
|
||||
}
|
||||
|
|
108
src/parse/mod.rs
108
src/parse/mod.rs
|
@ -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((
|
||||
Located {
|
||||
region: loc_expr_with_extras.region,
|
||||
value: Expr::Field(arena.alloc(loc_expr), fields),
|
||||
},
|
||||
state,
|
||||
)),
|
||||
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.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>> {
|
||||
|
|
|
@ -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,
|
||||
);
|
||||
|
||||
|
|
|
@ -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
|
||||
// }
|
||||
|
|
|
@ -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);
|
||||
}
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue