mirror of
https://github.com/roc-lang/roc.git
synced 2025-09-29 06:44:46 +00:00
Merge pull request #678 from rtfeldman/record-bool-tests
More REPL Improvements
This commit is contained in:
commit
d21ffb7eee
5 changed files with 472 additions and 212 deletions
|
@ -6,7 +6,7 @@ use roc_gen::{run_jit_function, run_jit_function_dynamic_type};
|
||||||
use roc_module::ident::{Lowercase, TagName};
|
use roc_module::ident::{Lowercase, TagName};
|
||||||
use roc_module::operator::CalledVia;
|
use roc_module::operator::CalledVia;
|
||||||
use roc_module::symbol::{Interns, ModuleId, Symbol};
|
use roc_module::symbol::{Interns, ModuleId, Symbol};
|
||||||
use roc_mono::layout::{Builtin, Layout};
|
use roc_mono::layout::{union_sorted_tags_help, Builtin, Layout, UnionVariant};
|
||||||
use roc_parse::ast::{AssignedField, Expr, StrLiteral};
|
use roc_parse::ast::{AssignedField, Expr, StrLiteral};
|
||||||
use roc_region::all::{Located, Region};
|
use roc_region::all::{Located, Region};
|
||||||
use roc_types::subs::{Content, FlatType, Subs, Variable};
|
use roc_types::subs::{Content, FlatType, Subs, Variable};
|
||||||
|
@ -64,6 +64,10 @@ fn jit_to_ast_help<'a>(
|
||||||
env, num, content
|
env, num, content
|
||||||
))
|
))
|
||||||
}
|
}
|
||||||
|
Layout::Builtin(Builtin::Int8) => {
|
||||||
|
// NOTE: this is does not handle 8-bit numbers yet
|
||||||
|
run_jit_function!(lib, main_fn_name, u8, |num| byte_to_ast(env, num, content))
|
||||||
|
}
|
||||||
Layout::Builtin(Builtin::Int64) => {
|
Layout::Builtin(Builtin::Int64) => {
|
||||||
run_jit_function!(lib, main_fn_name, i64, |num| num_to_ast(
|
run_jit_function!(lib, main_fn_name, i64, |num| num_to_ast(
|
||||||
env,
|
env,
|
||||||
|
@ -89,19 +93,20 @@ fn jit_to_ast_help<'a>(
|
||||||
Layout::Builtin(Builtin::List(_, elem_layout)) => run_jit_function!(
|
Layout::Builtin(Builtin::List(_, elem_layout)) => run_jit_function!(
|
||||||
lib,
|
lib,
|
||||||
main_fn_name,
|
main_fn_name,
|
||||||
(*const libc::c_void, usize),
|
(*const u8, usize),
|
||||||
|(ptr, len): (*const libc::c_void, usize)| {
|
|(ptr, len): (*const u8, usize)| { list_to_ast(env, ptr, len, elem_layout, content) }
|
||||||
list_to_ast(env, ptr, len, elem_layout, content)
|
|
||||||
}
|
|
||||||
),
|
),
|
||||||
Layout::PhantomEmptyStruct => run_jit_function!(lib, main_fn_name, &'static str, |_| {
|
Layout::Builtin(other) => {
|
||||||
|
todo!("add support for rendering builtin {:?} to the REPL", other)
|
||||||
|
}
|
||||||
|
Layout::PhantomEmptyStruct => run_jit_function!(lib, main_fn_name, &u8, |_| {
|
||||||
Expr::Record {
|
Expr::Record {
|
||||||
update: None,
|
update: None,
|
||||||
fields: &[],
|
fields: &[],
|
||||||
}
|
}
|
||||||
}),
|
}),
|
||||||
Layout::Struct(field_layouts) => {
|
Layout::Struct(field_layouts) => {
|
||||||
let ptr_to_ast = |ptr: *const libc::c_void| match content {
|
let ptr_to_ast = |ptr: *const u8| match content {
|
||||||
Content::Structure(FlatType::Record(fields, _)) => {
|
Content::Structure(FlatType::Record(fields, _)) => {
|
||||||
struct_to_ast(env, ptr, field_layouts, fields)
|
struct_to_ast(env, ptr, field_layouts, fields)
|
||||||
}
|
}
|
||||||
|
@ -113,13 +118,7 @@ fn jit_to_ast_help<'a>(
|
||||||
|
|
||||||
let (tag_name, payload_vars) = tags.iter().next().unwrap();
|
let (tag_name, payload_vars) = tags.iter().next().unwrap();
|
||||||
|
|
||||||
// We expect anything with payload vars
|
single_tag_union_to_ast(env, ptr, field_layouts, tag_name.clone(), payload_vars)
|
||||||
// that is a single Tag TagUnion
|
|
||||||
// has a Record content so the above case
|
|
||||||
// should match instead
|
|
||||||
debug_assert_eq!(payload_vars.len(), 0);
|
|
||||||
|
|
||||||
single_tag_union_to_ast(env, field_layouts, tag_name.clone(), payload_vars)
|
|
||||||
}
|
}
|
||||||
other => {
|
other => {
|
||||||
unreachable!(
|
unreachable!(
|
||||||
|
@ -132,76 +131,93 @@ fn jit_to_ast_help<'a>(
|
||||||
let fields = [Layout::Builtin(Builtin::Int64), layout.clone()];
|
let fields = [Layout::Builtin(Builtin::Int64), layout.clone()];
|
||||||
let layout = Layout::Struct(&fields);
|
let layout = Layout::Struct(&fields);
|
||||||
|
|
||||||
match env.ptr_bytes {
|
let result_stack_size = layout.stack_size(env.ptr_bytes);
|
||||||
// 64-bit target (8-byte pointers, 16-byte structs)
|
|
||||||
8 => match layout.stack_size(env.ptr_bytes) {
|
|
||||||
8 => {
|
|
||||||
// just one eightbyte, returned as-is
|
|
||||||
run_jit_function!(lib, main_fn_name, [u8; 8], |bytes: [u8; 8]| {
|
|
||||||
ptr_to_ast((&bytes).as_ptr() as *const libc::c_void)
|
|
||||||
})
|
|
||||||
}
|
|
||||||
16 => {
|
|
||||||
// two eightbytes, returned as-is
|
|
||||||
run_jit_function!(lib, main_fn_name, [u8; 16], |bytes: [u8; 16]| {
|
|
||||||
ptr_to_ast((&bytes).as_ptr() as *const libc::c_void)
|
|
||||||
})
|
|
||||||
}
|
|
||||||
larger_size => {
|
|
||||||
// anything more than 2 eightbytes
|
|
||||||
// the return "value" is a pointer to the result
|
|
||||||
run_jit_function_dynamic_type!(
|
run_jit_function_dynamic_type!(
|
||||||
lib,
|
lib,
|
||||||
main_fn_name,
|
main_fn_name,
|
||||||
larger_size as usize,
|
result_stack_size as usize,
|
||||||
|bytes: *const u8| { ptr_to_ast(bytes as *const libc::c_void) }
|
|bytes: *const u8| { ptr_to_ast(bytes as *const u8) }
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
Layout::Union(union_layouts) => match content {
|
||||||
|
Content::Structure(FlatType::TagUnion(tags, _)) => {
|
||||||
|
debug_assert_eq!(union_layouts.len(), tags.len());
|
||||||
|
|
||||||
|
let tags_vec: std::vec::Vec<(TagName, std::vec::Vec<Variable>)> =
|
||||||
|
tags.iter().map(|(a, b)| (a.clone(), b.clone())).collect();
|
||||||
|
|
||||||
|
let union_variant = union_sorted_tags_help(env.arena, tags_vec, None, env.subs);
|
||||||
|
|
||||||
|
let size = layout.stack_size(env.ptr_bytes);
|
||||||
|
match union_variant {
|
||||||
|
UnionVariant::Wrapped(tags_and_layouts) => {
|
||||||
|
run_jit_function_dynamic_type!(
|
||||||
|
lib,
|
||||||
|
main_fn_name,
|
||||||
|
size as usize,
|
||||||
|
|ptr: *const u8| {
|
||||||
|
// Because this is a `Wrapped`, the first 8 bytes encode the tag ID
|
||||||
|
let tag_id = *(ptr as *const i64);
|
||||||
|
|
||||||
|
// use the tag ID as an index, to get its name and layout of any arguments
|
||||||
|
let (tag_name, arg_layouts) = &tags_and_layouts[tag_id as usize];
|
||||||
|
|
||||||
|
let tag_expr = tag_name_to_expr(env, tag_name);
|
||||||
|
let loc_tag_expr = &*env.arena.alloc(Located::at_zero(tag_expr));
|
||||||
|
|
||||||
|
let variables = &tags[tag_name];
|
||||||
|
|
||||||
|
// because the arg_layouts include the tag ID, it is one longer
|
||||||
|
debug_assert_eq!(arg_layouts.len() - 1, variables.len());
|
||||||
|
|
||||||
|
// skip forward to the start of the first element, ignoring the tag id
|
||||||
|
let ptr = ptr.offset(8);
|
||||||
|
|
||||||
|
let it = variables.iter().copied().zip(&arg_layouts[1..]);
|
||||||
|
let output = sequence_of_expr(env, ptr, it);
|
||||||
|
let output = output.into_bump_slice();
|
||||||
|
|
||||||
|
Expr::Apply(loc_tag_expr, output, CalledVia::Space)
|
||||||
|
}
|
||||||
|
)
|
||||||
|
}
|
||||||
|
_ => unreachable!("any other variant would have a different layout"),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
Content::Structure(FlatType::RecursiveTagUnion(_, _, _)) => {
|
||||||
|
todo!("print recursive tag unions in the REPL")
|
||||||
|
}
|
||||||
|
other => unreachable!("Weird content for Union layout: {:?}", other),
|
||||||
},
|
},
|
||||||
// 32-bit target (4-byte pointers, 8-byte structs)
|
Layout::RecursiveUnion(_) | Layout::RecursivePointer => {
|
||||||
4 => {
|
todo!("add support for rendering recursive tag unions in the REPL")
|
||||||
// TODO what are valid return sizes here?
|
|
||||||
// this is just extrapolated from the 64-bit case above
|
|
||||||
// and not (yet) actually tested on a 32-bit system
|
|
||||||
match layout.stack_size(env.ptr_bytes) {
|
|
||||||
4 => {
|
|
||||||
// just one fourbyte, returned as-is
|
|
||||||
run_jit_function!(lib, main_fn_name, [u8; 4], |bytes: [u8; 4]| {
|
|
||||||
ptr_to_ast((&bytes).as_ptr() as *const libc::c_void)
|
|
||||||
})
|
|
||||||
}
|
}
|
||||||
8 => {
|
|
||||||
// just one fourbyte, returned as-is
|
Layout::FunctionPointer(_, _) | Layout::Closure(_, _, _) => {
|
||||||
run_jit_function!(lib, main_fn_name, [u8; 8], |bytes: [u8; 8]| {
|
todo!("add support for rendering functions in the REPL")
|
||||||
ptr_to_ast((&bytes).as_ptr() as *const libc::c_void)
|
|
||||||
})
|
|
||||||
}
|
}
|
||||||
larger_size => {
|
Layout::Pointer(_) => todo!("add support for rendering pointers in the REPL"),
|
||||||
// anything more than 2 fourbytes
|
|
||||||
// the return "value" is a pointer to the result
|
|
||||||
run_jit_function_dynamic_type!(
|
|
||||||
lib,
|
|
||||||
main_fn_name,
|
|
||||||
larger_size as usize,
|
|
||||||
|bytes: *const u8| { ptr_to_ast(bytes as *const libc::c_void) }
|
|
||||||
)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
other => {
|
|
||||||
panic!("Unsupported target: Roc cannot currently compile to systems where pointers are {} bytes in length.", other);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
other => {
|
|
||||||
todo!("TODO add support for rendering {:?} in the REPL", other);
|
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn tag_name_to_expr<'a>(env: &Env<'a, '_>, tag_name: &TagName) -> Expr<'a> {
|
||||||
|
match tag_name {
|
||||||
|
TagName::Global(_) => Expr::GlobalTag(
|
||||||
|
env.arena
|
||||||
|
.alloc_str(&tag_name.as_string(env.interns, env.home)),
|
||||||
|
),
|
||||||
|
TagName::Private(_) => Expr::PrivateTag(
|
||||||
|
env.arena
|
||||||
|
.alloc_str(&tag_name.as_string(env.interns, env.home)),
|
||||||
|
),
|
||||||
|
TagName::Closure(_) => unreachable!("User cannot type this"),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
fn ptr_to_ast<'a>(
|
fn ptr_to_ast<'a>(
|
||||||
env: &Env<'a, '_>,
|
env: &Env<'a, '_>,
|
||||||
ptr: *const libc::c_void,
|
ptr: *const u8,
|
||||||
layout: &Layout<'a>,
|
layout: &Layout<'a>,
|
||||||
content: &Content,
|
content: &Content,
|
||||||
) -> Expr<'a> {
|
) -> Expr<'a> {
|
||||||
|
@ -227,7 +243,7 @@ fn ptr_to_ast<'a>(
|
||||||
Layout::Builtin(Builtin::List(_, elem_layout)) => {
|
Layout::Builtin(Builtin::List(_, elem_layout)) => {
|
||||||
// Turn the (ptr, len) wrapper struct into actual ptr and len values.
|
// Turn the (ptr, len) wrapper struct into actual ptr and len values.
|
||||||
let len = unsafe { *(ptr.offset(env.ptr_bytes as isize) as *const usize) };
|
let len = unsafe { *(ptr.offset(env.ptr_bytes as isize) as *const usize) };
|
||||||
let ptr = unsafe { *(ptr as *const *const libc::c_void) };
|
let ptr = unsafe { *(ptr as *const *const u8) };
|
||||||
|
|
||||||
list_to_ast(env, ptr, len, elem_layout, content)
|
list_to_ast(env, ptr, len, elem_layout, content)
|
||||||
}
|
}
|
||||||
|
@ -241,6 +257,12 @@ fn ptr_to_ast<'a>(
|
||||||
Content::Structure(FlatType::Record(fields, _)) => {
|
Content::Structure(FlatType::Record(fields, _)) => {
|
||||||
struct_to_ast(env, ptr, field_layouts, fields)
|
struct_to_ast(env, ptr, field_layouts, fields)
|
||||||
}
|
}
|
||||||
|
Content::Structure(FlatType::TagUnion(tags, _)) => {
|
||||||
|
debug_assert_eq!(tags.len(), 1);
|
||||||
|
|
||||||
|
let (tag_name, payload_vars) = tags.iter().next().unwrap();
|
||||||
|
single_tag_union_to_ast(env, ptr, field_layouts, tag_name.clone(), payload_vars)
|
||||||
|
}
|
||||||
other => {
|
other => {
|
||||||
unreachable!(
|
unreachable!(
|
||||||
"Something had a Struct layout, but instead of a Record type, it had: {:?}",
|
"Something had a Struct layout, but instead of a Record type, it had: {:?}",
|
||||||
|
@ -259,7 +281,7 @@ fn ptr_to_ast<'a>(
|
||||||
|
|
||||||
fn list_to_ast<'a>(
|
fn list_to_ast<'a>(
|
||||||
env: &Env<'a, '_>,
|
env: &Env<'a, '_>,
|
||||||
ptr: *const libc::c_void,
|
ptr: *const u8,
|
||||||
len: usize,
|
len: usize,
|
||||||
elem_layout: &Layout<'a>,
|
elem_layout: &Layout<'a>,
|
||||||
content: &Content,
|
content: &Content,
|
||||||
|
@ -282,11 +304,11 @@ fn list_to_ast<'a>(
|
||||||
|
|
||||||
let arena = env.arena;
|
let arena = env.arena;
|
||||||
let mut output = Vec::with_capacity_in(len, &arena);
|
let mut output = Vec::with_capacity_in(len, &arena);
|
||||||
let elem_size = elem_layout.stack_size(env.ptr_bytes);
|
let elem_size = elem_layout.stack_size(env.ptr_bytes) as usize;
|
||||||
|
|
||||||
for index in 0..(len as isize) {
|
for index in 0..len {
|
||||||
let offset_bytes: isize = index * elem_size as isize;
|
let offset_bytes = index * elem_size;
|
||||||
let elem_ptr = unsafe { ptr.offset(offset_bytes) };
|
let elem_ptr = unsafe { ptr.add(offset_bytes) };
|
||||||
let loc_expr = &*arena.alloc(Located {
|
let loc_expr = &*arena.alloc(Located {
|
||||||
value: ptr_to_ast(env, elem_ptr, elem_layout, &elem_content),
|
value: ptr_to_ast(env, elem_ptr, elem_layout, &elem_content),
|
||||||
region: Region::zero(),
|
region: Region::zero(),
|
||||||
|
@ -302,7 +324,8 @@ fn list_to_ast<'a>(
|
||||||
|
|
||||||
fn single_tag_union_to_ast<'a>(
|
fn single_tag_union_to_ast<'a>(
|
||||||
env: &Env<'a, '_>,
|
env: &Env<'a, '_>,
|
||||||
field_layouts: &[Layout<'a>],
|
ptr: *const u8,
|
||||||
|
field_layouts: &'a [Layout<'a>],
|
||||||
tag_name: TagName,
|
tag_name: TagName,
|
||||||
payload_vars: &[Variable],
|
payload_vars: &[Variable],
|
||||||
) -> Expr<'a> {
|
) -> Expr<'a> {
|
||||||
|
@ -310,28 +333,50 @@ fn single_tag_union_to_ast<'a>(
|
||||||
|
|
||||||
let arena = env.arena;
|
let arena = env.arena;
|
||||||
|
|
||||||
let tag_expr = match tag_name {
|
let tag_expr = tag_name_to_expr(env, &tag_name);
|
||||||
TagName::Global(_) => {
|
|
||||||
Expr::GlobalTag(arena.alloc_str(&tag_name.as_string(env.interns, env.home)))
|
|
||||||
}
|
|
||||||
TagName::Private(_) => {
|
|
||||||
Expr::PrivateTag(arena.alloc_str(&tag_name.as_string(env.interns, env.home)))
|
|
||||||
}
|
|
||||||
TagName::Closure(_) => unreachable!("User cannot type this"),
|
|
||||||
};
|
|
||||||
|
|
||||||
let loc_tag_expr = &*arena.alloc(Located {
|
let loc_tag_expr = &*arena.alloc(Located::at_zero(tag_expr));
|
||||||
value: tag_expr,
|
|
||||||
region: Region::zero(),
|
|
||||||
});
|
|
||||||
|
|
||||||
Expr::Apply(loc_tag_expr, &[], CalledVia::Space)
|
let it = payload_vars.iter().copied().zip(field_layouts);
|
||||||
|
let output = sequence_of_expr(env, ptr as *const u8, it).into_bump_slice();
|
||||||
|
|
||||||
|
Expr::Apply(loc_tag_expr, output, CalledVia::Space)
|
||||||
|
}
|
||||||
|
|
||||||
|
fn sequence_of_expr<'a, I>(
|
||||||
|
env: &Env<'a, '_>,
|
||||||
|
ptr: *const u8,
|
||||||
|
sequence: I,
|
||||||
|
) -> Vec<'a, &'a Located<Expr<'a>>>
|
||||||
|
where
|
||||||
|
I: Iterator<Item = (Variable, &'a Layout<'a>)>,
|
||||||
|
I: ExactSizeIterator<Item = (Variable, &'a Layout<'a>)>,
|
||||||
|
{
|
||||||
|
let arena = env.arena;
|
||||||
|
let subs = env.subs;
|
||||||
|
let mut output = Vec::with_capacity_in(sequence.len(), &arena);
|
||||||
|
|
||||||
|
// We'll advance this as we iterate through the fields
|
||||||
|
let mut field_ptr = ptr as *const u8;
|
||||||
|
|
||||||
|
for (var, layout) in sequence {
|
||||||
|
let content = subs.get_without_compacting(var).content;
|
||||||
|
let expr = ptr_to_ast(env, field_ptr, layout, &content);
|
||||||
|
let loc_expr = Located::at_zero(expr);
|
||||||
|
|
||||||
|
output.push(&*arena.alloc(loc_expr));
|
||||||
|
|
||||||
|
// Advance the field pointer to the next field.
|
||||||
|
field_ptr = unsafe { field_ptr.offset(layout.stack_size(env.ptr_bytes) as isize) };
|
||||||
|
}
|
||||||
|
|
||||||
|
output
|
||||||
}
|
}
|
||||||
|
|
||||||
fn struct_to_ast<'a>(
|
fn struct_to_ast<'a>(
|
||||||
env: &Env<'a, '_>,
|
env: &Env<'a, '_>,
|
||||||
ptr: *const libc::c_void,
|
ptr: *const u8,
|
||||||
field_layouts: &[Layout<'a>],
|
field_layouts: &'a [Layout<'a>],
|
||||||
fields: &MutMap<Lowercase, RecordField<Variable>>,
|
fields: &MutMap<Lowercase, RecordField<Variable>>,
|
||||||
) -> Expr<'a> {
|
) -> Expr<'a> {
|
||||||
let arena = env.arena;
|
let arena = env.arena;
|
||||||
|
@ -339,7 +384,7 @@ fn struct_to_ast<'a>(
|
||||||
let mut output = Vec::with_capacity_in(field_layouts.len(), &arena);
|
let mut output = Vec::with_capacity_in(field_layouts.len(), &arena);
|
||||||
|
|
||||||
// The fields, sorted alphabetically
|
// The fields, sorted alphabetically
|
||||||
let sorted_fields = {
|
let mut sorted_fields = {
|
||||||
let mut vec = fields
|
let mut vec = fields
|
||||||
.iter()
|
.iter()
|
||||||
.collect::<std::vec::Vec<(&Lowercase, &RecordField<Variable>)>>();
|
.collect::<std::vec::Vec<(&Lowercase, &RecordField<Variable>)>>();
|
||||||
|
@ -349,6 +394,33 @@ fn struct_to_ast<'a>(
|
||||||
vec
|
vec
|
||||||
};
|
};
|
||||||
|
|
||||||
|
if sorted_fields.len() == 1 {
|
||||||
|
// this is a 1-field wrapper record around another record or 1-tag tag union
|
||||||
|
let (label, field) = sorted_fields.pop().unwrap();
|
||||||
|
|
||||||
|
let inner_content = env.subs.get_without_compacting(field.into_inner()).content;
|
||||||
|
|
||||||
|
let loc_expr = &*arena.alloc(Located {
|
||||||
|
value: ptr_to_ast(env, ptr, &Layout::Struct(field_layouts), &inner_content),
|
||||||
|
region: Region::zero(),
|
||||||
|
});
|
||||||
|
|
||||||
|
let field_name = Located {
|
||||||
|
value: &*arena.alloc_str(label.as_str()),
|
||||||
|
region: Region::zero(),
|
||||||
|
};
|
||||||
|
let loc_field = Located {
|
||||||
|
value: AssignedField::RequiredValue(field_name, &[], loc_expr),
|
||||||
|
region: Region::zero(),
|
||||||
|
};
|
||||||
|
|
||||||
|
let output = env.arena.alloc([loc_field]);
|
||||||
|
|
||||||
|
Expr::Record {
|
||||||
|
update: None,
|
||||||
|
fields: output,
|
||||||
|
}
|
||||||
|
} else {
|
||||||
debug_assert_eq!(sorted_fields.len(), field_layouts.len());
|
debug_assert_eq!(sorted_fields.len(), field_layouts.len());
|
||||||
|
|
||||||
// We'll advance this as we iterate through the fields
|
// We'll advance this as we iterate through the fields
|
||||||
|
@ -373,7 +445,8 @@ fn struct_to_ast<'a>(
|
||||||
output.push(loc_field);
|
output.push(loc_field);
|
||||||
|
|
||||||
// Advance the field pointer to the next field.
|
// Advance the field pointer to the next field.
|
||||||
field_ptr = unsafe { field_ptr.offset(field_layout.stack_size(env.ptr_bytes) as isize) };
|
field_ptr =
|
||||||
|
unsafe { field_ptr.offset(field_layout.stack_size(env.ptr_bytes) as isize) };
|
||||||
}
|
}
|
||||||
|
|
||||||
let output = output.into_bump_slice();
|
let output = output.into_bump_slice();
|
||||||
|
@ -382,6 +455,7 @@ fn struct_to_ast<'a>(
|
||||||
update: None,
|
update: None,
|
||||||
fields: output,
|
fields: output,
|
||||||
}
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
fn bool_to_ast<'a>(env: &Env<'a, '_>, value: bool, content: &Content) -> Expr<'a> {
|
fn bool_to_ast<'a>(env: &Env<'a, '_>, value: bool, content: &Content) -> Expr<'a> {
|
||||||
|
@ -468,20 +542,17 @@ fn bool_to_ast<'a>(env: &Env<'a, '_>, value: bool, content: &Content) -> Expr<'a
|
||||||
debug_assert!(payload_vars_1.is_empty());
|
debug_assert!(payload_vars_1.is_empty());
|
||||||
debug_assert!(payload_vars_2.is_empty());
|
debug_assert!(payload_vars_2.is_empty());
|
||||||
|
|
||||||
let tag_name_as_str_1 = &tag_name_1.as_string(env.interns, env.home);
|
|
||||||
let tag_name_as_str_2 = &tag_name_2.as_string(env.interns, env.home);
|
|
||||||
|
|
||||||
let tag_name = if value {
|
let tag_name = if value {
|
||||||
tag_name_as_str_1.max(tag_name_as_str_2)
|
max_by_key(tag_name_1, tag_name_2, |n| {
|
||||||
|
n.as_string(env.interns, env.home)
|
||||||
|
})
|
||||||
} else {
|
} else {
|
||||||
tag_name_as_str_1.min(tag_name_as_str_2)
|
min_by_key(tag_name_1, tag_name_2, |n| {
|
||||||
|
n.as_string(env.interns, env.home)
|
||||||
|
})
|
||||||
};
|
};
|
||||||
|
|
||||||
if tag_name.starts_with('@') {
|
tag_name_to_expr(env, tag_name)
|
||||||
Expr::PrivateTag(arena.alloc_str(tag_name))
|
|
||||||
} else {
|
|
||||||
Expr::GlobalTag(arena.alloc_str(tag_name))
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
other => {
|
other => {
|
||||||
unreachable!("Unexpected FlatType {:?} in bool_to_ast", other);
|
unreachable!("Unexpected FlatType {:?} in bool_to_ast", other);
|
||||||
|
@ -499,6 +570,117 @@ fn bool_to_ast<'a>(env: &Env<'a, '_>, value: bool, content: &Content) -> Expr<'a
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fn byte_to_ast<'a>(env: &Env<'a, '_>, value: u8, content: &Content) -> Expr<'a> {
|
||||||
|
use Content::*;
|
||||||
|
|
||||||
|
let arena = env.arena;
|
||||||
|
|
||||||
|
match content {
|
||||||
|
Structure(flat_type) => {
|
||||||
|
match flat_type {
|
||||||
|
FlatType::Record(fields, _) => {
|
||||||
|
debug_assert_eq!(fields.len(), 1);
|
||||||
|
|
||||||
|
let (label, field) = fields.iter().next().unwrap();
|
||||||
|
let loc_label = Located {
|
||||||
|
value: &*arena.alloc_str(label.as_str()),
|
||||||
|
region: Region::zero(),
|
||||||
|
};
|
||||||
|
|
||||||
|
let assigned_field = {
|
||||||
|
// We may be multiple levels deep in nested tag unions
|
||||||
|
// and/or records (e.g. { a: { b: { c: True } } }),
|
||||||
|
// so we need to do this recursively on the field type.
|
||||||
|
let field_var = *field.as_inner();
|
||||||
|
let field_content = env.subs.get_without_compacting(field_var).content;
|
||||||
|
let loc_expr = Located {
|
||||||
|
value: byte_to_ast(env, value, &field_content),
|
||||||
|
region: Region::zero(),
|
||||||
|
};
|
||||||
|
|
||||||
|
AssignedField::RequiredValue(loc_label, &[], arena.alloc(loc_expr))
|
||||||
|
};
|
||||||
|
|
||||||
|
let loc_assigned_field = Located {
|
||||||
|
value: assigned_field,
|
||||||
|
region: Region::zero(),
|
||||||
|
};
|
||||||
|
|
||||||
|
Expr::Record {
|
||||||
|
update: None,
|
||||||
|
fields: arena.alloc([loc_assigned_field]),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
FlatType::TagUnion(tags, _) if tags.len() == 1 => {
|
||||||
|
let (tag_name, payload_vars) = tags.iter().next().unwrap();
|
||||||
|
|
||||||
|
let loc_tag_expr = {
|
||||||
|
let tag_name = &tag_name.as_string(env.interns, env.home);
|
||||||
|
let tag_expr = if tag_name.starts_with('@') {
|
||||||
|
Expr::PrivateTag(arena.alloc_str(tag_name))
|
||||||
|
} else {
|
||||||
|
Expr::GlobalTag(arena.alloc_str(tag_name))
|
||||||
|
};
|
||||||
|
|
||||||
|
&*arena.alloc(Located {
|
||||||
|
value: tag_expr,
|
||||||
|
region: Region::zero(),
|
||||||
|
})
|
||||||
|
};
|
||||||
|
|
||||||
|
let payload = {
|
||||||
|
// Since this has the layout of a number, there should be
|
||||||
|
// exactly one payload in this tag.
|
||||||
|
debug_assert_eq!(payload_vars.len(), 1);
|
||||||
|
|
||||||
|
let var = *payload_vars.iter().next().unwrap();
|
||||||
|
let content = env.subs.get_without_compacting(var).content;
|
||||||
|
|
||||||
|
let loc_payload = &*arena.alloc(Located {
|
||||||
|
value: byte_to_ast(env, value, &content),
|
||||||
|
region: Region::zero(),
|
||||||
|
});
|
||||||
|
|
||||||
|
arena.alloc([loc_payload])
|
||||||
|
};
|
||||||
|
|
||||||
|
Expr::Apply(loc_tag_expr, payload, CalledVia::Space)
|
||||||
|
}
|
||||||
|
FlatType::TagUnion(tags, _) => {
|
||||||
|
// anything with fewer tags is not a byte
|
||||||
|
debug_assert!(tags.len() > 2);
|
||||||
|
|
||||||
|
let tags_vec: std::vec::Vec<(TagName, std::vec::Vec<Variable>)> =
|
||||||
|
tags.iter().map(|(a, b)| (a.clone(), b.clone())).collect();
|
||||||
|
|
||||||
|
let union_variant = union_sorted_tags_help(env.arena, tags_vec, None, env.subs);
|
||||||
|
|
||||||
|
match union_variant {
|
||||||
|
UnionVariant::ByteUnion(tagnames) => {
|
||||||
|
let tag_name = &tagnames[value as usize];
|
||||||
|
let tag_expr = tag_name_to_expr(env, tag_name);
|
||||||
|
let loc_tag_expr = Located::at_zero(tag_expr);
|
||||||
|
Expr::Apply(env.arena.alloc(loc_tag_expr), &[], CalledVia::Space)
|
||||||
|
}
|
||||||
|
_ => unreachable!("invalid union variant for a Byte!"),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
other => {
|
||||||
|
unreachable!("Unexpected FlatType {:?} in bool_to_ast", other);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
Alias(_, _, var) => {
|
||||||
|
let content = env.subs.get_without_compacting(*var).content;
|
||||||
|
|
||||||
|
byte_to_ast(env, value, &content)
|
||||||
|
}
|
||||||
|
other => {
|
||||||
|
unreachable!("Unexpected FlatType {:?} in bool_to_ast", other);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
fn num_to_ast<'a>(env: &Env<'a, '_>, num_expr: Expr<'a>, content: &Content) -> Expr<'a> {
|
fn num_to_ast<'a>(env: &Env<'a, '_>, num_expr: Expr<'a>, content: &Content) -> Expr<'a> {
|
||||||
use Content::*;
|
use Content::*;
|
||||||
|
|
||||||
|
@ -648,3 +830,30 @@ fn str_slice_to_ast<'a>(_arena: &'a Bump, string: &'a str) -> Expr<'a> {
|
||||||
Expr::Str(StrLiteral::PlainLine(string))
|
Expr::Str(StrLiteral::PlainLine(string))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// TODO this is currently nighly-only: use the implementation in std once it's stabilized
|
||||||
|
pub fn max_by<T, F: FnOnce(&T, &T) -> std::cmp::Ordering>(v1: T, v2: T, compare: F) -> T {
|
||||||
|
use std::cmp::Ordering;
|
||||||
|
|
||||||
|
match compare(&v1, &v2) {
|
||||||
|
Ordering::Less | Ordering::Equal => v2,
|
||||||
|
Ordering::Greater => v1,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn min_by<T, F: FnOnce(&T, &T) -> std::cmp::Ordering>(v1: T, v2: T, compare: F) -> T {
|
||||||
|
use std::cmp::Ordering;
|
||||||
|
|
||||||
|
match compare(&v1, &v2) {
|
||||||
|
Ordering::Less | Ordering::Equal => v1,
|
||||||
|
Ordering::Greater => v2,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn max_by_key<T, F: FnMut(&T) -> K, K: Ord>(v1: T, v2: T, mut f: F) -> T {
|
||||||
|
max_by(v1, v2, |v1, v2| f(v1).cmp(&f(v2)))
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn min_by_key<T, F: FnMut(&T) -> K, K: Ord>(v1: T, v2: T, mut f: F) -> T {
|
||||||
|
min_by(v1, v2, |v1, v2| f(v1).cmp(&f(v2)))
|
||||||
|
}
|
||||||
|
|
|
@ -90,12 +90,15 @@ mod repl_eval {
|
||||||
#[test]
|
#[test]
|
||||||
fn bool_in_record() {
|
fn bool_in_record() {
|
||||||
expect_success("{ x: 1 == 1 }", "{ x: True } : { x : Bool }");
|
expect_success("{ x: 1 == 1 }", "{ x: True } : { x : Bool }");
|
||||||
|
expect_success(
|
||||||
|
"{ z: { y: { x: 1 == 1 } } }",
|
||||||
|
"{ z: { y: { x: True } } } : { z : { y : { x : Bool } } }",
|
||||||
|
);
|
||||||
expect_success("{ x: 1 != 1 }", "{ x: False } : { x : Bool }");
|
expect_success("{ x: 1 != 1 }", "{ x: False } : { x : Bool }");
|
||||||
// TODO: see ptr_to_ast
|
expect_success(
|
||||||
// expect_success(
|
"{ x: 1 == 1, y: 1 != 1 }",
|
||||||
// "{ x: 1 == 1, y: 1 != 1 }",
|
"{ x: True, y: False } : { x : Bool, y : Bool }",
|
||||||
// "{ x: True, y: False } : { x : Bool, y : Bool }",
|
);
|
||||||
// );
|
|
||||||
}
|
}
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
|
@ -116,14 +119,46 @@ mod repl_eval {
|
||||||
expect_success("False", "False : [ False ]*");
|
expect_success("False", "False : [ False ]*");
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn byte_tag_union() {
|
||||||
|
expect_success(
|
||||||
|
"if 1 == 1 then Red else if 1 == 1 then Green else Blue",
|
||||||
|
"Red : [ Blue, Green, Red ]*",
|
||||||
|
);
|
||||||
|
|
||||||
|
expect_success(
|
||||||
|
"{ y: { x: if 1 == 1 then Red else if 1 == 1 then Green else Blue } }",
|
||||||
|
"{ y: { x: Red } } : { y : { x : [ Blue, Green, Red ]* } }",
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn tag_in_record() {
|
||||||
|
expect_success(
|
||||||
|
"{ x: Foo 1 2 3, y : 4 }",
|
||||||
|
"{ x: Foo 1 2 3, y: 4 } : { x : [ Foo (Num *) (Num *) (Num *) ]*, y : Num * }",
|
||||||
|
);
|
||||||
|
expect_success(
|
||||||
|
"{ x: Foo 1 2 3 }",
|
||||||
|
"{ x: Foo 1 2 3 } : { x : [ Foo (Num *) (Num *) (Num *) ]* }",
|
||||||
|
);
|
||||||
|
expect_success("{ x: Unit }", "{ x: Unit } : { x : [ Unit ]* }");
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn single_element_tag_union() {
|
||||||
|
expect_success("True 1", "True 1 : [ True (Num *) ]*");
|
||||||
|
expect_success("Foo 1 3.14", "Foo 1 3.14 : [ Foo (Num *) Float ]*");
|
||||||
|
}
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn tag_with_arguments() {
|
fn tag_with_arguments() {
|
||||||
expect_success("True 1", "True 1 : [ True (Num *) ]*");
|
expect_success("True 1", "True 1 : [ True (Num *) ]*");
|
||||||
// TODO handle more situations
|
|
||||||
// expect_success(
|
expect_success(
|
||||||
// "if 1 == 1 then True 1 else False 3.14",
|
"if 1 == 1 then True 3 else False 3.14",
|
||||||
// "True 1 : [ True (Num *), False Float ]*",
|
"True 3 : [ False Float, True (Num *) ]*",
|
||||||
// )
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
|
@ -273,32 +308,29 @@ mod repl_eval {
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
// TODO uncomment this once https://github.com/rtfeldman/roc/issues/295 is done
|
#[test]
|
||||||
// #[test]
|
fn basic_2_field_f64_record() {
|
||||||
// fn basic_2_field_f64_record() {
|
expect_success(
|
||||||
// expect_success(
|
"{ foo: 4.1, bar: 2.3 }",
|
||||||
// "{ foo: 4.1, bar: 2.3 }",
|
"{ bar: 2.3, foo: 4.1 } : { bar : Float, foo : Float }",
|
||||||
// "{ bar: 2.3, foo: 4.1 } : { bar : Float, foo : Float }",
|
);
|
||||||
// );
|
}
|
||||||
// }
|
|
||||||
|
|
||||||
// #[test]
|
#[test]
|
||||||
// fn basic_2_field_mixed_record() {
|
fn basic_2_field_mixed_record() {
|
||||||
// expect_success(
|
expect_success(
|
||||||
// "{ foo: 4.1, bar: 2 }",
|
"{ foo: 4.1, bar: 2 }",
|
||||||
// "{ bar: 2, foo: 4.1 } : { bar : Num *, foo : Float }",
|
"{ bar: 2, foo: 4.1 } : { bar : Num *, foo : Float }",
|
||||||
// );
|
);
|
||||||
// }
|
}
|
||||||
|
|
||||||
// TODO uncomment this once https://github.com/rtfeldman/roc/issues/295 is done
|
#[test]
|
||||||
//
|
fn basic_3_field_record() {
|
||||||
// #[test]
|
expect_success(
|
||||||
// fn basic_3_field_record() {
|
"{ foo: 4.1, bar: 2, baz: 0x5 }",
|
||||||
// expect_success(
|
"{ bar: 2, baz: 5, foo: 4.1 } : { bar : Num *, baz : Int, foo : Float }",
|
||||||
// "{ foo: 4.1, bar: 2, baz: 0x5 }",
|
);
|
||||||
// "{ foo: 4.1, bar: 2, baz: 0x5 } : { foo : Float, bar : Num *, baz : Int }",
|
}
|
||||||
// );
|
|
||||||
// }
|
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn list_of_1_field_records() {
|
fn list_of_1_field_records() {
|
||||||
|
|
|
@ -820,4 +820,28 @@ mod gen_records {
|
||||||
i64
|
i64
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn booleans_in_record() {
|
||||||
|
assert_evals_to!(
|
||||||
|
indoc!("{ x: 1 == 1, y: 1 == 1 }"),
|
||||||
|
(true, true),
|
||||||
|
(bool, bool)
|
||||||
|
);
|
||||||
|
assert_evals_to!(
|
||||||
|
indoc!("{ x: 1 != 1, y: 1 == 1 }"),
|
||||||
|
(false, true),
|
||||||
|
(bool, bool)
|
||||||
|
);
|
||||||
|
assert_evals_to!(
|
||||||
|
indoc!("{ x: 1 == 1, y: 1 != 1 }"),
|
||||||
|
(true, false),
|
||||||
|
(bool, bool)
|
||||||
|
);
|
||||||
|
assert_evals_to!(
|
||||||
|
indoc!("{ x: 1 != 1, y: 1 != 1 }"),
|
||||||
|
(false, false),
|
||||||
|
(bool, bool)
|
||||||
|
);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -936,7 +936,7 @@ fn get_recursion_var(subs: &Subs, var: Variable) -> Option<Variable> {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
fn union_sorted_tags_help<'a>(
|
pub fn union_sorted_tags_help<'a>(
|
||||||
arena: &'a Bump,
|
arena: &'a Bump,
|
||||||
mut tags_vec: std::vec::Vec<(TagName, std::vec::Vec<Variable>)>,
|
mut tags_vec: std::vec::Vec<(TagName, std::vec::Vec<Variable>)>,
|
||||||
opt_rec_var: Option<Variable>,
|
opt_rec_var: Option<Variable>,
|
||||||
|
|
43
shell.nix
43
shell.nix
|
@ -16,8 +16,7 @@ with {
|
||||||
with (pkgs);
|
with (pkgs);
|
||||||
|
|
||||||
let
|
let
|
||||||
darwin-frameworks =
|
darwin-frameworks = if isMacOS then
|
||||||
if isMacOS then
|
|
||||||
with pkgs.darwin.apple_sdk.frameworks; [
|
with pkgs.darwin.apple_sdk.frameworks; [
|
||||||
AppKit
|
AppKit
|
||||||
CoreFoundation
|
CoreFoundation
|
||||||
|
@ -29,12 +28,24 @@ let
|
||||||
]
|
]
|
||||||
else
|
else
|
||||||
[ ];
|
[ ];
|
||||||
|
|
||||||
|
linux-only = if !isMacOS then [
|
||||||
|
vulkan-headers
|
||||||
|
vulkan-loader
|
||||||
|
vulkan-tools
|
||||||
|
vulkan-validation-layers
|
||||||
|
xorg.libX11
|
||||||
|
xorg.libXcursor
|
||||||
|
xorg.libXrandr
|
||||||
|
xorg.libXi
|
||||||
|
] else
|
||||||
|
[ ];
|
||||||
|
|
||||||
llvmPkg = pkgs.llvm_10;
|
llvmPkg = pkgs.llvm_10;
|
||||||
lldPkg = pkgs.lld_10; # this should match llvm's version
|
lldPkg = pkgs.lld_10; # this should match llvm's version
|
||||||
clangPkg = pkgs.clang_10; # this should match llvm's version
|
clangPkg = pkgs.clang_10; # this should match llvm's version
|
||||||
zig = import ./nix/zig.nix { inherit pkgs isMacOS; };
|
zig = import ./nix/zig.nix { inherit pkgs isMacOS; };
|
||||||
inputs =
|
inputs = [
|
||||||
[
|
|
||||||
# build libraries
|
# build libraries
|
||||||
rustc
|
rustc
|
||||||
cargo
|
cargo
|
||||||
|
@ -51,12 +62,7 @@ let
|
||||||
# llb deps
|
# llb deps
|
||||||
libffi
|
libffi
|
||||||
libxml2
|
libxml2
|
||||||
xorg.libX11
|
|
||||||
zlib
|
zlib
|
||||||
vulkan-headers
|
|
||||||
vulkan-loader
|
|
||||||
vulkan-tools
|
|
||||||
vulkan-validation-layers
|
|
||||||
# faster builds - see https://github.com/rtfeldman/roc/blob/trunk/BUILDING_FROM_SOURCE.md#use-lld-for-the-linker
|
# faster builds - see https://github.com/rtfeldman/roc/blob/trunk/BUILDING_FROM_SOURCE.md#use-lld-for-the-linker
|
||||||
lldPkg
|
lldPkg
|
||||||
# dev tools
|
# dev tools
|
||||||
|
@ -64,24 +70,13 @@ let
|
||||||
# (import ./nix/zls.nix { inherit pkgs zig; })
|
# (import ./nix/zls.nix { inherit pkgs zig; })
|
||||||
ccls
|
ccls
|
||||||
];
|
];
|
||||||
|
|
||||||
in mkShell {
|
in mkShell {
|
||||||
buildInputs = inputs ++ darwin-frameworks;
|
buildInputs = inputs ++ darwin-frameworks ++ linux-only;
|
||||||
LLVM_SYS_100_PREFIX = "${llvmPkg}";
|
LLVM_SYS_100_PREFIX = "${llvmPkg}";
|
||||||
|
|
||||||
APPEND_LIBRARY_PATH = stdenv.lib.makeLibraryPath [
|
APPEND_LIBRARY_PATH = stdenv.lib.makeLibraryPath
|
||||||
pkgconfig
|
([ pkgconfig libcxx libcxxabi libunwind ] ++ linux-only);
|
||||||
vulkan-headers
|
|
||||||
vulkan-loader
|
|
||||||
vulkan-tools
|
|
||||||
vulkan-validation-layers
|
|
||||||
xorg.libX11
|
|
||||||
xorg.libXcursor
|
|
||||||
xorg.libXrandr
|
|
||||||
xorg.libXi
|
|
||||||
libcxx
|
|
||||||
libcxxabi
|
|
||||||
libunwind
|
|
||||||
];
|
|
||||||
|
|
||||||
# Aliases don't work cross shell, so we do this
|
# Aliases don't work cross shell, so we do this
|
||||||
shellHook = ''
|
shellHook = ''
|
||||||
|
|
Loading…
Add table
Add a link
Reference in a new issue