Code gen ints and floats

This commit is contained in:
Richard Feldman 2019-10-21 20:12:33 -04:00
parent e949188319
commit eeb2bb376c
13 changed files with 758 additions and 853 deletions

View file

@ -55,11 +55,11 @@ pub fn canonicalize_declaration<'a>(
// visited an Operator node we'd recursively try to apply this to each of its nested
// operators, and thena again on *their* nested operators, ultimately applying the
// rules multiple times unnecessarily.
let loc_expr = operator::desugar(arena, loc_expr);
let loc_expr = operator::desugar(arena, &loc_expr);
// 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).into();
// 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 (loc_expr, output) = canonicalize_expr(
@ -103,19 +103,18 @@ fn canonicalize_expr(
let (expr, output) = match expr {
ast::Expr::Int(string) => {
let (constraint, answer) = int_from_parsed(subs, string, env, expected, region.clone());
let (constraint, answer) = int_from_parsed(subs, string, env, expected, region);
(answer, Output::new(constraint))
}
ast::Expr::Float(string) => {
let (constraint, answer) =
float_from_parsed(subs, string, env, expected, region.clone());
let (constraint, answer) = float_from_parsed(subs, string, env, expected, region);
(answer, Output::new(constraint))
}
ast::Expr::Record(fields) => {
if fields.is_empty() {
let constraint = Eq(EmptyRec, expected, region.clone());
let constraint = Eq(EmptyRec, expected, region);
(EmptyRecord, Output::new(constraint))
} else {
@ -123,18 +122,14 @@ fn canonicalize_expr(
}
}
ast::Expr::Str(string) => {
let constraint = Eq(constrain::str_type(), expected, region.clone());
let constraint = Eq(constrain::str_type(), expected, region);
(Str((*string).into()), Output::new(constraint))
}
ast::Expr::List(loc_elems) => {
if loc_elems.is_empty() {
let list_var = subs.mk_flex_var();
let constraint = Eq(
constrain::empty_list_type(list_var),
expected,
region.clone(),
);
let constraint = Eq(constrain::empty_list_type(list_var), expected, region);
(List(list_var, Vec::new()), Output::new(constraint))
} else {
@ -150,14 +145,14 @@ fn canonicalize_expr(
let elem_expected = NoExpectation(elem_type.clone());
let list_elem_constraint = Eq(
list_type.clone(),
ForReason(Reason::ElemInList, elem_type, region.clone()),
region.clone(),
ForReason(Reason::ElemInList, elem_type, region),
region,
);
let (can_expr, elem_out) = canonicalize_expr(
env,
subs,
scope,
loc_elem.region.clone(),
loc_elem.region,
&loc_elem.value,
elem_expected,
);
@ -170,11 +165,7 @@ fn canonicalize_expr(
can_elems.push(can_expr);
}
constraints.push(Eq(
constrain::list_type(list_type),
expected,
region.clone(),
));
constraints.push(Eq(constrain::list_type(list_type), expected, region));
let mut output = Output::new(And(constraints));
@ -214,26 +205,19 @@ fn canonicalize_expr(
// (expr, output)
//}
ast::Expr::Apply((loc_fn, loc_args, application_style)) => {
ast::Expr::Apply(loc_fn, loc_args, application_style) => {
// The expression that evaluates to the function being called, e.g. `foo` in
// (foo) bar baz
let fn_var = subs.mk_flex_var();
let fn_type = Variable(fn_var);
let fn_region = loc_fn.region.clone();
let fn_region = loc_fn.region;
let fn_expected = NoExpectation(fn_type.clone());
// TODO look up the name and use NamedFnArg if possible.
let fn_reason = Reason::AnonymousFnCall(loc_args.len() as u8);
// Canonicalize the function expression and its arguments
let (fn_expr, mut output) = canonicalize_expr(
env,
subs,
scope,
loc_fn.region.clone(),
&loc_fn.value,
fn_expected,
);
let fn_con = output.constraint;
let (fn_expr, mut output) =
canonicalize_expr(env, subs, scope, loc_fn.region, &loc_fn.value, fn_expected);
// The function's return type
let ret_var = subs.mk_flex_var();
@ -252,17 +236,17 @@ fn canonicalize_expr(
let mut outputs = Vec::new();
for (index, loc_arg) in loc_args.iter().enumerate() {
let region = loc_arg.region.clone();
let region = loc_arg.region;
let arg_var = subs.mk_flex_var();
let arg_type = Variable(arg_var);
// TODO look up the name and use NamedFnArg if possible.
let reason = Reason::AnonymousFnArg(index as u8);
let expected_arg = ForReason(reason, arg_type.clone(), region.clone());
let expected_arg = ForReason(reason, arg_type.clone(), region);
let (arg_expr, arg_out) = canonicalize_expr(
env,
subs,
scope,
loc_arg.region.clone(),
loc_arg.region,
&loc_arg.value,
expected_arg,
);
@ -277,6 +261,9 @@ fn canonicalize_expr(
outputs.push(arg_out);
}
// We're not tail-calling a symbol (by name), we're tail-calling a function value.
output.tail_call = None;
let expr = match &fn_expr.value {
&Var(_, ref sym) | &FunctionPointer(_, ref sym) => {
// In the FunctionPointer case, we're calling an inline closure;
@ -285,10 +272,14 @@ fn canonicalize_expr(
CallByName(sym.clone(), args, *application_style)
}
_ => {
&RuntimeError(_) => {
// We can't call a runtime error; bail out by propagating it!
return (fn_expr, output);
}
not_var => {
// This could be something like ((if True then fn1 else fn2) arg1 arg2).
// Use CallPointer here.
panic!("TODO support function calls that aren't by name, via CallPointer");
panic!("TODO support function calls that aren't by name, via CallPointer, in this case: {:?}", not_var);
}
};
@ -296,8 +287,7 @@ fn canonicalize_expr(
output.references = output.references.union(arg_out.references);
}
// We're not tail-calling a symbol (by name), we're tail-calling a function value.
output.tail_call = None;
let fn_con = output.constraint;
// TODO occurs check!
// return $ exists vars $ CAnd ...
@ -305,14 +295,14 @@ fn canonicalize_expr(
let expected_fn_type = ForReason(
fn_reason,
Function(arg_types, Box::new(ret_type.clone())),
region.clone(),
region,
);
output.constraint = And(vec![
fn_con,
Eq(fn_type, expected_fn_type, fn_region),
And(arg_cons),
Eq(ret_type, expected, region.clone()),
Eq(ret_type, expected, region),
]);
(expr, output)
@ -323,13 +313,14 @@ fn canonicalize_expr(
} else {
Symbol::from_parts(module_parts, name)
};
let mut output = Output::new(Lookup(symbol, expected, region.clone()));
let mut output = Output::new(Lookup(symbol, expected, region));
let ident = Ident::new(module_parts, name);
let can_expr = match resolve_ident(&env, &scope, ident, &mut output.references) {
Ok(symbol) => Var(subs.mk_flex_var(), symbol),
Err(ident) => {
let loc_ident = Located {
region: region.clone(),
region: region,
value: ident,
};
@ -360,7 +351,7 @@ fn canonicalize_expr(
// Ok(symbol) => Var(symbol),
// Err(ident) => {
// let loc_ident = Located {
// region: loc_ident.region.clone(),
// region: loc_ident.region,
// value: ident,
// };
@ -411,7 +402,7 @@ fn canonicalize_expr(
// Ok(symbol) => ApplyVariant(symbol, opt_can_args),
// Err(variant_name) => {
// let loc_variant = Located {
// region: loc_expr.region.clone(),
// region: loc_expr.region,
// value: variant_name,
// };
@ -423,12 +414,12 @@ fn canonicalize_expr(
// (can_expr, output)
//}
ast::Expr::Defs((defs, loc_ret)) => {
ast::Expr::Defs(defs, loc_ret) => {
// The body expression gets a new scope for canonicalization,
// so clone it.
can_defs(env, subs, scope.clone(), defs, expected, loc_ret)
}
ast::Expr::Closure((loc_arg_patterns, loc_body_expr)) => {
ast::Expr::Closure(loc_arg_patterns, loc_body_expr) => {
// The globally unique symbol that will refer to this closure once it gets converted
// into a top-level procedure for code gen.
//
@ -479,7 +470,7 @@ fn canonicalize_expr(
env,
subs,
&mut scope,
loc_body_expr.region.clone(),
loc_body_expr.region,
&loc_body_expr.value,
body_type,
);
@ -505,9 +496,9 @@ fn canonicalize_expr(
ret_constraint,
})),
// "the closure's type is equal to the var we've stored for later use in the proc"
Eq(args.typ, NoExpectation(typ.clone()), region.clone()),
Eq(args.typ, NoExpectation(typ.clone()), region),
// "the var we've stored for later is equal to the overall expected type"
Eq(typ, expected, region.clone()),
Eq(typ, expected, region),
]);
// Now that we've collected all the references, check to see if any of the args we defined
@ -534,7 +525,7 @@ fn canonicalize_expr(
symbol.clone(),
can_args,
loc_body_expr,
region.clone(),
region,
output.references.clone(),
var,
args.ret_var,
@ -597,7 +588,7 @@ fn canonicalize_expr(
// for (ident, (symbol, region)) in assigned_idents {
// if !output.references.has_local(&symbol) {
// let loc_ident = Located {
// region: region.clone(),
// region: region,
// value: ident.clone(),
// };
@ -622,14 +613,6 @@ fn canonicalize_expr(
// (expr, output)
// }
ast::Expr::SpaceBefore(sub_expr, _spaces) => {
// Continue on; spaces don't do anything.
return canonicalize_expr(env, subs, scope, region, sub_expr, expected);
}
ast::Expr::SpaceAfter(sub_expr, _spaces) => {
// Continue on; spaces don't do anything.
return canonicalize_expr(env, subs, scope, region, sub_expr, expected);
}
ast::Expr::BlockStr(_)
| ast::Expr::Field(_, _)
| ast::Expr::QualifiedField(_, _)
@ -647,21 +630,35 @@ fn canonicalize_expr(
);
}
ast::Expr::BinaryInt(string) => {
let (constraint, answer) = bin_from_parsed(subs, string, env, expected, region.clone());
let (constraint, answer) = bin_from_parsed(subs, string, env, expected, region);
(answer, Output::new(constraint))
}
ast::Expr::HexInt(string) => {
let (constraint, answer) = hex_from_parsed(subs, string, env, expected, region.clone());
let (constraint, answer) = hex_from_parsed(subs, string, env, expected, region);
(answer, Output::new(constraint))
}
ast::Expr::OctalInt(string) => {
let (constraint, answer) = oct_from_parsed(subs, string, env, expected, region.clone());
let (constraint, answer) = oct_from_parsed(subs, string, env, expected, region);
(answer, Output::new(constraint))
}
ast::Expr::Operator((loc_left, loc_op, loc_right)) => {
// Below this point, we shouln't see any of these nodes anymore because
// operator desugaring should have removed them!
ast::Expr::SpaceBefore(sub_expr, _spaces) => {
panic!(
"A SpaceBefore did not get removed during operator desugaring somehow: {:?}",
sub_expr
);
}
ast::Expr::SpaceAfter(sub_expr, _spaces) => {
panic!(
"A SpaceAfter did not get removed during operator desugaring somehow: {:?}",
sub_expr
);
}
ast::Expr::Operator((_, loc_op, _)) => {
panic!("An operator did not get desugared somehow: {:?}", loc_op);
}
};
@ -892,10 +889,7 @@ fn add_idents_from_pattern<'a>(
&Identifier(name) => {
let symbol = scope.symbol(&name);
answer.push((
Ident::Unqualified(name.to_string()),
(symbol, region.clone()),
));
answer.push((Ident::Unqualified(name.to_string()), (symbol, *region)));
}
&QualifiedIdentifier(_name) => {
panic!("TODO implement QualifiedIdentifier pattern.");
@ -1294,7 +1288,7 @@ fn can_defs<'a>(
let value = Expr::RuntimeError(NoImplementation);
let loc_expr = Located {
value,
region: loc_annotation.region.clone(),
region: loc_annotation.region,
};
(None, (loc_expr, Output::new(True)))
@ -1336,7 +1330,7 @@ fn can_defs<'a>(
env,
subs,
&mut scope,
loc_expr.region.clone(),
loc_expr.region,
&loc_expr.value,
NoExpectation(expr_type),
);
@ -1394,6 +1388,7 @@ fn can_defs<'a>(
// Since everywhere in the code it'll be referred to by its assigned name,
// remove its generated name from the procedure map. (We'll re-insert it later.)
let mut procedure = env.procedures.remove(&symbol).unwrap();
let proc_var = procedure.var;
// The original ident name will be used for debugging and stack traces.
procedure.name = Some((*name).into());
@ -1420,7 +1415,7 @@ fn can_defs<'a>(
// Return a reference to the assigned symbol, since the auto-generated one no
// longer references any entry in the procedure map!
Var(subs.mk_flex_var(), assigned_symbol.clone())
Var(proc_var, assigned_symbol.clone())
}
_ => loc_can_expr.value,
};
@ -1462,7 +1457,7 @@ fn can_defs<'a>(
(
loc_can_pattern.clone(),
Located {
region: loc_can_expr.region.clone(),
region: loc_can_expr.region,
value: can_expr.clone(),
},
),
@ -1477,7 +1472,7 @@ fn can_defs<'a>(
env,
subs,
&mut scope,
loc_ret.region.clone(),
loc_ret.region,
&loc_ret.value,
expected,
);
@ -1554,7 +1549,7 @@ fn can_defs<'a>(
for (ident, (symbol, region)) in assigned_idents.clone() {
if !output.references.has_local(&symbol) {
let loc_ident = Located {
region: region.clone(),
region: region,
value: ident.clone(),
};
@ -1619,7 +1614,7 @@ fn can_defs<'a>(
let mut regions = Vec::with_capacity(can_assignments_by_symbol.len());
for (loc_pattern, loc_expr) in can_assignments_by_symbol.values() {
regions.push((loc_pattern.region.clone(), loc_expr.region.clone()));
regions.push((loc_pattern.region, loc_expr.region));
}
(

View file

@ -2,6 +2,7 @@ use bumpalo::collections::Vec;
use bumpalo::Bump;
use operator::Operator::Pizza;
use operator::{CalledVia, Operator};
use parse::ast::Def;
use parse::ast::Expr::{self, *};
use region::{Located, Region};
use types;
@ -31,16 +32,66 @@ fn new_op_expr<'a>(
}
/// Reorder the expression tree based on operator precedence and associativity rules,
/// then replace the Operator nodes with Apply nodes.
pub fn desugar<'a>(arena: &'a Bump, expr: Located<Expr<'a>>) -> Located<Expr<'a>> {
/// then replace the Operator nodes with Apply nodes. Also drop SpaceBefore and SpaceAfter nodes.
pub fn desugar<'a>(arena: &'a Bump, loc_expr: &'a Located<Expr<'a>>) -> &'a Located<Expr<'a>> {
use operator::Associativity::*;
use std::cmp::Ordering;
// NOTE: A potentially nice performance optimization here would be to use
// arena bump allocation for Infixes, arg_stack, and op_stack. As long as we
// allocate each element inside arg_stack outside the arena, this should end
// up being a decent bit more efficient.
let mut infixes = Infixes::new(arena.alloc(expr));
match &loc_expr.value {
Float(_)
| Int(_)
| HexInt(_)
| OctalInt(_)
| BinaryInt(_)
| Str(_)
| BlockStr(_)
| QualifiedField(_, _)
| AccessorFunction(_)
| Var(_, _)
| MalformedIdent(_)
| MalformedClosure
| PrecedenceConflict(_, _, _)
| Variant(_, _) => loc_expr,
Field(sub_expr, paths) => arena.alloc(Located {
region: loc_expr.region,
value: Field(desugar(arena, sub_expr), paths.clone()),
}),
List(elems) => {
let mut new_elems = Vec::with_capacity_in(elems.len(), arena);
for elem in elems {
new_elems.push(desugar(arena, elem));
}
let value: Expr<'a> = List(new_elems);
arena.alloc(Located {
region: loc_expr.region,
value,
})
}
Record(elems) => {
let mut new_elems = Vec::with_capacity_in(elems.len(), arena);
for elem in elems {
new_elems.push(desugar(arena, elem));
}
arena.alloc(Located {
region: loc_expr.region,
value: Record(new_elems),
})
}
AssignField(string, sub_expr) => arena.alloc(Located {
value: AssignField(string.clone(), desugar(arena, sub_expr)),
region: loc_expr.region,
}),
Closure(loc_patterns, loc_ret) => arena.alloc(Located {
region: loc_expr.region,
value: Closure(loc_patterns, desugar(arena, loc_ret)),
}),
Operator(_) => {
let mut infixes = Infixes::new(arena.alloc(loc_expr));
let mut arg_stack: Vec<&'a Located<Expr>> = Vec::new_in(arena);
let mut op_stack: Vec<Located<Operator>> = Vec::new_in(arena);
@ -108,14 +159,14 @@ pub fn desugar<'a>(arena: &'a Bump, expr: Located<Expr<'a>>) -> Located<Expr<'a>
next_op,
right.clone(),
);
let region = broken_expr.region.clone();
let region = broken_expr.region;
let value = Expr::PrecedenceConflict(
bad_op,
stack_op,
arena.alloc(broken_expr),
);
return Located { region, value };
return arena.alloc(Located { region, value });
}
_ => {
@ -144,7 +195,7 @@ pub fn desugar<'a>(arena: &'a Bump, expr: Located<Expr<'a>>) -> Located<Expr<'a>
let left = arg_stack.pop().unwrap();
let region = Region::span_across(&left.region, &right.region);
let expr = match loc_op.value {
let value = match loc_op.value {
Pizza => {
// Rewrite the Pizza operator into an Apply
panic!("TODO desugar |> operator into an Apply");
@ -155,27 +206,99 @@ pub fn desugar<'a>(arena: &'a Bump, expr: Located<Expr<'a>>) -> Located<Expr<'a>
let (module_parts, name) = desugar_binop(&binop, arena);
let mut args = Vec::with_capacity_in(2, arena);
args.push(left.clone());
args.push(right.clone());
args.push(*arena.alloc(left));
args.push(*arena.alloc(right));
let loc_expr = Located {
let loc_expr = arena.alloc(Located {
value: Expr::Var(module_parts, name),
region: loc_op.region,
};
});
Apply(arena.alloc((loc_expr, args, CalledVia::Operator(binop))))
Apply(loc_expr, args, CalledVia::Operator(binop))
}
};
arg_stack.push(arena.alloc(Located {
region,
value: expr,
}));
arg_stack.push(arena.alloc(Located { region, value }));
}
assert_eq!(arg_stack.len(), 1);
arg_stack.pop().unwrap().clone()
arg_stack.pop().unwrap()
}
Defs(pairs, loc_ret) => {
let mut desugared_defs = Vec::with_capacity_in(pairs.len(), arena);
for (_, def) in pairs {
let def = match def {
Def::AnnotationOnly(ann) => Def::AnnotationOnly(ann.clone()),
Def::BodyOnly(pattern, loc_expr) => {
Def::BodyOnly(pattern.clone(), desugar(arena, loc_expr))
}
Def::AnnotatedBody(annotation, pattern, loc_body) => Def::AnnotatedBody(
annotation.clone(),
pattern.clone(),
desugar(arena, loc_body),
),
};
desugared_defs.push((Vec::new_in(arena).into_bump_slice(), def));
}
arena.alloc(Located {
value: Defs(desugared_defs, desugar(arena, loc_ret)),
region: loc_expr.region,
})
}
Apply(loc_fn, loc_args, called_via) => {
let mut desugared_args = Vec::with_capacity_in(loc_args.len(), arena);
for loc_arg in loc_args {
desugared_args.push(desugar(arena, loc_arg));
}
arena.alloc(Located {
value: Apply(desugar(arena, loc_fn), desugared_args, called_via.clone()),
region: loc_expr.region,
})
}
// If(&'a (Loc<Expr<'a>>, Loc<Expr<'a>>, Loc<Expr<'a>>)),
// Case(
// &'a Loc<Expr<'a>>,
// Vec<'a, &'a (Loc<Pattern<'a>>, Loc<Expr<'a>>)>,
// ),
SpaceBefore(expr, _) => {
// Since we've already begun canonicalization, these are no longer needed
// and should be dropped.
desugar(
arena,
arena.alloc(Located {
// TODO FIXME performance disaster!!! Must remove this clone!
//
// This won't be easy because:
//
// * If this function takes an &'a Expr, then Infixes hits a problem.
// * If SpaceBefore holds a Loc<&'a Expr>, then Spaceable hits a problem.
// * If all the existing &'a Loc<Expr> values become Loc<&'a Expr>...who knows?
value: (*expr).clone(),
region: loc_expr.region,
}),
)
}
SpaceAfter(expr, _) => {
// Since we've already begun canonicalization, these are no longer needed
// and should be dropped.
desugar(
arena,
arena.alloc(Located {
// TODO FIXME performance disaster!!! Must remove this clone! (Not easy.)
value: (*expr).clone(),
region: loc_expr.region,
}),
)
}
other => panic!("TODO desugar {:?}", other),
}
}
#[inline(always)]
@ -197,7 +320,7 @@ fn desugar_binop<'a>(binop: &Operator, arena: &'a Bump) -> (&'a [&'a str], &'a s
),
DoubleSlash => (
bumpalo::vec![ in arena; types::MOD_INT ].into_bump_slice(),
"div",
"divFloor",
),
Percent => (
bumpalo::vec![ in arena; types::MOD_NUM ].into_bump_slice(),
@ -303,14 +426,17 @@ impl<'a> Iterator for Infixes<'a> {
fn next(&mut self) -> Option<InfixToken<'a>> {
match self.next_op.take() {
Some(op) => Some(InfixToken::Op(op)),
None => self.remaining_expr.take().map(|expr| match expr.value {
None => self
.remaining_expr
.take()
.map(|loc_expr| match loc_expr.value {
Expr::Operator((left, op, right)) => {
self.remaining_expr = Some(right);
self.next_op = Some(op.clone());
InfixToken::Arg(left)
}
_ => InfixToken::Arg(expr),
_ => InfixToken::Arg(loc_expr),
}),
}
}

View file

@ -51,7 +51,7 @@ pub fn canonicalize_pattern<'a>(
use self::PatternType::*;
use can::ast::Pattern::*;
let region = loc_pattern.region.clone();
let region = loc_pattern.region;
let pattern = match &loc_pattern.value {
&Identifier(ref name) => {
let unqualified_ident = Ident::Unqualified(name.to_string());
@ -66,7 +66,7 @@ pub fn canonicalize_pattern<'a>(
match shadowable_idents.get(&unqualified_ident) {
Some((_, region)) => {
let loc_shadowed_ident = Located {
region: region.clone(),
region: *region,
value: unqualified_ident,
};
@ -86,7 +86,7 @@ pub fn canonicalize_pattern<'a>(
match scope.idents.get(&qualified_ident) {
Some((_, region)) => {
let loc_shadowed_ident = Located {
region: region.clone(),
region: *region,
value: qualified_ident,
};
@ -105,7 +105,7 @@ pub fn canonicalize_pattern<'a>(
// This is a fresh identifier that wasn't already in scope.
// Add it to scope!
let symbol_and_region = (symbol.clone(), region.clone());
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,
@ -114,7 +114,7 @@ pub fn canonicalize_pattern<'a>(
scope
.idents
.insert(new_ident.clone(), symbol_and_region.clone());
shadowable_idents.insert(new_ident, symbol_and_region);
shadowable_idents.insert(new_ident, symbol_and_region.clone());
Pattern::Identifier(subs.mk_flex_var(), symbol)
}
@ -162,7 +162,7 @@ pub fn canonicalize_pattern<'a>(
Pattern::Variant(subs.mk_flex_var(), symbol)
} else {
let loc_name = Located {
region: region.clone(),
region: region,
value: variant,
};
// We couldn't find the variant name in scope. NAMING PROBLEM!
@ -188,7 +188,7 @@ pub fn canonicalize_pattern<'a>(
// },
&Underscore => match pattern_type {
CaseBranch | FunctionArg => Pattern::Underscore(subs.mk_flex_var()),
Assignment => unsupported_pattern(env, Assignment, region.clone()),
Assignment => unsupported_pattern(env, Assignment, region),
},
// &EmptyRecordLiteral => Pattern::EmptyRecordLiteral,
@ -204,7 +204,7 @@ pub fn canonicalize_pattern<'a>(
/// When we detect an unsupported pattern type (e.g. 5 = 1 + 2 is unsupported because you can't
/// assign to Int patterns), report it to Env and return an UnsupportedPattern runtime error pattern.
fn unsupported_pattern<'a>(env: &'a mut Env, pattern_type: PatternType, region: Region) -> Pattern {
env.problem(Problem::UnsupportedPattern(pattern_type, region.clone()));
env.problem(Problem::UnsupportedPattern(pattern_type, region));
Pattern::UnsupportedPattern(region)
}

View file

@ -11,7 +11,11 @@ impl Symbol {
}
pub fn from_parts(module_parts: &[&str], name: &str) -> Symbol {
Symbol(format!("{}{}", module_parts.join("."), name).into())
Symbol(if module_parts.is_empty() {
name.into()
} else {
format!("{}.{}", module_parts.join("."), name).into()
})
}
pub fn from_variant(variant_name: &VariantName, home: &str) -> Symbol {

View file

@ -30,11 +30,11 @@ fn num_literal(
) -> Constraint {
let num_var = subs.mk_flex_var();
let num_type = Variable(num_var);
let expected_literal = ForReason(reason, literal_type, region.clone());
let expected_literal = ForReason(reason, literal_type, region);
And(vec![
Eq(num_type.clone(), expected_literal, region.clone()),
Eq(num_type, expected, region.clone()),
Eq(num_type.clone(), expected_literal, region),
Eq(num_type, expected, region),
])
}

View file

@ -1,73 +1,27 @@
//! This is an example of the [Kaleidoscope tutorial](https://llvm.org/docs/tutorial/)
//! made in Rust, using Inkwell.
//! Currently, all features up to the [7th chapter](https://llvm.org/docs/tutorial/LangImpl07.html)
//! are available.
//! This example is supposed to be ran as a executable, which launches a REPL.
//! The source code is in the following order:
//! - Lexer,
//! - Parser,
//! - Emitter,
//! - Program.
//!
//! Both the `Parser` and the `Emitter` may fail, in which case they would return
//! an error represented by `Result<T, &'static str>`, for easier error reporting.
extern crate inkwell;
use std::borrow::Borrow;
use std::collections::HashMap;
use std::io::Write;
// use std::iter::Peekable;
// use std::ops::DerefMut;
// use std::str::Chars;
use inkwell::builder::Builder;
use inkwell::context::Context;
use inkwell::module::Module;
use inkwell::passes::PassManager;
use inkwell::types::BasicTypeEnum;
use inkwell::types::{BasicType, BasicTypeEnum};
use inkwell::values::{BasicValueEnum, FloatValue, FunctionValue, IntValue, PointerValue};
use inkwell::FloatPredicate;
use can::expr;
use can::expr::Expr;
use can::procedure::Procedure;
use can::symbol::Symbol;
use collections::ImMap;
use collections::MutMap;
use subs::Variable;
// ======================================================================================
// LEXER ================================================================================
// ======================================================================================
/// Represents a primitive syntax token.
#[derive(Debug, Clone)]
pub enum Token {
Binary,
Comma,
Comment,
Def,
Else,
EOF,
Extern,
For,
Ident(String),
If,
In,
LParen,
Number(f64),
Op(char),
RParen,
Then,
Unary,
Var,
}
use subs::FlatType::*;
use subs::{Content, Subs, Variable};
use types;
/// Defines the prototype (name and parameters) of a function.
#[derive(Debug)]
pub struct Prototype {
pub name: String,
pub args: Vec<String>,
pub args: Vec<(String, BasicTypeEnum)>,
pub ret: BasicTypeEnum,
}
/// Defines a user-defined or external function.
@ -84,46 +38,8 @@ pub struct Emitter<'a> {
pub fpm: &'a PassManager<FunctionValue>,
pub module: &'a Module,
pub function: &'a Function,
variables: HashMap<String, PointerValue>,
fn_value_opt: Option<FunctionValue>,
}
#[derive(Debug)]
pub enum Expr {
Binary {
op: char,
left: Box<Expr>,
right: Box<Expr>,
},
Call {
fn_name: String,
args: Vec<Expr>,
},
Conditional {
cond: Box<Expr>,
consequence: Box<Expr>,
alternative: Box<Expr>,
},
For {
var_name: String,
start: Box<Expr>,
end: Box<Expr>,
step: Option<Box<Expr>>,
body: Box<Expr>,
},
Number(f64),
Variable(String),
VarIn {
variables: Vec<(String, Option<Expr>)>,
body: Box<Expr>,
},
// variables: HashMap<String, PointerValue>,
// fn_value_opt: Option<FunctionValue>,
}
pub struct ModuleBuilder<'a> {
@ -132,8 +48,8 @@ pub struct ModuleBuilder<'a> {
pub fpm: &'a PassManager<FunctionValue>,
pub module: &'a Module,
pub function: &'a Function,
pub procedures: &'a MutMap<Symbol, Procedure>,
procedures: &'a MutMap<Symbol, Procedure>,
fn_value_opt: Option<FunctionValue>,
}
@ -161,11 +77,123 @@ impl<'a> ModuleBuilder<'a> {
self.module.get_function(name)
}
fn compile_expr(
&mut self,
expr: &expr::Expr,
vars: &mut ImMap<Symbol, PointerValue>,
) -> TypedVal {
/// Compiles the specified `Function` in the given `Context` and using the specified `Builder`, `PassManager`, and `Module`.
pub fn build(
context: &'a Context,
builder: &'a Builder,
pass_manager: &'a PassManager<FunctionValue>,
procedures: &'a MutMap<Symbol, Procedure>,
module: &'a Module,
function: &Function,
) -> Result<FunctionValue, &'static str> {
let mut compiler = ModuleBuilder {
context,
builder,
fpm: pass_manager,
module,
function,
procedures,
fn_value_opt: None,
};
compiler.compile_fn()
}
/// Compiles the specified `Prototype` into an extern LLVM `FunctionValue`.
fn compile_prototype(&self, proto: &Prototype) -> Result<FunctionValue, &'static str> {
let arg_types = proto
.args
.iter()
.map(|(_, typ)| typ.clone())
.collect::<Vec<BasicTypeEnum>>();
let fn_type = proto.ret.fn_type(&arg_types, false);
let fn_val = self.module.add_function(proto.name.as_str(), fn_type, None);
// set arguments names
// for (i, arg) in fn_val.get_param_iter().enumerate() {
// arg.set_name(proto.args[i].0.as_str());
// }
// finally return built prototype
Ok(fn_val)
}
/// Returns the `FunctionValue` representing the function being compiled.
#[inline]
fn fn_value(&self) -> FunctionValue {
// TODO shouldn't there be like a stack of these? What about functions inside functions?
// Also, do we even need this?
self.fn_value_opt.unwrap()
}
/// Creates a new stack allocation instruction in the entry block of the function.
fn create_entry_block_alloca(&self, name: &str) -> PointerValue {
let builder = self.context.create_builder();
let entry = self.fn_value().get_first_basic_block().unwrap();
match entry.get_first_instruction() {
Some(first_instr) => builder.position_before(&first_instr),
None => builder.position_at_end(&entry),
}
builder.build_alloca(self.context.f64_type(), name)
}
/// Compiles the specified `Function` into an LLVM `FunctionValue`.
fn compile_fn(&mut self) -> Result<FunctionValue, &'static str> {
let proto = &self.function.prototype;
let function = self.compile_prototype(proto)?;
// got external function, returning only compiled prototype
if self.function.body.is_none() {
return Ok(function);
}
let entry = self.context.append_basic_block(&function, "entry");
self.builder.position_at_end(&entry);
// update fn field
self.fn_value_opt = Some(function);
// build variables map
let mut vars = ImMap::default(); // TODO with_capacity(proto.args.len())
for (i, arg) in function.get_param_iter().enumerate() {
let arg_name = proto.args[i].0.as_str();
let alloca = self.create_entry_block_alloca(arg_name);
self.builder.build_store(alloca, arg);
vars.insert(
Symbol::from_parts(&[], proto.args[i].0.clone().as_str()),
alloca,
);
}
// compile body
let body = self.compile_expr(self.function.body.as_ref().unwrap(), &mut vars);
let basic_val: BasicValueEnum = body.into();
self.builder.build_return(Some(&basic_val));
// return the whole thing after verification and optimization
if function.verify(true) {
self.fpm.run_on(&function);
Ok(function)
} else {
unsafe {
function.delete();
}
Err("Invalid generated function.")
}
}
fn compile_expr(&mut self, expr: &Expr, vars: &mut ImMap<Symbol, PointerValue>) -> TypedVal {
use self::TypedVal::*;
use can::expr::Expr::*;
@ -191,7 +219,7 @@ impl<'a> ModuleBuilder<'a> {
}
Defs(_, _, _) => panic!("TODO gen defs"),
CallByName(symbol, loc_args, _) => {
CallByName(ref symbol, ref loc_args, _) => {
let func = self
.get_function(&*(symbol.clone()).into_boxed_str())
.unwrap_or_else(|| {
@ -214,500 +242,73 @@ impl<'a> ModuleBuilder<'a> {
.unwrap_or_else(|| panic!("Roc compiler error: Invalid call.")),
)
}
Str(_) | List(_, _) | Case(_, _, _) | Record(_, _) | EmptyRecord | RuntimeError(_) => {
Str(_)
| CallPointer(_, _, _)
| List(_, _)
| Case(_, _, _)
| Record(_, _)
| EmptyRecord
| RuntimeError(_) => {
panic!("TODO compile_expr for {:?}", expr);
}
}
}
}
impl<'a> Emitter<'a> {
/// Gets a defined function given its name.
#[inline]
fn get_function(&self, name: &str) -> Option<FunctionValue> {
self.module.get_function(name)
}
/// Returns the `FunctionValue` representing the function being compiled.
#[inline]
fn fn_value(&self) -> FunctionValue {
// TODO shouldn't there be like a stack of these? What about functions inside functions?
// Also, do we even need this?
self.fn_value_opt.unwrap()
}
/// Creates a new stack allocation instruction in the entry block of the function.
fn create_entry_block_alloca(&self, name: &str) -> PointerValue {
let builder = self.context.create_builder();
let entry = self.fn_value().get_first_basic_block().unwrap();
match entry.get_first_instruction() {
Some(first_instr) => builder.position_before(&first_instr),
None => builder.position_at_end(&entry),
}
builder.build_alloca(self.context.f64_type(), name)
}
/// Compiles the specified `Expr` into an LLVM `FloatValue`.
fn compile_expr(&mut self, expr: &Expr) -> Result<FloatValue, &'static str> {
match *expr {
// Expr::IntLiteral(num) => Ok(self.context.i64_type().const_int(num as u64, false)),
Expr::Number(num) => Ok(self.context.f64_type().const_float(num)),
Expr::Variable(ref name) => match self.variables.get(name.as_str()) {
Some(var) => Ok(self
.builder
.build_load(*var, name.as_str())
.into_float_value()),
None => Err("Could not find a matching variable."),
},
Expr::VarIn {
ref variables,
ref body,
} => {
let mut old_bindings = Vec::new();
for &(ref var_name, ref initializer) in variables {
let var_name = var_name.as_str();
let initial_val = match *initializer {
Some(ref init) => self.compile_expr(init)?,
None => self.context.f64_type().const_float(0.),
};
let alloca = self.create_entry_block_alloca(var_name);
self.builder.build_store(alloca, initial_val);
if let Some(old_binding) = self.variables.remove(var_name) {
old_bindings.push(old_binding);
}
self.variables.insert(var_name.to_string(), alloca);
}
let body = self.compile_expr(body)?;
for binding in old_bindings {
self.variables
.insert(binding.get_name().to_str().unwrap().to_string(), binding);
}
Ok(body)
}
Expr::Binary {
op,
ref left,
ref right,
} => {
if op == '=' {
// handle assignement
let var_name = match *left.borrow() {
Expr::Variable(ref var_name) => var_name,
_ => {
return Err("Expected variable as left-hand operator of assignement.");
}
};
let var_val = self.compile_expr(right)?;
let var = self
.variables
.get(var_name.as_str())
.ok_or("Undefined variable.")?;
self.builder.build_store(*var, var_val);
Ok(var_val)
} else {
let lhs = self.compile_expr(left)?;
let rhs = self.compile_expr(right)?;
match op {
'+' => Ok(self.builder.build_float_add(lhs, rhs, "tmpadd")),
'-' => Ok(self.builder.build_float_sub(lhs, rhs, "tmpsub")),
'*' => Ok(self.builder.build_float_mul(lhs, rhs, "tmpmul")),
'/' => Ok(self.builder.build_float_div(lhs, rhs, "tmpdiv")),
'<' => Ok({
let cmp = self.builder.build_float_compare(
FloatPredicate::ULT,
lhs,
rhs,
"tmpcmp",
);
self.builder.build_unsigned_int_to_float(
cmp,
self.context.f64_type(),
"tmpbool",
)
}),
'>' => Ok({
let cmp = self.builder.build_float_compare(
FloatPredicate::ULT,
rhs,
lhs,
"tmpcmp",
);
self.builder.build_unsigned_int_to_float(
cmp,
self.context.f64_type(),
"tmpbool",
)
}),
custom => {
let mut name = String::from("binary");
name.push(custom);
match self.get_function(name.as_str()) {
Some(fun) => {
match self
.builder
.build_call(fun, &[lhs.into(), rhs.into()], "tmpbin")
.try_as_basic_value()
.left()
{
Some(value) => Ok(value.into_float_value()),
None => Err("Invalid call produced."),
}
}
None => Err("Undefined binary operator."),
}
}
}
}
}
Expr::Call {
ref fn_name,
ref args,
} => match self.get_function(fn_name.as_str()) {
Some(fun) => {
let mut compiled_args = Vec::with_capacity(args.len());
for arg in args {
compiled_args.push(self.compile_expr(arg)?);
}
let argsv: Vec<BasicValueEnum> = compiled_args
.iter()
.by_ref()
.map(|&val| val.into())
.collect();
match self
.builder
.build_call(fun, argsv.as_slice(), "tmp")
.try_as_basic_value()
.left()
{
Some(value) => Ok(value.into_float_value()),
None => Err("Invalid call produced."),
}
}
None => Err("Unknown function."),
},
Expr::Conditional {
ref cond,
ref consequence,
ref alternative,
} => {
let parent = self.fn_value();
let zero_const = self.context.f64_type().const_float(0.0);
// create condition by comparing without 0.0 and returning an int
let cond = self.compile_expr(cond)?;
let cond = self.builder.build_float_compare(
FloatPredicate::ONE,
cond,
zero_const,
"ifcond",
);
// build branch
let then_bb = self.context.append_basic_block(&parent, "then");
let else_bb = self.context.append_basic_block(&parent, "else");
let cont_bb = self.context.append_basic_block(&parent, "ifcont");
self.builder
.build_conditional_branch(cond, &then_bb, &else_bb);
// build then block
self.builder.position_at_end(&then_bb);
let then_val = self.compile_expr(consequence)?;
self.builder.build_unconditional_branch(&cont_bb);
let then_bb = self.builder.get_insert_block().unwrap();
// build else block
self.builder.position_at_end(&else_bb);
let else_val = self.compile_expr(alternative)?;
self.builder.build_unconditional_branch(&cont_bb);
let else_bb = self.builder.get_insert_block().unwrap();
// emit merge block
self.builder.position_at_end(&cont_bb);
let phi = self.builder.build_phi(self.context.f64_type(), "iftmp");
phi.add_incoming(&[(&then_val, &then_bb), (&else_val, &else_bb)]);
Ok(phi.as_basic_value().into_float_value())
}
Expr::For {
ref var_name,
ref start,
ref end,
ref step,
ref body,
} => {
let parent = self.fn_value();
let start_alloca = self.create_entry_block_alloca(var_name);
let start = self.compile_expr(start)?;
self.builder.build_store(start_alloca, start);
// go from current block to loop block
let loop_bb = self.context.append_basic_block(&parent, "loop");
self.builder.build_unconditional_branch(&loop_bb);
self.builder.position_at_end(&loop_bb);
let old_val = self.variables.remove(var_name.as_str());
self.variables.insert(var_name.to_owned(), start_alloca);
// emit body
self.compile_expr(body)?;
// emit step
let step = match *step {
Some(ref step) => self.compile_expr(step)?,
None => self.context.f64_type().const_float(1.0),
};
// compile end condition
let end_cond = self.compile_expr(end)?;
let curr_var = self.builder.build_load(start_alloca, var_name);
let next_var =
self.builder
.build_float_add(curr_var.into_float_value(), step, "nextvar");
self.builder.build_store(start_alloca, next_var);
let end_cond = self.builder.build_float_compare(
FloatPredicate::ONE,
end_cond,
self.context.f64_type().const_float(0.0),
"loopcond",
);
let after_bb = self.context.append_basic_block(&parent, "afterloop");
self.builder
.build_conditional_branch(end_cond, &loop_bb, &after_bb);
self.builder.position_at_end(&after_bb);
self.variables.remove(var_name);
if let Some(val) = old_val {
self.variables.insert(var_name.to_owned(), val);
}
Ok(self.context.f64_type().const_float(0.0))
}
}
}
/// Compiles the specified `Prototype` into an extern LLVM `FunctionValue`.
fn compile_prototype(&self, proto: &Prototype) -> Result<FunctionValue, &'static str> {
let ret_type = self.context.f64_type();
let args_types = std::iter::repeat(ret_type)
.take(proto.args.len())
.map(|f| f.into())
.collect::<Vec<BasicTypeEnum>>();
let args_types = args_types.as_slice();
let fn_type = self.context.f64_type().fn_type(args_types, false);
let fn_val = self.module.add_function(proto.name.as_str(), fn_type, None);
// set arguments names
for (i, arg) in fn_val.get_param_iter().enumerate() {
arg.into_float_value().set_name(proto.args[i].as_str());
}
// finally return built prototype
Ok(fn_val)
}
/// Compiles the specified `Function` into an LLVM `FunctionValue`.
fn compile_fn(&mut self) -> Result<FunctionValue, &'static str> {
let proto = &self.function.prototype;
let function = self.compile_prototype(proto)?;
// got external function, returning only compiled prototype
if self.function.body.is_none() {
return Ok(function);
}
let entry = self.context.append_basic_block(&function, "entry");
self.builder.position_at_end(&entry);
// update fn field
self.fn_value_opt = Some(function);
// build variables map
self.variables.reserve(proto.args.len());
for (i, arg) in function.get_param_iter().enumerate() {
let arg_name = proto.args[i].as_str();
let alloca = self.create_entry_block_alloca(arg_name);
self.builder.build_store(alloca, arg);
self.variables.insert(proto.args[i].clone(), alloca);
}
// compile body
let body = self.compile_expr(self.function.body.as_ref().unwrap())?;
self.builder.build_return(Some(&body));
// return the whole thing after verification and optimization
if function.verify(true) {
self.fpm.run_on(&function);
Ok(function)
} else {
unsafe {
function.delete();
}
Err("Invalid generated function.")
}
}
/// Compiles the specified `Function` in the given `Context` and using the specified `Builder`, `PassManager`, and `Module`.
pub fn compile(
context: &'a Context,
builder: &'a Builder,
pass_manager: &'a PassManager<FunctionValue>,
module: &'a Module,
function: &Function,
) -> Result<FunctionValue, &'static str> {
let mut compiler = Emitter {
context: context,
builder: builder,
fpm: pass_manager,
module: module,
function: function,
fn_value_opt: None,
variables: HashMap::new(),
};
compiler.compile_fn()
}
}
// ======================================================================================
// PROGRAM ==============================================================================
// ======================================================================================
// macro used to print & flush without printing a new line
macro_rules! print_flush {
( $( $x:expr ),* ) => {
print!( $($x, )* );
std::io::stdout().flush().expect("Could not flush to standard output.");
};
}
#[no_mangle]
pub extern "C" fn putchard(x: f64) -> f64 {
print_flush!("{}", x as u8 as char);
x
}
#[no_mangle]
pub extern "C" fn printd(x: f64) -> f64 {
println!("{}", x);
x
}
// Adding the functions above to a global array,
// so Rust compiler won't remove them.
#[used]
static EXTERNAL_FNS: [extern "C" fn(f64) -> f64; 2] = [putchard, printd];
#[test]
fn gen() {
use inkwell::OptimizationLevel;
// use self::inkwell::support::add_symbol;
let context = Context::create();
let module = context.create_module("repl");
let builder = context.create_builder();
// Create FPM
let fpm = PassManager::create(&module);
fpm.add_instruction_combining_pass();
fpm.add_reassociate_pass();
fpm.add_gvn_pass();
fpm.add_cfg_simplification_pass();
fpm.add_basic_alias_analysis_pass();
fpm.add_promote_memory_to_register_pass();
fpm.add_instruction_combining_pass();
fpm.add_reassociate_pass();
fpm.initialize();
// make module
let module = context.create_module("tmp");
let name = "main".to_string();
let prototype = Prototype {
pub fn content_to_basic_type(
content: Content,
subs: &mut Subs,
context: &Context,
) -> Result<BasicTypeEnum, String> {
match content {
Content::Structure(flat_type) => match flat_type {
Apply {
module_name,
name,
args: Vec::new(),
};
let function = Function {
prototype,
body: Some(Expr::Number(12345.0)),
is_anon: false,
};
// make main(), a function which returns an f64
Emitter::compile(&context, &builder, &fpm, &module, &function).expect("Error compiling main");
// let fn_type = context.f64_type().fn_type(&Vec::new(), false);
// let function = module.add_function(proto.name.as_str(), fn_type, None);
// make execution engine
let ee = module
.create_jit_execution_engine(OptimizationLevel::None)
.unwrap();
let maybe_fn = unsafe { ee.get_function::<unsafe extern "C" fn() -> f64>("main") };
let compiled_fn = match maybe_fn {
Ok(f) => f,
Err(err) => {
panic!("!> Error during execution: {:?}", err);
args,
} => {
if &*module_name == types::MOD_NUM && &*name == types::TYPE_NUM {
num_to_basic_type(subs.get(*args.iter().next().unwrap()).content, context)
} else {
panic!(
"TODO handle content_to_basic_type for flat_type {}.{} with args {:?}",
module_name, name, args
);
}
};
unsafe {
assert_eq!(12345.0, compiled_fn.call());
}
other => panic!("TODO handle content_to_basic_type for {:?}", other),
},
other => Err(format!("Cannot convert {:?} to BasicTypeEnum", other)),
}
}
pub fn num_to_basic_type(content: Content, context: &Context) -> Result<BasicTypeEnum, String> {
match content {
Content::Structure(flat_type) => match flat_type {
Apply {
module_name,
name,
args,
} => {
if &*module_name == types::MOD_FLOAT && &*name == types::TYPE_FLOATINGPOINT && args.is_empty() {
debug_assert!(args.is_empty());
Ok(BasicTypeEnum::FloatType(context.f64_type()))
} else if &*module_name == types::MOD_INT && &*name == types::TYPE_INTEGER && args.is_empty() {
debug_assert!(args.is_empty());
Ok(BasicTypeEnum::IntType(context.i64_type()))
} else {
Err(format!("Unrecognized numeric type: Num {}.{} with args {:?}", module_name, name, args))
}
}
other => panic!(
"TODO handle content_to_basic_type (branch 0) for {:?} which is NESTED inside Num.Num",
other
),
},
other => panic!(
"TODO handle content_to_basic_type (branch 1) for {:?} which is NESTED inside Num.Num",
other
),
}
}

View file

@ -71,7 +71,9 @@ impl Operator {
LeftAssociative
}
And | Or | Caret => RightAssociative,
Equals | LessThan | GreaterThan | LessThanOrEq | GreaterThanOrEq => NonAssociative,
Equals | NotEquals | LessThan | GreaterThan | LessThanOrEq | GreaterThanOrEq => {
NonAssociative
}
}
}
@ -80,7 +82,7 @@ impl Operator {
Caret => 7,
Star | Slash | DoubleSlash | DoublePercent | Percent => 6,
Plus | Minus => 5,
Equals | LessThan | GreaterThan | LessThanOrEq | GreaterThanOrEq => 4,
Equals | NotEquals | LessThan | GreaterThan | LessThanOrEq | GreaterThanOrEq => 4,
And => 3,
Or => 2,
Pizza => 1,

View file

@ -45,8 +45,8 @@ pub enum Expr<'a> {
AccessorFunction(&'a str),
// Collection Literals
List(Vec<'a, Loc<Expr<'a>>>),
Record(Vec<'a, Loc<Expr<'a>>>),
List(Vec<'a, &'a Loc<Expr<'a>>>),
Record(Vec<'a, &'a Loc<Expr<'a>>>),
AssignField(Loc<&'a str>, &'a Loc<Expr<'a>>),
// Lookups
@ -54,19 +54,17 @@ pub enum Expr<'a> {
Variant(&'a [&'a str], &'a str),
// Pattern Matching
Closure(&'a (Vec<'a, Loc<Pattern<'a>>>, Loc<Expr<'a>>)),
Closure(&'a Vec<'a, Loc<Pattern<'a>>>, &'a Loc<Expr<'a>>),
/// Multiple defs in a row
Defs(
&'a (
Vec<'a, (&'a [CommentOrNewline<'a>], Def<'a>)>,
Loc<Expr<'a>>,
),
&'a Loc<Expr<'a>>,
),
// Application
/// To apply by name, do Apply(Var(...), ...)
/// To apply a variant by name, do Apply(Variant(...), ...)
Apply(&'a (Loc<Expr<'a>>, Vec<'a, Loc<Expr<'a>>>, CalledVia)),
Apply(&'a Loc<Expr<'a>>, Vec<'a, &'a Loc<Expr<'a>>>, CalledVia),
Operator(&'a (Loc<Expr<'a>>, Loc<Operator>, Loc<Expr<'a>>)),
// Conditionals
@ -384,7 +382,7 @@ pub fn format<'a>(
buf.push_str(name);
}
Apply((loc_expr, loc_args, _)) => {
Apply(loc_expr, loc_args, _) => {
if apply_needs_parens {
buf.push('(');
}
@ -434,10 +432,10 @@ pub fn format<'a>(
buf.push('}');
}
Closure((loc_patterns, loc_ret)) => {
Closure(loc_patterns, loc_ret) => {
buf.push('\\');
for loc_pattern in loc_patterns {
for loc_pattern in loc_patterns.iter() {
buf.push_str(&format_pattern(arena, &loc_pattern.value, indent, true));
buf.push(' ');
@ -447,7 +445,7 @@ pub fn format<'a>(
buf.push_str(&format(arena, &loc_ret.value, indent, false));
}
Defs((defs, ret)) => {
Defs(defs, ret) => {
// The first def is actually at the end of the list, because
// it gets added there with .push() for efficiency. (The order of parsed defs doesn't
// matter because canonicalization sorts them anyway.) The other

View file

@ -160,13 +160,25 @@ pub fn loc_parenthetical_expr<'a>(min_indent: u16) -> impl Parser<'a, Located<Ex
let (loc_expr, opt_extras) = loc_expr_with_extras.value;
match opt_extras {
Some(Either::First(loc_args)) => Ok((
Some(Either::First(loc_args)) => {
let mut allocated_args = Vec::with_capacity_in(loc_args.len(), arena);
for loc_arg in loc_args {
allocated_args.push(&*arena.alloc(loc_arg));
}
Ok((
Located {
region: loc_expr_with_extras.region,
value: Expr::Apply(arena.alloc((loc_expr, loc_args, CalledVia::Space))),
value: Expr::Apply(
arena.alloc(loc_expr),
allocated_args,
CalledVia::Space,
),
},
state,
)),
))
}
// '=' after optional spaces
Some(Either::Second(Either::Second((spaces_before_equals, equals_indent)))) => {
let region = loc_expr.region;
@ -185,7 +197,7 @@ pub fn loc_parenthetical_expr<'a>(min_indent: u16) -> impl Parser<'a, Located<Ex
};
let loc_first_pattern = Located {
region: region.clone(),
region: region,
value,
};
@ -231,15 +243,15 @@ 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::Apply((loc_val, loc_args, _)) => {
let region = loc_val.region.clone();
Expr::Apply(loc_val, loc_args, _) => {
let region = loc_val.region;
let value = expr_to_pattern(arena, &loc_val.value)?;
let val_pattern = arena.alloc(Located { region, value });
let mut arg_patterns = Vec::with_capacity_in(loc_args.len(), arena);
for loc_arg in loc_args {
let region = loc_arg.region.clone();
let region = loc_arg.region;
let value = expr_to_pattern(arena, &loc_arg.value)?;
arg_patterns.push(Located { region, value });
@ -263,7 +275,7 @@ fn expr_to_pattern<'a>(arena: &'a Bump, expr: &Expr<'a>) -> Result<Pattern<'a>,
let mut loc_patterns = Vec::with_capacity_in(loc_exprs.len(), arena);
for loc_expr in loc_exprs {
let region = loc_expr.region.clone();
let region = loc_expr.region;
let value = expr_to_pattern(arena, &loc_expr.value)?;
loc_patterns.push(Located { region, value });
@ -285,10 +297,10 @@ fn expr_to_pattern<'a>(arena: &'a Bump, expr: &Expr<'a>) -> Result<Pattern<'a>,
| Expr::AccessorFunction(_)
| Expr::Field(_, _)
| Expr::List(_)
| Expr::Closure(_)
| Expr::Closure(_, _)
| Expr::Operator(_)
| Expr::AssignField(_, _)
| Expr::Defs(_)
| Expr::Defs(_, _)
| Expr::If(_)
| Expr::Case(_, _)
| Expr::MalformedClosure
@ -430,7 +442,7 @@ fn parse_def_expr<'a>(
// their regions will ever be visible to the user.)
defs.push((&[], first_def));
Ok((Expr::Defs(arena.alloc((defs, loc_ret))), state))
Ok((Expr::Defs(defs, arena.alloc(loc_ret)), state))
}
},
)
@ -507,7 +519,7 @@ fn closure<'a>(min_indent: u16) -> impl Parser<'a, Expr<'a>> {
),
|arena, opt_contents| match opt_contents {
None => Expr::MalformedClosure,
Some((params, loc_body)) => Expr::Closure(arena.alloc((params, loc_body))),
Some((params, loc_body)) => Expr::Closure(arena.alloc(params), arena.alloc(loc_body)),
},
)
}
@ -759,8 +771,14 @@ pub fn ident_etc<'a>(min_indent: u16) -> impl Parser<'a, Expr<'a>> {
value: ident_to_expr(loc_ident.value),
};
let mut allocated_args = Vec::with_capacity_in(loc_args.len(), arena);
for loc_arg in loc_args {
allocated_args.push(&*arena.alloc(loc_arg));
}
Ok((
Expr::Apply(arena.alloc((loc_expr, loc_args, CalledVia::Space))),
Expr::Apply(arena.alloc(loc_expr), allocated_args, CalledVia::Space),
state,
))
}
@ -880,7 +898,18 @@ pub fn list_literal<'a>(min_indent: u16) -> impl Parser<'a, Expr<'a>> {
min_indent,
);
attempt(Attempting::List, map(elems, Expr::List))
attempt(
Attempting::List,
map_with_arena(elems, |arena, parsed_elems| {
let mut allocated = Vec::with_capacity_in(parsed_elems.len(), arena);
for parsed_elem in parsed_elems {
allocated.push(&*arena.alloc(parsed_elem));
}
Expr::List(allocated)
}),
)
}
pub fn record_literal<'a>(min_indent: u16) -> impl Parser<'a, Expr<'a>> {
@ -912,7 +941,15 @@ pub fn record_literal<'a>(min_indent: u16) -> impl Parser<'a, Expr<'a>> {
optional(and(space0(min_indent), equals_with_indent())),
),
move |arena, state, (loc_field_exprs, opt_def)| match opt_def {
None => Ok((Expr::Record(loc_field_exprs.value), state)),
None => {
let mut allocated = Vec::with_capacity_in(loc_field_exprs.value.len(), arena);
for loc_field_expr in loc_field_exprs.value {
allocated.push(&*arena.alloc(loc_field_expr));
}
Ok((Expr::Record(allocated), state))
}
Some((spaces_before_equals, equals_indent)) => {
let region = loc_field_exprs.region;
let field_exprs = loc_field_exprs.value;

View file

@ -3,7 +3,7 @@ use std::fmt;
/// TODO replace Located with this
pub type Loc<T> = Located<T>;
#[derive(Clone, Eq, PartialEq, PartialOrd, Ord)]
#[derive(Copy, Clone, Eq, PartialEq, PartialOrd, Ord)]
pub struct Region {
pub start_line: u32,
pub end_line: u32,
@ -81,7 +81,7 @@ impl<T> Located<T> {
impl<T> Located<T> {
pub fn with_value<U>(&self, value: U) -> Located<U> {
Located {
region: self.region.clone(),
region: self.region,
value: value,
}
}
@ -91,7 +91,7 @@ impl<T> Located<T> {
F: (FnOnce(&T) -> U),
{
Located {
region: self.region.clone(),
region: self.region,
value: transform(&self.value),
}
}
@ -102,7 +102,7 @@ where
T: fmt::Debug,
{
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
let region = self.region.clone();
let region = self.region;
if region.start_line == 0
&& region.start_col == 0

View file

@ -17,6 +17,8 @@ pub const MOD_NUM: &'static str = "Num";
pub const MOD_DEFAULT: &'static str = "Default";
pub const TYPE_NUM: &'static str = "Num";
pub const TYPE_INTEGER: &'static str = "Integer";
pub const TYPE_FLOATINGPOINT: &'static str = "FloatingPoint";
#[derive(PartialEq, Eq, Debug, Clone)]
pub enum Type {

117
tests/test_gen.rs Normal file
View file

@ -0,0 +1,117 @@
#[macro_use]
extern crate pretty_assertions;
#[macro_use]
extern crate indoc;
extern crate bumpalo;
extern crate inkwell;
extern crate roc;
mod helpers;
#[cfg(test)]
mod test_gen {
use helpers::can_expr;
use inkwell::context::Context;
use inkwell::execution_engine::ExecutionEngine;
use inkwell::passes::PassManager;
use roc::gen::{content_to_basic_type, Function, ModuleBuilder, Prototype};
use roc::infer::infer_expr;
// HELPERS
fn gen_engine(src: &str) -> ExecutionEngine {
use inkwell::OptimizationLevel;
// use self::inkwell::support::add_symbol;
let (expr, output, _problems, procedures, mut subs, variable) = can_expr(src);
let content = infer_expr(
&mut subs,
procedures.clone(), /* TODO shouldn't have to clone this... */
&output.constraint,
variable,
);
let context = Context::create();
let module = context.create_module("repl");
let builder = context.create_builder();
// Create FPM
let fpm = PassManager::create(&module);
fpm.add_instruction_combining_pass();
fpm.add_reassociate_pass();
fpm.add_gvn_pass();
fpm.add_cfg_simplification_pass();
fpm.add_basic_alias_analysis_pass();
fpm.add_promote_memory_to_register_pass();
fpm.add_instruction_combining_pass();
fpm.add_reassociate_pass();
fpm.initialize();
// make module
let module = context.create_module("tmp");
let name = "main".to_string();
let prototype = Prototype {
name,
args: Vec::new(),
ret: content_to_basic_type(content, &mut subs, &context).unwrap_or_else(|reason| {
panic!(
"content_to_basic_type failed during test because: {}",
reason
)
}),
};
let function = Function {
prototype,
body: Some(expr),
is_anon: false,
};
// make main(), a function which returns an f64
ModuleBuilder::build(&context, &builder, &fpm, &procedures, &module, &function)
.expect("Error compiling main");
// make execution engine
module
.create_jit_execution_engine(OptimizationLevel::None)
.unwrap()
}
#[test]
fn gen_float() {
let ee = gen_engine("12345.0");
let maybe_fn = unsafe { ee.get_function::<unsafe extern "C" fn() -> f64>("main") };
let compiled_fn = match maybe_fn {
Ok(f) => f,
Err(err) => {
panic!("!> Error during execution: {:?}", err);
}
};
unsafe {
assert_eq!(12345.0, compiled_fn.call());
}
}
#[test]
fn gen_int() {
let ee = gen_engine(indoc!(r#"321"#));
let maybe_fn = unsafe { ee.get_function::<unsafe extern "C" fn() -> i64>("main") };
let compiled_fn = match maybe_fn {
Ok(f) => f,
Err(err) => {
panic!("!> Error during execution: {:?}", err);
}
};
unsafe {
assert_eq!(321, compiled_fn.call());
}
}
}

View file

@ -17,6 +17,7 @@ mod test_parse {
use bumpalo::collections::vec::Vec;
use bumpalo::{self, Bump};
use helpers::parse_with;
use roc::operator::CalledVia;
use roc::operator::Operator::*;
use roc::parse::ast::CommentOrNewline::*;
use roc::parse::ast::Expr::{self, *};
@ -480,7 +481,7 @@ mod test_parse {
#[test]
fn packed_singleton_list() {
let arena = Bump::new();
let elems = bumpalo::vec![in &arena; Located::new(0, 0, 1, 2, Int("1"))];
let elems = bumpalo::vec![in &arena; &*arena.alloc(Located::new(0, 0, 1, 2, Int("1")))];
let expected = List(elems);
let actual = parse_with(&arena, "[1]");
@ -490,7 +491,7 @@ mod test_parse {
#[test]
fn spaced_singleton_list() {
let arena = Bump::new();
let elems = bumpalo::vec![in &arena; Located::new(0, 0, 2, 3, Int("1"))];
let elems = bumpalo::vec![in &arena; &*arena.alloc(Located::new(0, 0, 2, 3, Int("1")))];
let expected = List(elems);
let actual = parse_with(&arena, "[ 1 ]");
@ -555,10 +556,13 @@ mod test_parse {
fn basic_apply() {
let arena = Bump::new();
let module_parts = Vec::new_in(&arena).into_bump_slice();
let arg = Located::new(0, 0, 5, 6, Int("1"));
let args = bumpalo::vec![in &arena; arg];
let tuple = arena.alloc((Located::new(0, 0, 0, 4, Var(module_parts, "whee")), args));
let expected = Expr::Apply(tuple);
let arg = arena.alloc(Located::new(0, 0, 5, 6, Int("1")));
let args = bumpalo::vec![in &arena; &*arg];
let expected = Expr::Apply(
arena.alloc(Located::new(0, 0, 0, 4, Var(module_parts, "whee"))),
args,
CalledVia::Space,
);
let actual = parse_with(&arena, "whee 1");
assert_eq!(Ok(expected), actual);
@ -568,11 +572,14 @@ mod test_parse {
fn apply_two_args() {
let arena = Bump::new();
let module_parts = Vec::new_in(&arena).into_bump_slice();
let arg1 = Located::new(0, 0, 6, 8, Int("12"));
let arg2 = Located::new(0, 0, 10, 12, Int("34"));
let args = bumpalo::vec![in &arena; arg1, arg2];
let tuple = arena.alloc((Located::new(0, 0, 0, 4, Var(module_parts, "whee")), args));
let expected = Expr::Apply(tuple);
let arg1 = arena.alloc(Located::new(0, 0, 6, 8, Int("12")));
let arg2 = arena.alloc(Located::new(0, 0, 10, 12, Int("34")));
let args = bumpalo::vec![in &arena; &*arg1, &*arg2];
let expected = Expr::Apply(
arena.alloc(Located::new(0, 0, 0, 4, Var(module_parts, "whee"))),
args,
CalledVia::Space,
);
let actual = parse_with(&arena, "whee 12 34");
assert_eq!(Ok(expected), actual);
@ -582,12 +589,15 @@ mod test_parse {
fn apply_three_args() {
let arena = Bump::new();
let module_parts = Vec::new_in(&arena).into_bump_slice();
let arg1 = Located::new(0, 0, 2, 3, Var(module_parts, "b"));
let arg2 = Located::new(0, 0, 4, 5, Var(module_parts, "c"));
let arg3 = Located::new(0, 0, 6, 7, Var(module_parts, "d"));
let args = bumpalo::vec![in &arena; arg1, arg2, arg3];
let tuple = arena.alloc((Located::new(0, 0, 0, 1, Var(module_parts, "a")), args));
let expected = Expr::Apply(tuple);
let arg1 = arena.alloc(Located::new(0, 0, 2, 3, Var(module_parts, "b")));
let arg2 = arena.alloc(Located::new(0, 0, 4, 5, Var(module_parts, "c")));
let arg3 = arena.alloc(Located::new(0, 0, 6, 7, Var(module_parts, "d")));
let args = bumpalo::vec![in &arena; &*arg1, &*arg2, &*arg3];
let expected = Expr::Apply(
arena.alloc(Located::new(0, 0, 0, 1, Var(module_parts, "a"))),
args,
CalledVia::Space,
);
let actual = parse_with(&arena, "a b c d");
assert_eq!(Ok(expected), actual);
@ -597,10 +607,13 @@ mod test_parse {
fn parenthetical_apply() {
let arena = Bump::new();
let module_parts = Vec::new_in(&arena).into_bump_slice();
let arg = Located::new(0, 0, 7, 8, Int("1"));
let args = bumpalo::vec![in &arena; arg];
let tuple = arena.alloc((Located::new(0, 0, 1, 5, Var(module_parts, "whee")), args));
let expected = Expr::Apply(tuple);
let arg = arena.alloc(Located::new(0, 0, 7, 8, Int("1")));
let args = bumpalo::vec![in &arena; &*arg];
let expected = Expr::Apply(
arena.alloc(Located::new(0, 0, 1, 5, Var(module_parts, "whee"))),
args,
CalledVia::Space,
);
let actual = parse_with(&arena, "(whee) 1");
assert_eq!(Ok(expected), actual);
@ -613,8 +626,10 @@ mod test_parse {
let arena = Bump::new();
let pattern = Located::new(0, 0, 1, 2, Identifier("a"));
let patterns = bumpalo::vec![in &arena; pattern];
let tuple = arena.alloc((patterns, Located::new(0, 0, 6, 8, Int("42"))));
let expected = Closure(tuple);
let expected = Closure(
arena.alloc(patterns),
arena.alloc(Located::new(0, 0, 6, 8, Int("42"))),
);
let actual = parse_with(&arena, "\\a -> 42");
assert_eq!(Ok(expected), actual);
@ -625,8 +640,10 @@ mod test_parse {
let arena = Bump::new();
let pattern = Located::new(0, 0, 1, 2, Underscore);
let patterns = bumpalo::vec![in &arena; pattern];
let tuple = arena.alloc((patterns, Located::new(0, 0, 6, 8, Int("42"))));
let expected = Closure(tuple);
let expected = Closure(
arena.alloc(patterns),
arena.alloc(Located::new(0, 0, 6, 8, Int("42"))),
);
let actual = parse_with(&arena, "\\_ -> 42");
assert_eq!(Ok(expected), actual);
@ -638,8 +655,10 @@ mod test_parse {
let arg1 = Located::new(0, 0, 1, 2, Identifier("a"));
let arg2 = Located::new(0, 0, 3, 4, Identifier("b"));
let patterns = bumpalo::vec![in &arena; arg1, arg2];
let tuple = arena.alloc((patterns, Located::new(0, 0, 8, 10, Int("42"))));
let expected = Closure(tuple);
let expected = Closure(
arena.alloc(patterns),
arena.alloc(Located::new(0, 0, 8, 10, Int("42"))),
);
let actual = parse_with(&arena, "\\a b -> 42");
assert_eq!(Ok(expected), actual);
@ -652,8 +671,10 @@ mod test_parse {
let arg2 = Located::new(0, 0, 3, 4, Identifier("b"));
let arg3 = Located::new(0, 0, 5, 6, Identifier("c"));
let patterns = bumpalo::vec![in &arena; arg1, arg2, arg3];
let tuple = arena.alloc((patterns, Located::new(0, 0, 10, 12, Int("42"))));
let expected = Closure(tuple);
let expected = Closure(
arena.alloc(patterns),
arena.alloc(Located::new(0, 0, 10, 12, Int("42"))),
);
let actual = parse_with(&arena, "\\a b c -> 42");
assert_eq!(Ok(expected), actual);
@ -665,8 +686,10 @@ mod test_parse {
let underscore1 = Located::new(0, 0, 1, 2, Underscore);
let underscore2 = Located::new(0, 0, 3, 4, Underscore);
let patterns = bumpalo::vec![in &arena; underscore1, underscore2];
let tuple = arena.alloc((patterns, Located::new(0, 0, 8, 10, Int("42"))));
let expected = Closure(tuple);
let expected = Closure(
arena.alloc(patterns),
arena.alloc(Located::new(0, 0, 8, 10, Int("42"))),
);
let actual = parse_with(&arena, "\\_ _ -> 42");
assert_eq!(Ok(expected), actual);
@ -687,7 +710,7 @@ mod test_parse {
let loc_ret = Located::new(3, 3, 0, 2, ret);
let reset_indentation = bumpalo::vec![in &arena; LineComment(" leading comment")];
let expected = Expr::SpaceBefore(
arena.alloc(Defs(arena.alloc((defs, loc_ret)))),
arena.alloc(Defs(defs, arena.alloc(loc_ret))),
reset_indentation.into_bump_slice(),
);
@ -716,7 +739,7 @@ mod test_parse {
let loc_ret = Located::new(3, 3, 0, 2, ret);
let reset_indentation = bumpalo::vec![in &arena; LineComment(" leading comment")];
let expected = Expr::SpaceBefore(
arena.alloc(Defs(arena.alloc((defs, loc_ret)))),
arena.alloc(Defs(defs, arena.alloc(loc_ret))),
reset_indentation.into_bump_slice(),
);
@ -756,7 +779,7 @@ mod test_parse {
let loc_ret = Located::new(4, 4, 0, 2, ret);
let reset_indentation = bumpalo::vec![in &arena; LineComment(" leading comment")];
let expected = Expr::SpaceBefore(
arena.alloc(Defs(arena.alloc((defs, loc_ret)))),
arena.alloc(Defs(defs, arena.alloc(loc_ret))),
reset_indentation.into_bump_slice(),
);
@ -801,7 +824,7 @@ mod test_parse {
let loc_ret = Located::new(4, 4, 0, 2, ret);
let reset_indentation = bumpalo::vec![in &arena; LineComment(" leading comment")];
let expected = Expr::SpaceBefore(
arena.alloc(Defs(arena.alloc((defs, loc_ret)))),
arena.alloc(Defs(defs, arena.alloc(loc_ret))),
reset_indentation.into_bump_slice(),
);