Merge branch 'trunk' into builtin-count-graphemes

This commit is contained in:
Jared Ramirez 2020-11-07 18:49:29 -06:00 committed by GitHub
commit 74b09605a7
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
35 changed files with 1640 additions and 251 deletions

View file

@ -162,15 +162,20 @@ mod cli_run {
); );
} }
// #[test]
// #[serial(effect)]
// fn run_effect_unoptimized() {
// check_output(
// &example_file("effect", "Main.roc"),
// &[],
// "I am Dep2.str2\n",
// true,
// );
// }
#[test] #[test]
#[serial(multi_dep_str)] #[serial(multi_dep_str)]
fn run_multi_dep_str_unoptimized() { fn run_multi_dep_str_unoptimized() {
// if true {
// todo!(
// "fix this test so it no longer deadlocks and hangs during monomorphization! The test never shows the error; to see the panic error, run this: cargo run run cli/tests/fixtures/multi-dep-str/Main.roc"
// );
// }
check_output( check_output(
&fixture_file("multi-dep-str", "Main.roc"), &fixture_file("multi-dep-str", "Main.roc"),
&[], &[],
@ -184,7 +189,7 @@ mod cli_run {
fn run_multi_dep_str_optimized() { fn run_multi_dep_str_optimized() {
check_output( check_output(
&fixture_file("multi-dep-str", "Main.roc"), &fixture_file("multi-dep-str", "Main.roc"),
&[], &["--optimize"],
"I am Dep2.str2\n", "I am Dep2.str2\n",
true, true,
); );
@ -202,11 +207,11 @@ mod cli_run {
} }
#[test] #[test]
#[serial(multi_dep_str)] #[serial(multi_dep_thunk)]
fn run_multi_dep_thunk_optimized() { fn run_multi_dep_thunk_optimized() {
check_output( check_output(
&fixture_file("multi-dep-thunk", "Main.roc"), &fixture_file("multi-dep-thunk", "Main.roc"),
&[], &["--optimize"],
"I am Dep2.value2\n", "I am Dep2.value2\n",
true, true,
); );

View file

@ -11,7 +11,7 @@ use crate::procedure::References;
use crate::scope::Scope; use crate::scope::Scope;
use inlinable_string::InlinableString; use inlinable_string::InlinableString;
use roc_collections::all::{ImSet, MutMap, MutSet, SendMap}; use roc_collections::all::{ImSet, MutMap, MutSet, SendMap};
use roc_module::ident::{Lowercase, TagName}; use roc_module::ident::{ForeignSymbol, Lowercase, TagName};
use roc_module::low_level::LowLevel; use roc_module::low_level::LowLevel;
use roc_module::operator::CalledVia; use roc_module::operator::CalledVia;
use roc_module::symbol::Symbol; use roc_module::symbol::Symbol;
@ -98,6 +98,11 @@ pub enum Expr {
args: Vec<(Variable, Expr)>, args: Vec<(Variable, Expr)>,
ret_var: Variable, ret_var: Variable,
}, },
ForeignCall {
foreign_symbol: ForeignSymbol,
args: Vec<(Variable, Expr)>,
ret_var: Variable,
},
Closure { Closure {
function_type: Variable, function_type: Variable,
@ -1170,7 +1175,8 @@ pub fn inline_calls(var_store: &mut VarStore, scope: &mut Scope, expr: Expr) ->
| other @ Accessor { .. } | other @ Accessor { .. }
| other @ Update { .. } | other @ Update { .. }
| other @ Var(_) | other @ Var(_)
| other @ RunLowLevel { .. } => other, | other @ RunLowLevel { .. }
| other @ ForeignCall { .. } => other,
List { List {
list_var, list_var,

View file

@ -445,7 +445,7 @@ fn fix_values_captured_in_closure_expr(
fix_values_captured_in_closure_expr(&mut loc_arg.value, no_capture_symbols); fix_values_captured_in_closure_expr(&mut loc_arg.value, no_capture_symbols);
} }
} }
RunLowLevel { args, .. } => { RunLowLevel { args, .. } | ForeignCall { args, .. } => {
for (_, arg) in args.iter_mut() { for (_, arg) in args.iter_mut() {
fix_values_captured_in_closure_expr(arg, no_capture_symbols); fix_values_captured_in_closure_expr(arg, no_capture_symbols);
} }

View file

@ -858,6 +858,52 @@ pub fn constrain_expr(
]), ]),
) )
} }
ForeignCall {
args,
ret_var,
foreign_symbol,
} => {
// This is a modified version of what we do for function calls.
// The operation's return type
let ret_type = Variable(*ret_var);
// This will be used in the occurs check
let mut vars = Vec::with_capacity(1 + args.len());
vars.push(*ret_var);
let mut arg_types = Vec::with_capacity(args.len());
let mut arg_cons = Vec::with_capacity(args.len());
let mut add_arg = |index, arg_type: Type, arg| {
let reason = Reason::ForeignCallArg {
foreign_symbol: foreign_symbol.clone(),
arg_index: Index::zero_based(index),
};
let expected_arg = ForReason(reason, arg_type.clone(), Region::zero());
let arg_con = constrain_expr(env, Region::zero(), arg, expected_arg);
arg_types.push(arg_type);
arg_cons.push(arg_con);
};
for (index, (arg_var, arg)) in args.iter().enumerate() {
vars.push(*arg_var);
add_arg(index, Variable(*arg_var), arg);
}
let category = Category::ForeignCall;
exists(
vars,
And(vec![
And(arg_cons),
Eq(ret_type, expected, category, region),
]),
)
}
RuntimeError(_) => { RuntimeError(_) => {
// Runtime Errors have no constraints because they're going to crash. // Runtime Errors have no constraints because they're going to crash.
True True

View file

@ -961,6 +961,60 @@ pub fn constrain_expr(
]), ]),
) )
} }
ForeignCall {
foreign_symbol,
args,
ret_var,
} => {
// This is a modified version of what we do for function calls.
let ret_type = Variable(*ret_var);
let mut vars = Vec::with_capacity(1 + args.len());
vars.push(*ret_var);
// Canonicalize the function expression and its arguments
let mut arg_types = Vec::with_capacity(args.len());
let mut arg_cons = Vec::with_capacity(args.len());
for (index, (arg_var, arg_expr)) in args.iter().enumerate() {
let arg_type = Variable(*arg_var);
let reason = Reason::ForeignCallArg {
foreign_symbol: foreign_symbol.clone(),
arg_index: Index::zero_based(index),
};
let expected_arg = Expected::ForReason(reason, arg_type.clone(), region);
let arg_con = constrain_expr(
env,
var_store,
var_usage,
applied_usage_constraint,
Region::zero(),
arg_expr,
expected_arg,
);
vars.push(*arg_var);
arg_types.push(arg_type);
arg_cons.push(arg_con);
}
let expected_uniq_type = var_store.fresh();
vars.push(expected_uniq_type);
let category = Category::ForeignCall;
exists(
vars,
And(vec![
And(arg_cons),
Eq(ret_type, expected, category, region),
]),
)
}
LetRec(defs, loc_ret, var) => { LetRec(defs, loc_ret, var) => {
// NOTE doesn't currently unregister bound symbols // NOTE doesn't currently unregister bound symbols
// may be a problem when symbols are not globally unique // may be a problem when symbols are not globally unique

View file

@ -810,6 +810,38 @@ pub fn build_exp_expr<'a, 'ctx, 'env>(
Literal(literal) => build_exp_literal(env, literal), Literal(literal) => build_exp_literal(env, literal),
RunLowLevel(op, symbols) => run_low_level(env, scope, parent, layout, *op, symbols), RunLowLevel(op, symbols) => run_low_level(env, scope, parent, layout, *op, symbols),
ForeignCall {
foreign_symbol,
arguments,
ret_layout,
} => {
let mut arg_vals: Vec<BasicValueEnum> =
Vec::with_capacity_in(arguments.len(), env.arena);
let mut arg_types = Vec::with_capacity_in(arguments.len(), env.arena);
for arg in arguments.iter() {
let (value, layout) = load_symbol_and_layout(env, scope, arg);
arg_vals.push(value);
let arg_type =
basic_type_from_layout(env.arena, env.context, layout, env.ptr_bytes);
arg_types.push(arg_type);
}
let ret_type =
basic_type_from_layout(env.arena, env.context, ret_layout, env.ptr_bytes);
let function_type = get_fn_type(&ret_type, &arg_types);
let function = get_foreign_symbol(env, foreign_symbol.clone(), function_type);
let call = env.builder.build_call(function, arg_vals.as_slice(), "tmp");
// this is a foreign function, use c calling convention
call.set_call_convention(C_CALL_CONV);
call.try_as_basic_value()
.left()
.unwrap_or_else(|| panic!("LLVM error: Invalid call by pointer."))
}
FunctionCall { FunctionCall {
call_type: ByName(name), call_type: ByName(name),
full_layout, full_layout,
@ -1116,9 +1148,10 @@ pub fn build_exp_expr<'a, 'ctx, 'env>(
env.arena.alloc(format!("closure_field_access_{}_", index)), env.arena.alloc(format!("closure_field_access_{}_", index)),
) )
.unwrap(), .unwrap(),
(other, layout) => { (other, layout) => unreachable!(
unreachable!("can only index into struct layout {:?} {:?}", other, layout) "can only index into struct layout\nValue: {:?}\nLayout: {:?}\nIndex: {:?}",
} other, layout, index
),
} }
} }
@ -3418,6 +3451,28 @@ fn cxa_rethrow_exception<'a, 'ctx, 'env>(env: &Env<'a, 'ctx, 'env>) -> BasicValu
call.try_as_basic_value().left().unwrap() call.try_as_basic_value().left().unwrap()
} }
fn get_foreign_symbol<'a, 'ctx, 'env>(
env: &Env<'a, 'ctx, 'env>,
foreign_symbol: roc_module::ident::ForeignSymbol,
function_type: FunctionType<'ctx>,
) -> FunctionValue<'ctx> {
let module = env.module;
match module.get_function(foreign_symbol.as_str()) {
Some(gvalue) => gvalue,
None => {
let foreign_function = module.add_function(
foreign_symbol.as_str(),
function_type,
Some(Linkage::External),
);
foreign_function.set_call_conventions(C_CALL_CONV);
foreign_function
}
}
}
fn get_gxx_personality_v0<'a, 'ctx, 'env>(env: &Env<'a, 'ctx, 'env>) -> FunctionValue<'ctx> { fn get_gxx_personality_v0<'a, 'ctx, 'env>(env: &Env<'a, 'ctx, 'env>) -> FunctionValue<'ctx> {
let name = "__gxx_personality_v0"; let name = "__gxx_personality_v0";

View file

@ -121,11 +121,6 @@ fn comments_or_new_lines_to_docs<'a>(
docs.push_str(doc_str); docs.push_str(doc_str);
docs.push_str("\n"); docs.push_str("\n");
} }
// TODO: Lines with only `##` are not being parsed as a
// DocComment, but as a LineComment("#\r"). This pattern should cover this.
// The problem is that this is only valid if it is at the start
// of a line. False positive example: `x = 2 ##`.
LineComment("#\r") => docs.push_str("\n"),
Newline | LineComment(_) => {} Newline | LineComment(_) => {}
} }
} }

File diff suppressed because it is too large Load diff

View file

@ -23,6 +23,10 @@ pub struct Lowercase(InlinableString);
#[derive(Clone, PartialEq, Eq, Hash, PartialOrd, Ord)] #[derive(Clone, PartialEq, Eq, Hash, PartialOrd, Ord)]
pub struct Uppercase(InlinableString); pub struct Uppercase(InlinableString);
/// A string representing a foreign (linked-in) symbol
#[derive(Clone, PartialEq, Eq, Hash, PartialOrd, Ord, Debug)]
pub struct ForeignSymbol(InlinableString);
#[derive(Clone, Debug, PartialEq, Eq, Hash, PartialOrd, Ord)] #[derive(Clone, Debug, PartialEq, Eq, Hash, PartialOrd, Ord)]
pub enum TagName { pub enum TagName {
/// Global tags have no module, but tend to be short strings (since they're /// Global tags have no module, but tend to be short strings (since they're
@ -125,6 +129,28 @@ impl<'a> Into<Box<str>> for ModuleName {
} }
} }
impl ForeignSymbol {
pub fn as_str(&self) -> &str {
&*self.0
}
pub fn as_inline_str(&self) -> &InlinableString {
&self.0
}
}
impl<'a> From<&'a str> for ForeignSymbol {
fn from(string: &'a str) -> Self {
Self(string.into())
}
}
impl<'a> From<String> for ForeignSymbol {
fn from(string: String) -> Self {
Self(string.into())
}
}
impl Uppercase { impl Uppercase {
pub fn as_str(&self) -> &str { pub fn as_str(&self) -> &str {
&*self.0 &*self.0

View file

@ -374,6 +374,15 @@ impl<'a> BorrowInfState<'a> {
self.own_args_using_bools(args, ps); self.own_args_using_bools(args, ps);
} }
ForeignCall { arguments, .. } => {
// very unsure what demand ForeignCall should place upon its arguments
self.own_var(z);
let ps = foreign_borrow_signature(self.arena, arguments.len());
self.own_args_using_bools(arguments, ps);
}
Literal(_) | FunctionPointer(_, _) | RuntimeErrorFunction(_) => {} Literal(_) | FunctionPointer(_, _) | RuntimeErrorFunction(_) => {}
} }
} }
@ -492,6 +501,11 @@ impl<'a> BorrowInfState<'a> {
} }
} }
pub fn foreign_borrow_signature(arena: &Bump, arity: usize) -> &[bool] {
let all = bumpalo::vec![in arena; false; arity];
all.into_bump_slice()
}
pub fn lowlevel_borrow_signature(arena: &Bump, op: LowLevel) -> &[bool] { pub fn lowlevel_borrow_signature(arena: &Bump, op: LowLevel) -> &[bool] {
use LowLevel::*; use LowLevel::*;

View file

@ -125,6 +125,9 @@ pub fn occuring_variables_expr(expr: &Expr<'_>, result: &mut MutSet<Symbol>) {
RunLowLevel(_, args) => { RunLowLevel(_, args) => {
result.extend(args.iter()); result.extend(args.iter());
} }
ForeignCall { arguments, .. } => {
result.extend(arguments.iter());
}
EmptyArray | RuntimeErrorFunction(_) | Literal(_) => {} EmptyArray | RuntimeErrorFunction(_) | Literal(_) => {}
} }
@ -463,6 +466,13 @@ impl<'a> Context<'a> {
self.arena.alloc(Stmt::Let(z, v, l, b)) self.arena.alloc(Stmt::Let(z, v, l, b))
} }
ForeignCall { arguments, .. } => {
let ps = crate::borrow::foreign_borrow_signature(self.arena, arguments.len());
let b = self.add_dec_after_lowlevel(arguments, ps, b, b_live_vars);
self.arena.alloc(Stmt::Let(z, v, l, b))
}
FunctionCall { FunctionCall {
args: ys, args: ys,
arg_layouts, arg_layouts,

View file

@ -4,7 +4,7 @@ use crate::layout::{Builtin, ClosureLayout, Layout, LayoutCache, LayoutProblem};
use bumpalo::collections::Vec; use bumpalo::collections::Vec;
use bumpalo::Bump; use bumpalo::Bump;
use roc_collections::all::{default_hasher, MutMap, MutSet}; use roc_collections::all::{default_hasher, MutMap, MutSet};
use roc_module::ident::{Ident, Lowercase, TagName}; use roc_module::ident::{ForeignSymbol, Ident, Lowercase, TagName};
use roc_module::low_level::LowLevel; use roc_module::low_level::LowLevel;
use roc_module::symbol::{IdentIds, ModuleId, Symbol}; use roc_module::symbol::{IdentIds, ModuleId, Symbol};
use roc_problem::can::RuntimeError; use roc_problem::can::RuntimeError;
@ -444,7 +444,7 @@ impl<'a> Procs<'a> {
// register the pending specialization, so this gets code genned later // register the pending specialization, so this gets code genned later
add_pending(pending_specializations, symbol, layout.clone(), pending); add_pending(pending_specializations, symbol, layout.clone(), pending);
debug_assert!(!self.partial_procs.contains_key(&symbol), "Procs was told to insert a value for symbol {:?}, but there was already an entry for that key! Procs should never attempt to insert duplicates.", symbol); debug_assert!(!self.partial_procs.contains_key(&symbol), "Procs was told to insert a value for symbol {:?}, but there was already an entry for that key! The same PartialProc should never be added twice", symbol);
self.partial_procs.insert( self.partial_procs.insert(
symbol, symbol,
@ -829,6 +829,11 @@ pub enum Expr<'a> {
args: &'a [Symbol], args: &'a [Symbol],
}, },
RunLowLevel(LowLevel, &'a [Symbol]), RunLowLevel(LowLevel, &'a [Symbol]),
ForeignCall {
foreign_symbol: ForeignSymbol,
arguments: &'a [Symbol],
ret_layout: Layout<'a>,
},
Tag { Tag {
tag_layout: Layout<'a>, tag_layout: Layout<'a>,
@ -943,6 +948,17 @@ impl<'a> Expr<'a> {
.text(format!("lowlevel {:?} ", lowlevel)) .text(format!("lowlevel {:?} ", lowlevel))
.append(alloc.intersperse(it, " ")) .append(alloc.intersperse(it, " "))
} }
ForeignCall {
foreign_symbol,
arguments,
..
} => {
let it = arguments.iter().map(|s| symbol_to_doc(alloc, *s));
alloc
.text(format!("foreign {:?} ", foreign_symbol.as_str()))
.append(alloc.intersperse(it, " "))
}
Tag { Tag {
tag_name, tag_name,
arguments, arguments,
@ -1331,8 +1347,27 @@ pub fn specialize_all<'a>(
let it = procs.externals_others_need.specs.clone(); let it = procs.externals_others_need.specs.clone();
let it = it let it = it
.into_iter() .into_iter()
.map(|(symbol, solved_types)| solved_types.into_iter().map(move |s| (symbol, s))) .map(|(symbol, solved_types)| {
// for some unclear reason, the MutSet does not deduplicate according to the hash
// instance. So we do it manually here
let mut as_vec: std::vec::Vec<_> = solved_types.into_iter().collect();
use std::collections::hash_map::DefaultHasher;
use std::hash::{Hash, Hasher};
let hash_the_thing = |x: &SolvedType| {
let mut hasher = DefaultHasher::new();
x.hash(&mut hasher);
hasher.finish()
};
as_vec.sort_by_key(|x| hash_the_thing(x));
as_vec.dedup_by_key(|x| hash_the_thing(x));
as_vec.into_iter().map(move |s| (symbol, s))
})
.flatten(); .flatten();
for (name, solved_type) in it.into_iter() { for (name, solved_type) in it.into_iter() {
let partial_proc = match procs.partial_procs.get(&name) { let partial_proc = match procs.partial_procs.get(&name) {
Some(v) => v.clone(), Some(v) => v.clone(),
@ -1396,7 +1431,6 @@ pub fn specialize_all<'a>(
procs procs
.specialized .specialized
.insert((name, outside_layout.clone()), InProgress); .insert((name, outside_layout.clone()), InProgress);
match specialize( match specialize(
env, env,
&mut procs, &mut procs,
@ -1407,8 +1441,16 @@ pub fn specialize_all<'a>(
) { ) {
Ok((proc, layout)) => { Ok((proc, layout)) => {
debug_assert_eq!(outside_layout, layout); debug_assert_eq!(outside_layout, layout);
procs.specialized.remove(&(name, outside_layout));
procs.specialized.insert((name, layout), Done(proc)); if let Layout::Closure(args, closure, ret) = layout {
procs.specialized.remove(&(name, outside_layout));
let layout = ClosureLayout::extend_function_layout(
env.arena, args, closure, ret,
);
procs.specialized.insert((name, layout), Done(proc));
} else {
procs.specialized.insert((name, layout), Done(proc));
}
} }
Err(error) => { Err(error) => {
let error_msg = env.arena.alloc(format!( let error_msg = env.arena.alloc(format!(
@ -1451,9 +1493,7 @@ fn specialize_external<'a>(
let unified = roc_unify::unify::unify(env.subs, annotation, fn_var); let unified = roc_unify::unify::unify(env.subs, annotation, fn_var);
let is_valid = matches!(unified, roc_unify::unify::Unified::Success(_)); let is_valid = matches!(unified, roc_unify::unify::Unified::Success(_));
debug_assert!(is_valid); debug_assert!(is_valid, "unificaton failure for {:?}", proc_name);
let mut specialized_body = from_can(env, fn_var, body, procs, layout_cache);
// if this is a closure, add the closure record argument // if this is a closure, add the closure record argument
let pattern_symbols = if let CapturedSymbols::Captured(_) = captured_symbols { let pattern_symbols = if let CapturedSymbols::Captured(_) = captured_symbols {
@ -1467,6 +1507,7 @@ fn specialize_external<'a>(
let (proc_args, opt_closure_layout, ret_layout) = let (proc_args, opt_closure_layout, ret_layout) =
build_specialized_proc_from_var(env, layout_cache, proc_name, pattern_symbols, fn_var)?; build_specialized_proc_from_var(env, layout_cache, proc_name, pattern_symbols, fn_var)?;
let mut specialized_body = from_can(env, fn_var, body, procs, layout_cache);
// unpack the closure symbols, if any // unpack the closure symbols, if any
if let CapturedSymbols::Captured(captured) = captured_symbols { if let CapturedSymbols::Captured(captured) = captured_symbols {
let mut layouts = Vec::with_capacity_in(captured.len(), env.arena); let mut layouts = Vec::with_capacity_in(captured.len(), env.arena);
@ -1669,9 +1710,9 @@ fn build_specialized_proc_adapter<'a>(
#[allow(clippy::type_complexity)] #[allow(clippy::type_complexity)]
fn build_specialized_proc<'a>( fn build_specialized_proc<'a>(
arena: &'a Bump, arena: &'a Bump,
_proc_name: Symbol, proc_name: Symbol,
pattern_symbols: &[Symbol], pattern_symbols: &[Symbol],
pattern_layouts: Vec<Layout<'a>>, pattern_layouts: Vec<'a, Layout<'a>>,
opt_closure_layout: Option<ClosureLayout<'a>>, opt_closure_layout: Option<ClosureLayout<'a>>,
ret_layout: Layout<'a>, ret_layout: Layout<'a>,
) -> Result<SpecializedLayout<'a>, LayoutProblem> { ) -> Result<SpecializedLayout<'a>, LayoutProblem> {
@ -1709,7 +1750,8 @@ fn build_specialized_proc<'a>(
debug_assert_eq!( debug_assert_eq!(
pattern_layouts_len + 1, pattern_layouts_len + 1,
pattern_symbols.len(), pattern_symbols.len(),
"Tried to zip two vecs with different lengths!" "Tried to zip two vecs with different lengths in {:?}!",
proc_name,
); );
let proc_args = proc_args.into_bump_slice(); let proc_args = proc_args.into_bump_slice();
@ -1738,16 +1780,22 @@ fn build_specialized_proc<'a>(
// make sure there is not arg_closure argument without a closure layout // make sure there is not arg_closure argument without a closure layout
debug_assert!(pattern_symbols.last() != Some(&Symbol::ARG_CLOSURE)); debug_assert!(pattern_symbols.last() != Some(&Symbol::ARG_CLOSURE));
// since this is not a closure, the number of arguments should match between symbols use std::cmp::Ordering;
// and layout match pattern_layouts_len.cmp(&pattern_symbols.len()) {
debug_assert_eq!( Ordering::Equal => {
pattern_layouts_len, let proc_args = proc_args.into_bump_slice();
pattern_symbols.len(),
"Tried to zip two vecs with different lengths!"
);
let proc_args = proc_args.into_bump_slice();
Ok((proc_args, None, ret_layout)) Ok((proc_args, None, ret_layout))
}
Ordering::Greater => {
// so far, the problem when hitting this branch was always somewhere else
// I think this branch should not be reachable in a bugfree compiler
panic!("more arguments (according to the layout) than argument symbols")
}
Ordering::Less => {
panic!("more argument symbols than arguments (according to the layout)")
}
}
} }
} }
} }
@ -2943,16 +2991,19 @@ pub fn with_hole<'a>(
}; };
match loc_expr.value { match loc_expr.value {
roc_can::expr::Expr::Var(proc_name) if is_known(&proc_name) => call_by_name( roc_can::expr::Expr::Var(proc_name) if is_known(&proc_name) => {
env, // a call by a known name
procs, call_by_name(
fn_var, env,
proc_name, procs,
loc_args, fn_var,
layout_cache, proc_name,
assigned, loc_args,
hole, layout_cache,
), assigned,
hole,
)
}
_ => { _ => {
// Call by pointer - the closure was anonymous, e.g. // Call by pointer - the closure was anonymous, e.g.
// //
@ -3126,6 +3177,42 @@ pub fn with_hole<'a>(
} }
} }
ForeignCall {
foreign_symbol,
args,
ret_var,
} => {
let mut arg_symbols = Vec::with_capacity_in(args.len(), env.arena);
for (_, arg_expr) in args.iter() {
arg_symbols.push(possible_reuse_symbol(env, procs, &arg_expr));
}
let arg_symbols = arg_symbols.into_bump_slice();
// layout of the return type
let layout = layout_cache
.from_var(env.arena, ret_var, env.subs)
.unwrap_or_else(|err| todo!("TODO turn fn_var into a RuntimeError {:?}", err));
let result = Stmt::Let(
assigned,
Expr::ForeignCall {
foreign_symbol,
arguments: arg_symbols,
ret_layout: layout.clone(),
},
layout,
hole,
);
let iter = args
.into_iter()
.rev()
.map(|(a, b)| (a, Located::at_zero(b)))
.zip(arg_symbols.iter().rev());
assign_to_symbols(env, procs, layout_cache, iter, result)
}
RunLowLevel { op, args, ret_var } => { RunLowLevel { op, args, ret_var } => {
let op = optimize_low_level(env.subs, op, &args); let op = optimize_low_level(env.subs, op, &args);
@ -3956,6 +4043,35 @@ fn substitute_in_expr<'a>(
None None
} }
} }
ForeignCall {
foreign_symbol,
arguments,
ret_layout,
} => {
let mut did_change = false;
let new_args = Vec::from_iter_in(
arguments.iter().map(|s| match substitute(subs, *s) {
None => *s,
Some(s) => {
did_change = true;
s
}
}),
arena,
);
if did_change {
let args = new_args.into_bump_slice();
Some(ForeignCall {
foreign_symbol: foreign_symbol.clone(),
arguments: args,
ret_layout: ret_layout.clone(),
})
} else {
None
}
}
Tag { Tag {
tag_layout, tag_layout,

View file

@ -109,10 +109,8 @@ impl<'a> ClosureLayout<'a> {
use UnionVariant::*; use UnionVariant::*;
match variant { match variant {
Never | Unit => { Never => Ok(None),
// a max closure size of 0 means this is a standard top-level function Unit => Ok(None),
Ok(None)
}
BoolUnion { .. } => { BoolUnion { .. } => {
let closure_layout = ClosureLayout::from_bool(arena); let closure_layout = ClosureLayout::from_bool(arena);

View file

@ -52,9 +52,9 @@ pub struct AppHeader<'a> {
pub struct PlatformHeader<'a> { pub struct PlatformHeader<'a> {
pub name: Loc<PackageName<'a>>, pub name: Loc<PackageName<'a>>,
pub provides: Vec<'a, Loc<ExposesEntry<'a>>>, pub provides: Vec<'a, Loc<ExposesEntry<'a>>>,
pub requires: Vec<'a, Loc<ExposesEntry<'a>>>, pub requires: Vec<'a, Loc<TypedIdent<'a>>>,
pub imports: Vec<'a, Loc<ImportsEntry<'a>>>, pub imports: Vec<'a, Loc<ImportsEntry<'a>>>,
pub effects: Vec<'a, Loc<EffectsEntry<'a>>>, pub effects: Effects<'a>,
// Potential comments and newlines - these will typically all be empty. // Potential comments and newlines - these will typically all be empty.
pub after_platform_keyword: &'a [CommentOrNewline<'a>], pub after_platform_keyword: &'a [CommentOrNewline<'a>],
@ -64,23 +64,31 @@ pub struct PlatformHeader<'a> {
pub after_requires: &'a [CommentOrNewline<'a>], pub after_requires: &'a [CommentOrNewline<'a>],
pub before_imports: &'a [CommentOrNewline<'a>], pub before_imports: &'a [CommentOrNewline<'a>],
pub after_imports: &'a [CommentOrNewline<'a>], pub after_imports: &'a [CommentOrNewline<'a>],
pub before_effects: &'a [CommentOrNewline<'a>],
pub after_effects: &'a [CommentOrNewline<'a>],
} }
#[derive(Clone, Debug, PartialEq)] #[derive(Clone, Debug, PartialEq)]
pub enum EffectsEntry<'a> { pub struct Effects<'a> {
pub spaces_before_effects_keyword: &'a [CommentOrNewline<'a>],
pub spaces_after_effects_keyword: &'a [CommentOrNewline<'a>],
pub spaces_after_type_name: &'a [CommentOrNewline<'a>],
pub type_name: &'a str,
pub entries: Vec<'a, Loc<TypedIdent<'a>>>,
}
#[derive(Clone, Debug, PartialEq)]
pub enum TypedIdent<'a> {
/// e.g. /// e.g.
/// ///
/// printLine : Str -> Effect {} /// printLine : Str -> Effect {}
Effect { Entry {
ident: Loc<&'a str>, ident: Loc<&'a str>,
spaces_before_colon: &'a [CommentOrNewline<'a>],
ann: Loc<TypeAnnotation<'a>>, ann: Loc<TypeAnnotation<'a>>,
}, },
// Spaces // Spaces
SpaceBefore(&'a EffectsEntry<'a>, &'a [CommentOrNewline<'a>]), SpaceBefore(&'a TypedIdent<'a>, &'a [CommentOrNewline<'a>]),
SpaceAfter(&'a EffectsEntry<'a>, &'a [CommentOrNewline<'a>]), SpaceAfter(&'a TypedIdent<'a>, &'a [CommentOrNewline<'a>]),
} }
#[derive(Clone, Debug, PartialEq)] #[derive(Clone, Debug, PartialEq)]
@ -620,12 +628,12 @@ impl<'a> Spaceable<'a> for ImportsEntry<'a> {
} }
} }
impl<'a> Spaceable<'a> for EffectsEntry<'a> { impl<'a> Spaceable<'a> for TypedIdent<'a> {
fn before(&'a self, spaces: &'a [CommentOrNewline<'a>]) -> Self { fn before(&'a self, spaces: &'a [CommentOrNewline<'a>]) -> Self {
EffectsEntry::SpaceBefore(self, spaces) TypedIdent::SpaceBefore(self, spaces)
} }
fn after(&'a self, spaces: &'a [CommentOrNewline<'a>]) -> Self { fn after(&'a self, spaces: &'a [CommentOrNewline<'a>]) -> Self {
EffectsEntry::SpaceAfter(self, spaces) TypedIdent::SpaceAfter(self, spaces)
} }
} }

View file

@ -317,11 +317,26 @@ fn spaces<'a>(
'\n' => { '\n' => {
state = state.newline()?; state = state.newline()?;
// This was a newline, so end this line comment. match (comment_line_buf.len(), comment_line_buf.chars().next())
space_list.push(LineComment(comment_line_buf.into_bump_str())); {
comment_line_buf = String::new_in(arena); (1, Some('#')) => {
// This is a line with `##` - that is,
// a doc comment new line.
space_list.push(DocComment(""));
comment_line_buf = String::new_in(arena);
line_state = LineState::Normal; line_state = LineState::Normal;
}
_ => {
// This was a newline, so end this line comment.
space_list.push(LineComment(
comment_line_buf.into_bump_str(),
));
comment_line_buf = String::new_in(arena);
line_state = LineState::Normal;
}
}
} }
nonblank => { nonblank => {
// Chars can have btye lengths of more than 1! // Chars can have btye lengths of more than 1!

View file

@ -30,7 +30,7 @@ macro_rules! loc_parenthetical_expr {
then( then(
loc!(and!( loc!(and!(
between!( between!(
ascii_char('(' ), ascii_char(b'(' ),
map_with_arena!( map_with_arena!(
space0_around( space0_around(
loc!(move |arena, state| parse_expr($min_indent, arena, state)), loc!(move |arena, state| parse_expr($min_indent, arena, state)),
@ -43,7 +43,7 @@ macro_rules! loc_parenthetical_expr {
} }
} }
), ),
ascii_char(')' ) ascii_char(b')' )
), ),
optional(either!( optional(either!(
// There may optionally be function args after the ')' // There may optionally be function args after the ')'
@ -59,7 +59,7 @@ macro_rules! loc_parenthetical_expr {
// as if there were any args they'd have consumed it anyway // as if there were any args they'd have consumed it anyway
// e.g. in `((foo bar) baz.blah)` the `.blah` will be consumed by the `baz` parser // e.g. in `((foo bar) baz.blah)` the `.blah` will be consumed by the `baz` parser
either!( either!(
one_or_more!(skip_first!(ascii_char('.' ), lowercase_ident())), one_or_more!(skip_first!(ascii_char(b'.' ), lowercase_ident())),
and!(space0($min_indent), equals_with_indent()) and!(space0($min_indent), equals_with_indent())
) )
)) ))
@ -170,7 +170,7 @@ pub fn unary_op<'a>(min_indent: u16) -> impl Parser<'a, Expr<'a>> {
one_of!( one_of!(
map_with_arena!( map_with_arena!(
and!( and!(
loc!(ascii_char('!')), loc!(ascii_char(b'!')),
loc!(move |arena, state| parse_expr(min_indent, arena, state)) loc!(move |arena, state| parse_expr(min_indent, arena, state))
), ),
|arena: &'a Bump, (loc_op, loc_expr): (Located<()>, Located<Expr<'a>>)| { |arena: &'a Bump, (loc_op, loc_expr): (Located<()>, Located<Expr<'a>>)| {
@ -179,7 +179,7 @@ pub fn unary_op<'a>(min_indent: u16) -> impl Parser<'a, Expr<'a>> {
), ),
map_with_arena!( map_with_arena!(
and!( and!(
loc!(ascii_char('-')), loc!(ascii_char(b'-')),
loc!(move |arena, state| parse_expr(min_indent, arena, state)) loc!(move |arena, state| parse_expr(min_indent, arena, state))
), ),
|arena: &'a Bump, (loc_op, loc_expr): (Located<()>, Located<Expr<'a>>)| { |arena: &'a Bump, (loc_op, loc_expr): (Located<()>, Located<Expr<'a>>)| {
@ -449,9 +449,9 @@ pub fn loc_parenthetical_def<'a>(min_indent: u16) -> impl Parser<'a, Located<Exp
let (loc_tuple, state) = loc!(and!( let (loc_tuple, state) = loc!(and!(
space0_after( space0_after(
between!( between!(
ascii_char('('), ascii_char(b'('),
space0_around(loc_pattern(min_indent), min_indent), space0_around(loc_pattern(min_indent), min_indent),
ascii_char(')') ascii_char(b')')
), ),
min_indent, min_indent,
), ),
@ -481,7 +481,10 @@ pub fn loc_parenthetical_def<'a>(min_indent: u16) -> impl Parser<'a, Located<Exp
/// The '=' used in a def can't be followed by another '=' (or else it's actually /// The '=' used in a def can't be followed by another '=' (or else it's actually
/// an "==") and also it can't be followed by '>' (or else it's actually an "=>") /// an "==") and also it can't be followed by '>' (or else it's actually an "=>")
fn equals_for_def<'a>() -> impl Parser<'a, ()> { fn equals_for_def<'a>() -> impl Parser<'a, ()> {
not_followed_by(ascii_char('='), one_of!(ascii_char('='), ascii_char('>'))) not_followed_by(
ascii_char(b'='),
one_of!(ascii_char(b'='), ascii_char(b'>')),
)
} }
/// A definition, consisting of one of these: /// A definition, consisting of one of these:
@ -512,7 +515,7 @@ pub fn def<'a>(min_indent: u16) -> impl Parser<'a, Def<'a>> {
), ),
// Annotation // Annotation
skip_first!( skip_first!(
ascii_char(':'), ascii_char(b':'),
// Spaces after the ':' (at a normal indentation level) and then the type. // Spaces after the ':' (at a normal indentation level) and then the type.
// The type itself must be indented more than the pattern and ':' // The type itself must be indented more than the pattern and ':'
space0_before(type_annotation::located(indented_more), indented_more) space0_before(type_annotation::located(indented_more), indented_more)
@ -838,7 +841,7 @@ fn closure<'a>(min_indent: u16) -> impl Parser<'a, Expr<'a>> {
map_with_arena!( map_with_arena!(
skip_first!( skip_first!(
// All closures start with a '\' - e.g. (\x -> x + 1) // All closures start with a '\' - e.g. (\x -> x + 1)
ascii_char('\\'), ascii_char(b'\\'),
// Once we see the '\', we're committed to parsing this as a closure. // Once we see the '\', we're committed to parsing this as a closure.
// It may turn out to be malformed, but it is definitely a closure. // It may turn out to be malformed, but it is definitely a closure.
optional(and!( optional(and!(
@ -847,7 +850,7 @@ fn closure<'a>(min_indent: u16) -> impl Parser<'a, Expr<'a>> {
Attempting::ClosureParams, Attempting::ClosureParams,
// Params are comma-separated // Params are comma-separated
sep_by1( sep_by1(
ascii_char(','), ascii_char(b','),
space0_around(loc_closure_param(min_indent), min_indent) space0_around(loc_closure_param(min_indent), min_indent)
) )
), ),
@ -896,9 +899,9 @@ fn parse_closure_param<'a>(
// If you wrap it in parens, you can match any arbitrary pattern at all. // If you wrap it in parens, you can match any arbitrary pattern at all.
// e.g. \User.UserId userId -> ... // e.g. \User.UserId userId -> ...
between!( between!(
ascii_char('('), ascii_char(b'('),
space0_around(loc_pattern(min_indent), min_indent), space0_around(loc_pattern(min_indent), min_indent),
ascii_char(')') ascii_char(b')')
) )
) )
.parse(arena, state) .parse(arena, state)
@ -922,9 +925,9 @@ fn loc_pattern<'a>(min_indent: u16) -> impl Parser<'a, Located<Pattern<'a>>> {
fn loc_parenthetical_pattern<'a>(min_indent: u16) -> impl Parser<'a, Located<Pattern<'a>>> { fn loc_parenthetical_pattern<'a>(min_indent: u16) -> impl Parser<'a, Located<Pattern<'a>>> {
between!( between!(
ascii_char('('), ascii_char(b'('),
move |arena, state| loc_pattern(min_indent).parse(arena, state), move |arena, state| loc_pattern(min_indent).parse(arena, state),
ascii_char(')') ascii_char(b')')
) )
} }
@ -939,13 +942,13 @@ fn string_pattern<'a>() -> impl Parser<'a, Pattern<'a>> {
} }
fn underscore_pattern<'a>() -> impl Parser<'a, Pattern<'a>> { fn underscore_pattern<'a>() -> impl Parser<'a, Pattern<'a>> {
map!(ascii_char('_'), |_| Pattern::Underscore) map!(ascii_char(b'_'), |_| Pattern::Underscore)
} }
fn record_destructure<'a>(min_indent: u16) -> impl Parser<'a, Pattern<'a>> { fn record_destructure<'a>(min_indent: u16) -> impl Parser<'a, Pattern<'a>> {
then( then(
collection!( collection!(
ascii_char('{'), ascii_char(b'{'),
move |arena: &'a bumpalo::Bump, move |arena: &'a bumpalo::Bump,
state: crate::parser::State<'a>| state: crate::parser::State<'a>|
-> crate::parser::ParseResult<'a, Located<crate::ast::Pattern<'a>>> { -> crate::parser::ParseResult<'a, Located<crate::ast::Pattern<'a>>> {
@ -963,11 +966,11 @@ fn record_destructure<'a>(min_indent: u16) -> impl Parser<'a, Pattern<'a>> {
// (This is true in both literals and types.) // (This is true in both literals and types.)
let (opt_loc_val, state) = crate::parser::optional(either!( let (opt_loc_val, state) = crate::parser::optional(either!(
skip_first!( skip_first!(
ascii_char(':'), ascii_char(b':'),
space0_before(loc_pattern(min_indent), min_indent) space0_before(loc_pattern(min_indent), min_indent)
), ),
skip_first!( skip_first!(
ascii_char('?'), ascii_char(b'?'),
space0_before(loc!(expr(min_indent)), min_indent) space0_before(loc!(expr(min_indent)), min_indent)
) )
)) ))
@ -1006,8 +1009,8 @@ fn record_destructure<'a>(min_indent: u16) -> impl Parser<'a, Pattern<'a>> {
Ok((answer, state)) Ok((answer, state))
}, },
ascii_char(','), ascii_char(b','),
ascii_char('}'), ascii_char(b'}'),
min_indent min_indent
), ),
move |_arena, state, loc_patterns| { move |_arena, state, loc_patterns| {
@ -1250,7 +1253,7 @@ mod when {
) -> impl Parser<'a, (Vec<'a, Located<Pattern<'a>>>, Option<Located<Expr<'a>>>)> { ) -> impl Parser<'a, (Vec<'a, Located<Pattern<'a>>>, Option<Located<Expr<'a>>>)> {
and!( and!(
sep_by1( sep_by1(
ascii_char('|'), ascii_char(b'|'),
space0_around(loc_pattern(min_indent), min_indent), space0_around(loc_pattern(min_indent), min_indent),
), ),
optional(skip_first!( optional(skip_first!(
@ -1350,14 +1353,14 @@ fn unary_negate_function_arg<'a>(min_indent: u16) -> impl Parser<'a, Located<Exp
// Try to parse a number literal *before* trying to parse unary negate, // Try to parse a number literal *before* trying to parse unary negate,
// because otherwise (foo -1) will parse as (foo (Num.neg 1)) // because otherwise (foo -1) will parse as (foo (Num.neg 1))
loc!(number_literal()), loc!(number_literal()),
loc!(ascii_char('-')) loc!(ascii_char(b'-'))
) )
), ),
one_of!( one_of!(
ascii_char(' '), ascii_char(b' '),
ascii_char('#'), ascii_char(b'#'),
ascii_char('\n'), ascii_char(b'\n'),
ascii_char('>') ascii_char(b'>')
), ),
), ),
move |arena, state, (spaces, num_or_minus_char)| { move |arena, state, (spaces, num_or_minus_char)| {
@ -1660,27 +1663,27 @@ fn binop<'a>() -> impl Parser<'a, BinOp> {
map!(ascii_string("!="), |_| BinOp::NotEquals), map!(ascii_string("!="), |_| BinOp::NotEquals),
map!(ascii_string("&&"), |_| BinOp::And), map!(ascii_string("&&"), |_| BinOp::And),
map!(ascii_string("||"), |_| BinOp::Or), map!(ascii_string("||"), |_| BinOp::Or),
map!(ascii_char('+'), |_| BinOp::Plus), map!(ascii_char(b'+'), |_| BinOp::Plus),
map!(ascii_char('*'), |_| BinOp::Star), map!(ascii_char(b'*'), |_| BinOp::Star),
map!(ascii_char('-'), |_| BinOp::Minus), map!(ascii_char(b'-'), |_| BinOp::Minus),
map!(ascii_string("//"), |_| BinOp::DoubleSlash), map!(ascii_string("//"), |_| BinOp::DoubleSlash),
map!(ascii_char('/'), |_| BinOp::Slash), map!(ascii_char(b'/'), |_| BinOp::Slash),
map!(ascii_string("<="), |_| BinOp::LessThanOrEq), map!(ascii_string("<="), |_| BinOp::LessThanOrEq),
map!(ascii_char('<'), |_| BinOp::LessThan), map!(ascii_char(b'<'), |_| BinOp::LessThan),
map!(ascii_string(">="), |_| BinOp::GreaterThanOrEq), map!(ascii_string(">="), |_| BinOp::GreaterThanOrEq),
map!(ascii_char('>'), |_| BinOp::GreaterThan), map!(ascii_char(b'>'), |_| BinOp::GreaterThan),
map!(ascii_char('^'), |_| BinOp::Caret), map!(ascii_char(b'^'), |_| BinOp::Caret),
map!(ascii_string("%%"), |_| BinOp::DoublePercent), map!(ascii_string("%%"), |_| BinOp::DoublePercent),
map!(ascii_char('%'), |_| BinOp::Percent) map!(ascii_char(b'%'), |_| BinOp::Percent)
) )
} }
pub fn list_literal<'a>(min_indent: u16) -> impl Parser<'a, Expr<'a>> { pub fn list_literal<'a>(min_indent: u16) -> impl Parser<'a, Expr<'a>> {
let elems = collection!( let elems = collection!(
ascii_char('['), ascii_char(b'['),
loc!(expr(min_indent)), loc!(expr(min_indent)),
ascii_char(','), ascii_char(b','),
ascii_char(']'), ascii_char(b']'),
min_indent min_indent
); );
@ -1723,7 +1726,7 @@ pub fn record_literal<'a>(min_indent: u16) -> impl Parser<'a, Expr<'a>> {
// there can be field access, e.g. `{ x : 4 }.x` // there can be field access, e.g. `{ x : 4 }.x`
let (accesses, state) = optional(one_or_more!(skip_first!( let (accesses, state) = optional(one_or_more!(skip_first!(
ascii_char('.'), ascii_char(b'.'),
lowercase_ident() lowercase_ident()
))) )))
.parse(arena, state)?; .parse(arena, state)?;
@ -1819,7 +1822,7 @@ pub fn record_literal<'a>(min_indent: u16) -> impl Parser<'a, Expr<'a>> {
/// This is mainly for matching tags in closure params, e.g. \@Foo -> ... /// This is mainly for matching tags in closure params, e.g. \@Foo -> ...
pub fn private_tag<'a>() -> impl Parser<'a, &'a str> { pub fn private_tag<'a>() -> impl Parser<'a, &'a str> {
map_with_arena!( map_with_arena!(
skip_first!(ascii_char('@'), global_tag()), skip_first!(ascii_char(b'@'), global_tag()),
|arena: &'a Bump, name: &'a str| { |arena: &'a Bump, name: &'a str| {
let mut buf = String::with_capacity_in(1 + name.len(), arena); let mut buf = String::with_capacity_in(1 + name.len(), arena);

View file

@ -393,6 +393,15 @@ pub fn lowercase_ident<'a>() -> impl Parser<'a, &'a str> {
} }
} }
/// This could be:
///
/// * A module name
/// * A type name
/// * A global tag
pub fn uppercase_ident<'a>() -> impl Parser<'a, &'a str> {
global_tag_or_ident(|first_char| first_char.is_uppercase())
}
pub fn unqualified_ident<'a>() -> impl Parser<'a, &'a str> { pub fn unqualified_ident<'a>() -> impl Parser<'a, &'a str> {
global_tag_or_ident(|first_char| first_char.is_alphabetic()) global_tag_or_ident(|first_char| first_char.is_alphabetic())
} }

View file

@ -1,11 +1,11 @@
use crate::ast::{ use crate::ast::{
AppHeader, Attempting, CommentOrNewline, Def, EffectsEntry, ExposesEntry, ImportsEntry, AppHeader, Attempting, CommentOrNewline, Def, Effects, ExposesEntry, ImportsEntry,
InterfaceHeader, Module, PlatformHeader, InterfaceHeader, Module, PlatformHeader, TypedIdent,
}; };
use crate::blankspace::{space0_around, space1}; use crate::blankspace::{space0, space0_around, space0_before, space1};
use crate::expr::def; use crate::expr::def;
use crate::header::{ModuleName, PackageName}; use crate::header::{ModuleName, PackageName};
use crate::ident::unqualified_ident; use crate::ident::{lowercase_ident, unqualified_ident, uppercase_ident};
use crate::parser::{ use crate::parser::{
self, ascii_char, ascii_string, loc, optional, peek_utf8_char, peek_utf8_char_at, unexpected, self, ascii_char, ascii_string, loc, optional, peek_utf8_char, peek_utf8_char_at, unexpected,
unexpected_eof, ParseResult, Parser, State, unexpected_eof, ParseResult, Parser, State,
@ -78,7 +78,7 @@ pub fn package_name<'a>() -> impl Parser<'a, PackageName<'a>> {
map!( map!(
and!( and!(
parse_package_part, parse_package_part,
skip_first!(ascii_char('/'), parse_package_part) skip_first!(ascii_char(b'/'), parse_package_part)
), ),
|(account, pkg)| { PackageName { account, pkg } } |(account, pkg)| { PackageName { account, pkg } }
) )
@ -216,10 +216,7 @@ fn platform_header<'a>() -> impl Parser<'a, PlatformHeader<'a>> {
((before_provides, after_provides), provides), ((before_provides, after_provides), provides),
( (
((before_requires, after_requires), requires), ((before_requires, after_requires), requires),
( (((before_imports, after_imports), imports), effects),
((before_imports, after_imports), imports),
((before_effects, after_effects), effects),
),
), ),
), ),
)| { )| {
@ -236,8 +233,6 @@ fn platform_header<'a>() -> impl Parser<'a, PlatformHeader<'a>> {
after_requires, after_requires,
before_imports, before_imports,
after_imports, after_imports,
before_effects,
after_effects,
} }
}, },
) )
@ -259,10 +254,10 @@ fn provides<'a>() -> impl Parser<
and!( and!(
and!(skip_second!(space1(1), ascii_string("provides")), space1(1)), and!(skip_second!(space1(1), ascii_string("provides")), space1(1)),
collection!( collection!(
ascii_char('['), ascii_char(b'['),
loc!(exposes_entry()), loc!(exposes_entry()),
ascii_char(','), ascii_char(b','),
ascii_char(']'), ascii_char(b']'),
1 1
) )
) )
@ -273,16 +268,16 @@ fn requires<'a>() -> impl Parser<
'a, 'a,
( (
(&'a [CommentOrNewline<'a>], &'a [CommentOrNewline<'a>]), (&'a [CommentOrNewline<'a>], &'a [CommentOrNewline<'a>]),
Vec<'a, Located<ExposesEntry<'a>>>, Vec<'a, Located<TypedIdent<'a>>>,
), ),
> { > {
and!( and!(
and!(skip_second!(space1(1), ascii_string("requires")), space1(1)), and!(skip_second!(space1(1), ascii_string("requires")), space1(1)),
collection!( collection!(
ascii_char('['), ascii_char(b'{'),
loc!(exposes_entry()), loc!(typed_ident()),
ascii_char(','), ascii_char(b','),
ascii_char(']'), ascii_char(b'}'),
1 1
) )
) )
@ -299,10 +294,10 @@ fn exposes<'a>() -> impl Parser<
and!( and!(
and!(skip_second!(space1(1), ascii_string("exposes")), space1(1)), and!(skip_second!(space1(1), ascii_string("exposes")), space1(1)),
collection!( collection!(
ascii_char('['), ascii_char(b'['),
loc!(exposes_entry()), loc!(exposes_entry()),
ascii_char(','), ascii_char(b','),
ascii_char(']'), ascii_char(b']'),
1 1
) )
) )
@ -319,44 +314,72 @@ fn imports<'a>() -> impl Parser<
and!( and!(
and!(skip_second!(space1(1), ascii_string("imports")), space1(1)), and!(skip_second!(space1(1), ascii_string("imports")), space1(1)),
collection!( collection!(
ascii_char('['), ascii_char(b'['),
loc!(imports_entry()), loc!(imports_entry()),
ascii_char(','), ascii_char(b','),
ascii_char(']'), ascii_char(b']'),
1 1
) )
) )
} }
#[inline(always)] #[inline(always)]
fn effects<'a>() -> impl Parser< fn effects<'a>() -> impl Parser<'a, Effects<'a>> {
'a, move |arena, state| {
( let (spaces_before_effects_keyword, state) =
(&'a [CommentOrNewline<'a>], &'a [CommentOrNewline<'a>]), skip_second!(space1(0), ascii_string("effects")).parse(arena, state)?;
Vec<'a, Located<EffectsEntry<'a>>>, let (spaces_after_effects_keyword, state) = space1(0).parse(arena, state)?;
), let ((type_name, spaces_after_type_name), state) =
> { and!(uppercase_ident(), space1(0)).parse(arena, state)?;
and!( let (entries, state) = collection!(
and!(skip_second!(space1(1), ascii_string("effects")), space1(1)), ascii_char(b'{'),
collection!( loc!(typed_ident()),
ascii_char('{'), ascii_char(b','),
loc!(effects_entry()), ascii_char(b'}'),
ascii_char(','),
ascii_char('}'),
1 1
) )
) .parse(arena, state)?;
Ok((
Effects {
spaces_before_effects_keyword,
spaces_after_effects_keyword,
spaces_after_type_name,
type_name,
entries,
},
state,
))
}
} }
#[inline(always)] #[inline(always)]
fn effects_entry<'a>() -> impl Parser<'a, EffectsEntry<'a>> { fn typed_ident<'a>() -> impl Parser<'a, TypedIdent<'a>> {
// e.g. move |arena, state| {
// // You must have a field name, e.g. "email"
// printLine : Str -> Effect {} let (ident, state) = loc!(lowercase_ident()).parse(arena, state)?;
map!(
and!(loc(unqualified_ident()), type_annotation::located(0)), let (spaces_before_colon, state) = space0(0).parse(arena, state)?;
|(ident, ann)| { EffectsEntry::Effect { ident, ann } }
) let (ann, state) = skip_first!(
ascii_char(b':'),
space0_before(type_annotation::located(0), 0)
)
.parse(arena, state)?;
// e.g.
//
// printLine : Str -> Effect {}
Ok((
TypedIdent::Entry {
ident,
spaces_before_colon,
ann,
},
state,
))
}
} }
#[inline(always)] #[inline(always)]
@ -372,12 +395,12 @@ fn imports_entry<'a>() -> impl Parser<'a, ImportsEntry<'a>> {
module_name(), module_name(),
// e.g. `.{ Task, after}` // e.g. `.{ Task, after}`
optional(skip_first!( optional(skip_first!(
ascii_char('.'), ascii_char(b'.'),
collection!( collection!(
ascii_char('{'), ascii_char(b'{'),
loc!(exposes_entry()), loc!(exposes_entry()),
ascii_char(','), ascii_char(b','),
ascii_char('}'), ascii_char(b'}'),
1 1
) )
)) ))

View file

@ -433,14 +433,9 @@ fn line_too_long(attempting: Attempting, state: State<'_>) -> (Fail, State<'_>)
} }
/// A single ASCII char. /// A single ASCII char.
pub fn ascii_char<'a>(expected: char) -> impl Parser<'a, ()> { pub fn ascii_char<'a>(expected: u8) -> impl Parser<'a, ()> {
// Make sure this really is an ASCII char!
debug_assert!(expected.len_utf8() == 1);
move |_arena, state: State<'a>| match state.bytes.first() { move |_arena, state: State<'a>| match state.bytes.first() {
Some(&actual) if expected == actual as char => { Some(&actual) if expected == actual => Ok(((), state.advance_without_indenting(1)?)),
Ok(((), state.advance_without_indenting(1)?))
}
Some(_) => Err(unexpected(0, state, Attempting::Keyword)), Some(_) => Err(unexpected(0, state, Attempting::Keyword)),
_ => Err(unexpected_eof(0, Attempting::Keyword, state)), _ => Err(unexpected_eof(0, Attempting::Keyword, state)),
} }
@ -788,7 +783,7 @@ macro_rules! collection {
// We could change the AST to add extra storage specifically to // We could change the AST to add extra storage specifically to
// support empty literals containing newlines or comments, but this // support empty literals containing newlines or comments, but this
// does not seem worth even the tiniest regression in compiler performance. // does not seem worth even the tiniest regression in compiler performance.
zero_or_more!($crate::parser::ascii_char(' ')), zero_or_more!($crate::parser::ascii_char(b' ')),
skip_second!( skip_second!(
$crate::parser::sep_by0( $crate::parser::sep_by0(
$delimiter, $delimiter,
@ -1025,8 +1020,8 @@ macro_rules! record_field {
// Having a value is optional; both `{ email }` and `{ email: blah }` work. // Having a value is optional; both `{ email }` and `{ email: blah }` work.
// (This is true in both literals and types.) // (This is true in both literals and types.)
let (opt_loc_val, state) = $crate::parser::optional(either!( let (opt_loc_val, state) = $crate::parser::optional(either!(
skip_first!(ascii_char(':'), space0_before($val_parser, $min_indent)), skip_first!(ascii_char(b':'), space0_before($val_parser, $min_indent)),
skip_first!(ascii_char('?'), space0_before($val_parser, $min_indent)) skip_first!(ascii_char(b'?'), space0_before($val_parser, $min_indent))
)) ))
.parse(arena, state)?; .parse(arena, state)?;
@ -1055,10 +1050,10 @@ macro_rules! record_field {
macro_rules! record_without_update { macro_rules! record_without_update {
($val_parser:expr, $min_indent:expr) => { ($val_parser:expr, $min_indent:expr) => {
collection!( collection!(
ascii_char('{'), ascii_char(b'{'),
loc!(record_field!($val_parser, $min_indent)), loc!(record_field!($val_parser, $min_indent)),
ascii_char(','), ascii_char(b','),
ascii_char('}'), ascii_char(b'}'),
$min_indent $min_indent
) )
}; };
@ -1068,7 +1063,7 @@ macro_rules! record_without_update {
macro_rules! record { macro_rules! record {
($val_parser:expr, $min_indent:expr) => { ($val_parser:expr, $min_indent:expr) => {
skip_first!( skip_first!(
$crate::parser::ascii_char('{'), $crate::parser::ascii_char(b'{'),
and!( and!(
// You can optionally have an identifier followed by an '&' to // You can optionally have an identifier followed by an '&' to
// make this a record update, e.g. { Foo.user & username: "blah" }. // make this a record update, e.g. { Foo.user & username: "blah" }.
@ -1084,7 +1079,7 @@ macro_rules! record {
)), )),
$min_indent $min_indent
), ),
$crate::parser::ascii_char('&') $crate::parser::ascii_char(b'&')
)), )),
loc!(skip_first!( loc!(skip_first!(
// We specifically allow space characters inside here, so that // We specifically allow space characters inside here, so that
@ -1098,16 +1093,16 @@ macro_rules! record {
// We could change the AST to add extra storage specifically to // We could change the AST to add extra storage specifically to
// support empty literals containing newlines or comments, but this // support empty literals containing newlines or comments, but this
// does not seem worth even the tiniest regression in compiler performance. // does not seem worth even the tiniest regression in compiler performance.
zero_or_more!($crate::parser::ascii_char(' ')), zero_or_more!($crate::parser::ascii_char(b' ')),
skip_second!( skip_second!(
$crate::parser::sep_by0( $crate::parser::sep_by0(
$crate::parser::ascii_char(','), $crate::parser::ascii_char(b','),
$crate::blankspace::space0_around( $crate::blankspace::space0_around(
loc!(record_field!($val_parser, $min_indent)), loc!(record_field!($val_parser, $min_indent)),
$min_indent $min_indent
) )
), ),
$crate::parser::ascii_char('}') $crate::parser::ascii_char(b'}')
) )
)) ))
) )

View file

@ -162,7 +162,7 @@ pub fn parse<'a>() -> impl Parser<'a, StrLiteral<'a>> {
// canonicalization error if that expression variant // canonicalization error if that expression variant
// is not allowed inside a string interpolation. // is not allowed inside a string interpolation.
let (loc_expr, new_state) = let (loc_expr, new_state) =
skip_second!(loc(allocated(expr::expr(0))), ascii_char(')')) skip_second!(loc(allocated(expr::expr(0))), ascii_char(b')'))
.parse(arena, state)?; .parse(arena, state)?;
// Advance the iterator past the expr we just parsed. // Advance the iterator past the expr we just parsed.
@ -185,9 +185,12 @@ pub fn parse<'a>() -> impl Parser<'a, StrLiteral<'a>> {
// Parse the hex digits, surrounded by parens, then // Parse the hex digits, surrounded by parens, then
// give a canonicalization error if the digits form // give a canonicalization error if the digits form
// an invalid unicode code point. // an invalid unicode code point.
let (loc_digits, new_state) = let (loc_digits, new_state) = between!(
between!(ascii_char('('), loc(ascii_hex_digits()), ascii_char(')')) ascii_char(b'('),
.parse(arena, state)?; loc(ascii_hex_digits()),
ascii_char(b')')
)
.parse(arena, state)?;
// Advance the iterator past the expr we just parsed. // Advance the iterator past the expr we just parsed.
for _ in 0..(original_byte_count - new_state.bytes.len()) { for _ in 0..(original_byte_count - new_state.bytes.len()) {

View file

@ -22,10 +22,10 @@ macro_rules! tag_union {
map!( map!(
and!( and!(
collection!( collection!(
ascii_char('['), ascii_char(b'['),
loc!(tag_type($min_indent)), loc!(tag_type($min_indent)),
ascii_char(','), ascii_char(b','),
ascii_char(']'), ascii_char(b']'),
$min_indent $min_indent
), ),
optional( optional(
@ -89,7 +89,7 @@ pub fn term<'a>(min_indent: u16) -> impl Parser<'a, Located<TypeAnnotation<'a>>>
/// The `*` type variable, e.g. in (List *) Wildcard, /// The `*` type variable, e.g. in (List *) Wildcard,
fn loc_wildcard<'a>() -> impl Parser<'a, Located<TypeAnnotation<'a>>> { fn loc_wildcard<'a>() -> impl Parser<'a, Located<TypeAnnotation<'a>>> {
map!(loc!(ascii_char('*')), |loc_val: Located<()>| { map!(loc!(ascii_char(b'*')), |loc_val: Located<()>| {
loc_val.map(|_| TypeAnnotation::Wildcard) loc_val.map(|_| TypeAnnotation::Wildcard)
}) })
} }
@ -112,12 +112,12 @@ pub fn loc_applied_arg<'a>(min_indent: u16) -> impl Parser<'a, Located<TypeAnnot
#[inline(always)] #[inline(always)]
fn loc_parenthetical_type<'a>(min_indent: u16) -> impl Parser<'a, Located<TypeAnnotation<'a>>> { fn loc_parenthetical_type<'a>(min_indent: u16) -> impl Parser<'a, Located<TypeAnnotation<'a>>> {
between!( between!(
ascii_char('('), ascii_char(b'('),
space0_around( space0_around(
move |arena, state| expression(min_indent).parse(arena, state), move |arena, state| expression(min_indent).parse(arena, state),
min_indent, min_indent,
), ),
ascii_char(')') ascii_char(b')')
) )
} }
@ -208,7 +208,7 @@ fn expression<'a>(min_indent: u16) -> impl Parser<'a, Located<TypeAnnotation<'a>
move |arena, state: State<'a>| { move |arena, state: State<'a>| {
let (first, state) = space0_before(term(min_indent), min_indent).parse(arena, state)?; let (first, state) = space0_before(term(min_indent), min_indent).parse(arena, state)?;
let (rest, state) = zero_or_more!(skip_first!( let (rest, state) = zero_or_more!(skip_first!(
ascii_char(','), ascii_char(b','),
space0_around(term(min_indent), min_indent) space0_around(term(min_indent), min_indent)
)) ))
.parse(arena, state)?; .parse(arena, state)?;

View file

@ -2345,18 +2345,20 @@ mod test_parse {
let arena = Bump::new(); let arena = Bump::new();
let newlines = &[Newline, Newline]; let newlines = &[Newline, Newline];
let def = Def::Body( let def = Def::Body(
arena.alloc(Located::new(4, 4, 0, 1, Identifier("x"))), arena.alloc(Located::new(6, 6, 0, 1, Identifier("x"))),
arena.alloc(Located::new(4, 4, 4, 5, Num("5"))), arena.alloc(Located::new(6, 6, 4, 5, Num("5"))),
); );
let loc_def = &*arena.alloc(Located::new(4, 4, 0, 1, def)); let loc_def = &*arena.alloc(Located::new(6, 6, 0, 1, def));
let defs = &[loc_def]; let defs = &[loc_def];
let ret = Expr::SpaceBefore(arena.alloc(Num("42")), newlines); let ret = Expr::SpaceBefore(arena.alloc(Num("42")), newlines);
let loc_ret = Located::new(6, 6, 0, 2, ret); let loc_ret = Located::new(8, 8, 0, 2, ret);
let reset_indentation = &[ let reset_indentation = &[
DocComment("first line of docs"), DocComment("first line of docs"),
DocComment(" second line"), DocComment(" second line"),
DocComment(" third line"), DocComment(" third line"),
DocComment("fourth line"), DocComment("fourth line"),
DocComment(""),
DocComment("sixth line after doc new line"),
]; ];
let expected = Expr::SpaceBefore( let expected = Expr::SpaceBefore(
arena.alloc(Defs(defs, arena.alloc(loc_ret))), arena.alloc(Defs(defs, arena.alloc(loc_ret))),
@ -2370,6 +2372,8 @@ mod test_parse {
## second line ## second line
## third line ## third line
## fourth line ## fourth line
##
## sixth line after doc new line
x = 5 x = 5
42 42

View file

@ -777,6 +777,16 @@ fn to_expr_report<'b>(
op op
); );
} }
Reason::ForeignCallArg {
foreign_symbol,
arg_index,
} => {
panic!(
"Compiler bug: argument #{} to foreign symbol {:?} was the wrong type!",
arg_index.ordinal(),
foreign_symbol
);
}
Reason::FloatLiteral | Reason::IntLiteral | Reason::NumLiteral => { Reason::FloatLiteral | Reason::IntLiteral | Reason::NumLiteral => {
unreachable!("I don't think these can be reached") unreachable!("I don't think these can be reached")
} }
@ -954,6 +964,9 @@ fn add_category<'b>(
op op
); );
} }
ForeignCall => {
panic!("Compiler bug: invalid return type from foreign call",);
}
Uniqueness => alloc.concat(vec![ Uniqueness => alloc.concat(vec![
this_is, this_is,

View file

@ -5,6 +5,7 @@ use roc_collections::all::{ImMap, MutSet, SendMap};
use roc_module::ident::{Lowercase, TagName}; use roc_module::ident::{Lowercase, TagName};
use roc_module::symbol::Symbol; use roc_module::symbol::Symbol;
use roc_region::all::{Located, Region}; use roc_region::all::{Located, Region};
use std::hash::{Hash, Hasher};
/// A marker that a given Subs has been solved. /// A marker that a given Subs has been solved.
/// The only way to obtain a Solved<Subs> is by running the solver on it. /// The only way to obtain a Solved<Subs> is by running the solver on it.
@ -25,8 +26,136 @@ impl<T> Solved<T> {
} }
} }
/// A custom hash instance, that treats flex vars specially, so that
///
/// `Foo 100 200 100` hashes to the same as `Foo 300 100 300`
///
/// i.e., we can rename the flex variables, so long as it happens consistently.
/// this is important so we don't generate the same PartialProc twice.
impl Hash for SolvedType {
fn hash<H: Hasher>(&self, state: &mut H) {
hash_solved_type_help(self, &mut Vec::new(), state);
}
}
impl PartialEq for SolvedType {
fn eq(&self, other: &Self) -> bool {
use std::collections::hash_map::DefaultHasher;
let mut state = DefaultHasher::new();
hash_solved_type_help(self, &mut Vec::new(), &mut state);
let hash1 = state.finish();
let mut state = DefaultHasher::new();
hash_solved_type_help(other, &mut Vec::new(), &mut state);
let hash2 = state.finish();
hash1 == hash2
}
}
fn hash_solved_type_help<H: Hasher>(
solved_type: &SolvedType,
flex_vars: &mut Vec<VarId>,
state: &mut H,
) {
use SolvedType::*;
match solved_type {
Flex(var_id) => {
var_id_hash_help(*var_id, flex_vars, state);
}
Wildcard => "wildcard".hash(state),
EmptyRecord => "empty_record".hash(state),
EmptyTagUnion => "empty_tag_union".hash(state),
Error => "error".hash(state),
Func(arguments, closure, result) => {
for x in arguments {
hash_solved_type_help(x, flex_vars, state);
}
hash_solved_type_help(closure, flex_vars, state);
hash_solved_type_help(result, flex_vars, state);
}
Apply(name, arguments) => {
name.hash(state);
for x in arguments {
hash_solved_type_help(x, flex_vars, state);
}
}
Rigid(name) => name.hash(state),
Erroneous(problem) => problem.hash(state),
Boolean(solved_bool) => solved_bool.hash(state),
Record { fields, ext } => {
for (name, x) in fields {
name.hash(state);
"record_field".hash(state);
hash_solved_type_help(x.as_inner(), flex_vars, state);
}
hash_solved_type_help(ext, flex_vars, state);
}
TagUnion(tags, ext) => {
for (name, arguments) in tags {
name.hash(state);
for x in arguments {
hash_solved_type_help(x, flex_vars, state);
}
}
hash_solved_type_help(ext, flex_vars, state);
}
RecursiveTagUnion(rec, tags, ext) => {
var_id_hash_help(*rec, flex_vars, state);
for (name, arguments) in tags {
name.hash(state);
for x in arguments {
hash_solved_type_help(x, flex_vars, state);
}
}
hash_solved_type_help(ext, flex_vars, state);
}
Alias(name, arguments, actual) => {
name.hash(state);
for (name, x) in arguments {
name.hash(state);
hash_solved_type_help(x, flex_vars, state);
}
hash_solved_type_help(actual, flex_vars, state);
}
HostExposedAlias {
name,
arguments,
actual,
actual_var,
} => {
name.hash(state);
for (name, x) in arguments {
name.hash(state);
hash_solved_type_help(x, flex_vars, state);
}
hash_solved_type_help(actual, flex_vars, state);
var_id_hash_help(*actual_var, flex_vars, state);
}
}
}
fn var_id_hash_help<H: Hasher>(var_id: VarId, flex_vars: &mut Vec<VarId>, state: &mut H) {
let opt_index = flex_vars.iter().position(|x| *x == var_id);
match opt_index {
Some(index) => index.hash(state),
None => {
flex_vars.len().hash(state);
flex_vars.push(var_id);
}
}
}
/// This is a fully solved type, with no Variables remaining in it. /// This is a fully solved type, with no Variables remaining in it.
#[derive(Debug, Clone, PartialEq, Eq, Hash)] #[derive(Debug, Clone, Eq)]
pub enum SolvedType { pub enum SolvedType {
/// A function. The types of its arguments, then the type of its return value. /// A function. The types of its arguments, then the type of its return value.
Func(Vec<SolvedType>, Box<SolvedType>, Box<SolvedType>), Func(Vec<SolvedType>, Box<SolvedType>, Box<SolvedType>),

View file

@ -3,7 +3,7 @@ use crate::pretty_print::Parens;
use crate::subs::{Subs, VarStore, Variable}; use crate::subs::{Subs, VarStore, Variable};
use inlinable_string::InlinableString; use inlinable_string::InlinableString;
use roc_collections::all::{union, ImMap, ImSet, Index, MutMap, MutSet, SendMap}; use roc_collections::all::{union, ImMap, ImSet, Index, MutMap, MutSet, SendMap};
use roc_module::ident::{Ident, Lowercase, TagName}; use roc_module::ident::{ForeignSymbol, Ident, Lowercase, TagName};
use roc_module::low_level::LowLevel; use roc_module::low_level::LowLevel;
use roc_module::symbol::{Interns, ModuleId, Symbol}; use roc_module::symbol::{Interns, ModuleId, Symbol};
use roc_region::all::{Located, Region}; use roc_region::all::{Located, Region};
@ -966,6 +966,10 @@ pub enum Reason {
op: LowLevel, op: LowLevel,
arg_index: Index, arg_index: Index,
}, },
ForeignCallArg {
foreign_symbol: ForeignSymbol,
arg_index: Index,
},
FloatLiteral, FloatLiteral,
IntLiteral, IntLiteral,
NumLiteral, NumLiteral,
@ -992,6 +996,7 @@ pub enum Category {
Lookup(Symbol), Lookup(Symbol),
CallResult(Option<Symbol>), CallResult(Option<Symbol>),
LowLevelOpResult(LowLevel), LowLevelOpResult(LowLevel),
ForeignCall,
TagApply { TagApply {
tag_name: TagName, tag_name: TagName,
args_count: usize, args_count: usize,

View file

@ -790,7 +790,8 @@ pub fn annotate_usage(expr: &Expr, usage: &mut VarUsage) {
| Str { .. } | Str { .. }
| EmptyRecord | EmptyRecord
| Accessor { .. } | Accessor { .. }
| RunLowLevel { .. } => {} | RunLowLevel { .. }
| ForeignCall { .. } => {}
Var(symbol) => usage.register_unique(*symbol), Var(symbol) => usage.register_unique(*symbol),

View file

@ -266,8 +266,13 @@ mod test_docs {
}, },
ModuleEntry { ModuleEntry {
name: "multiline".to_string(), name: "multiline".to_string(),
docs: "<p>Multiline documentation.\nWithout any complex syntax yet!</p>\n" docs: "<p>Multiline documentation.\nWithout any complex syntax yet!</p>\n".to_string(),
.to_string(), }, ModuleEntry {
name: "multiparagraph".to_string(),
docs: "<p>Multiparagraph documentation.</p>\n<p>Without any complex syntax yet!</p>\n".to_string(),
}, ModuleEntry {
name: "codeblock".to_string(),
docs: "<p>Turns &gt;&gt;&gt; into code block for now.</p>\n<pre><code class=\"language-roc\">codeblock</code></pre>\n".to_string(),
}, },
]; ];

View file

@ -1,5 +1,5 @@
interface Test interface Test
exposes [ singleline, multiline ] exposes [ singleline, multiline, multiparagraph, codeblock ]
imports [] imports []
## Single line documentation. ## Single line documentation.
@ -9,5 +9,15 @@ singleline : Bool -> Bool
## Without any complex syntax yet! ## Without any complex syntax yet!
multiline : Bool -> Bool multiline : Bool -> Bool
## Multiparagraph documentation.
##
## Without any complex syntax yet!
multiparagraph : Bool -> Bool
## No documentation for not exposed function. ## No documentation for not exposed function.
notExposed : Bool -> Bool notExposed : Bool -> Bool
## Turns >>> into code block for now.
##
## >>> codeblock
codeblock : Bool -> Bool

View file

@ -0,0 +1,6 @@
platform folkertdev/foo
provides [ mainForHost ]
requires { main : Effect {} }
imports []
effects Effect
{ putChar : Int -> Effect {}, putLine : Str -> Effect {} }

8
examples/effect/Main.roc Normal file
View file

@ -0,0 +1,8 @@
app Main provides [ main ] imports [ Effect ]
main : Effect.Effect {} as Fx
main =
Effect.putLine "Hello"
|> Effect.after \{} -> Effect.putChar 87
# |> Effect.after \{} -> Effect.putLine "orld"

23
examples/effect/platform/Cargo.lock generated Normal file
View file

@ -0,0 +1,23 @@
# This file is automatically @generated by Cargo.
# It is not intended for manual editing.
[[package]]
name = "host"
version = "0.1.0"
dependencies = [
"roc_std 0.1.0",
]
[[package]]
name = "libc"
version = "0.2.79"
source = "registry+https://github.com/rust-lang/crates.io-index"
[[package]]
name = "roc_std"
version = "0.1.0"
dependencies = [
"libc 0.2.79 (registry+https://github.com/rust-lang/crates.io-index)",
]
[metadata]
"checksum libc 0.2.79 (registry+https://github.com/rust-lang/crates.io-index)" = "2448f6066e80e3bfc792e9c98bf705b4b0fc6e8ef5b43e5889aff0eaa9c58743"

View file

@ -0,0 +1,13 @@
[package]
name = "host"
version = "0.1.0"
authors = ["Richard Feldman <oss@rtfeldman.com>"]
edition = "2018"
[lib]
crate-type = ["staticlib"]
[dependencies]
roc_std = { path = "../../../roc_std" }
[workspace]

View file

@ -0,0 +1,7 @@
#include <stdio.h>
extern int rust_main();
int main() {
return rust_main();
}

View file

@ -0,0 +1,107 @@
use roc_std::alloca;
use roc_std::RocCallResult;
use roc_std::RocStr;
use std::alloc::Layout;
use std::time::SystemTime;
extern "C" {
#[link_name = "main_1_exposed"]
fn roc_main(output: *mut u8) -> ();
#[link_name = "main_1_size"]
fn roc_main_size() -> i64;
#[link_name = "main_1_Fx_caller"]
fn call_Fx(function_pointer: *const u8, closure_data: *const u8, output: *mut u8) -> ();
#[link_name = "main_1_Fx_size"]
fn size_Fx() -> i64;
}
#[no_mangle]
pub fn roc_fx_putChar(foo: i64) -> () {
let character = foo as u8 as char;
print!("{}", character);
()
}
#[no_mangle]
pub fn roc_fx_putLine(line: RocStr) -> () {
let bytes = line.as_slice();
let string = unsafe { std::str::from_utf8_unchecked(bytes) };
println!("{}", string);
()
}
unsafe fn call_the_closure(function_pointer: *const u8, closure_data_ptr: *const u8) -> i64 {
let size = size_Fx() as usize;
alloca::with_stack_bytes(size, |buffer| {
let buffer: *mut std::ffi::c_void = buffer;
let buffer: *mut u8 = buffer as *mut u8;
call_Fx(
function_pointer,
closure_data_ptr as *const u8,
buffer as *mut u8,
);
let output = &*(buffer as *mut RocCallResult<i64>);
match output.into() {
Ok(v) => v,
Err(e) => panic!("failed with {}", e),
}
})
}
#[no_mangle]
pub fn rust_main() -> isize {
println!("Running Roc closure");
let start_time = SystemTime::now();
let size = unsafe { roc_main_size() } as usize;
let layout = Layout::array::<u8>(size).unwrap();
let answer = unsafe {
let buffer = std::alloc::alloc(layout);
roc_main(buffer);
let output = &*(buffer as *mut RocCallResult<()>);
match output.into() {
Ok(()) => {
let function_pointer = {
// this is a pointer to the location where the function pointer is stored
// we pass just the function pointer
let temp = buffer.offset(8) as *const i64;
(*temp) as *const u8
};
let closure_data_ptr = buffer.offset(16);
call_the_closure(function_pointer as *const u8, closure_data_ptr as *const u8)
}
Err(msg) => {
std::alloc::dealloc(buffer, layout);
panic!("Roc failed with message: {}", msg);
}
}
};
let end_time = SystemTime::now();
let duration = end_time.duration_since(start_time).unwrap();
println!(
"Roc closure took {:.4} ms to compute this answer: {:?}",
duration.as_secs_f64() * 1000.0,
// truncate the answer, so stdout is not swamped
answer
);
// Exit code
0
}

View file

@ -1,6 +1,4 @@
app Quicksort app Quicksort provides [ quicksort ] imports [ Utils.{swap} ]
provides [ quicksort ]
imports [ Utils.{swap} ]
quicksort : List Int -> List Int quicksort : List Int -> List Int