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]
#[serial(multi_dep_str)]
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(
&fixture_file("multi-dep-str", "Main.roc"),
&[],
@ -184,7 +189,7 @@ mod cli_run {
fn run_multi_dep_str_optimized() {
check_output(
&fixture_file("multi-dep-str", "Main.roc"),
&[],
&["--optimize"],
"I am Dep2.str2\n",
true,
);
@ -202,11 +207,11 @@ mod cli_run {
}
#[test]
#[serial(multi_dep_str)]
#[serial(multi_dep_thunk)]
fn run_multi_dep_thunk_optimized() {
check_output(
&fixture_file("multi-dep-thunk", "Main.roc"),
&[],
&["--optimize"],
"I am Dep2.value2\n",
true,
);

View File

@ -11,7 +11,7 @@ use crate::procedure::References;
use crate::scope::Scope;
use inlinable_string::InlinableString;
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::operator::CalledVia;
use roc_module::symbol::Symbol;
@ -98,6 +98,11 @@ pub enum Expr {
args: Vec<(Variable, Expr)>,
ret_var: Variable,
},
ForeignCall {
foreign_symbol: ForeignSymbol,
args: Vec<(Variable, Expr)>,
ret_var: Variable,
},
Closure {
function_type: Variable,
@ -1170,7 +1175,8 @@ pub fn inline_calls(var_store: &mut VarStore, scope: &mut Scope, expr: Expr) ->
| other @ Accessor { .. }
| other @ Update { .. }
| other @ Var(_)
| other @ RunLowLevel { .. } => other,
| other @ RunLowLevel { .. }
| other @ ForeignCall { .. } => other,
List {
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);
}
}
RunLowLevel { args, .. } => {
RunLowLevel { args, .. } | ForeignCall { args, .. } => {
for (_, arg) in args.iter_mut() {
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(_) => {
// Runtime Errors have no constraints because they're going to crash.
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) => {
// NOTE doesn't currently unregister bound symbols
// 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),
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 {
call_type: ByName(name),
full_layout,
@ -1116,9 +1148,10 @@ pub fn build_exp_expr<'a, 'ctx, 'env>(
env.arena.alloc(format!("closure_field_access_{}_", index)),
)
.unwrap(),
(other, layout) => {
unreachable!("can only index into struct layout {:?} {:?}", other, layout)
}
(other, layout) => unreachable!(
"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()
}
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> {
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("\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(_) => {}
}
}

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)]
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)]
pub enum TagName {
/// 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 {
pub fn as_str(&self) -> &str {
&*self.0

View File

@ -374,6 +374,15 @@ impl<'a> BorrowInfState<'a> {
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(_) => {}
}
}
@ -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] {
use LowLevel::*;

View File

@ -125,6 +125,9 @@ pub fn occuring_variables_expr(expr: &Expr<'_>, result: &mut MutSet<Symbol>) {
RunLowLevel(_, args) => {
result.extend(args.iter());
}
ForeignCall { arguments, .. } => {
result.extend(arguments.iter());
}
EmptyArray | RuntimeErrorFunction(_) | Literal(_) => {}
}
@ -463,6 +466,13 @@ impl<'a> Context<'a> {
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 {
args: ys,
arg_layouts,

View File

@ -4,7 +4,7 @@ use crate::layout::{Builtin, ClosureLayout, Layout, LayoutCache, LayoutProblem};
use bumpalo::collections::Vec;
use bumpalo::Bump;
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::symbol::{IdentIds, ModuleId, Symbol};
use roc_problem::can::RuntimeError;
@ -444,7 +444,7 @@ impl<'a> Procs<'a> {
// register the pending specialization, so this gets code genned later
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(
symbol,
@ -829,6 +829,11 @@ pub enum Expr<'a> {
args: &'a [Symbol],
},
RunLowLevel(LowLevel, &'a [Symbol]),
ForeignCall {
foreign_symbol: ForeignSymbol,
arguments: &'a [Symbol],
ret_layout: Layout<'a>,
},
Tag {
tag_layout: Layout<'a>,
@ -943,6 +948,17 @@ impl<'a> Expr<'a> {
.text(format!("lowlevel {:?} ", lowlevel))
.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_name,
arguments,
@ -1331,8 +1347,27 @@ pub fn specialize_all<'a>(
let it = procs.externals_others_need.specs.clone();
let it = it
.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();
for (name, solved_type) in it.into_iter() {
let partial_proc = match procs.partial_procs.get(&name) {
Some(v) => v.clone(),
@ -1396,7 +1431,6 @@ pub fn specialize_all<'a>(
procs
.specialized
.insert((name, outside_layout.clone()), InProgress);
match specialize(
env,
&mut procs,
@ -1407,8 +1441,16 @@ pub fn specialize_all<'a>(
) {
Ok((proc, 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) => {
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 is_valid = matches!(unified, roc_unify::unify::Unified::Success(_));
debug_assert!(is_valid);
let mut specialized_body = from_can(env, fn_var, body, procs, layout_cache);
debug_assert!(is_valid, "unificaton failure for {:?}", proc_name);
// if this is a closure, add the closure record argument
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) =
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
if let CapturedSymbols::Captured(captured) = captured_symbols {
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)]
fn build_specialized_proc<'a>(
arena: &'a Bump,
_proc_name: Symbol,
proc_name: Symbol,
pattern_symbols: &[Symbol],
pattern_layouts: Vec<Layout<'a>>,
pattern_layouts: Vec<'a, Layout<'a>>,
opt_closure_layout: Option<ClosureLayout<'a>>,
ret_layout: Layout<'a>,
) -> Result<SpecializedLayout<'a>, LayoutProblem> {
@ -1709,7 +1750,8 @@ fn build_specialized_proc<'a>(
debug_assert_eq!(
pattern_layouts_len + 1,
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();
@ -1738,16 +1780,22 @@ fn build_specialized_proc<'a>(
// make sure there is not arg_closure argument without a closure layout
debug_assert!(pattern_symbols.last() != Some(&Symbol::ARG_CLOSURE));
// since this is not a closure, the number of arguments should match between symbols
// and layout
debug_assert_eq!(
pattern_layouts_len,
pattern_symbols.len(),
"Tried to zip two vecs with different lengths!"
);
let proc_args = proc_args.into_bump_slice();
use std::cmp::Ordering;
match pattern_layouts_len.cmp(&pattern_symbols.len()) {
Ordering::Equal => {
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 {
roc_can::expr::Expr::Var(proc_name) if is_known(&proc_name) => call_by_name(
env,
procs,
fn_var,
proc_name,
loc_args,
layout_cache,
assigned,
hole,
),
roc_can::expr::Expr::Var(proc_name) if is_known(&proc_name) => {
// a call by a known name
call_by_name(
env,
procs,
fn_var,
proc_name,
loc_args,
layout_cache,
assigned,
hole,
)
}
_ => {
// 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 } => {
let op = optimize_low_level(env.subs, op, &args);
@ -3956,6 +4043,35 @@ fn substitute_in_expr<'a>(
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_layout,

View File

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

View File

@ -52,9 +52,9 @@ pub struct AppHeader<'a> {
pub struct PlatformHeader<'a> {
pub name: Loc<PackageName<'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 effects: Vec<'a, Loc<EffectsEntry<'a>>>,
pub effects: Effects<'a>,
// Potential comments and newlines - these will typically all be empty.
pub after_platform_keyword: &'a [CommentOrNewline<'a>],
@ -64,23 +64,31 @@ pub struct PlatformHeader<'a> {
pub after_requires: &'a [CommentOrNewline<'a>],
pub before_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)]
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.
///
/// printLine : Str -> Effect {}
Effect {
Entry {
ident: Loc<&'a str>,
spaces_before_colon: &'a [CommentOrNewline<'a>],
ann: Loc<TypeAnnotation<'a>>,
},
// Spaces
SpaceBefore(&'a EffectsEntry<'a>, &'a [CommentOrNewline<'a>]),
SpaceAfter(&'a EffectsEntry<'a>, &'a [CommentOrNewline<'a>]),
SpaceBefore(&'a TypedIdent<'a>, &'a [CommentOrNewline<'a>]),
SpaceAfter(&'a TypedIdent<'a>, &'a [CommentOrNewline<'a>]),
}
#[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 {
EffectsEntry::SpaceBefore(self, spaces)
TypedIdent::SpaceBefore(self, spaces)
}
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' => {
state = state.newline()?;
// 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);
match (comment_line_buf.len(), comment_line_buf.chars().next())
{
(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 => {
// Chars can have btye lengths of more than 1!

View File

@ -30,7 +30,7 @@ macro_rules! loc_parenthetical_expr {
then(
loc!(and!(
between!(
ascii_char('(' ),
ascii_char(b'(' ),
map_with_arena!(
space0_around(
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!(
// 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
// e.g. in `((foo bar) baz.blah)` the `.blah` will be consumed by the `baz` parser
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())
)
))
@ -170,7 +170,7 @@ pub fn unary_op<'a>(min_indent: u16) -> impl Parser<'a, Expr<'a>> {
one_of!(
map_with_arena!(
and!(
loc!(ascii_char('!')),
loc!(ascii_char(b'!')),
loc!(move |arena, state| parse_expr(min_indent, arena, state))
),
|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!(
and!(
loc!(ascii_char('-')),
loc!(ascii_char(b'-')),
loc!(move |arena, state| parse_expr(min_indent, arena, state))
),
|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!(
space0_after(
between!(
ascii_char('('),
ascii_char(b'('),
space0_around(loc_pattern(min_indent), min_indent),
ascii_char(')')
ascii_char(b')')
),
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
/// an "==") and also it can't be followed by '>' (or else it's actually an "=>")
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:
@ -512,7 +515,7 @@ pub fn def<'a>(min_indent: u16) -> impl Parser<'a, Def<'a>> {
),
// Annotation
skip_first!(
ascii_char(':'),
ascii_char(b':'),
// Spaces after the ':' (at a normal indentation level) and then the type.
// The type itself must be indented more than the pattern and ':'
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!(
skip_first!(
// 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.
// It may turn out to be malformed, but it is definitely a closure.
optional(and!(
@ -847,7 +850,7 @@ fn closure<'a>(min_indent: u16) -> impl Parser<'a, Expr<'a>> {
Attempting::ClosureParams,
// Params are comma-separated
sep_by1(
ascii_char(','),
ascii_char(b','),
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.
// e.g. \User.UserId userId -> ...
between!(
ascii_char('('),
ascii_char(b'('),
space0_around(loc_pattern(min_indent), min_indent),
ascii_char(')')
ascii_char(b')')
)
)
.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>>> {
between!(
ascii_char('('),
ascii_char(b'('),
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>> {
map!(ascii_char('_'), |_| Pattern::Underscore)
map!(ascii_char(b'_'), |_| Pattern::Underscore)
}
fn record_destructure<'a>(min_indent: u16) -> impl Parser<'a, Pattern<'a>> {
then(
collection!(
ascii_char('{'),
ascii_char(b'{'),
move |arena: &'a bumpalo::Bump,
state: crate::parser::State<'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.)
let (opt_loc_val, state) = crate::parser::optional(either!(
skip_first!(
ascii_char(':'),
ascii_char(b':'),
space0_before(loc_pattern(min_indent), min_indent)
),
skip_first!(
ascii_char('?'),
ascii_char(b'?'),
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))
},
ascii_char(','),
ascii_char('}'),
ascii_char(b','),
ascii_char(b'}'),
min_indent
),
move |_arena, state, loc_patterns| {
@ -1250,7 +1253,7 @@ mod when {
) -> impl Parser<'a, (Vec<'a, Located<Pattern<'a>>>, Option<Located<Expr<'a>>>)> {
and!(
sep_by1(
ascii_char('|'),
ascii_char(b'|'),
space0_around(loc_pattern(min_indent), min_indent),
),
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,
// because otherwise (foo -1) will parse as (foo (Num.neg 1))
loc!(number_literal()),
loc!(ascii_char('-'))
loc!(ascii_char(b'-'))
)
),
one_of!(
ascii_char(' '),
ascii_char('#'),
ascii_char('\n'),
ascii_char('>')
ascii_char(b' '),
ascii_char(b'#'),
ascii_char(b'\n'),
ascii_char(b'>')
),
),
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::And),
map!(ascii_string("||"), |_| BinOp::Or),
map!(ascii_char('+'), |_| BinOp::Plus),
map!(ascii_char('*'), |_| BinOp::Star),
map!(ascii_char('-'), |_| BinOp::Minus),
map!(ascii_char(b'+'), |_| BinOp::Plus),
map!(ascii_char(b'*'), |_| BinOp::Star),
map!(ascii_char(b'-'), |_| BinOp::Minus),
map!(ascii_string("//"), |_| BinOp::DoubleSlash),
map!(ascii_char('/'), |_| BinOp::Slash),
map!(ascii_char(b'/'), |_| BinOp::Slash),
map!(ascii_string("<="), |_| BinOp::LessThanOrEq),
map!(ascii_char('<'), |_| BinOp::LessThan),
map!(ascii_char(b'<'), |_| BinOp::LessThan),
map!(ascii_string(">="), |_| BinOp::GreaterThanOrEq),
map!(ascii_char('>'), |_| BinOp::GreaterThan),
map!(ascii_char('^'), |_| BinOp::Caret),
map!(ascii_char(b'>'), |_| BinOp::GreaterThan),
map!(ascii_char(b'^'), |_| BinOp::Caret),
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>> {
let elems = collection!(
ascii_char('['),
ascii_char(b'['),
loc!(expr(min_indent)),
ascii_char(','),
ascii_char(']'),
ascii_char(b','),
ascii_char(b']'),
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`
let (accesses, state) = optional(one_or_more!(skip_first!(
ascii_char('.'),
ascii_char(b'.'),
lowercase_ident()
)))
.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 -> ...
pub fn private_tag<'a>() -> impl Parser<'a, &'a str> {
map_with_arena!(
skip_first!(ascii_char('@'), global_tag()),
skip_first!(ascii_char(b'@'), global_tag()),
|arena: &'a Bump, name: &'a str| {
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> {
global_tag_or_ident(|first_char| first_char.is_alphabetic())
}

View File

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

View File

@ -433,14 +433,9 @@ fn line_too_long(attempting: Attempting, state: State<'_>) -> (Fail, State<'_>)
}
/// A single ASCII char.
pub fn ascii_char<'a>(expected: char) -> impl Parser<'a, ()> {
// Make sure this really is an ASCII char!
debug_assert!(expected.len_utf8() == 1);
pub fn ascii_char<'a>(expected: u8) -> impl Parser<'a, ()> {
move |_arena, state: State<'a>| match state.bytes.first() {
Some(&actual) if expected == actual as char => {
Ok(((), state.advance_without_indenting(1)?))
}
Some(&actual) if expected == actual => Ok(((), state.advance_without_indenting(1)?)),
Some(_) => Err(unexpected(0, state, Attempting::Keyword)),
_ => 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
// support empty literals containing newlines or comments, but this
// 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!(
$crate::parser::sep_by0(
$delimiter,
@ -1025,8 +1020,8 @@ macro_rules! record_field {
// Having a value is optional; both `{ email }` and `{ email: blah }` work.
// (This is true in both literals and types.)
let (opt_loc_val, state) = $crate::parser::optional(either!(
skip_first!(ascii_char(':'), 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)),
skip_first!(ascii_char(b'?'), space0_before($val_parser, $min_indent))
))
.parse(arena, state)?;
@ -1055,10 +1050,10 @@ macro_rules! record_field {
macro_rules! record_without_update {
($val_parser:expr, $min_indent:expr) => {
collection!(
ascii_char('{'),
ascii_char(b'{'),
loc!(record_field!($val_parser, $min_indent)),
ascii_char(','),
ascii_char('}'),
ascii_char(b','),
ascii_char(b'}'),
$min_indent
)
};
@ -1068,7 +1063,7 @@ macro_rules! record_without_update {
macro_rules! record {
($val_parser:expr, $min_indent:expr) => {
skip_first!(
$crate::parser::ascii_char('{'),
$crate::parser::ascii_char(b'{'),
and!(
// You can optionally have an identifier followed by an '&' to
// make this a record update, e.g. { Foo.user & username: "blah" }.
@ -1084,7 +1079,7 @@ macro_rules! record {
)),
$min_indent
),
$crate::parser::ascii_char('&')
$crate::parser::ascii_char(b'&')
)),
loc!(skip_first!(
// 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
// support empty literals containing newlines or comments, but this
// 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!(
$crate::parser::sep_by0(
$crate::parser::ascii_char(','),
$crate::parser::ascii_char(b','),
$crate::blankspace::space0_around(
loc!(record_field!($val_parser, $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
// is not allowed inside a string interpolation.
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)?;
// 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
// give a canonicalization error if the digits form
// an invalid unicode code point.
let (loc_digits, new_state) =
between!(ascii_char('('), loc(ascii_hex_digits()), ascii_char(')'))
.parse(arena, state)?;
let (loc_digits, new_state) = between!(
ascii_char(b'('),
loc(ascii_hex_digits()),
ascii_char(b')')
)
.parse(arena, state)?;
// Advance the iterator past the expr we just parsed.
for _ in 0..(original_byte_count - new_state.bytes.len()) {

View File

@ -22,10 +22,10 @@ macro_rules! tag_union {
map!(
and!(
collection!(
ascii_char('['),
ascii_char(b'['),
loc!(tag_type($min_indent)),
ascii_char(','),
ascii_char(']'),
ascii_char(b','),
ascii_char(b']'),
$min_indent
),
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,
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)
})
}
@ -112,12 +112,12 @@ pub fn loc_applied_arg<'a>(min_indent: u16) -> impl Parser<'a, Located<TypeAnnot
#[inline(always)]
fn loc_parenthetical_type<'a>(min_indent: u16) -> impl Parser<'a, Located<TypeAnnotation<'a>>> {
between!(
ascii_char('('),
ascii_char(b'('),
space0_around(
move |arena, state| expression(min_indent).parse(arena, state),
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>| {
let (first, state) = space0_before(term(min_indent), min_indent).parse(arena, state)?;
let (rest, state) = zero_or_more!(skip_first!(
ascii_char(','),
ascii_char(b','),
space0_around(term(min_indent), min_indent)
))
.parse(arena, state)?;

View File

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

View File

@ -777,6 +777,16 @@ fn to_expr_report<'b>(
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 => {
unreachable!("I don't think these can be reached")
}
@ -954,6 +964,9 @@ fn add_category<'b>(
op
);
}
ForeignCall => {
panic!("Compiler bug: invalid return type from foreign call",);
}
Uniqueness => alloc.concat(vec![
this_is,

View File

@ -5,6 +5,7 @@ use roc_collections::all::{ImMap, MutSet, SendMap};
use roc_module::ident::{Lowercase, TagName};
use roc_module::symbol::Symbol;
use roc_region::all::{Located, Region};
use std::hash::{Hash, Hasher};
/// A marker that a given Subs has been solved.
/// 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.
#[derive(Debug, Clone, PartialEq, Eq, Hash)]
#[derive(Debug, Clone, Eq)]
pub enum SolvedType {
/// A function. The types of its arguments, then the type of its return value.
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 inlinable_string::InlinableString;
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::symbol::{Interns, ModuleId, Symbol};
use roc_region::all::{Located, Region};
@ -966,6 +966,10 @@ pub enum Reason {
op: LowLevel,
arg_index: Index,
},
ForeignCallArg {
foreign_symbol: ForeignSymbol,
arg_index: Index,
},
FloatLiteral,
IntLiteral,
NumLiteral,
@ -992,6 +996,7 @@ pub enum Category {
Lookup(Symbol),
CallResult(Option<Symbol>),
LowLevelOpResult(LowLevel),
ForeignCall,
TagApply {
tag_name: TagName,
args_count: usize,

View File

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

View File

@ -266,8 +266,13 @@ mod test_docs {
},
ModuleEntry {
name: "multiline".to_string(),
docs: "<p>Multiline documentation.\nWithout any complex syntax yet!</p>\n"
.to_string(),
docs: "<p>Multiline documentation.\nWithout any complex syntax yet!</p>\n".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
exposes [ singleline, multiline ]
exposes [ singleline, multiline, multiparagraph, codeblock ]
imports []
## Single line documentation.
@ -9,5 +9,15 @@ singleline : Bool -> Bool
## Without any complex syntax yet!
multiline : Bool -> Bool
## Multiparagraph documentation.
##
## Without any complex syntax yet!
multiparagraph : Bool -> Bool
## No documentation for not exposed function.
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
provides [ quicksort ]
imports [ Utils.{swap} ]
app Quicksort provides [ quicksort ] imports [ Utils.{swap} ]
quicksort : List Int -> List Int