mirror of
https://github.com/roc-lang/roc.git
synced 2025-09-30 23:31:12 +00:00
Attempt to sort all the symbols.
This commit is contained in:
parent
83cbc1d927
commit
e3e92b56fb
6 changed files with 240 additions and 79 deletions
|
@ -2,7 +2,7 @@ use region::{Located, Region};
|
|||
use operator::Operator;
|
||||
use operator::Operator::Pizza;
|
||||
use operator::Associativity::*;
|
||||
use collections::{ImSet, ImMap, MutMap};
|
||||
use collections::{ImSortedSet, ImSortedMap, MutMap, MutSortedMap, MutSet};
|
||||
use std::cmp::Ordering;
|
||||
use expr::{Ident, VariantName};
|
||||
use expr;
|
||||
|
@ -85,7 +85,7 @@ pub enum Pattern {
|
|||
|
||||
/// A globally unique identifier, used for both vars and variants.
|
||||
/// It will be used directly in code gen.
|
||||
#[derive(Clone, Debug, PartialEq, Eq, Hash)]
|
||||
#[derive(Clone, Debug, PartialEq, Eq, Hash, PartialOrd, Ord)]
|
||||
pub struct Symbol(String);
|
||||
|
||||
impl Symbol {
|
||||
|
@ -114,13 +114,13 @@ impl Into<String> for Symbol {
|
|||
|
||||
#[derive(Clone, Debug, PartialEq)]
|
||||
struct Scope {
|
||||
pub idents: ImMap<Ident, (Symbol, Region)>,
|
||||
pub idents: ImSortedMap<Ident, (Symbol, Region)>,
|
||||
symbol_prefix: String,
|
||||
next_unique_id: u64,
|
||||
}
|
||||
|
||||
impl Scope {
|
||||
pub fn new(symbol_prefix: String, declared_idents: ImMap<Ident, (Symbol, Region)>) -> Scope {
|
||||
pub fn new(symbol_prefix: String, declared_idents: ImSortedMap<Ident, (Symbol, Region)>) -> Scope {
|
||||
Scope {
|
||||
symbol_prefix,
|
||||
|
||||
|
@ -146,22 +146,22 @@ impl Scope {
|
|||
#[derive(Clone, Debug, PartialEq)]
|
||||
pub struct Procedure {
|
||||
pub name: Option<String>,
|
||||
pub closes_over: ImSet<Symbol>,
|
||||
pub is_self_tail_recursive: bool,
|
||||
pub definition: Region,
|
||||
pub args: Vec<Pattern>,
|
||||
pub body: Expr
|
||||
pub body: Expr,
|
||||
pub references: References,
|
||||
}
|
||||
|
||||
impl Procedure {
|
||||
pub fn new(definition: Region, closes_over: ImSet<Symbol>, args: Vec<Pattern>, body: Expr) -> Procedure {
|
||||
pub fn new(definition: Region, args: Vec<Pattern>, body: Expr, references: References) -> Procedure {
|
||||
Procedure {
|
||||
name: None,
|
||||
closes_over,
|
||||
is_self_tail_recursive: false,
|
||||
definition,
|
||||
args,
|
||||
body
|
||||
body,
|
||||
references,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -176,19 +176,19 @@ struct Env {
|
|||
problems: Vec<Problem>,
|
||||
|
||||
/// Variants either declared in this module, or imported.
|
||||
variants: ImMap<Symbol, Located<expr::VariantName>>,
|
||||
variants: ImSortedMap<Symbol, Located<expr::VariantName>>,
|
||||
|
||||
/// Former closures converted to top-level procedures.
|
||||
procedures: MutMap<Symbol, Procedure>,
|
||||
procedures: MutSortedMap<Symbol, Procedure>,
|
||||
}
|
||||
|
||||
impl Env {
|
||||
pub fn new(home: String, declared_variants: ImMap<Symbol, Located<expr::VariantName>>) -> Env {
|
||||
pub fn new(home: String, declared_variants: ImSortedMap<Symbol, Located<expr::VariantName>>) -> Env {
|
||||
Env {
|
||||
home,
|
||||
variants: declared_variants,
|
||||
problems: Vec::new(),
|
||||
procedures: MutMap::default(),
|
||||
procedures: MutSortedMap::default(),
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -199,16 +199,16 @@ impl Env {
|
|||
pub fn register_closure(
|
||||
&mut self,
|
||||
symbol: Symbol,
|
||||
closes_over: ImSet<Symbol>,
|
||||
args: Vec<Pattern>,
|
||||
body: Expr,
|
||||
definition: Region
|
||||
definition: Region,
|
||||
references: References
|
||||
) -> () {
|
||||
// We can't if the closure is self tail recursive yet, because it doesn't know its final name yet.
|
||||
// (Assign sets that.) Assume this is false, and let Assign change it to true after it sets final name.
|
||||
let is_self_tail_recursive = false;
|
||||
let name = None; // The Assign logic is also responsible for setting names after the fact.
|
||||
let procedure = Procedure {closes_over, args, name, body, is_self_tail_recursive, definition};
|
||||
let procedure = Procedure {args, name, body, is_self_tail_recursive, definition, references};
|
||||
|
||||
self.procedures.insert(symbol, procedure);
|
||||
}
|
||||
|
@ -218,9 +218,9 @@ pub fn canonicalize_declaration(
|
|||
home: String,
|
||||
name: &str,
|
||||
loc_expr: Located<expr::Expr>,
|
||||
declared_idents: &ImMap<Ident, (Symbol, Region)>,
|
||||
declared_variants: &ImMap<Symbol, Located<expr::VariantName>>,
|
||||
) -> (Located<Expr>, Output, Vec<Problem>, MutMap<Symbol, Procedure>) {
|
||||
declared_idents: &ImSortedMap<Ident, (Symbol, Region)>,
|
||||
declared_variants: &ImSortedMap<Symbol, Located<expr::VariantName>>,
|
||||
) -> (Located<Expr>, Output, Vec<Problem>, MutSortedMap<Symbol, Procedure>) {
|
||||
// If we're canonicalizing the declaration `foo = ...` inside the `Main` module,
|
||||
// scope_prefix will be "Main$foo$" and its first closure will be named "Main$foo$0"
|
||||
let scope_prefix = format!("{}${}$", home, name);
|
||||
|
@ -244,19 +244,24 @@ pub struct Output {
|
|||
pub tail_call: Option<Symbol>,
|
||||
}
|
||||
|
||||
/// These are all ordered sets because they end up getting traversed in a graph search
|
||||
/// to determine how assignments shuold be ordered. We want builds to be reproducible,
|
||||
/// so it's important that building the same code gives the same order every time!
|
||||
#[derive(Clone, Debug, PartialEq)]
|
||||
pub struct References {
|
||||
pub locals: ImSet<Symbol>,
|
||||
pub globals: ImSet<Symbol>,
|
||||
pub variants: ImSet<Symbol>,
|
||||
pub locals: ImSortedSet<Symbol>,
|
||||
pub globals: ImSortedSet<Symbol>,
|
||||
pub variants: ImSortedSet<Symbol>,
|
||||
pub calls: ImSortedSet<Symbol>,
|
||||
}
|
||||
|
||||
impl References {
|
||||
pub fn new() -> References {
|
||||
References {
|
||||
locals: ImSet::default(),
|
||||
globals: ImSet::default(),
|
||||
variants: ImSet::default(),
|
||||
locals: ImSortedSet::default(),
|
||||
globals: ImSortedSet::default(),
|
||||
variants: ImSortedSet::default(),
|
||||
calls: ImSortedSet::default(),
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -264,6 +269,7 @@ impl References {
|
|||
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
|
||||
}
|
||||
|
@ -414,6 +420,9 @@ fn canonicalize(
|
|||
let can_expr =
|
||||
match resolve_ident(&env, &scope, ident, &mut output.references) {
|
||||
Ok(symbol) => {
|
||||
// Record that we did, in fact, call this symbol.
|
||||
output.references.calls.insert(symbol.clone());
|
||||
|
||||
// CallByName expressions are considered tail calls,
|
||||
// so that their parents in the expression tree will
|
||||
// correctly inherit tail-call-ness from them.
|
||||
|
@ -505,13 +514,13 @@ fn canonicalize(
|
|||
|
||||
// Add the assigned identifiers to scope. If there's a collision, it means there
|
||||
// was shadowing, which will be handled later.
|
||||
let assigned_idents: ImMap<Ident, (Symbol, Region)> =
|
||||
let assigned_idents: ImSortedMap<Ident, (Symbol, Region)> =
|
||||
idents_from_patterns(assignments.clone().into_iter().map(|(loc_pattern, _)| loc_pattern), &scope);
|
||||
|
||||
scope.idents = scope.idents.union(assigned_idents.clone());
|
||||
|
||||
let mut refs_by_assignment: MutMap<Symbol, (Located<Ident>, References)> = MutMap::default();
|
||||
let mut can_assignments_by_symbol: MutMap<Symbol, (Pattern, Located<Expr>)> = MutMap::default();
|
||||
let mut can_assignments_by_symbol: MutSortedMap<Symbol, (Pattern, Located<Expr>)> = MutSortedMap::default();
|
||||
|
||||
for (loc_pattern, expr) in assignments {
|
||||
// Each assignment gets to have all the idents in scope that are assigned in this
|
||||
|
@ -564,6 +573,14 @@ fn canonicalize(
|
|||
// when code elsewhere calls it by assigned name, it'll resolve properly.
|
||||
env.procedures.insert(assigned_symbol.clone(), procedure);
|
||||
|
||||
// Recursion doesn't count as referencing. (If it did, all recursive functions
|
||||
// would result in circular assignment errors!)
|
||||
refs_by_assignment
|
||||
.entry(assigned_symbol.clone())
|
||||
.and_modify(|(_, refs)| {
|
||||
refs.locals = refs.locals.without(assigned_symbol);
|
||||
});
|
||||
|
||||
// Return a pointer to the assigned symbol, since the auto-generated one no
|
||||
// longer references any entry in the procedure map!
|
||||
FunctionPointer(assigned_symbol.clone())
|
||||
|
@ -587,7 +604,7 @@ fn canonicalize(
|
|||
let (ret_expr, mut output) = canonicalize(env, &mut scope, *box_loc_returned);
|
||||
|
||||
// Determine the full set of references by traversing the graph.
|
||||
let mut visited_symbols = ImSet::default();
|
||||
let mut visited_symbols = MutSet::default();
|
||||
|
||||
// Start with the return expression's referenced locals. They are the only ones that count!
|
||||
//
|
||||
|
@ -602,7 +619,16 @@ fn canonicalize(
|
|||
// we'd erroneously give a warning that `b` was unused since it wasn't directly referenced.
|
||||
for symbol in output.references.locals.clone().into_iter() {
|
||||
// Traverse the graph and look up *all* the references for this local symbol.
|
||||
let refs = get_all_referenced(symbol, &mut visited_symbols, &refs_by_assignment);
|
||||
let refs = references_from_local(symbol, &mut visited_symbols, &refs_by_assignment, &env.procedures);
|
||||
|
||||
output.references = output.references.union(refs);
|
||||
}
|
||||
|
||||
for symbol in output.references.calls.clone().into_iter() {
|
||||
// Traverse the graph and look up *all* the references for this call.
|
||||
// Reuse the same visited_symbols as before; if we already visited it, we
|
||||
// won't learn anything new from visiting it again!
|
||||
let refs = references_from_call(symbol, &mut visited_symbols, &refs_by_assignment, &env.procedures);
|
||||
|
||||
output.references = output.references.union(refs);
|
||||
}
|
||||
|
@ -621,7 +647,7 @@ fn canonicalize(
|
|||
// This way, during code gen, no assignment will refer to a value that hasn't been initialized yet.
|
||||
// As a bonus, the topological sort also reveals any cycles between the assignments, allowing
|
||||
// us to give a CircularAssignment error.
|
||||
let successors = |symbol: &Symbol| -> ImSet<Symbol> {
|
||||
let successors = |symbol: &Symbol| -> ImSortedSet<Symbol> {
|
||||
let (_, references) = refs_by_assignment.get(symbol).unwrap();
|
||||
|
||||
references.locals.clone()
|
||||
|
@ -635,6 +661,7 @@ fn canonicalize(
|
|||
let can_assignments =
|
||||
sorted_symbols
|
||||
.into_iter()
|
||||
.rev() // Topological sort gives us the reverse of the sorting we want!
|
||||
.map(|symbol| can_assignments_by_symbol.get(&symbol).unwrap().clone())
|
||||
.collect();
|
||||
|
||||
|
@ -675,7 +702,7 @@ fn canonicalize(
|
|||
|
||||
// Add the arguments' idents to scope.idents. If there's a collision,
|
||||
// it means there was shadowing, which will be handled later.
|
||||
let arg_idents: ImMap<Ident, (Symbol, Region)> =
|
||||
let arg_idents: ImSortedMap<Ident, (Symbol, Region)> =
|
||||
idents_from_patterns(loc_arg_patterns.clone().into_iter(), &scope);
|
||||
|
||||
scope.idents = scope.idents.union(arg_idents.clone());
|
||||
|
@ -704,12 +731,14 @@ fn canonicalize(
|
|||
output.references.locals.remove(&arg_symbol);
|
||||
}
|
||||
|
||||
// We only ever need to close over locals. Globals are always available!
|
||||
// Note: This must happen *after* removing args from locals. Never close over arguments!
|
||||
let closes_over: ImSet<Symbol> = output.references.locals.clone();
|
||||
// We've finished analyzing the closure. Its references.locals are now the values it closes over,
|
||||
// since we removed the only locals it shouldn't close over (its arguments).
|
||||
// Register it as a top-level procedure in the Env!
|
||||
env.register_closure(symbol.clone(), can_args, loc_body_expr.value, region, output.references);
|
||||
|
||||
// We've finished analyzing the closure. Register it as a top-level procedure in the Env!
|
||||
env.register_closure(symbol.clone(), closes_over, can_args, loc_body_expr.value, region);
|
||||
// Having now registered the closure's references, the function pointer that remains has
|
||||
// no references. The references we registered will be used only if this symbol gets called!
|
||||
output.references = References::new();
|
||||
|
||||
// Always return a function pointer, in case that's how the closure is being used (e.g. with Apply).
|
||||
// It's possible that Assign will rewrite this. In that case, Assign will need to know the symbol we
|
||||
|
@ -739,7 +768,7 @@ fn canonicalize(
|
|||
// Patterns introduce new idents to the scope!
|
||||
// Add the assigned identifiers to scope. If there's a collision, it means there
|
||||
// was shadowing, which will be handled later.
|
||||
let assigned_idents: ImMap<Ident, (Symbol, Region)> =
|
||||
let assigned_idents: ImSortedMap<Ident, (Symbol, Region)> =
|
||||
idents_from_patterns(std::iter::once(loc_pattern), &scope);
|
||||
|
||||
scope.idents = scope.idents.union(assigned_idents.clone());
|
||||
|
@ -798,10 +827,11 @@ fn canonicalize(
|
|||
(Located {region, value: expr}, output)
|
||||
}
|
||||
|
||||
fn get_all_referenced<T>(
|
||||
fn references_from_local<T>(
|
||||
assigned_symbol: Symbol,
|
||||
visited: &mut ImSet<Symbol>,
|
||||
refs_by_assignment: &MutMap<Symbol, (T, References)>
|
||||
visited: &mut MutSet<Symbol>,
|
||||
refs_by_assignment: &MutMap<Symbol, (T, References)>,
|
||||
procedures: &MutSortedMap<Symbol, Procedure>,
|
||||
) -> References {
|
||||
match refs_by_assignment.get(&assigned_symbol) {
|
||||
Some((_, refs)) => {
|
||||
|
@ -809,14 +839,24 @@ fn get_all_referenced<T>(
|
|||
|
||||
visited.insert(assigned_symbol);
|
||||
|
||||
for local in refs.locals.clone() {
|
||||
for local in refs.locals.iter() {
|
||||
if !visited.contains(&local) {
|
||||
let other_refs = get_all_referenced(local.clone(), visited, refs_by_assignment);
|
||||
let other_refs = references_from_local(local.clone(), visited, refs_by_assignment, procedures);
|
||||
|
||||
answer = answer.union(other_refs);
|
||||
}
|
||||
|
||||
answer.locals.insert(local);
|
||||
answer.locals.insert(local.clone());
|
||||
}
|
||||
|
||||
for call in refs.calls.iter() {
|
||||
if !visited.contains(&call) {
|
||||
let other_refs = references_from_call(call.clone(), visited, refs_by_assignment, procedures);
|
||||
|
||||
answer = answer.union(other_refs);
|
||||
}
|
||||
|
||||
answer.calls.insert(call.clone());
|
||||
}
|
||||
|
||||
answer
|
||||
|
@ -825,10 +865,47 @@ fn get_all_referenced<T>(
|
|||
}
|
||||
}
|
||||
|
||||
fn idents_from_patterns<I>(loc_patterns: I, scope: &Scope) -> ImMap<Ident, (Symbol, Region)>
|
||||
fn references_from_call<T>(
|
||||
call_symbol: Symbol,
|
||||
visited: &mut MutSet<Symbol>,
|
||||
refs_by_assignment: &MutMap<Symbol, (T, References)>,
|
||||
procedures: &MutSortedMap<Symbol, Procedure>,
|
||||
) -> References {
|
||||
// This shuold be safe to unwrap. All unrecognized call symbols should have been recorded as
|
||||
// such, and should never have made it into output.references.calls!
|
||||
let procedure = procedures.get(&call_symbol).unwrap();
|
||||
let mut answer = procedure.references.clone();
|
||||
|
||||
visited.insert(call_symbol);
|
||||
|
||||
for closed_over_local in procedure.references.locals.iter() {
|
||||
if !visited.contains(&closed_over_local) {
|
||||
let other_refs = references_from_local(closed_over_local.clone(), visited, refs_by_assignment, procedures);
|
||||
|
||||
answer = answer.union(other_refs);
|
||||
}
|
||||
|
||||
answer.locals.insert(closed_over_local.clone());
|
||||
}
|
||||
|
||||
for call in procedure.references.calls.iter() {
|
||||
if !visited.contains(&call) {
|
||||
let other_refs = references_from_call(call.clone(), visited, refs_by_assignment, procedures);
|
||||
|
||||
answer = answer.union(other_refs);
|
||||
}
|
||||
|
||||
answer.calls.insert(call.clone());
|
||||
}
|
||||
|
||||
answer
|
||||
}
|
||||
|
||||
|
||||
fn idents_from_patterns<I>(loc_patterns: I, scope: &Scope) -> ImSortedMap<Ident, (Symbol, Region)>
|
||||
where I: Iterator<Item = Located<expr::Pattern>>
|
||||
{
|
||||
let mut answer = ImMap::default();
|
||||
let mut answer = ImSortedMap::default();
|
||||
|
||||
for loc_pattern in loc_patterns {
|
||||
add_idents_from_pattern(loc_pattern, scope, &mut answer);
|
||||
|
@ -841,7 +918,7 @@ where I: Iterator<Item = Located<expr::Pattern>>
|
|||
fn add_idents_from_pattern(
|
||||
loc_pattern: Located<expr::Pattern>,
|
||||
scope: &Scope,
|
||||
answer: &mut ImMap<Ident, (Symbol, Region)>
|
||||
answer: &mut ImSortedMap<Ident, (Symbol, Region)>
|
||||
) {
|
||||
use expr::Pattern::*;
|
||||
|
||||
|
@ -863,7 +940,7 @@ fn add_idents_from_pattern(
|
|||
|
||||
fn remove_idents(
|
||||
pattern: expr::Pattern,
|
||||
idents: &mut ImMap<Ident, (Symbol, Region)>
|
||||
idents: &mut ImSortedMap<Ident, (Symbol, Region)>
|
||||
) {
|
||||
use expr::Pattern::*;
|
||||
|
||||
|
@ -968,7 +1045,7 @@ fn canonicalize_pattern(
|
|||
scope: &mut Scope,
|
||||
pattern_type: &PatternType,
|
||||
loc_pattern: &Located<expr::Pattern>,
|
||||
shadowable_idents: &mut ImMap<Ident, (Symbol, Region)>,
|
||||
shadowable_idents: &mut ImSortedMap<Ident, (Symbol, Region)>,
|
||||
) -> Pattern {
|
||||
use expr::Pattern::*;
|
||||
|
||||
|
|
|
@ -19,3 +19,18 @@ pub type ImMap<K, V> =
|
|||
|
||||
pub type ImSet<K> =
|
||||
im_rc::hashset::HashSet<K, BuildHasherDefault<FxHasher>>;
|
||||
|
||||
// OrdMap equivalents, for naming symmetry.
|
||||
// Someday we may switch these implementations out.
|
||||
|
||||
pub type MutSortedMap<K, V> =
|
||||
std::collections::BTreeMap<K, V>;
|
||||
|
||||
pub type MutSortedSet<K> =
|
||||
std::collections::BTreeSet<K>;
|
||||
|
||||
pub type ImSortedMap<K, V> =
|
||||
im_rc::ordmap::OrdMap<K, V>;
|
||||
|
||||
pub type ImSortedSet<K> =
|
||||
im_rc::ordset::OrdSet<K>;
|
||||
|
|
|
@ -46,7 +46,7 @@ pub enum VariantName {
|
|||
/// 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
|
||||
#[derive(Clone, Debug, PartialEq, Eq, Hash)]
|
||||
#[derive(Clone, Debug, PartialEq, Eq, Hash, PartialOrd, Ord)]
|
||||
pub enum Ident {
|
||||
Unqualified(String),
|
||||
Qualified(String, String),
|
||||
|
|
|
@ -1,6 +1,6 @@
|
|||
use std::fmt;
|
||||
|
||||
#[derive(Copy, Clone, Debug, Eq, PartialEq)]
|
||||
#[derive(Copy, Clone, Debug, Eq, PartialEq, PartialOrd, Ord)]
|
||||
pub struct Region {
|
||||
pub start_line: u32,
|
||||
pub start_col: u32,
|
||||
|
@ -9,7 +9,7 @@ pub struct Region {
|
|||
pub end_col: u32,
|
||||
}
|
||||
|
||||
#[derive(Copy, Clone, Eq, PartialEq)]
|
||||
#[derive(Copy, Clone, Eq, PartialEq, PartialOrd, Ord)]
|
||||
pub struct Located<T> {
|
||||
pub region: Region,
|
||||
pub value: T,
|
||||
|
|
|
@ -1,7 +1,6 @@
|
|||
use roc::expr::{Expr, Pattern};
|
||||
use roc::region::{Located, Region};
|
||||
use std::hash::Hash;
|
||||
use roc::collections::{MutMap};
|
||||
use roc::collections::{MutSortedMap};
|
||||
|
||||
pub fn loc_box<T>(val: T) -> Box<Located<T>> {
|
||||
Box::new(loc(val))
|
||||
|
@ -74,11 +73,11 @@ pub fn zero_loc_pattern(loc_pattern: Located<Pattern>) -> Located<Pattern> {
|
|||
}
|
||||
|
||||
#[allow(dead_code)] // For some reason rustc thinks this isn't used. It is, though, in test_canonicalize.rs
|
||||
pub fn mut_map_from_pairs<K, V, I>(pairs: I) -> MutMap<K, V>
|
||||
pub fn mut_sorted_map_from_pairs<K, V, I>(pairs: I) -> MutSortedMap<K, V>
|
||||
where I: IntoIterator<Item=(K, V)>,
|
||||
K: Hash + Eq
|
||||
K: Ord
|
||||
{
|
||||
let mut answer = MutMap::default();
|
||||
let mut answer = MutSortedMap::default();
|
||||
|
||||
for (key, value) in pairs {
|
||||
answer.insert(key, value);
|
||||
|
|
|
@ -11,27 +11,28 @@ mod test_canonicalize {
|
|||
use roc::canonicalize;
|
||||
use roc::canonicalize::{Expr, Output, Problem, Symbol, References, Procedure, Pattern};
|
||||
use roc::canonicalize::Expr::*;
|
||||
use roc::canonicalize::Pattern::*;
|
||||
use roc::expr::{Ident};
|
||||
use roc::expr;
|
||||
use roc::operator::Operator;
|
||||
use roc::region::{Located, Region};
|
||||
use roc::parse;
|
||||
use roc::collections::{ImMap, ImSet, MutMap};
|
||||
use roc::collections::{ImSortedMap, ImSortedSet, MutSortedMap};
|
||||
use roc::parse_state::{IndentablePosition};
|
||||
use combine::{Parser, eof};
|
||||
use combine::stream::state::{State};
|
||||
use helpers::{loc, loc_box, empty_region, zero_loc_expr, mut_map_from_pairs};
|
||||
use helpers::{loc, loc_box, empty_region, zero_loc_expr, mut_sorted_map_from_pairs};
|
||||
|
||||
fn can_expr(expr_str: &str) -> (Expr, Output, Vec<Problem>, MutMap<Symbol, Procedure>) {
|
||||
can_expr_with("testDecl", expr_str, &ImMap::default(), &ImMap::default())
|
||||
fn can_expr(expr_str: &str) -> (Expr, Output, Vec<Problem>, MutSortedMap<Symbol, Procedure>) {
|
||||
can_expr_with("testDecl", expr_str, &ImSortedMap::default(), &ImSortedMap::default())
|
||||
}
|
||||
|
||||
fn can_expr_with(
|
||||
name: &str,
|
||||
expr_str: &str,
|
||||
declared_idents: &ImMap<Ident, (Symbol, Region)>,
|
||||
declared_variants: &ImMap<Symbol, Located<expr::VariantName>>,
|
||||
) -> (Expr, Output, Vec<Problem>, MutMap<Symbol, Procedure>) {
|
||||
declared_idents: &ImSortedMap<Ident, (Symbol, Region)>,
|
||||
declared_variants: &ImSortedMap<Symbol, Located<expr::VariantName>>,
|
||||
) -> (Expr, Output, Vec<Problem>, MutSortedMap<Symbol, Procedure>) {
|
||||
let parse_state: State<&str, IndentablePosition> = State::with_positioner(expr_str, IndentablePosition::default());
|
||||
let expr = match parse::expr().skip(eof()).easy_parse(parse_state) {
|
||||
Ok((expr, state)) => {
|
||||
|
@ -70,19 +71,17 @@ mod test_canonicalize {
|
|||
locals: Vec<&'a str>,
|
||||
globals: Vec<&'a str>,
|
||||
variants: Vec<&'a str>,
|
||||
calls: Vec<&'a str>,
|
||||
tail_call: Option<&'a str>
|
||||
}
|
||||
|
||||
impl<'a> Into<Output> for Out<'a> {
|
||||
fn into(self) -> Output {
|
||||
fn vec_to_set<'b>(vec: Vec<&'b str>) -> ImSet<Symbol> {
|
||||
ImSet::from(vec.into_iter().map(sym).collect::<Vec<_>>())
|
||||
}
|
||||
|
||||
let references = References {
|
||||
locals: vec_to_set(self.locals),
|
||||
globals: vec_to_set(self.globals),
|
||||
variants: vec_to_set(self.variants)
|
||||
variants: vec_to_set(self.variants),
|
||||
calls: vec_to_set(self.calls),
|
||||
};
|
||||
|
||||
let tail_call = self.tail_call.map(sym);
|
||||
|
@ -91,6 +90,10 @@ mod test_canonicalize {
|
|||
}
|
||||
}
|
||||
|
||||
fn vec_to_set<'a>(vec: Vec<&'a str>) -> ImSortedSet<Symbol> {
|
||||
ImSortedSet::from(vec.into_iter().map(sym).collect::<Vec<_>>())
|
||||
}
|
||||
|
||||
// BASIC CANONICALIZATION
|
||||
|
||||
#[test]
|
||||
|
@ -109,14 +112,14 @@ mod test_canonicalize {
|
|||
locals: vec!["func"],
|
||||
globals: vec![],
|
||||
variants: vec![],
|
||||
calls: vec!["func"],
|
||||
tail_call: None
|
||||
}.into());
|
||||
|
||||
assert_eq!(procedures,
|
||||
mut_map_from_pairs(vec![(sym("func"),
|
||||
mut_sorted_map_from_pairs(vec![(sym("func"),
|
||||
Procedure {
|
||||
name: Some("func".to_string()),
|
||||
closes_over: ImSet::default(),
|
||||
is_self_tail_recursive: false,
|
||||
definition: empty_region(),
|
||||
args: vec![Pattern::Identifier(sym("arg"))],
|
||||
|
@ -124,7 +127,13 @@ mod test_canonicalize {
|
|||
loc_box(Expr::Var(sym("arg"))),
|
||||
loc(Operator::Plus),
|
||||
loc_box(Expr::Int(1))
|
||||
)
|
||||
),
|
||||
references: References {
|
||||
locals: vec_to_set(vec![]),
|
||||
globals: vec_to_set(vec![]),
|
||||
variants: vec_to_set(vec![]),
|
||||
calls: vec_to_set(vec![]),
|
||||
}
|
||||
})])
|
||||
);
|
||||
}
|
||||
|
@ -149,6 +158,7 @@ mod test_canonicalize {
|
|||
locals: vec!["func", "local"],
|
||||
globals: vec![],
|
||||
variants: vec![],
|
||||
calls: vec!["func"],
|
||||
tail_call: None
|
||||
}.into());
|
||||
}
|
||||
|
@ -173,11 +183,11 @@ mod test_canonicalize {
|
|||
locals: vec!["local"],
|
||||
globals: vec![],
|
||||
variants: vec![],
|
||||
calls: vec![],
|
||||
tail_call: None
|
||||
}.into());
|
||||
}
|
||||
|
||||
|
||||
// UNRECOGNIZED
|
||||
|
||||
#[test]
|
||||
|
@ -198,6 +208,7 @@ mod test_canonicalize {
|
|||
locals: vec![],
|
||||
globals: vec![],
|
||||
variants: vec![],
|
||||
calls: vec![],
|
||||
tail_call: None
|
||||
}.into());
|
||||
}
|
||||
|
@ -219,6 +230,7 @@ mod test_canonicalize {
|
|||
locals: vec!["a", "b"],
|
||||
globals: vec![],
|
||||
variants: vec![],
|
||||
calls: vec![],
|
||||
tail_call: None
|
||||
}.into());
|
||||
}
|
||||
|
@ -226,11 +238,12 @@ mod test_canonicalize {
|
|||
// UNUSED
|
||||
|
||||
#[test]
|
||||
fn mutual_unused_closed_over_vars() {
|
||||
fn mutual_unused_circular_vars() {
|
||||
// This should report that both a and b are unused, since the return expr never references them.
|
||||
// It should not report them as circular, since we haven't solved the halting problem here.
|
||||
let (_, output, problems, _) = can_expr(indoc!(r#"
|
||||
a = \_ -> b 7
|
||||
b = \_ -> a 6
|
||||
a = \arg -> if arg > 0 then b 7 else 0
|
||||
b = \arg -> if arg > 0 then a (arg - 1) else 0
|
||||
c = 5
|
||||
|
||||
c
|
||||
|
@ -242,10 +255,9 @@ mod test_canonicalize {
|
|||
locals: vec!["c"],
|
||||
globals: vec![],
|
||||
variants: vec![],
|
||||
calls: vec![],
|
||||
tail_call: None
|
||||
}.into());
|
||||
|
||||
panic!("TODO this shuoldn't report circular assignment problems; we haven't solved the halting problem here!");
|
||||
}
|
||||
|
||||
#[test]
|
||||
|
@ -266,6 +278,7 @@ mod test_canonicalize {
|
|||
locals: vec!["fibonacci"],
|
||||
globals: vec![],
|
||||
variants: vec![],
|
||||
calls: vec!["fibonacci"],
|
||||
tail_call: Some("fibonacci")
|
||||
}.into());
|
||||
}
|
||||
|
@ -289,6 +302,7 @@ mod test_canonicalize {
|
|||
locals: vec!["func", "x", "y", "z"],
|
||||
globals: vec![],
|
||||
variants: vec![],
|
||||
calls: vec!["func"],
|
||||
tail_call: None
|
||||
}.into());
|
||||
|
||||
|
@ -304,7 +318,59 @@ mod test_canonicalize {
|
|||
"#)).0);
|
||||
}
|
||||
|
||||
// TODO test reordering where closed-over values are part of the dependency chain
|
||||
#[test]
|
||||
fn reorder_closed_over_assignments() {
|
||||
let (expr, output, problems, _) = can_expr(indoc!(r#"
|
||||
z = func1 x
|
||||
x = 9
|
||||
y = func2 3
|
||||
func1 = \arg -> func2 arg + y
|
||||
func2 = \arg -> arg + x
|
||||
|
||||
z
|
||||
"#));
|
||||
|
||||
assert_eq!(problems, vec![]);
|
||||
|
||||
assert_eq!(output, Out {
|
||||
locals: vec!["func1", "func2", "x", "y", "z"],
|
||||
globals: vec![],
|
||||
variants: vec![],
|
||||
calls: vec!["func1", "func2"],
|
||||
tail_call: None
|
||||
}.into());
|
||||
|
||||
// This should get reordered to the following, so that in code gen
|
||||
// everything will have been set before it gets read.
|
||||
// (The order of the function definitions doesn't matter.)
|
||||
assert_assignment_order(expr,
|
||||
vec!["func1", "x", "z", "func2", "y"],
|
||||
);
|
||||
}
|
||||
|
||||
fn assert_assignment_order(expr: Expr, expected_strings: Vec<&str>) {
|
||||
match expr {
|
||||
Assign(assignments, _) => {
|
||||
let expected_symbols: Vec<Symbol> = expected_strings.into_iter().map(sym).collect();
|
||||
let actual_symbols: Vec<Symbol> = assignments.into_iter().map(|(pattern, _)| {
|
||||
match pattern {
|
||||
Identifier(symbol) => {
|
||||
symbol
|
||||
},
|
||||
_ => {
|
||||
panic!("Called assert_assignment_order passing an Assign expr with non-Identifier patterns!");
|
||||
}
|
||||
}
|
||||
}).collect();
|
||||
|
||||
assert_eq!(actual_symbols, expected_symbols);
|
||||
}
|
||||
_ => {
|
||||
panic!("Called assert_assignment_order passing a non-Assign expr!");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
// CIRCULAR ASSIGNMENT
|
||||
|
||||
|
@ -329,6 +395,10 @@ mod test_canonicalize {
|
|||
panic!("TODO strongly_connected_component doesn't sort these, but we want them sorted!");
|
||||
}
|
||||
|
||||
|
||||
// TODO verify that Apply handles output.references.calls correctly
|
||||
|
||||
|
||||
// UNSUPPORTED PATTERNS
|
||||
|
||||
// TODO verify that in closures and assignments, you can't assign to int/string/underscore/etc
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue