mirror of
https://github.com/roc-lang/roc.git
synced 2025-09-26 13:29:12 +00:00
Merge remote-tracking branch 'origin/main' into abilities-syntax
This commit is contained in:
commit
2da41be29f
524 changed files with 47536 additions and 15089 deletions
|
@ -14,8 +14,8 @@ use roc_module::low_level::LowLevel;
|
|||
use roc_module::symbol::Symbol;
|
||||
|
||||
use roc_mono::ir::{
|
||||
Call, CallType, EntryPoint, Expr, HigherOrderLowLevel, HostExposedLayouts, ListLiteralElement,
|
||||
Literal, ModifyRc, OptLevel, Proc, ProcLayout, SingleEntryPoint, Stmt,
|
||||
Call, CallType, EntryPoint, ErasedField, Expr, HigherOrderLowLevel, HostExposedLambdaSet,
|
||||
ListLiteralElement, Literal, ModifyRc, OptLevel, Proc, ProcLayout, SingleEntryPoint, Stmt,
|
||||
};
|
||||
use roc_mono::layout::{
|
||||
Builtin, InLayout, Layout, LayoutInterner, LayoutRepr, Niche, RawFunctionLayout,
|
||||
|
@ -117,7 +117,7 @@ where
|
|||
}
|
||||
|
||||
if debug() {
|
||||
for (i, c) in (format!("{:?}", symbol)).chars().take(25).enumerate() {
|
||||
for (i, c) in (format!("{symbol:?}")).chars().take(25).enumerate() {
|
||||
name_bytes[25 + i] = c as u8;
|
||||
}
|
||||
}
|
||||
|
@ -131,21 +131,23 @@ fn bytes_as_ascii(bytes: &[u8]) -> String {
|
|||
let mut buf = String::new();
|
||||
|
||||
for byte in bytes {
|
||||
write!(buf, "{:02X}", byte).unwrap();
|
||||
write!(buf, "{byte:02X}").unwrap();
|
||||
}
|
||||
|
||||
buf
|
||||
}
|
||||
|
||||
pub fn spec_program<'a, 'r, I>(
|
||||
pub fn spec_program<'a, 'r, I1, I2>(
|
||||
arena: &'a Bump,
|
||||
interner: &'r mut STLayoutInterner<'a>,
|
||||
interner: &'r STLayoutInterner<'a>,
|
||||
opt_level: OptLevel,
|
||||
entry_point: roc_mono::ir::EntryPoint<'a>,
|
||||
procs: I,
|
||||
procs: I1,
|
||||
hels: I2,
|
||||
) -> Result<morphic_lib::Solutions>
|
||||
where
|
||||
I: Iterator<Item = &'r Proc<'a>>,
|
||||
I1: Iterator<Item = &'r Proc<'a>>,
|
||||
I2: Iterator<Item = &'r HostExposedLambdaSet<'a>>,
|
||||
{
|
||||
let main_module = {
|
||||
let mut m = ModDefBuilder::new();
|
||||
|
@ -181,40 +183,38 @@ where
|
|||
|
||||
let mut type_definitions = MutSet::default();
|
||||
let mut host_exposed_functions = Vec::new();
|
||||
let mut erased_functions = Vec::new();
|
||||
|
||||
for hels in hels {
|
||||
match hels.raw_function_layout {
|
||||
RawFunctionLayout::Function(_, _, _) => {
|
||||
let it = hels.proc_layout.arguments.iter().copied();
|
||||
let bytes =
|
||||
func_name_bytes_help(hels.symbol, it, Niche::NONE, hels.proc_layout.result);
|
||||
|
||||
host_exposed_functions.push((bytes, hels.proc_layout.arguments));
|
||||
}
|
||||
RawFunctionLayout::ErasedFunction(..) => {
|
||||
let it = hels.proc_layout.arguments.iter().copied();
|
||||
let bytes =
|
||||
func_name_bytes_help(hels.symbol, it, Niche::NONE, hels.proc_layout.result);
|
||||
|
||||
host_exposed_functions.push((bytes, hels.proc_layout.arguments));
|
||||
}
|
||||
RawFunctionLayout::ZeroArgumentThunk(_) => {
|
||||
let bytes =
|
||||
func_name_bytes_help(hels.symbol, [], Niche::NONE, hels.proc_layout.result);
|
||||
|
||||
host_exposed_functions.push((bytes, hels.proc_layout.arguments));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// all other functions
|
||||
for proc in procs {
|
||||
let bytes = func_name_bytes(proc);
|
||||
let func_name = FuncName(&bytes);
|
||||
|
||||
if let HostExposedLayouts::HostExposed { aliases, .. } = &proc.host_exposed_layouts {
|
||||
for (_, hels) in aliases {
|
||||
match hels.raw_function_layout {
|
||||
RawFunctionLayout::Function(_, _, _) => {
|
||||
let it = hels.proc_layout.arguments.iter().copied();
|
||||
let bytes = func_name_bytes_help(
|
||||
hels.symbol,
|
||||
it,
|
||||
Niche::NONE,
|
||||
hels.proc_layout.result,
|
||||
);
|
||||
|
||||
host_exposed_functions.push((bytes, hels.proc_layout.arguments));
|
||||
}
|
||||
RawFunctionLayout::ZeroArgumentThunk(_) => {
|
||||
let bytes = func_name_bytes_help(
|
||||
hels.symbol,
|
||||
[],
|
||||
Niche::NONE,
|
||||
hels.proc_layout.result,
|
||||
);
|
||||
|
||||
host_exposed_functions.push((bytes, hels.proc_layout.arguments));
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if debug() {
|
||||
eprintln!(
|
||||
"{:?}: {:?} with {:?} args",
|
||||
|
@ -226,6 +226,11 @@ where
|
|||
|
||||
let (spec, type_names) = proc_spec(arena, interner, proc)?;
|
||||
|
||||
if proc.is_erased {
|
||||
let args = &*arena.alloc_slice_fill_iter(proc.args.iter().map(|(lay, _)| *lay));
|
||||
erased_functions.push((bytes, args));
|
||||
}
|
||||
|
||||
type_definitions.extend(type_names);
|
||||
|
||||
m.add_func(func_name, spec)?;
|
||||
|
@ -253,6 +258,7 @@ where
|
|||
entry_point_layout,
|
||||
Some(roc_main),
|
||||
&host_exposed_functions,
|
||||
&erased_functions,
|
||||
)?;
|
||||
|
||||
type_definitions.extend(env.type_names);
|
||||
|
@ -279,8 +285,14 @@ where
|
|||
.collect();
|
||||
|
||||
let mut env = Env::new();
|
||||
let entry_point_function =
|
||||
build_entry_point(&mut env, interner, layout, None, &host_exposed)?;
|
||||
let entry_point_function = build_entry_point(
|
||||
&mut env,
|
||||
interner,
|
||||
layout,
|
||||
None,
|
||||
&host_exposed,
|
||||
&erased_functions,
|
||||
)?;
|
||||
|
||||
type_definitions.extend(env.type_names);
|
||||
|
||||
|
@ -360,10 +372,11 @@ fn terrible_hack(builder: &mut FuncDefBuilder, block: BlockId, type_id: TypeId)
|
|||
|
||||
fn build_entry_point<'a>(
|
||||
env: &mut Env<'a>,
|
||||
interner: &mut STLayoutInterner<'a>,
|
||||
interner: &STLayoutInterner<'a>,
|
||||
layout: roc_mono::ir::ProcLayout<'a>,
|
||||
entry_point_function: Option<FuncName>,
|
||||
host_exposed_functions: &[([u8; SIZE], &'a [InLayout<'a>])],
|
||||
erased_functions: &[([u8; SIZE], &'a [InLayout<'a>])],
|
||||
) -> Result<FuncDef> {
|
||||
let mut builder = FuncDefBuilder::new();
|
||||
let outer_block = builder.add_block();
|
||||
|
@ -394,7 +407,7 @@ fn build_entry_point<'a>(
|
|||
}
|
||||
|
||||
// add fake calls to host-exposed functions so they are specialized
|
||||
for (name_bytes, layouts) in host_exposed_functions {
|
||||
for (name_bytes, layouts) in host_exposed_functions.iter().chain(erased_functions) {
|
||||
let host_exposed_func_name = FuncName(name_bytes);
|
||||
|
||||
if Some(host_exposed_func_name) == entry_point_function {
|
||||
|
@ -403,7 +416,7 @@ fn build_entry_point<'a>(
|
|||
|
||||
let block = builder.add_block();
|
||||
|
||||
let struct_layout = interner.insert_direct_no_semantic(LayoutRepr::struct_(layouts));
|
||||
let struct_layout = LayoutRepr::struct_(layouts);
|
||||
let type_id = layout_spec(env, &mut builder, interner, struct_layout)?;
|
||||
|
||||
let argument = builder.add_unknown_with(block, &[], type_id)?;
|
||||
|
@ -433,7 +446,7 @@ fn build_entry_point<'a>(
|
|||
|
||||
fn proc_spec<'a>(
|
||||
arena: &'a Bump,
|
||||
interner: &mut STLayoutInterner<'a>,
|
||||
interner: &STLayoutInterner<'a>,
|
||||
proc: &Proc<'a>,
|
||||
) -> Result<(FuncDef, MutSet<UnionLayout<'a>>)> {
|
||||
let mut builder = FuncDefBuilder::new();
|
||||
|
@ -460,10 +473,14 @@ fn proc_spec<'a>(
|
|||
)?;
|
||||
|
||||
let root = BlockExpr(block, value_id);
|
||||
let args_struct_layout =
|
||||
interner.insert_direct_no_semantic(LayoutRepr::struct_(argument_layouts.into_bump_slice()));
|
||||
let args_struct_layout = LayoutRepr::struct_(argument_layouts.into_bump_slice());
|
||||
let arg_type_id = layout_spec(&mut env, &mut builder, interner, args_struct_layout)?;
|
||||
let ret_type_id = layout_spec(&mut env, &mut builder, interner, proc.ret_layout)?;
|
||||
let ret_type_id = layout_spec(
|
||||
&mut env,
|
||||
&mut builder,
|
||||
interner,
|
||||
interner.get_repr(proc.ret_layout),
|
||||
)?;
|
||||
|
||||
let spec = builder.build(arg_type_id, ret_type_id, root)?;
|
||||
|
||||
|
@ -506,6 +523,12 @@ fn apply_refcount_operation(
|
|||
builder.add_recursive_touch(block, argument)?;
|
||||
}
|
||||
ModifyRc::DecRef(symbol) => {
|
||||
// this is almost certainly suboptimal, but not incorrect
|
||||
let argument = env.symbols[symbol];
|
||||
builder.add_recursive_touch(block, argument)?;
|
||||
}
|
||||
ModifyRc::Free(symbol) => {
|
||||
// this is almost certainly suboptimal, but not incorrect
|
||||
let argument = env.symbols[symbol];
|
||||
builder.add_recursive_touch(block, argument)?;
|
||||
}
|
||||
|
@ -516,7 +539,7 @@ fn apply_refcount_operation(
|
|||
|
||||
fn stmt_spec<'a>(
|
||||
builder: &mut FuncDefBuilder,
|
||||
interner: &mut STLayoutInterner<'a>,
|
||||
interner: &STLayoutInterner<'a>,
|
||||
env: &mut Env<'a>,
|
||||
block: BlockId,
|
||||
layout: InLayout<'a>,
|
||||
|
@ -601,10 +624,15 @@ fn stmt_spec<'a>(
|
|||
let mut type_ids = Vec::new();
|
||||
|
||||
for p in parameters.iter() {
|
||||
type_ids.push(layout_spec(env, builder, interner, p.layout)?);
|
||||
type_ids.push(layout_spec(
|
||||
env,
|
||||
builder,
|
||||
interner,
|
||||
interner.get_repr(p.layout),
|
||||
)?);
|
||||
}
|
||||
|
||||
let ret_type_id = layout_spec(env, builder, interner, layout)?;
|
||||
let ret_type_id = layout_spec(env, builder, interner, interner.get_repr(layout))?;
|
||||
|
||||
let jp_arg_type_id = builder.add_tuple_type(&type_ids)?;
|
||||
|
||||
|
@ -645,7 +673,7 @@ fn stmt_spec<'a>(
|
|||
builder.add_sub_block(block, BlockExpr(cont_block, cont_value_id))
|
||||
}
|
||||
Jump(id, symbols) => {
|
||||
let ret_type_id = layout_spec(env, builder, interner, layout)?;
|
||||
let ret_type_id = layout_spec(env, builder, interner, interner.get_repr(layout))?;
|
||||
let argument = build_tuple_value(builder, env, block, symbols)?;
|
||||
|
||||
let jpid = env.join_points[id];
|
||||
|
@ -654,7 +682,7 @@ fn stmt_spec<'a>(
|
|||
Crash(msg, _) => {
|
||||
// Model this as a foreign call rather than TERMINATE because
|
||||
// we want ownership of the message.
|
||||
let result_type = layout_spec(env, builder, interner, layout)?;
|
||||
let result_type = layout_spec(env, builder, interner, interner.get_repr(layout))?;
|
||||
|
||||
builder.add_unknown_with(block, &[env.symbols[msg]], result_type)
|
||||
}
|
||||
|
@ -692,7 +720,7 @@ fn build_recursive_tuple_type<'a>(
|
|||
let mut field_types = Vec::new();
|
||||
|
||||
for field in layouts.iter() {
|
||||
let type_id = layout_spec_help(env, builder, interner, *field)?;
|
||||
let type_id = layout_spec_help(env, builder, interner, interner.get_repr(*field))?;
|
||||
field_types.push(type_id);
|
||||
}
|
||||
|
||||
|
@ -708,7 +736,12 @@ fn build_tuple_type<'a>(
|
|||
let mut field_types = Vec::new();
|
||||
|
||||
for field in layouts.iter() {
|
||||
field_types.push(layout_spec(env, builder, interner, *field)?);
|
||||
field_types.push(layout_spec(
|
||||
env,
|
||||
builder,
|
||||
interner,
|
||||
interner.get_repr(*field),
|
||||
)?);
|
||||
}
|
||||
|
||||
builder.add_tuple_type(&field_types)
|
||||
|
@ -742,7 +775,7 @@ fn add_loop(
|
|||
|
||||
fn call_spec<'a>(
|
||||
builder: &mut FuncDefBuilder,
|
||||
interner: &mut STLayoutInterner<'a>,
|
||||
interner: &STLayoutInterner<'a>,
|
||||
env: &mut Env<'a>,
|
||||
block: BlockId,
|
||||
layout: InLayout<'a>,
|
||||
|
@ -768,6 +801,16 @@ fn call_spec<'a>(
|
|||
let module = MOD_APP;
|
||||
builder.add_call(block, spec_var, module, name, arg_value_id)
|
||||
}
|
||||
ByPointer {
|
||||
pointer,
|
||||
ret_layout,
|
||||
arg_layouts: _,
|
||||
} => {
|
||||
let result_type = layout_spec(env, builder, interner, interner.get_repr(*ret_layout))?;
|
||||
let fnptr = env.symbols[pointer];
|
||||
let arg_value_id = build_tuple_value(builder, env, block, call.arguments)?;
|
||||
builder.add_unknown_with(block, &[fnptr, arg_value_id], result_type)
|
||||
}
|
||||
Foreign {
|
||||
foreign_symbol: _,
|
||||
ret_layout,
|
||||
|
@ -778,7 +821,7 @@ fn call_spec<'a>(
|
|||
.map(|symbol| env.symbols[symbol])
|
||||
.collect();
|
||||
|
||||
let result_type = layout_spec(env, builder, interner, *ret_layout)?;
|
||||
let result_type = layout_spec(env, builder, interner, interner.get_repr(*ret_layout))?;
|
||||
|
||||
builder.add_unknown_with(block, &arguments, result_type)
|
||||
}
|
||||
|
@ -849,11 +892,10 @@ fn call_spec<'a>(
|
|||
list_append(builder, block, update_mode_var, state, new_element)
|
||||
};
|
||||
|
||||
let output_element_type = layout_spec(env, builder, interner, *return_layout)?;
|
||||
let output_element_type =
|
||||
layout_spec(env, builder, interner, interner.get_repr(*return_layout))?;
|
||||
|
||||
let state_layout = interner.insert_direct_no_semantic(LayoutRepr::Builtin(
|
||||
Builtin::List(*return_layout),
|
||||
));
|
||||
let state_layout = LayoutRepr::Builtin(Builtin::List(*return_layout));
|
||||
let state_type = layout_spec(env, builder, interner, state_layout)?;
|
||||
|
||||
let init_state = new_list(builder, block, output_element_type)?;
|
||||
|
@ -880,8 +922,7 @@ fn call_spec<'a>(
|
|||
|
||||
let arg0_layout = argument_layouts[0];
|
||||
|
||||
let state_layout = interner
|
||||
.insert_direct_no_semantic(LayoutRepr::Builtin(Builtin::List(arg0_layout)));
|
||||
let state_layout = LayoutRepr::Builtin(Builtin::List(arg0_layout));
|
||||
let state_type = layout_spec(env, builder, interner, state_layout)?;
|
||||
let init_state = list;
|
||||
|
||||
|
@ -906,11 +947,10 @@ fn call_spec<'a>(
|
|||
list_append(builder, block, update_mode_var, state, new_element)
|
||||
};
|
||||
|
||||
let output_element_type = layout_spec(env, builder, interner, *return_layout)?;
|
||||
let output_element_type =
|
||||
layout_spec(env, builder, interner, interner.get_repr(*return_layout))?;
|
||||
|
||||
let state_layout = interner.insert_direct_no_semantic(LayoutRepr::Builtin(
|
||||
Builtin::List(*return_layout),
|
||||
));
|
||||
let state_layout = LayoutRepr::Builtin(Builtin::List(*return_layout));
|
||||
let state_type = layout_spec(env, builder, interner, state_layout)?;
|
||||
|
||||
let init_state = new_list(builder, block, output_element_type)?;
|
||||
|
@ -941,11 +981,10 @@ fn call_spec<'a>(
|
|||
list_append(builder, block, update_mode_var, state, new_element)
|
||||
};
|
||||
|
||||
let output_element_type = layout_spec(env, builder, interner, *return_layout)?;
|
||||
let output_element_type =
|
||||
layout_spec(env, builder, interner, interner.get_repr(*return_layout))?;
|
||||
|
||||
let state_layout = interner.insert_direct_no_semantic(LayoutRepr::Builtin(
|
||||
Builtin::List(*return_layout),
|
||||
));
|
||||
let state_layout = LayoutRepr::Builtin(Builtin::List(*return_layout));
|
||||
let state_type = layout_spec(env, builder, interner, state_layout)?;
|
||||
|
||||
let init_state = new_list(builder, block, output_element_type)?;
|
||||
|
@ -982,11 +1021,10 @@ fn call_spec<'a>(
|
|||
list_append(builder, block, update_mode_var, state, new_element)
|
||||
};
|
||||
|
||||
let output_element_type = layout_spec(env, builder, interner, *return_layout)?;
|
||||
let output_element_type =
|
||||
layout_spec(env, builder, interner, interner.get_repr(*return_layout))?;
|
||||
|
||||
let state_layout = interner.insert_direct_no_semantic(LayoutRepr::Builtin(
|
||||
Builtin::List(*return_layout),
|
||||
));
|
||||
let state_layout = LayoutRepr::Builtin(Builtin::List(*return_layout));
|
||||
let state_type = layout_spec(env, builder, interner, state_layout)?;
|
||||
|
||||
let init_state = new_list(builder, block, output_element_type)?;
|
||||
|
@ -1042,7 +1080,7 @@ fn lowlevel_spec<'a>(
|
|||
) -> Result<ValueId> {
|
||||
use LowLevel::*;
|
||||
|
||||
let type_id = layout_spec(env, builder, interner, layout)?;
|
||||
let type_id = layout_spec(env, builder, interner, interner.get_repr(layout))?;
|
||||
let mode = update_mode.to_bytes();
|
||||
let update_mode_var = UpdateModeVar(&mode);
|
||||
|
||||
|
@ -1150,7 +1188,8 @@ fn lowlevel_spec<'a>(
|
|||
|
||||
match interner.get_repr(layout) {
|
||||
LayoutRepr::Builtin(Builtin::List(element_layout)) => {
|
||||
let type_id = layout_spec(env, builder, interner, element_layout)?;
|
||||
let type_id =
|
||||
layout_spec(env, builder, interner, interner.get_repr(element_layout))?;
|
||||
new_list(builder, block, type_id)
|
||||
}
|
||||
_ => unreachable!("empty array does not have a list layout"),
|
||||
|
@ -1198,7 +1237,7 @@ fn lowlevel_spec<'a>(
|
|||
// TODO overly pessimstic
|
||||
let arguments: Vec<_> = arguments.iter().map(|symbol| env.symbols[symbol]).collect();
|
||||
|
||||
let result_type = layout_spec(env, builder, interner, layout)?;
|
||||
let result_type = layout_spec(env, builder, interner, interner.get_repr(layout))?;
|
||||
|
||||
builder.add_unknown_with(block, &arguments, result_type)
|
||||
}
|
||||
|
@ -1283,7 +1322,7 @@ fn worst_case_type(context: &mut impl TypeContext) -> Result<TypeId> {
|
|||
|
||||
fn expr_spec<'a>(
|
||||
builder: &mut FuncDefBuilder,
|
||||
interner: &mut STLayoutInterner<'a>,
|
||||
interner: &STLayoutInterner<'a>,
|
||||
env: &mut Env<'a>,
|
||||
block: BlockId,
|
||||
layout: InLayout<'a>,
|
||||
|
@ -1294,21 +1333,16 @@ fn expr_spec<'a>(
|
|||
match expr {
|
||||
Literal(literal) => literal_spec(builder, block, literal),
|
||||
NullPointer => {
|
||||
let pointer_type = layout_spec(env, builder, interner, layout)?;
|
||||
let pointer_type = layout_spec(env, builder, interner, interner.get_repr(layout))?;
|
||||
|
||||
builder.add_unknown_with(block, &[], pointer_type)
|
||||
}
|
||||
Call(call) => call_spec(builder, interner, env, block, layout, call),
|
||||
Reuse {
|
||||
tag_layout,
|
||||
tag_id,
|
||||
arguments,
|
||||
..
|
||||
}
|
||||
| Tag {
|
||||
Tag {
|
||||
tag_layout,
|
||||
tag_id,
|
||||
arguments,
|
||||
reuse: _,
|
||||
} => {
|
||||
let data_id = build_tuple_value(builder, env, block, arguments)?;
|
||||
|
||||
|
@ -1347,16 +1381,6 @@ fn expr_spec<'a>(
|
|||
|
||||
builder.add_make_named(block, MOD_APP, type_name, tag_value_id)
|
||||
}
|
||||
ExprBox { symbol } => {
|
||||
let value_id = env.symbols[symbol];
|
||||
|
||||
with_new_heap_cell(builder, block, value_id)
|
||||
}
|
||||
ExprUnbox { symbol } => {
|
||||
let tuple_id = env.symbols[symbol];
|
||||
|
||||
builder.add_get_tuple_field(block, tuple_id, BOX_VALUE_INDEX)
|
||||
}
|
||||
Struct(fields) => build_tuple_value(builder, env, block, fields),
|
||||
UnionAtIndex {
|
||||
index,
|
||||
|
@ -1412,6 +1436,38 @@ fn expr_spec<'a>(
|
|||
builder.add_get_tuple_field(block, variant_id, index)
|
||||
}
|
||||
},
|
||||
UnionFieldPtrAtIndex {
|
||||
index,
|
||||
tag_id,
|
||||
structure,
|
||||
union_layout,
|
||||
} => {
|
||||
let index = (*index) as u32;
|
||||
let tag_value_id = env.symbols[structure];
|
||||
|
||||
let type_name_bytes = recursive_tag_union_name_bytes(union_layout).as_bytes();
|
||||
let type_name = TypeName(&type_name_bytes);
|
||||
|
||||
// unwrap the named wrapper
|
||||
let union_id = builder.add_unwrap_named(block, MOD_APP, type_name, tag_value_id)?;
|
||||
|
||||
// now we have a tuple (cell, union { ... }); decompose
|
||||
let heap_cell = builder.add_get_tuple_field(block, union_id, TAG_CELL_INDEX)?;
|
||||
let union_data = builder.add_get_tuple_field(block, union_id, TAG_DATA_INDEX)?;
|
||||
|
||||
// we're reading from this value, so touch the heap cell
|
||||
builder.add_touch(block, heap_cell)?;
|
||||
|
||||
// next, unwrap the union at the tag id that we've got
|
||||
let variant_id = builder.add_unwrap_union(block, union_data, *tag_id as u32)?;
|
||||
|
||||
let value = builder.add_get_tuple_field(block, variant_id, index)?;
|
||||
|
||||
// construct the box. Here the heap_cell of the tag is re-used, I'm hoping that that
|
||||
// conveys to morphic that we're borrowing into the existing tag?!
|
||||
builder.add_make_tuple(block, &[heap_cell, value])
|
||||
}
|
||||
|
||||
StructAtIndex {
|
||||
index, structure, ..
|
||||
} => {
|
||||
|
@ -1419,7 +1475,7 @@ fn expr_spec<'a>(
|
|||
builder.add_get_tuple_field(block, value_id, *index as u32)
|
||||
}
|
||||
Array { elem_layout, elems } => {
|
||||
let type_id = layout_spec(env, builder, interner, *elem_layout)?;
|
||||
let type_id = layout_spec(env, builder, interner, interner.get_repr(*elem_layout))?;
|
||||
|
||||
let list = new_list(builder, block, type_id)?;
|
||||
|
||||
|
@ -1446,7 +1502,8 @@ fn expr_spec<'a>(
|
|||
|
||||
EmptyArray => match interner.get_repr(layout) {
|
||||
LayoutRepr::Builtin(Builtin::List(element_layout)) => {
|
||||
let type_id = layout_spec(env, builder, interner, element_layout)?;
|
||||
let type_id =
|
||||
layout_spec(env, builder, interner, interner.get_repr(element_layout))?;
|
||||
new_list(builder, block, type_id)
|
||||
}
|
||||
_ => unreachable!("empty array does not have a list layout"),
|
||||
|
@ -1483,8 +1540,30 @@ fn expr_spec<'a>(
|
|||
let value = with_new_heap_cell(builder, block, union_data)?;
|
||||
builder.add_make_named(block, MOD_APP, type_name, value)
|
||||
}
|
||||
FunctionPointer { .. } => {
|
||||
let pointer_type = layout_spec(env, builder, interner, interner.get_repr(layout))?;
|
||||
|
||||
builder.add_unknown_with(block, &[], pointer_type)
|
||||
}
|
||||
ErasedMake { callee, value } => {
|
||||
let value = match value {
|
||||
Some(v) => box_erasure_value_unknown(builder, block, env.symbols[v]),
|
||||
// model nullptr
|
||||
None => box_erasure_value_unknown_nullptr(builder, block),
|
||||
}?;
|
||||
|
||||
let callee = env.symbols[callee];
|
||||
|
||||
erasure_make(builder, block, value, callee)
|
||||
}
|
||||
ErasedLoad { symbol, field } => {
|
||||
let value = env.symbols[symbol];
|
||||
let loaded_type = layout_spec(env, builder, interner, interner.get_repr(layout))?;
|
||||
|
||||
erasure_load(builder, block, value, *field, loaded_type)
|
||||
}
|
||||
RuntimeErrorFunction(_) => {
|
||||
let type_id = layout_spec(env, builder, interner, layout)?;
|
||||
let type_id = layout_spec(env, builder, interner, interner.get_repr(layout))?;
|
||||
|
||||
builder.add_terminate(block, type_id)
|
||||
}
|
||||
|
@ -1493,6 +1572,16 @@ fn expr_spec<'a>(
|
|||
|
||||
builder.add_make_tuple(block, &[])
|
||||
}
|
||||
Alloca { initializer, .. } => {
|
||||
let initializer = &initializer.as_ref().map(|s| env.symbols[s]);
|
||||
let values = match initializer {
|
||||
Some(initializer) => std::slice::from_ref(initializer),
|
||||
None => &[],
|
||||
};
|
||||
|
||||
let type_id = layout_spec(env, builder, interner, interner.get_repr(layout))?;
|
||||
builder.add_unknown_with(block, values, type_id)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -1515,7 +1604,7 @@ fn layout_spec<'a>(
|
|||
env: &mut Env<'a>,
|
||||
builder: &mut impl TypeContext,
|
||||
interner: &STLayoutInterner<'a>,
|
||||
layout: InLayout<'a>,
|
||||
layout: LayoutRepr<'a>,
|
||||
) -> Result<TypeId> {
|
||||
layout_spec_help(env, builder, interner, layout)
|
||||
}
|
||||
|
@ -1539,16 +1628,19 @@ fn layout_spec_help<'a>(
|
|||
env: &mut Env<'a>,
|
||||
builder: &mut impl TypeContext,
|
||||
interner: &STLayoutInterner<'a>,
|
||||
layout: InLayout<'a>,
|
||||
layout: LayoutRepr<'a>,
|
||||
) -> Result<TypeId> {
|
||||
use LayoutRepr::*;
|
||||
|
||||
match interner.get_repr(layout) {
|
||||
match layout {
|
||||
Builtin(builtin) => builtin_spec(env, builder, interner, &builtin),
|
||||
Struct(field_layouts) => build_recursive_tuple_type(env, builder, interner, field_layouts),
|
||||
LambdaSet(lambda_set) => {
|
||||
layout_spec_help(env, builder, interner, lambda_set.runtime_representation())
|
||||
}
|
||||
LambdaSet(lambda_set) => layout_spec_help(
|
||||
env,
|
||||
builder,
|
||||
interner,
|
||||
interner.get_repr(lambda_set.runtime_representation()),
|
||||
),
|
||||
Union(union_layout) => {
|
||||
match union_layout {
|
||||
UnionLayout::NonRecursive(&[]) => {
|
||||
|
@ -1575,12 +1667,14 @@ fn layout_spec_help<'a>(
|
|||
}
|
||||
}
|
||||
|
||||
Boxed(inner_layout) => {
|
||||
let inner_type = layout_spec_help(env, builder, interner, inner_layout)?;
|
||||
Ptr(inner_layout) => {
|
||||
let inner_type =
|
||||
layout_spec_help(env, builder, interner, interner.get_repr(inner_layout))?;
|
||||
let cell_type = builder.add_heap_cell_type();
|
||||
|
||||
builder.add_tuple_type(&[cell_type, inner_type])
|
||||
}
|
||||
|
||||
// TODO(recursive-layouts): update once we have recursive pointer loops
|
||||
RecursivePointer(union_layout) => match interner.get_repr(union_layout) {
|
||||
LayoutRepr::Union(union_layout) => {
|
||||
|
@ -1592,6 +1686,9 @@ fn layout_spec_help<'a>(
|
|||
}
|
||||
_ => internal_error!("somehow, a non-recursive layout is under a recursive pointer"),
|
||||
},
|
||||
|
||||
FunctionPointer(_) => function_pointer_type(builder),
|
||||
Erased(_) => erasure_type(builder),
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -1608,7 +1705,8 @@ fn builtin_spec<'a>(
|
|||
Decimal | Float(_) => builder.add_tuple_type(&[]),
|
||||
Str => str_type(builder),
|
||||
List(element_layout) => {
|
||||
let element_type = layout_spec_help(env, builder, interner, *element_layout)?;
|
||||
let element_type =
|
||||
layout_spec_help(env, builder, interner, interner.get_repr(*element_layout))?;
|
||||
|
||||
let cell = builder.add_heap_cell_type();
|
||||
let bag = builder.add_bag_type(element_type)?;
|
||||
|
@ -1634,10 +1732,6 @@ fn static_list_type<TC: TypeContext>(builder: &mut TC) -> Result<TypeId> {
|
|||
const LIST_CELL_INDEX: u32 = 0;
|
||||
const LIST_BAG_INDEX: u32 = 1;
|
||||
|
||||
#[allow(dead_code)]
|
||||
const BOX_CELL_INDEX: u32 = LIST_CELL_INDEX;
|
||||
const BOX_VALUE_INDEX: u32 = LIST_BAG_INDEX;
|
||||
|
||||
const TAG_CELL_INDEX: u32 = 0;
|
||||
const TAG_DATA_INDEX: u32 = 1;
|
||||
|
||||
|
@ -1671,3 +1765,78 @@ fn new_num(builder: &mut FuncDefBuilder, block: BlockId) -> Result<ValueId> {
|
|||
// we model all our numbers as unit values
|
||||
builder.add_make_tuple(block, &[])
|
||||
}
|
||||
|
||||
fn function_pointer_type<TC: TypeContext>(builder: &mut TC) -> Result<TypeId> {
|
||||
builder.add_tuple_type(&[])
|
||||
}
|
||||
|
||||
const ERASURE_CALEE_INDEX: u32 = 0;
|
||||
const ERASURE_VALUE_INDEX: u32 = 1;
|
||||
|
||||
/// Erasure type modeled as
|
||||
///
|
||||
/// ```text
|
||||
/// Tuple(callee: FnPtr, value: HeapCell)
|
||||
/// ```
|
||||
fn erasure_type<TC: TypeContext>(builder: &mut TC) -> Result<TypeId> {
|
||||
let value_cell_id = builder.add_heap_cell_type();
|
||||
let callee_id = function_pointer_type(builder)?;
|
||||
builder.add_tuple_type(&[value_cell_id, callee_id])
|
||||
}
|
||||
|
||||
fn erasure_box_value_type<TC: TypeContext>(builder: &mut TC) -> TypeId {
|
||||
builder.add_heap_cell_type()
|
||||
}
|
||||
|
||||
fn box_erasure_value_unknown(
|
||||
builder: &mut FuncDefBuilder,
|
||||
block: BlockId,
|
||||
value: ValueId,
|
||||
) -> Result<ValueId> {
|
||||
let heap_cell = erasure_box_value_type(builder);
|
||||
builder.add_unknown_with(block, &[value], heap_cell)
|
||||
}
|
||||
|
||||
fn box_erasure_value_unknown_nullptr(
|
||||
builder: &mut FuncDefBuilder,
|
||||
block: BlockId,
|
||||
) -> Result<ValueId> {
|
||||
let heap_cell = erasure_box_value_type(builder);
|
||||
builder.add_unknown_with(block, &[], heap_cell)
|
||||
}
|
||||
|
||||
/// Erasure value modeled as
|
||||
///
|
||||
/// ```text
|
||||
/// callee = make_tuple(&[])
|
||||
/// value = unknown(make_tuple(...captures))
|
||||
///
|
||||
/// x : Tuple(callee: FnPtr, value: HeapCell)
|
||||
/// x = make_tuple(callee, value)
|
||||
/// ```
|
||||
fn erasure_make(
|
||||
builder: &mut FuncDefBuilder,
|
||||
block: BlockId,
|
||||
value: ValueId,
|
||||
callee: ValueId,
|
||||
) -> Result<ValueId> {
|
||||
builder.add_make_tuple(block, &[value, callee])
|
||||
}
|
||||
|
||||
fn erasure_load(
|
||||
builder: &mut FuncDefBuilder,
|
||||
block: BlockId,
|
||||
value: ValueId,
|
||||
field: ErasedField,
|
||||
loaded_type: TypeId,
|
||||
) -> Result<ValueId> {
|
||||
match field {
|
||||
ErasedField::Callee => builder.add_get_tuple_field(block, value, ERASURE_CALEE_INDEX),
|
||||
ErasedField::Value | ErasedField::ValuePtr => {
|
||||
let unknown_heap_cell_value =
|
||||
builder.add_get_tuple_field(block, value, ERASURE_VALUE_INDEX)?;
|
||||
// Cast the unknown cell to the wanted type
|
||||
builder.add_unknown_with(block, &[unknown_heap_cell_value], loaded_type)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -126,7 +126,7 @@ fn find_wasi_libc_path() -> PathBuf {
|
|||
internal_error!("cannot find `wasi-libc.a`")
|
||||
}
|
||||
|
||||
#[cfg(all(unix, not(target_os = "macos")))]
|
||||
#[cfg(unix)]
|
||||
#[allow(clippy::too_many_arguments)]
|
||||
pub fn build_zig_host_native(
|
||||
env_path: &str,
|
||||
|
@ -161,7 +161,7 @@ pub fn build_zig_host_native(
|
|||
|
||||
zig_cmd.args([
|
||||
zig_host_src,
|
||||
&format!("-femit-bin={}", emit_bin),
|
||||
&format!("-femit-bin={emit_bin}"),
|
||||
"--pkg-begin",
|
||||
"glue",
|
||||
find_zig_glue_path().to_str().unwrap(),
|
||||
|
@ -257,99 +257,6 @@ pub fn build_zig_host_native(
|
|||
zig_cmd
|
||||
}
|
||||
|
||||
#[cfg(target_os = "macos")]
|
||||
#[allow(clippy::too_many_arguments)]
|
||||
pub fn build_zig_host_native(
|
||||
env_path: &str,
|
||||
env_home: &str,
|
||||
emit_bin: &str,
|
||||
zig_host_src: &str,
|
||||
_target: &str,
|
||||
opt_level: OptLevel,
|
||||
shared_lib_path: Option<&Path>,
|
||||
builtins_host_path: &Path,
|
||||
// For compatibility with the non-macOS def above. Keep these in sync.
|
||||
) -> Command {
|
||||
use serde_json::Value;
|
||||
|
||||
// Run `zig env` to find the location of zig's std/ directory
|
||||
let zig_env_output = zig().args(["env"]).output().unwrap();
|
||||
|
||||
let zig_env_json = if zig_env_output.status.success() {
|
||||
std::str::from_utf8(&zig_env_output.stdout).unwrap_or_else(|utf8_err| {
|
||||
internal_error!(
|
||||
"`zig env` failed; its stderr output was invalid utf8 ({:?})",
|
||||
utf8_err
|
||||
);
|
||||
})
|
||||
} else {
|
||||
match std::str::from_utf8(&zig_env_output.stderr) {
|
||||
Ok(stderr) => internal_error!("`zig env` failed - stderr output was: {:?}", stderr),
|
||||
Err(utf8_err) => internal_error!(
|
||||
"`zig env` failed; its stderr output was invalid utf8 ({:?})",
|
||||
utf8_err
|
||||
),
|
||||
}
|
||||
};
|
||||
|
||||
let mut zig_compiler_rt_path = match serde_json::from_str(zig_env_json) {
|
||||
Ok(Value::Object(map)) => match map.get("std_dir") {
|
||||
Some(Value::String(std_dir)) => PathBuf::from(Path::new(std_dir)),
|
||||
_ => {
|
||||
internal_error!("Expected JSON containing a `std_dir` String field from `zig env`, but got: {:?}", zig_env_json);
|
||||
}
|
||||
},
|
||||
_ => {
|
||||
internal_error!(
|
||||
"Expected JSON containing a `std_dir` field from `zig env`, but got: {:?}",
|
||||
zig_env_json
|
||||
);
|
||||
}
|
||||
};
|
||||
|
||||
zig_compiler_rt_path.push("special");
|
||||
zig_compiler_rt_path.push("compiler_rt.zig");
|
||||
|
||||
let mut zig_cmd = zig();
|
||||
zig_cmd
|
||||
.env_clear()
|
||||
.env("PATH", env_path)
|
||||
.env("HOME", env_home);
|
||||
if let Some(shared_lib_path) = shared_lib_path {
|
||||
zig_cmd.args([
|
||||
"build-exe",
|
||||
"-fPIE",
|
||||
shared_lib_path.to_str().unwrap(),
|
||||
builtins_host_path.to_str().unwrap(),
|
||||
]);
|
||||
} else {
|
||||
zig_cmd.args(["build-obj"]);
|
||||
}
|
||||
zig_cmd.args([
|
||||
zig_host_src,
|
||||
&format!("-femit-bin={}", emit_bin),
|
||||
"--pkg-begin",
|
||||
"glue",
|
||||
find_zig_glue_path().to_str().unwrap(),
|
||||
"--pkg-end",
|
||||
// include the zig runtime
|
||||
"--pkg-begin",
|
||||
"compiler_rt",
|
||||
zig_compiler_rt_path.to_str().unwrap(),
|
||||
"--pkg-end",
|
||||
// include libc
|
||||
"--library",
|
||||
"c",
|
||||
]);
|
||||
if matches!(opt_level, OptLevel::Optimize) {
|
||||
zig_cmd.args(["-O", "ReleaseSafe"]);
|
||||
} else if matches!(opt_level, OptLevel::Size) {
|
||||
zig_cmd.args(["-O", "ReleaseSmall"]);
|
||||
}
|
||||
|
||||
zig_cmd
|
||||
}
|
||||
|
||||
pub fn build_zig_host_wasm32(
|
||||
env_path: &str,
|
||||
env_home: &str,
|
||||
|
@ -492,7 +399,7 @@ pub fn build_swift_host_native(
|
|||
|
||||
match arch {
|
||||
Architecture::Aarch64(_) => command.arg("-arm64"),
|
||||
_ => command.arg(format!("-{}", arch)),
|
||||
_ => command.arg(format!("-{arch}")),
|
||||
};
|
||||
|
||||
command
|
||||
|
@ -623,7 +530,7 @@ pub fn rebuild_host(
|
|||
// on windows, we need the nightly toolchain so we can use `-Z export-executable-symbols`
|
||||
// using `+nightly` only works when running cargo through rustup
|
||||
let mut cmd = rustup();
|
||||
cmd.args(["run", "nightly-2022-10-30", "cargo"]);
|
||||
cmd.args(["run", "nightly-2023-04-15", "cargo"]);
|
||||
|
||||
cmd
|
||||
} else {
|
||||
|
@ -1021,7 +928,7 @@ fn link_linux(
|
|||
.map(|segments| segments.join("/"))
|
||||
.collect::<Vec<String>>()
|
||||
.join("\n");
|
||||
eprintln!("We looked in the following directories:\n{}", dirs);
|
||||
eprintln!("We looked in the following directories:\n{dirs}");
|
||||
process::exit(1);
|
||||
}
|
||||
};
|
||||
|
@ -1171,6 +1078,12 @@ fn link_macos(
|
|||
// "--gc-sections",
|
||||
"-arch",
|
||||
&arch,
|
||||
// Suppress warnings, because otherwise it prints:
|
||||
//
|
||||
// ld: warning: -undefined dynamic_lookup may not work with chained fixups
|
||||
//
|
||||
// We can't disable that option without breaking either x64 mac or ARM mac
|
||||
"-w",
|
||||
"-macos_version_min",
|
||||
&get_macos_version(),
|
||||
])
|
||||
|
@ -1178,8 +1091,8 @@ fn link_macos(
|
|||
|
||||
let sdk_path = "/Library/Developer/CommandLineTools/SDKs/MacOSX.sdk/usr/lib";
|
||||
if Path::new(sdk_path).exists() {
|
||||
ld_command.arg(format!("-L{}", sdk_path));
|
||||
ld_command.arg(format!("-L{}/swift", sdk_path));
|
||||
ld_command.arg(format!("-L{sdk_path}"));
|
||||
ld_command.arg(format!("-L{sdk_path}/swift"));
|
||||
};
|
||||
|
||||
let roc_link_flags = match env::var("ROC_LINK_FLAGS") {
|
||||
|
@ -1381,9 +1294,7 @@ pub fn llvm_module_to_dylib(
|
|||
|
||||
assert!(
|
||||
exit_status.success(),
|
||||
"\n___________\nLinking command failed with status {:?}:\n\n {:?}\n___________\n",
|
||||
exit_status,
|
||||
child
|
||||
"\n___________\nLinking command failed with status {exit_status:?}:\n\n {child:?}\n___________\n"
|
||||
);
|
||||
|
||||
// Load the dylib
|
||||
|
|
|
@ -8,8 +8,8 @@ use roc_gen_dev::AssemblyBackendMode;
|
|||
use roc_gen_llvm::llvm::build::{module_from_builtins, LlvmBackendMode};
|
||||
use roc_gen_llvm::llvm::externs::add_default_roc_externs;
|
||||
use roc_load::{
|
||||
EntryPoint, ExecutionMode, ExpectMetadata, LoadConfig, LoadMonomorphizedError, LoadedModule,
|
||||
LoadingProblem, MonomorphizedModule, Threading,
|
||||
EntryPoint, ExecutionMode, ExpectMetadata, FunctionKind, LoadConfig, LoadMonomorphizedError,
|
||||
LoadedModule, LoadingProblem, MonomorphizedModule, Threading,
|
||||
};
|
||||
use roc_mono::ir::{OptLevel, SingleEntryPoint};
|
||||
use roc_packaging::cache::RocCacheDir;
|
||||
|
@ -133,7 +133,7 @@ pub fn gen_from_mono_module<'a>(
|
|||
// TODO make this polymorphic in the llvm functions so it can be reused for another backend.
|
||||
fn gen_from_mono_module_llvm<'a>(
|
||||
arena: &'a bumpalo::Bump,
|
||||
mut loaded: MonomorphizedModule<'a>,
|
||||
loaded: MonomorphizedModule<'a>,
|
||||
roc_file_path: &Path,
|
||||
target: &target_lexicon::Triple,
|
||||
opt_level: OptLevel,
|
||||
|
@ -166,7 +166,7 @@ fn gen_from_mono_module_llvm<'a>(
|
|||
|
||||
let kind_id = Attribute::get_named_enum_kind_id("alwaysinline");
|
||||
debug_assert!(kind_id > 0);
|
||||
let enum_attr = context.create_enum_attribute(kind_id, 1);
|
||||
let enum_attr = context.create_enum_attribute(kind_id, 0);
|
||||
|
||||
for function in module.get_functions() {
|
||||
let name = function.get_name().to_str().unwrap();
|
||||
|
@ -232,9 +232,10 @@ fn gen_from_mono_module_llvm<'a>(
|
|||
|
||||
roc_gen_llvm::llvm::build::build_procedures(
|
||||
&env,
|
||||
&mut loaded.layout_interner,
|
||||
&loaded.layout_interner,
|
||||
opt_level,
|
||||
loaded.procedures,
|
||||
loaded.host_exposed_lambda_sets,
|
||||
entry_point,
|
||||
Some(&app_ll_file),
|
||||
&loaded.glue_layouts,
|
||||
|
@ -320,10 +321,10 @@ fn gen_from_mono_module_llvm<'a>(
|
|||
if !unrecognized.is_empty() {
|
||||
let out = unrecognized
|
||||
.iter()
|
||||
.map(|x| format!("{:?}", x))
|
||||
.map(|x| format!("{x:?}"))
|
||||
.collect::<Vec<String>>()
|
||||
.join(", ");
|
||||
eprintln!("Unrecognized sanitizer: {}\nSupported options are \"address\", \"memory\", \"thread\", \"cargo-fuzz\", and \"afl.rs\".", out);
|
||||
eprintln!("Unrecognized sanitizer: {out}\nSupported options are \"address\", \"memory\", \"thread\", \"cargo-fuzz\", and \"afl.rs\".");
|
||||
eprintln!("Note: \"cargo-fuzz\" and \"afl.rs\" both enable sanitizer coverage for fuzzing. They just use different parameters to match the respective libraries.")
|
||||
}
|
||||
|
||||
|
@ -340,7 +341,7 @@ fn gen_from_mono_module_llvm<'a>(
|
|||
}
|
||||
let opt = opt.output().unwrap();
|
||||
|
||||
assert!(opt.stderr.is_empty(), "{:#?}", opt);
|
||||
assert!(opt.stderr.is_empty(), "{opt:#?}");
|
||||
|
||||
// write the .o file. Note that this builds the .o for the local machine,
|
||||
// and ignores the `target_machine` entirely.
|
||||
|
@ -358,7 +359,7 @@ fn gen_from_mono_module_llvm<'a>(
|
|||
.output()
|
||||
.unwrap();
|
||||
|
||||
assert!(bc_to_object.status.success(), "{:#?}", bc_to_object);
|
||||
assert!(bc_to_object.status.success(), "{bc_to_object:#?}");
|
||||
|
||||
MemoryBuffer::create_from_file(&app_o_file).expect("memory buffer creation works")
|
||||
} else if emit_debug_info {
|
||||
|
@ -414,7 +415,7 @@ fn gen_from_mono_module_llvm<'a>(
|
|||
.output()
|
||||
.unwrap();
|
||||
|
||||
assert!(ll_to_object.stderr.is_empty(), "{:#?}", ll_to_object);
|
||||
assert!(ll_to_object.stderr.is_empty(), "{ll_to_object:#?}");
|
||||
}
|
||||
_ => unreachable!(),
|
||||
}
|
||||
|
@ -716,13 +717,13 @@ pub fn handle_error_module(
|
|||
pub fn handle_loading_problem(problem: LoadingProblem) -> std::io::Result<i32> {
|
||||
match problem {
|
||||
LoadingProblem::FormattedReport(report) => {
|
||||
print!("{}", report);
|
||||
print!("{report}");
|
||||
Ok(1)
|
||||
}
|
||||
_ => {
|
||||
// TODO: tighten up the types here, we should always end up with a
|
||||
// formatted report from load.
|
||||
print!("Failed with error: {:?}", problem);
|
||||
print!("Failed with error: {problem:?}");
|
||||
Ok(1)
|
||||
}
|
||||
}
|
||||
|
@ -740,8 +741,20 @@ pub fn standard_load_config(
|
|||
BuildOrdering::AlwaysBuild => ExecutionMode::Executable,
|
||||
};
|
||||
|
||||
// UNSTABLE(lambda-erasure)
|
||||
let function_kind = if cfg!(debug_assertions) {
|
||||
if std::env::var("EXPERIMENTAL_ROC_ERASE").is_ok() {
|
||||
FunctionKind::Erased
|
||||
} else {
|
||||
FunctionKind::LambdaSet
|
||||
}
|
||||
} else {
|
||||
FunctionKind::LambdaSet
|
||||
};
|
||||
|
||||
LoadConfig {
|
||||
target_info,
|
||||
function_kind,
|
||||
render: RenderTarget::ColorTerminal,
|
||||
palette: DEFAULT_PALETTE,
|
||||
threading,
|
||||
|
@ -889,7 +902,7 @@ fn build_loaded_file<'a>(
|
|||
buf.push('\n');
|
||||
|
||||
use std::fmt::Write;
|
||||
write!(buf, "{}", module_timing).unwrap();
|
||||
write!(buf, "{module_timing}").unwrap();
|
||||
|
||||
if it.peek().is_some() {
|
||||
buf.push('\n');
|
||||
|
@ -914,10 +927,7 @@ fn build_loaded_file<'a>(
|
|||
.expect("Failed to (re)build platform.");
|
||||
|
||||
if emit_timings && !is_platform_prebuilt {
|
||||
println!(
|
||||
"Finished rebuilding the platform in {} ms\n",
|
||||
rebuild_duration
|
||||
);
|
||||
println!("Finished rebuilding the platform in {rebuild_duration} ms\n");
|
||||
}
|
||||
|
||||
Some(HostRebuildTiming::BeforeApp(rebuild_duration))
|
||||
|
@ -957,8 +967,7 @@ fn build_loaded_file<'a>(
|
|||
|
||||
if emit_timings {
|
||||
println!(
|
||||
"\n\nCompilation finished!\n\nHere's how long each module took to compile:\n\n{}",
|
||||
buf
|
||||
"\n\nCompilation finished!\n\nHere's how long each module took to compile:\n\n{buf}"
|
||||
);
|
||||
|
||||
println!(
|
||||
|
@ -972,10 +981,7 @@ fn build_loaded_file<'a>(
|
|||
let rebuild_duration = thread.join().expect("Failed to (re)build platform.");
|
||||
|
||||
if emit_timings && !is_platform_prebuilt {
|
||||
println!(
|
||||
"Finished rebuilding the platform in {} ms\n",
|
||||
rebuild_duration
|
||||
);
|
||||
println!("Finished rebuilding the platform in {rebuild_duration} ms\n");
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -1007,7 +1013,7 @@ fn build_loaded_file<'a>(
|
|||
};
|
||||
let app_o_file = tempfile::Builder::new()
|
||||
.prefix("roc_app")
|
||||
.suffix(&format!(".{}", extension))
|
||||
.suffix(&format!(".{extension}"))
|
||||
.tempfile()
|
||||
.map_err(|err| todo!("TODO Gracefully handle tempfile creation error {:?}", err))?;
|
||||
let app_o_file = app_o_file.path();
|
||||
|
@ -1212,6 +1218,8 @@ pub fn check_file<'a>(
|
|||
|
||||
let load_config = LoadConfig {
|
||||
target_info,
|
||||
// TODO: we may not want this for just checking.
|
||||
function_kind: FunctionKind::LambdaSet,
|
||||
// TODO: expose this from CLI?
|
||||
render: RenderTarget::ColorTerminal,
|
||||
palette: DEFAULT_PALETTE,
|
||||
|
@ -1257,8 +1265,7 @@ pub fn check_file<'a>(
|
|||
|
||||
if emit_timings {
|
||||
println!(
|
||||
"\n\nCompilation finished!\n\nHere's how long each module took to compile:\n\n{}",
|
||||
buf
|
||||
"\n\nCompilation finished!\n\nHere's how long each module took to compile:\n\n{buf}"
|
||||
);
|
||||
|
||||
println!("Finished checking in {} ms\n", compilation_end.as_millis(),);
|
||||
|
|
|
@ -87,12 +87,12 @@ pub fn target_zig_str(target: &Triple) -> &'static str {
|
|||
architecture: Architecture::X86_64,
|
||||
operating_system: OperatingSystem::Darwin,
|
||||
..
|
||||
} => "x86_64-apple-darwin",
|
||||
} => "x86_64-macos-gnu",
|
||||
Triple {
|
||||
architecture: Architecture::Aarch64(_),
|
||||
operating_system: OperatingSystem::Darwin,
|
||||
..
|
||||
} => "aarch64-apple-darwin",
|
||||
} => "aarch64-macos-gnu",
|
||||
_ => internal_error!("TODO gracefully handle unsupported target: {:?}", target),
|
||||
}
|
||||
}
|
||||
|
@ -149,20 +149,24 @@ pub fn target_machine(
|
|||
|
||||
init_arch(target);
|
||||
|
||||
let code_model = match target.architecture {
|
||||
// LLVM 12 will not compile our programs without a large code model.
|
||||
// The reason is not totally clear to me, but my guess is a few special-cases in
|
||||
// llvm/lib/Target/AArch64/AArch64ISelLowering.cpp (instructions)
|
||||
// llvm/lib/Target/AArch64/AArch64Subtarget.cpp (GoT tables)
|
||||
// Revisit when upgrading to LLVM 13.
|
||||
Architecture::Aarch64(..) => CodeModel::Large,
|
||||
_ => CodeModel::Default,
|
||||
};
|
||||
// We used to have a problem that LLVM 12 would not compile our programs without a large code model.
|
||||
// The reason was not totally clear to us, but one guess is a few special-cases in
|
||||
// llvm/lib/Target/AArch64/AArch64ISelLowering.cpp (instructions)
|
||||
// llvm/lib/Target/AArch64/AArch64Subtarget.cpp (GoT tables)
|
||||
// Revisit when upgrading to LLVM 13.
|
||||
//
|
||||
// Most recently, we seem to only see this problem on macOS ARM64; removing this
|
||||
// failed macOS CI here: https://github.com/roc-lang/roc/pull/5644
|
||||
#[cfg(all(target_arch = "aarch64", target_os = "macos"))]
|
||||
let code_model = CodeModel::Large;
|
||||
|
||||
#[cfg(not(all(target_arch = "aarch64", target_os = "macos")))]
|
||||
let code_model = CodeModel::Default;
|
||||
|
||||
Target::from_name(arch).unwrap().create_target_machine(
|
||||
&TargetTriple::create(target_triple_str(target)),
|
||||
"generic",
|
||||
"", // TODO: this probably should be TargetMachine::get_host_cpu_features() to enable all features.
|
||||
"",
|
||||
opt,
|
||||
reloc,
|
||||
code_model,
|
||||
|
|
|
@ -35,6 +35,7 @@ fn main() {
|
|||
|
||||
generate_bc_file(&bitcode_path, "ir-i386", "builtins-i386");
|
||||
generate_bc_file(&bitcode_path, "ir-x86_64", "builtins-x86_64");
|
||||
generate_bc_file(&bitcode_path, "ir-aarch64", "builtins-aarch64");
|
||||
generate_bc_file(
|
||||
&bitcode_path,
|
||||
"ir-windows-x86_64",
|
||||
|
@ -61,12 +62,12 @@ fn generate_bc_file(bitcode_path: &Path, zig_object: &str, file_name: &str) {
|
|||
ll_path.set_extension("ll");
|
||||
let dest_ir_host = ll_path.to_str().expect("Invalid dest ir path");
|
||||
|
||||
println!("Compiling host ir to: {}", dest_ir_host);
|
||||
println!("Compiling host ir to: {dest_ir_host}");
|
||||
|
||||
let mut bc_path = bitcode_path.join(file_name);
|
||||
bc_path.set_extension("bc");
|
||||
let dest_bc_64bit = bc_path.to_str().expect("Invalid dest bc path");
|
||||
println!("Compiling 64-bit bitcode to: {}", dest_bc_64bit);
|
||||
println!("Compiling 64-bit bitcode to: {dest_bc_64bit}");
|
||||
|
||||
// workaround for github.com/ziglang/zig/issues/9711
|
||||
#[cfg(target_os = "macos")]
|
||||
|
@ -104,7 +105,7 @@ fn run_command(mut command: Command, flaky_fail_counter: usize) {
|
|||
false => {
|
||||
let error_str = match str::from_utf8(&output.stderr) {
|
||||
Ok(stderr) => stderr.to_string(),
|
||||
Err(_) => format!("Failed to run \"{}\"", command_str),
|
||||
Err(_) => format!("Failed to run \"{command_str}\""),
|
||||
};
|
||||
|
||||
// Flaky test errors that only occur sometimes on MacOS ci server.
|
||||
|
|
|
@ -66,7 +66,7 @@ fn generate_object_file(bitcode_path: &Path, zig_object: &str, object_file_name:
|
|||
let src_obj_path = bitcode_path.join(object_file_name);
|
||||
let src_obj = src_obj_path.to_str().expect("Invalid src object path");
|
||||
|
||||
println!("Compiling zig object `{}` to: {}", zig_object, src_obj);
|
||||
println!("Compiling zig object `{zig_object}` to: {src_obj}");
|
||||
|
||||
if !DEBUG {
|
||||
let mut zig_cmd = zig();
|
||||
|
@ -77,7 +77,7 @@ fn generate_object_file(bitcode_path: &Path, zig_object: &str, object_file_name:
|
|||
|
||||
run_command(zig_cmd, 0);
|
||||
|
||||
println!("Moving zig object `{}` to: {}", zig_object, dest_obj);
|
||||
println!("Moving zig object `{zig_object}` to: {dest_obj}");
|
||||
|
||||
// we store this .o file in rust's `target` folder (for wasm we need to leave a copy here too)
|
||||
fs::copy(src_obj, dest_obj).unwrap_or_else(|err| {
|
||||
|
@ -167,7 +167,7 @@ fn run_command(mut command: Command, flaky_fail_counter: usize) {
|
|||
false => {
|
||||
let error_str = match str::from_utf8(&output.stderr) {
|
||||
Ok(stderr) => stderr.to_string(),
|
||||
Err(_) => format!("Failed to run \"{}\"", command_str),
|
||||
Err(_) => format!("Failed to run \"{command_str}\""),
|
||||
};
|
||||
|
||||
// Flaky test errors that only occur sometimes on MacOS ci server.
|
||||
|
|
|
@ -25,18 +25,19 @@ pub fn build(b: *Builder) void {
|
|||
const host_target = b.standardTargetOptions(.{
|
||||
.default_target = CrossTarget{
|
||||
.cpu_model = .baseline,
|
||||
// TODO allow for native target for maximum speed
|
||||
},
|
||||
});
|
||||
const linux32_target = makeLinux32Target();
|
||||
const linux64_target = makeLinux64Target();
|
||||
const linux_x64_target = makeLinuxX64Target();
|
||||
const linux_aarch64_target = makeLinuxAarch64Target();
|
||||
const windows64_target = makeWindows64Target();
|
||||
const wasm32_target = makeWasm32Target();
|
||||
|
||||
// LLVM IR
|
||||
generateLlvmIrFile(b, mode, host_target, main_path, "ir", "builtins-host");
|
||||
generateLlvmIrFile(b, mode, linux32_target, main_path, "ir-i386", "builtins-i386");
|
||||
generateLlvmIrFile(b, mode, linux64_target, main_path, "ir-x86_64", "builtins-x86_64");
|
||||
generateLlvmIrFile(b, mode, linux_x64_target, main_path, "ir-x86_64", "builtins-x86_64");
|
||||
generateLlvmIrFile(b, mode, linux_aarch64_target, main_path, "ir-aarch64", "builtins-aarch64");
|
||||
generateLlvmIrFile(b, mode, windows64_target, main_path, "ir-windows-x86_64", "builtins-windows-x86_64");
|
||||
generateLlvmIrFile(b, mode, wasm32_target, main_path, "ir-wasm32", "builtins-wasm32");
|
||||
|
||||
|
@ -89,6 +90,7 @@ fn generateObjectFile(
|
|||
obj.strip = true;
|
||||
obj.target = target;
|
||||
obj.link_function_sections = true;
|
||||
obj.force_pic = true;
|
||||
const obj_step = b.step(step_name, "Build object file for linking");
|
||||
obj_step.dependOn(&obj.step);
|
||||
}
|
||||
|
@ -103,7 +105,17 @@ fn makeLinux32Target() CrossTarget {
|
|||
return target;
|
||||
}
|
||||
|
||||
fn makeLinux64Target() CrossTarget {
|
||||
fn makeLinuxAarch64Target() CrossTarget {
|
||||
var target = CrossTarget.parse(.{}) catch unreachable;
|
||||
|
||||
target.cpu_arch = std.Target.Cpu.Arch.aarch64;
|
||||
target.os_tag = std.Target.Os.Tag.linux;
|
||||
target.abi = std.Target.Abi.musl;
|
||||
|
||||
return target;
|
||||
}
|
||||
|
||||
fn makeLinuxX64Target() CrossTarget {
|
||||
var target = CrossTarget.parse(.{}) catch unreachable;
|
||||
|
||||
target.cpu_arch = std.Target.Cpu.Arch.x86_64;
|
||||
|
|
|
@ -18,6 +18,7 @@ const v2u64 = @Vector(2, u64);
|
|||
// Export it as weak incase it is already linked in by something else.
|
||||
comptime {
|
||||
@export(__muloti4, .{ .name = "__muloti4", .linkage = .Weak });
|
||||
@export(__lshrti3, .{ .name = "__lshrti3", .linkage = .Weak });
|
||||
if (want_windows_v2u64_abi) {
|
||||
@export(__divti3_windows_x86_64, .{ .name = "__divti3", .linkage = .Weak });
|
||||
@export(__modti3_windows_x86_64, .{ .name = "__modti3", .linkage = .Weak });
|
||||
|
@ -440,3 +441,47 @@ pub inline fn floatFractionalBits(comptime T: type) comptime_int {
|
|||
else => @compileError("unknown floating point type " ++ @typeName(T)),
|
||||
};
|
||||
}
|
||||
|
||||
pub fn __lshrti3(a: i128, b: i32) callconv(.C) i128 {
|
||||
return lshrXi3(i128, a, b);
|
||||
}
|
||||
|
||||
// Logical shift right: shift in 0 from left to right
|
||||
// Precondition: 0 <= b < T.bit_count
|
||||
inline fn lshrXi3(comptime T: type, a: T, b: i32) T {
|
||||
const word_t = HalveInt(T, false);
|
||||
const S = std.math.Log2Int(word_t.HalfT);
|
||||
|
||||
const input = word_t{ .all = a };
|
||||
var output: word_t = undefined;
|
||||
|
||||
if (b >= word_t.bits) {
|
||||
output.s.high = 0;
|
||||
output.s.low = input.s.high >> @intCast(S, b - word_t.bits);
|
||||
} else if (b == 0) {
|
||||
return a;
|
||||
} else {
|
||||
output.s.high = input.s.high >> @intCast(S, b);
|
||||
output.s.low = input.s.high << @intCast(S, word_t.bits - b);
|
||||
output.s.low |= input.s.low >> @intCast(S, b);
|
||||
}
|
||||
|
||||
return output.all;
|
||||
}
|
||||
|
||||
/// Allows to access underlying bits as two equally sized lower and higher
|
||||
/// signed or unsigned integers.
|
||||
fn HalveInt(comptime T: type, comptime signed_half: bool) type {
|
||||
return extern union {
|
||||
pub const bits = @divExact(@typeInfo(T).Int.bits, 2);
|
||||
pub const HalfTU = std.meta.Int(.unsigned, bits);
|
||||
pub const HalfTS = std.meta.Int(.signed, bits);
|
||||
pub const HalfT = if (signed_half) HalfTS else HalfTU;
|
||||
|
||||
all: T,
|
||||
s: if (native_endian == .Little)
|
||||
extern struct { low: HalfT, high: HalfT }
|
||||
else
|
||||
extern struct { high: HalfT, low: HalfT },
|
||||
};
|
||||
}
|
||||
|
|
|
@ -171,8 +171,8 @@ comptime {
|
|||
exportStrFn(str.fromUtf8RangeC, "from_utf8_range");
|
||||
exportStrFn(str.repeat, "repeat");
|
||||
exportStrFn(str.strTrim, "trim");
|
||||
exportStrFn(str.strTrimLeft, "trim_left");
|
||||
exportStrFn(str.strTrimRight, "trim_right");
|
||||
exportStrFn(str.strTrimStart, "trim_start");
|
||||
exportStrFn(str.strTrimEnd, "trim_end");
|
||||
exportStrFn(str.strCloneTo, "clone_to");
|
||||
exportStrFn(str.withCapacity, "with_capacity");
|
||||
exportStrFn(str.strGraphemes, "graphemes");
|
||||
|
@ -195,8 +195,10 @@ comptime {
|
|||
exportUtilsFn(utils.test_panic, "test_panic");
|
||||
exportUtilsFn(utils.increfRcPtrC, "incref_rc_ptr");
|
||||
exportUtilsFn(utils.decrefRcPtrC, "decref_rc_ptr");
|
||||
exportUtilsFn(utils.freeRcPtrC, "free_rc_ptr");
|
||||
exportUtilsFn(utils.increfDataPtrC, "incref_data_ptr");
|
||||
exportUtilsFn(utils.decrefDataPtrC, "decref_data_ptr");
|
||||
exportUtilsFn(utils.freeDataPtrC, "free_data_ptr");
|
||||
exportUtilsFn(utils.isUnique, "is_unique");
|
||||
exportUtilsFn(utils.decrefCheckNullC, "decref_check_null");
|
||||
exportUtilsFn(utils.allocateWithRefcountC, "allocate_with_refcount");
|
||||
|
|
|
@ -2302,7 +2302,7 @@ pub fn strTrim(input_string: RocStr) callconv(.C) RocStr {
|
|||
}
|
||||
}
|
||||
|
||||
pub fn strTrimLeft(input_string: RocStr) callconv(.C) RocStr {
|
||||
pub fn strTrimStart(input_string: RocStr) callconv(.C) RocStr {
|
||||
var string = input_string;
|
||||
|
||||
if (string.isEmpty()) {
|
||||
|
@ -2350,7 +2350,7 @@ pub fn strTrimLeft(input_string: RocStr) callconv(.C) RocStr {
|
|||
}
|
||||
}
|
||||
|
||||
pub fn strTrimRight(input_string: RocStr) callconv(.C) RocStr {
|
||||
pub fn strTrimEnd(input_string: RocStr) callconv(.C) RocStr {
|
||||
var string = input_string;
|
||||
|
||||
if (string.isEmpty()) {
|
||||
|
@ -2583,22 +2583,22 @@ test "strTrim: small to small" {
|
|||
try expect(trimmed.isSmallStr());
|
||||
}
|
||||
|
||||
test "strTrimLeft: empty" {
|
||||
const trimmedEmpty = strTrimLeft(RocStr.empty());
|
||||
test "strTrimStart: empty" {
|
||||
const trimmedEmpty = strTrimStart(RocStr.empty());
|
||||
try expect(trimmedEmpty.eq(RocStr.empty()));
|
||||
}
|
||||
|
||||
test "strTrimLeft: blank" {
|
||||
test "strTrimStart: blank" {
|
||||
const original_bytes = " ";
|
||||
const original = RocStr.init(original_bytes, original_bytes.len);
|
||||
defer original.decref();
|
||||
|
||||
const trimmed = strTrimLeft(original);
|
||||
const trimmed = strTrimStart(original);
|
||||
|
||||
try expect(trimmed.eq(RocStr.empty()));
|
||||
}
|
||||
|
||||
test "strTrimLeft: large to large" {
|
||||
test "strTrimStart: large to large" {
|
||||
const original_bytes = " hello even more giant world ";
|
||||
const original = RocStr.init(original_bytes, original_bytes.len);
|
||||
defer original.decref();
|
||||
|
@ -2611,12 +2611,12 @@ test "strTrimLeft: large to large" {
|
|||
|
||||
try expect(!expected.isSmallStr());
|
||||
|
||||
const trimmed = strTrimLeft(original);
|
||||
const trimmed = strTrimStart(original);
|
||||
|
||||
try expect(trimmed.eq(expected));
|
||||
}
|
||||
|
||||
test "strTrimLeft: large to small" {
|
||||
test "strTrimStart: large to small" {
|
||||
// `original` will be consumed by the concat; do not free explicitly
|
||||
const original_bytes = " hello ";
|
||||
const original = RocStr.init(original_bytes, original_bytes.len);
|
||||
|
@ -2629,14 +2629,14 @@ test "strTrimLeft: large to small" {
|
|||
|
||||
try expect(expected.isSmallStr());
|
||||
|
||||
const trimmed = strTrimLeft(original);
|
||||
const trimmed = strTrimStart(original);
|
||||
defer trimmed.decref();
|
||||
|
||||
try expect(trimmed.eq(expected));
|
||||
try expect(!trimmed.isSmallStr());
|
||||
}
|
||||
|
||||
test "strTrimLeft: small to small" {
|
||||
test "strTrimStart: small to small" {
|
||||
const original_bytes = " hello ";
|
||||
const original = RocStr.init(original_bytes, original_bytes.len);
|
||||
defer original.decref();
|
||||
|
@ -2649,28 +2649,28 @@ test "strTrimLeft: small to small" {
|
|||
|
||||
try expect(expected.isSmallStr());
|
||||
|
||||
const trimmed = strTrimLeft(original);
|
||||
const trimmed = strTrimStart(original);
|
||||
|
||||
try expect(trimmed.eq(expected));
|
||||
try expect(trimmed.isSmallStr());
|
||||
}
|
||||
|
||||
test "strTrimRight: empty" {
|
||||
const trimmedEmpty = strTrimRight(RocStr.empty());
|
||||
test "strTrimEnd: empty" {
|
||||
const trimmedEmpty = strTrimEnd(RocStr.empty());
|
||||
try expect(trimmedEmpty.eq(RocStr.empty()));
|
||||
}
|
||||
|
||||
test "strTrimRight: blank" {
|
||||
test "strTrimEnd: blank" {
|
||||
const original_bytes = " ";
|
||||
const original = RocStr.init(original_bytes, original_bytes.len);
|
||||
defer original.decref();
|
||||
|
||||
const trimmed = strTrimRight(original);
|
||||
const trimmed = strTrimEnd(original);
|
||||
|
||||
try expect(trimmed.eq(RocStr.empty()));
|
||||
}
|
||||
|
||||
test "strTrimRight: large to large" {
|
||||
test "strTrimEnd: large to large" {
|
||||
const original_bytes = " hello even more giant world ";
|
||||
const original = RocStr.init(original_bytes, original_bytes.len);
|
||||
defer original.decref();
|
||||
|
@ -2683,12 +2683,12 @@ test "strTrimRight: large to large" {
|
|||
|
||||
try expect(!expected.isSmallStr());
|
||||
|
||||
const trimmed = strTrimRight(original);
|
||||
const trimmed = strTrimEnd(original);
|
||||
|
||||
try expect(trimmed.eq(expected));
|
||||
}
|
||||
|
||||
test "strTrimRight: large to small" {
|
||||
test "strTrimEnd: large to small" {
|
||||
// `original` will be consumed by the concat; do not free explicitly
|
||||
const original_bytes = " hello ";
|
||||
const original = RocStr.init(original_bytes, original_bytes.len);
|
||||
|
@ -2701,14 +2701,14 @@ test "strTrimRight: large to small" {
|
|||
|
||||
try expect(expected.isSmallStr());
|
||||
|
||||
const trimmed = strTrimRight(original);
|
||||
const trimmed = strTrimEnd(original);
|
||||
defer trimmed.decref();
|
||||
|
||||
try expect(trimmed.eq(expected));
|
||||
try expect(!trimmed.isSmallStr());
|
||||
}
|
||||
|
||||
test "strTrimRight: small to small" {
|
||||
test "strTrimEnd: small to small" {
|
||||
const original_bytes = " hello ";
|
||||
const original = RocStr.init(original_bytes, original_bytes.len);
|
||||
defer original.decref();
|
||||
|
@ -2721,7 +2721,7 @@ test "strTrimRight: small to small" {
|
|||
|
||||
try expect(expected.isSmallStr());
|
||||
|
||||
const trimmed = strTrimRight(original);
|
||||
const trimmed = strTrimEnd(original);
|
||||
|
||||
try expect(trimmed.eq(expected));
|
||||
try expect(trimmed.isSmallStr());
|
||||
|
|
|
@ -220,6 +220,29 @@ pub fn increfDataPtrC(
|
|||
return increfRcPtrC(isizes, inc_amount);
|
||||
}
|
||||
|
||||
pub fn freeDataPtrC(
|
||||
bytes_or_null: ?[*]isize,
|
||||
alignment: u32,
|
||||
) callconv(.C) void {
|
||||
var bytes = bytes_or_null orelse return;
|
||||
|
||||
const ptr = @ptrToInt(bytes);
|
||||
const tag_mask: usize = if (@sizeOf(usize) == 8) 0b111 else 0b11;
|
||||
const masked_ptr = ptr & ~tag_mask;
|
||||
|
||||
const isizes: [*]isize = @intToPtr([*]isize, masked_ptr);
|
||||
|
||||
return freeRcPtrC(isizes - 1, alignment);
|
||||
}
|
||||
|
||||
pub fn freeRcPtrC(
|
||||
bytes_or_null: ?[*]isize,
|
||||
alignment: u32,
|
||||
) callconv(.C) void {
|
||||
var bytes = bytes_or_null orelse return;
|
||||
return free_ptr_to_refcount(bytes, alignment);
|
||||
}
|
||||
|
||||
pub fn decref(
|
||||
bytes_or_null: ?[*]u8,
|
||||
data_bytes: usize,
|
||||
|
@ -236,13 +259,23 @@ pub fn decref(
|
|||
decref_ptr_to_refcount(isizes - 1, alignment);
|
||||
}
|
||||
|
||||
inline fn decref_ptr_to_refcount(
|
||||
inline fn free_ptr_to_refcount(
|
||||
refcount_ptr: [*]isize,
|
||||
alignment: u32,
|
||||
) void {
|
||||
if (RC_TYPE == Refcount.none) return;
|
||||
const extra_bytes = std.math.max(alignment, @sizeOf(usize));
|
||||
|
||||
// NOTE: we don't even check whether the refcount is "infinity" here!
|
||||
dealloc(@ptrCast([*]u8, refcount_ptr) - (extra_bytes - @sizeOf(usize)), alignment);
|
||||
}
|
||||
|
||||
inline fn decref_ptr_to_refcount(
|
||||
refcount_ptr: [*]isize,
|
||||
alignment: u32,
|
||||
) void {
|
||||
if (RC_TYPE == Refcount.none) return;
|
||||
|
||||
if (DEBUG_INCDEC and builtin.target.cpu.arch != .wasm32) {
|
||||
std.debug.print("| decrement {*}: ", .{refcount_ptr});
|
||||
}
|
||||
|
@ -264,13 +297,13 @@ inline fn decref_ptr_to_refcount(
|
|||
}
|
||||
|
||||
if (refcount == REFCOUNT_ONE_ISIZE) {
|
||||
dealloc(@ptrCast([*]u8, refcount_ptr) - (extra_bytes - @sizeOf(usize)), alignment);
|
||||
free_ptr_to_refcount(refcount_ptr, alignment);
|
||||
}
|
||||
},
|
||||
Refcount.atomic => {
|
||||
var last = @atomicRmw(isize, &refcount_ptr[0], std.builtin.AtomicRmwOp.Sub, 1, Monotonic);
|
||||
if (last == REFCOUNT_ONE_ISIZE) {
|
||||
dealloc(@ptrCast([*]u8, refcount_ptr) - (extra_bytes - @sizeOf(usize)), alignment);
|
||||
free_ptr_to_refcount(refcount_ptr, alignment);
|
||||
}
|
||||
},
|
||||
Refcount.none => unreachable,
|
||||
|
|
|
@ -1,3 +1,7 @@
|
|||
## Most users won't need Box, it is used for:
|
||||
## - Holding unknown Roc types when developing [platforms](https://github.com/roc-lang/roc/wiki/Roc-concepts-explained#platform).
|
||||
## - To improve performance in rare cases.
|
||||
##
|
||||
interface Box
|
||||
exposes [box, unbox]
|
||||
imports []
|
||||
|
|
|
@ -7,6 +7,7 @@ interface Dict
|
|||
clear,
|
||||
capacity,
|
||||
len,
|
||||
isEmpty,
|
||||
get,
|
||||
contains,
|
||||
insert,
|
||||
|
@ -21,6 +22,8 @@ interface Dict
|
|||
insertAll,
|
||||
keepShared,
|
||||
removeAll,
|
||||
map,
|
||||
joinMap,
|
||||
]
|
||||
imports [
|
||||
Bool.{ Bool, Eq },
|
||||
|
@ -98,14 +101,14 @@ Dict k v := {
|
|||
data : List (k, v),
|
||||
size : Nat,
|
||||
} where k implements Hash & Eq
|
||||
implements [
|
||||
Eq {
|
||||
isEq,
|
||||
},
|
||||
Hash {
|
||||
hash: hashDict,
|
||||
},
|
||||
]
|
||||
implements [
|
||||
Eq {
|
||||
isEq,
|
||||
},
|
||||
Hash {
|
||||
hash: hashDict,
|
||||
},
|
||||
]
|
||||
|
||||
isEq : Dict k v, Dict k v -> Bool where k implements Hash & Eq, v implements Eq
|
||||
isEq = \xs, ys ->
|
||||
|
@ -139,12 +142,12 @@ empty = \{} ->
|
|||
## Returns the max number of elements the dictionary can hold before requiring a rehash.
|
||||
## ```
|
||||
## foodDict =
|
||||
## Dict.empty {}
|
||||
## |> Dict.insert "apple" "fruit"
|
||||
## Dict.empty {}
|
||||
## |> Dict.insert "apple" "fruit"
|
||||
##
|
||||
## capacityOfDict = Dict.capacity foodDict
|
||||
## ```
|
||||
capacity : Dict k v -> Nat where k implements Hash & Eq
|
||||
capacity : Dict * * -> Nat
|
||||
capacity = \@Dict { dataIndices } ->
|
||||
cap = List.len dataIndices
|
||||
|
||||
|
@ -192,10 +195,20 @@ fromList = \data ->
|
|||
## |> Dict.len
|
||||
## |> Bool.isEq 3
|
||||
## ```
|
||||
len : Dict k v -> Nat where k implements Hash & Eq
|
||||
len : Dict * * -> Nat
|
||||
len = \@Dict { size } ->
|
||||
size
|
||||
|
||||
## Check if the dictinoary is empty.
|
||||
## ```
|
||||
## Dict.isEmpty (Dict.empty {} |> Dict.insert "key" 42)
|
||||
##
|
||||
## Dict.isEmpty (Dict.empty {})
|
||||
## ```
|
||||
isEmpty : Dict * * -> Bool
|
||||
isEmpty = \@Dict { size } ->
|
||||
size == 0
|
||||
|
||||
## Clears all elements from a dictionary keeping around the allocation if it isn't huge.
|
||||
## ```
|
||||
## songs =
|
||||
|
@ -225,6 +238,30 @@ clear = \@Dict { metadata, dataIndices, data } ->
|
|||
size: 0,
|
||||
}
|
||||
|
||||
## Convert each value in the dictionary to something new, by calling a conversion
|
||||
## function on each of them which receives both the key and the old value. Then return a
|
||||
## new dictionary containing the same keys and the converted values.
|
||||
map : Dict k a, (k, a -> b) -> Dict k b
|
||||
where k implements Hash & Eq, b implements Hash & Eq
|
||||
map = \dict, transform ->
|
||||
init = withCapacity (capacity dict)
|
||||
|
||||
walk dict init \answer, k, v ->
|
||||
insert answer k (transform k v)
|
||||
|
||||
## Like [Dict.map], except the transformation function wraps the return value
|
||||
## in a dictionary. At the end, all the dictionaries get joined together
|
||||
## (using [Dict.insertAll]) into one dictionary.
|
||||
##
|
||||
## You may know a similar function named `concatMap` in other languages.
|
||||
joinMap : Dict a b, (a, b -> Dict x y) -> Dict x y
|
||||
where a implements Hash & Eq, x implements Hash & Eq
|
||||
joinMap = \dict, transform ->
|
||||
init = withCapacity (capacity dict) # Might be a pessimization
|
||||
|
||||
walk dict init \answer, k, v ->
|
||||
insertAll answer (transform k v)
|
||||
|
||||
## Iterate through the keys and values in the dictionary and call the provided
|
||||
## function with signature `state, k, v -> state` for each value, with an
|
||||
## initial `state` value provided for the first call.
|
||||
|
@ -976,16 +1013,16 @@ expect
|
|||
# TODO: Add a builtin to distinguish big endian systems and change loading orders.
|
||||
# TODO: Switch out Wymum on systems with slow 128bit multiplication.
|
||||
LowLevelHasher := { originalSeed : U64, state : U64 } implements [
|
||||
Hasher {
|
||||
addBytes,
|
||||
addU8,
|
||||
addU16,
|
||||
addU32,
|
||||
addU64,
|
||||
addU128,
|
||||
complete,
|
||||
},
|
||||
]
|
||||
Hasher {
|
||||
addBytes,
|
||||
addU8,
|
||||
addU16,
|
||||
addU32,
|
||||
addU64,
|
||||
addU128,
|
||||
complete,
|
||||
},
|
||||
]
|
||||
|
||||
# unsafe primitive that does not perform a bounds check
|
||||
# TODO hide behind an InternalList.roc module
|
||||
|
|
|
@ -208,6 +208,9 @@ interface List
|
|||
## * Even when copying is faster, other list operations may still be slightly slower with persistent data structures. For example, even if it were a persistent data structure, [List.map], [List.walk], and [List.keepIf] would all need to traverse every element in the list and build up the result from scratch. These operations are all
|
||||
## * Roc's compiler optimizes many list operations into in-place mutations behind the scenes, depending on how the list is being used. For example, [List.map], [List.keepIf], and [List.set] can all be optimized to perform in-place mutations.
|
||||
## * If possible, it is usually best for performance to use large lists in a way where the optimizer can turn them into in-place mutations. If this is not possible, a persistent data structure might be faster - but this is a rare enough scenario that it would not be good for the average Roc program's performance if this were the way [List] worked by default. Instead, you can look outside Roc's standard modules for an implementation of a persistent data structure - likely built using [List] under the hood!
|
||||
|
||||
# separator so List.isEmpty doesn't absorb the above into its doc comment
|
||||
|
||||
## Check if the list is empty.
|
||||
## ```
|
||||
## List.isEmpty [1, 2, 3]
|
||||
|
@ -222,6 +225,13 @@ isEmpty = \list ->
|
|||
# but will cause a reference count increment on the value it got out of the list
|
||||
getUnsafe : List a, Nat -> a
|
||||
|
||||
## Returns an element from a list at the given index.
|
||||
##
|
||||
## Returns `Err OutOfBounds` if the given index exceeds the List's length
|
||||
## ```
|
||||
## expect List.get [100, 200, 300] 1 == Ok 200
|
||||
## expect List.get [100, 200, 300] 5 == Err OutOfBounds
|
||||
## ```
|
||||
get : List a, Nat -> Result a [OutOfBounds]
|
||||
get = \list, index ->
|
||||
if index < List.len list then
|
||||
|
@ -352,6 +362,10 @@ releaseExcessCapacity : List a -> List a
|
|||
concat : List a, List a -> List a
|
||||
|
||||
## Returns the last element in the list, or `ListWasEmpty` if it was empty.
|
||||
## ```
|
||||
## expect List.last [1, 2, 3] == Ok 3
|
||||
## expect List.last [] == Err ListWasEmpty
|
||||
## ```
|
||||
last : List a -> Result a [ListWasEmpty]
|
||||
last = \list ->
|
||||
when List.get list (Num.subSaturated (List.len list) 1) is
|
||||
|
@ -383,7 +397,7 @@ repeatHelp = \value, count, accum ->
|
|||
|
||||
## Returns the list with its elements reversed.
|
||||
## ```
|
||||
## List.reverse [1, 2, 3]
|
||||
## expect List.reverse [1, 2, 3] == [3, 2, 1]
|
||||
## ```
|
||||
reverse : List a -> List a
|
||||
reverse = \list ->
|
||||
|
@ -397,9 +411,9 @@ reverseHelp = \list, left, right ->
|
|||
|
||||
## Join the given lists together into one list.
|
||||
## ```
|
||||
## List.join [[1, 2, 3], [4, 5], [], [6, 7]]
|
||||
## List.join [[], []]
|
||||
## List.join []
|
||||
## expect List.join [[1], [2, 3], [], [4, 5]] == [1, 2, 3, 4, 5]
|
||||
## expect List.join [[], []] == []
|
||||
## expect List.join [] == []
|
||||
## ```
|
||||
join : List (List a) -> List a
|
||||
join = \lists ->
|
||||
|
@ -597,6 +611,10 @@ dropIf = \list, predicate ->
|
|||
|
||||
## Run the given function on each element of a list, and return the
|
||||
## number of elements for which the function returned `Bool.true`.
|
||||
## ```
|
||||
## expect List.countIf [1, -2, -3] Num.isNegative == 2
|
||||
## expect List.countIf [1, 2, 3] (\num -> num > 1 ) == 2
|
||||
## ```
|
||||
countIf : List a, (a -> Bool) -> Nat
|
||||
countIf = \list, predicate ->
|
||||
walkState = \state, elem ->
|
||||
|
@ -610,11 +628,12 @@ countIf = \list, predicate ->
|
|||
## This works like [List.map], except only the transformed values that are
|
||||
## wrapped in `Ok` are kept. Any that are wrapped in `Err` are dropped.
|
||||
## ```
|
||||
## List.keepOks [["a", "b"], [], [], ["c", "d", "e"]] List.last
|
||||
## expect List.keepOks ["1", "Two", "23", "Bird"] Str.toI32 == [1, 23]
|
||||
##
|
||||
## fn = \str -> if Str.isEmpty str then Err StrWasEmpty else Ok (Str.len str)
|
||||
## expect List.keepOks [["a", "b"], [], ["c", "d", "e"], [] ] List.first == ["a", "c"]
|
||||
##
|
||||
## List.keepOks ["", "a", "bc", "", "d", "ef", ""]
|
||||
## fn = \str -> if Str.isEmpty str then Err StrWasEmpty else Ok str
|
||||
## expect List.keepOks ["", "a", "bc", "", "d", "ef", ""] fn == ["a", "bc", "d", "ef"]
|
||||
## ```
|
||||
keepOks : List before, (before -> Result after *) -> List after
|
||||
keepOks = \list, toResult ->
|
||||
|
@ -646,9 +665,9 @@ keepErrs = \list, toResult ->
|
|||
## Convert each element in the list to something new, by calling a conversion
|
||||
## function on each of them. Then return a new list of the converted values.
|
||||
## ```
|
||||
## List.map [1, 2, 3] (\num -> num + 1)
|
||||
## expect List.map [1, 2, 3] (\num -> num + 1) == [2, 3, 4]
|
||||
##
|
||||
## List.map ["", "a", "bc"] Str.isEmpty
|
||||
## expect List.map ["", "a", "bc"] Str.isEmpty == [Bool.true, Bool.false, Bool.false]
|
||||
## ```
|
||||
map : List a, (a -> b) -> List b
|
||||
|
||||
|
@ -675,6 +694,9 @@ map4 : List a, List b, List c, List d, (a, b, c, d -> e) -> List e
|
|||
|
||||
## This works like [List.map], except it also passes the index
|
||||
## of the element to the conversion function.
|
||||
## ```
|
||||
## expect List.mapWithIndex [10, 20, 30] (\num, index -> num + index) == [10, 21, 32]
|
||||
## ```
|
||||
mapWithIndex : List a, (a, Nat -> b) -> List b
|
||||
mapWithIndex = \src, func ->
|
||||
length = len src
|
||||
|
@ -1160,8 +1182,17 @@ mapTry = \list, toResult ->
|
|||
Result.map (toResult elem) \ok ->
|
||||
List.append state ok
|
||||
|
||||
## This is the same as `iterate` but with [Result] instead of `[Continue, Break]`.
|
||||
## Using `Result` saves a conditional in `mapTry`.
|
||||
## Same as [List.walk], except you can stop walking early by returning `Err`.
|
||||
##
|
||||
## ## Performance Details
|
||||
##
|
||||
## Compared to [List.walk], this can potentially visit fewer elements (which can
|
||||
## improve performance) at the cost of making each step take longer.
|
||||
## However, the added cost to each step is extremely small, and can easily
|
||||
## be outweighed if it results in skipping even a small number of elements.
|
||||
##
|
||||
## As such, it is typically better for performance to use this over [List.walk]
|
||||
## if returning `Break` earlier than the last element is expected to be common.
|
||||
walkTry : List elem, state, (state, elem -> Result state err) -> Result state err
|
||||
walkTry = \list, init, func ->
|
||||
walkTryHelp list init func 0 (List.len list)
|
||||
|
|
|
@ -33,12 +33,17 @@ interface Num
|
|||
Decimal,
|
||||
Binary32,
|
||||
Binary64,
|
||||
e,
|
||||
pi,
|
||||
tau,
|
||||
abs,
|
||||
absDiff,
|
||||
neg,
|
||||
add,
|
||||
sub,
|
||||
mul,
|
||||
min,
|
||||
max,
|
||||
isLt,
|
||||
isLte,
|
||||
isGt,
|
||||
|
@ -81,6 +86,7 @@ interface Num
|
|||
bitwiseAnd,
|
||||
bitwiseXor,
|
||||
bitwiseOr,
|
||||
bitwiseNot,
|
||||
shiftLeftBy,
|
||||
shiftRightBy,
|
||||
shiftRightZfBy,
|
||||
|
@ -492,6 +498,18 @@ F32 : Num (FloatingPoint Binary32)
|
|||
## [sqrt] and trigonometry are massively slower with [Dec] than with [F64].
|
||||
Dec : Num (FloatingPoint Decimal)
|
||||
|
||||
## Euler's number (e)
|
||||
e : Frac *
|
||||
e = 2.71828182845904523536028747135266249775724709369995
|
||||
|
||||
## Archimedes' constant (π)
|
||||
pi : Frac *
|
||||
pi = 3.14159265358979323846264338327950288419716939937510
|
||||
|
||||
## Circle constant (τ)
|
||||
tau : Frac *
|
||||
tau = 2 * pi
|
||||
|
||||
# ------- Functions
|
||||
## Convert a number to a [Str].
|
||||
##
|
||||
|
@ -777,6 +795,34 @@ sub : Num a, Num a -> Num a
|
|||
## ∞ or -∞. For all other number types, overflow results in a panic.
|
||||
mul : Num a, Num a -> Num a
|
||||
|
||||
## Obtains the smaller between two numbers of the same type.
|
||||
##
|
||||
## ```
|
||||
## Num.min 100 0
|
||||
##
|
||||
## Num.min 3.0 -3.0
|
||||
## ```
|
||||
min : Num a, Num a -> Num a
|
||||
min = \a, b ->
|
||||
if a < b then
|
||||
a
|
||||
else
|
||||
b
|
||||
|
||||
## Obtains the greater between two numbers of the same type.
|
||||
##
|
||||
## ```
|
||||
## Num.max 100 0
|
||||
##
|
||||
## Num.max 3.0 -3.0
|
||||
## ```
|
||||
max : Num a, Num a -> Num a
|
||||
max = \a, b ->
|
||||
if a > b then
|
||||
a
|
||||
else
|
||||
b
|
||||
|
||||
sin : Frac a -> Frac a
|
||||
cos : Frac a -> Frac a
|
||||
|
||||
|
@ -930,10 +976,25 @@ remChecked = \a, b ->
|
|||
|
||||
isMultipleOf : Int a, Int a -> Bool
|
||||
|
||||
## Does a "bitwise and". Each bit of the output is 1 if the corresponding bit
|
||||
## of x AND of y is 1, otherwise it's 0.
|
||||
bitwiseAnd : Int a, Int a -> Int a
|
||||
|
||||
## Does a "bitwise exclusive or". Each bit of the output is the same as the
|
||||
## corresponding bit in x if that bit in y is 0, and it's the complement of
|
||||
## the bit in x if that bit in y is 1.
|
||||
bitwiseXor : Int a, Int a -> Int a
|
||||
|
||||
## Does a "bitwise or". Each bit of the output is 0 if the corresponding bit
|
||||
## of x OR of y is 0, otherwise it's 1.
|
||||
bitwiseOr : Int a, Int a -> Int a
|
||||
|
||||
## Returns the complement of x - the number you get by switching each 1 for a
|
||||
## 0 and each 0 for a 1. This is the same as -x - 1.
|
||||
bitwiseNot : Int a -> Int a
|
||||
bitwiseNot = \n ->
|
||||
bitwiseXor n (subWrap 0 1)
|
||||
|
||||
## Bitwise left shift of a number by another
|
||||
##
|
||||
## The least significant bits always become 0. This means that shifting left is
|
||||
|
|
|
@ -42,8 +42,8 @@ withDefault = \result, default ->
|
|||
## function on it. Then returns a new `Ok` holding the transformed value. If the
|
||||
## result is `Err`, this has no effect. Use [mapErr] to transform an `Err`.
|
||||
## ```
|
||||
## Result.map (Ok 12) Num.negate
|
||||
## Result.map (Err "yipes!") Num.negate
|
||||
## Result.map (Ok 12) Num.neg
|
||||
## Result.map (Err "yipes!") Num.neg
|
||||
## ```
|
||||
##
|
||||
## Functions like `map` are common in Roc; see for example [List.map],
|
||||
|
|
|
@ -7,6 +7,8 @@ interface Set
|
|||
walkUntil,
|
||||
insert,
|
||||
len,
|
||||
isEmpty,
|
||||
capacity,
|
||||
remove,
|
||||
contains,
|
||||
toList,
|
||||
|
@ -14,6 +16,8 @@ interface Set
|
|||
union,
|
||||
intersection,
|
||||
difference,
|
||||
map,
|
||||
joinMap,
|
||||
]
|
||||
imports [
|
||||
List,
|
||||
|
@ -26,14 +30,14 @@ interface Set
|
|||
## Provides a [set](https://en.wikipedia.org/wiki/Set_(abstract_data_type))
|
||||
## type which stores a collection of unique values, without any ordering
|
||||
Set k := Dict.Dict k {} where k implements Hash & Eq
|
||||
implements [
|
||||
Eq {
|
||||
isEq,
|
||||
},
|
||||
Hash {
|
||||
hash: hashSet,
|
||||
},
|
||||
]
|
||||
implements [
|
||||
Eq {
|
||||
isEq,
|
||||
},
|
||||
Hash {
|
||||
hash: hashSet,
|
||||
},
|
||||
]
|
||||
|
||||
isEq : Set k, Set k -> Bool where k implements Hash & Eq
|
||||
isEq = \xs, ys ->
|
||||
|
@ -59,6 +63,13 @@ hashSet = \hasher, @Set inner -> Hash.hash hasher inner
|
|||
empty : {} -> Set k where k implements Hash & Eq
|
||||
empty = \{} -> @Set (Dict.empty {})
|
||||
|
||||
## Return a dictionary with space allocated for a number of entries. This
|
||||
## may provide a performance optimization if you know how many entries will be
|
||||
## inserted.
|
||||
withCapacity : Nat -> Set k where k implements Hash & Eq
|
||||
withCapacity = \cap ->
|
||||
@Set (Dict.withCapacity cap)
|
||||
|
||||
## Creates a new `Set` with a single value.
|
||||
## ```
|
||||
## singleItemSet = Set.single "Apple"
|
||||
|
@ -115,10 +126,32 @@ expect
|
|||
##
|
||||
## expect countValues == 3
|
||||
## ```
|
||||
len : Set k -> Nat where k implements Hash & Eq
|
||||
len : Set * -> Nat
|
||||
len = \@Set dict ->
|
||||
Dict.len dict
|
||||
|
||||
## Returns the max number of elements the set can hold before requiring a rehash.
|
||||
## ```
|
||||
## foodSet =
|
||||
## Set.empty {}
|
||||
## |> Set.insert "apple"
|
||||
##
|
||||
## capacityOfSet = Set.capacity foodSet
|
||||
## ```
|
||||
capacity : Set * -> Nat
|
||||
capacity = \@Set dict ->
|
||||
Dict.capacity dict
|
||||
|
||||
## Check if the set is empty.
|
||||
## ```
|
||||
## Set.isEmpty (Set.empty {} |> Set.insert 42)
|
||||
##
|
||||
## Set.isEmpty (Set.empty {})
|
||||
## ```
|
||||
isEmpty : Set * -> Bool
|
||||
isEmpty = \@Set dict ->
|
||||
Dict.isEmpty dict
|
||||
|
||||
# Inserting a duplicate key has no effect on length.
|
||||
expect
|
||||
actual =
|
||||
|
@ -261,6 +294,28 @@ walk : Set k, state, (state, k -> state) -> state where k implements Hash & Eq
|
|||
walk = \@Set dict, state, step ->
|
||||
Dict.walk dict state (\s, k, _ -> step s k)
|
||||
|
||||
## Convert each value in the set to something new, by calling a conversion
|
||||
## function on each of them which receives the old value. Then return a
|
||||
## new set containing the converted values.
|
||||
map : Set a, (a -> b) -> Set b where a implements Hash & Eq, b implements Hash & Eq
|
||||
map = \set, transform ->
|
||||
init = withCapacity (capacity set)
|
||||
|
||||
walk set init \answer, k ->
|
||||
insert answer (transform k)
|
||||
|
||||
## Like [Set.map], except the transformation function wraps the return value
|
||||
## in a set. At the end, all the sets get joined together
|
||||
## (using [Set.union]) into one set.
|
||||
##
|
||||
## You may know a similar function named `concatMap` in other languages.
|
||||
joinMap : Set a, (a -> Set b) -> Set b where a implements Hash & Eq, b implements Hash & Eq
|
||||
joinMap = \set, transform ->
|
||||
init = withCapacity (capacity set) # Might be a pessimization
|
||||
|
||||
walk set init \answer, k ->
|
||||
union answer (transform k)
|
||||
|
||||
## Iterate through the values of a given `Set` and build a value, can stop
|
||||
## iterating part way through the collection.
|
||||
## ```
|
||||
|
|
|
@ -103,8 +103,8 @@ interface Str
|
|||
startsWith,
|
||||
endsWith,
|
||||
trim,
|
||||
trimLeft,
|
||||
trimRight,
|
||||
trimStart,
|
||||
trimEnd,
|
||||
toDec,
|
||||
toF64,
|
||||
toF32,
|
||||
|
@ -448,15 +448,15 @@ trim : Str -> Str
|
|||
|
||||
## Return the [Str] with all whitespace removed from the beginning.
|
||||
## ```
|
||||
## expect Str.trimLeft " Hello \n\n" == "Hello \n\n"
|
||||
## expect Str.trimStart " Hello \n\n" == "Hello \n\n"
|
||||
## ```
|
||||
trimLeft : Str -> Str
|
||||
trimStart : Str -> Str
|
||||
|
||||
## Return the [Str] with all whitespace removed from the end.
|
||||
## ```
|
||||
## expect Str.trimRight " Hello \n\n" == " Hello"
|
||||
## expect Str.trimEnd " Hello \n\n" == " Hello"
|
||||
## ```
|
||||
trimRight : Str -> Str
|
||||
trimEnd : Str -> Str
|
||||
|
||||
## Encode a [Str] to a [Dec]. A [Dec] value is a 128-bit decimal
|
||||
## [fixed-point number](https://en.wikipedia.org/wiki/Fixed-point_arithmetic).
|
||||
|
@ -638,12 +638,13 @@ countUtf8Bytes : Str -> Nat
|
|||
substringUnsafe : Str, Nat, Nat -> Str
|
||||
|
||||
## Returns the given [Str] with each occurrence of a substring replaced.
|
||||
## Returns [Err NotFound] if the substring is not found.
|
||||
## If the substring is not found, returns the original string.
|
||||
##
|
||||
## ```
|
||||
## expect Str.replaceEach "foo/bar/baz" "/" "_" == Ok "foo_bar_baz"
|
||||
## expect Str.replaceEach "not here" "/" "_" == Err NotFound
|
||||
## expect Str.replaceEach "foo/bar/baz" "/" "_" == "foo_bar_baz"
|
||||
## expect Str.replaceEach "not here" "/" "_" == "not here"
|
||||
## ```
|
||||
replaceEach : Str, Str, Str -> Result Str [NotFound]
|
||||
replaceEach : Str, Str, Str -> Str
|
||||
replaceEach = \haystack, needle, flower ->
|
||||
when splitFirst haystack needle is
|
||||
Ok { before, after } ->
|
||||
|
@ -653,9 +654,8 @@ replaceEach = \haystack, needle, flower ->
|
|||
|> Str.concat before
|
||||
|> Str.concat flower
|
||||
|> replaceEachHelp after needle flower
|
||||
|> Ok
|
||||
|
||||
Err err -> Err err
|
||||
Err NotFound -> haystack
|
||||
|
||||
replaceEachHelp : Str, Str, Str, Str -> Str
|
||||
replaceEachHelp = \buf, haystack, needle, flower ->
|
||||
|
@ -668,39 +668,44 @@ replaceEachHelp = \buf, haystack, needle, flower ->
|
|||
|
||||
Err NotFound -> Str.concat buf haystack
|
||||
|
||||
expect Str.replaceEach "abXdeXghi" "X" "_" == Ok "ab_de_ghi"
|
||||
expect Str.replaceEach "abXdeXghi" "X" "_" == "ab_de_ghi"
|
||||
expect Str.replaceEach "abcdefg" "nothing" "_" == "abcdefg"
|
||||
|
||||
## Returns the given [Str] with the first occurrence of a substring replaced.
|
||||
## Returns [Err NotFound] if the substring is not found.
|
||||
## If the substring is not found, returns the original string.
|
||||
##
|
||||
## ```
|
||||
## expect Str.replaceFirst "foo/bar/baz" "/" "_" == Ok "foo_bar/baz"
|
||||
## expect Str.replaceFirst "no slashes here" "/" "_" == Err NotFound
|
||||
## expect Str.replaceFirst "foo/bar/baz" "/" "_" == "foo_bar/baz"
|
||||
## expect Str.replaceFirst "no slashes here" "/" "_" == "no slashes here"
|
||||
## ```
|
||||
replaceFirst : Str, Str, Str -> Result Str [NotFound]
|
||||
replaceFirst : Str, Str, Str -> Str
|
||||
replaceFirst = \haystack, needle, flower ->
|
||||
when splitFirst haystack needle is
|
||||
Ok { before, after } ->
|
||||
Ok "\(before)\(flower)\(after)"
|
||||
"\(before)\(flower)\(after)"
|
||||
|
||||
Err err -> Err err
|
||||
Err NotFound -> haystack
|
||||
|
||||
expect Str.replaceFirst "abXdeXghi" "X" "_" == Ok "ab_deXghi"
|
||||
expect Str.replaceFirst "abXdeXghi" "X" "_" == "ab_deXghi"
|
||||
expect Str.replaceFirst "abcdefg" "nothing" "_" == "abcdefg"
|
||||
|
||||
## Returns the given [Str] with the last occurrence of a substring replaced.
|
||||
## Returns [Err NotFound] if the substring is not found.
|
||||
## If the substring is not found, returns the original string.
|
||||
##
|
||||
## ```
|
||||
## expect Str.replaceLast "foo/bar/baz" "/" "_" == Ok "foo/bar_baz"
|
||||
## expect Str.replaceLast "no slashes here" "/" "_" == Err NotFound
|
||||
## expect Str.replaceLast "foo/bar/baz" "/" "_" == "foo/bar_baz"
|
||||
## expect Str.replaceLast "no slashes here" "/" "_" == "no slashes here"
|
||||
## ```
|
||||
replaceLast : Str, Str, Str -> Result Str [NotFound]
|
||||
replaceLast : Str, Str, Str -> Str
|
||||
replaceLast = \haystack, needle, flower ->
|
||||
when splitLast haystack needle is
|
||||
Ok { before, after } ->
|
||||
Ok "\(before)\(flower)\(after)"
|
||||
"\(before)\(flower)\(after)"
|
||||
|
||||
Err err -> Err err
|
||||
Err NotFound -> haystack
|
||||
|
||||
expect Str.replaceLast "abXdeXghi" "X" "_" == Ok "abXde_ghi"
|
||||
expect Str.replaceLast "abXdeXghi" "X" "_" == "abXde_ghi"
|
||||
expect Str.replaceLast "abcdefg" "nothing" "_" == "abcdefg"
|
||||
|
||||
## Returns the given [Str] before the first occurrence of a [delimiter](https://www.computerhope.com/jargon/d/delimite.htm), as well
|
||||
## as the rest of the string after that occurrence.
|
||||
|
|
|
@ -44,49 +44,49 @@ interface TotallyNotJson
|
|||
## An opaque type with the `EncoderFormatting` and
|
||||
## `DecoderFormatting` abilities.
|
||||
Json := { fieldNameMapping : FieldNameMapping }
|
||||
implements [
|
||||
EncoderFormatting {
|
||||
u8: encodeU8,
|
||||
u16: encodeU16,
|
||||
u32: encodeU32,
|
||||
u64: encodeU64,
|
||||
u128: encodeU128,
|
||||
i8: encodeI8,
|
||||
i16: encodeI16,
|
||||
i32: encodeI32,
|
||||
i64: encodeI64,
|
||||
i128: encodeI128,
|
||||
f32: encodeF32,
|
||||
f64: encodeF64,
|
||||
dec: encodeDec,
|
||||
bool: encodeBool,
|
||||
string: encodeString,
|
||||
list: encodeList,
|
||||
record: encodeRecord,
|
||||
tuple: encodeTuple,
|
||||
tag: encodeTag,
|
||||
},
|
||||
DecoderFormatting {
|
||||
u8: decodeU8,
|
||||
u16: decodeU16,
|
||||
u32: decodeU32,
|
||||
u64: decodeU64,
|
||||
u128: decodeU128,
|
||||
i8: decodeI8,
|
||||
i16: decodeI16,
|
||||
i32: decodeI32,
|
||||
i64: decodeI64,
|
||||
i128: decodeI128,
|
||||
f32: decodeF32,
|
||||
f64: decodeF64,
|
||||
dec: decodeDec,
|
||||
bool: decodeBool,
|
||||
string: decodeString,
|
||||
list: decodeList,
|
||||
record: decodeRecord,
|
||||
tuple: decodeTuple,
|
||||
},
|
||||
]
|
||||
implements [
|
||||
EncoderFormatting {
|
||||
u8: encodeU8,
|
||||
u16: encodeU16,
|
||||
u32: encodeU32,
|
||||
u64: encodeU64,
|
||||
u128: encodeU128,
|
||||
i8: encodeI8,
|
||||
i16: encodeI16,
|
||||
i32: encodeI32,
|
||||
i64: encodeI64,
|
||||
i128: encodeI128,
|
||||
f32: encodeF32,
|
||||
f64: encodeF64,
|
||||
dec: encodeDec,
|
||||
bool: encodeBool,
|
||||
string: encodeString,
|
||||
list: encodeList,
|
||||
record: encodeRecord,
|
||||
tuple: encodeTuple,
|
||||
tag: encodeTag,
|
||||
},
|
||||
DecoderFormatting {
|
||||
u8: decodeU8,
|
||||
u16: decodeU16,
|
||||
u32: decodeU32,
|
||||
u64: decodeU64,
|
||||
u128: decodeU128,
|
||||
i8: decodeI8,
|
||||
i16: decodeI16,
|
||||
i32: decodeI32,
|
||||
i64: decodeI64,
|
||||
i128: decodeI128,
|
||||
f32: decodeF32,
|
||||
f64: decodeF64,
|
||||
dec: decodeDec,
|
||||
bool: decodeBool,
|
||||
string: decodeString,
|
||||
list: decodeList,
|
||||
record: decodeRecord,
|
||||
tuple: decodeTuple,
|
||||
},
|
||||
]
|
||||
|
||||
## Returns a JSON `Encoder` and `Decoder`
|
||||
json = @Json { fieldNameMapping: Default }
|
||||
|
|
|
@ -339,8 +339,8 @@ pub const STR_TO_UTF8: &str = "roc_builtins.str.to_utf8";
|
|||
pub const STR_FROM_UTF8_RANGE: &str = "roc_builtins.str.from_utf8_range";
|
||||
pub const STR_REPEAT: &str = "roc_builtins.str.repeat";
|
||||
pub const STR_TRIM: &str = "roc_builtins.str.trim";
|
||||
pub const STR_TRIM_LEFT: &str = "roc_builtins.str.trim_left";
|
||||
pub const STR_TRIM_RIGHT: &str = "roc_builtins.str.trim_right";
|
||||
pub const STR_TRIM_START: &str = "roc_builtins.str.trim_start";
|
||||
pub const STR_TRIM_END: &str = "roc_builtins.str.trim_end";
|
||||
pub const STR_GET_UNSAFE: &str = "roc_builtins.str.get_unsafe";
|
||||
pub const STR_RESERVE: &str = "roc_builtins.str.reserve";
|
||||
pub const STR_APPEND_SCALAR: &str = "roc_builtins.str.append_scalar";
|
||||
|
@ -393,8 +393,10 @@ pub const UTILS_TEST_PANIC: &str = "roc_builtins.utils.test_panic";
|
|||
pub const UTILS_ALLOCATE_WITH_REFCOUNT: &str = "roc_builtins.utils.allocate_with_refcount";
|
||||
pub const UTILS_INCREF_RC_PTR: &str = "roc_builtins.utils.incref_rc_ptr";
|
||||
pub const UTILS_DECREF_RC_PTR: &str = "roc_builtins.utils.decref_rc_ptr";
|
||||
pub const UTILS_FREE_RC_PTR: &str = "roc_builtins.utils.free_rc_ptr";
|
||||
pub const UTILS_INCREF_DATA_PTR: &str = "roc_builtins.utils.incref_data_ptr";
|
||||
pub const UTILS_DECREF_DATA_PTR: &str = "roc_builtins.utils.decref_data_ptr";
|
||||
pub const UTILS_FREE_DATA_PTR: &str = "roc_builtins.utils.free_data_ptr";
|
||||
pub const UTILS_IS_UNIQUE: &str = "roc_builtins.utils.is_unique";
|
||||
pub const UTILS_DECREF_CHECK_NULL: &str = "roc_builtins.utils.decref_check_null";
|
||||
pub const UTILS_DICT_PSEUDO_SEED: &str = "roc_builtins.utils.dict_pseudo_seed";
|
||||
|
|
|
@ -469,8 +469,7 @@ impl IAbilitiesStore<Resolved> {
|
|||
|
||||
debug_assert!(
|
||||
old_specialization.is_none(),
|
||||
"Existing resolution: {:?}",
|
||||
old_specialization
|
||||
"Existing resolution: {old_specialization:?}"
|
||||
);
|
||||
}
|
||||
|
||||
|
|
|
@ -847,29 +847,17 @@ fn can_annotation_help(
|
|||
let alias = scope.lookup_alias(symbol).unwrap();
|
||||
local_aliases.insert(symbol, alias.clone());
|
||||
|
||||
if vars.is_empty() && env.home == symbol.module_id() {
|
||||
let actual_var = var_store.fresh();
|
||||
introduced_variables.insert_host_exposed_alias(symbol, actual_var);
|
||||
Type::HostExposedAlias {
|
||||
name: symbol,
|
||||
type_arguments: vars,
|
||||
lambda_set_variables: alias.lambda_set_variables.clone(),
|
||||
actual: Box::new(alias.typ.clone()),
|
||||
actual_var,
|
||||
}
|
||||
} else {
|
||||
Type::Alias {
|
||||
symbol,
|
||||
type_arguments: vars.into_iter().map(OptAbleType::unbound).collect(),
|
||||
lambda_set_variables: alias.lambda_set_variables.clone(),
|
||||
infer_ext_in_output_types: alias
|
||||
.infer_ext_in_output_variables
|
||||
.iter()
|
||||
.map(|v| Type::Variable(*v))
|
||||
.collect(),
|
||||
actual: Box::new(alias.typ.clone()),
|
||||
kind: alias.kind,
|
||||
}
|
||||
Type::Alias {
|
||||
symbol,
|
||||
type_arguments: vars.into_iter().map(OptAbleType::unbound).collect(),
|
||||
lambda_set_variables: alias.lambda_set_variables.clone(),
|
||||
infer_ext_in_output_types: alias
|
||||
.infer_ext_in_output_variables
|
||||
.iter()
|
||||
.map(|v| Type::Variable(*v))
|
||||
.collect(),
|
||||
actual: Box::new(alias.typ.clone()),
|
||||
kind: alias.kind,
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -85,13 +85,19 @@ macro_rules! map_symbol_to_lowlevel_and_arity {
|
|||
// these are used internally and not tied to a symbol
|
||||
LowLevel::Hash => unimplemented!(),
|
||||
LowLevel::PtrCast => unimplemented!(),
|
||||
LowLevel::PtrWrite => unimplemented!(),
|
||||
LowLevel::PtrStore => unimplemented!(),
|
||||
LowLevel::PtrLoad => unimplemented!(),
|
||||
LowLevel::PtrClearTagId => unimplemented!(),
|
||||
LowLevel::RefCountIncRcPtr => unimplemented!(),
|
||||
LowLevel::RefCountDecRcPtr=> unimplemented!(),
|
||||
LowLevel::RefCountIncDataPtr => unimplemented!(),
|
||||
LowLevel::RefCountDecDataPtr=> unimplemented!(),
|
||||
LowLevel::RefCountIsUnique => unimplemented!(),
|
||||
|
||||
LowLevel::SetJmp => unimplemented!(),
|
||||
LowLevel::LongJmp => unimplemented!(),
|
||||
LowLevel::SetLongJmpBuffer => unimplemented!(),
|
||||
|
||||
// these are not implemented, not sure why
|
||||
LowLevel::StrFromInt => unimplemented!(),
|
||||
LowLevel::StrFromFloat => unimplemented!(),
|
||||
|
@ -118,8 +124,8 @@ map_symbol_to_lowlevel_and_arity! {
|
|||
StrToUtf8; STR_TO_UTF8; 1,
|
||||
StrRepeat; STR_REPEAT; 2,
|
||||
StrTrim; STR_TRIM; 1,
|
||||
StrTrimLeft; STR_TRIM_LEFT; 1,
|
||||
StrTrimRight; STR_TRIM_RIGHT; 1,
|
||||
StrTrimStart; STR_TRIM_START; 1,
|
||||
StrTrimEnd; STR_TRIM_END; 1,
|
||||
StrToScalars; STR_TO_SCALARS; 1,
|
||||
StrGetUnsafe; STR_GET_UNSAFE; 2,
|
||||
StrSubstringUnsafe; STR_SUBSTRING_UNSAFE; 3,
|
||||
|
|
|
@ -176,7 +176,7 @@ impl Constraints {
|
|||
|
||||
let mut buf = String::new();
|
||||
|
||||
writeln!(buf, "Constraints statistics for module {:?}:", module_id)?;
|
||||
writeln!(buf, "Constraints statistics for module {module_id:?}:")?;
|
||||
|
||||
writeln!(buf, " constraints length: {}:", self.constraints.len())?;
|
||||
writeln!(
|
||||
|
@ -833,16 +833,16 @@ impl std::fmt::Debug for Constraint {
|
|||
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
|
||||
match self {
|
||||
Self::Eq(Eq(arg0, arg1, arg2, arg3)) => {
|
||||
write!(f, "Eq({:?}, {:?}, {:?}, {:?})", arg0, arg1, arg2, arg3)
|
||||
write!(f, "Eq({arg0:?}, {arg1:?}, {arg2:?}, {arg3:?})")
|
||||
}
|
||||
Self::Store(arg0, arg1, arg2, arg3) => {
|
||||
write!(f, "Store({:?}, {:?}, {:?}, {:?})", arg0, arg1, arg2, arg3)
|
||||
write!(f, "Store({arg0:?}, {arg1:?}, {arg2:?}, {arg3:?})")
|
||||
}
|
||||
Self::Lookup(arg0, arg1, arg2) => {
|
||||
write!(f, "Lookup({:?}, {:?}, {:?})", arg0, arg1, arg2)
|
||||
write!(f, "Lookup({arg0:?}, {arg1:?}, {arg2:?})")
|
||||
}
|
||||
Self::Pattern(arg0, arg1, arg2, arg3) => {
|
||||
write!(f, "Pattern({:?}, {:?}, {:?}, {:?})", arg0, arg1, arg2, arg3)
|
||||
write!(f, "Pattern({arg0:?}, {arg1:?}, {arg2:?}, {arg3:?})")
|
||||
}
|
||||
Self::True => write!(f, "True"),
|
||||
Self::SaveTheEnvironment => write!(f, "SaveTheEnvironment"),
|
||||
|
@ -851,27 +851,19 @@ impl std::fmt::Debug for Constraint {
|
|||
Self::IsOpenType(arg0) => f.debug_tuple("IsOpenType").field(arg0).finish(),
|
||||
Self::IncludesTag(arg0) => f.debug_tuple("IncludesTag").field(arg0).finish(),
|
||||
Self::PatternPresence(arg0, arg1, arg2, arg3) => {
|
||||
write!(
|
||||
f,
|
||||
"PatternPresence({:?}, {:?}, {:?}, {:?})",
|
||||
arg0, arg1, arg2, arg3
|
||||
)
|
||||
write!(f, "PatternPresence({arg0:?}, {arg1:?}, {arg2:?}, {arg3:?})")
|
||||
}
|
||||
Self::Exhaustive(arg0, arg1, arg2, arg3) => {
|
||||
write!(
|
||||
f,
|
||||
"Exhaustive({:?}, {:?}, {:?}, {:?})",
|
||||
arg0, arg1, arg2, arg3
|
||||
)
|
||||
write!(f, "Exhaustive({arg0:?}, {arg1:?}, {arg2:?}, {arg3:?})")
|
||||
}
|
||||
Self::Resolve(arg0) => {
|
||||
write!(f, "Resolve({:?})", arg0)
|
||||
write!(f, "Resolve({arg0:?})")
|
||||
}
|
||||
Self::CheckCycle(arg0, arg1) => {
|
||||
write!(f, "CheckCycle({:?}, {:?})", arg0, arg1)
|
||||
write!(f, "CheckCycle({arg0:?}, {arg1:?})")
|
||||
}
|
||||
Self::IngestedFile(arg0, arg1, arg2) => {
|
||||
write!(f, "IngestedFile({:?}, {:?}, {:?})", arg0, arg1, arg2)
|
||||
write!(f, "IngestedFile({arg0:?}, {arg1:?}, {arg2:?})")
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -24,7 +24,7 @@ trait CopyEnv {
|
|||
if descriptor.copy.into_variable().is_some() {
|
||||
descriptor.copy = OptVariable::NONE;
|
||||
} else {
|
||||
debug_assert!(false, "{:?} marked as copied but it wasn't", var);
|
||||
debug_assert!(false, "{var:?} marked as copied but it wasn't");
|
||||
}
|
||||
})
|
||||
}
|
||||
|
@ -1148,6 +1148,7 @@ fn deep_copy_type_vars<C: CopyEnv>(
|
|||
})
|
||||
})
|
||||
}
|
||||
ErasedLambda => ErasedLambda,
|
||||
|
||||
RangedNumber(range) => {
|
||||
perform_clone!(RangedNumber(range))
|
||||
|
@ -1320,7 +1321,7 @@ mod test {
|
|||
FlexVar(Some(name)) => {
|
||||
assert_eq!(subs[*name].as_str(), "a");
|
||||
}
|
||||
it => panic!("{:?}", it),
|
||||
it => panic!("{it:?}"),
|
||||
}
|
||||
assert_eq!(var, variant_var);
|
||||
assert!(matches!(
|
||||
|
@ -1337,7 +1338,7 @@ mod test {
|
|||
FlexVar(Some(name)) => {
|
||||
assert_eq!(subs[*name].as_str(), "b");
|
||||
}
|
||||
it => panic!("{:?}", it),
|
||||
it => panic!("{it:?}"),
|
||||
}
|
||||
|
||||
match arg.value {
|
||||
|
@ -1355,10 +1356,10 @@ mod test {
|
|||
assert_eq!(name.0.as_str(), "G");
|
||||
assert_eq!(arguments.len(), 0);
|
||||
}
|
||||
e => panic!("{:?}", e),
|
||||
e => panic!("{e:?}"),
|
||||
}
|
||||
}
|
||||
e => panic!("{:?}", e),
|
||||
e => panic!("{e:?}"),
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -1403,7 +1404,7 @@ mod test {
|
|||
FlexVar(Some(name)) => {
|
||||
assert_eq!(target[*name].as_str(), "a");
|
||||
}
|
||||
it => panic!("{:?}", it),
|
||||
it => panic!("{it:?}"),
|
||||
}
|
||||
assert_eq!(var, variant_var);
|
||||
assert!(matches!(
|
||||
|
@ -1418,7 +1419,7 @@ mod test {
|
|||
FlexVar(Some(name)) => {
|
||||
assert_eq!(target[*name].as_str(), "b");
|
||||
}
|
||||
it => panic!("{:?}", it),
|
||||
it => panic!("{it:?}"),
|
||||
}
|
||||
|
||||
match arg.value {
|
||||
|
@ -1436,10 +1437,10 @@ mod test {
|
|||
assert_eq!(name.0.as_str(), "G");
|
||||
assert_eq!(arguments.len(), 0);
|
||||
}
|
||||
e => panic!("{:?}", e),
|
||||
e => panic!("{e:?}"),
|
||||
}
|
||||
}
|
||||
e => panic!("{:?}", e),
|
||||
e => panic!("{e:?}"),
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -88,6 +88,23 @@ pub struct Annotation {
|
|||
pub region: Region,
|
||||
}
|
||||
|
||||
impl Annotation {
|
||||
fn freshen(mut self, var_store: &mut VarStore) -> Self {
|
||||
let mut substitutions = MutMap::default();
|
||||
|
||||
for v in self.introduced_variables.lambda_sets.iter_mut() {
|
||||
let new = var_store.fresh();
|
||||
substitutions.insert(*v, new);
|
||||
|
||||
*v = new;
|
||||
}
|
||||
|
||||
self.signature.substitute_variables(&substitutions);
|
||||
|
||||
self
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Debug)]
|
||||
pub(crate) struct CanDefs {
|
||||
defs: Vec<Option<Def>>,
|
||||
|
@ -1069,8 +1086,7 @@ fn canonicalize_value_defs<'a>(
|
|||
debug_assert_eq!(env.home, s.module_id());
|
||||
debug_assert!(
|
||||
!symbol_to_index.iter().any(|(id, _)| *id == s.ident_id()),
|
||||
"{:?}",
|
||||
s
|
||||
"{s:?}"
|
||||
);
|
||||
|
||||
symbol_to_index.push((s.ident_id(), def_index as u32));
|
||||
|
@ -1639,6 +1655,14 @@ pub(crate) fn sort_can_defs_new(
|
|||
}
|
||||
};
|
||||
|
||||
let host_annotation = if exposed_symbols.contains(&symbol) {
|
||||
def.annotation
|
||||
.clone()
|
||||
.map(|a| (var_store.fresh(), a.freshen(var_store)))
|
||||
} else {
|
||||
None
|
||||
};
|
||||
|
||||
if is_initial && !exposed_symbols.contains(&symbol) {
|
||||
env.problem(Problem::DefsOnlyUsedInRecursion(1, def.region()));
|
||||
}
|
||||
|
@ -1650,6 +1674,7 @@ pub(crate) fn sort_can_defs_new(
|
|||
Loc::at(def.loc_expr.region, closure_data),
|
||||
def.expr_var,
|
||||
def.annotation,
|
||||
host_annotation,
|
||||
specializes,
|
||||
);
|
||||
}
|
||||
|
@ -1659,55 +1684,80 @@ pub(crate) fn sort_can_defs_new(
|
|||
def.loc_expr,
|
||||
def.expr_var,
|
||||
def.annotation,
|
||||
host_annotation,
|
||||
specializes,
|
||||
);
|
||||
}
|
||||
}
|
||||
} else {
|
||||
match def.loc_pattern.value {
|
||||
Pattern::Identifier(symbol) => match def.loc_expr.value {
|
||||
Closure(closure_data) => {
|
||||
declarations.push_function_def(
|
||||
Loc::at(def.loc_pattern.region, symbol),
|
||||
Loc::at(def.loc_expr.region, closure_data),
|
||||
def.expr_var,
|
||||
def.annotation,
|
||||
None,
|
||||
);
|
||||
Pattern::Identifier(symbol) => {
|
||||
let host_annotation = if exposed_symbols.contains(&symbol) {
|
||||
def.annotation
|
||||
.clone()
|
||||
.map(|a| (var_store.fresh(), a.freshen(var_store)))
|
||||
} else {
|
||||
None
|
||||
};
|
||||
|
||||
match def.loc_expr.value {
|
||||
Closure(closure_data) => {
|
||||
declarations.push_function_def(
|
||||
Loc::at(def.loc_pattern.region, symbol),
|
||||
Loc::at(def.loc_expr.region, closure_data),
|
||||
def.expr_var,
|
||||
def.annotation,
|
||||
host_annotation,
|
||||
None,
|
||||
);
|
||||
}
|
||||
_ => {
|
||||
declarations.push_value_def(
|
||||
Loc::at(def.loc_pattern.region, symbol),
|
||||
def.loc_expr,
|
||||
def.expr_var,
|
||||
def.annotation,
|
||||
host_annotation,
|
||||
None,
|
||||
);
|
||||
}
|
||||
}
|
||||
_ => {
|
||||
declarations.push_value_def(
|
||||
Loc::at(def.loc_pattern.region, symbol),
|
||||
def.loc_expr,
|
||||
def.expr_var,
|
||||
def.annotation,
|
||||
None,
|
||||
);
|
||||
}
|
||||
},
|
||||
}
|
||||
Pattern::AbilityMemberSpecialization {
|
||||
ident: symbol,
|
||||
specializes,
|
||||
} => match def.loc_expr.value {
|
||||
Closure(closure_data) => {
|
||||
declarations.push_function_def(
|
||||
Loc::at(def.loc_pattern.region, symbol),
|
||||
Loc::at(def.loc_expr.region, closure_data),
|
||||
def.expr_var,
|
||||
def.annotation,
|
||||
Some(specializes),
|
||||
);
|
||||
} => {
|
||||
let host_annotation = if exposed_symbols.contains(&symbol) {
|
||||
def.annotation
|
||||
.clone()
|
||||
.map(|a| (var_store.fresh(), a.freshen(var_store)))
|
||||
} else {
|
||||
None
|
||||
};
|
||||
|
||||
match def.loc_expr.value {
|
||||
Closure(closure_data) => {
|
||||
declarations.push_function_def(
|
||||
Loc::at(def.loc_pattern.region, symbol),
|
||||
Loc::at(def.loc_expr.region, closure_data),
|
||||
def.expr_var,
|
||||
def.annotation,
|
||||
host_annotation,
|
||||
Some(specializes),
|
||||
);
|
||||
}
|
||||
_ => {
|
||||
declarations.push_value_def(
|
||||
Loc::at(def.loc_pattern.region, symbol),
|
||||
def.loc_expr,
|
||||
def.expr_var,
|
||||
def.annotation,
|
||||
host_annotation,
|
||||
Some(specializes),
|
||||
);
|
||||
}
|
||||
}
|
||||
_ => {
|
||||
declarations.push_value_def(
|
||||
Loc::at(def.loc_pattern.region, symbol),
|
||||
def.loc_expr,
|
||||
def.expr_var,
|
||||
def.annotation,
|
||||
Some(specializes),
|
||||
);
|
||||
}
|
||||
},
|
||||
}
|
||||
_ => {
|
||||
declarations.push_destructure_def(
|
||||
def.loc_pattern,
|
||||
|
@ -1750,6 +1800,14 @@ pub(crate) fn sort_can_defs_new(
|
|||
Some(r) => Some(Region::span_across(&r, &def.region())),
|
||||
};
|
||||
|
||||
let host_annotation = if exposed_symbols.contains(&symbol) {
|
||||
def.annotation
|
||||
.clone()
|
||||
.map(|a| (var_store.fresh(), a.freshen(var_store)))
|
||||
} else {
|
||||
None
|
||||
};
|
||||
|
||||
match def.loc_expr.value {
|
||||
Closure(closure_data) => {
|
||||
declarations.push_recursive_def(
|
||||
|
@ -1757,6 +1815,7 @@ pub(crate) fn sort_can_defs_new(
|
|||
Loc::at(def.loc_expr.region, closure_data),
|
||||
def.expr_var,
|
||||
def.annotation,
|
||||
host_annotation,
|
||||
specializes,
|
||||
);
|
||||
}
|
||||
|
@ -1766,6 +1825,7 @@ pub(crate) fn sort_can_defs_new(
|
|||
def.loc_expr,
|
||||
def.expr_var,
|
||||
def.annotation,
|
||||
host_annotation,
|
||||
specializes,
|
||||
);
|
||||
}
|
||||
|
@ -1838,7 +1898,7 @@ pub(crate) fn sort_can_defs(
|
|||
);
|
||||
|
||||
let declaration = if def_ordering.references.get_row_col(index, index) {
|
||||
debug_assert!(!is_specialization, "Self-recursive specializations can only be determined during solving - but it was determined for {:?} now, that's a bug!", def);
|
||||
debug_assert!(!is_specialization, "Self-recursive specializations can only be determined during solving - but it was determined for {def:?} now, that's a bug!");
|
||||
|
||||
if is_initial
|
||||
&& !def
|
||||
|
@ -2272,12 +2332,18 @@ fn canonicalize_pending_body<'a>(
|
|||
|
||||
opt_loc_annotation: Option<Loc<crate::annotation::Annotation>>,
|
||||
) -> DefOutput {
|
||||
let mut loc_value = &loc_expr.value;
|
||||
|
||||
while let ast::Expr::ParensAround(value) = loc_value {
|
||||
loc_value = value;
|
||||
}
|
||||
|
||||
// We treat closure definitions `foo = \a, b -> ...` differently from other body expressions,
|
||||
// because they need more bookkeeping (for tail calls, closure captures, etc.)
|
||||
//
|
||||
// Only defs of the form `foo = ...` can be closure declarations or self tail calls.
|
||||
let (loc_can_expr, def_references) = {
|
||||
match (&loc_can_pattern.value, &loc_expr.value) {
|
||||
match (&loc_can_pattern.value, &loc_value) {
|
||||
(
|
||||
Pattern::Identifier(defined_symbol)
|
||||
| Pattern::AbilityMemberSpecialization {
|
||||
|
|
|
@ -222,16 +222,16 @@ pub(crate) fn synthesize_member_impl<'a>(
|
|||
ability_member: Symbol,
|
||||
) -> (Symbol, Loc<Pattern>, &'a Loc<ast::Expr<'a>>) {
|
||||
// @Opaq
|
||||
let at_opaque = env.arena.alloc_str(&format!("@{}", opaque_name));
|
||||
let at_opaque = env.arena.alloc_str(&format!("@{opaque_name}"));
|
||||
|
||||
let (impl_name, def_body): (String, ast::Expr<'a>) = match ability_member {
|
||||
Symbol::ENCODE_TO_ENCODER => (
|
||||
format!("#{}_toEncoder", opaque_name),
|
||||
format!("#{opaque_name}_toEncoder"),
|
||||
to_encoder(env, at_opaque),
|
||||
),
|
||||
Symbol::DECODE_DECODER => (format!("#{}_decoder", opaque_name), decoder(env, at_opaque)),
|
||||
Symbol::HASH_HASH => (format!("#{}_hash", opaque_name), hash(env, at_opaque)),
|
||||
Symbol::BOOL_IS_EQ => (format!("#{}_isEq", opaque_name), is_eq(env, at_opaque)),
|
||||
Symbol::DECODE_DECODER => (format!("#{opaque_name}_decoder"), decoder(env, at_opaque)),
|
||||
Symbol::HASH_HASH => (format!("#{opaque_name}_hash"), hash(env, at_opaque)),
|
||||
Symbol::BOOL_IS_EQ => (format!("#{opaque_name}_isEq"), is_eq(env, at_opaque)),
|
||||
other => internal_error!("{:?} is not a derivable ability member!", other),
|
||||
};
|
||||
|
||||
|
|
|
@ -1362,7 +1362,7 @@ pub fn build_host_exposed_def(
|
|||
match typ.shallow_structural_dealias() {
|
||||
Type::Function(args, _, _) => {
|
||||
for i in 0..args.len() {
|
||||
let name = format!("closure_arg_{}_{}", ident, i);
|
||||
let name = format!("closure_arg_{ident}_{i}");
|
||||
|
||||
let arg_symbol = {
|
||||
let ident = name.clone().into();
|
||||
|
@ -1381,7 +1381,7 @@ pub fn build_host_exposed_def(
|
|||
linked_symbol_arguments.push((arg_var, Expr::Var(arg_symbol, arg_var)));
|
||||
}
|
||||
|
||||
let foreign_symbol_name = format!("roc_fx_{}", ident);
|
||||
let foreign_symbol_name = format!("roc_fx_{ident}");
|
||||
let low_level_call = Expr::ForeignCall {
|
||||
foreign_symbol: foreign_symbol_name.into(),
|
||||
args: linked_symbol_arguments,
|
||||
|
@ -1389,7 +1389,7 @@ pub fn build_host_exposed_def(
|
|||
};
|
||||
|
||||
let effect_closure_symbol = {
|
||||
let name = format!("effect_closure_{}", ident);
|
||||
let name = format!("effect_closure_{ident}");
|
||||
|
||||
let ident = name.into();
|
||||
scope.introduce(ident, Region::zero()).unwrap()
|
||||
|
@ -1435,7 +1435,7 @@ pub fn build_host_exposed_def(
|
|||
_ => {
|
||||
// not a function
|
||||
|
||||
let foreign_symbol_name = format!("roc_fx_{}", ident);
|
||||
let foreign_symbol_name = format!("roc_fx_{ident}");
|
||||
let low_level_call = Expr::ForeignCall {
|
||||
foreign_symbol: foreign_symbol_name.into(),
|
||||
args: linked_symbol_arguments,
|
||||
|
@ -1443,7 +1443,7 @@ pub fn build_host_exposed_def(
|
|||
};
|
||||
|
||||
let effect_closure_symbol = {
|
||||
let name = format!("effect_closure_{}", ident);
|
||||
let name = format!("effect_closure_{ident}");
|
||||
|
||||
let ident = name.into();
|
||||
scope.introduce(ident, Region::zero()).unwrap()
|
||||
|
|
|
@ -67,8 +67,7 @@ impl<'a> Env<'a> {
|
|||
) -> Result<Symbol, RuntimeError> {
|
||||
debug_assert!(
|
||||
!module_name_str.is_empty(),
|
||||
"Called env.qualified_lookup with an unqualified ident: {:?}",
|
||||
ident
|
||||
"Called env.qualified_lookup with an unqualified ident: {ident:?}"
|
||||
);
|
||||
|
||||
let module_name = ModuleName::from(module_name_str);
|
||||
|
|
|
@ -140,6 +140,7 @@ fn index_var(
|
|||
| Content::FlexAbleVar(_, _)
|
||||
| Content::RigidAbleVar(_, _)
|
||||
| Content::LambdaSet(_)
|
||||
| Content::ErasedLambda
|
||||
| Content::RangedNumber(..) => return Err(TypeError),
|
||||
Content::Error => return Err(TypeError),
|
||||
Content::RecursionVar {
|
||||
|
|
|
@ -18,7 +18,7 @@ use roc_module::called_via::CalledVia;
|
|||
use roc_module::ident::{ForeignSymbol, Lowercase, TagName};
|
||||
use roc_module::low_level::LowLevel;
|
||||
use roc_module::symbol::Symbol;
|
||||
use roc_parse::ast::{self, Defs, StrLiteral};
|
||||
use roc_parse::ast::{self, Defs, PrecedenceConflict, StrLiteral};
|
||||
use roc_parse::ident::Accessor;
|
||||
use roc_parse::pattern::PatternType::*;
|
||||
use roc_problem::can::{PrecedenceProblem, Problem, RuntimeError};
|
||||
|
@ -1403,14 +1403,13 @@ pub fn canonicalize_expr<'a>(
|
|||
|
||||
(answer, Output::default())
|
||||
}
|
||||
&ast::Expr::ParensAround(sub_expr) => {
|
||||
let (loc_expr, output) = canonicalize_expr(env, var_store, scope, region, sub_expr);
|
||||
|
||||
(loc_expr.value, output)
|
||||
}
|
||||
// Below this point, we shouln't see any of these nodes anymore because
|
||||
// operator desugaring should have removed them!
|
||||
bad_expr @ ast::Expr::ParensAround(_) => {
|
||||
internal_error!(
|
||||
"A ParensAround did not get removed during operator desugaring somehow: {:#?}",
|
||||
bad_expr
|
||||
);
|
||||
}
|
||||
bad_expr @ ast::Expr::SpaceBefore(_, _) => {
|
||||
internal_error!(
|
||||
"A SpaceBefore did not get removed during operator desugaring somehow: {:#?}",
|
||||
|
@ -2377,11 +2376,115 @@ fn flatten_str_literal<'a>(
|
|||
}
|
||||
}
|
||||
|
||||
/// Comments, newlines, and nested interpolation are disallowed inside interpolation
|
||||
pub fn is_valid_interpolation(expr: &ast::Expr<'_>) -> bool {
|
||||
match expr {
|
||||
ast::Expr::Var { .. } => true,
|
||||
ast::Expr::RecordAccess(sub_expr, _) => is_valid_interpolation(sub_expr),
|
||||
_ => false,
|
||||
// These definitely contain neither comments nor newlines, so they are valid
|
||||
ast::Expr::Var { .. }
|
||||
| ast::Expr::SingleQuote(_)
|
||||
| ast::Expr::Str(StrLiteral::PlainLine(_))
|
||||
| ast::Expr::Float(_)
|
||||
| ast::Expr::Num(_)
|
||||
| ast::Expr::NonBase10Int { .. }
|
||||
| ast::Expr::AccessorFunction(_)
|
||||
| ast::Expr::Crash
|
||||
| ast::Expr::Underscore(_)
|
||||
| ast::Expr::MalformedIdent(_, _)
|
||||
| ast::Expr::Tag(_)
|
||||
| ast::Expr::OpaqueRef(_)
|
||||
| ast::Expr::MalformedClosure => true,
|
||||
// Newlines are disallowed inside interpolation, and these all require newlines
|
||||
ast::Expr::Dbg(_, _)
|
||||
| ast::Expr::Defs(_, _)
|
||||
| ast::Expr::Expect(_, _)
|
||||
| ast::Expr::When(_, _)
|
||||
| ast::Expr::Backpassing(_, _, _)
|
||||
| ast::Expr::IngestedFile(_, _)
|
||||
| ast::Expr::SpaceBefore(_, _)
|
||||
| ast::Expr::Str(StrLiteral::Block(_))
|
||||
| ast::Expr::SpaceAfter(_, _) => false,
|
||||
// These can contain subexpressions, so we need to recursively check those
|
||||
ast::Expr::Str(StrLiteral::Line(segments)) => {
|
||||
segments.iter().all(|segment| match segment {
|
||||
ast::StrSegment::EscapedChar(_)
|
||||
| ast::StrSegment::Unicode(_)
|
||||
| ast::StrSegment::Plaintext(_) => true,
|
||||
// Disallow nested interpolation. Alternatively, we could allow it but require
|
||||
// a comment above it apologizing to the next person who has to read the code.
|
||||
ast::StrSegment::Interpolated(_) => false,
|
||||
})
|
||||
}
|
||||
ast::Expr::Record(fields) => fields.iter().all(|loc_field| match loc_field.value {
|
||||
ast::AssignedField::RequiredValue(_label, loc_comments, loc_val)
|
||||
| ast::AssignedField::OptionalValue(_label, loc_comments, loc_val) => {
|
||||
loc_comments.is_empty() && is_valid_interpolation(&loc_val.value)
|
||||
}
|
||||
ast::AssignedField::Malformed(_) | ast::AssignedField::LabelOnly(_) => true,
|
||||
ast::AssignedField::SpaceBefore(_, _) | ast::AssignedField::SpaceAfter(_, _) => false,
|
||||
}),
|
||||
ast::Expr::Tuple(fields) => fields
|
||||
.iter()
|
||||
.all(|loc_field| is_valid_interpolation(&loc_field.value)),
|
||||
ast::Expr::MultipleRecordBuilders(loc_expr)
|
||||
| ast::Expr::UnappliedRecordBuilder(loc_expr)
|
||||
| ast::Expr::PrecedenceConflict(PrecedenceConflict { expr: loc_expr, .. })
|
||||
| ast::Expr::UnaryOp(loc_expr, _)
|
||||
| ast::Expr::Closure(_, loc_expr) => is_valid_interpolation(&loc_expr.value),
|
||||
ast::Expr::TupleAccess(sub_expr, _)
|
||||
| ast::Expr::ParensAround(sub_expr)
|
||||
| ast::Expr::RecordAccess(sub_expr, _) => is_valid_interpolation(sub_expr),
|
||||
ast::Expr::Apply(loc_expr, args, _called_via) => {
|
||||
is_valid_interpolation(&loc_expr.value)
|
||||
&& args
|
||||
.iter()
|
||||
.all(|loc_arg| is_valid_interpolation(&loc_arg.value))
|
||||
}
|
||||
ast::Expr::BinOps(loc_exprs, loc_expr) => {
|
||||
is_valid_interpolation(&loc_expr.value)
|
||||
&& loc_exprs
|
||||
.iter()
|
||||
.all(|(loc_expr, _binop)| is_valid_interpolation(&loc_expr.value))
|
||||
}
|
||||
ast::Expr::If(branches, final_branch) => {
|
||||
is_valid_interpolation(&final_branch.value)
|
||||
&& branches.iter().all(|(loc_before, loc_after)| {
|
||||
is_valid_interpolation(&loc_before.value)
|
||||
&& is_valid_interpolation(&loc_after.value)
|
||||
})
|
||||
}
|
||||
ast::Expr::List(elems) => elems
|
||||
.iter()
|
||||
.all(|loc_expr| is_valid_interpolation(&loc_expr.value)),
|
||||
ast::Expr::RecordUpdate { update, fields } => {
|
||||
is_valid_interpolation(&update.value)
|
||||
&& fields.iter().all(|loc_field| match loc_field.value {
|
||||
ast::AssignedField::RequiredValue(_label, loc_comments, loc_val)
|
||||
| ast::AssignedField::OptionalValue(_label, loc_comments, loc_val) => {
|
||||
loc_comments.is_empty() && is_valid_interpolation(&loc_val.value)
|
||||
}
|
||||
ast::AssignedField::Malformed(_) | ast::AssignedField::LabelOnly(_) => true,
|
||||
ast::AssignedField::SpaceBefore(_, _)
|
||||
| ast::AssignedField::SpaceAfter(_, _) => false,
|
||||
})
|
||||
}
|
||||
ast::Expr::RecordBuilder(fields) => fields.iter().all(|loc_field| match loc_field.value {
|
||||
ast::RecordBuilderField::Value(_label, comments, loc_expr) => {
|
||||
comments.is_empty() && is_valid_interpolation(&loc_expr.value)
|
||||
}
|
||||
ast::RecordBuilderField::ApplyValue(
|
||||
_label,
|
||||
comments_before,
|
||||
comments_after,
|
||||
loc_expr,
|
||||
) => {
|
||||
comments_before.is_empty()
|
||||
&& comments_after.is_empty()
|
||||
&& is_valid_interpolation(&loc_expr.value)
|
||||
}
|
||||
ast::RecordBuilderField::Malformed(_) | ast::RecordBuilderField::LabelOnly(_) => true,
|
||||
ast::RecordBuilderField::SpaceBefore(_, _)
|
||||
| ast::RecordBuilderField::SpaceAfter(_, _) => false,
|
||||
}),
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -2536,6 +2639,8 @@ pub struct Declarations {
|
|||
// used for ability member specializatons.
|
||||
pub specializes: VecMap<usize, Symbol>,
|
||||
|
||||
pub host_exposed_annotations: VecMap<usize, (Variable, crate::def::Annotation)>,
|
||||
|
||||
pub function_bodies: Vec<Loc<FunctionDef>>,
|
||||
pub expressions: Vec<Loc<Expr>>,
|
||||
pub destructs: Vec<DestructureDef>,
|
||||
|
@ -2557,6 +2662,7 @@ impl Declarations {
|
|||
variables: Vec::with_capacity(capacity),
|
||||
symbols: Vec::with_capacity(capacity),
|
||||
annotations: Vec::with_capacity(capacity),
|
||||
host_exposed_annotations: VecMap::new(),
|
||||
function_bodies: Vec::with_capacity(capacity),
|
||||
expressions: Vec::with_capacity(capacity),
|
||||
specializes: VecMap::default(), // number of specializations is probably low
|
||||
|
@ -2587,6 +2693,7 @@ impl Declarations {
|
|||
loc_closure_data: Loc<ClosureData>,
|
||||
expr_var: Variable,
|
||||
annotation: Option<Annotation>,
|
||||
host_annotation: Option<(Variable, Annotation)>,
|
||||
specializes: Option<Symbol>,
|
||||
) -> usize {
|
||||
let index = self.declarations.len();
|
||||
|
@ -2609,6 +2716,11 @@ impl Declarations {
|
|||
Recursive::TailRecursive => DeclarationTag::TailRecursive(function_def_index),
|
||||
};
|
||||
|
||||
if let Some(annotation) = host_annotation {
|
||||
self.host_exposed_annotations
|
||||
.insert(self.declarations.len(), annotation);
|
||||
}
|
||||
|
||||
self.declarations.push(tag);
|
||||
self.variables.push(expr_var);
|
||||
self.symbols.push(symbol);
|
||||
|
@ -2629,6 +2741,7 @@ impl Declarations {
|
|||
loc_closure_data: Loc<ClosureData>,
|
||||
expr_var: Variable,
|
||||
annotation: Option<Annotation>,
|
||||
host_annotation: Option<(Variable, Annotation)>,
|
||||
specializes: Option<Symbol>,
|
||||
) -> usize {
|
||||
let index = self.declarations.len();
|
||||
|
@ -2644,6 +2757,11 @@ impl Declarations {
|
|||
|
||||
let function_def_index = Index::push_new(&mut self.function_bodies, loc_function_def);
|
||||
|
||||
if let Some(annotation) = host_annotation {
|
||||
self.host_exposed_annotations
|
||||
.insert(self.declarations.len(), annotation);
|
||||
}
|
||||
|
||||
self.declarations
|
||||
.push(DeclarationTag::Function(function_def_index));
|
||||
self.variables.push(expr_var);
|
||||
|
@ -2701,10 +2819,16 @@ impl Declarations {
|
|||
loc_expr: Loc<Expr>,
|
||||
expr_var: Variable,
|
||||
annotation: Option<Annotation>,
|
||||
host_annotation: Option<(Variable, Annotation)>,
|
||||
specializes: Option<Symbol>,
|
||||
) -> usize {
|
||||
let index = self.declarations.len();
|
||||
|
||||
if let Some(annotation) = host_annotation {
|
||||
self.host_exposed_annotations
|
||||
.insert(self.declarations.len(), annotation);
|
||||
}
|
||||
|
||||
self.declarations.push(DeclarationTag::Value);
|
||||
self.variables.push(expr_var);
|
||||
self.symbols.push(symbol);
|
||||
|
@ -2759,6 +2883,7 @@ impl Declarations {
|
|||
def.expr_var,
|
||||
def.annotation,
|
||||
None,
|
||||
None,
|
||||
);
|
||||
}
|
||||
|
||||
|
@ -2769,6 +2894,7 @@ impl Declarations {
|
|||
def.expr_var,
|
||||
def.annotation,
|
||||
None,
|
||||
None,
|
||||
);
|
||||
}
|
||||
},
|
||||
|
@ -2779,6 +2905,7 @@ impl Declarations {
|
|||
def.expr_var,
|
||||
def.annotation,
|
||||
None,
|
||||
None,
|
||||
);
|
||||
}
|
||||
},
|
||||
|
|
|
@ -347,9 +347,7 @@ pub fn canonicalize_module_defs<'a>(
|
|||
// the symbol should already be added to the scope when this module is canonicalized
|
||||
debug_assert!(
|
||||
scope.contains_alias(symbol) || scope.abilities_store.is_ability(symbol),
|
||||
"The {:?} is not a type alias or ability known in {:?}",
|
||||
symbol,
|
||||
home
|
||||
"The {symbol:?} is not a type alias or ability known in {home:?}"
|
||||
);
|
||||
|
||||
// but now we know this symbol by a different identifier, so we still need to add it to
|
||||
|
|
|
@ -220,8 +220,7 @@ fn from_str_radix(src: &str, radix: u32) -> Result<ParsedNumResult, IntErrorKind
|
|||
|
||||
assert!(
|
||||
(2..=36).contains(&radix),
|
||||
"from_str_radix_int: must lie in the range `[2, 36]` - found {}",
|
||||
radix
|
||||
"from_str_radix_int: must lie in the range `[2, 36]` - found {radix}"
|
||||
);
|
||||
|
||||
let (opt_exact_bound, src) = parse_literal_suffix(src);
|
||||
|
|
|
@ -7,7 +7,9 @@ use roc_module::called_via::BinOp::Pizza;
|
|||
use roc_module::called_via::{BinOp, CalledVia};
|
||||
use roc_module::ident::ModuleName;
|
||||
use roc_parse::ast::Expr::{self, *};
|
||||
use roc_parse::ast::{AssignedField, Collection, RecordBuilderField, ValueDef, WhenBranch};
|
||||
use roc_parse::ast::{
|
||||
AssignedField, Collection, RecordBuilderField, StrLiteral, StrSegment, ValueDef, WhenBranch,
|
||||
};
|
||||
use roc_region::all::{Loc, Region};
|
||||
|
||||
// BinOp precedence logic adapted from Gluon by Markus Westerlind
|
||||
|
@ -129,7 +131,6 @@ pub fn desugar_expr<'a>(arena: &'a Bump, loc_expr: &'a Loc<Expr<'a>>) -> &'a Loc
|
|||
Float(..)
|
||||
| Num(..)
|
||||
| NonBase10Int { .. }
|
||||
| Str(_)
|
||||
| SingleQuote(_)
|
||||
| AccessorFunction(_)
|
||||
| Var { .. }
|
||||
|
@ -144,6 +145,28 @@ pub fn desugar_expr<'a>(arena: &'a Bump, loc_expr: &'a Loc<Expr<'a>>) -> &'a Loc
|
|||
| IngestedFile(_, _)
|
||||
| Crash => loc_expr,
|
||||
|
||||
Str(str_literal) => match str_literal {
|
||||
StrLiteral::PlainLine(_) => loc_expr,
|
||||
StrLiteral::Line(segments) => {
|
||||
let region = loc_expr.region;
|
||||
let value = Str(StrLiteral::Line(desugar_str_segments(arena, segments)));
|
||||
|
||||
arena.alloc(Loc { region, value })
|
||||
}
|
||||
StrLiteral::Block(lines) => {
|
||||
let region = loc_expr.region;
|
||||
let new_lines = Vec::from_iter_in(
|
||||
lines
|
||||
.iter()
|
||||
.map(|segments| desugar_str_segments(arena, segments)),
|
||||
arena,
|
||||
);
|
||||
let value = Str(StrLiteral::Block(new_lines.into_bump_slice()));
|
||||
|
||||
arena.alloc(Loc { region, value })
|
||||
}
|
||||
},
|
||||
|
||||
TupleAccess(sub_expr, paths) => {
|
||||
let region = loc_expr.region;
|
||||
let loc_sub_expr = Loc {
|
||||
|
@ -288,7 +311,7 @@ pub fn desugar_expr<'a>(arena: &'a Bump, loc_expr: &'a Loc<Expr<'a>>) -> &'a Loc
|
|||
|
||||
break builder_arg.closure;
|
||||
}
|
||||
SpaceBefore(expr, _) | SpaceAfter(expr, _) | ParensAround(expr) => {
|
||||
SpaceBefore(expr, _) | SpaceAfter(expr, _) => {
|
||||
current = *expr;
|
||||
}
|
||||
_ => break loc_arg,
|
||||
|
@ -382,7 +405,7 @@ pub fn desugar_expr<'a>(arena: &'a Bump, loc_expr: &'a Loc<Expr<'a>>) -> &'a Loc
|
|||
region: loc_expr.region,
|
||||
})
|
||||
}
|
||||
SpaceBefore(expr, _) | SpaceAfter(expr, _) | ParensAround(expr) => {
|
||||
SpaceBefore(expr, _) | SpaceAfter(expr, _) => {
|
||||
// Since we've already begun canonicalization, spaces and parens
|
||||
// are no longer needed and should be dropped.
|
||||
desugar_expr(
|
||||
|
@ -393,6 +416,20 @@ pub fn desugar_expr<'a>(arena: &'a Bump, loc_expr: &'a Loc<Expr<'a>>) -> &'a Loc
|
|||
}),
|
||||
)
|
||||
}
|
||||
ParensAround(expr) => {
|
||||
let desugared = desugar_expr(
|
||||
arena,
|
||||
arena.alloc(Loc {
|
||||
value: **expr,
|
||||
region: loc_expr.region,
|
||||
}),
|
||||
);
|
||||
|
||||
arena.alloc(Loc {
|
||||
value: ParensAround(&desugared.value),
|
||||
region: loc_expr.region,
|
||||
})
|
||||
}
|
||||
If(if_thens, final_else_branch) => {
|
||||
// If does not get desugared into `when` so we can give more targeted error messages during type checking.
|
||||
let desugared_final_else = &*arena.alloc(desugar_expr(arena, final_else_branch));
|
||||
|
@ -430,6 +467,34 @@ pub fn desugar_expr<'a>(arena: &'a Bump, loc_expr: &'a Loc<Expr<'a>>) -> &'a Loc
|
|||
}
|
||||
}
|
||||
|
||||
fn desugar_str_segments<'a>(
|
||||
arena: &'a Bump,
|
||||
segments: &'a [StrSegment<'a>],
|
||||
) -> &'a [StrSegment<'a>] {
|
||||
Vec::from_iter_in(
|
||||
segments.iter().map(|segment| match segment {
|
||||
StrSegment::Plaintext(_) | StrSegment::Unicode(_) | StrSegment::EscapedChar(_) => {
|
||||
*segment
|
||||
}
|
||||
StrSegment::Interpolated(loc_expr) => {
|
||||
let loc_desugared = desugar_expr(
|
||||
arena,
|
||||
arena.alloc(Loc {
|
||||
region: loc_expr.region,
|
||||
value: *loc_expr.value,
|
||||
}),
|
||||
);
|
||||
StrSegment::Interpolated(Loc {
|
||||
region: loc_desugared.region,
|
||||
value: arena.alloc(loc_desugared.value),
|
||||
})
|
||||
}
|
||||
}),
|
||||
arena,
|
||||
)
|
||||
.into_bump_slice()
|
||||
}
|
||||
|
||||
fn desugar_field<'a>(
|
||||
arena: &'a Bump,
|
||||
field: &'a AssignedField<'a, Expr<'a>>,
|
||||
|
|
|
@ -531,7 +531,7 @@ pub fn canonicalize_pattern<'a>(
|
|||
use std::ops::Neg;
|
||||
|
||||
let sign_str = if is_negative { "-" } else { "" };
|
||||
let int_str = format!("{}{}", sign_str, int).into_boxed_str();
|
||||
let int_str = format!("{sign_str}{int}").into_boxed_str();
|
||||
let i = match int {
|
||||
// Safety: this is fine because I128::MAX = |I128::MIN| - 1
|
||||
IntValue::I128(n) if is_negative => {
|
||||
|
|
|
@ -37,8 +37,7 @@ pub struct CanExprOut {
|
|||
pub fn can_expr_with(arena: &Bump, home: ModuleId, expr_str: &str) -> CanExprOut {
|
||||
let loc_expr = roc_parse::test_helpers::parse_loc_with(arena, expr_str).unwrap_or_else(|e| {
|
||||
panic!(
|
||||
"can_expr_with() got a parse error when attempting to canonicalize:\n\n{:?} {:?}",
|
||||
expr_str, e
|
||||
"can_expr_with() got a parse error when attempting to canonicalize:\n\n{expr_str:?} {e:?}"
|
||||
)
|
||||
});
|
||||
|
||||
|
|
|
@ -422,7 +422,7 @@ mod test_can {
|
|||
let CanExprOut { problems, .. } = can_expr_with(&arena, test_home(), src);
|
||||
|
||||
assert_eq!(problems.len(), 2);
|
||||
println!("{:#?}", problems);
|
||||
println!("{problems:#?}");
|
||||
assert!(problems.iter().any(|problem| matches!(
|
||||
problem,
|
||||
Problem::RuntimeError(RuntimeError::Shadowing { .. })
|
||||
|
|
19
crates/compiler/checkmate/Cargo.toml
Normal file
19
crates/compiler/checkmate/Cargo.toml
Normal file
|
@ -0,0 +1,19 @@
|
|||
[package]
|
||||
name = "roc_checkmate"
|
||||
description = "A framework for debugging the solver."
|
||||
|
||||
authors.workspace = true
|
||||
edition.workspace = true
|
||||
license.workspace = true
|
||||
version.workspace = true
|
||||
|
||||
[dependencies]
|
||||
roc_checkmate_schema = { path = "../checkmate_schema" }
|
||||
roc_module = { path = "../module" }
|
||||
roc_solve_schema = { path = "../solve_schema" }
|
||||
roc_types = { path = "../types" }
|
||||
chrono.workspace = true
|
||||
|
||||
[build-dependencies]
|
||||
roc_checkmate_schema = { path = "../checkmate_schema" }
|
||||
serde_json.workspace = true
|
5
crates/compiler/checkmate/README.md
Normal file
5
crates/compiler/checkmate/README.md
Normal file
|
@ -0,0 +1,5 @@
|
|||
# `checkmate`
|
||||
|
||||
A tool to debug the solver (checker + inference + specialization engine).
|
||||
|
||||
See [the document](https://rwx.notion.site/Type-debugging-tools-de42260060784cacbaf08ea4d61e0eb9?pvs=4).
|
13
crates/compiler/checkmate/build.rs
Normal file
13
crates/compiler/checkmate/build.rs
Normal file
|
@ -0,0 +1,13 @@
|
|||
use std::fs;
|
||||
|
||||
use roc_checkmate_schema::AllEvents;
|
||||
|
||||
fn main() {
|
||||
println!("cargo:rerun-if-changed=../checkmate_schema");
|
||||
let schema = AllEvents::schema();
|
||||
fs::write(
|
||||
"schema.json",
|
||||
serde_json::to_string_pretty(&schema).unwrap(),
|
||||
)
|
||||
.unwrap();
|
||||
}
|
907
crates/compiler/checkmate/schema.json
Normal file
907
crates/compiler/checkmate/schema.json
Normal file
|
@ -0,0 +1,907 @@
|
|||
{
|
||||
"$schema": "http://json-schema.org/draft-07/schema#",
|
||||
"title": "AllEvents",
|
||||
"type": "array",
|
||||
"items": {
|
||||
"$ref": "#/definitions/Event"
|
||||
},
|
||||
"definitions": {
|
||||
"AliasKind": {
|
||||
"oneOf": [
|
||||
{
|
||||
"type": "object",
|
||||
"required": [
|
||||
"type"
|
||||
],
|
||||
"properties": {
|
||||
"type": {
|
||||
"type": "string",
|
||||
"enum": [
|
||||
"Structural"
|
||||
]
|
||||
}
|
||||
}
|
||||
},
|
||||
{
|
||||
"type": "object",
|
||||
"required": [
|
||||
"type"
|
||||
],
|
||||
"properties": {
|
||||
"type": {
|
||||
"type": "string",
|
||||
"enum": [
|
||||
"Opaque"
|
||||
]
|
||||
}
|
||||
}
|
||||
}
|
||||
]
|
||||
},
|
||||
"AliasTypeVariables": {
|
||||
"type": "object",
|
||||
"required": [
|
||||
"infer_ext_in_output_position_variables",
|
||||
"lambda_set_variables",
|
||||
"type_variables"
|
||||
],
|
||||
"properties": {
|
||||
"infer_ext_in_output_position_variables": {
|
||||
"type": "array",
|
||||
"items": {
|
||||
"$ref": "#/definitions/Variable"
|
||||
}
|
||||
},
|
||||
"lambda_set_variables": {
|
||||
"type": "array",
|
||||
"items": {
|
||||
"$ref": "#/definitions/Variable"
|
||||
}
|
||||
},
|
||||
"type_variables": {
|
||||
"type": "array",
|
||||
"items": {
|
||||
"$ref": "#/definitions/Variable"
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"ClosureType": {
|
||||
"type": "object",
|
||||
"required": [
|
||||
"environment",
|
||||
"function"
|
||||
],
|
||||
"properties": {
|
||||
"environment": {
|
||||
"type": "array",
|
||||
"items": {
|
||||
"$ref": "#/definitions/Variable"
|
||||
}
|
||||
},
|
||||
"function": {
|
||||
"$ref": "#/definitions/Symbol"
|
||||
}
|
||||
}
|
||||
},
|
||||
"Content": {
|
||||
"oneOf": [
|
||||
{
|
||||
"type": "object",
|
||||
"required": [
|
||||
"type"
|
||||
],
|
||||
"properties": {
|
||||
"name": {
|
||||
"type": [
|
||||
"string",
|
||||
"null"
|
||||
]
|
||||
},
|
||||
"type": {
|
||||
"type": "string",
|
||||
"enum": [
|
||||
"Flex"
|
||||
]
|
||||
}
|
||||
}
|
||||
},
|
||||
{
|
||||
"type": "object",
|
||||
"required": [
|
||||
"name",
|
||||
"type"
|
||||
],
|
||||
"properties": {
|
||||
"name": {
|
||||
"type": "string"
|
||||
},
|
||||
"type": {
|
||||
"type": "string",
|
||||
"enum": [
|
||||
"Rigid"
|
||||
]
|
||||
}
|
||||
}
|
||||
},
|
||||
{
|
||||
"type": "object",
|
||||
"required": [
|
||||
"abilities",
|
||||
"type"
|
||||
],
|
||||
"properties": {
|
||||
"abilities": {
|
||||
"type": "array",
|
||||
"items": {
|
||||
"$ref": "#/definitions/Symbol"
|
||||
}
|
||||
},
|
||||
"name": {
|
||||
"type": [
|
||||
"string",
|
||||
"null"
|
||||
]
|
||||
},
|
||||
"type": {
|
||||
"type": "string",
|
||||
"enum": [
|
||||
"FlexAble"
|
||||
]
|
||||
}
|
||||
}
|
||||
},
|
||||
{
|
||||
"type": "object",
|
||||
"required": [
|
||||
"abilities",
|
||||
"name",
|
||||
"type"
|
||||
],
|
||||
"properties": {
|
||||
"abilities": {
|
||||
"type": "array",
|
||||
"items": {
|
||||
"$ref": "#/definitions/Symbol"
|
||||
}
|
||||
},
|
||||
"name": {
|
||||
"type": "string"
|
||||
},
|
||||
"type": {
|
||||
"type": "string",
|
||||
"enum": [
|
||||
"RigidAble"
|
||||
]
|
||||
}
|
||||
}
|
||||
},
|
||||
{
|
||||
"type": "object",
|
||||
"required": [
|
||||
"structure",
|
||||
"type"
|
||||
],
|
||||
"properties": {
|
||||
"name": {
|
||||
"type": [
|
||||
"string",
|
||||
"null"
|
||||
]
|
||||
},
|
||||
"structure": {
|
||||
"$ref": "#/definitions/Variable"
|
||||
},
|
||||
"type": {
|
||||
"type": "string",
|
||||
"enum": [
|
||||
"Recursive"
|
||||
]
|
||||
}
|
||||
}
|
||||
},
|
||||
{
|
||||
"type": "object",
|
||||
"required": [
|
||||
"ambient_function",
|
||||
"solved",
|
||||
"type",
|
||||
"unspecialized"
|
||||
],
|
||||
"properties": {
|
||||
"ambient_function": {
|
||||
"$ref": "#/definitions/Variable"
|
||||
},
|
||||
"recursion_var": {
|
||||
"anyOf": [
|
||||
{
|
||||
"$ref": "#/definitions/Variable"
|
||||
},
|
||||
{
|
||||
"type": "null"
|
||||
}
|
||||
]
|
||||
},
|
||||
"solved": {
|
||||
"type": "array",
|
||||
"items": {
|
||||
"$ref": "#/definitions/ClosureType"
|
||||
}
|
||||
},
|
||||
"type": {
|
||||
"type": "string",
|
||||
"enum": [
|
||||
"LambdaSet"
|
||||
]
|
||||
},
|
||||
"unspecialized": {
|
||||
"type": "array",
|
||||
"items": {
|
||||
"$ref": "#/definitions/UnspecializedClosureType"
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
{
|
||||
"type": "object",
|
||||
"required": [
|
||||
"type"
|
||||
],
|
||||
"properties": {
|
||||
"type": {
|
||||
"type": "string",
|
||||
"enum": [
|
||||
"ErasedLambda"
|
||||
]
|
||||
}
|
||||
}
|
||||
},
|
||||
{
|
||||
"type": "object",
|
||||
"required": [
|
||||
"kind",
|
||||
"name",
|
||||
"real_variable",
|
||||
"type",
|
||||
"variables"
|
||||
],
|
||||
"properties": {
|
||||
"kind": {
|
||||
"$ref": "#/definitions/AliasKind"
|
||||
},
|
||||
"name": {
|
||||
"$ref": "#/definitions/Symbol"
|
||||
},
|
||||
"real_variable": {
|
||||
"$ref": "#/definitions/Variable"
|
||||
},
|
||||
"type": {
|
||||
"type": "string",
|
||||
"enum": [
|
||||
"Alias"
|
||||
]
|
||||
},
|
||||
"variables": {
|
||||
"$ref": "#/definitions/AliasTypeVariables"
|
||||
}
|
||||
}
|
||||
},
|
||||
{
|
||||
"type": "object",
|
||||
"required": [
|
||||
"symbol",
|
||||
"type",
|
||||
"variables"
|
||||
],
|
||||
"properties": {
|
||||
"symbol": {
|
||||
"$ref": "#/definitions/Symbol"
|
||||
},
|
||||
"type": {
|
||||
"type": "string",
|
||||
"enum": [
|
||||
"Apply"
|
||||
]
|
||||
},
|
||||
"variables": {
|
||||
"type": "array",
|
||||
"items": {
|
||||
"$ref": "#/definitions/Variable"
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
{
|
||||
"type": "object",
|
||||
"required": [
|
||||
"arguments",
|
||||
"lambda_type",
|
||||
"ret",
|
||||
"type"
|
||||
],
|
||||
"properties": {
|
||||
"arguments": {
|
||||
"type": "array",
|
||||
"items": {
|
||||
"$ref": "#/definitions/Variable"
|
||||
}
|
||||
},
|
||||
"lambda_type": {
|
||||
"$ref": "#/definitions/Variable"
|
||||
},
|
||||
"ret": {
|
||||
"$ref": "#/definitions/Variable"
|
||||
},
|
||||
"type": {
|
||||
"type": "string",
|
||||
"enum": [
|
||||
"Function"
|
||||
]
|
||||
}
|
||||
}
|
||||
},
|
||||
{
|
||||
"type": "object",
|
||||
"required": [
|
||||
"extension",
|
||||
"fields",
|
||||
"type"
|
||||
],
|
||||
"properties": {
|
||||
"extension": {
|
||||
"$ref": "#/definitions/Variable"
|
||||
},
|
||||
"fields": {
|
||||
"type": "object",
|
||||
"additionalProperties": {
|
||||
"$ref": "#/definitions/RecordField"
|
||||
}
|
||||
},
|
||||
"type": {
|
||||
"type": "string",
|
||||
"enum": [
|
||||
"Record"
|
||||
]
|
||||
}
|
||||
}
|
||||
},
|
||||
{
|
||||
"type": "object",
|
||||
"required": [
|
||||
"elements",
|
||||
"extension",
|
||||
"type"
|
||||
],
|
||||
"properties": {
|
||||
"elements": {
|
||||
"type": "object",
|
||||
"additionalProperties": {
|
||||
"$ref": "#/definitions/Variable"
|
||||
}
|
||||
},
|
||||
"extension": {
|
||||
"$ref": "#/definitions/Variable"
|
||||
},
|
||||
"type": {
|
||||
"type": "string",
|
||||
"enum": [
|
||||
"Tuple"
|
||||
]
|
||||
}
|
||||
}
|
||||
},
|
||||
{
|
||||
"type": "object",
|
||||
"required": [
|
||||
"extension",
|
||||
"tags",
|
||||
"type"
|
||||
],
|
||||
"properties": {
|
||||
"extension": {
|
||||
"$ref": "#/definitions/TagUnionExtension"
|
||||
},
|
||||
"tags": {
|
||||
"type": "object",
|
||||
"additionalProperties": {
|
||||
"type": "array",
|
||||
"items": {
|
||||
"$ref": "#/definitions/Variable"
|
||||
}
|
||||
}
|
||||
},
|
||||
"type": {
|
||||
"type": "string",
|
||||
"enum": [
|
||||
"TagUnion"
|
||||
]
|
||||
}
|
||||
}
|
||||
},
|
||||
{
|
||||
"type": "object",
|
||||
"required": [
|
||||
"extension",
|
||||
"functions",
|
||||
"tags",
|
||||
"type"
|
||||
],
|
||||
"properties": {
|
||||
"extension": {
|
||||
"$ref": "#/definitions/TagUnionExtension"
|
||||
},
|
||||
"functions": {
|
||||
"type": "array",
|
||||
"items": {
|
||||
"$ref": "#/definitions/Symbol"
|
||||
}
|
||||
},
|
||||
"tags": {
|
||||
"type": "array",
|
||||
"items": {
|
||||
"type": "string"
|
||||
}
|
||||
},
|
||||
"type": {
|
||||
"type": "string",
|
||||
"enum": [
|
||||
"FunctionOrTagUnion"
|
||||
]
|
||||
}
|
||||
}
|
||||
},
|
||||
{
|
||||
"type": "object",
|
||||
"required": [
|
||||
"extension",
|
||||
"recursion_var",
|
||||
"tags",
|
||||
"type"
|
||||
],
|
||||
"properties": {
|
||||
"extension": {
|
||||
"$ref": "#/definitions/TagUnionExtension"
|
||||
},
|
||||
"recursion_var": {
|
||||
"$ref": "#/definitions/Variable"
|
||||
},
|
||||
"tags": {
|
||||
"type": "object",
|
||||
"additionalProperties": {
|
||||
"type": "array",
|
||||
"items": {
|
||||
"$ref": "#/definitions/Variable"
|
||||
}
|
||||
}
|
||||
},
|
||||
"type": {
|
||||
"type": "string",
|
||||
"enum": [
|
||||
"RecursiveTagUnion"
|
||||
]
|
||||
}
|
||||
}
|
||||
},
|
||||
{
|
||||
"type": "object",
|
||||
"required": [
|
||||
"type"
|
||||
],
|
||||
"properties": {
|
||||
"type": {
|
||||
"type": "string",
|
||||
"enum": [
|
||||
"EmptyRecord"
|
||||
]
|
||||
}
|
||||
}
|
||||
},
|
||||
{
|
||||
"type": "object",
|
||||
"required": [
|
||||
"type"
|
||||
],
|
||||
"properties": {
|
||||
"type": {
|
||||
"type": "string",
|
||||
"enum": [
|
||||
"EmptyTuple"
|
||||
]
|
||||
}
|
||||
}
|
||||
},
|
||||
{
|
||||
"type": "object",
|
||||
"required": [
|
||||
"type"
|
||||
],
|
||||
"properties": {
|
||||
"type": {
|
||||
"type": "string",
|
||||
"enum": [
|
||||
"EmptyTagUnion"
|
||||
]
|
||||
}
|
||||
}
|
||||
},
|
||||
{
|
||||
"type": "object",
|
||||
"required": [
|
||||
"range",
|
||||
"type"
|
||||
],
|
||||
"properties": {
|
||||
"range": {
|
||||
"$ref": "#/definitions/NumericRange"
|
||||
},
|
||||
"type": {
|
||||
"type": "string",
|
||||
"enum": [
|
||||
"RangedNumber"
|
||||
]
|
||||
}
|
||||
}
|
||||
},
|
||||
{
|
||||
"type": "object",
|
||||
"required": [
|
||||
"type"
|
||||
],
|
||||
"properties": {
|
||||
"type": {
|
||||
"type": "string",
|
||||
"enum": [
|
||||
"Error"
|
||||
]
|
||||
}
|
||||
}
|
||||
}
|
||||
]
|
||||
},
|
||||
"Event": {
|
||||
"oneOf": [
|
||||
{
|
||||
"type": "object",
|
||||
"required": [
|
||||
"left",
|
||||
"mode",
|
||||
"right",
|
||||
"subevents",
|
||||
"type"
|
||||
],
|
||||
"properties": {
|
||||
"left": {
|
||||
"$ref": "#/definitions/Variable"
|
||||
},
|
||||
"mode": {
|
||||
"$ref": "#/definitions/UnificationMode"
|
||||
},
|
||||
"right": {
|
||||
"$ref": "#/definitions/Variable"
|
||||
},
|
||||
"subevents": {
|
||||
"type": "array",
|
||||
"items": {
|
||||
"$ref": "#/definitions/Event"
|
||||
}
|
||||
},
|
||||
"success": {
|
||||
"type": [
|
||||
"boolean",
|
||||
"null"
|
||||
]
|
||||
},
|
||||
"type": {
|
||||
"type": "string",
|
||||
"enum": [
|
||||
"Unification"
|
||||
]
|
||||
}
|
||||
}
|
||||
},
|
||||
{
|
||||
"type": "object",
|
||||
"required": [
|
||||
"from",
|
||||
"to",
|
||||
"type"
|
||||
],
|
||||
"properties": {
|
||||
"from": {
|
||||
"$ref": "#/definitions/Variable"
|
||||
},
|
||||
"to": {
|
||||
"$ref": "#/definitions/Variable"
|
||||
},
|
||||
"type": {
|
||||
"type": "string",
|
||||
"enum": [
|
||||
"VariableUnified"
|
||||
]
|
||||
}
|
||||
}
|
||||
},
|
||||
{
|
||||
"type": "object",
|
||||
"required": [
|
||||
"type",
|
||||
"variable"
|
||||
],
|
||||
"properties": {
|
||||
"content": {
|
||||
"anyOf": [
|
||||
{
|
||||
"$ref": "#/definitions/Content"
|
||||
},
|
||||
{
|
||||
"type": "null"
|
||||
}
|
||||
]
|
||||
},
|
||||
"rank": {
|
||||
"anyOf": [
|
||||
{
|
||||
"$ref": "#/definitions/Rank"
|
||||
},
|
||||
{
|
||||
"type": "null"
|
||||
}
|
||||
]
|
||||
},
|
||||
"type": {
|
||||
"type": "string",
|
||||
"enum": [
|
||||
"VariableSetDescriptor"
|
||||
]
|
||||
},
|
||||
"variable": {
|
||||
"$ref": "#/definitions/Variable"
|
||||
}
|
||||
}
|
||||
}
|
||||
]
|
||||
},
|
||||
"NumericRange": {
|
||||
"type": "object",
|
||||
"required": [
|
||||
"kind",
|
||||
"min_width",
|
||||
"signed"
|
||||
],
|
||||
"properties": {
|
||||
"kind": {
|
||||
"$ref": "#/definitions/NumericRangeKind"
|
||||
},
|
||||
"min_width": {
|
||||
"type": "integer",
|
||||
"format": "uint32",
|
||||
"minimum": 0.0
|
||||
},
|
||||
"signed": {
|
||||
"type": "boolean"
|
||||
}
|
||||
}
|
||||
},
|
||||
"NumericRangeKind": {
|
||||
"oneOf": [
|
||||
{
|
||||
"type": "object",
|
||||
"required": [
|
||||
"type"
|
||||
],
|
||||
"properties": {
|
||||
"type": {
|
||||
"type": "string",
|
||||
"enum": [
|
||||
"Int"
|
||||
]
|
||||
}
|
||||
}
|
||||
},
|
||||
{
|
||||
"type": "object",
|
||||
"required": [
|
||||
"type"
|
||||
],
|
||||
"properties": {
|
||||
"type": {
|
||||
"type": "string",
|
||||
"enum": [
|
||||
"AnyNum"
|
||||
]
|
||||
}
|
||||
}
|
||||
}
|
||||
]
|
||||
},
|
||||
"Rank": {
|
||||
"type": "integer",
|
||||
"format": "uint32",
|
||||
"minimum": 0.0
|
||||
},
|
||||
"RecordField": {
|
||||
"type": "object",
|
||||
"required": [
|
||||
"field_type",
|
||||
"kind"
|
||||
],
|
||||
"properties": {
|
||||
"field_type": {
|
||||
"$ref": "#/definitions/Variable"
|
||||
},
|
||||
"kind": {
|
||||
"$ref": "#/definitions/RecordFieldKind"
|
||||
}
|
||||
}
|
||||
},
|
||||
"RecordFieldKind": {
|
||||
"oneOf": [
|
||||
{
|
||||
"type": "object",
|
||||
"required": [
|
||||
"type"
|
||||
],
|
||||
"properties": {
|
||||
"type": {
|
||||
"type": "string",
|
||||
"enum": [
|
||||
"Demanded"
|
||||
]
|
||||
}
|
||||
}
|
||||
},
|
||||
{
|
||||
"type": "object",
|
||||
"required": [
|
||||
"rigid",
|
||||
"type"
|
||||
],
|
||||
"properties": {
|
||||
"rigid": {
|
||||
"type": "boolean"
|
||||
},
|
||||
"type": {
|
||||
"type": "string",
|
||||
"enum": [
|
||||
"Required"
|
||||
]
|
||||
}
|
||||
}
|
||||
},
|
||||
{
|
||||
"type": "object",
|
||||
"required": [
|
||||
"rigid",
|
||||
"type"
|
||||
],
|
||||
"properties": {
|
||||
"rigid": {
|
||||
"type": "boolean"
|
||||
},
|
||||
"type": {
|
||||
"type": "string",
|
||||
"enum": [
|
||||
"Optional"
|
||||
]
|
||||
}
|
||||
}
|
||||
}
|
||||
]
|
||||
},
|
||||
"Symbol": {
|
||||
"type": "string"
|
||||
},
|
||||
"TagUnionExtension": {
|
||||
"oneOf": [
|
||||
{
|
||||
"type": "object",
|
||||
"required": [
|
||||
"type",
|
||||
"variable"
|
||||
],
|
||||
"properties": {
|
||||
"type": {
|
||||
"type": "string",
|
||||
"enum": [
|
||||
"Openness"
|
||||
]
|
||||
},
|
||||
"variable": {
|
||||
"$ref": "#/definitions/Variable"
|
||||
}
|
||||
}
|
||||
},
|
||||
{
|
||||
"type": "object",
|
||||
"required": [
|
||||
"type",
|
||||
"variable"
|
||||
],
|
||||
"properties": {
|
||||
"type": {
|
||||
"type": "string",
|
||||
"enum": [
|
||||
"Any"
|
||||
]
|
||||
},
|
||||
"variable": {
|
||||
"$ref": "#/definitions/Variable"
|
||||
}
|
||||
}
|
||||
}
|
||||
]
|
||||
},
|
||||
"UnificationMode": {
|
||||
"oneOf": [
|
||||
{
|
||||
"type": "object",
|
||||
"required": [
|
||||
"type"
|
||||
],
|
||||
"properties": {
|
||||
"type": {
|
||||
"type": "string",
|
||||
"enum": [
|
||||
"Eq"
|
||||
]
|
||||
}
|
||||
}
|
||||
},
|
||||
{
|
||||
"type": "object",
|
||||
"required": [
|
||||
"type"
|
||||
],
|
||||
"properties": {
|
||||
"type": {
|
||||
"type": "string",
|
||||
"enum": [
|
||||
"Present"
|
||||
]
|
||||
}
|
||||
}
|
||||
},
|
||||
{
|
||||
"type": "object",
|
||||
"required": [
|
||||
"type"
|
||||
],
|
||||
"properties": {
|
||||
"type": {
|
||||
"type": "string",
|
||||
"enum": [
|
||||
"LambdaSetSpecialization"
|
||||
]
|
||||
}
|
||||
}
|
||||
}
|
||||
]
|
||||
},
|
||||
"UnspecializedClosureType": {
|
||||
"type": "object",
|
||||
"required": [
|
||||
"ability_member",
|
||||
"lambda_set_region",
|
||||
"specialization"
|
||||
],
|
||||
"properties": {
|
||||
"ability_member": {
|
||||
"$ref": "#/definitions/Symbol"
|
||||
},
|
||||
"lambda_set_region": {
|
||||
"type": "integer",
|
||||
"format": "uint8",
|
||||
"minimum": 0.0
|
||||
},
|
||||
"specialization": {
|
||||
"$ref": "#/definitions/Variable"
|
||||
}
|
||||
}
|
||||
},
|
||||
"Variable": {
|
||||
"type": "integer",
|
||||
"format": "uint32",
|
||||
"minimum": 0.0
|
||||
}
|
||||
}
|
||||
}
|
175
crates/compiler/checkmate/src/collector.rs
Normal file
175
crates/compiler/checkmate/src/collector.rs
Normal file
|
@ -0,0 +1,175 @@
|
|||
use std::error::Error;
|
||||
|
||||
use roc_checkmate_schema::{AllEvents, Event};
|
||||
use roc_types::subs as s;
|
||||
|
||||
use crate::convert::AsSchema;
|
||||
|
||||
#[derive(Debug)]
|
||||
pub struct Collector {
|
||||
events: AllEvents,
|
||||
current_event_path: Vec<usize>,
|
||||
}
|
||||
|
||||
impl Default for Collector {
|
||||
fn default() -> Self {
|
||||
Self::new()
|
||||
}
|
||||
}
|
||||
|
||||
impl Collector {
|
||||
pub fn new() -> Self {
|
||||
Self {
|
||||
events: AllEvents(Vec::new()),
|
||||
current_event_path: Vec::new(),
|
||||
}
|
||||
}
|
||||
|
||||
pub fn unify(&mut self, subs: &s::Subs, from: s::Variable, to: s::Variable) {
|
||||
let to = to.as_schema(subs);
|
||||
let from = from.as_schema(subs);
|
||||
self.add_event(Event::VariableUnified { to, from });
|
||||
}
|
||||
|
||||
pub fn set_content(&mut self, subs: &s::Subs, var: s::Variable, content: s::Content) {
|
||||
let variable = var.as_schema(subs);
|
||||
let content = content.as_schema(subs);
|
||||
self.add_event(Event::VariableSetDescriptor {
|
||||
variable,
|
||||
content: Some(content),
|
||||
rank: None,
|
||||
});
|
||||
}
|
||||
|
||||
pub fn set_rank(&mut self, subs: &s::Subs, var: s::Variable, rank: s::Rank) {
|
||||
let variable = var.as_schema(subs);
|
||||
let rank = rank.as_schema(subs);
|
||||
self.add_event(Event::VariableSetDescriptor {
|
||||
variable,
|
||||
rank: Some(rank),
|
||||
content: None,
|
||||
});
|
||||
}
|
||||
|
||||
pub fn set_descriptor(&mut self, subs: &s::Subs, var: s::Variable, descriptor: s::Descriptor) {
|
||||
let variable = var.as_schema(subs);
|
||||
let rank = descriptor.rank.as_schema(subs);
|
||||
let content = descriptor.content.as_schema(subs);
|
||||
self.add_event(Event::VariableSetDescriptor {
|
||||
variable,
|
||||
rank: Some(rank),
|
||||
content: Some(content),
|
||||
});
|
||||
}
|
||||
|
||||
pub fn start_unification(
|
||||
&mut self,
|
||||
subs: &s::Subs,
|
||||
left: s::Variable,
|
||||
right: s::Variable,
|
||||
mode: roc_solve_schema::UnificationMode,
|
||||
) {
|
||||
let left = left.as_schema(subs);
|
||||
let right = right.as_schema(subs);
|
||||
let mode = mode.as_schema(subs);
|
||||
let subevents = Vec::new();
|
||||
self.add_event(Event::Unification {
|
||||
left,
|
||||
right,
|
||||
mode,
|
||||
subevents,
|
||||
success: None,
|
||||
});
|
||||
}
|
||||
|
||||
pub fn end_unification(
|
||||
&mut self,
|
||||
subs: &s::Subs,
|
||||
left: s::Variable,
|
||||
right: s::Variable,
|
||||
success: bool,
|
||||
) {
|
||||
let current_event = self.get_path_event();
|
||||
match current_event {
|
||||
EventW::Sub(Event::Unification {
|
||||
left: l,
|
||||
right: r,
|
||||
success: s,
|
||||
..
|
||||
}) => {
|
||||
assert_eq!(left.as_schema(subs), *l);
|
||||
assert_eq!(right.as_schema(subs), *r);
|
||||
assert!(s.is_none());
|
||||
*s = Some(success);
|
||||
}
|
||||
_ => panic!("end_unification called when not in a unification"),
|
||||
}
|
||||
self.current_event_path.pop();
|
||||
}
|
||||
|
||||
pub fn write(&self, writer: impl std::io::Write) -> Result<(), Box<dyn Error>> {
|
||||
self.events.write(writer)?;
|
||||
Ok(())
|
||||
}
|
||||
|
||||
fn add_event(&mut self, event: impl Into<Event>) {
|
||||
let mut event = event.into();
|
||||
let is_appendable = EventW::Sub(&mut event).appendable();
|
||||
let event = event;
|
||||
|
||||
let path_event = self.get_path_event();
|
||||
let new_event_index = path_event.append(event);
|
||||
if is_appendable {
|
||||
self.current_event_path.push(new_event_index);
|
||||
}
|
||||
}
|
||||
|
||||
fn get_path_event(&mut self) -> EventW {
|
||||
let mut event = EventW::Top(&mut self.events);
|
||||
for i in &self.current_event_path {
|
||||
event = event.index(*i);
|
||||
}
|
||||
event
|
||||
}
|
||||
}
|
||||
|
||||
enum EventW<'a> {
|
||||
Top(&'a mut AllEvents),
|
||||
Sub(&'a mut Event),
|
||||
}
|
||||
|
||||
impl<'a> EventW<'a> {
|
||||
fn append(self, event: Event) -> usize {
|
||||
let list = self.subevents_mut().unwrap();
|
||||
let index = list.len();
|
||||
list.push(event);
|
||||
index
|
||||
}
|
||||
|
||||
fn appendable(self) -> bool {
|
||||
self.subevents().is_some()
|
||||
}
|
||||
|
||||
fn index(self, index: usize) -> EventW<'a> {
|
||||
Self::Sub(&mut self.subevents_mut().unwrap()[index])
|
||||
}
|
||||
}
|
||||
|
||||
impl<'a> EventW<'a> {
|
||||
fn subevents(self) -> Option<&'a Vec<Event>> {
|
||||
use EventW::*;
|
||||
match self {
|
||||
Top(events) => Some(&events.0),
|
||||
Sub(Event::Unification { subevents, .. }) => Some(subevents),
|
||||
Sub(Event::VariableUnified { .. } | Event::VariableSetDescriptor { .. }) => None,
|
||||
}
|
||||
}
|
||||
fn subevents_mut(self) -> Option<&'a mut Vec<Event>> {
|
||||
use EventW::*;
|
||||
match self {
|
||||
Top(events) => Some(&mut events.0),
|
||||
Sub(Event::Unification { subevents, .. }) => Some(subevents),
|
||||
Sub(Event::VariableUnified { .. } | Event::VariableSetDescriptor { .. }) => None,
|
||||
}
|
||||
}
|
||||
}
|
323
crates/compiler/checkmate/src/convert.rs
Normal file
323
crates/compiler/checkmate/src/convert.rs
Normal file
|
@ -0,0 +1,323 @@
|
|||
use std::collections::HashMap;
|
||||
|
||||
use roc_module::{ident, symbol};
|
||||
use roc_types::{
|
||||
num,
|
||||
subs::{self, GetSubsSlice, Subs, SubsIndex, SubsSlice, UnionLabels},
|
||||
types,
|
||||
};
|
||||
|
||||
use roc_checkmate_schema::{
|
||||
AliasKind, AliasTypeVariables, ClosureType, Content, NumericRange, NumericRangeKind, Rank,
|
||||
RecordField, RecordFieldKind, Symbol, TagUnionExtension, UnificationMode,
|
||||
UnspecializedClosureType, Variable,
|
||||
};
|
||||
|
||||
pub trait AsSchema<T> {
|
||||
fn as_schema(&self, subs: &Subs) -> T;
|
||||
}
|
||||
|
||||
impl<T, U> AsSchema<Option<U>> for Option<T>
|
||||
where
|
||||
T: AsSchema<U>,
|
||||
T: Copy,
|
||||
{
|
||||
fn as_schema(&self, subs: &Subs) -> Option<U> {
|
||||
self.map(|i| i.as_schema(subs))
|
||||
}
|
||||
}
|
||||
|
||||
impl<T, U> AsSchema<Vec<U>> for &[T]
|
||||
where
|
||||
T: AsSchema<U>,
|
||||
{
|
||||
fn as_schema(&self, subs: &Subs) -> Vec<U> {
|
||||
self.iter().map(|i| i.as_schema(subs)).collect()
|
||||
}
|
||||
}
|
||||
|
||||
impl<T, U> AsSchema<U> for SubsIndex<T>
|
||||
where
|
||||
Subs: std::ops::Index<SubsIndex<T>, Output = T>,
|
||||
T: AsSchema<U>,
|
||||
{
|
||||
fn as_schema(&self, subs: &Subs) -> U {
|
||||
subs[*self].as_schema(subs)
|
||||
}
|
||||
}
|
||||
|
||||
impl<T, U> AsSchema<Vec<U>> for SubsSlice<T>
|
||||
where
|
||||
Subs: GetSubsSlice<T>,
|
||||
T: AsSchema<U>,
|
||||
{
|
||||
fn as_schema(&self, subs: &Subs) -> Vec<U> {
|
||||
subs.get_subs_slice(*self)
|
||||
.iter()
|
||||
.map(|i| i.as_schema(subs))
|
||||
.collect()
|
||||
}
|
||||
}
|
||||
|
||||
impl AsSchema<Content> for subs::Content {
|
||||
fn as_schema(&self, subs: &Subs) -> Content {
|
||||
use {subs::Content as A, Content as B};
|
||||
match self {
|
||||
A::FlexVar(name) => B::Flex(name.as_schema(subs)),
|
||||
A::RigidVar(name) => B::Rigid(name.as_schema(subs)),
|
||||
A::FlexAbleVar(name, abilities) => {
|
||||
B::FlexAble(name.as_schema(subs), abilities.as_schema(subs))
|
||||
}
|
||||
A::RigidAbleVar(name, abilities) => {
|
||||
B::RigidAble(name.as_schema(subs), abilities.as_schema(subs))
|
||||
}
|
||||
A::RecursionVar {
|
||||
structure,
|
||||
opt_name,
|
||||
} => B::Recursive(opt_name.as_schema(subs), structure.as_schema(subs)),
|
||||
A::LambdaSet(lambda_set) => lambda_set.as_schema(subs),
|
||||
A::ErasedLambda => B::ErasedLambda(),
|
||||
A::Structure(flat_type) => flat_type.as_schema(subs),
|
||||
A::Alias(name, type_vars, real_var, kind) => B::Alias(
|
||||
name.as_schema(subs),
|
||||
type_vars.as_schema(subs),
|
||||
real_var.as_schema(subs),
|
||||
kind.as_schema(subs),
|
||||
),
|
||||
A::RangedNumber(range) => B::RangedNumber(range.as_schema(subs)),
|
||||
A::Error => B::Error(),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl AsSchema<Content> for subs::FlatType {
|
||||
fn as_schema(&self, subs: &Subs) -> Content {
|
||||
match self {
|
||||
subs::FlatType::Apply(symbol, variables) => {
|
||||
Content::Apply(symbol.as_schema(subs), variables.as_schema(subs))
|
||||
}
|
||||
subs::FlatType::Func(arguments, closure, ret) => Content::Function(
|
||||
arguments.as_schema(subs),
|
||||
closure.as_schema(subs),
|
||||
ret.as_schema(subs),
|
||||
),
|
||||
subs::FlatType::Record(fields, ext) => {
|
||||
Content::Record(fields.as_schema(subs), ext.as_schema(subs))
|
||||
}
|
||||
subs::FlatType::Tuple(elems, ext) => {
|
||||
Content::Tuple(elems.as_schema(subs), ext.as_schema(subs))
|
||||
}
|
||||
subs::FlatType::TagUnion(tags, ext) => {
|
||||
Content::TagUnion(tags.as_schema(subs), ext.as_schema(subs))
|
||||
}
|
||||
subs::FlatType::FunctionOrTagUnion(tags, functions, ext) => {
|
||||
Content::FunctionOrTagUnion(
|
||||
functions.as_schema(subs),
|
||||
tags.as_schema(subs),
|
||||
ext.as_schema(subs),
|
||||
)
|
||||
}
|
||||
subs::FlatType::RecursiveTagUnion(rec_var, tags, ext) => Content::RecursiveTagUnion(
|
||||
rec_var.as_schema(subs),
|
||||
tags.as_schema(subs),
|
||||
ext.as_schema(subs),
|
||||
),
|
||||
subs::FlatType::EmptyRecord => Content::EmptyRecord(),
|
||||
subs::FlatType::EmptyTuple => Content::EmptyTuple(),
|
||||
subs::FlatType::EmptyTagUnion => Content::EmptyTagUnion(),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl AsSchema<Content> for subs::LambdaSet {
|
||||
fn as_schema(&self, subs: &Subs) -> Content {
|
||||
let subs::LambdaSet {
|
||||
solved,
|
||||
unspecialized,
|
||||
recursion_var,
|
||||
ambient_function,
|
||||
} = self;
|
||||
|
||||
Content::LambdaSet(
|
||||
solved.as_schema(subs),
|
||||
unspecialized.as_schema(subs),
|
||||
recursion_var.as_schema(subs),
|
||||
ambient_function.as_schema(subs),
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
impl AsSchema<String> for ident::Lowercase {
|
||||
fn as_schema(&self, _subs: &Subs) -> String {
|
||||
self.to_string()
|
||||
}
|
||||
}
|
||||
|
||||
impl AsSchema<Symbol> for symbol::Symbol {
|
||||
fn as_schema(&self, _subs: &Subs) -> Symbol {
|
||||
Symbol(format!("{:#?}", self))
|
||||
}
|
||||
}
|
||||
|
||||
impl AsSchema<Variable> for subs::Variable {
|
||||
fn as_schema(&self, _subs: &Subs) -> Variable {
|
||||
Variable(self.index())
|
||||
}
|
||||
}
|
||||
|
||||
impl AsSchema<Option<Variable>> for subs::OptVariable {
|
||||
fn as_schema(&self, _subs: &Subs) -> Option<Variable> {
|
||||
self.into_variable().map(|i| i.as_schema(_subs))
|
||||
}
|
||||
}
|
||||
|
||||
impl AsSchema<Vec<ClosureType>> for UnionLabels<symbol::Symbol> {
|
||||
fn as_schema(&self, subs: &Subs) -> Vec<ClosureType> {
|
||||
self.iter_from_subs(subs)
|
||||
.map(|(function, environment)| ClosureType {
|
||||
function: function.as_schema(subs),
|
||||
environment: environment.as_schema(subs),
|
||||
})
|
||||
.collect()
|
||||
}
|
||||
}
|
||||
|
||||
impl AsSchema<UnspecializedClosureType> for types::Uls {
|
||||
fn as_schema(&self, subs: &Subs) -> UnspecializedClosureType {
|
||||
let types::Uls(specialization, ability_member, lambda_set_region) = self;
|
||||
|
||||
UnspecializedClosureType {
|
||||
specialization: specialization.as_schema(subs),
|
||||
ability_member: ability_member.as_schema(subs),
|
||||
lambda_set_region: *lambda_set_region,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl AsSchema<AliasTypeVariables> for subs::AliasVariables {
|
||||
fn as_schema(&self, subs: &Subs) -> AliasTypeVariables {
|
||||
let type_variables = self.type_variables().as_schema(subs);
|
||||
let lambda_set_variables = self.lambda_set_variables().as_schema(subs);
|
||||
let infer_ext_in_output_position_variables =
|
||||
self.infer_ext_in_output_variables().as_schema(subs);
|
||||
|
||||
AliasTypeVariables {
|
||||
type_variables,
|
||||
lambda_set_variables,
|
||||
infer_ext_in_output_position_variables,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl AsSchema<AliasKind> for types::AliasKind {
|
||||
fn as_schema(&self, _subs: &Subs) -> AliasKind {
|
||||
match self {
|
||||
types::AliasKind::Structural => AliasKind::Structural,
|
||||
types::AliasKind::Opaque => AliasKind::Opaque,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl AsSchema<HashMap<String, RecordField>> for subs::RecordFields {
|
||||
fn as_schema(&self, subs: &Subs) -> HashMap<String, RecordField> {
|
||||
let mut map = HashMap::new();
|
||||
for (name, var, field) in self.iter_all() {
|
||||
let name = name.as_schema(subs);
|
||||
let field_type = var.as_schema(subs);
|
||||
let kind = field.as_schema(subs);
|
||||
map.insert(name, RecordField { field_type, kind });
|
||||
}
|
||||
map
|
||||
}
|
||||
}
|
||||
|
||||
impl AsSchema<RecordFieldKind> for types::RecordField<()> {
|
||||
fn as_schema(&self, _subs: &Subs) -> RecordFieldKind {
|
||||
match self {
|
||||
types::RecordField::Demanded(_) => RecordFieldKind::Demanded,
|
||||
types::RecordField::Required(_) => RecordFieldKind::Required { rigid: false },
|
||||
types::RecordField::Optional(_) => RecordFieldKind::Optional { rigid: false },
|
||||
types::RecordField::RigidRequired(_) => RecordFieldKind::Required { rigid: true },
|
||||
types::RecordField::RigidOptional(_) => RecordFieldKind::Optional { rigid: true },
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl AsSchema<HashMap<u32, Variable>> for subs::TupleElems {
|
||||
fn as_schema(&self, subs: &Subs) -> HashMap<u32, Variable> {
|
||||
let mut map = HashMap::new();
|
||||
for (index, var) in self.iter_all() {
|
||||
let name = subs[index] as _;
|
||||
let var = var.as_schema(subs);
|
||||
map.insert(name, var);
|
||||
}
|
||||
map
|
||||
}
|
||||
}
|
||||
|
||||
impl AsSchema<HashMap<String, Vec<Variable>>> for subs::UnionTags {
|
||||
fn as_schema(&self, subs: &Subs) -> HashMap<String, Vec<Variable>> {
|
||||
let mut map = HashMap::new();
|
||||
for (tag, payloads) in self.iter_from_subs(subs) {
|
||||
map.insert(tag.as_schema(subs), payloads.as_schema(subs));
|
||||
}
|
||||
map
|
||||
}
|
||||
}
|
||||
|
||||
impl AsSchema<TagUnionExtension> for subs::TagExt {
|
||||
fn as_schema(&self, subs: &Subs) -> TagUnionExtension {
|
||||
match self {
|
||||
subs::TagExt::Openness(var) => TagUnionExtension::Openness(var.as_schema(subs)),
|
||||
subs::TagExt::Any(var) => TagUnionExtension::Any(var.as_schema(subs)),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl AsSchema<NumericRange> for num::NumericRange {
|
||||
fn as_schema(&self, _subs: &Subs) -> NumericRange {
|
||||
let kind =
|
||||
match self {
|
||||
num::NumericRange::IntAtLeastSigned(_)
|
||||
| num::NumericRange::IntAtLeastEitherSign(_) => NumericRangeKind::Int,
|
||||
num::NumericRange::NumAtLeastSigned(_)
|
||||
| num::NumericRange::NumAtLeastEitherSign(_) => NumericRangeKind::AnyNum,
|
||||
};
|
||||
|
||||
let min_width = self.min_width();
|
||||
let (signedness, width) = min_width.signedness_and_width();
|
||||
let signed = signedness.is_signed();
|
||||
|
||||
NumericRange {
|
||||
kind,
|
||||
signed,
|
||||
min_width: width,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl AsSchema<String> for ident::TagName {
|
||||
fn as_schema(&self, _subs: &Subs) -> String {
|
||||
self.0.to_string()
|
||||
}
|
||||
}
|
||||
|
||||
impl AsSchema<Rank> for subs::Rank {
|
||||
fn as_schema(&self, _subs: &Subs) -> Rank {
|
||||
Rank(self.into_usize() as _)
|
||||
}
|
||||
}
|
||||
|
||||
impl AsSchema<UnificationMode> for roc_solve_schema::UnificationMode {
|
||||
fn as_schema(&self, _subs: &Subs) -> UnificationMode {
|
||||
if self.is_eq() {
|
||||
UnificationMode::Eq
|
||||
} else if self.is_present() {
|
||||
UnificationMode::Present
|
||||
} else if self.is_lambda_set_specialization() {
|
||||
UnificationMode::LambdaSetSpecialization
|
||||
} else {
|
||||
unreachable!()
|
||||
}
|
||||
}
|
||||
}
|
62
crates/compiler/checkmate/src/lib.rs
Normal file
62
crates/compiler/checkmate/src/lib.rs
Normal file
|
@ -0,0 +1,62 @@
|
|||
mod collector;
|
||||
mod convert;
|
||||
|
||||
pub use collector::Collector;
|
||||
|
||||
pub fn is_checkmate_enabled() -> bool {
|
||||
#[cfg(debug_assertions)]
|
||||
{
|
||||
let flag = std::env::var("ROC_CHECKMATE");
|
||||
flag.as_deref() == Ok("1")
|
||||
}
|
||||
#[cfg(not(debug_assertions))]
|
||||
{
|
||||
false
|
||||
}
|
||||
}
|
||||
|
||||
#[macro_export]
|
||||
macro_rules! debug_checkmate {
|
||||
($opt_collector:expr, $cm:ident => $expr:expr) => {
|
||||
#[cfg(debug_assertions)]
|
||||
{
|
||||
if let Some($cm) = $opt_collector.as_mut() {
|
||||
$expr
|
||||
}
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
#[macro_export]
|
||||
macro_rules! dump_checkmate {
|
||||
($opt_collector:expr) => {
|
||||
#[cfg(debug_assertions)]
|
||||
{
|
||||
if let Some(cm) = $opt_collector.as_ref() {
|
||||
$crate::dump_checkmate(cm);
|
||||
}
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
pub fn dump_checkmate(collector: &Collector) {
|
||||
let timestamp = chrono::Local::now().format("%Y%m%d_%H-%M-%S");
|
||||
let filename = format!("checkmate_{timestamp}.json");
|
||||
let fi = std::fs::File::create(&filename).unwrap();
|
||||
collector.write(fi).unwrap();
|
||||
eprintln!("Wrote checkmate output to {filename}");
|
||||
}
|
||||
|
||||
#[macro_export]
|
||||
macro_rules! with_checkmate {
|
||||
({ on => $on:expr, off => $off:expr, }) => {{
|
||||
#[cfg(debug_assertions)]
|
||||
{
|
||||
$on
|
||||
}
|
||||
#[cfg(not(debug_assertions))]
|
||||
{
|
||||
$off
|
||||
}
|
||||
}};
|
||||
}
|
17
crates/compiler/checkmate/www/.gitignore
vendored
Normal file
17
crates/compiler/checkmate/www/.gitignore
vendored
Normal file
|
@ -0,0 +1,17 @@
|
|||
/node_modules
|
||||
/.pnp
|
||||
.pnp.js
|
||||
|
||||
/coverage
|
||||
|
||||
/build
|
||||
|
||||
.DS_Store
|
||||
.env.local
|
||||
.env.development.local
|
||||
.env.test.local
|
||||
.env.production.local
|
||||
|
||||
npm-debug.log*
|
||||
yarn-debug.log*
|
||||
yarn-error.log*
|
4
crates/compiler/checkmate/www/.vim/coc-settings.json
Normal file
4
crates/compiler/checkmate/www/.vim/coc-settings.json
Normal file
|
@ -0,0 +1,4 @@
|
|||
{
|
||||
"tsserver.useLocalTsdk": true,
|
||||
"tsserver.tsdk": "${workspaceFolder}/node_modules/typescript/lib"
|
||||
}
|
19129
crates/compiler/checkmate/www/package-lock.json
generated
Normal file
19129
crates/compiler/checkmate/www/package-lock.json
generated
Normal file
File diff suppressed because it is too large
Load diff
54
crates/compiler/checkmate/www/package.json
Normal file
54
crates/compiler/checkmate/www/package.json
Normal file
|
@ -0,0 +1,54 @@
|
|||
{
|
||||
"name": "checkmate",
|
||||
"version": "0.1.0",
|
||||
"private": true,
|
||||
"dependencies": {
|
||||
"elkjs": "^0.8.2",
|
||||
"react": "^18.2.0",
|
||||
"react-dom": "^18.2.0",
|
||||
"react-router-dom": "^6.14.2",
|
||||
"react-router-hash-link": "^2.4.3",
|
||||
"react-tooltip": "^5.19.0",
|
||||
"reactflow": "^11.7.4"
|
||||
},
|
||||
"scripts": {
|
||||
"start": "react-scripts start",
|
||||
"build": "npm run build:codegen && react-scripts build",
|
||||
"build:codegen": "node ./scripts/gen_schema_dts.js",
|
||||
"check": "tsc",
|
||||
"lint": "eslint src/ scripts/",
|
||||
"eject": "react-scripts eject"
|
||||
},
|
||||
"eslintConfig": {
|
||||
"extends": [
|
||||
"react-app",
|
||||
"react-app/jest"
|
||||
]
|
||||
},
|
||||
"browserslist": {
|
||||
"production": [
|
||||
">0.2%",
|
||||
"not dead",
|
||||
"not op_mini all"
|
||||
],
|
||||
"development": [
|
||||
"last 1 chrome version",
|
||||
"last 1 firefox version",
|
||||
"last 1 safari version"
|
||||
]
|
||||
},
|
||||
"devDependencies": {
|
||||
"@babel/plugin-proposal-private-property-in-object": "^7.21.11",
|
||||
"@types/jest": "^27.5.2",
|
||||
"@types/node": "^16.18.38",
|
||||
"@types/react": "^18.2.15",
|
||||
"@types/react-dom": "^18.2.7",
|
||||
"@types/react-router-hash-link": "^2.4.6",
|
||||
"clsx": "^2.0.0",
|
||||
"json-schema-to-typescript": "^13.0.2",
|
||||
"react-scripts": "5.0.1",
|
||||
"tailwindcss": "^3.3.3",
|
||||
"tiny-typed-emitter": "^2.1.0",
|
||||
"typescript": "^4.9.5"
|
||||
}
|
||||
}
|
14
crates/compiler/checkmate/www/public/index.html
Normal file
14
crates/compiler/checkmate/www/public/index.html
Normal file
|
@ -0,0 +1,14 @@
|
|||
<!DOCTYPE html>
|
||||
<html lang="en">
|
||||
<head>
|
||||
<meta charset="utf-8" />
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1" />
|
||||
<meta name="theme-color" content="#000000" />
|
||||
<meta name="description" content="checkmate in N" />
|
||||
<title>Checkmate</title>
|
||||
</head>
|
||||
<body>
|
||||
<noscript>You need to enable JavaScript to run this app.</noscript>
|
||||
<div id="root"></div>
|
||||
</body>
|
||||
</html>
|
24
crates/compiler/checkmate/www/scripts/gen_schema_dts.js
Normal file
24
crates/compiler/checkmate/www/scripts/gen_schema_dts.js
Normal file
|
@ -0,0 +1,24 @@
|
|||
const {compileFromFile} = require("json-schema-to-typescript");
|
||||
const fs = require("node:fs/promises");
|
||||
const path = require("node:path");
|
||||
|
||||
const SCHEMA_PATH = path.resolve(
|
||||
__dirname,
|
||||
"..",
|
||||
"..",
|
||||
"schema.json"
|
||||
);
|
||||
|
||||
const DTS_PATH = path.resolve(
|
||||
__dirname,
|
||||
"..",
|
||||
"src",
|
||||
"schema.d.ts"
|
||||
);
|
||||
|
||||
async function main() {
|
||||
const result = await compileFromFile(SCHEMA_PATH);
|
||||
await fs.writeFile(DTS_PATH, result);
|
||||
}
|
||||
|
||||
main().catch(console.error);
|
37
crates/compiler/checkmate/www/src/App.tsx
Normal file
37
crates/compiler/checkmate/www/src/App.tsx
Normal file
|
@ -0,0 +1,37 @@
|
|||
import React from "react";
|
||||
import FileInput, { LoadedEvents } from "./components/FileInput";
|
||||
import Ui from "./components/Ui";
|
||||
import { BrowserRouter } from "react-router-dom";
|
||||
|
||||
export default function App() {
|
||||
const [events, setEvents] = React.useState<LoadedEvents | null>(null);
|
||||
|
||||
return (
|
||||
<BrowserRouter>
|
||||
<div className="w-screen h-screen p-2 bg-gray-100 flex flex-col">
|
||||
<div>
|
||||
<FileInput setResult={setEvents} />
|
||||
</div>
|
||||
<div className="flex-1 overflow-hidden">
|
||||
<EventsWrapper events={events} />
|
||||
</div>
|
||||
</div>
|
||||
</BrowserRouter>
|
||||
);
|
||||
}
|
||||
|
||||
interface EventsWrapperProps {
|
||||
events: LoadedEvents | null;
|
||||
}
|
||||
|
||||
function EventsWrapper({ events }: EventsWrapperProps): JSX.Element {
|
||||
if (events === null) {
|
||||
return <div></div>;
|
||||
}
|
||||
switch (events.kind) {
|
||||
case "ok":
|
||||
return <Ui events={events.events} />;
|
||||
case "err":
|
||||
return <div className="text-red-400 text-lg">{events.error}</div>;
|
||||
}
|
||||
}
|
545
crates/compiler/checkmate/www/src/checkmate.json
Normal file
545
crates/compiler/checkmate/www/src/checkmate.json
Normal file
|
@ -0,0 +1,545 @@
|
|||
[
|
||||
{
|
||||
"type": "Unification",
|
||||
"left": 1540,
|
||||
"right": 71,
|
||||
"mode": { "type": "Eq" },
|
||||
"success": true,
|
||||
"subevents": [
|
||||
{
|
||||
"type": "VariableSetDescriptor",
|
||||
"variable": 71,
|
||||
"rank": 1,
|
||||
"content": {
|
||||
"type": "Function",
|
||||
"arguments": [1543, 1544],
|
||||
"lambda_type": 1542,
|
||||
"ret": 1541
|
||||
}
|
||||
},
|
||||
{ "type": "VariableUnified", "from": 1540, "to": 71 },
|
||||
{ "type": "VariableUnified", "from": 71, "to": 71 }
|
||||
]
|
||||
},
|
||||
{
|
||||
"type": "Unification",
|
||||
"left": 71,
|
||||
"right": 1546,
|
||||
"mode": { "type": "Eq" },
|
||||
"success": true,
|
||||
"subevents": [
|
||||
{
|
||||
"type": "Unification",
|
||||
"left": 1543,
|
||||
"right": 67,
|
||||
"mode": { "type": "Eq" },
|
||||
"success": true,
|
||||
"subevents": [
|
||||
{
|
||||
"type": "VariableSetDescriptor",
|
||||
"variable": 67,
|
||||
"rank": 1,
|
||||
"content": {
|
||||
"type": "Alias",
|
||||
"name": "`Num.Num`",
|
||||
"variables": {
|
||||
"type_variables": [1545],
|
||||
"lambda_set_variables": [],
|
||||
"infer_ext_in_output_position_variables": []
|
||||
},
|
||||
"real_variable": 1545,
|
||||
"kind": { "type": "Opaque" }
|
||||
}
|
||||
},
|
||||
{ "type": "VariableUnified", "from": 1543, "to": 67 },
|
||||
{ "type": "VariableUnified", "from": 67, "to": 67 }
|
||||
]
|
||||
},
|
||||
{
|
||||
"type": "Unification",
|
||||
"left": 1544,
|
||||
"right": 69,
|
||||
"mode": { "type": "Eq" },
|
||||
"success": true,
|
||||
"subevents": [
|
||||
{
|
||||
"type": "VariableSetDescriptor",
|
||||
"variable": 69,
|
||||
"rank": 1,
|
||||
"content": {
|
||||
"type": "Alias",
|
||||
"name": "`Num.Num`",
|
||||
"variables": {
|
||||
"type_variables": [1545],
|
||||
"lambda_set_variables": [],
|
||||
"infer_ext_in_output_position_variables": []
|
||||
},
|
||||
"real_variable": 1545,
|
||||
"kind": { "type": "Opaque" }
|
||||
}
|
||||
},
|
||||
{ "type": "VariableUnified", "from": 1544, "to": 69 },
|
||||
{ "type": "VariableUnified", "from": 69, "to": 69 }
|
||||
]
|
||||
},
|
||||
{
|
||||
"type": "Unification",
|
||||
"left": 1541,
|
||||
"right": 73,
|
||||
"mode": { "type": "Eq" },
|
||||
"success": true,
|
||||
"subevents": [
|
||||
{
|
||||
"type": "VariableSetDescriptor",
|
||||
"variable": 73,
|
||||
"rank": 1,
|
||||
"content": {
|
||||
"type": "Alias",
|
||||
"name": "`Num.Num`",
|
||||
"variables": {
|
||||
"type_variables": [1545],
|
||||
"lambda_set_variables": [],
|
||||
"infer_ext_in_output_position_variables": []
|
||||
},
|
||||
"real_variable": 1545,
|
||||
"kind": { "type": "Opaque" }
|
||||
}
|
||||
},
|
||||
{ "type": "VariableUnified", "from": 1541, "to": 73 },
|
||||
{ "type": "VariableUnified", "from": 73, "to": 73 }
|
||||
]
|
||||
},
|
||||
{
|
||||
"type": "Unification",
|
||||
"left": 1542,
|
||||
"right": 72,
|
||||
"mode": { "type": "Eq" },
|
||||
"success": true,
|
||||
"subevents": [
|
||||
{
|
||||
"type": "VariableSetDescriptor",
|
||||
"variable": 72,
|
||||
"rank": 1,
|
||||
"content": {
|
||||
"type": "LambdaSet",
|
||||
"solved": [{ "function": "`Num.add`", "environment": [] }],
|
||||
"unspecialized": [],
|
||||
"recursion_var": null,
|
||||
"ambient_function": 1540
|
||||
}
|
||||
},
|
||||
{ "type": "VariableUnified", "from": 1542, "to": 72 },
|
||||
{ "type": "VariableUnified", "from": 72, "to": 72 }
|
||||
]
|
||||
},
|
||||
{
|
||||
"type": "VariableSetDescriptor",
|
||||
"variable": 1546,
|
||||
"rank": 1,
|
||||
"content": {
|
||||
"type": "Function",
|
||||
"arguments": [67, 69],
|
||||
"lambda_type": 1542,
|
||||
"ret": 73
|
||||
}
|
||||
},
|
||||
{ "type": "VariableUnified", "from": 71, "to": 1546 },
|
||||
{ "type": "VariableUnified", "from": 1546, "to": 1546 }
|
||||
]
|
||||
},
|
||||
{
|
||||
"type": "Unification",
|
||||
"left": 66,
|
||||
"right": 1547,
|
||||
"mode": { "type": "Eq" },
|
||||
"success": true,
|
||||
"subevents": [
|
||||
{
|
||||
"type": "VariableSetDescriptor",
|
||||
"variable": 1547,
|
||||
"rank": 1,
|
||||
"content": {
|
||||
"type": "RangedNumber",
|
||||
"range": {
|
||||
"kind": { "type": "AnyNum" },
|
||||
"signed": true,
|
||||
"min_width": 8
|
||||
}
|
||||
}
|
||||
},
|
||||
{ "type": "VariableUnified", "from": 66, "to": 1547 },
|
||||
{ "type": "VariableUnified", "from": 1547, "to": 1547 }
|
||||
]
|
||||
},
|
||||
{
|
||||
"type": "Unification",
|
||||
"left": 1548,
|
||||
"right": 67,
|
||||
"mode": { "type": "Eq" },
|
||||
"success": true,
|
||||
"subevents": [
|
||||
{
|
||||
"type": "Unification",
|
||||
"left": 66,
|
||||
"right": 1545,
|
||||
"mode": { "type": "Eq" },
|
||||
"success": true,
|
||||
"subevents": [
|
||||
{
|
||||
"type": "VariableSetDescriptor",
|
||||
"variable": 1545,
|
||||
"rank": 1,
|
||||
"content": {
|
||||
"type": "RangedNumber",
|
||||
"range": {
|
||||
"kind": { "type": "AnyNum" },
|
||||
"signed": true,
|
||||
"min_width": 8
|
||||
}
|
||||
}
|
||||
},
|
||||
{ "type": "VariableUnified", "from": 1547, "to": 1545 },
|
||||
{ "type": "VariableUnified", "from": 1545, "to": 1545 }
|
||||
]
|
||||
},
|
||||
{
|
||||
"type": "VariableSetDescriptor",
|
||||
"variable": 67,
|
||||
"rank": 1,
|
||||
"content": {
|
||||
"type": "Alias",
|
||||
"name": "`Num.Num`",
|
||||
"variables": {
|
||||
"type_variables": [66],
|
||||
"lambda_set_variables": [],
|
||||
"infer_ext_in_output_position_variables": []
|
||||
},
|
||||
"real_variable": 66,
|
||||
"kind": { "type": "Opaque" }
|
||||
}
|
||||
},
|
||||
{ "type": "VariableUnified", "from": 1548, "to": 67 },
|
||||
{ "type": "VariableUnified", "from": 67, "to": 67 }
|
||||
]
|
||||
},
|
||||
{
|
||||
"type": "Unification",
|
||||
"left": 68,
|
||||
"right": 1549,
|
||||
"mode": { "type": "Eq" },
|
||||
"success": true,
|
||||
"subevents": [
|
||||
{
|
||||
"type": "VariableSetDescriptor",
|
||||
"variable": 1549,
|
||||
"rank": 1,
|
||||
"content": {
|
||||
"type": "RangedNumber",
|
||||
"range": {
|
||||
"kind": { "type": "AnyNum" },
|
||||
"signed": true,
|
||||
"min_width": 8
|
||||
}
|
||||
}
|
||||
},
|
||||
{ "type": "VariableUnified", "from": 68, "to": 1549 },
|
||||
{ "type": "VariableUnified", "from": 1549, "to": 1549 }
|
||||
]
|
||||
},
|
||||
{
|
||||
"type": "Unification",
|
||||
"left": 1550,
|
||||
"right": 69,
|
||||
"mode": { "type": "Eq" },
|
||||
"success": true,
|
||||
"subevents": [
|
||||
{
|
||||
"type": "Unification",
|
||||
"left": 68,
|
||||
"right": 1545,
|
||||
"mode": { "type": "Eq" },
|
||||
"success": true,
|
||||
"subevents": [
|
||||
{
|
||||
"type": "VariableSetDescriptor",
|
||||
"variable": 1545,
|
||||
"rank": 1,
|
||||
"content": {
|
||||
"type": "RangedNumber",
|
||||
"range": {
|
||||
"kind": { "type": "AnyNum" },
|
||||
"signed": true,
|
||||
"min_width": 8
|
||||
}
|
||||
}
|
||||
},
|
||||
{ "type": "VariableUnified", "from": 1549, "to": 1545 },
|
||||
{ "type": "VariableUnified", "from": 1545, "to": 1545 }
|
||||
]
|
||||
},
|
||||
{
|
||||
"type": "VariableSetDescriptor",
|
||||
"variable": 69,
|
||||
"rank": 1,
|
||||
"content": {
|
||||
"type": "Alias",
|
||||
"name": "`Num.Num`",
|
||||
"variables": {
|
||||
"type_variables": [68],
|
||||
"lambda_set_variables": [],
|
||||
"infer_ext_in_output_position_variables": []
|
||||
},
|
||||
"real_variable": 68,
|
||||
"kind": { "type": "Opaque" }
|
||||
}
|
||||
},
|
||||
{ "type": "VariableUnified", "from": 1550, "to": 69 },
|
||||
{ "type": "VariableUnified", "from": 69, "to": 69 }
|
||||
]
|
||||
},
|
||||
{
|
||||
"type": "Unification",
|
||||
"left": 73,
|
||||
"right": 1551,
|
||||
"mode": { "type": "Eq" },
|
||||
"success": true,
|
||||
"subevents": [
|
||||
{
|
||||
"type": "Unification",
|
||||
"left": 73,
|
||||
"right": 1552,
|
||||
"mode": { "type": "Eq" },
|
||||
"success": true,
|
||||
"subevents": [
|
||||
{
|
||||
"type": "Unification",
|
||||
"left": 1545,
|
||||
"right": 1553,
|
||||
"mode": { "type": "Eq" },
|
||||
"success": true,
|
||||
"subevents": [
|
||||
{
|
||||
"type": "Unification",
|
||||
"left": 1556,
|
||||
"right": 1554,
|
||||
"mode": { "type": "Eq" },
|
||||
"success": true,
|
||||
"subevents": [
|
||||
{
|
||||
"type": "VariableSetDescriptor",
|
||||
"variable": 1554,
|
||||
"rank": 1,
|
||||
"content": {
|
||||
"type": "Alias",
|
||||
"name": "`Num.Unsigned8`",
|
||||
"variables": {
|
||||
"type_variables": [],
|
||||
"lambda_set_variables": [],
|
||||
"infer_ext_in_output_position_variables": []
|
||||
},
|
||||
"real_variable": 1555,
|
||||
"kind": { "type": "Opaque" }
|
||||
}
|
||||
},
|
||||
{ "type": "VariableUnified", "from": 1556, "to": 1554 },
|
||||
{ "type": "VariableUnified", "from": 1554, "to": 1554 }
|
||||
]
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"type": "Unification",
|
||||
"left": 1545,
|
||||
"right": 1553,
|
||||
"mode": { "type": "Eq" },
|
||||
"success": true,
|
||||
"subevents": [
|
||||
{
|
||||
"type": "VariableSetDescriptor",
|
||||
"variable": 1553,
|
||||
"rank": 1,
|
||||
"content": {
|
||||
"type": "Alias",
|
||||
"name": "`Num.Integer`",
|
||||
"variables": {
|
||||
"type_variables": [1556],
|
||||
"lambda_set_variables": [],
|
||||
"infer_ext_in_output_position_variables": []
|
||||
},
|
||||
"real_variable": 1556,
|
||||
"kind": { "type": "Opaque" }
|
||||
}
|
||||
},
|
||||
{ "type": "VariableUnified", "from": 1545, "to": 1553 },
|
||||
{ "type": "VariableUnified", "from": 1553, "to": 1553 }
|
||||
]
|
||||
},
|
||||
{
|
||||
"type": "VariableSetDescriptor",
|
||||
"variable": 1552,
|
||||
"rank": 1,
|
||||
"content": {
|
||||
"type": "Alias",
|
||||
"name": "`Num.Num`",
|
||||
"variables": {
|
||||
"type_variables": [1545],
|
||||
"lambda_set_variables": [],
|
||||
"infer_ext_in_output_position_variables": []
|
||||
},
|
||||
"real_variable": 1545,
|
||||
"kind": { "type": "Opaque" }
|
||||
}
|
||||
},
|
||||
{ "type": "VariableUnified", "from": 73, "to": 1552 },
|
||||
{ "type": "VariableUnified", "from": 1552, "to": 1552 }
|
||||
]
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"type": "Unification",
|
||||
"left": 1551,
|
||||
"right": 75,
|
||||
"mode": { "type": "Eq" },
|
||||
"success": true,
|
||||
"subevents": [
|
||||
{
|
||||
"type": "VariableSetDescriptor",
|
||||
"variable": 75,
|
||||
"rank": 1,
|
||||
"content": {
|
||||
"type": "Alias",
|
||||
"name": "`Num.U8`",
|
||||
"variables": {
|
||||
"type_variables": [],
|
||||
"lambda_set_variables": [],
|
||||
"infer_ext_in_output_position_variables": []
|
||||
},
|
||||
"real_variable": 1552,
|
||||
"kind": { "type": "Structural" }
|
||||
}
|
||||
},
|
||||
{ "type": "VariableUnified", "from": 1551, "to": 75 },
|
||||
{ "type": "VariableUnified", "from": 75, "to": 75 }
|
||||
]
|
||||
},
|
||||
{
|
||||
"type": "Unification",
|
||||
"left": 80,
|
||||
"right": 75,
|
||||
"mode": { "type": "Eq" },
|
||||
"success": true,
|
||||
"subevents": [
|
||||
{
|
||||
"type": "VariableSetDescriptor",
|
||||
"variable": 75,
|
||||
"rank": 1,
|
||||
"content": {
|
||||
"type": "Alias",
|
||||
"name": "`Num.U8`",
|
||||
"variables": {
|
||||
"type_variables": [],
|
||||
"lambda_set_variables": [],
|
||||
"infer_ext_in_output_position_variables": []
|
||||
},
|
||||
"real_variable": 1552,
|
||||
"kind": { "type": "Structural" }
|
||||
}
|
||||
},
|
||||
{ "type": "VariableUnified", "from": 80, "to": 75 },
|
||||
{ "type": "VariableUnified", "from": 75, "to": 75 }
|
||||
]
|
||||
},
|
||||
{
|
||||
"type": "Unification",
|
||||
"left": 1557,
|
||||
"right": 79,
|
||||
"mode": { "type": "Eq" },
|
||||
"success": true,
|
||||
"subevents": [
|
||||
{
|
||||
"type": "VariableSetDescriptor",
|
||||
"variable": 79,
|
||||
"rank": 1,
|
||||
"content": {
|
||||
"type": "Alias",
|
||||
"name": "`Bool.Bool`",
|
||||
"variables": {
|
||||
"type_variables": [],
|
||||
"lambda_set_variables": [],
|
||||
"infer_ext_in_output_position_variables": []
|
||||
},
|
||||
"real_variable": 1558,
|
||||
"kind": { "type": "Opaque" }
|
||||
}
|
||||
},
|
||||
{ "type": "VariableUnified", "from": 1557, "to": 79 },
|
||||
{ "type": "VariableUnified", "from": 79, "to": 79 }
|
||||
]
|
||||
},
|
||||
{
|
||||
"type": "Unification",
|
||||
"left": 79,
|
||||
"right": 5,
|
||||
"mode": { "type": "Eq" },
|
||||
"success": true,
|
||||
"subevents": [
|
||||
{
|
||||
"type": "Unification",
|
||||
"left": 1558,
|
||||
"right": 4,
|
||||
"mode": { "type": "Eq" },
|
||||
"success": true,
|
||||
"subevents": [
|
||||
{
|
||||
"type": "Unification",
|
||||
"left": 84,
|
||||
"right": 3,
|
||||
"mode": { "type": "Eq" },
|
||||
"success": true,
|
||||
"subevents": [
|
||||
{
|
||||
"type": "VariableSetDescriptor",
|
||||
"variable": 3,
|
||||
"rank": 0,
|
||||
"content": { "type": "EmptyTagUnion" }
|
||||
},
|
||||
{ "type": "VariableUnified", "from": 84, "to": 3 },
|
||||
{ "type": "VariableUnified", "from": 3, "to": 3 }
|
||||
]
|
||||
},
|
||||
{
|
||||
"type": "VariableSetDescriptor",
|
||||
"variable": 4,
|
||||
"rank": 0,
|
||||
"content": {
|
||||
"type": "TagUnion",
|
||||
"tags": { "False": [], "True": [] },
|
||||
"extension": { "type": "Any", "variable": 84 }
|
||||
}
|
||||
},
|
||||
{ "type": "VariableUnified", "from": 1558, "to": 4 },
|
||||
{ "type": "VariableUnified", "from": 4, "to": 4 }
|
||||
]
|
||||
},
|
||||
{
|
||||
"type": "VariableSetDescriptor",
|
||||
"variable": 5,
|
||||
"rank": 0,
|
||||
"content": {
|
||||
"type": "Alias",
|
||||
"name": "`Bool.Bool`",
|
||||
"variables": {
|
||||
"type_variables": [],
|
||||
"lambda_set_variables": [],
|
||||
"infer_ext_in_output_position_variables": []
|
||||
},
|
||||
"real_variable": 1558,
|
||||
"kind": { "type": "Opaque" }
|
||||
}
|
||||
},
|
||||
{ "type": "VariableUnified", "from": 79, "to": 5 },
|
||||
{ "type": "VariableUnified", "from": 5, "to": 5 }
|
||||
]
|
||||
}
|
||||
]
|
|
@ -0,0 +1,28 @@
|
|||
import clsx from "clsx";
|
||||
|
||||
interface EpochCellProps {
|
||||
className?: string;
|
||||
children?: React.ReactNode;
|
||||
focus?: boolean;
|
||||
}
|
||||
|
||||
export const EPOCH_STYLES =
|
||||
"text-slate-900 font-mono bg-slate-200 p-1 py-0 rounded-sm text-sm transition ease-in-out duration-700 mr-2";
|
||||
|
||||
export default function EpochCell({
|
||||
className,
|
||||
children,
|
||||
focus,
|
||||
}: EpochCellProps) {
|
||||
return (
|
||||
<span
|
||||
className={clsx(
|
||||
EPOCH_STYLES,
|
||||
className,
|
||||
focus === true ? "ring-2 ring-blue-500" : "ring-1 ring-slate-500"
|
||||
)}
|
||||
>
|
||||
{children}
|
||||
</span>
|
||||
);
|
||||
}
|
|
@ -0,0 +1,18 @@
|
|||
import clsx from "clsx";
|
||||
import { Variable } from "../../schema";
|
||||
import { VariableName } from "./VariableName";
|
||||
|
||||
export interface UnknownVariableProps {
|
||||
variable: Variable;
|
||||
}
|
||||
|
||||
export function UnknownVariable({
|
||||
variable,
|
||||
}: UnknownVariableProps): JSX.Element {
|
||||
return (
|
||||
<div className={clsx("rounded-md whitespace-nowrap space-x-1 pr-1")}>
|
||||
<VariableName className="inline-block" variable={variable} />
|
||||
<span>???</span>
|
||||
</div>
|
||||
);
|
||||
}
|
|
@ -0,0 +1,73 @@
|
|||
import { ComponentProps } from "react";
|
||||
import clsx from "clsx";
|
||||
import { QuerySubs, TypeDescriptor } from "../../engine/subs";
|
||||
import { Variable } from "../../schema";
|
||||
import DrawHeadConstructor from "../Content/HeadConstructor";
|
||||
import { contentStyles } from "./../Content";
|
||||
import { VariableName } from "./VariableName";
|
||||
|
||||
interface VariableElProps {
|
||||
variable: Variable;
|
||||
subs: QuerySubs;
|
||||
onClick?: (variable: Variable) => void;
|
||||
nested?: boolean;
|
||||
raw?: boolean;
|
||||
}
|
||||
|
||||
export function VariableElPretty(props: VariableElProps): JSX.Element {
|
||||
const { variable, subs } = props;
|
||||
const desc = subs.get_root(variable);
|
||||
const content = (
|
||||
<DrawHeadConstructor
|
||||
desc={desc}
|
||||
drawVariablePretty={(variable) => (
|
||||
<VariableElPretty {...props} variable={variable} nested />
|
||||
)}
|
||||
drawVariableRaw={(variable) => (
|
||||
<VariableElRaw {...props} variable={variable} nested raw />
|
||||
)}
|
||||
/>
|
||||
);
|
||||
return (
|
||||
<Helper {...props} desc={desc}>
|
||||
{content}
|
||||
</Helper>
|
||||
);
|
||||
}
|
||||
|
||||
function VariableElRaw(props: VariableElProps): JSX.Element {
|
||||
const desc = props.subs.get_root(props.variable);
|
||||
return <Helper {...props} desc={desc}></Helper>;
|
||||
}
|
||||
|
||||
function Helper({
|
||||
children,
|
||||
variable,
|
||||
desc,
|
||||
onClick,
|
||||
nested,
|
||||
raw,
|
||||
}: VariableElProps &
|
||||
Pick<ComponentProps<"div">, "children"> & {
|
||||
desc: TypeDescriptor | undefined;
|
||||
}): JSX.Element {
|
||||
const { bg } = contentStyles(desc);
|
||||
return (
|
||||
<span
|
||||
className={clsx(
|
||||
"rounded-md whitespace-nowrap",
|
||||
bg,
|
||||
nested ? "text-sm" : "p-0.5 pl-0 text-base"
|
||||
)}
|
||||
>
|
||||
{(!nested || raw) && (
|
||||
<VariableName
|
||||
variable={variable}
|
||||
onClick={onClick}
|
||||
className={nested ? "text-md" : "p-0.5"}
|
||||
/>
|
||||
)}
|
||||
{children ? <span className="px-1">{children}</span> : <></>}
|
||||
</span>
|
||||
);
|
||||
}
|
|
@ -0,0 +1,34 @@
|
|||
import clsx from "clsx";
|
||||
import { QuerySubs } from "../../engine/subs";
|
||||
import { Variable } from "../../schema";
|
||||
import { VariableName } from "./VariableName";
|
||||
|
||||
export interface VariableLinkProps {
|
||||
variable: Variable;
|
||||
subs: QuerySubs;
|
||||
onClick?: (variable: Variable) => void;
|
||||
}
|
||||
|
||||
export function VariableLink({
|
||||
variable,
|
||||
subs,
|
||||
onClick,
|
||||
}: VariableLinkProps): JSX.Element {
|
||||
const root = subs.get_root_key(variable);
|
||||
|
||||
if (variable === root) {
|
||||
throw new Error("VariableLink: variable is root");
|
||||
}
|
||||
|
||||
return (
|
||||
<div className={clsx("rounded-md whitespace-nowrap space-x-1")}>
|
||||
<VariableName className="inline-block" variable={variable} />
|
||||
<span>→</span>
|
||||
<VariableName
|
||||
className="inline-block"
|
||||
variable={root}
|
||||
onClick={onClick}
|
||||
/>
|
||||
</div>
|
||||
);
|
||||
}
|
|
@ -0,0 +1,30 @@
|
|||
import clsx from "clsx";
|
||||
import { Variable } from "../../schema";
|
||||
|
||||
export interface VariableNameProps {
|
||||
variable: Variable;
|
||||
onClick?: (variable: Variable) => void;
|
||||
className?: string;
|
||||
}
|
||||
|
||||
export function VariableName({
|
||||
variable,
|
||||
onClick,
|
||||
className,
|
||||
}: VariableNameProps): JSX.Element {
|
||||
return (
|
||||
<span
|
||||
className={clsx(
|
||||
"ring-1 ring-inset ring-black-100 px-1 bg-white rounded-md",
|
||||
onClick && "cursor-pointer",
|
||||
className
|
||||
)}
|
||||
onClick={(e) => {
|
||||
e.stopPropagation();
|
||||
onClick?.(variable);
|
||||
}}
|
||||
>
|
||||
{variable}
|
||||
</span>
|
||||
);
|
||||
}
|
|
@ -0,0 +1,344 @@
|
|||
import { TypeDescriptor } from "../../engine/subs";
|
||||
import {
|
||||
ClosureType,
|
||||
RecordFieldKind,
|
||||
UnspecializedClosureType,
|
||||
Variable,
|
||||
} from "../../schema";
|
||||
|
||||
type DrawVariable = (variable: Variable) => JSX.Element;
|
||||
|
||||
export interface DrawHeadConstructorProps {
|
||||
desc: TypeDescriptor | undefined;
|
||||
drawVariablePretty: DrawVariable;
|
||||
drawVariableRaw: DrawVariable;
|
||||
}
|
||||
|
||||
export default function DrawHeadConstructor({
|
||||
desc,
|
||||
drawVariablePretty,
|
||||
drawVariableRaw,
|
||||
}: DrawHeadConstructorProps): JSX.Element {
|
||||
if (!desc) {
|
||||
return <>???</>;
|
||||
}
|
||||
const content = desc.content;
|
||||
switch (content.type) {
|
||||
case "Flex":
|
||||
case "Rigid": {
|
||||
const { name } = content;
|
||||
return name ? <>{name}</> : <>_</>;
|
||||
}
|
||||
case "FlexAble":
|
||||
case "RigidAble": {
|
||||
const { name, abilities } = content;
|
||||
const nameEl = name ? <>{name}</> : <>_</>;
|
||||
return (
|
||||
<>
|
||||
{nameEl} has {abilities.join(", ")}
|
||||
</>
|
||||
);
|
||||
}
|
||||
case "Recursive": {
|
||||
const { name, structure } = content;
|
||||
const structureEl = drawVariableRaw(structure);
|
||||
const nameEl = name ? <>{name} to </> : <></>;
|
||||
return (
|
||||
<>
|
||||
<{nameEl}
|
||||
{structureEl}>
|
||||
</>
|
||||
);
|
||||
}
|
||||
case "LambdaSet": {
|
||||
const { ambient_function, solved, unspecialized, recursion_var } =
|
||||
content;
|
||||
const ambientFunctionEl = drawVariableRaw(ambient_function);
|
||||
const solvedEl = (
|
||||
<DrawSolved drawVariableRaw={drawVariableRaw} solved={solved} />
|
||||
);
|
||||
const unspecializedEl = (
|
||||
<DrawUnspecialized
|
||||
drawVariableRaw={drawVariableRaw}
|
||||
unspecialized={unspecialized}
|
||||
/>
|
||||
);
|
||||
const recursionVarEl = recursion_var ? (
|
||||
<> as <{drawVariableRaw(recursion_var)}></>
|
||||
) : (
|
||||
<></>
|
||||
);
|
||||
return (
|
||||
<>
|
||||
[{solvedEl}
|
||||
{unspecializedEl}]{recursionVarEl} ^{ambientFunctionEl}
|
||||
</>
|
||||
);
|
||||
}
|
||||
case "ErasedLambda": {
|
||||
return <>?</>;
|
||||
}
|
||||
case "Alias": {
|
||||
const { kind, name, variables } = content;
|
||||
const prefix = kind.type === "Opaque" ? "@" : "";
|
||||
const variablesEl = (
|
||||
<DrawVarArgumentsList
|
||||
drawVariableRaw={drawVariableRaw}
|
||||
variables={variables.type_variables}
|
||||
/>
|
||||
);
|
||||
return (
|
||||
<span>
|
||||
{prefix}
|
||||
{sym(name)}
|
||||
{variablesEl}
|
||||
</span>
|
||||
);
|
||||
}
|
||||
case "Apply": {
|
||||
const { symbol, variables } = content;
|
||||
const variablesEl = (
|
||||
<DrawVarArgumentsList
|
||||
drawVariableRaw={drawVariableRaw}
|
||||
variables={variables}
|
||||
/>
|
||||
);
|
||||
return (
|
||||
<>
|
||||
{sym(symbol)}
|
||||
{variablesEl}
|
||||
</>
|
||||
);
|
||||
}
|
||||
case "Function": {
|
||||
const { arguments: args, lambda_type, ret } = content;
|
||||
const argsEl = args.map((arg, i) => (
|
||||
<span key={i}>
|
||||
{i !== 0 ? ", " : ""}
|
||||
{drawVariablePretty(arg)}
|
||||
</span>
|
||||
));
|
||||
const lambdaTypeEl = drawVariablePretty(lambda_type);
|
||||
const retEl = drawVariablePretty(ret);
|
||||
return (
|
||||
<>
|
||||
{argsEl}
|
||||
{" -"}
|
||||
{lambdaTypeEl}
|
||||
{"-> "}
|
||||
{retEl}
|
||||
</>
|
||||
);
|
||||
}
|
||||
case "Record": {
|
||||
const { fields, extension } = content;
|
||||
const fieldsEl = Object.entries(fields).map(([key, value], i) => {
|
||||
const { field_type, kind } = value;
|
||||
return (
|
||||
<span key={i}>
|
||||
{i !== 0 ? ", " : ""}
|
||||
{key} {<DrawFieldKind kind={kind} />}{" "}
|
||||
{drawVariablePretty(field_type)}
|
||||
</span>
|
||||
);
|
||||
});
|
||||
return (
|
||||
<>
|
||||
{"{"}
|
||||
{fieldsEl}
|
||||
{"}"}
|
||||
{drawVariablePretty(extension)}
|
||||
</>
|
||||
);
|
||||
}
|
||||
case "Tuple": {
|
||||
const { elements, extension } = content;
|
||||
const elemsEl = Object.entries(elements).map(([key, value], i) => {
|
||||
return (
|
||||
<span key={i}>
|
||||
{i !== 0 ? ", " : ""}
|
||||
{key}: {drawVariablePretty(value)}
|
||||
</span>
|
||||
);
|
||||
});
|
||||
return (
|
||||
<>
|
||||
({elemsEl}){drawVariablePretty(extension)}
|
||||
</>
|
||||
);
|
||||
}
|
||||
case "TagUnion": {
|
||||
const { tags, extension } = content;
|
||||
return (
|
||||
<>
|
||||
<DrawTags tags={tags} drawVariableRaw={drawVariableRaw} />
|
||||
{drawVariablePretty(extension.variable)}
|
||||
</>
|
||||
);
|
||||
}
|
||||
case "RecursiveTagUnion": {
|
||||
const { tags, extension, recursion_var } = content;
|
||||
return (
|
||||
<>
|
||||
(<DrawTags tags={tags} drawVariableRaw={drawVariableRaw} />
|
||||
{drawVariablePretty(extension.variable)} as <
|
||||
{drawVariableRaw(recursion_var)}>)
|
||||
</>
|
||||
);
|
||||
}
|
||||
case "FunctionOrTagUnion": {
|
||||
const { functions, tags, extension } = content;
|
||||
const functionsEl = functions.map((f, i) => (
|
||||
<span key={i}>
|
||||
{i !== 0 ? ", " : ""}
|
||||
{sym(f)}
|
||||
</span>
|
||||
));
|
||||
const tagsEl = tags.map((t, i) => (
|
||||
<span key={i}>
|
||||
{i !== 0 ? ", " : ""}
|
||||
{t}
|
||||
</span>
|
||||
));
|
||||
return (
|
||||
<>
|
||||
[{functionsEl} | {tagsEl}]{drawVariablePretty(extension.variable)}
|
||||
</>
|
||||
);
|
||||
}
|
||||
case "RangedNumber": {
|
||||
const {
|
||||
range: { kind, min_width, signed },
|
||||
} = content;
|
||||
switch (kind.type) {
|
||||
case "AnyNum":
|
||||
return <>ℚ{min_width}+</>;
|
||||
case "Int":
|
||||
return signed ? <>ℤ{min_width}+</> : <>ℕ{min_width}+</>;
|
||||
}
|
||||
break;
|
||||
}
|
||||
case "EmptyRecord": {
|
||||
return <>{"{}"}</>;
|
||||
}
|
||||
case "EmptyTuple": {
|
||||
return <>()</>;
|
||||
}
|
||||
case "EmptyTagUnion": {
|
||||
return <>[]</>;
|
||||
}
|
||||
case "Error": {
|
||||
return <>⊥</>;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
function DrawVarArgumentsList({
|
||||
variables,
|
||||
drawVariableRaw,
|
||||
}: {
|
||||
variables: Variable[];
|
||||
drawVariableRaw: DrawVariable;
|
||||
}): JSX.Element {
|
||||
return variables.length !== 0 ? (
|
||||
<>
|
||||
{" "}
|
||||
{variables.map((v, i) => (
|
||||
<span key={i}>{drawVariableRaw(v)}</span>
|
||||
))}
|
||||
</>
|
||||
) : (
|
||||
<></>
|
||||
);
|
||||
}
|
||||
|
||||
function DrawSolved({
|
||||
solved,
|
||||
drawVariableRaw,
|
||||
}: {
|
||||
solved: ClosureType[];
|
||||
drawVariableRaw: DrawVariable;
|
||||
}): JSX.Element {
|
||||
const tags = solved.map(({ environment, function: fn }, i) => (
|
||||
<span key={i}>
|
||||
{i !== 0 ? ", " : ""}
|
||||
<DrawTag
|
||||
tag={sym(fn)}
|
||||
variables={environment}
|
||||
drawVariableRaw={drawVariableRaw}
|
||||
/>
|
||||
</span>
|
||||
));
|
||||
return <>[{tags}]</>;
|
||||
}
|
||||
|
||||
function DrawTags({
|
||||
tags,
|
||||
drawVariableRaw,
|
||||
}: {
|
||||
tags: Record<string, Variable[]>;
|
||||
drawVariableRaw: DrawVariable;
|
||||
}): JSX.Element {
|
||||
const tagsEl = Object.entries(tags).map(([tag, vars], i) => (
|
||||
<span key={i}>
|
||||
{i !== 0 ? ", " : ""}
|
||||
<DrawTag tag={tag} variables={vars} drawVariableRaw={drawVariableRaw} />
|
||||
</span>
|
||||
));
|
||||
return <>[{tagsEl}]</>;
|
||||
}
|
||||
|
||||
function DrawUnspecialized({
|
||||
unspecialized,
|
||||
drawVariableRaw,
|
||||
}: {
|
||||
unspecialized: UnspecializedClosureType[];
|
||||
drawVariableRaw: DrawVariable;
|
||||
}): JSX.Element {
|
||||
const unspecs = unspecialized.map(
|
||||
({ ability_member, lambda_set_region, specialization }, i) => (
|
||||
<span key={i}>
|
||||
{" + "}
|
||||
{drawVariableRaw(specialization)}:{sym(ability_member)}:
|
||||
{lambda_set_region}
|
||||
</span>
|
||||
)
|
||||
);
|
||||
return <>{unspecs}</>;
|
||||
}
|
||||
|
||||
function DrawTag({
|
||||
tag,
|
||||
variables,
|
||||
drawVariableRaw,
|
||||
}: {
|
||||
tag: string;
|
||||
variables: Variable[];
|
||||
drawVariableRaw: DrawVariable;
|
||||
}): JSX.Element {
|
||||
return (
|
||||
<>
|
||||
{tag}
|
||||
<DrawVarArgumentsList
|
||||
drawVariableRaw={drawVariableRaw}
|
||||
variables={variables}
|
||||
/>
|
||||
</>
|
||||
);
|
||||
}
|
||||
|
||||
function DrawFieldKind({ kind }: { kind: RecordFieldKind }): JSX.Element {
|
||||
switch (kind.type) {
|
||||
case "Required":
|
||||
case "Demanded":
|
||||
return <>:</>;
|
||||
case "Optional":
|
||||
return <>?</>;
|
||||
}
|
||||
}
|
||||
|
||||
function sym(symbol: string): string {
|
||||
if (symbol.startsWith("`")) symbol = symbol.slice(1);
|
||||
if (symbol.endsWith("`")) symbol = symbol.slice(0, -1);
|
||||
return symbol.split(".").at(-1)!;
|
||||
}
|
|
@ -0,0 +1,68 @@
|
|||
import { TypeDescriptor } from "../../engine/subs";
|
||||
import { assertExhaustive } from "../../utils/exhaustive";
|
||||
|
||||
export interface ContentStyles {
|
||||
name: string;
|
||||
bg: string;
|
||||
}
|
||||
|
||||
export function contentStyles(desc: TypeDescriptor | undefined): ContentStyles {
|
||||
if (!desc) {
|
||||
return { name: "???", bg: "bg-red-500" };
|
||||
}
|
||||
|
||||
const content = desc.content;
|
||||
switch (content.type) {
|
||||
case "Flex":
|
||||
return { name: "Flex", bg: "bg-blue-300" };
|
||||
case "FlexAble":
|
||||
return { name: "FlexAble", bg: "bg-blue-400" };
|
||||
case "Rigid":
|
||||
return { name: "Rigid", bg: "bg-indigo-300" };
|
||||
case "RigidAble":
|
||||
return { name: "RigidAble", bg: "bg-indigo-400" };
|
||||
case "Recursive":
|
||||
return { name: "Rec", bg: "bg-blue-grey-500" };
|
||||
case "LambdaSet":
|
||||
return { name: "LambdaSet", bg: "bg-green-500" };
|
||||
case "ErasedLambda":
|
||||
return { name: "ErasedLambda", bg: "bg-green-700" };
|
||||
case "Alias": {
|
||||
switch (content.kind.type) {
|
||||
case "Structural":
|
||||
return { name: "Alias", bg: "bg-yellow-300" };
|
||||
case "Opaque":
|
||||
return { name: "Opaque", bg: "bg-amber-400" };
|
||||
default:
|
||||
assertExhaustive(content.kind);
|
||||
}
|
||||
break;
|
||||
}
|
||||
case "Apply":
|
||||
return { name: "Apply", bg: "bg-orange-500" };
|
||||
case "Function":
|
||||
return { name: "Func", bg: "bg-teal-400" };
|
||||
case "Record":
|
||||
return { name: "Record", bg: "bg-purple-400" };
|
||||
case "Tuple":
|
||||
return { name: "Tuple", bg: "bg-deep-purple-400" };
|
||||
case "TagUnion":
|
||||
return { name: "Tags", bg: "bg-cyan-200" };
|
||||
case "FunctionOrTagUnion":
|
||||
return { name: "Func|Tags", bg: "bg-cyan-300" };
|
||||
case "RecursiveTagUnion":
|
||||
return { name: "RecTags", bg: "bg-cyan-400" };
|
||||
case "RangedNumber":
|
||||
return { name: "ℕ", bg: "bg-lime-400" };
|
||||
case "EmptyRecord":
|
||||
return { name: "{}", bg: "bg-purple-400" };
|
||||
case "EmptyTuple":
|
||||
return { name: "()", bg: "bg-deep-purple-400" };
|
||||
case "EmptyTagUnion":
|
||||
return { name: "[]", bg: "bg-cyan-200" };
|
||||
case "Error":
|
||||
return { name: "Error", bg: "bg-red-400" };
|
||||
}
|
||||
}
|
||||
|
||||
export const LinkStyles: ContentStyles = { name: "Link", bg: "bg-slate-500" };
|
|
@ -0,0 +1,27 @@
|
|||
import { EventEpoch } from "../../engine/engine";
|
||||
import { Variable } from "../../schema";
|
||||
import { VariableElPretty } from "../Common/Variable";
|
||||
import { CommonProps } from "./types";
|
||||
|
||||
interface VariableProps extends CommonProps {
|
||||
epoch: EventEpoch;
|
||||
variable: Variable;
|
||||
}
|
||||
|
||||
export function VariableEl({
|
||||
engine,
|
||||
epoch,
|
||||
variable,
|
||||
graphEe,
|
||||
}: VariableProps): JSX.Element {
|
||||
engine.stepTo(epoch);
|
||||
return (
|
||||
<VariableElPretty
|
||||
variable={variable}
|
||||
subs={engine.subs}
|
||||
onClick={(variable: Variable) => {
|
||||
graphEe.emit("focusVariable", variable);
|
||||
}}
|
||||
></VariableElPretty>
|
||||
);
|
||||
}
|
|
@ -0,0 +1,9 @@
|
|||
import { TypedEmitter } from "tiny-typed-emitter";
|
||||
import type { Engine, EventEpoch } from "../../engine/engine";
|
||||
import { GraphMessage } from "../../utils/events";
|
||||
|
||||
export interface CommonProps {
|
||||
selectedEpochs: EventEpoch[];
|
||||
engine: Engine;
|
||||
graphEe: TypedEmitter<GraphMessage>;
|
||||
}
|
|
@ -0,0 +1,76 @@
|
|||
export interface GroupInfo {
|
||||
group: string;
|
||||
groupHover: string;
|
||||
}
|
||||
|
||||
export function depthToGroupInfo(depth: number): GroupInfo {
|
||||
switch (depth) {
|
||||
case 0:
|
||||
return {
|
||||
group: `group/event-0`,
|
||||
groupHover: `group-hover/event-0:opacity-100`,
|
||||
};
|
||||
case 1:
|
||||
return {
|
||||
group: `group/event-1`,
|
||||
groupHover: `group-hover/event-1:opacity-100`,
|
||||
};
|
||||
case 2:
|
||||
return {
|
||||
group: `group/event-2`,
|
||||
groupHover: `group-hover/event-2:opacity-100`,
|
||||
};
|
||||
case 3:
|
||||
return {
|
||||
group: `group/event-3`,
|
||||
groupHover: `group-hover/event-3:opacity-100`,
|
||||
};
|
||||
case 4:
|
||||
return {
|
||||
group: `group/event-4`,
|
||||
groupHover: `group-hover/event-4:opacity-100`,
|
||||
};
|
||||
case 5:
|
||||
return {
|
||||
group: `group/event-5`,
|
||||
groupHover: `group-hover/event-5:opacity-100`,
|
||||
};
|
||||
case 6:
|
||||
return {
|
||||
group: `group/event-6`,
|
||||
groupHover: `group-hover/event-6:opacity-100`,
|
||||
};
|
||||
case 7:
|
||||
return {
|
||||
group: `group/event-7`,
|
||||
groupHover: `group-hover/event-7:opacity-100`,
|
||||
};
|
||||
case 8:
|
||||
return {
|
||||
group: `group/event-8`,
|
||||
groupHover: `group-hover/event-8:opacity-100`,
|
||||
};
|
||||
case 9:
|
||||
return {
|
||||
group: `group/event-9`,
|
||||
groupHover: `group-hover/event-9:opacity-100`,
|
||||
};
|
||||
case 10:
|
||||
return {
|
||||
group: `group/event-10`,
|
||||
groupHover: `group-hover/event-10:opacity-100`,
|
||||
};
|
||||
case 11:
|
||||
return {
|
||||
group: `group/event-11`,
|
||||
groupHover: `group-hover/event-11:opacity-100`,
|
||||
};
|
||||
case 12:
|
||||
return {
|
||||
group: `group/event-12`,
|
||||
groupHover: `group-hover/event-12:opacity-100`,
|
||||
};
|
||||
default:
|
||||
throw new Error(`Too deep: ${depth}`);
|
||||
}
|
||||
}
|
423
crates/compiler/checkmate/www/src/components/EventList/index.tsx
Normal file
423
crates/compiler/checkmate/www/src/components/EventList/index.tsx
Normal file
|
@ -0,0 +1,423 @@
|
|||
import clsx from "clsx";
|
||||
import React, { useCallback, useMemo, useState } from "react";
|
||||
import { TypedEmitter } from "tiny-typed-emitter";
|
||||
import { EventEpoch } from "../../engine/engine";
|
||||
import { lastSubEvent } from "../../engine/event_util";
|
||||
import { useFocusOutlineEvent } from "../../hooks/useFocusOutlineEvent";
|
||||
import { UnificationMode, Event } from "../../schema";
|
||||
import {
|
||||
EventListMessage,
|
||||
GlobalMessage,
|
||||
GraphMessage,
|
||||
LoadEpochView,
|
||||
} from "../../utils/events";
|
||||
import { Refine } from "../../utils/refine";
|
||||
import EpochCell from "../Common/EpochCell";
|
||||
import { CommonProps } from "../EventItem/types";
|
||||
import { VariableEl } from "../EventItem/Variable";
|
||||
import { depthToGroupInfo } from "./depthGroup";
|
||||
|
||||
interface EventListProps extends CommonProps {
|
||||
events: Event[];
|
||||
eventListEe: TypedEmitter<EventListMessage>;
|
||||
globalEe: TypedEmitter<GlobalMessage>;
|
||||
depth: number;
|
||||
}
|
||||
|
||||
const MT = "my-2.5";
|
||||
const LOWER_OPACITY = "opacity-40";
|
||||
|
||||
export default function EventList(props: EventListProps): JSX.Element {
|
||||
const { events, depth } = props;
|
||||
return (
|
||||
<ul className={clsx(MT, "space-y-2.5", depth === 0 ? "" : "ml-[1em]")}>
|
||||
{events.map((event, i) => (
|
||||
<li key={i}>
|
||||
<OneEvent {...props} event={event} />
|
||||
</li>
|
||||
))}
|
||||
</ul>
|
||||
);
|
||||
}
|
||||
|
||||
interface OneEventProps extends CommonProps {
|
||||
event: Event;
|
||||
eventListEe: TypedEmitter<EventListMessage>;
|
||||
globalEe: TypedEmitter<GlobalMessage>;
|
||||
depth: number;
|
||||
}
|
||||
|
||||
function OneEvent(props: OneEventProps): JSX.Element {
|
||||
const { event } = props;
|
||||
switch (event.type) {
|
||||
case "Unification":
|
||||
return <UnificationEvent {...props} event={event} />;
|
||||
case "VariableUnified":
|
||||
return <></>;
|
||||
case "VariableSetDescriptor":
|
||||
return <></>;
|
||||
}
|
||||
}
|
||||
|
||||
const DROPDOWN_CLOSED = "▶";
|
||||
const DROPDOWN_OPEN = "▼";
|
||||
|
||||
const UN_UNKNOWN = "💭";
|
||||
const UN_SUCCESS = "✅";
|
||||
const UN_FAILURE = "❌";
|
||||
|
||||
function epochInRange(
|
||||
epoch: EventEpoch,
|
||||
[start, end]: [EventEpoch, EventEpoch]
|
||||
): boolean {
|
||||
return epoch >= start && epoch <= end;
|
||||
}
|
||||
|
||||
interface UnificationProps extends CommonProps {
|
||||
event: Refine<Event, "Unification">;
|
||||
eventListEe: TypedEmitter<EventListMessage>;
|
||||
globalEe: TypedEmitter<GlobalMessage>;
|
||||
depth: number;
|
||||
}
|
||||
|
||||
const COL_1_P = "pl-1.5";
|
||||
|
||||
const COL_1_OUTLINE_STYLES = clsx(COL_1_P, "outline-event-col-1");
|
||||
const COL_3_OUTLINE_STYLES = clsx("outline-event-col-3");
|
||||
|
||||
const COL_1_ROUNDED = "rounded-l-md";
|
||||
const COL_3_ROUNDED = "rounded-r-md";
|
||||
|
||||
const UN_EXPANDED_OUTLINE_STYLES = clsx("ring-inset ring-2 ring-blue-500");
|
||||
|
||||
const TRANSITION_SHADOW = "transition-shadow ease-in-out duration-500";
|
||||
const TRANSITION_OPACITY = "transition-opacity ease-in-out duration-150";
|
||||
|
||||
const GROUP_STLYES = "relative overflow-hidden";
|
||||
|
||||
// Space for the hover cells at the end of line.
|
||||
const EOL_SPACE = "pr-12";
|
||||
|
||||
function UnificationEvent(props: UnificationProps): JSX.Element {
|
||||
const {
|
||||
engine,
|
||||
event,
|
||||
selectedEpochs,
|
||||
graphEe,
|
||||
eventListEe,
|
||||
depth,
|
||||
globalEe,
|
||||
} = props;
|
||||
const { mode, subevents, success } = event;
|
||||
|
||||
const beforeUnificationEpoch = engine.getEventIndex(event);
|
||||
const afterUnificationEpoch = engine.getEventIndex(lastSubEvent(event));
|
||||
|
||||
const containedEpoch = selectedEpochs.find((epoch) =>
|
||||
epochInRange(epoch, [beforeUnificationEpoch, afterUnificationEpoch])
|
||||
);
|
||||
|
||||
const leftVar = useMemo(
|
||||
() => (epoch: EventEpoch) =>
|
||||
<VariableEl {...props} epoch={epoch} variable={event.left} />,
|
||||
[event.left, props]
|
||||
);
|
||||
const rightVar = useMemo(
|
||||
() => (epoch: EventEpoch) =>
|
||||
<VariableEl {...props} epoch={epoch} variable={event.right} />,
|
||||
[event.right, props]
|
||||
);
|
||||
|
||||
const [isOpen, setIsOpen] = useState(false);
|
||||
const isOutlined = useFocusOutlineEvent<"focusEpoch", EventEpoch | undefined>(
|
||||
{
|
||||
ee: eventListEe,
|
||||
value: containedEpoch,
|
||||
event: "focusEpoch",
|
||||
}
|
||||
);
|
||||
|
||||
const modeIcon = useMemo(() => <UnificationModeIcon mode={mode} />, [mode]);
|
||||
|
||||
const resultIcon = success ? UN_SUCCESS : UN_FAILURE;
|
||||
const resultHeadline = <Headline icon={resultIcon}></Headline>;
|
||||
|
||||
const epochCell = useMemo(() => {
|
||||
if (containedEpoch === undefined) return null;
|
||||
return <EventListEpochCell epoch={containedEpoch} graphEe={graphEe} />;
|
||||
}, [containedEpoch, graphEe]);
|
||||
|
||||
const getBeforeUnificationHeadline = useCallback(
|
||||
({
|
||||
epoch,
|
||||
collapsedMode,
|
||||
}: {
|
||||
epoch: EventEpoch;
|
||||
collapsedMode?: boolean;
|
||||
}) => {
|
||||
const topHeadline = (
|
||||
<Headline icon={isOpen ? UN_UNKNOWN : resultIcon}></Headline>
|
||||
);
|
||||
|
||||
const optEpochCell =
|
||||
collapsedMode && containedEpoch !== undefined && epochCell;
|
||||
|
||||
return (
|
||||
<button
|
||||
onClick={() => setIsOpen(!isOpen)}
|
||||
className={clsx(
|
||||
"w-full text-left whitespace-nowrap h-full overflow-scroll",
|
||||
collapsedMode && COL_1_P
|
||||
)}
|
||||
>
|
||||
<span className={EOL_SPACE}>
|
||||
{optEpochCell}
|
||||
<span
|
||||
className={clsx(
|
||||
"mr-2",
|
||||
isOpen ? "text-slate-500" : "text-slate-400"
|
||||
)}
|
||||
>
|
||||
{isOpen ? DROPDOWN_OPEN : DROPDOWN_CLOSED}
|
||||
</span>
|
||||
{topHeadline} {leftVar(epoch)} {modeIcon} {rightVar(epoch)}
|
||||
</span>
|
||||
</button>
|
||||
);
|
||||
},
|
||||
[isOpen, resultIcon, containedEpoch, epochCell, leftVar, modeIcon, rightVar]
|
||||
);
|
||||
|
||||
const { group, groupHover } = depthToGroupInfo(depth);
|
||||
|
||||
if (!isOpen) {
|
||||
const headLine = getBeforeUnificationHeadline({
|
||||
epoch: afterUnificationEpoch,
|
||||
collapsedMode: true,
|
||||
});
|
||||
return (
|
||||
<div
|
||||
className={clsx(
|
||||
"rounded-md",
|
||||
TRANSITION_SHADOW,
|
||||
group,
|
||||
GROUP_STLYES,
|
||||
containedEpoch !== undefined &&
|
||||
isOutlined &&
|
||||
UN_EXPANDED_OUTLINE_STYLES
|
||||
)}
|
||||
>
|
||||
<div
|
||||
className={clsx(
|
||||
TRANSITION_OPACITY,
|
||||
containedEpoch === undefined && LOWER_OPACITY
|
||||
)}
|
||||
>
|
||||
{headLine}
|
||||
</div>
|
||||
<LoadEpochGraphLauncher
|
||||
groupHover={groupHover}
|
||||
epoch={afterUnificationEpoch}
|
||||
globalEe={globalEe}
|
||||
className="bottom-0 right-2"
|
||||
/>
|
||||
</div>
|
||||
);
|
||||
} else {
|
||||
const beforeIsCurrentEpoch = beforeUnificationEpoch === containedEpoch;
|
||||
const afterIsCurrentEpoch = afterUnificationEpoch === containedEpoch;
|
||||
|
||||
const epochCellBefore = beforeIsCurrentEpoch && epochCell;
|
||||
const epochCellAfter = afterIsCurrentEpoch && epochCell;
|
||||
|
||||
const outlineEpochCellAfter = afterIsCurrentEpoch && isOutlined;
|
||||
const outlineEpochCellBefore = beforeIsCurrentEpoch && isOutlined;
|
||||
|
||||
const headlineBefore = getBeforeUnificationHeadline({
|
||||
epoch: beforeUnificationEpoch,
|
||||
});
|
||||
|
||||
const dropdownTransparent = (
|
||||
<span className="text-transparent mr-2">{DROPDOWN_OPEN}</span>
|
||||
);
|
||||
|
||||
const headlineAfter = (
|
||||
<div className={clsx("whitespace-nowrap")}>
|
||||
{dropdownTransparent}
|
||||
{resultHeadline} {leftVar(afterUnificationEpoch)} {modeIcon}{" "}
|
||||
{rightVar(afterUnificationEpoch)}
|
||||
</div>
|
||||
);
|
||||
|
||||
return (
|
||||
<div className={clsx(group, GROUP_STLYES)}>
|
||||
<div
|
||||
className={clsx(
|
||||
"grid gap-0 grid-cols-[min-content_min-content_1fr_auto] opacity-100",
|
||||
"overflow-scroll",
|
||||
TRANSITION_OPACITY
|
||||
)}
|
||||
>
|
||||
{/* Row 1: unification start */}
|
||||
<div
|
||||
className={clsx(
|
||||
"row-start-1 col-start-1",
|
||||
TRANSITION_SHADOW,
|
||||
COL_1_ROUNDED,
|
||||
outlineEpochCellBefore ? COL_1_OUTLINE_STYLES : COL_1_P
|
||||
)}
|
||||
>
|
||||
{epochCellBefore}
|
||||
</div>
|
||||
<div
|
||||
className={clsx(
|
||||
"row-start-1 col-start-3 col-end-5",
|
||||
TRANSITION_SHADOW,
|
||||
COL_3_ROUNDED,
|
||||
outlineEpochCellBefore && COL_3_OUTLINE_STYLES,
|
||||
EOL_SPACE
|
||||
)}
|
||||
>
|
||||
{headlineBefore}
|
||||
</div>
|
||||
|
||||
{/* Row 2: inner traces */}
|
||||
<div className={clsx("row-start-2 col-start-1")}></div>
|
||||
<div className={clsx("row-start-2col-start-3 col-end-4", "w-full")}>
|
||||
<EventList
|
||||
{...props}
|
||||
depth={depth + 1}
|
||||
engine={engine}
|
||||
events={subevents}
|
||||
/>
|
||||
</div>
|
||||
|
||||
{/* Row 3: unification end */}
|
||||
<div
|
||||
className={clsx(
|
||||
"row-start-3 col-start-1",
|
||||
TRANSITION_SHADOW,
|
||||
COL_1_ROUNDED,
|
||||
outlineEpochCellAfter ? COL_1_OUTLINE_STYLES : COL_1_P
|
||||
)}
|
||||
>
|
||||
{epochCellAfter}
|
||||
</div>
|
||||
<div
|
||||
className={clsx(
|
||||
"row-start-3 col-start-3 col-end-5",
|
||||
TRANSITION_SHADOW,
|
||||
COL_3_ROUNDED,
|
||||
outlineEpochCellAfter && COL_3_OUTLINE_STYLES,
|
||||
EOL_SPACE
|
||||
)}
|
||||
>
|
||||
{headlineAfter}
|
||||
</div>
|
||||
|
||||
{/* Col 2: dropdown line */}
|
||||
<div
|
||||
className={clsx(
|
||||
"row-start-1 row-end-4 col-start-2 h-full",
|
||||
"relative z-[1] h-full",
|
||||
"before:content-[''] before:border-l before:border-slate-500 before:z-[-1]",
|
||||
"before:absolute before:w-0 before:h-[calc(100%-1.5rem)] before:top-[1rem] before:left-[0.3rem]"
|
||||
)}
|
||||
></div>
|
||||
</div>
|
||||
|
||||
<LoadEpochGraphLauncher
|
||||
groupHover={groupHover}
|
||||
epoch={beforeUnificationEpoch}
|
||||
globalEe={globalEe}
|
||||
className="top-0 right-2"
|
||||
/>
|
||||
<LoadEpochGraphLauncher
|
||||
groupHover={groupHover}
|
||||
epoch={afterUnificationEpoch}
|
||||
globalEe={globalEe}
|
||||
className="bottom-0 right-2"
|
||||
/>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
function Headline({ icon }: { icon: string }): JSX.Element {
|
||||
return (
|
||||
<div className="inline-block align-middle">
|
||||
<div className="">{icon}</div>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
function UnificationModeIcon({ mode }: { mode: UnificationMode }): JSX.Element {
|
||||
switch (mode.type) {
|
||||
case "Eq":
|
||||
return <>~</>;
|
||||
case "Present":
|
||||
return <>+=</>;
|
||||
case "LambdaSetSpecialization":
|
||||
return <>|~|</>;
|
||||
}
|
||||
}
|
||||
|
||||
interface EventListEpochCellProps {
|
||||
epoch: EventEpoch;
|
||||
graphEe: TypedEmitter<GraphMessage>;
|
||||
}
|
||||
|
||||
function EventListEpochCell({
|
||||
epoch,
|
||||
graphEe,
|
||||
}: EventListEpochCellProps): JSX.Element {
|
||||
return (
|
||||
<span
|
||||
id={`events-${epoch}`}
|
||||
className={clsx("cursor-pointer rounded-md")}
|
||||
onClick={(e) => {
|
||||
e.stopPropagation();
|
||||
graphEe.emit("focusEpoch", epoch);
|
||||
}}
|
||||
>
|
||||
<EpochCell>{epoch}</EpochCell>
|
||||
</span>
|
||||
);
|
||||
}
|
||||
|
||||
interface LoadEpochGraphLauncherProps {
|
||||
groupHover: string;
|
||||
epoch: EventEpoch;
|
||||
globalEe: TypedEmitter<GlobalMessage>;
|
||||
className?: string;
|
||||
}
|
||||
|
||||
function LoadEpochGraphLauncher({
|
||||
groupHover,
|
||||
epoch,
|
||||
className,
|
||||
globalEe,
|
||||
}: LoadEpochGraphLauncherProps): JSX.Element {
|
||||
return (
|
||||
<div className={clsx("absolute opacity-0", groupHover, className)}>
|
||||
<span className="space-x-0.5 bg-gray-200 ring-1 ring-slate-300 rounded-sm px-1 opacity-80 hover:opacity-100">
|
||||
<span
|
||||
className="text-blue-400 hover:text-blue-500 cursor-pointer"
|
||||
onClick={() => {
|
||||
globalEe.emit("loadEpoch", epoch, LoadEpochView.Top);
|
||||
}}
|
||||
>
|
||||
↑
|
||||
</span>
|
||||
<span
|
||||
className="text-blue-400 hover:text-blue-500 cursor-pointer"
|
||||
onClick={() => {
|
||||
globalEe.emit("loadEpoch", epoch, LoadEpochView.Bot);
|
||||
}}
|
||||
>
|
||||
↓
|
||||
</span>
|
||||
</span>
|
||||
</div>
|
||||
);
|
||||
}
|
48
crates/compiler/checkmate/www/src/components/FileInput.tsx
Normal file
48
crates/compiler/checkmate/www/src/components/FileInput.tsx
Normal file
|
@ -0,0 +1,48 @@
|
|||
import { AllEvents } from "../schema";
|
||||
|
||||
export type EventsOk = {
|
||||
kind: "ok";
|
||||
events: AllEvents;
|
||||
};
|
||||
|
||||
export type EventsErr = {
|
||||
kind: "err";
|
||||
error: string;
|
||||
};
|
||||
|
||||
export type LoadedEvents = EventsOk | EventsErr;
|
||||
|
||||
interface FileInputProps {
|
||||
setResult(result: LoadedEvents): void;
|
||||
}
|
||||
|
||||
export default function FileInput({ setResult }: FileInputProps) {
|
||||
async function setFile(e: React.ChangeEvent<HTMLInputElement>) {
|
||||
e.preventDefault();
|
||||
const files = e.target.files;
|
||||
if (!files) {
|
||||
setResult({ kind: "err", error: "Please choose a checkmate file." });
|
||||
return;
|
||||
}
|
||||
const file = files[0];
|
||||
const buf = await file.text();
|
||||
try {
|
||||
const events: AllEvents = JSON.parse(buf);
|
||||
setResult({ kind: "ok", events });
|
||||
} catch (e) {
|
||||
setResult({ kind: "err", error: "Invalid checkmate file." });
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
return (
|
||||
<input
|
||||
type="file"
|
||||
name="small-file-input"
|
||||
id="small-file-input"
|
||||
onChange={(e) => setFile(e)}
|
||||
className="block w-full border border-gray-200 shadow-sm rounded-md text-sm
|
||||
file:bg-roc-purple-bg file:border-0 file:mr-4 file:py-2 file:px-4 cursor-pointer"
|
||||
></input>
|
||||
);
|
||||
}
|
|
@ -0,0 +1,286 @@
|
|||
import clsx from "clsx";
|
||||
import { Handle, Position } from "reactflow";
|
||||
import { Variable } from "../../schema";
|
||||
import { assertExhaustive } from "../../utils/exhaustive";
|
||||
import { contentStyles, LinkStyles } from "../Content";
|
||||
import { VariableElPretty } from "../Common/Variable";
|
||||
import { SubsSnapshot, TypeDescriptor } from "../../engine/subs";
|
||||
import { TypedEmitter } from "tiny-typed-emitter";
|
||||
import { VariableLink } from "../Common/VariableLink";
|
||||
import { VariableMessage } from "../../utils/events";
|
||||
import { useFocusOutlineEvent } from "../../hooks/useFocusOutlineEvent";
|
||||
import { UnknownVariable } from "../Common/UnknownVariable";
|
||||
|
||||
type AddSubVariableLink = ({
|
||||
from,
|
||||
variable,
|
||||
}: {
|
||||
from: Variable;
|
||||
variable: Variable;
|
||||
}) => void;
|
||||
|
||||
export interface VariableNodeProps {
|
||||
data: {
|
||||
subs: SubsSnapshot;
|
||||
rawVariable: Variable;
|
||||
addSubVariableLink: AddSubVariableLink;
|
||||
isOutlined: boolean;
|
||||
ee: TypedEmitter<VariableMessage>;
|
||||
};
|
||||
targetPosition?: Position;
|
||||
sourcePosition?: Position;
|
||||
}
|
||||
|
||||
export default function VariableNode({
|
||||
data,
|
||||
targetPosition,
|
||||
sourcePosition,
|
||||
}: VariableNodeProps): JSX.Element {
|
||||
const {
|
||||
subs,
|
||||
rawVariable,
|
||||
addSubVariableLink,
|
||||
isOutlined: isOutlinedProp,
|
||||
ee: eeProp,
|
||||
} = data;
|
||||
|
||||
const isOutlined = useFocusOutlineEvent({
|
||||
ee: eeProp,
|
||||
value: rawVariable,
|
||||
event: "focus",
|
||||
defaultIsOutlined: isOutlinedProp,
|
||||
});
|
||||
|
||||
const varType = subs.get(rawVariable);
|
||||
|
||||
let renderContent: JSX.Element;
|
||||
let bgStyles: string;
|
||||
const isContent = varType?.type === "descriptor";
|
||||
switch (varType?.type) {
|
||||
case undefined: {
|
||||
bgStyles = "bg-red-500";
|
||||
renderContent = <UnknownVariable variable={rawVariable} />;
|
||||
|
||||
break;
|
||||
}
|
||||
case "link": {
|
||||
bgStyles = LinkStyles.bg;
|
||||
|
||||
renderContent = (
|
||||
<VariableLink
|
||||
subs={subs}
|
||||
variable={rawVariable}
|
||||
onClick={() =>
|
||||
addSubVariableLink({
|
||||
from: rawVariable,
|
||||
variable: subs.get_root_key(rawVariable),
|
||||
})
|
||||
}
|
||||
/>
|
||||
);
|
||||
|
||||
break;
|
||||
}
|
||||
case "descriptor": {
|
||||
const variable = rawVariable;
|
||||
const desc: TypeDescriptor = varType;
|
||||
|
||||
const styles = contentStyles(desc);
|
||||
bgStyles = styles.bg;
|
||||
const basis: BasisProps = {
|
||||
subs,
|
||||
origin: variable,
|
||||
addSubVariableLink,
|
||||
};
|
||||
|
||||
const content = Object.entries(
|
||||
VariableNodeContent(variable, desc, basis)
|
||||
).filter((el): el is [string, JSX.Element] => !!el[1]);
|
||||
|
||||
let expandedContent = <></>;
|
||||
if (content.length > 0) {
|
||||
expandedContent = (
|
||||
<ul className="text-sm text-left mt-2 space-y-1">
|
||||
{content.map(([key, value], i) => (
|
||||
<li key={i} className="space-x-2">
|
||||
{key}: {value}
|
||||
</li>
|
||||
))}
|
||||
</ul>
|
||||
);
|
||||
}
|
||||
|
||||
renderContent = (
|
||||
<>
|
||||
<div>
|
||||
<VariableElPretty variable={variable} subs={subs} />
|
||||
</div>
|
||||
{expandedContent}
|
||||
</>
|
||||
);
|
||||
|
||||
break;
|
||||
}
|
||||
default: {
|
||||
assertExhaustive(varType);
|
||||
}
|
||||
}
|
||||
|
||||
return (
|
||||
<div
|
||||
className={clsx(
|
||||
bgStyles,
|
||||
"bg-opacity-50 rounded-md transition ease-in-out duration-700",
|
||||
isContent ? "py-2 px-4 border" : "p-0",
|
||||
isOutlined && "ring-2 ring-blue-500",
|
||||
"text-center font-mono"
|
||||
)}
|
||||
>
|
||||
<Handle
|
||||
type="target"
|
||||
position={targetPosition ?? Position.Top}
|
||||
isConnectable={false}
|
||||
style={{ background: "transparent", border: "none" }}
|
||||
/>
|
||||
{renderContent}
|
||||
<Handle
|
||||
type="source"
|
||||
position={sourcePosition ?? Position.Bottom}
|
||||
isConnectable={false}
|
||||
style={{ background: "transparent", border: "none" }}
|
||||
/>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
function VariableNodeContent(
|
||||
variable: Variable,
|
||||
desc: TypeDescriptor | undefined,
|
||||
basis: BasisProps
|
||||
): Record<string, JSX.Element | null> {
|
||||
if (!desc) return {};
|
||||
const { content } = desc;
|
||||
|
||||
switch (content.type) {
|
||||
case "Flex":
|
||||
case "Rigid": {
|
||||
const { name } = content;
|
||||
return { name: name ? <>{name}</> : null };
|
||||
}
|
||||
case "FlexAble":
|
||||
case "RigidAble": {
|
||||
const { name, abilities } = content;
|
||||
return {
|
||||
name: <>{name}</>,
|
||||
abilities: <>[{abilities.join(", ")}]</>,
|
||||
};
|
||||
}
|
||||
case "Recursive": {
|
||||
const { name, structure } = content;
|
||||
return {
|
||||
name: <>{name}</>,
|
||||
structure: <SubVariable {...basis} variable={structure} />,
|
||||
};
|
||||
}
|
||||
case "LambdaSet": {
|
||||
const { ambient_function, solved, unspecialized, recursion_var } =
|
||||
content;
|
||||
return {
|
||||
"^": <SubVariable {...basis} variable={ambient_function} />,
|
||||
as: recursion_var ? (
|
||||
<SubVariable {...basis} variable={recursion_var} />
|
||||
) : null,
|
||||
};
|
||||
}
|
||||
case "ErasedLambda": {
|
||||
return {};
|
||||
}
|
||||
case "Alias": {
|
||||
const { name, real_variable, variables } = content;
|
||||
return {
|
||||
name: <>{name}</>,
|
||||
};
|
||||
}
|
||||
case "Apply": {
|
||||
const { name, variables } = content;
|
||||
return {
|
||||
name: <>{name}</>,
|
||||
};
|
||||
}
|
||||
case "Function": {
|
||||
const { arguments: args, lambda_type, ret } = content;
|
||||
return {
|
||||
args: (
|
||||
<>
|
||||
{args.map((arg, i) => (
|
||||
<SubVariable key={i} {...basis} variable={arg} />
|
||||
))}
|
||||
</>
|
||||
),
|
||||
"||": <SubVariable {...basis} variable={lambda_type} />,
|
||||
ret: <SubVariable {...basis} variable={ret} />,
|
||||
};
|
||||
}
|
||||
case "FunctionOrTagUnion": {
|
||||
const { tags, functions, extension } = content;
|
||||
return {
|
||||
tags: <>[{tags.join(", ")}]</>,
|
||||
fns: <>[{functions.join(", ")}]</>,
|
||||
};
|
||||
}
|
||||
case "TagUnion": {
|
||||
const { tags, extension } = content;
|
||||
return {};
|
||||
}
|
||||
case "RecursiveTagUnion": {
|
||||
const { recursion_var, extension, tags } = content;
|
||||
return {
|
||||
as: <SubVariable {...basis} variable={recursion_var} />,
|
||||
};
|
||||
}
|
||||
case "Record": {
|
||||
const { fields, extension } = content;
|
||||
return {};
|
||||
}
|
||||
case "Tuple": {
|
||||
const { elements, extension } = content;
|
||||
return {};
|
||||
}
|
||||
case "RangedNumber": {
|
||||
const { range } = content;
|
||||
return {};
|
||||
}
|
||||
case "EmptyRecord":
|
||||
case "EmptyTuple":
|
||||
case "EmptyTagUnion":
|
||||
case "Error": {
|
||||
return {};
|
||||
}
|
||||
default: {
|
||||
return assertExhaustive(content);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
interface BasisProps {
|
||||
subs: SubsSnapshot;
|
||||
origin: Variable;
|
||||
addSubVariableLink: AddSubVariableLink;
|
||||
}
|
||||
|
||||
function SubVariable({
|
||||
subs,
|
||||
origin,
|
||||
variable,
|
||||
addSubVariableLink,
|
||||
}: {
|
||||
variable: Variable;
|
||||
} & BasisProps): JSX.Element {
|
||||
return (
|
||||
<VariableElPretty
|
||||
variable={variable}
|
||||
subs={subs}
|
||||
onClick={() => addSubVariableLink({ from: origin, variable })}
|
||||
/>
|
||||
);
|
||||
}
|
|
@ -0,0 +1,567 @@
|
|||
import ELK, {
|
||||
type ElkNode,
|
||||
type LayoutOptions,
|
||||
} from "elkjs/lib/elk.bundled.js";
|
||||
import ReactFlow, {
|
||||
Node,
|
||||
Edge,
|
||||
Background,
|
||||
BackgroundVariant,
|
||||
useReactFlow,
|
||||
ReactFlowProvider,
|
||||
NodeChange,
|
||||
applyNodeChanges,
|
||||
EdgeChange,
|
||||
applyEdgeChanges,
|
||||
Panel,
|
||||
NodeTypes,
|
||||
useStore,
|
||||
ReactFlowState,
|
||||
Position,
|
||||
MarkerType,
|
||||
EdgeMarkerType,
|
||||
} from "reactflow";
|
||||
import { useCallback, useEffect, useRef, useState } from "react";
|
||||
import { Variable } from "../../schema";
|
||||
|
||||
import "reactflow/dist/style.css";
|
||||
import clsx from "clsx";
|
||||
import VariableNode, { VariableNodeProps } from "./VariableNode";
|
||||
import { SubsSnapshot } from "../../engine/subs";
|
||||
import { TypedEmitter } from "tiny-typed-emitter";
|
||||
import EpochCell from "../Common/EpochCell";
|
||||
import { HashLink } from "react-router-hash-link";
|
||||
import {
|
||||
EventListMessage,
|
||||
GraphMessage,
|
||||
VariableMessage,
|
||||
} from "../../utils/events";
|
||||
import { useFocusOutlineEvent } from "../../hooks/useFocusOutlineEvent";
|
||||
|
||||
export interface VariablesGraphProps {
|
||||
subs: SubsSnapshot;
|
||||
graphEe: TypedEmitter<GraphMessage>;
|
||||
eventListEe: TypedEmitter<EventListMessage>;
|
||||
}
|
||||
|
||||
function horizontalityToPositions(isHorizontal: boolean): {
|
||||
targetPosition: Position;
|
||||
sourcePosition: Position;
|
||||
} {
|
||||
return {
|
||||
targetPosition: isHorizontal ? Position.Left : Position.Top,
|
||||
sourcePosition: isHorizontal ? Position.Right : Position.Bottom,
|
||||
};
|
||||
}
|
||||
|
||||
interface LayoutedElements {
|
||||
nodes: Node[];
|
||||
edges: Edge[];
|
||||
}
|
||||
|
||||
const LAYOUT_CONFIG_DOWN = {
|
||||
keypress: "j",
|
||||
emoji: "⬇️",
|
||||
elkLayoutOptions: {
|
||||
"elk.algorithm": "layered",
|
||||
"elk.direction": "DOWN",
|
||||
},
|
||||
isHorizontal: false,
|
||||
} as const;
|
||||
const LAYOUT_CONFIG_RIGHT = {
|
||||
keypress: "l",
|
||||
emoji: "➡️",
|
||||
elkLayoutOptions: {
|
||||
"elk.algorithm": "layered",
|
||||
"elk.direction": "RIGHT",
|
||||
},
|
||||
isHorizontal: true,
|
||||
} as const;
|
||||
const LAYOUT_CONFIG_RADIAL = {
|
||||
keypress: "r",
|
||||
emoji: "🌐",
|
||||
elkLayoutOptions: {
|
||||
"elk.algorithm": "radial",
|
||||
},
|
||||
isHorizontal: false,
|
||||
} as const;
|
||||
const LAYOUT_CONFIG_FORCE = {
|
||||
keypress: "f",
|
||||
emoji: "🧲",
|
||||
elkLayoutOptions: {
|
||||
"elk.algorithm": "force",
|
||||
},
|
||||
isHorizontal: false,
|
||||
} as const;
|
||||
|
||||
type LayoutConfiguration =
|
||||
| typeof LAYOUT_CONFIG_DOWN
|
||||
| typeof LAYOUT_CONFIG_RIGHT
|
||||
| typeof LAYOUT_CONFIG_RADIAL
|
||||
| typeof LAYOUT_CONFIG_FORCE;
|
||||
|
||||
const LAYOUT_CONFIGURATIONS: LayoutConfiguration[] = [
|
||||
LAYOUT_CONFIG_DOWN,
|
||||
LAYOUT_CONFIG_RIGHT,
|
||||
LAYOUT_CONFIG_RADIAL,
|
||||
LAYOUT_CONFIG_FORCE,
|
||||
];
|
||||
|
||||
type ComputeElkLayoutOptions = Pick<
|
||||
LayoutConfiguration,
|
||||
"elkLayoutOptions" | "isHorizontal"
|
||||
>;
|
||||
|
||||
interface ComputeLayoutedElementsProps
|
||||
extends LayoutedElements,
|
||||
ComputeElkLayoutOptions {}
|
||||
|
||||
// Elk has a *huge* amount of options to configure. To see everything you can
|
||||
// tweak check out:
|
||||
//
|
||||
// - https://www.eclipse.org/elk/reference/algorithms.html
|
||||
// - https://www.eclipse.org/elk/reference/options.html
|
||||
const baseElkOptions: LayoutOptions = {
|
||||
"elk.layered.spacing.nodeNodeBetweenLayers": "100",
|
||||
"elk.spacing.nodeNode": "80",
|
||||
};
|
||||
|
||||
async function computeLayoutedElements({
|
||||
nodes,
|
||||
edges,
|
||||
elkLayoutOptions,
|
||||
isHorizontal,
|
||||
}: ComputeLayoutedElementsProps): Promise<LayoutedElements> {
|
||||
if (nodes.length === 0) {
|
||||
return Promise.resolve({
|
||||
nodes: [],
|
||||
edges: [],
|
||||
});
|
||||
}
|
||||
|
||||
const elk = new ELK();
|
||||
const graph: ElkNode = {
|
||||
id: "root",
|
||||
layoutOptions: {
|
||||
...baseElkOptions,
|
||||
...elkLayoutOptions,
|
||||
},
|
||||
//@ts-ignore
|
||||
children: nodes.map((node) => ({
|
||||
...node,
|
||||
// Adjust the target and source handle positions based on the layout
|
||||
// direction.
|
||||
targetPosition: isHorizontal ? "left" : "top",
|
||||
sourcePosition: isHorizontal ? "right" : "bottom",
|
||||
|
||||
// Hardcode a width and height for elk to use when layouting.
|
||||
//width: 150,
|
||||
//height: 50,
|
||||
})),
|
||||
//@ts-ignore
|
||||
edges: edges.map((edge) => ({
|
||||
...edge,
|
||||
})),
|
||||
};
|
||||
|
||||
const layoutedGraph = await elk.layout(graph);
|
||||
|
||||
if (!layoutedGraph.children || !layoutedGraph.edges) {
|
||||
throw new Error("Elk did not return a valid graph");
|
||||
}
|
||||
|
||||
return {
|
||||
//@ts-ignore
|
||||
nodes: layoutedGraph.children.map((node) => ({
|
||||
...node,
|
||||
// React Flow expects a position property on the node instead of `x`
|
||||
// and `y` fields.
|
||||
position: { x: node.x, y: node.y },
|
||||
})),
|
||||
//@ts-ignore
|
||||
edges: layoutedGraph.edges,
|
||||
};
|
||||
}
|
||||
|
||||
const NODE_TYPES: NodeTypes = {
|
||||
variable: VariableNode,
|
||||
};
|
||||
|
||||
type VariableNodeData = VariableNodeProps["data"];
|
||||
type RFVariableNode = Node<VariableNodeData>;
|
||||
|
||||
function newVariable(
|
||||
id: string,
|
||||
data: VariableNodeData,
|
||||
isHorizontal: boolean
|
||||
): RFVariableNode {
|
||||
return {
|
||||
id,
|
||||
position: { x: 0, y: 0 },
|
||||
type: "variable",
|
||||
data,
|
||||
...horizontalityToPositions(isHorizontal),
|
||||
};
|
||||
}
|
||||
|
||||
function canAddVariable(variableName: string, existingNodes: Node[]): boolean {
|
||||
return !existingNodes.some((n) => n.id === variableName);
|
||||
}
|
||||
|
||||
function canAddEdge(edgeName: string, existingEdges: Edge[]): boolean {
|
||||
return !existingEdges.some((e) => e.id === edgeName);
|
||||
}
|
||||
|
||||
function addNode(node: Node): NodeChange {
|
||||
return {
|
||||
type: "add",
|
||||
item: node,
|
||||
};
|
||||
}
|
||||
|
||||
function addEdge(edge: Edge): EdgeChange {
|
||||
return {
|
||||
type: "add",
|
||||
item: edge,
|
||||
};
|
||||
}
|
||||
|
||||
// Auto-layout logic due in part to the `feldera/dbsp` project, licensed under
|
||||
// the MIT license.
|
||||
//
|
||||
// The source code for the original project can be found at
|
||||
// https://github.com/feldera/dbsp/blob/585a1926a6d3a0f8176dc80db5e906ec7d095400/web-ui/src/streaming/builder/hooks/useAutoLayout.ts#L215
|
||||
// and its license at
|
||||
// https://github.com/feldera/dbsp/blob/585a1926a6d3a0f8176dc80db5e906ec7d095400/LICENSE
|
||||
const nodeCountSelector = (state: ReactFlowState) =>
|
||||
state.nodeInternals.size + state.edges.length;
|
||||
const nodesSetInViewSelector = (state: ReactFlowState) =>
|
||||
Array.from(state.nodeInternals.values()).every(
|
||||
(node) => node.width && node.height
|
||||
);
|
||||
|
||||
type RedoLayoutFn = () => Promise<void>;
|
||||
function useRedoLayout(options: ComputeElkLayoutOptions): RedoLayoutFn {
|
||||
const nodeCount = useStore(nodeCountSelector);
|
||||
const nodesInitialized = useStore(nodesSetInViewSelector);
|
||||
const { getNodes, setNodes, getEdges } = useReactFlow();
|
||||
const instance = useReactFlow();
|
||||
|
||||
return useCallback(async () => {
|
||||
if (!nodeCount || !nodesInitialized) {
|
||||
return;
|
||||
}
|
||||
const { nodes } = await computeLayoutedElements({
|
||||
nodes: getNodes(),
|
||||
edges: getEdges(),
|
||||
...options,
|
||||
});
|
||||
setNodes(nodes);
|
||||
window.requestAnimationFrame(() => {
|
||||
instance.fitView();
|
||||
});
|
||||
}, [
|
||||
nodeCount,
|
||||
nodesInitialized,
|
||||
getNodes,
|
||||
getEdges,
|
||||
options,
|
||||
setNodes,
|
||||
instance,
|
||||
]);
|
||||
}
|
||||
|
||||
// Does positioning of the nodes in the graph.
|
||||
function useAutoLayout(options: ComputeElkLayoutOptions) {
|
||||
const redoLayout = useRedoLayout(options);
|
||||
|
||||
useEffect(() => {
|
||||
// This wrapping is of course redundant, but exercised for the purpose of
|
||||
// explicitness.
|
||||
async function inner() {
|
||||
await redoLayout();
|
||||
}
|
||||
inner();
|
||||
}, [redoLayout]);
|
||||
}
|
||||
|
||||
function useKeydown({
|
||||
layoutConfig,
|
||||
setLayoutConfig,
|
||||
graphEe,
|
||||
}: {
|
||||
layoutConfig: LayoutConfiguration;
|
||||
setLayoutConfig: React.Dispatch<React.SetStateAction<LayoutConfiguration>>;
|
||||
graphEe: TypedEmitter<GraphMessage>;
|
||||
}) {
|
||||
const redoLayout = useRedoLayout(layoutConfig);
|
||||
|
||||
const keyDownHandler = useCallback(
|
||||
async (key: string) => {
|
||||
switch (key) {
|
||||
case "c": {
|
||||
await redoLayout();
|
||||
return;
|
||||
}
|
||||
default: {
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
for (const config of LAYOUT_CONFIGURATIONS) {
|
||||
if (key === config.keypress) {
|
||||
setLayoutConfig(config);
|
||||
return;
|
||||
}
|
||||
}
|
||||
},
|
||||
[redoLayout, setLayoutConfig]
|
||||
);
|
||||
graphEe.on("keydown", async (key) => await keyDownHandler(key));
|
||||
}
|
||||
|
||||
function Graph({
|
||||
subs,
|
||||
graphEe,
|
||||
eventListEe,
|
||||
}: VariablesGraphProps): JSX.Element {
|
||||
const instance = useReactFlow();
|
||||
|
||||
// We need to reset the graph when the subs snapshot changes. I'm not sure
|
||||
// why this isn't done by the existing state manager.
|
||||
useEffect(() => {
|
||||
instance.setNodes([]);
|
||||
instance.setEdges([]);
|
||||
}, [instance, subs.epoch]);
|
||||
|
||||
const varEe = useRef(new TypedEmitter<VariableMessage>());
|
||||
// Allow an unbounded number of listeners since we attach a listener for each
|
||||
// variable.
|
||||
varEe.current.setMaxListeners(Infinity);
|
||||
|
||||
const isOutlined = useFocusOutlineEvent({
|
||||
ee: graphEe,
|
||||
value: subs.epoch,
|
||||
event: "focusEpoch",
|
||||
});
|
||||
|
||||
const [layoutConfig, setLayoutConfig] =
|
||||
useState<LayoutConfiguration>(LAYOUT_CONFIG_DOWN);
|
||||
|
||||
const [elements, setElements] = useState<LayoutedElements>({
|
||||
nodes: [],
|
||||
edges: [],
|
||||
});
|
||||
|
||||
const [variablesNeedingFocus, setVariablesNeedingFocus] = useState<
|
||||
Set<Variable>
|
||||
>(new Set());
|
||||
|
||||
useEffect(() => {
|
||||
if (variablesNeedingFocus.size === 0) {
|
||||
return;
|
||||
}
|
||||
for (const variable of variablesNeedingFocus) {
|
||||
varEe.current.emit("focus", variable);
|
||||
}
|
||||
setVariablesNeedingFocus(new Set());
|
||||
}, [variablesNeedingFocus]);
|
||||
|
||||
useAutoLayout(layoutConfig);
|
||||
useKeydown({
|
||||
layoutConfig,
|
||||
setLayoutConfig,
|
||||
graphEe,
|
||||
});
|
||||
|
||||
const onNodesChange = useCallback((changes: NodeChange[]) => {
|
||||
setElements(({ nodes, edges }) => {
|
||||
return {
|
||||
nodes: applyNodeChanges(changes, nodes),
|
||||
edges,
|
||||
};
|
||||
});
|
||||
}, []);
|
||||
|
||||
const onEdgesChange = useCallback((changes: EdgeChange[]) => {
|
||||
setElements(({ nodes, edges }) => {
|
||||
return {
|
||||
nodes,
|
||||
edges: applyEdgeChanges(changes, edges),
|
||||
};
|
||||
});
|
||||
}, []);
|
||||
|
||||
interface AddNewVariableParams {
|
||||
from?: Variable;
|
||||
variable: Variable;
|
||||
}
|
||||
|
||||
const addNewVariable = useCallback(
|
||||
({ from, variable }: AddNewVariableParams) => {
|
||||
const variablesToFocus = new Set<Variable>();
|
||||
|
||||
setElements(({ nodes, edges }) => {
|
||||
let fromVariable: Variable | undefined = from;
|
||||
let toVariable: Variable | undefined = variable;
|
||||
|
||||
const nodeChanges: NodeChange[] = [];
|
||||
const edgeChanges: EdgeChange[] = [];
|
||||
|
||||
while (toVariable !== undefined) {
|
||||
const toVariableName = toVariable.toString();
|
||||
if (canAddVariable(toVariableName, nodes)) {
|
||||
const newVariableNode = newVariable(
|
||||
toVariable.toString(),
|
||||
{
|
||||
subs,
|
||||
rawVariable: toVariable,
|
||||
addSubVariableLink: addNewVariable,
|
||||
isOutlined: true,
|
||||
ee: varEe.current,
|
||||
},
|
||||
layoutConfig.isHorizontal
|
||||
);
|
||||
|
||||
nodeChanges.push(addNode(newVariableNode));
|
||||
}
|
||||
|
||||
if (fromVariable !== undefined) {
|
||||
const edgeName = `${fromVariable}->${toVariable}`;
|
||||
if (canAddEdge(edgeName, edges)) {
|
||||
let markerEnd: EdgeMarkerType | undefined;
|
||||
if (subs.get_root_key(fromVariable) === toVariable) {
|
||||
markerEnd = {
|
||||
type: MarkerType.ArrowClosed,
|
||||
width: 20,
|
||||
height: 20,
|
||||
};
|
||||
}
|
||||
|
||||
const newEdge = addEdge({
|
||||
id: `${fromVariable}->${toVariable}`,
|
||||
source: fromVariable.toString(),
|
||||
target: toVariableName,
|
||||
markerEnd,
|
||||
});
|
||||
|
||||
edgeChanges.push(newEdge);
|
||||
}
|
||||
}
|
||||
|
||||
variablesToFocus.add(toVariable);
|
||||
|
||||
fromVariable = toVariable;
|
||||
const rootToVariable = subs.get_root_key(toVariable);
|
||||
if (toVariable !== rootToVariable) {
|
||||
toVariable = rootToVariable;
|
||||
} else {
|
||||
toVariable = undefined;
|
||||
}
|
||||
}
|
||||
|
||||
const newNodes = applyNodeChanges(nodeChanges, nodes);
|
||||
const newEdges = applyEdgeChanges(edgeChanges, edges);
|
||||
|
||||
return { nodes: newNodes, edges: newEdges };
|
||||
});
|
||||
|
||||
setVariablesNeedingFocus(variablesToFocus);
|
||||
},
|
||||
[layoutConfig.isHorizontal, subs]
|
||||
);
|
||||
|
||||
const addNewVariableNode = useCallback(
|
||||
(variable: Variable) => {
|
||||
addNewVariable({ variable });
|
||||
},
|
||||
[addNewVariable]
|
||||
);
|
||||
|
||||
graphEe.on("focusVariable", addNewVariableNode);
|
||||
|
||||
return (
|
||||
<ReactFlow
|
||||
nodes={elements.nodes}
|
||||
edges={elements.edges}
|
||||
onNodesChange={(e) => onNodesChange(e)}
|
||||
onEdgesChange={(e) => onEdgesChange(e)}
|
||||
fitView
|
||||
nodesDraggable
|
||||
nodesConnectable={false}
|
||||
nodeTypes={NODE_TYPES}
|
||||
proOptions={{
|
||||
// https://reactflow.dev/docs/guides/remove-attribution/
|
||||
hideAttribution: true,
|
||||
}}
|
||||
className={clsx(
|
||||
"ring-inset rounded-md transition ease-in-out duration-700",
|
||||
isOutlined && "ring-2 ring-blue-500"
|
||||
)}
|
||||
>
|
||||
<Panel position="top-left">
|
||||
<HashLink
|
||||
smooth
|
||||
to={`#events-${subs.epoch}`}
|
||||
onClick={() => {
|
||||
eventListEe.emit("focusEpoch", subs.epoch);
|
||||
}}
|
||||
>
|
||||
<EpochCell>Epoch {subs.epoch}</EpochCell>
|
||||
</HashLink>
|
||||
</Panel>
|
||||
<Panel position="top-right">
|
||||
<LayoutPanel
|
||||
layoutConfig={layoutConfig}
|
||||
setLayoutConfig={setLayoutConfig}
|
||||
/>
|
||||
</Panel>
|
||||
|
||||
<Background variant={BackgroundVariant.Dots} />
|
||||
</ReactFlow>
|
||||
);
|
||||
}
|
||||
|
||||
interface LayoutPanelProps {
|
||||
layoutConfig: LayoutConfiguration;
|
||||
setLayoutConfig: React.Dispatch<React.SetStateAction<LayoutConfiguration>>;
|
||||
}
|
||||
|
||||
function LayoutPanel({
|
||||
layoutConfig,
|
||||
setLayoutConfig,
|
||||
}: LayoutPanelProps): JSX.Element {
|
||||
const commonStyle = "rounded cursor-pointer text-2xl select-none";
|
||||
|
||||
return (
|
||||
<>
|
||||
{LAYOUT_CONFIGURATIONS.map((config, i) => (
|
||||
<span
|
||||
key={i}
|
||||
className={clsx(
|
||||
commonStyle,
|
||||
i !== 0 ? "ml-2" : "",
|
||||
config !== layoutConfig ? "opacity-50" : ""
|
||||
)}
|
||||
onClick={() => {
|
||||
setLayoutConfig(config);
|
||||
}}
|
||||
>
|
||||
{config.emoji}
|
||||
</span>
|
||||
))}
|
||||
</>
|
||||
);
|
||||
}
|
||||
|
||||
export default function VariablesGraph({
|
||||
subs,
|
||||
graphEe,
|
||||
eventListEe,
|
||||
}: VariablesGraphProps) {
|
||||
return (
|
||||
<ReactFlowProvider>
|
||||
<Graph subs={subs} graphEe={graphEe} eventListEe={eventListEe} />
|
||||
</ReactFlowProvider>
|
||||
);
|
||||
}
|
111
crates/compiler/checkmate/www/src/components/Ui.tsx
Normal file
111
crates/compiler/checkmate/www/src/components/Ui.tsx
Normal file
|
@ -0,0 +1,111 @@
|
|||
import React, { useEffect, useState } from "react";
|
||||
import { AllEvents } from "../schema";
|
||||
import { Engine, EventEpoch } from "../engine/engine";
|
||||
import EventList from "./EventList/index";
|
||||
import VariablesGraph from "./Graph/VariablesGraph";
|
||||
import { TypedEmitter } from "tiny-typed-emitter";
|
||||
import {
|
||||
EventListMessage,
|
||||
GlobalMessage,
|
||||
GraphMessage,
|
||||
LoadEpochView,
|
||||
} from "../utils/events";
|
||||
import { assertExhaustive } from "../utils/exhaustive";
|
||||
import { SubsSnapshot } from "../engine/subs";
|
||||
|
||||
interface UiProps {
|
||||
events: AllEvents;
|
||||
}
|
||||
|
||||
export default function Ui({ events }: UiProps): JSX.Element {
|
||||
const engine = React.useRef(new Engine(events));
|
||||
|
||||
const graphEe = React.useRef(new TypedEmitter<GraphMessage>());
|
||||
const eventListEe = React.useRef(new TypedEmitter<EventListMessage>());
|
||||
const globalEe = React.useRef(new TypedEmitter<GlobalMessage>());
|
||||
|
||||
const [subsTop, setSubsTop] = useState<SubsSnapshot | undefined>(undefined);
|
||||
const [subsBot, setSubsBot] = useState<SubsSnapshot | undefined>(undefined);
|
||||
|
||||
useEffect(() => {
|
||||
globalEe.current.on("loadEpoch", (epoch, view) => {
|
||||
switch (view) {
|
||||
case LoadEpochView.Top: {
|
||||
setSubsTop(engine.current.stepToSnapshot(epoch));
|
||||
break;
|
||||
}
|
||||
case LoadEpochView.Bot: {
|
||||
setSubsBot(engine.current.stepToSnapshot(epoch));
|
||||
break;
|
||||
}
|
||||
default:
|
||||
assertExhaustive(view);
|
||||
}
|
||||
});
|
||||
}, []);
|
||||
|
||||
const selectedEpochs = [subsTop?.epoch, subsBot?.epoch]
|
||||
.filter((x): x is EventEpoch => x !== undefined)
|
||||
.sort();
|
||||
|
||||
return (
|
||||
<div
|
||||
className="flex flex-col md:flex-row gap-0 w-full h-full"
|
||||
onKeyDown={(e) => {
|
||||
graphEe.current.emit("keydown", e.key);
|
||||
}}
|
||||
>
|
||||
<div className="font-mono mt-2 text-lg md:flex-1 overflow-x-hidden overflow-y-scroll">
|
||||
<EventList
|
||||
engine={engine.current}
|
||||
depth={0}
|
||||
events={events}
|
||||
graphEe={graphEe.current}
|
||||
eventListEe={eventListEe.current}
|
||||
globalEe={globalEe.current}
|
||||
selectedEpochs={selectedEpochs}
|
||||
/>
|
||||
</div>
|
||||
<div className="flex-1 min-h-[50%] h-full flex flex-col place-content-center shadow">
|
||||
{selectedEpochs.length === 0 && (
|
||||
<span className="text-center">
|
||||
<span className="p-2 border rounded-md bg-gray-200 inline-block">
|
||||
Select an event to view.
|
||||
</span>
|
||||
</span>
|
||||
)}
|
||||
{subsTop !== undefined && (
|
||||
<VariablesGraphView
|
||||
subs={subsTop}
|
||||
graphEe={graphEe.current}
|
||||
eventListEe={eventListEe.current}
|
||||
/>
|
||||
)}
|
||||
{/* */}
|
||||
{subsBot !== undefined && (
|
||||
<VariablesGraphView
|
||||
subs={subsBot}
|
||||
graphEe={graphEe.current}
|
||||
eventListEe={eventListEe.current}
|
||||
/>
|
||||
)}
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
interface VariablesGraphViewProps {
|
||||
subs: SubsSnapshot;
|
||||
graphEe: TypedEmitter<GraphMessage>;
|
||||
eventListEe: TypedEmitter<EventListMessage>;
|
||||
}
|
||||
|
||||
function VariablesGraphView({
|
||||
subs,
|
||||
graphEe,
|
||||
eventListEe,
|
||||
}: VariablesGraphViewProps): JSX.Element {
|
||||
return (
|
||||
<VariablesGraph subs={subs} graphEe={graphEe} eventListEe={eventListEe} />
|
||||
);
|
||||
}
|
162
crates/compiler/checkmate/www/src/engine/engine.tsx
Normal file
162
crates/compiler/checkmate/www/src/engine/engine.tsx
Normal file
|
@ -0,0 +1,162 @@
|
|||
import { Event, Variable } from "../schema";
|
||||
import { assertExhaustive } from "../utils/exhaustive";
|
||||
import {
|
||||
ChangeEvent,
|
||||
makeDeleteVariable,
|
||||
makeRevertVariable,
|
||||
RollbackChange,
|
||||
Subs,
|
||||
SubsSnapshot,
|
||||
} from "./subs";
|
||||
|
||||
export type EventEpoch = number & { __eventIndex: never };
|
||||
|
||||
function* flattenEvents(events: Event[]): Generator<Event> {
|
||||
for (const event of events) {
|
||||
yield event;
|
||||
switch (event.type) {
|
||||
case "Unification": {
|
||||
yield* flattenEvents(event.subevents);
|
||||
break;
|
||||
}
|
||||
case "VariableUnified":
|
||||
case "VariableSetDescriptor":
|
||||
break;
|
||||
default:
|
||||
assertExhaustive(event);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
function getFlatEvents(events: Event[]): {
|
||||
flatEvents: Event[];
|
||||
map: Map<Event, EventEpoch>;
|
||||
} {
|
||||
const map = new Map<Event, EventEpoch>();
|
||||
const flatEvents = Array.from(flattenEvents(events));
|
||||
let i = 0;
|
||||
for (const event of flatEvents) {
|
||||
map.set(event, i as EventEpoch);
|
||||
i++;
|
||||
}
|
||||
return { flatEvents, map };
|
||||
}
|
||||
|
||||
export class Engine {
|
||||
#eventIndexMap: Map<Event, EventEpoch>;
|
||||
#events: Event[];
|
||||
#subs: Subs = Subs.new();
|
||||
#reverseEvents: Map<EventEpoch, RollbackChange> = new Map();
|
||||
|
||||
#nextIndexForward: EventEpoch = 0 as EventEpoch;
|
||||
|
||||
constructor(events: Event[]) {
|
||||
const { flatEvents, map } = getFlatEvents(events);
|
||||
this.#eventIndexMap = map;
|
||||
this.#events = flatEvents;
|
||||
}
|
||||
|
||||
getEventIndex(event: Event): EventEpoch {
|
||||
const index = this.#eventIndexMap.get(event);
|
||||
if (index === undefined) {
|
||||
throw new Error("Event not found");
|
||||
}
|
||||
return index;
|
||||
}
|
||||
|
||||
get step(): EventEpoch {
|
||||
return this.#nextIndexForward;
|
||||
}
|
||||
|
||||
stepTo(eventIndex: EventEpoch): void {
|
||||
while (this.#nextIndexForward <= eventIndex) {
|
||||
this.stepForward(this.#nextIndexForward);
|
||||
++this.#nextIndexForward;
|
||||
}
|
||||
while (this.#nextIndexForward > eventIndex + 1) {
|
||||
--this.#nextIndexForward;
|
||||
this.stepBackward(this.#nextIndexForward);
|
||||
}
|
||||
|
||||
if (this.#nextIndexForward !== eventIndex + 1) {
|
||||
throw new Error("Invalid event index");
|
||||
}
|
||||
}
|
||||
|
||||
stepToSnapshot(eventIndex: EventEpoch): SubsSnapshot {
|
||||
this.stepTo(eventIndex);
|
||||
return this.subsSnapshot();
|
||||
}
|
||||
|
||||
get subs(): Readonly<Subs> {
|
||||
return this.#subs;
|
||||
}
|
||||
|
||||
subsSnapshot(): SubsSnapshot {
|
||||
return this.#subs.snapshot({
|
||||
epoch: (this.#nextIndexForward - 1) as EventEpoch,
|
||||
});
|
||||
}
|
||||
|
||||
lastEventIndex(): EventEpoch {
|
||||
return (this.#events.length - 1) as EventEpoch;
|
||||
}
|
||||
|
||||
private stepForward(eventIndex: EventEpoch): void {
|
||||
const event = this.#events[eventIndex];
|
||||
if (!isApplicable(event)) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (!this.#reverseEvents.has(eventIndex)) {
|
||||
const variable = applicableVariable(event);
|
||||
const current = this.#subs.get(variable);
|
||||
let revert: RollbackChange;
|
||||
if (!current) {
|
||||
revert = makeDeleteVariable({ variable });
|
||||
} else {
|
||||
revert = makeRevertVariable({ variable, to: current });
|
||||
}
|
||||
this.#reverseEvents.set(eventIndex, revert);
|
||||
}
|
||||
|
||||
this.#subs.apply(event);
|
||||
}
|
||||
|
||||
private stepBackward(eventIndex: EventEpoch): void {
|
||||
const event = this.#events[eventIndex];
|
||||
if (!isApplicable(event)) {
|
||||
return;
|
||||
}
|
||||
|
||||
const revert = this.#reverseEvents.get(eventIndex);
|
||||
if (!revert) {
|
||||
throw new Error("No revert found");
|
||||
}
|
||||
|
||||
this.#subs.apply(revert);
|
||||
}
|
||||
}
|
||||
|
||||
function isApplicable(event: Event): event is ChangeEvent {
|
||||
switch (event.type) {
|
||||
case "VariableUnified":
|
||||
case "VariableSetDescriptor":
|
||||
return true;
|
||||
case "Unification":
|
||||
return false;
|
||||
default:
|
||||
assertExhaustive(event);
|
||||
}
|
||||
}
|
||||
|
||||
function applicableVariable(event: ChangeEvent): Variable {
|
||||
switch (event.type) {
|
||||
case "VariableUnified":
|
||||
return event.from;
|
||||
case "VariableSetDescriptor":
|
||||
return event.variable;
|
||||
default:
|
||||
assertExhaustive(event);
|
||||
}
|
||||
}
|
19
crates/compiler/checkmate/www/src/engine/event_util.tsx
Normal file
19
crates/compiler/checkmate/www/src/engine/event_util.tsx
Normal file
|
@ -0,0 +1,19 @@
|
|||
import { Event } from "../schema";
|
||||
|
||||
export function lastSubEvent(event: Event): Event {
|
||||
switch (event.type) {
|
||||
case "Unification": {
|
||||
const subevents = event.subevents;
|
||||
if (subevents.length === 0) {
|
||||
return event;
|
||||
}
|
||||
return lastSubEvent(event.subevents[event.subevents.length - 1]);
|
||||
}
|
||||
case "VariableUnified": {
|
||||
return event;
|
||||
}
|
||||
case "VariableSetDescriptor": {
|
||||
return event;
|
||||
}
|
||||
}
|
||||
}
|
179
crates/compiler/checkmate/www/src/engine/subs.tsx
Normal file
179
crates/compiler/checkmate/www/src/engine/subs.tsx
Normal file
|
@ -0,0 +1,179 @@
|
|||
import { Content, Rank, Variable, Event } from "../schema";
|
||||
import { assertExhaustive } from "../utils/exhaustive";
|
||||
import { Refine } from "../utils/refine";
|
||||
import { EventEpoch } from "./engine";
|
||||
|
||||
export type TypeLink = {
|
||||
type: "link";
|
||||
to: Variable;
|
||||
};
|
||||
|
||||
function link({ to }: Omit<TypeLink, "type">): TypeLink {
|
||||
return { type: "link", to };
|
||||
}
|
||||
|
||||
export type TypeDescriptor = {
|
||||
type: "descriptor";
|
||||
rank: Rank;
|
||||
content: Content;
|
||||
};
|
||||
|
||||
function descriptor({
|
||||
rank,
|
||||
content,
|
||||
}: Omit<TypeDescriptor, "type">): TypeDescriptor {
|
||||
return { type: "descriptor", rank, content };
|
||||
}
|
||||
|
||||
export type VarType = TypeLink | TypeDescriptor;
|
||||
|
||||
export type RevertVariableChange = {
|
||||
type: "revertTo";
|
||||
variable: Variable;
|
||||
to: VarType;
|
||||
};
|
||||
|
||||
export type DeleteVariableChange = {
|
||||
type: "delete";
|
||||
variable: Variable;
|
||||
};
|
||||
|
||||
export type RollbackChange = RevertVariableChange | DeleteVariableChange;
|
||||
|
||||
export function makeRevertVariable({
|
||||
variable,
|
||||
to,
|
||||
}: Omit<RevertVariableChange, "type">): RevertVariableChange {
|
||||
return { type: "revertTo", variable, to: { ...to } };
|
||||
}
|
||||
|
||||
export function makeDeleteVariable({
|
||||
variable,
|
||||
}: Omit<DeleteVariableChange, "type">): DeleteVariableChange {
|
||||
return { type: "delete", variable };
|
||||
}
|
||||
|
||||
export type ChangeEvent =
|
||||
| Refine<Event, "VariableUnified">
|
||||
| Refine<Event, "VariableSetDescriptor">;
|
||||
|
||||
export type Change = ChangeEvent | RollbackChange;
|
||||
|
||||
export class Subs implements QuerySubs {
|
||||
#map: Map<Variable, VarType>;
|
||||
|
||||
private constructor(map: Map<Variable, VarType>) {
|
||||
this.#map = map;
|
||||
}
|
||||
|
||||
static new(): Subs {
|
||||
return new Subs(new Map());
|
||||
}
|
||||
|
||||
get(variable: Variable): VarType | undefined {
|
||||
return this.#map.get(variable);
|
||||
}
|
||||
|
||||
get_root(variable: Variable): TypeDescriptor | undefined {
|
||||
const type = this.get(variable);
|
||||
if (type === undefined) {
|
||||
return undefined;
|
||||
}
|
||||
switch (type.type) {
|
||||
case "descriptor":
|
||||
return type;
|
||||
case "link":
|
||||
return this.get_root(type.to);
|
||||
default:
|
||||
assertExhaustive(type);
|
||||
}
|
||||
}
|
||||
|
||||
get_root_key(variable: Variable): Variable {
|
||||
const type = this.get(variable);
|
||||
if (type === undefined) {
|
||||
return variable;
|
||||
}
|
||||
switch (type.type) {
|
||||
case "descriptor":
|
||||
return variable;
|
||||
case "link":
|
||||
return this.get_root_key(type.to);
|
||||
default:
|
||||
assertExhaustive(type);
|
||||
}
|
||||
}
|
||||
|
||||
snapshot({ epoch }: { epoch: EventEpoch }): SubsSnapshot {
|
||||
const snapshotMap = new Map<Variable, VarType>();
|
||||
for (const [key, value] of this.#map) {
|
||||
snapshotMap.set(key, { ...value });
|
||||
}
|
||||
const snapshot = new Subs(snapshotMap);
|
||||
return {
|
||||
epoch,
|
||||
get(variable: Variable): VarType | undefined {
|
||||
return snapshot.get(variable);
|
||||
},
|
||||
get_root(variable: Variable): TypeDescriptor | undefined {
|
||||
return snapshot.get_root(variable);
|
||||
},
|
||||
get_root_key(variable: Variable): Variable {
|
||||
return snapshot.get_root_key(variable);
|
||||
},
|
||||
__snapshot__: SnapshotSymbol,
|
||||
};
|
||||
}
|
||||
|
||||
apply(change: Change): void {
|
||||
switch (change.type) {
|
||||
case "VariableUnified": {
|
||||
const { from, to } = change;
|
||||
if (from !== to) {
|
||||
this.#map.set(from, link({ to }));
|
||||
}
|
||||
break;
|
||||
}
|
||||
case "VariableSetDescriptor": {
|
||||
const { variable, rank, content } = change;
|
||||
const existing = this.get_root(variable);
|
||||
if (existing !== undefined) {
|
||||
const nu = descriptor({ ...existing });
|
||||
if (rank) nu.rank = rank;
|
||||
if (content) nu.content = content;
|
||||
this.#map.set(variable, nu);
|
||||
} else {
|
||||
if (typeof rank !== "number") throw new Error("rank is required");
|
||||
if (!content) throw new Error("content is required");
|
||||
this.#map.set(variable, descriptor({ rank, content }));
|
||||
}
|
||||
break;
|
||||
}
|
||||
case "revertTo": {
|
||||
const { variable, to } = change;
|
||||
this.#map.set(variable, { ...to });
|
||||
break;
|
||||
}
|
||||
case "delete": {
|
||||
const { variable } = change;
|
||||
this.#map.delete(variable);
|
||||
break;
|
||||
}
|
||||
default:
|
||||
assertExhaustive(change);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
const SnapshotSymbol = Symbol("Snapshot");
|
||||
|
||||
export interface QuerySubs {
|
||||
get(variable: Variable): VarType | undefined;
|
||||
get_root(variable: Variable): TypeDescriptor | undefined;
|
||||
get_root_key(variable: Variable): Variable;
|
||||
}
|
||||
|
||||
export interface SubsSnapshot extends QuerySubs {
|
||||
readonly epoch: EventEpoch;
|
||||
__snapshot__: typeof SnapshotSymbol;
|
||||
}
|
|
@ -0,0 +1,42 @@
|
|||
import { useEffect, useState } from "react";
|
||||
import type { TypedEmitter } from "tiny-typed-emitter";
|
||||
|
||||
type Events<Name extends string, T> = {
|
||||
[K in Name]: (value: T) => void;
|
||||
};
|
||||
|
||||
interface UseFocusOutlineEventProps<Name extends string, T> {
|
||||
value: T;
|
||||
ee: TypedEmitter<Events<Name, T>>;
|
||||
event: Name;
|
||||
defaultIsOutlined?: boolean;
|
||||
}
|
||||
|
||||
export function useFocusOutlineEvent<Name extends string, T>({
|
||||
value,
|
||||
ee,
|
||||
event,
|
||||
defaultIsOutlined = false,
|
||||
}: UseFocusOutlineEventProps<Name, T>) {
|
||||
const [isOutlined, setIsOutlined] = useState(defaultIsOutlined);
|
||||
|
||||
useEffect(() => {
|
||||
ee.on(event, (focusValue: T) => {
|
||||
if (focusValue !== value) return;
|
||||
setIsOutlined(true);
|
||||
});
|
||||
}, [ee, event, value]);
|
||||
|
||||
useEffect(() => {
|
||||
if (!isOutlined) return;
|
||||
const timer = setTimeout(() => {
|
||||
setIsOutlined(false);
|
||||
}, 500);
|
||||
|
||||
return () => {
|
||||
clearTimeout(timer);
|
||||
};
|
||||
}, [isOutlined]);
|
||||
|
||||
return isOutlined;
|
||||
}
|
17
crates/compiler/checkmate/www/src/index.css
Normal file
17
crates/compiler/checkmate/www/src/index.css
Normal file
|
@ -0,0 +1,17 @@
|
|||
@tailwind base;
|
||||
@tailwind components;
|
||||
@tailwind utilities;
|
||||
|
||||
.outline-event-col-1 {
|
||||
box-shadow: inset 2px 0px 0px #3B82F6, /* Left */
|
||||
inset -2px 0px 0px transparent, /* Right */
|
||||
inset 0px 2px 0px #3B82F6, /* Bottom */
|
||||
inset 0px -2px 0px #3B82F6; /* Top */
|
||||
}
|
||||
|
||||
.outline-event-col-3 {
|
||||
box-shadow: inset 2px 0px 0px transparent, /* Left */
|
||||
inset -2px 0px 0px #3B82F6, /* Right */
|
||||
inset 0px 2px 0px #3B82F6, /* Bottom */
|
||||
inset 0px -2px 0px #3B82F6; /* Top */
|
||||
}
|
13
crates/compiler/checkmate/www/src/index.tsx
Normal file
13
crates/compiler/checkmate/www/src/index.tsx
Normal file
|
@ -0,0 +1,13 @@
|
|||
import React from "react";
|
||||
import ReactDOM from "react-dom/client";
|
||||
import "./index.css";
|
||||
import App from "./App";
|
||||
|
||||
const root = ReactDOM.createRoot(
|
||||
document.getElementById("root") as HTMLElement
|
||||
);
|
||||
root.render(
|
||||
<React.StrictMode>
|
||||
<App />
|
||||
</React.StrictMode>
|
||||
);
|
243
crates/compiler/checkmate/www/src/schema.d.ts
vendored
Normal file
243
crates/compiler/checkmate/www/src/schema.d.ts
vendored
Normal file
|
@ -0,0 +1,243 @@
|
|||
/* eslint-disable */
|
||||
/**
|
||||
* This file was automatically generated by json-schema-to-typescript.
|
||||
* DO NOT MODIFY IT BY HAND. Instead, modify the source JSONSchema file,
|
||||
* and run json-schema-to-typescript to regenerate this file.
|
||||
*/
|
||||
|
||||
export type Event =
|
||||
| {
|
||||
left: Variable;
|
||||
mode: UnificationMode;
|
||||
right: Variable;
|
||||
subevents: Event[];
|
||||
success?: boolean | null;
|
||||
type: "Unification";
|
||||
[k: string]: unknown;
|
||||
}
|
||||
| {
|
||||
from: Variable;
|
||||
to: Variable;
|
||||
type: "VariableUnified";
|
||||
[k: string]: unknown;
|
||||
}
|
||||
| {
|
||||
content?: Content | null;
|
||||
rank?: Rank | null;
|
||||
type: "VariableSetDescriptor";
|
||||
variable: Variable;
|
||||
[k: string]: unknown;
|
||||
};
|
||||
export type Variable = number;
|
||||
export type UnificationMode =
|
||||
| {
|
||||
type: "Eq";
|
||||
[k: string]: unknown;
|
||||
}
|
||||
| {
|
||||
type: "Present";
|
||||
[k: string]: unknown;
|
||||
}
|
||||
| {
|
||||
type: "LambdaSetSpecialization";
|
||||
[k: string]: unknown;
|
||||
};
|
||||
export type Content =
|
||||
| {
|
||||
name?: string | null;
|
||||
type: "Flex";
|
||||
[k: string]: unknown;
|
||||
}
|
||||
| {
|
||||
name: string;
|
||||
type: "Rigid";
|
||||
[k: string]: unknown;
|
||||
}
|
||||
| {
|
||||
abilities: Symbol[];
|
||||
name?: string | null;
|
||||
type: "FlexAble";
|
||||
[k: string]: unknown;
|
||||
}
|
||||
| {
|
||||
abilities: Symbol[];
|
||||
name: string;
|
||||
type: "RigidAble";
|
||||
[k: string]: unknown;
|
||||
}
|
||||
| {
|
||||
name?: string | null;
|
||||
structure: Variable;
|
||||
type: "Recursive";
|
||||
[k: string]: unknown;
|
||||
}
|
||||
| {
|
||||
ambient_function: Variable;
|
||||
recursion_var?: Variable | null;
|
||||
solved: ClosureType[];
|
||||
type: "LambdaSet";
|
||||
unspecialized: UnspecializedClosureType[];
|
||||
[k: string]: unknown;
|
||||
}
|
||||
| {
|
||||
type: "ErasedLambda";
|
||||
[k: string]: unknown;
|
||||
}
|
||||
| {
|
||||
kind: AliasKind;
|
||||
name: Symbol;
|
||||
real_variable: Variable;
|
||||
type: "Alias";
|
||||
variables: AliasTypeVariables;
|
||||
[k: string]: unknown;
|
||||
}
|
||||
| {
|
||||
symbol: Symbol;
|
||||
type: "Apply";
|
||||
variables: Variable[];
|
||||
[k: string]: unknown;
|
||||
}
|
||||
| {
|
||||
arguments: Variable[];
|
||||
lambda_type: Variable;
|
||||
ret: Variable;
|
||||
type: "Function";
|
||||
[k: string]: unknown;
|
||||
}
|
||||
| {
|
||||
extension: Variable;
|
||||
fields: {
|
||||
[k: string]: RecordField;
|
||||
};
|
||||
type: "Record";
|
||||
[k: string]: unknown;
|
||||
}
|
||||
| {
|
||||
elements: {
|
||||
[k: string]: Variable;
|
||||
};
|
||||
extension: Variable;
|
||||
type: "Tuple";
|
||||
[k: string]: unknown;
|
||||
}
|
||||
| {
|
||||
extension: TagUnionExtension;
|
||||
tags: {
|
||||
[k: string]: Variable[];
|
||||
};
|
||||
type: "TagUnion";
|
||||
[k: string]: unknown;
|
||||
}
|
||||
| {
|
||||
extension: TagUnionExtension;
|
||||
functions: Symbol[];
|
||||
tags: string[];
|
||||
type: "FunctionOrTagUnion";
|
||||
[k: string]: unknown;
|
||||
}
|
||||
| {
|
||||
extension: TagUnionExtension;
|
||||
recursion_var: Variable;
|
||||
tags: {
|
||||
[k: string]: Variable[];
|
||||
};
|
||||
type: "RecursiveTagUnion";
|
||||
[k: string]: unknown;
|
||||
}
|
||||
| {
|
||||
type: "EmptyRecord";
|
||||
[k: string]: unknown;
|
||||
}
|
||||
| {
|
||||
type: "EmptyTuple";
|
||||
[k: string]: unknown;
|
||||
}
|
||||
| {
|
||||
type: "EmptyTagUnion";
|
||||
[k: string]: unknown;
|
||||
}
|
||||
| {
|
||||
range: NumericRange;
|
||||
type: "RangedNumber";
|
||||
[k: string]: unknown;
|
||||
}
|
||||
| {
|
||||
type: "Error";
|
||||
[k: string]: unknown;
|
||||
};
|
||||
export type Symbol = string;
|
||||
export type AliasKind =
|
||||
| {
|
||||
type: "Structural";
|
||||
[k: string]: unknown;
|
||||
}
|
||||
| {
|
||||
type: "Opaque";
|
||||
[k: string]: unknown;
|
||||
};
|
||||
export type RecordFieldKind =
|
||||
| {
|
||||
type: "Demanded";
|
||||
[k: string]: unknown;
|
||||
}
|
||||
| {
|
||||
rigid: boolean;
|
||||
type: "Required";
|
||||
[k: string]: unknown;
|
||||
}
|
||||
| {
|
||||
rigid: boolean;
|
||||
type: "Optional";
|
||||
[k: string]: unknown;
|
||||
};
|
||||
export type TagUnionExtension =
|
||||
| {
|
||||
type: "Openness";
|
||||
variable: Variable;
|
||||
[k: string]: unknown;
|
||||
}
|
||||
| {
|
||||
type: "Any";
|
||||
variable: Variable;
|
||||
[k: string]: unknown;
|
||||
};
|
||||
export type NumericRangeKind =
|
||||
| {
|
||||
type: "Int";
|
||||
[k: string]: unknown;
|
||||
}
|
||||
| {
|
||||
type: "AnyNum";
|
||||
[k: string]: unknown;
|
||||
};
|
||||
export type Rank = number;
|
||||
export type AllEvents = Event[];
|
||||
|
||||
export interface ClosureType {
|
||||
environment: Variable[];
|
||||
function: Symbol;
|
||||
[k: string]: unknown;
|
||||
}
|
||||
export interface UnspecializedClosureType {
|
||||
ability_member: Symbol;
|
||||
lambda_set_region: number;
|
||||
specialization: Variable;
|
||||
[k: string]: unknown;
|
||||
}
|
||||
export interface AliasTypeVariables {
|
||||
infer_ext_in_output_position_variables: Variable[];
|
||||
lambda_set_variables: Variable[];
|
||||
type_variables: Variable[];
|
||||
[k: string]: unknown;
|
||||
}
|
||||
export interface RecordField {
|
||||
field_type: Variable;
|
||||
kind: RecordFieldKind;
|
||||
[k: string]: unknown;
|
||||
}
|
||||
export interface NumericRange {
|
||||
kind: NumericRangeKind;
|
||||
min_width: number;
|
||||
signed: boolean;
|
||||
[k: string]: unknown;
|
||||
}
|
25
crates/compiler/checkmate/www/src/utils/events.ts
Normal file
25
crates/compiler/checkmate/www/src/utils/events.ts
Normal file
|
@ -0,0 +1,25 @@
|
|||
import { EventEpoch } from "../engine/engine";
|
||||
import { Variable } from "../schema";
|
||||
|
||||
export interface VariableMessage {
|
||||
focus: (variable: Variable) => void;
|
||||
}
|
||||
|
||||
export interface GraphMessage {
|
||||
focusEpoch: (epoch: EventEpoch) => void;
|
||||
focusVariable: (variable: Variable) => void;
|
||||
keydown: (key: string) => void;
|
||||
}
|
||||
|
||||
export interface EventListMessage {
|
||||
focusEpoch: (epoch: EventEpoch) => void;
|
||||
}
|
||||
|
||||
export enum LoadEpochView {
|
||||
Top,
|
||||
Bot,
|
||||
}
|
||||
|
||||
export interface GlobalMessage {
|
||||
loadEpoch: (epoch: EventEpoch, view: LoadEpochView) => void;
|
||||
}
|
3
crates/compiler/checkmate/www/src/utils/exhaustive.ts
Normal file
3
crates/compiler/checkmate/www/src/utils/exhaustive.ts
Normal file
|
@ -0,0 +1,3 @@
|
|||
export function assertExhaustive(_: never): never {
|
||||
throw new Error("Exhaustive switch");
|
||||
}
|
3
crates/compiler/checkmate/www/src/utils/refine.ts
Normal file
3
crates/compiler/checkmate/www/src/utils/refine.ts
Normal file
|
@ -0,0 +1,3 @@
|
|||
export type Refine<T extends { type: string }, Type extends string> = T & {
|
||||
type: Type;
|
||||
};
|
17
crates/compiler/checkmate/www/tailwind.config.js
Normal file
17
crates/compiler/checkmate/www/tailwind.config.js
Normal file
|
@ -0,0 +1,17 @@
|
|||
/** @type {import('tailwindcss').Config} */
|
||||
module.exports = {
|
||||
mode: 'jit',
|
||||
content: [
|
||||
"./src/**/*.{js,jsx,ts,tsx}",
|
||||
],
|
||||
theme: {
|
||||
extend: {
|
||||
colors: {
|
||||
'roc-purple': '#7c38f5',
|
||||
'roc-purple-bg': '#ece2fd',
|
||||
},
|
||||
},
|
||||
},
|
||||
plugins: [],
|
||||
}
|
||||
|
20
crates/compiler/checkmate/www/tsconfig.json
Normal file
20
crates/compiler/checkmate/www/tsconfig.json
Normal file
|
@ -0,0 +1,20 @@
|
|||
{
|
||||
"compilerOptions": {
|
||||
"target": "es2015",
|
||||
"lib": ["dom", "dom.iterable", "esnext"],
|
||||
"allowJs": true,
|
||||
"skipLibCheck": true,
|
||||
"esModuleInterop": true,
|
||||
"allowSyntheticDefaultImports": true,
|
||||
"strict": true,
|
||||
"forceConsistentCasingInFileNames": true,
|
||||
"noFallthroughCasesInSwitch": true,
|
||||
"module": "esnext",
|
||||
"moduleResolution": "node",
|
||||
"resolveJsonModule": true,
|
||||
"isolatedModules": true,
|
||||
"noEmit": true,
|
||||
"jsx": "react-jsx"
|
||||
},
|
||||
"include": ["src"]
|
||||
}
|
13
crates/compiler/checkmate_schema/Cargo.toml
Normal file
13
crates/compiler/checkmate_schema/Cargo.toml
Normal file
|
@ -0,0 +1,13 @@
|
|||
[package]
|
||||
name = "roc_checkmate_schema"
|
||||
description = "Schema for checkmate."
|
||||
|
||||
authors.workspace = true
|
||||
edition.workspace = true
|
||||
license.workspace = true
|
||||
version.workspace = true
|
||||
|
||||
[dependencies]
|
||||
serde.workspace = true
|
||||
serde_json.workspace = true
|
||||
schemars.workspace = true
|
225
crates/compiler/checkmate_schema/src/lib.rs
Normal file
225
crates/compiler/checkmate_schema/src/lib.rs
Normal file
|
@ -0,0 +1,225 @@
|
|||
use std::collections::HashMap;
|
||||
|
||||
use schemars::{schema::RootSchema, schema_for, JsonSchema};
|
||||
use serde::Serialize;
|
||||
|
||||
#[derive(Serialize, JsonSchema, Debug)]
|
||||
#[serde(tag = "type")]
|
||||
pub enum Constraint {}
|
||||
|
||||
#[derive(Serialize, JsonSchema, Debug, PartialEq)]
|
||||
pub struct Variable(pub u32);
|
||||
|
||||
macro_rules! impl_content {
|
||||
($($name:ident { $($arg:ident: $ty:ty,)* },)*) => {
|
||||
#[derive(Serialize, JsonSchema, Debug)]
|
||||
#[serde(tag = "type")]
|
||||
pub enum Content {
|
||||
$(
|
||||
$name {
|
||||
$($arg: $ty),*
|
||||
},
|
||||
)*
|
||||
}
|
||||
|
||||
impl Content {
|
||||
$(
|
||||
#[allow(non_snake_case)]
|
||||
pub fn $name($($arg: $ty),*) -> Self {
|
||||
Self::$name { $($arg),* }
|
||||
}
|
||||
)*
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
impl_content! {
|
||||
Flex {
|
||||
name: Option<String>,
|
||||
},
|
||||
Rigid {
|
||||
name: String,
|
||||
},
|
||||
FlexAble {
|
||||
name: Option<String>,
|
||||
abilities: Vec<Symbol>,
|
||||
},
|
||||
RigidAble {
|
||||
name: String,
|
||||
abilities: Vec<Symbol>,
|
||||
},
|
||||
Recursive {
|
||||
name: Option<String>,
|
||||
structure: Variable,
|
||||
},
|
||||
LambdaSet {
|
||||
solved: Vec<ClosureType>,
|
||||
unspecialized: Vec<UnspecializedClosureType>,
|
||||
recursion_var: Option<Variable>,
|
||||
ambient_function: Variable,
|
||||
},
|
||||
ErasedLambda {},
|
||||
Alias {
|
||||
name: Symbol,
|
||||
variables: AliasTypeVariables,
|
||||
real_variable: Variable,
|
||||
kind: AliasKind,
|
||||
},
|
||||
Apply {
|
||||
symbol: Symbol,
|
||||
variables: Vec<Variable>,
|
||||
},
|
||||
Function {
|
||||
arguments: Vec<Variable>,
|
||||
lambda_type: Variable,
|
||||
ret: Variable,
|
||||
},
|
||||
Record {
|
||||
fields: HashMap<String, RecordField>,
|
||||
extension: Variable,
|
||||
},
|
||||
Tuple {
|
||||
elements: HashMap<u32, Variable>,
|
||||
extension: Variable,
|
||||
},
|
||||
TagUnion {
|
||||
tags: HashMap<String, Vec<Variable>>,
|
||||
extension: TagUnionExtension,
|
||||
},
|
||||
FunctionOrTagUnion {
|
||||
functions: Vec<Symbol>,
|
||||
tags: Vec<String>,
|
||||
extension: TagUnionExtension,
|
||||
},
|
||||
RecursiveTagUnion {
|
||||
recursion_var: Variable,
|
||||
tags: HashMap<String, Vec<Variable>>,
|
||||
extension: TagUnionExtension,
|
||||
},
|
||||
EmptyRecord {},
|
||||
EmptyTuple {},
|
||||
EmptyTagUnion {},
|
||||
RangedNumber {
|
||||
range: NumericRange,
|
||||
},
|
||||
Error {},
|
||||
}
|
||||
|
||||
#[derive(Serialize, JsonSchema, Debug)]
|
||||
pub struct ClosureType {
|
||||
pub function: Symbol,
|
||||
pub environment: Vec<Variable>,
|
||||
}
|
||||
|
||||
#[derive(Serialize, JsonSchema, Debug)]
|
||||
pub struct UnspecializedClosureType {
|
||||
pub specialization: Variable,
|
||||
pub ability_member: Symbol,
|
||||
pub lambda_set_region: u8,
|
||||
}
|
||||
|
||||
#[derive(Serialize, JsonSchema, Debug)]
|
||||
#[serde(tag = "type")]
|
||||
pub enum AliasKind {
|
||||
Structural,
|
||||
Opaque,
|
||||
}
|
||||
|
||||
#[derive(Serialize, JsonSchema, Debug)]
|
||||
pub struct AliasTypeVariables {
|
||||
pub type_variables: Vec<Variable>,
|
||||
pub lambda_set_variables: Vec<Variable>,
|
||||
pub infer_ext_in_output_position_variables: Vec<Variable>,
|
||||
}
|
||||
|
||||
#[derive(Serialize, JsonSchema, Debug)]
|
||||
pub struct RecordField {
|
||||
pub kind: RecordFieldKind,
|
||||
pub field_type: Variable,
|
||||
}
|
||||
|
||||
#[derive(Serialize, JsonSchema, Debug)]
|
||||
#[serde(tag = "type")]
|
||||
pub enum RecordFieldKind {
|
||||
Demanded,
|
||||
Required { rigid: bool },
|
||||
Optional { rigid: bool },
|
||||
}
|
||||
|
||||
#[derive(Serialize, JsonSchema, Debug)]
|
||||
#[serde(tag = "type", content = "variable")]
|
||||
pub enum TagUnionExtension {
|
||||
Openness(Variable),
|
||||
Any(Variable),
|
||||
}
|
||||
|
||||
#[derive(Serialize, JsonSchema, Debug)]
|
||||
pub struct NumericRange {
|
||||
pub kind: NumericRangeKind,
|
||||
pub signed: bool,
|
||||
pub min_width: u32,
|
||||
}
|
||||
|
||||
#[derive(Serialize, JsonSchema, Debug)]
|
||||
#[serde(tag = "type")]
|
||||
pub enum NumericRangeKind {
|
||||
Int,
|
||||
AnyNum,
|
||||
}
|
||||
|
||||
#[derive(Serialize, JsonSchema, Debug)]
|
||||
pub struct Rank(pub u32);
|
||||
|
||||
#[derive(Serialize, JsonSchema, Debug)]
|
||||
pub struct Descriptor {
|
||||
pub content: Content,
|
||||
pub rank: Rank,
|
||||
}
|
||||
|
||||
#[derive(Serialize, JsonSchema, Debug)]
|
||||
pub struct Symbol(
|
||||
// TODO: should this be module ID + symbol?
|
||||
pub String,
|
||||
);
|
||||
|
||||
#[derive(Serialize, JsonSchema, Debug)]
|
||||
#[serde(tag = "type")]
|
||||
pub enum UnificationMode {
|
||||
Eq,
|
||||
Present,
|
||||
LambdaSetSpecialization,
|
||||
}
|
||||
|
||||
#[derive(Serialize, JsonSchema, Debug)]
|
||||
#[serde(tag = "type")]
|
||||
pub enum Event {
|
||||
Unification {
|
||||
left: Variable,
|
||||
right: Variable,
|
||||
mode: UnificationMode,
|
||||
success: Option<bool>,
|
||||
subevents: Vec<Event>,
|
||||
},
|
||||
VariableUnified {
|
||||
from: Variable,
|
||||
to: Variable,
|
||||
},
|
||||
VariableSetDescriptor {
|
||||
variable: Variable,
|
||||
rank: Option<Rank>,
|
||||
content: Option<Content>,
|
||||
},
|
||||
}
|
||||
|
||||
#[derive(Serialize, JsonSchema, Debug)]
|
||||
pub struct AllEvents(pub Vec<Event>);
|
||||
|
||||
impl AllEvents {
|
||||
pub fn schema() -> RootSchema {
|
||||
schema_for!(AllEvents)
|
||||
}
|
||||
|
||||
pub fn write(&self, writer: impl std::io::Write) -> Result<(), serde_json::Error> {
|
||||
serde_json::to_writer(writer, self)
|
||||
}
|
||||
}
|
|
@ -202,7 +202,7 @@ fn int_to_ordinal(number: usize) -> std::string::String {
|
|||
},
|
||||
};
|
||||
|
||||
format!("{}{}", number, ending)
|
||||
format!("{number}{ending}")
|
||||
}
|
||||
|
||||
#[macro_export]
|
||||
|
|
|
@ -48,9 +48,9 @@ enum Kind {
|
|||
impl Debug for Kind {
|
||||
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
|
||||
match self {
|
||||
Self::Generated(arg0) => write!(f, "Generated({})", arg0),
|
||||
Self::Generated(arg0) => write!(f, "Generated({arg0})"),
|
||||
Self::Empty => write!(f, "Empty"),
|
||||
Self::Interned(arg0) => write!(f, "Interned({})", arg0),
|
||||
Self::Interned(arg0) => write!(f, "Interned({arg0})"),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -155,7 +155,7 @@ impl SmallStringInterner {
|
|||
let index = self.lengths.len();
|
||||
|
||||
let offset = self.buffer.len();
|
||||
write!(self.buffer, "{}", index).unwrap();
|
||||
write!(self.buffer, "{index}").unwrap();
|
||||
|
||||
// this is a generated name, so store it as a negative length
|
||||
let length = Length(-((self.buffer.len() - offset) as i16));
|
||||
|
|
|
@ -34,6 +34,12 @@ impl<T: PartialEq> VecSet<T> {
|
|||
self.elements.is_empty()
|
||||
}
|
||||
|
||||
pub fn singleton(value: T) -> Self {
|
||||
Self {
|
||||
elements: vec![value],
|
||||
}
|
||||
}
|
||||
|
||||
pub fn swap_remove(&mut self, index: usize) -> T {
|
||||
self.elements.swap_remove(index)
|
||||
}
|
||||
|
|
|
@ -1044,8 +1044,7 @@ pub fn constrain_expr(
|
|||
|
||||
debug_assert!(
|
||||
intersection.is_empty(),
|
||||
"Two patterns introduce the same symbols - that's a bug!\n{:?}",
|
||||
intersection
|
||||
"Two patterns introduce the same symbols - that's a bug!\n{intersection:?}"
|
||||
);
|
||||
}
|
||||
|
||||
|
@ -2473,6 +2472,26 @@ fn constrain_empty_record(
|
|||
constraints.equal_types(record_type_index, expected, Category::Record, region)
|
||||
}
|
||||
|
||||
fn add_host_annotation(
|
||||
types: &mut Types,
|
||||
constraints: &mut Constraints,
|
||||
host_exposed_annotation: Option<&(Variable, roc_can::def::Annotation)>,
|
||||
constraint: Constraint,
|
||||
) -> Constraint {
|
||||
if let Some((var, ann)) = host_exposed_annotation {
|
||||
let host_annotation = {
|
||||
let type_index = types.from_old_type(&ann.signature);
|
||||
constraints.push_type(types, type_index)
|
||||
};
|
||||
|
||||
let store_constr = constraints.store(host_annotation, *var, file!(), line!());
|
||||
|
||||
constraints.and_constraint([store_constr, constraint])
|
||||
} else {
|
||||
constraint
|
||||
}
|
||||
}
|
||||
|
||||
/// Constrain top-level module declarations
|
||||
#[inline(always)]
|
||||
pub fn constrain_decls(
|
||||
|
@ -2499,6 +2518,7 @@ pub fn constrain_decls(
|
|||
|
||||
use roc_can::expr::DeclarationTag::*;
|
||||
let tag = declarations.declarations[index];
|
||||
|
||||
match tag {
|
||||
Value => {
|
||||
constraint = constrain_value_def(
|
||||
|
@ -2509,6 +2529,77 @@ pub fn constrain_decls(
|
|||
index,
|
||||
constraint,
|
||||
);
|
||||
|
||||
constraint = add_host_annotation(
|
||||
types,
|
||||
constraints,
|
||||
declarations.host_exposed_annotations.get(&index),
|
||||
constraint,
|
||||
);
|
||||
}
|
||||
Function(function_def_index) => {
|
||||
constraint = constrain_function_def(
|
||||
types,
|
||||
constraints,
|
||||
&mut env,
|
||||
declarations,
|
||||
index,
|
||||
function_def_index,
|
||||
constraint,
|
||||
);
|
||||
|
||||
constraint = add_host_annotation(
|
||||
types,
|
||||
constraints,
|
||||
declarations.host_exposed_annotations.get(&index),
|
||||
constraint,
|
||||
);
|
||||
}
|
||||
Recursive(_) | TailRecursive(_) => {
|
||||
constraint = add_host_annotation(
|
||||
types,
|
||||
constraints,
|
||||
declarations.host_exposed_annotations.get(&index),
|
||||
constraint,
|
||||
);
|
||||
|
||||
// for the type it does not matter that a recursive call is a tail call
|
||||
constraint = constrain_recursive_declarations(
|
||||
types,
|
||||
constraints,
|
||||
&mut env,
|
||||
declarations,
|
||||
index..index + 1,
|
||||
constraint,
|
||||
IllegalCycleMark::empty(),
|
||||
);
|
||||
}
|
||||
Destructure(destructure_def_index) => {
|
||||
constraint = constrain_destructure_def(
|
||||
types,
|
||||
constraints,
|
||||
&mut env,
|
||||
declarations,
|
||||
index,
|
||||
destructure_def_index,
|
||||
constraint,
|
||||
);
|
||||
}
|
||||
MutualRecursion { length, cycle_mark } => {
|
||||
// the next `length` defs belong to this group
|
||||
let length = length as usize;
|
||||
|
||||
constraint = constrain_recursive_declarations(
|
||||
types,
|
||||
constraints,
|
||||
&mut env,
|
||||
declarations,
|
||||
index + 1..index + 1 + length,
|
||||
constraint,
|
||||
cycle_mark,
|
||||
);
|
||||
|
||||
index += length;
|
||||
}
|
||||
Expectation => {
|
||||
let loc_expr = &declarations.expressions[index];
|
||||
|
@ -2566,56 +2657,6 @@ pub fn constrain_decls(
|
|||
Generalizable(false),
|
||||
)
|
||||
}
|
||||
Function(function_def_index) => {
|
||||
constraint = constrain_function_def(
|
||||
types,
|
||||
constraints,
|
||||
&mut env,
|
||||
declarations,
|
||||
index,
|
||||
function_def_index,
|
||||
constraint,
|
||||
);
|
||||
}
|
||||
Recursive(_) | TailRecursive(_) => {
|
||||
// for the type it does not matter that a recursive call is a tail call
|
||||
constraint = constrain_recursive_declarations(
|
||||
types,
|
||||
constraints,
|
||||
&mut env,
|
||||
declarations,
|
||||
index..index + 1,
|
||||
constraint,
|
||||
IllegalCycleMark::empty(),
|
||||
);
|
||||
}
|
||||
Destructure(destructure_def_index) => {
|
||||
constraint = constrain_destructure_def(
|
||||
types,
|
||||
constraints,
|
||||
&mut env,
|
||||
declarations,
|
||||
index,
|
||||
destructure_def_index,
|
||||
constraint,
|
||||
);
|
||||
}
|
||||
MutualRecursion { length, cycle_mark } => {
|
||||
// the next `length` defs belong to this group
|
||||
let length = length as usize;
|
||||
|
||||
constraint = constrain_recursive_declarations(
|
||||
types,
|
||||
constraints,
|
||||
&mut env,
|
||||
declarations,
|
||||
index + 1..index + 1 + length,
|
||||
constraint,
|
||||
cycle_mark,
|
||||
);
|
||||
|
||||
index += length;
|
||||
}
|
||||
}
|
||||
|
||||
index += 1;
|
||||
|
|
|
@ -92,6 +92,9 @@ flags! {
|
|||
/// Prints all type mismatches hit during type unification.
|
||||
ROC_PRINT_MISMATCHES
|
||||
|
||||
/// Prints all type variables entered for fixpoint-fixing.
|
||||
ROC_PRINT_FIXPOINT_FIXING
|
||||
|
||||
/// Verifies that after let-generalization of a def, any rigid variables in the type annotation
|
||||
/// of the def are indeed generalized.
|
||||
///
|
||||
|
@ -132,6 +135,10 @@ flags! {
|
|||
/// instructions.
|
||||
ROC_PRINT_IR_AFTER_REFCOUNT
|
||||
|
||||
/// Writes a pretty-printed mono IR to stderr after the tail recursion (modulo cons)
|
||||
/// has been applied.
|
||||
ROC_PRINT_IR_AFTER_TRMC
|
||||
|
||||
/// Writes a pretty-printed mono IR to stderr after performing dropspecialization.
|
||||
/// Which inlines drop functions to remove pairs of alloc/dealloc instructions of its children.
|
||||
ROC_PRINT_IR_AFTER_DROP_SPECIALIZATION
|
||||
|
|
|
@ -9,11 +9,13 @@ version.workspace = true
|
|||
|
||||
[dependencies]
|
||||
roc_can = { path = "../can" }
|
||||
roc_checkmate = { path = "../checkmate" }
|
||||
roc_collections = { path = "../collections" }
|
||||
roc_derive_key = { path = "../derive_key" }
|
||||
roc_error_macros = { path = "../../error_macros" }
|
||||
roc_module = { path = "../module" }
|
||||
roc_region = { path = "../region" }
|
||||
roc_solve_schema = { path = "../solve_schema" }
|
||||
roc_types = { path = "../types" }
|
||||
roc_unify = { path = "../unify" }
|
||||
|
||||
|
|
|
@ -298,7 +298,7 @@ fn hash_tag_union(
|
|||
Expr::Int(
|
||||
discr_num_var,
|
||||
discr_precision_var,
|
||||
format!("{}", discr_n).into_boxed_str(),
|
||||
format!("{discr_n}").into_boxed_str(),
|
||||
IntValue::I128((discr_n as i128).to_ne_bytes()),
|
||||
IntBound::Exact(discr_width),
|
||||
),
|
||||
|
|
|
@ -1,6 +1,8 @@
|
|||
use roc_can::{abilities::SpecializationLambdaSets, module::ExposedByModule};
|
||||
use roc_checkmate::with_checkmate;
|
||||
use roc_error_macros::internal_error;
|
||||
use roc_module::symbol::{IdentIds, Symbol};
|
||||
use roc_solve_schema::UnificationMode;
|
||||
use roc_types::{
|
||||
subs::{instantiate_rigids, Subs, Variable},
|
||||
types::Polarity,
|
||||
|
@ -31,7 +33,7 @@ impl Env<'_> {
|
|||
let name = if i == 1 {
|
||||
hint.clone()
|
||||
} else {
|
||||
format!("{}{}", hint, i)
|
||||
format!("{hint}{i}")
|
||||
};
|
||||
if self.derived_ident_ids.get_id(&name).is_none() {
|
||||
break name;
|
||||
|
@ -71,13 +73,20 @@ impl Env<'_> {
|
|||
}
|
||||
|
||||
pub fn unify(&mut self, left: Variable, right: Variable) {
|
||||
use roc_unify::unify::{unify, Env, Mode, Unified};
|
||||
use roc_unify::{
|
||||
unify::{unify, Unified},
|
||||
Env,
|
||||
};
|
||||
|
||||
let unified = unify(
|
||||
&mut Env::new(self.subs),
|
||||
// TODO(checkmate): pass checkmate through
|
||||
&mut with_checkmate!({
|
||||
on => Env::new(self.subs, None),
|
||||
off => Env::new(self.subs),
|
||||
}),
|
||||
left,
|
||||
right,
|
||||
Mode::EQ,
|
||||
UnificationMode::EQ,
|
||||
Polarity::OF_PATTERN,
|
||||
);
|
||||
|
||||
|
@ -103,15 +112,22 @@ impl Env<'_> {
|
|||
specialization_type: Variable,
|
||||
ability_member: Symbol,
|
||||
) -> SpecializationLambdaSets {
|
||||
use roc_unify::unify::{unify_introduced_ability_specialization, Env, Mode, Unified};
|
||||
use roc_unify::{
|
||||
unify::{unify_introduced_ability_specialization, Unified},
|
||||
Env,
|
||||
};
|
||||
|
||||
let member_signature = self.import_builtin_symbol_var(ability_member);
|
||||
|
||||
let unified = unify_introduced_ability_specialization(
|
||||
&mut Env::new(self.subs),
|
||||
// TODO(checkmate): pass checkmate through
|
||||
&mut with_checkmate!({
|
||||
on => Env::new(self.subs, None),
|
||||
off => Env::new(self.subs),
|
||||
}),
|
||||
member_signature,
|
||||
specialization_type,
|
||||
Mode::EQ,
|
||||
UnificationMode::EQ,
|
||||
);
|
||||
|
||||
match unified {
|
||||
|
@ -151,7 +167,7 @@ impl Env<'_> {
|
|||
== self.subs.get_root_key_without_compacting(lambda_set)
|
||||
});
|
||||
debug_assert!(belongs_to_specialized_lambda_sets,
|
||||
"Did not expect derivers to need to specialize unspecialized lambda sets, but we got one: {:?} for {:?}", lambda_set, spec_var)
|
||||
"Did not expect derivers to need to specialize unspecialized lambda sets, but we got one: {lambda_set:?} for {spec_var:?}")
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -103,7 +103,7 @@ impl FlatDecodable {
|
|||
| Content::RigidVar(_)
|
||||
| Content::FlexAbleVar(_, _)
|
||||
| Content::RigidAbleVar(_, _) => Err(UnboundVar),
|
||||
Content::LambdaSet(_) => Err(Underivable),
|
||||
Content::LambdaSet(_) | Content::ErasedLambda => Err(Underivable),
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -137,7 +137,7 @@ impl FlatEncodable {
|
|||
| Content::RigidVar(_)
|
||||
| Content::FlexAbleVar(_, _)
|
||||
| Content::RigidAbleVar(_, _) => Err(UnboundVar),
|
||||
Content::LambdaSet(_) => Err(Underivable),
|
||||
Content::LambdaSet(_) | Content::ErasedLambda => Err(Underivable),
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -148,7 +148,7 @@ impl FlatHash {
|
|||
| Content::RigidVar(_)
|
||||
| Content::FlexAbleVar(_, _)
|
||||
| Content::RigidAbleVar(_, _) => Err(UnboundVar),
|
||||
Content::LambdaSet(_) => Err(Underivable),
|
||||
Content::LambdaSet(_) | Content::ErasedLambda => Err(Underivable),
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -241,7 +241,7 @@ fn is_exhaustive(matrix: &RefPatternMatrix, n: usize) -> PatternMatrix {
|
|||
.collect();
|
||||
let rest: Vec<Vec<Pattern>> = is_exhaustive(&new_matrix, n - 1);
|
||||
|
||||
let last: _ = alt_list
|
||||
let last = alt_list
|
||||
.iter()
|
||||
.filter_map(|r| is_missing(alts.clone(), &ctors, r));
|
||||
|
||||
|
|
|
@ -117,7 +117,7 @@ impl<'a> Formattable for TypeDef<'a> {
|
|||
buf,
|
||||
Parens::NotNeeded,
|
||||
Newlines::from_bool(make_multiline),
|
||||
indent + 1 + INDENT,
|
||||
indent + INDENT,
|
||||
);
|
||||
}
|
||||
}
|
||||
|
|
|
@ -455,6 +455,18 @@ impl CallConv<AArch64GeneralReg, AArch64FloatReg, AArch64Assembler> for AArch64C
|
|||
) {
|
||||
todo!("Loading returned complex symbols for AArch64");
|
||||
}
|
||||
|
||||
fn setjmp(_buf: &mut Vec<'_, u8>) {
|
||||
todo!()
|
||||
}
|
||||
|
||||
fn longjmp(_buf: &mut Vec<'_, u8>) {
|
||||
todo!()
|
||||
}
|
||||
|
||||
fn roc_panic(_buf: &mut Vec<'_, u8>, _relocs: &mut Vec<'_, Relocation>) {
|
||||
todo!()
|
||||
}
|
||||
}
|
||||
|
||||
impl Assembler<AArch64GeneralReg, AArch64FloatReg> for AArch64Assembler {
|
||||
|
@ -529,7 +541,17 @@ impl Assembler<AArch64GeneralReg, AArch64FloatReg> for AArch64Assembler {
|
|||
_fn_name: String,
|
||||
_dst: AArch64GeneralReg,
|
||||
) {
|
||||
todo!("calling functions literal for AArch64");
|
||||
todo!("function pointer for AArch64");
|
||||
}
|
||||
|
||||
#[inline(always)]
|
||||
fn data_pointer(
|
||||
_buf: &mut Vec<'_, u8>,
|
||||
_relocs: &mut Vec<'_, Relocation>,
|
||||
_fn_name: String,
|
||||
_dst: AArch64GeneralReg,
|
||||
) {
|
||||
todo!("data pointer for AArch64");
|
||||
}
|
||||
|
||||
#[inline(always)]
|
||||
|
@ -832,6 +854,10 @@ impl Assembler<AArch64GeneralReg, AArch64FloatReg> for AArch64Assembler {
|
|||
todo!("saving floating point reg to base offset for AArch64");
|
||||
}
|
||||
#[inline(always)]
|
||||
fn mov_base32_freg32(_buf: &mut Vec<'_, u8>, _offset: i32, _src: AArch64FloatReg) {
|
||||
todo!("saving floating point reg to base offset for AArch64");
|
||||
}
|
||||
#[inline(always)]
|
||||
fn movesd_mem64_offset32_freg64(
|
||||
_buf: &mut Vec<'_, u8>,
|
||||
_ptr: AArch64GeneralReg,
|
||||
|
@ -965,33 +991,32 @@ impl Assembler<AArch64GeneralReg, AArch64FloatReg> for AArch64Assembler {
|
|||
}
|
||||
|
||||
#[inline(always)]
|
||||
fn movsx_reg64_base32(buf: &mut Vec<'_, u8>, dst: AArch64GeneralReg, offset: i32, size: u8) {
|
||||
debug_assert!(size <= 8);
|
||||
if size == 8 {
|
||||
Self::mov_reg64_base32(buf, dst, offset);
|
||||
} else if size == 4 {
|
||||
todo!("sign extending 4 byte values");
|
||||
} else if size == 2 {
|
||||
todo!("sign extending 2 byte values");
|
||||
} else if size == 1 {
|
||||
todo!("sign extending 1 byte values");
|
||||
} else {
|
||||
internal_error!("Invalid size for sign extension: {}", size);
|
||||
fn movsx_reg_base32(
|
||||
buf: &mut Vec<'_, u8>,
|
||||
register_width: RegisterWidth,
|
||||
dst: AArch64GeneralReg,
|
||||
offset: i32,
|
||||
) {
|
||||
match register_width {
|
||||
RegisterWidth::W8 => todo!("sign extend 1 byte values"),
|
||||
RegisterWidth::W16 => todo!("sign extend 2 byte values"),
|
||||
RegisterWidth::W32 => todo!("sign extend 4 byte values"),
|
||||
RegisterWidth::W64 => Self::mov_reg64_base32(buf, dst, offset),
|
||||
}
|
||||
}
|
||||
|
||||
#[inline(always)]
|
||||
fn movzx_reg64_base32(buf: &mut Vec<'_, u8>, dst: AArch64GeneralReg, offset: i32, size: u8) {
|
||||
debug_assert!(size <= 8);
|
||||
if size == 8 {
|
||||
Self::mov_reg64_base32(buf, dst, offset);
|
||||
} else if size == 4 {
|
||||
todo!("zero extending 4 byte values");
|
||||
} else if size == 2 {
|
||||
todo!("zero extending 2 byte values");
|
||||
} else if size == 1 {
|
||||
todo!("zero extending 1 byte values");
|
||||
} else {
|
||||
internal_error!("Invalid size for zero extension: {}", size);
|
||||
fn movzx_reg_base32(
|
||||
buf: &mut Vec<'_, u8>,
|
||||
register_width: RegisterWidth,
|
||||
dst: AArch64GeneralReg,
|
||||
offset: i32,
|
||||
) {
|
||||
match register_width {
|
||||
RegisterWidth::W8 => todo!("zero extend 1 byte values"),
|
||||
RegisterWidth::W16 => todo!("zero extend 2 byte values"),
|
||||
RegisterWidth::W32 => todo!("zero extend 4 byte values"),
|
||||
RegisterWidth::W64 => Self::mov_reg64_base32(buf, dst, offset),
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -3161,7 +3186,7 @@ mod tests {
|
|||
UsesZR => "xzr".to_owned(),
|
||||
UsesSP => "sp".to_owned(),
|
||||
},
|
||||
_ => format!("{}", self),
|
||||
_ => format!("{self}"),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -5,7 +5,7 @@ use crate::{
|
|||
use bumpalo::collections::{CollectIn, Vec};
|
||||
use roc_builtins::bitcode::{self, FloatWidth, IntWidth};
|
||||
use roc_collections::all::MutMap;
|
||||
use roc_error_macros::internal_error;
|
||||
use roc_error_macros::{internal_error, todo_lambda_erasure};
|
||||
use roc_module::symbol::{Interns, ModuleId, Symbol};
|
||||
use roc_mono::code_gen_help::{CallerProc, CodeGenHelp, HelperOp};
|
||||
use roc_mono::ir::{
|
||||
|
@ -13,7 +13,7 @@ use roc_mono::ir::{
|
|||
SelfRecursive, Stmt,
|
||||
};
|
||||
use roc_mono::layout::{
|
||||
Builtin, InLayout, Layout, LayoutIds, LayoutInterner, LayoutRepr, STLayoutInterner,
|
||||
Builtin, InLayout, LambdaName, Layout, LayoutIds, LayoutInterner, LayoutRepr, STLayoutInterner,
|
||||
TagIdIntType, UnionLayout,
|
||||
};
|
||||
use roc_mono::low_level::HigherOrder;
|
||||
|
@ -134,6 +134,10 @@ pub trait CallConv<GeneralReg: RegTrait, FloatReg: RegTrait, ASM: Assembler<Gene
|
|||
sym: &Symbol,
|
||||
layout: &InLayout<'a>,
|
||||
);
|
||||
|
||||
fn setjmp(buf: &mut Vec<'_, u8>);
|
||||
fn longjmp(buf: &mut Vec<'_, u8>);
|
||||
fn roc_panic(buf: &mut Vec<'_, u8>, relocs: &mut Vec<'_, Relocation>);
|
||||
}
|
||||
|
||||
pub enum CompareOperation {
|
||||
|
@ -238,6 +242,13 @@ pub trait Assembler<GeneralReg: RegTrait, FloatReg: RegTrait>: Sized + Copy {
|
|||
dst: GeneralReg,
|
||||
);
|
||||
|
||||
fn data_pointer(
|
||||
buf: &mut Vec<'_, u8>,
|
||||
relocs: &mut Vec<'_, Relocation>,
|
||||
fn_name: String,
|
||||
dst: GeneralReg,
|
||||
);
|
||||
|
||||
/// Jumps by an offset of offset bytes unconditionally.
|
||||
/// It should always generate the same number of bytes to enable replacement if offset changes.
|
||||
/// It returns the base offset to calculate the jump from (generally the instruction after the jump).
|
||||
|
@ -321,6 +332,7 @@ pub trait Assembler<GeneralReg: RegTrait, FloatReg: RegTrait>: Sized + Copy {
|
|||
fn mov_reg8_base32(buf: &mut Vec<'_, u8>, dst: GeneralReg, offset: i32);
|
||||
|
||||
fn mov_base32_freg64(buf: &mut Vec<'_, u8>, offset: i32, src: FloatReg);
|
||||
fn mov_base32_freg32(buf: &mut Vec<'_, u8>, offset: i32, src: FloatReg);
|
||||
|
||||
fn mov_base32_reg64(buf: &mut Vec<'_, u8>, offset: i32, src: GeneralReg);
|
||||
fn mov_base32_reg32(buf: &mut Vec<'_, u8>, offset: i32, src: GeneralReg);
|
||||
|
@ -391,10 +403,19 @@ pub trait Assembler<GeneralReg: RegTrait, FloatReg: RegTrait>: Sized + Copy {
|
|||
|
||||
/// Sign extends the data at `offset` with `size` as it copies it to `dst`
|
||||
/// size must be less than or equal to 8.
|
||||
fn movsx_reg64_base32(buf: &mut Vec<'_, u8>, dst: GeneralReg, offset: i32, size: u8);
|
||||
/// Zero extends the data at `offset` with `size` as it copies it to `dst`
|
||||
/// size must be less than or equal to 8.
|
||||
fn movzx_reg64_base32(buf: &mut Vec<'_, u8>, dst: GeneralReg, offset: i32, size: u8);
|
||||
fn movsx_reg_base32(
|
||||
buf: &mut Vec<'_, u8>,
|
||||
register_width: RegisterWidth,
|
||||
dst: GeneralReg,
|
||||
offset: i32,
|
||||
);
|
||||
|
||||
fn movzx_reg_base32(
|
||||
buf: &mut Vec<'_, u8>,
|
||||
register_width: RegisterWidth,
|
||||
dst: GeneralReg,
|
||||
offset: i32,
|
||||
);
|
||||
|
||||
fn mov_freg64_stack32(buf: &mut Vec<'_, u8>, dst: FloatReg, offset: i32);
|
||||
fn mov_reg64_stack32(buf: &mut Vec<'_, u8>, dst: GeneralReg, offset: i32);
|
||||
|
@ -704,6 +725,9 @@ impl<
|
|||
fn interner(&self) -> &STLayoutInterner<'a> {
|
||||
self.layout_interner
|
||||
}
|
||||
fn relocations_mut(&mut self) -> &mut Vec<'a, Relocation> {
|
||||
&mut self.relocs
|
||||
}
|
||||
fn module_interns_helpers_mut(
|
||||
&mut self,
|
||||
) -> (
|
||||
|
@ -877,12 +901,47 @@ impl<
|
|||
(out.into_bump_slice(), offset)
|
||||
}
|
||||
|
||||
fn build_roc_setjmp(&mut self) -> &'a [u8] {
|
||||
let mut out = bumpalo::vec![in self.env.arena];
|
||||
|
||||
CC::setjmp(&mut out);
|
||||
|
||||
out.into_bump_slice()
|
||||
}
|
||||
|
||||
fn build_roc_longjmp(&mut self) -> &'a [u8] {
|
||||
let mut out = bumpalo::vec![in self.env.arena];
|
||||
|
||||
CC::longjmp(&mut out);
|
||||
|
||||
out.into_bump_slice()
|
||||
}
|
||||
|
||||
fn build_roc_panic(&mut self) -> (&'a [u8], Vec<'a, Relocation>) {
|
||||
let mut out = bumpalo::vec![in self.env.arena];
|
||||
let mut relocs = bumpalo::vec![in self.env.arena];
|
||||
|
||||
CC::roc_panic(&mut out, &mut relocs);
|
||||
|
||||
(out.into_bump_slice(), relocs)
|
||||
}
|
||||
|
||||
fn build_fn_pointer(&mut self, dst: &Symbol, fn_name: String) {
|
||||
let reg = self.storage_manager.claim_general_reg(&mut self.buf, dst);
|
||||
|
||||
ASM::function_pointer(&mut self.buf, &mut self.relocs, fn_name, reg)
|
||||
}
|
||||
|
||||
fn build_data_pointer(&mut self, dst: &Symbol, data_name: String) {
|
||||
let reg = self.storage_manager.claim_general_reg(&mut self.buf, dst);
|
||||
|
||||
// now, this gives a pointer to the value
|
||||
ASM::data_pointer(&mut self.buf, &mut self.relocs, data_name, reg);
|
||||
|
||||
// dereference
|
||||
ASM::mov_reg64_mem64_offset32(&mut self.buf, reg, reg, 0);
|
||||
}
|
||||
|
||||
fn build_fn_call(
|
||||
&mut self,
|
||||
dst: &Symbol,
|
||||
|
@ -926,7 +985,8 @@ impl<
|
|||
let dst_reg = self.storage_manager.claim_float_reg(&mut self.buf, dst);
|
||||
ASM::mov_freg64_freg64(&mut self.buf, dst_reg, CC::FLOAT_RETURN_REGS[0]);
|
||||
}
|
||||
LayoutRepr::I128 | LayoutRepr::U128 => {
|
||||
// Note that on windows there is only 1 general return register so we can't use this optimisation
|
||||
LayoutRepr::I128 | LayoutRepr::U128 if CC::GENERAL_RETURN_REGS.len() > 1 => {
|
||||
let offset = self.storage_manager.claim_stack_area(dst, 16);
|
||||
|
||||
ASM::mov_base32_reg64(&mut self.buf, offset, CC::GENERAL_RETURN_REGS[0]);
|
||||
|
@ -1263,6 +1323,18 @@ impl<
|
|||
}
|
||||
|
||||
fn build_num_mul(&mut self, dst: &Symbol, src1: &Symbol, src2: &Symbol, layout: &InLayout<'a>) {
|
||||
// for the time being, `num_mul` is implemented as wrapping multiplication. In roc, the normal
|
||||
// `mul` should panic on overflow, but we just don't do that yet
|
||||
self.build_num_mul_wrap(dst, src1, src2, layout)
|
||||
}
|
||||
|
||||
fn build_num_mul_wrap(
|
||||
&mut self,
|
||||
dst: &Symbol,
|
||||
src1: &Symbol,
|
||||
src2: &Symbol,
|
||||
layout: &InLayout<'a>,
|
||||
) {
|
||||
use Builtin::Int;
|
||||
|
||||
match self.layout_interner.get_repr(*layout) {
|
||||
|
@ -1324,7 +1396,7 @@ impl<
|
|||
let src2_reg = self.storage_manager.load_to_float_reg(&mut self.buf, src2);
|
||||
ASM::mul_freg32_freg32_freg32(&mut self.buf, dst_reg, src1_reg, src2_reg);
|
||||
}
|
||||
x => todo!("NumMul: layout, {:?}", x),
|
||||
x => todo!("NumMulWrap: layout, {:?}", x),
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -1578,8 +1650,8 @@ impl<
|
|||
HelperOp::Eq,
|
||||
);
|
||||
|
||||
let fn_name = self.function_symbol_to_string(
|
||||
eq_symbol,
|
||||
let fn_name = self.lambda_name_to_string(
|
||||
LambdaName::no_niche(eq_symbol),
|
||||
[*arg_layout, *arg_layout].into_iter(),
|
||||
None,
|
||||
Layout::U8,
|
||||
|
@ -1803,6 +1875,34 @@ impl<
|
|||
);
|
||||
}
|
||||
|
||||
fn build_num_cmp(
|
||||
&mut self,
|
||||
dst: &Symbol,
|
||||
src1: &Symbol,
|
||||
src2: &Symbol,
|
||||
arg_layout: &InLayout<'a>,
|
||||
) {
|
||||
// This implements the expression:
|
||||
// (x != y) as u8 + (x < y) as u8
|
||||
// For x==y: (false as u8) + (false as u8) = 0 = RocOrder::Eq
|
||||
// For x>y: (true as u8) + (false as u8) = 1 = RocOrder::Gt
|
||||
// For x<y: (true as u8) + (true as u8) = 2 = RocOrder::Lt
|
||||
// u8 is represented in the stack machine as i32, but written to memory as 1 byte
|
||||
let not_equal = self.debug_symbol("not_equal");
|
||||
|
||||
self.build_neq(¬_equal, src1, src2, arg_layout);
|
||||
self.build_num_lt(dst, src1, src2, arg_layout);
|
||||
|
||||
let neq_reg = self
|
||||
.storage_manager
|
||||
.load_to_general_reg(&mut self.buf, ¬_equal);
|
||||
let dst_reg = self.storage_manager.load_to_general_reg(&mut self.buf, dst);
|
||||
|
||||
ASM::add_reg64_reg64_reg64(&mut self.buf, dst_reg, dst_reg, neq_reg);
|
||||
|
||||
self.free_symbol(¬_equal);
|
||||
}
|
||||
|
||||
fn build_num_lt(
|
||||
&mut self,
|
||||
dst: &Symbol,
|
||||
|
@ -1935,8 +2035,8 @@ impl<
|
|||
self.load_layout_stack_size(old_element_layout, old_element_width);
|
||||
self.load_layout_stack_size(new_element_layout, new_element_width);
|
||||
|
||||
let caller_string = self.function_symbol_to_string(
|
||||
caller_proc.proc_symbol,
|
||||
let caller_string = self.lambda_name_to_string(
|
||||
LambdaName::no_niche(caller_proc.proc_symbol),
|
||||
std::iter::empty(),
|
||||
None,
|
||||
Layout::UNIT,
|
||||
|
@ -2742,14 +2842,7 @@ impl<
|
|||
.storage_manager
|
||||
.load_to_general_reg(&mut self.buf, structure);
|
||||
|
||||
let mask_symbol = self.debug_symbol("tag_id_mask");
|
||||
let mask_reg = self
|
||||
.storage_manager
|
||||
.claim_general_reg(&mut self.buf, &mask_symbol);
|
||||
ASM::mov_reg64_imm64(&mut self.buf, mask_reg, (!0b111) as _);
|
||||
|
||||
// mask out the tag id bits
|
||||
ASM::and_reg64_reg64_reg64(&mut self.buf, ptr_reg, ptr_reg, mask_reg);
|
||||
let (mask_symbol, mask_reg) = self.clear_tag_id(ptr_reg);
|
||||
|
||||
let mut offset = 0;
|
||||
for field in &other_fields[..index as usize] {
|
||||
|
@ -2760,11 +2853,13 @@ impl<
|
|||
&mut self.buf,
|
||||
&mut self.storage_manager,
|
||||
self.layout_interner,
|
||||
ptr_reg,
|
||||
mask_reg,
|
||||
offset as i32,
|
||||
element_layout,
|
||||
*sym,
|
||||
);
|
||||
|
||||
self.free_symbol(&mask_symbol)
|
||||
}
|
||||
UnionLayout::Recursive(tag_layouts) => {
|
||||
let other_fields = tag_layouts[tag_id as usize];
|
||||
|
@ -2775,15 +2870,13 @@ impl<
|
|||
.load_to_general_reg(&mut self.buf, structure);
|
||||
|
||||
// mask out the tag id bits
|
||||
if !union_layout.stores_tag_id_as_data(self.storage_manager.target_info) {
|
||||
let mask_symbol = self.debug_symbol("tag_id_mask");
|
||||
let mask_reg = self
|
||||
.storage_manager
|
||||
.claim_general_reg(&mut self.buf, &mask_symbol);
|
||||
ASM::mov_reg64_imm64(&mut self.buf, mask_reg, (!0b111) as _);
|
||||
|
||||
ASM::and_reg64_reg64_reg64(&mut self.buf, ptr_reg, ptr_reg, mask_reg);
|
||||
}
|
||||
let (unmasked_symbol, unmasked_reg) =
|
||||
if union_layout.stores_tag_id_as_data(self.storage_manager.target_info) {
|
||||
(None, ptr_reg)
|
||||
} else {
|
||||
let (mask_symbol, mask_reg) = self.clear_tag_id(ptr_reg);
|
||||
(Some(mask_symbol), mask_reg)
|
||||
};
|
||||
|
||||
let mut offset = 0;
|
||||
for field in &other_fields[..index as usize] {
|
||||
|
@ -2794,16 +2887,113 @@ impl<
|
|||
&mut self.buf,
|
||||
&mut self.storage_manager,
|
||||
self.layout_interner,
|
||||
ptr_reg,
|
||||
unmasked_reg,
|
||||
offset as i32,
|
||||
element_layout,
|
||||
*sym,
|
||||
);
|
||||
|
||||
if let Some(unmasked_symbol) = unmasked_symbol {
|
||||
self.free_symbol(&unmasked_symbol);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fn build_ptr_write(
|
||||
fn load_union_field_ptr_at_index(
|
||||
&mut self,
|
||||
sym: &Symbol,
|
||||
structure: &Symbol,
|
||||
tag_id: TagIdIntType,
|
||||
index: u64,
|
||||
union_layout: &UnionLayout<'a>,
|
||||
) {
|
||||
let ptr_reg = self
|
||||
.storage_manager
|
||||
.load_to_general_reg(&mut self.buf, structure);
|
||||
|
||||
let sym_reg = self.storage_manager.claim_general_reg(&mut self.buf, sym);
|
||||
|
||||
match union_layout {
|
||||
UnionLayout::NonRecursive(_) => {
|
||||
unreachable!("operation not supported")
|
||||
}
|
||||
UnionLayout::NonNullableUnwrapped(field_layouts) => {
|
||||
let mut offset = 0;
|
||||
for field in &field_layouts[..index as usize] {
|
||||
offset += self.layout_interner.stack_size(*field);
|
||||
}
|
||||
|
||||
ASM::add_reg64_reg64_imm32(&mut self.buf, sym_reg, ptr_reg, offset as i32);
|
||||
}
|
||||
UnionLayout::NullableUnwrapped {
|
||||
nullable_id,
|
||||
other_fields,
|
||||
} => {
|
||||
debug_assert_ne!(tag_id, *nullable_id as TagIdIntType);
|
||||
|
||||
let mut offset = 0;
|
||||
for field in &other_fields[..index as usize] {
|
||||
offset += self.layout_interner.stack_size(*field);
|
||||
}
|
||||
|
||||
ASM::add_reg64_reg64_imm32(&mut self.buf, sym_reg, ptr_reg, offset as i32);
|
||||
}
|
||||
|
||||
UnionLayout::NullableWrapped {
|
||||
nullable_id,
|
||||
other_tags,
|
||||
} => {
|
||||
debug_assert_ne!(tag_id, *nullable_id as TagIdIntType);
|
||||
|
||||
let other_fields = if tag_id < *nullable_id {
|
||||
other_tags[tag_id as usize]
|
||||
} else {
|
||||
other_tags[tag_id as usize - 1]
|
||||
};
|
||||
|
||||
let (mask_symbol, mask_reg) = self.clear_tag_id(ptr_reg);
|
||||
|
||||
let mut offset = 0;
|
||||
for field in &other_fields[..index as usize] {
|
||||
offset += self.layout_interner.stack_size(*field);
|
||||
}
|
||||
|
||||
ASM::add_reg64_reg64_imm32(&mut self.buf, sym_reg, mask_reg, offset as i32);
|
||||
|
||||
self.free_symbol(&mask_symbol);
|
||||
}
|
||||
UnionLayout::Recursive(tag_layouts) => {
|
||||
let other_fields = tag_layouts[tag_id as usize];
|
||||
|
||||
let ptr_reg = self
|
||||
.storage_manager
|
||||
.load_to_general_reg(&mut self.buf, structure);
|
||||
|
||||
// mask out the tag id bits
|
||||
let (unmasked_symbol, unmasked_reg) =
|
||||
if union_layout.stores_tag_id_as_data(self.storage_manager.target_info) {
|
||||
(None, ptr_reg)
|
||||
} else {
|
||||
let (mask_symbol, mask_reg) = self.clear_tag_id(ptr_reg);
|
||||
(Some(mask_symbol), mask_reg)
|
||||
};
|
||||
|
||||
let mut offset = 0;
|
||||
for field in &other_fields[..index as usize] {
|
||||
offset += self.layout_interner.stack_size(*field);
|
||||
}
|
||||
|
||||
ASM::add_reg64_reg64_imm32(&mut self.buf, sym_reg, unmasked_reg, offset as i32);
|
||||
|
||||
if let Some(unmasked_symbol) = unmasked_symbol {
|
||||
self.free_symbol(&unmasked_symbol);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fn build_ptr_store(
|
||||
&mut self,
|
||||
sym: Symbol,
|
||||
ptr: Symbol,
|
||||
|
@ -2839,6 +3029,63 @@ impl<
|
|||
ASM::mov_base32_reg64(&mut self.buf, base_offset, ptr_reg);
|
||||
}
|
||||
|
||||
fn build_ptr_load(&mut self, sym: Symbol, ptr: Symbol, element_layout: InLayout<'a>) {
|
||||
let ptr_reg = self
|
||||
.storage_manager
|
||||
.load_to_general_reg(&mut self.buf, &ptr);
|
||||
|
||||
let offset = 0;
|
||||
|
||||
Self::ptr_read(
|
||||
&mut self.buf,
|
||||
&mut self.storage_manager,
|
||||
self.layout_interner,
|
||||
ptr_reg,
|
||||
offset,
|
||||
element_layout,
|
||||
sym,
|
||||
);
|
||||
}
|
||||
|
||||
fn build_ptr_clear_tag_id(&mut self, sym: Symbol, ptr: Symbol) {
|
||||
let buf = &mut self.buf;
|
||||
|
||||
let ptr_reg = self.storage_manager.load_to_general_reg(buf, &ptr);
|
||||
let sym_reg = self.storage_manager.claim_general_reg(buf, &sym);
|
||||
|
||||
ASM::mov_reg64_imm64(buf, sym_reg, !0b111);
|
||||
ASM::and_reg64_reg64_reg64(buf, sym_reg, sym_reg, ptr_reg);
|
||||
}
|
||||
|
||||
fn build_alloca(&mut self, sym: Symbol, value: Option<Symbol>, element_layout: InLayout<'a>) {
|
||||
// 1. acquire some stack space
|
||||
let element_width = self.interner().stack_size(element_layout);
|
||||
let allocation = self.debug_symbol("stack_allocation");
|
||||
let ptr = self.debug_symbol("ptr");
|
||||
|
||||
if element_width == 0 {
|
||||
self.storage_manager.claim_pointer_stack_area(sym);
|
||||
return;
|
||||
}
|
||||
|
||||
let base_offset = self
|
||||
.storage_manager
|
||||
.claim_stack_area(&allocation, element_width);
|
||||
|
||||
let ptr_reg = self.storage_manager.claim_general_reg(&mut self.buf, &ptr);
|
||||
|
||||
ASM::mov_reg64_reg64(&mut self.buf, ptr_reg, CC::BASE_PTR_REG);
|
||||
ASM::add_reg64_reg64_imm32(&mut self.buf, ptr_reg, ptr_reg, base_offset);
|
||||
|
||||
if let Some(value) = value {
|
||||
self.build_ptr_store(sym, ptr, value, element_layout);
|
||||
} else {
|
||||
// this is now a pointer to uninitialized memory!
|
||||
let r = self.storage_manager.claim_general_reg(&mut self.buf, &sym);
|
||||
ASM::mov_reg64_reg64(&mut self.buf, r, ptr_reg);
|
||||
}
|
||||
}
|
||||
|
||||
fn expr_box(
|
||||
&mut self,
|
||||
sym: Symbol,
|
||||
|
@ -2871,27 +3118,13 @@ impl<
|
|||
self.free_symbol(&element_width_symbol);
|
||||
self.free_symbol(&element_alignment_symbol);
|
||||
|
||||
self.build_ptr_write(sym, allocation, value, element_layout);
|
||||
self.build_ptr_store(sym, allocation, value, element_layout);
|
||||
|
||||
self.free_symbol(&allocation);
|
||||
}
|
||||
|
||||
fn expr_unbox(&mut self, dst: Symbol, ptr: Symbol, element_layout: InLayout<'a>) {
|
||||
let ptr_reg = self
|
||||
.storage_manager
|
||||
.load_to_general_reg(&mut self.buf, &ptr);
|
||||
|
||||
let offset = 0;
|
||||
|
||||
Self::ptr_read(
|
||||
&mut self.buf,
|
||||
&mut self.storage_manager,
|
||||
self.layout_interner,
|
||||
ptr_reg,
|
||||
offset,
|
||||
element_layout,
|
||||
dst,
|
||||
);
|
||||
self.build_ptr_load(dst, ptr, element_layout)
|
||||
}
|
||||
|
||||
fn get_tag_id(&mut self, sym: &Symbol, structure: &Symbol, union_layout: &UnionLayout<'a>) {
|
||||
|
@ -3016,9 +3249,7 @@ impl<
|
|||
|
||||
let target_info = self.storage_manager.target_info;
|
||||
if union_layout.stores_tag_id_as_data(target_info) {
|
||||
let offset = union_layout
|
||||
.tag_id_offset(self.interner(), target_info)
|
||||
.unwrap() as i32;
|
||||
let offset = union_layout.tag_id_offset(self.interner()).unwrap() as i32;
|
||||
|
||||
let ptr_reg = self
|
||||
.storage_manager
|
||||
|
@ -3062,13 +3293,10 @@ impl<
|
|||
tag_id: TagIdIntType,
|
||||
reuse: Option<Symbol>,
|
||||
) {
|
||||
let target_info = self.storage_manager.target_info;
|
||||
|
||||
let layout_interner: &mut STLayoutInterner<'a> = self.layout_interner;
|
||||
let buf: &mut Vec<'a, u8> = &mut self.buf;
|
||||
|
||||
let (data_size, data_alignment) =
|
||||
union_layout.data_size_and_alignment(layout_interner, target_info);
|
||||
let (data_size, data_alignment) = union_layout.data_size_and_alignment(layout_interner);
|
||||
|
||||
match union_layout {
|
||||
UnionLayout::NonRecursive(field_layouts) => {
|
||||
|
@ -3464,7 +3692,8 @@ impl<
|
|||
}
|
||||
LayoutRepr::Union(UnionLayout::NonRecursive(_))
|
||||
| LayoutRepr::Builtin(_)
|
||||
| LayoutRepr::Struct(_) => {
|
||||
| LayoutRepr::Struct(_)
|
||||
| LayoutRepr::Erased(_) => {
|
||||
internal_error!("All primitive values should fit in a single register");
|
||||
}
|
||||
}
|
||||
|
@ -3806,6 +4035,19 @@ impl<
|
|||
CC: CallConv<GeneralReg, FloatReg, ASM>,
|
||||
> Backend64Bit<'a, 'r, GeneralReg, FloatReg, ASM, CC>
|
||||
{
|
||||
fn clear_tag_id(&mut self, ptr_reg: GeneralReg) -> (Symbol, GeneralReg) {
|
||||
let unmasked_symbol = self.debug_symbol("unmasked");
|
||||
let unmasked_reg = self
|
||||
.storage_manager
|
||||
.claim_general_reg(&mut self.buf, &unmasked_symbol);
|
||||
|
||||
ASM::mov_reg64_imm64(&mut self.buf, unmasked_reg, (!0b111) as _);
|
||||
|
||||
ASM::and_reg64_reg64_reg64(&mut self.buf, unmasked_reg, ptr_reg, unmasked_reg);
|
||||
|
||||
(unmasked_symbol, unmasked_reg)
|
||||
}
|
||||
|
||||
fn compare(
|
||||
&mut self,
|
||||
op: CompareOperation,
|
||||
|
@ -4023,7 +4265,18 @@ impl<
|
|||
Builtin::Int(int_width) => match int_width {
|
||||
IntWidth::I128 | IntWidth::U128 => {
|
||||
// can we treat this as 2 u64's?
|
||||
todo!()
|
||||
storage_manager.with_tmp_general_reg(
|
||||
buf,
|
||||
|storage_manager, buf, tmp_reg| {
|
||||
let base_offset = storage_manager.claim_stack_area(&dst, 16);
|
||||
|
||||
ASM::mov_reg64_mem64_offset32(buf, tmp_reg, ptr_reg, offset);
|
||||
ASM::mov_base32_reg64(buf, base_offset, tmp_reg);
|
||||
|
||||
ASM::mov_reg64_mem64_offset32(buf, tmp_reg, ptr_reg, offset + 8);
|
||||
ASM::mov_base32_reg64(buf, base_offset + 8, tmp_reg);
|
||||
},
|
||||
);
|
||||
}
|
||||
IntWidth::I64 | IntWidth::U64 => {
|
||||
let dst_reg = storage_manager.claim_general_reg(buf, &dst);
|
||||
|
@ -4035,10 +4288,12 @@ impl<
|
|||
}
|
||||
IntWidth::I16 | IntWidth::U16 => {
|
||||
let dst_reg = storage_manager.claim_general_reg(buf, &dst);
|
||||
ASM::xor_reg64_reg64_reg64(buf, dst_reg, dst_reg, dst_reg);
|
||||
ASM::mov_reg16_mem16_offset32(buf, dst_reg, ptr_reg, offset);
|
||||
}
|
||||
IntWidth::I8 | IntWidth::U8 => {
|
||||
let dst_reg = storage_manager.claim_general_reg(buf, &dst);
|
||||
ASM::xor_reg64_reg64_reg64(buf, dst_reg, dst_reg, dst_reg);
|
||||
ASM::mov_reg8_mem8_offset32(buf, dst_reg, ptr_reg, offset);
|
||||
}
|
||||
},
|
||||
|
@ -4053,10 +4308,21 @@ impl<
|
|||
Builtin::Bool => {
|
||||
// the same as an 8-bit integer
|
||||
let dst_reg = storage_manager.claim_general_reg(buf, &dst);
|
||||
|
||||
ASM::xor_reg64_reg64_reg64(buf, dst_reg, dst_reg, dst_reg);
|
||||
ASM::mov_reg8_mem8_offset32(buf, dst_reg, ptr_reg, offset);
|
||||
}
|
||||
Builtin::Decimal => {
|
||||
// same as 128-bit integer
|
||||
storage_manager.with_tmp_general_reg(buf, |storage_manager, buf, tmp_reg| {
|
||||
let base_offset = storage_manager.claim_stack_area(&dst, 16);
|
||||
|
||||
ASM::mov_reg64_mem64_offset32(buf, tmp_reg, ptr_reg, offset);
|
||||
ASM::mov_base32_reg64(buf, base_offset, tmp_reg);
|
||||
|
||||
ASM::mov_reg64_mem64_offset32(buf, tmp_reg, ptr_reg, offset + 8);
|
||||
ASM::mov_base32_reg64(buf, base_offset + 8, tmp_reg);
|
||||
});
|
||||
}
|
||||
Builtin::Str | Builtin::List(_) => {
|
||||
storage_manager.with_tmp_general_reg(buf, |storage_manager, buf, tmp_reg| {
|
||||
|
@ -4123,6 +4389,8 @@ impl<
|
|||
dst,
|
||||
);
|
||||
}
|
||||
|
||||
LayoutRepr::Erased(_) => todo_lambda_erasure!(),
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -4336,7 +4604,7 @@ macro_rules! single_register_layouts {
|
|||
#[macro_export]
|
||||
macro_rules! pointer_layouts {
|
||||
() => {
|
||||
LayoutRepr::Boxed(_)
|
||||
LayoutRepr::Ptr(_)
|
||||
| LayoutRepr::RecursivePointer(_)
|
||||
| LayoutRepr::Union(
|
||||
UnionLayout::Recursive(_)
|
||||
|
@ -4344,5 +4612,6 @@ macro_rules! pointer_layouts {
|
|||
| UnionLayout::NullableWrapped { .. }
|
||||
| UnionLayout::NullableUnwrapped { .. },
|
||||
)
|
||||
| LayoutRepr::FunctionPointer(_)
|
||||
};
|
||||
}
|
||||
|
|
|
@ -6,7 +6,7 @@ use crate::{
|
|||
use bumpalo::collections::Vec;
|
||||
use roc_builtins::bitcode::{FloatWidth, IntWidth};
|
||||
use roc_collections::all::{MutMap, MutSet};
|
||||
use roc_error_macros::internal_error;
|
||||
use roc_error_macros::{internal_error, todo_lambda_erasure};
|
||||
use roc_module::symbol::Symbol;
|
||||
use roc_mono::{
|
||||
ir::{JoinPointId, Param},
|
||||
|
@ -23,6 +23,8 @@ use RegStorage::*;
|
|||
use StackStorage::*;
|
||||
use Storage::*;
|
||||
|
||||
use super::RegisterWidth;
|
||||
|
||||
#[derive(Copy, Clone, Debug, PartialEq, Eq)]
|
||||
pub enum RegStorage<GeneralReg: RegTrait, FloatReg: RegTrait> {
|
||||
General(GeneralReg),
|
||||
|
@ -343,10 +345,19 @@ impl<
|
|||
sign_extend,
|
||||
}) => {
|
||||
let reg = self.get_general_reg(buf);
|
||||
|
||||
let register_width = match size {
|
||||
8 => RegisterWidth::W64,
|
||||
4 => RegisterWidth::W32,
|
||||
2 => RegisterWidth::W16,
|
||||
1 => RegisterWidth::W8,
|
||||
_ => internal_error!("Invalid size: {size}"),
|
||||
};
|
||||
|
||||
if sign_extend {
|
||||
ASM::movsx_reg64_base32(buf, reg, base_offset, size as u8);
|
||||
ASM::movsx_reg_base32(buf, register_width, reg, base_offset);
|
||||
} else {
|
||||
ASM::movzx_reg64_base32(buf, reg, base_offset, size as u8);
|
||||
ASM::movzx_reg_base32(buf, register_width, reg, base_offset);
|
||||
}
|
||||
self.general_used_regs.push((reg, *sym));
|
||||
self.symbol_storage_map.insert(*sym, Reg(General(reg)));
|
||||
|
@ -469,10 +480,18 @@ impl<
|
|||
}) => {
|
||||
debug_assert!(*size <= 8);
|
||||
|
||||
let register_width = match size {
|
||||
8 => RegisterWidth::W64,
|
||||
4 => RegisterWidth::W32,
|
||||
2 => RegisterWidth::W16,
|
||||
1 => RegisterWidth::W8,
|
||||
_ => internal_error!("Invalid size: {size}"),
|
||||
};
|
||||
|
||||
if *sign_extend {
|
||||
ASM::movsx_reg64_base32(buf, reg, *base_offset, *size as u8)
|
||||
ASM::movsx_reg_base32(buf, register_width, reg, *base_offset)
|
||||
} else {
|
||||
ASM::movzx_reg64_base32(buf, reg, *base_offset, *size as u8)
|
||||
ASM::movzx_reg_base32(buf, register_width, reg, *base_offset)
|
||||
}
|
||||
}
|
||||
Stack(Complex { size, .. }) => {
|
||||
|
@ -614,8 +633,7 @@ impl<
|
|||
|
||||
let (union_offset, _) = self.stack_offset_and_size(structure);
|
||||
|
||||
let (data_size, data_alignment) =
|
||||
union_layout.data_size_and_alignment(layout_interner, self.target_info);
|
||||
let (data_size, data_alignment) = union_layout.data_size_and_alignment(layout_interner);
|
||||
let id_offset = data_size - data_alignment;
|
||||
let discriminant = union_layout.discriminant();
|
||||
|
||||
|
@ -765,7 +783,11 @@ impl<
|
|||
let reg = self.load_to_float_reg(buf, sym);
|
||||
ASM::mov_base32_freg64(buf, to_offset, reg);
|
||||
}
|
||||
FloatWidth::F32 => todo!(),
|
||||
FloatWidth::F32 => {
|
||||
debug_assert_eq!(to_offset % 4, 0);
|
||||
let reg = self.load_to_float_reg(buf, sym);
|
||||
ASM::mov_base32_freg64(buf, to_offset, reg);
|
||||
}
|
||||
},
|
||||
Builtin::Bool => {
|
||||
// same as 8-bit integer, but we special-case true/false because these symbols
|
||||
|
@ -785,7 +807,13 @@ impl<
|
|||
}
|
||||
}
|
||||
}
|
||||
Builtin::Decimal => todo!(),
|
||||
Builtin::Decimal => {
|
||||
let (from_offset, size) = self.stack_offset_and_size(sym);
|
||||
debug_assert_eq!(from_offset % 8, 0);
|
||||
debug_assert_eq!(size % 8, 0);
|
||||
debug_assert_eq!(size, layout_interner.stack_size(*layout));
|
||||
self.copy_to_stack_offset(buf, size, from_offset, to_offset)
|
||||
}
|
||||
Builtin::Str | Builtin::List(_) => {
|
||||
let (from_offset, size) = self.stack_offset_and_size(sym);
|
||||
debug_assert_eq!(size, layout_interner.stack_size(*layout));
|
||||
|
@ -808,7 +836,8 @@ impl<
|
|||
|
||||
self.copy_to_stack_offset(buf, size, from_offset, to_offset)
|
||||
}
|
||||
LayoutRepr::RecursivePointer(_) | LayoutRepr::Boxed(_) | LayoutRepr::Union(_) => {
|
||||
LayoutRepr::Erased(_) => todo_lambda_erasure!(),
|
||||
pointer_layouts!() => {
|
||||
// like a 64-bit integer
|
||||
debug_assert_eq!(to_offset % 8, 0);
|
||||
let reg = self.load_to_general_reg(buf, sym);
|
||||
|
@ -1146,12 +1175,7 @@ impl<
|
|||
) {
|
||||
let mut param_storage = bumpalo::vec![in self.env.arena];
|
||||
param_storage.reserve(params.len());
|
||||
for Param {
|
||||
symbol,
|
||||
ownership: _,
|
||||
layout,
|
||||
} in params
|
||||
{
|
||||
for Param { symbol, layout } in params {
|
||||
// Claim a location for every join point parameter to be loaded at.
|
||||
// Put everything on the stack for simplicity.
|
||||
self.joinpoint_argument_stack_storage(layout_interner, *symbol, *layout);
|
||||
|
|
File diff suppressed because it is too large
Load diff
|
@ -10,17 +10,17 @@ use std::collections::hash_map::Entry;
|
|||
use bumpalo::{collections::Vec, Bump};
|
||||
use roc_builtins::bitcode::{self, FloatWidth, IntWidth};
|
||||
use roc_collections::all::{MutMap, MutSet};
|
||||
use roc_error_macros::internal_error;
|
||||
use roc_error_macros::{internal_error, todo_lambda_erasure};
|
||||
use roc_module::ident::ModuleName;
|
||||
use roc_module::low_level::{LowLevel, LowLevelWrapperType};
|
||||
use roc_module::symbol::{Interns, ModuleId, Symbol};
|
||||
use roc_mono::code_gen_help::{CallerProc, CodeGenHelp};
|
||||
use roc_mono::ir::{
|
||||
BranchInfo, CallType, CrashTag, Expr, HigherOrderLowLevel, JoinPointId, ListLiteralElement,
|
||||
Literal, Param, Proc, ProcLayout, SelfRecursive, Stmt,
|
||||
Literal, ModifyRc, Param, Proc, ProcLayout, SelfRecursive, Stmt,
|
||||
};
|
||||
use roc_mono::layout::{
|
||||
Builtin, InLayout, Layout, LayoutIds, LayoutInterner, LayoutRepr, STLayoutInterner,
|
||||
Builtin, InLayout, LambdaName, Layout, LayoutIds, LayoutInterner, LayoutRepr, STLayoutInterner,
|
||||
TagIdIntType, UnionLayout,
|
||||
};
|
||||
use roc_mono::list_element_layout;
|
||||
|
@ -138,15 +138,22 @@ impl<'a> LastSeenMap<'a> {
|
|||
|
||||
Expr::Call(call) => self.scan_ast_call(call, stmt),
|
||||
|
||||
Expr::Tag { arguments, .. } => {
|
||||
Expr::Tag {
|
||||
arguments, reuse, ..
|
||||
} => {
|
||||
if let Some(ru) = reuse {
|
||||
self.set_last_seen(ru.symbol, stmt);
|
||||
}
|
||||
|
||||
for sym in *arguments {
|
||||
self.set_last_seen(*sym, stmt);
|
||||
}
|
||||
}
|
||||
Expr::ExprBox { symbol } => {
|
||||
self.set_last_seen(*symbol, stmt);
|
||||
Expr::ErasedMake { value, callee } => {
|
||||
value.map(|v| self.set_last_seen(v, stmt));
|
||||
self.set_last_seen(*callee, stmt);
|
||||
}
|
||||
Expr::ExprUnbox { symbol } => {
|
||||
Expr::ErasedLoad { symbol, field: _ } => {
|
||||
self.set_last_seen(*symbol, stmt);
|
||||
}
|
||||
Expr::Struct(syms) => {
|
||||
|
@ -163,6 +170,9 @@ impl<'a> LastSeenMap<'a> {
|
|||
Expr::UnionAtIndex { structure, .. } => {
|
||||
self.set_last_seen(*structure, stmt);
|
||||
}
|
||||
Expr::UnionFieldPtrAtIndex { structure, .. } => {
|
||||
self.set_last_seen(*structure, stmt);
|
||||
}
|
||||
Expr::Array { elems, .. } => {
|
||||
for elem in *elems {
|
||||
if let ListLiteralElement::Symbol(sym) = elem {
|
||||
|
@ -170,19 +180,17 @@ impl<'a> LastSeenMap<'a> {
|
|||
}
|
||||
}
|
||||
}
|
||||
Expr::Reuse {
|
||||
symbol, arguments, ..
|
||||
} => {
|
||||
self.set_last_seen(*symbol, stmt);
|
||||
for sym in *arguments {
|
||||
self.set_last_seen(*sym, stmt);
|
||||
}
|
||||
}
|
||||
Expr::Reset { symbol, .. } | Expr::ResetRef { symbol, .. } => {
|
||||
self.set_last_seen(*symbol, stmt);
|
||||
}
|
||||
Expr::EmptyArray => {}
|
||||
Expr::Alloca { initializer, .. } => {
|
||||
if let Some(initializer) = initializer {
|
||||
self.set_last_seen(*initializer, stmt);
|
||||
}
|
||||
}
|
||||
Expr::RuntimeErrorFunction(_) => {}
|
||||
Expr::FunctionPointer { .. } => todo_lambda_erasure!(),
|
||||
Expr::EmptyArray => {}
|
||||
}
|
||||
self.scan_ast_help(following);
|
||||
}
|
||||
|
@ -270,6 +278,7 @@ impl<'a> LastSeenMap<'a> {
|
|||
|
||||
match call_type {
|
||||
CallType::ByName { .. } => {}
|
||||
CallType::ByPointer { .. } => {}
|
||||
CallType::LowLevel { .. } => {}
|
||||
CallType::HigherOrder { .. } => {}
|
||||
CallType::Foreign { .. } => {}
|
||||
|
@ -282,6 +291,7 @@ trait Backend<'a> {
|
|||
fn interns(&self) -> &Interns;
|
||||
fn interns_mut(&mut self) -> &mut Interns;
|
||||
fn interner(&self) -> &STLayoutInterner<'a>;
|
||||
fn relocations_mut(&mut self) -> &mut Vec<'a, Relocation>;
|
||||
|
||||
fn interner_mut(&mut self) -> &mut STLayoutInterner<'a> {
|
||||
self.module_interns_helpers_mut().1
|
||||
|
@ -317,9 +327,9 @@ trait Backend<'a> {
|
|||
&mut Vec<'a, CallerProc<'a>>,
|
||||
);
|
||||
|
||||
fn function_symbol_to_string<'b, I>(
|
||||
fn lambda_name_to_string<'b, I>(
|
||||
&self,
|
||||
symbol: Symbol,
|
||||
name: LambdaName,
|
||||
arguments: I,
|
||||
_lambda_set: Option<InLayout>,
|
||||
result: InLayout,
|
||||
|
@ -327,18 +337,27 @@ trait Backend<'a> {
|
|||
where
|
||||
I: Iterator<Item = InLayout<'b>>,
|
||||
{
|
||||
use std::fmt::Write;
|
||||
use std::hash::{BuildHasher, Hash, Hasher};
|
||||
|
||||
// NOTE: due to randomness, this will not be consistent between runs
|
||||
let mut state = roc_collections::all::BuildHasher::default().build_hasher();
|
||||
let symbol = name.name();
|
||||
|
||||
let mut buf = String::with_capacity(1024);
|
||||
|
||||
for a in arguments {
|
||||
a.hash(&mut state);
|
||||
write!(buf, "{:?}", self.interner().dbg_stable(a)).expect("capacity");
|
||||
}
|
||||
|
||||
// lambda set should not matter; it should already be added as an argument
|
||||
// lambda_set.hash(&mut state);
|
||||
// but the niche of the lambda name may be the only thing differentiating two different
|
||||
// implementations of a function with the same symbol
|
||||
write!(buf, "{:?}", name.niche().dbg_stable(self.interner())).expect("capacity");
|
||||
|
||||
result.hash(&mut state);
|
||||
write!(buf, "{:?}", self.interner().dbg_stable(result)).expect("capacity");
|
||||
|
||||
// NOTE: due to randomness, this will not be consistent between runs
|
||||
let mut state = roc_collections::all::BuildHasher::default().build_hasher();
|
||||
buf.hash(&mut state);
|
||||
|
||||
let interns = self.interns();
|
||||
let ident_string = symbol.as_str(interns);
|
||||
|
@ -347,7 +366,7 @@ trait Backend<'a> {
|
|||
// the functions from the generates #help module (refcounting, equality) is always suffixed
|
||||
// with 1. That is fine, they are always unique anyway.
|
||||
if ident_string.contains("#help") {
|
||||
format!("{}_{}_1", module_string, ident_string)
|
||||
format!("{module_string}_{ident_string}_1")
|
||||
} else {
|
||||
format!("{}_{}_{}", module_string, ident_string, state.finish())
|
||||
}
|
||||
|
@ -384,13 +403,13 @@ trait Backend<'a> {
|
|||
fn increment_fn_pointer(&mut self, layout: InLayout<'a>) -> Symbol {
|
||||
let box_layout = self
|
||||
.interner_mut()
|
||||
.insert_direct_no_semantic(LayoutRepr::Boxed(layout));
|
||||
.insert_direct_no_semantic(LayoutRepr::Ptr(layout));
|
||||
|
||||
let element_increment = self.debug_symbol("element_increment");
|
||||
let element_increment_symbol = self.build_indirect_inc(layout);
|
||||
|
||||
let element_increment_string = self.function_symbol_to_string(
|
||||
element_increment_symbol,
|
||||
let element_increment_string = self.lambda_name_to_string(
|
||||
LambdaName::no_niche(element_increment_symbol),
|
||||
[box_layout].into_iter(),
|
||||
None,
|
||||
Layout::UNIT,
|
||||
|
@ -404,13 +423,13 @@ trait Backend<'a> {
|
|||
fn decrement_fn_pointer(&mut self, layout: InLayout<'a>) -> Symbol {
|
||||
let box_layout = self
|
||||
.interner_mut()
|
||||
.insert_direct_no_semantic(LayoutRepr::Boxed(layout));
|
||||
.insert_direct_no_semantic(LayoutRepr::Ptr(layout));
|
||||
|
||||
let element_decrement = self.debug_symbol("element_decrement");
|
||||
let element_decrement_symbol = self.build_indirect_dec(layout);
|
||||
|
||||
let element_decrement_string = self.function_symbol_to_string(
|
||||
element_decrement_symbol,
|
||||
let element_decrement_string = self.lambda_name_to_string(
|
||||
LambdaName::no_niche(element_decrement_symbol),
|
||||
[box_layout].into_iter(),
|
||||
None,
|
||||
Layout::UNIT,
|
||||
|
@ -445,6 +464,11 @@ trait Backend<'a> {
|
|||
/// Used for generating wrappers for malloc/realloc/free
|
||||
fn build_wrapped_jmp(&mut self) -> (&'a [u8], u64);
|
||||
|
||||
// use for roc_panic
|
||||
fn build_roc_setjmp(&mut self) -> &'a [u8];
|
||||
fn build_roc_longjmp(&mut self) -> &'a [u8];
|
||||
fn build_roc_panic(&mut self) -> (&'a [u8], Vec<'a, Relocation>);
|
||||
|
||||
/// build_proc creates a procedure and outputs it to the wrapped object writer.
|
||||
/// Returns the procedure bytes, its relocations, and the names of the refcounting functions it references.
|
||||
fn build_proc(
|
||||
|
@ -452,8 +476,8 @@ trait Backend<'a> {
|
|||
proc: Proc<'a>,
|
||||
layout_ids: &mut LayoutIds<'a>,
|
||||
) -> (Vec<u8>, Vec<Relocation>, Vec<'a, (Symbol, String)>) {
|
||||
let proc_name = self.function_symbol_to_string(
|
||||
proc.name.name(),
|
||||
let proc_name = self.lambda_name_to_string(
|
||||
proc.name,
|
||||
proc.args.iter().map(|t| t.0),
|
||||
proc.closure_data_layout,
|
||||
proc.ret_layout,
|
||||
|
@ -513,6 +537,29 @@ trait Backend<'a> {
|
|||
self.return_symbol(sym, ret_layout);
|
||||
self.free_symbols(stmt);
|
||||
}
|
||||
Stmt::Refcounting(ModifyRc::Free(symbol), following) => {
|
||||
let dst = Symbol::DEV_TMP;
|
||||
|
||||
let layout = *self.layout_map().get(symbol).unwrap();
|
||||
let alignment_bytes = self.interner().allocation_alignment_bytes(layout);
|
||||
let alignment = self.debug_symbol("alignment");
|
||||
self.load_literal_i32(&alignment, alignment_bytes as i32);
|
||||
|
||||
// NOTE: UTILS_FREE_DATA_PTR clears any tag id bits
|
||||
|
||||
self.build_fn_call(
|
||||
&dst,
|
||||
bitcode::UTILS_FREE_DATA_PTR.to_string(),
|
||||
&[*symbol, alignment],
|
||||
&[Layout::I64, Layout::I32],
|
||||
&Layout::UNIT,
|
||||
);
|
||||
|
||||
self.free_symbol(&dst);
|
||||
self.free_symbol(&alignment);
|
||||
|
||||
self.build_stmt(layout_ids, following, ret_layout)
|
||||
}
|
||||
Stmt::Refcounting(modify, following) => {
|
||||
let sym = modify.get_symbol();
|
||||
let layout = *self.layout_map().get(&sym).unwrap();
|
||||
|
@ -683,15 +730,15 @@ trait Backend<'a> {
|
|||
// implementation in `build_builtin` inlines some of the symbols.
|
||||
return self.build_builtin(
|
||||
sym,
|
||||
func_sym.name(),
|
||||
*func_sym,
|
||||
arguments,
|
||||
arg_layouts,
|
||||
ret_layout,
|
||||
);
|
||||
}
|
||||
|
||||
let fn_name = self.function_symbol_to_string(
|
||||
func_sym.name(),
|
||||
let fn_name = self.lambda_name_to_string(
|
||||
*func_sym,
|
||||
arg_layouts.iter().copied(),
|
||||
None,
|
||||
*ret_layout,
|
||||
|
@ -702,6 +749,10 @@ trait Backend<'a> {
|
|||
self.build_fn_call(sym, fn_name, arguments, arg_layouts, ret_layout)
|
||||
}
|
||||
|
||||
CallType::ByPointer { .. } => {
|
||||
todo_lambda_erasure!()
|
||||
}
|
||||
|
||||
CallType::LowLevel { op: lowlevel, .. } => {
|
||||
let mut arg_layouts: bumpalo::collections::Vec<InLayout<'a>> =
|
||||
bumpalo::vec![in self.env().arena];
|
||||
|
@ -785,6 +836,14 @@ trait Backend<'a> {
|
|||
} => {
|
||||
self.load_union_at_index(sym, structure, *tag_id, *index, union_layout);
|
||||
}
|
||||
Expr::UnionFieldPtrAtIndex {
|
||||
structure,
|
||||
tag_id,
|
||||
union_layout,
|
||||
index,
|
||||
} => {
|
||||
self.load_union_field_ptr_at_index(sym, structure, *tag_id, *index, union_layout);
|
||||
}
|
||||
Expr::GetTagId {
|
||||
structure,
|
||||
union_layout,
|
||||
|
@ -795,39 +854,18 @@ trait Backend<'a> {
|
|||
tag_layout,
|
||||
tag_id,
|
||||
arguments,
|
||||
..
|
||||
reuse,
|
||||
} => {
|
||||
self.load_literal_symbols(arguments);
|
||||
self.tag(sym, arguments, tag_layout, *tag_id, None);
|
||||
}
|
||||
Expr::ExprBox { symbol: value } => {
|
||||
let element_layout = match self.interner().get_repr(*layout) {
|
||||
LayoutRepr::Boxed(boxed) => boxed,
|
||||
_ => unreachable!("{:?}", self.interner().dbg(*layout)),
|
||||
};
|
||||
|
||||
self.load_literal_symbols([*value].as_slice());
|
||||
self.expr_box(*sym, *value, element_layout, None)
|
||||
}
|
||||
Expr::ExprUnbox { symbol: ptr } => {
|
||||
let element_layout = *layout;
|
||||
|
||||
self.load_literal_symbols([*ptr].as_slice());
|
||||
self.expr_unbox(*sym, *ptr, element_layout)
|
||||
let reuse = reuse.map(|ru| ru.symbol);
|
||||
self.tag(sym, arguments, tag_layout, *tag_id, reuse);
|
||||
}
|
||||
Expr::NullPointer => {
|
||||
self.load_literal_i64(sym, 0);
|
||||
}
|
||||
Expr::Reuse {
|
||||
tag_layout,
|
||||
tag_id,
|
||||
symbol: reused,
|
||||
arguments,
|
||||
..
|
||||
} => {
|
||||
self.load_literal_symbols(arguments);
|
||||
self.tag(sym, arguments, tag_layout, *tag_id, Some(*reused));
|
||||
}
|
||||
Expr::FunctionPointer { .. } => todo_lambda_erasure!(),
|
||||
Expr::ErasedMake { .. } => todo_lambda_erasure!(),
|
||||
Expr::ErasedLoad { .. } => todo_lambda_erasure!(),
|
||||
Expr::Reset { symbol, .. } => {
|
||||
let layout = *self.layout_map().get(symbol).unwrap();
|
||||
|
||||
|
@ -868,6 +906,12 @@ trait Backend<'a> {
|
|||
|
||||
self.build_expr(sym, &new_expr, &Layout::BOOL)
|
||||
}
|
||||
Expr::Alloca {
|
||||
initializer,
|
||||
element_layout,
|
||||
} => {
|
||||
self.build_alloca(*sym, *initializer, *element_layout);
|
||||
}
|
||||
Expr::RuntimeErrorFunction(_) => todo!(),
|
||||
}
|
||||
}
|
||||
|
@ -960,7 +1004,7 @@ trait Backend<'a> {
|
|||
ret_layout,
|
||||
),
|
||||
LowLevel::NumMul => self.build_num_mul(sym, &args[0], &args[1], ret_layout),
|
||||
LowLevel::NumMulWrap => self.build_num_mul(sym, &args[0], &args[1], ret_layout),
|
||||
LowLevel::NumMulWrap => self.build_num_mul_wrap(sym, &args[0], &args[1], ret_layout),
|
||||
LowLevel::NumDivTruncUnchecked | LowLevel::NumDivFrac => {
|
||||
debug_assert_eq!(
|
||||
2,
|
||||
|
@ -1482,16 +1526,16 @@ trait Backend<'a> {
|
|||
arg_layouts,
|
||||
ret_layout,
|
||||
),
|
||||
LowLevel::StrTrimLeft => self.build_fn_call(
|
||||
LowLevel::StrTrimStart => self.build_fn_call(
|
||||
sym,
|
||||
bitcode::STR_TRIM_LEFT.to_string(),
|
||||
bitcode::STR_TRIM_START.to_string(),
|
||||
args,
|
||||
arg_layouts,
|
||||
ret_layout,
|
||||
),
|
||||
LowLevel::StrTrimRight => self.build_fn_call(
|
||||
LowLevel::StrTrimEnd => self.build_fn_call(
|
||||
sym,
|
||||
bitcode::STR_TRIM_RIGHT.to_string(),
|
||||
bitcode::STR_TRIM_END.to_string(),
|
||||
args,
|
||||
arg_layouts,
|
||||
ret_layout,
|
||||
|
@ -1572,14 +1616,22 @@ trait Backend<'a> {
|
|||
|
||||
self.build_ptr_cast(sym, &args[0])
|
||||
}
|
||||
LowLevel::PtrWrite => {
|
||||
let element_layout = match self.interner().get_repr(*ret_layout) {
|
||||
LayoutRepr::Boxed(boxed) => boxed,
|
||||
LowLevel::PtrStore => {
|
||||
let element_layout = match self.interner().get_repr(arg_layouts[0]) {
|
||||
LayoutRepr::Ptr(inner) => inner,
|
||||
_ => unreachable!("cannot write to {:?}", self.interner().dbg(*ret_layout)),
|
||||
};
|
||||
|
||||
self.build_ptr_write(*sym, args[0], args[1], element_layout);
|
||||
self.build_ptr_store(*sym, args[0], args[1], element_layout);
|
||||
}
|
||||
LowLevel::PtrLoad => {
|
||||
self.build_ptr_load(*sym, args[0], *ret_layout);
|
||||
}
|
||||
|
||||
LowLevel::PtrClearTagId => {
|
||||
self.build_ptr_clear_tag_id(*sym, args[0]);
|
||||
}
|
||||
|
||||
LowLevel::RefCountDecRcPtr => self.build_fn_call(
|
||||
sym,
|
||||
bitcode::UTILS_DECREF_RC_PTR.to_string(),
|
||||
|
@ -1615,6 +1667,23 @@ trait Backend<'a> {
|
|||
arg_layouts,
|
||||
ret_layout,
|
||||
),
|
||||
LowLevel::SetJmp => self.build_fn_call(
|
||||
sym,
|
||||
String::from("roc_setjmp"),
|
||||
args,
|
||||
arg_layouts,
|
||||
ret_layout,
|
||||
),
|
||||
LowLevel::LongJmp => self.build_fn_call(
|
||||
sym,
|
||||
String::from("roc_longjmp"),
|
||||
args,
|
||||
arg_layouts,
|
||||
ret_layout,
|
||||
),
|
||||
LowLevel::SetLongJmpBuffer => {
|
||||
self.build_data_pointer(sym, String::from("setlongjmp_buffer"));
|
||||
}
|
||||
LowLevel::DictPseudoSeed => self.build_fn_call(
|
||||
sym,
|
||||
bitcode::UTILS_DICT_PSEUDO_SEED.to_string(),
|
||||
|
@ -1807,6 +1876,10 @@ trait Backend<'a> {
|
|||
);
|
||||
}
|
||||
|
||||
LowLevel::NumCompare => {
|
||||
self.build_num_cmp(sym, &args[0], &args[1], &arg_layouts[0]);
|
||||
}
|
||||
|
||||
x => todo!("low level, {:?}", x),
|
||||
}
|
||||
}
|
||||
|
@ -1816,12 +1889,12 @@ trait Backend<'a> {
|
|||
fn build_builtin(
|
||||
&mut self,
|
||||
sym: &Symbol,
|
||||
func_sym: Symbol,
|
||||
func_name: LambdaName,
|
||||
args: &'a [Symbol],
|
||||
arg_layouts: &[InLayout<'a>],
|
||||
ret_layout: &InLayout<'a>,
|
||||
) {
|
||||
match func_sym {
|
||||
match func_name.name() {
|
||||
Symbol::NUM_IS_ZERO => {
|
||||
debug_assert_eq!(
|
||||
1,
|
||||
|
@ -1844,8 +1917,8 @@ trait Backend<'a> {
|
|||
}
|
||||
Symbol::LIST_GET | Symbol::LIST_SET | Symbol::LIST_REPLACE | Symbol::LIST_APPEND => {
|
||||
// TODO: This is probably simple enough to be worth inlining.
|
||||
let fn_name = self.function_symbol_to_string(
|
||||
func_sym,
|
||||
let fn_name = self.lambda_name_to_string(
|
||||
func_name,
|
||||
arg_layouts.iter().copied(),
|
||||
None,
|
||||
*ret_layout,
|
||||
|
@ -1876,8 +1949,8 @@ trait Backend<'a> {
|
|||
}
|
||||
Symbol::STR_IS_VALID_SCALAR => {
|
||||
// just call the function
|
||||
let fn_name = self.function_symbol_to_string(
|
||||
func_sym,
|
||||
let fn_name = self.lambda_name_to_string(
|
||||
func_name,
|
||||
arg_layouts.iter().copied(),
|
||||
None,
|
||||
*ret_layout,
|
||||
|
@ -1888,8 +1961,8 @@ trait Backend<'a> {
|
|||
}
|
||||
_other => {
|
||||
// just call the function
|
||||
let fn_name = self.function_symbol_to_string(
|
||||
func_sym,
|
||||
let fn_name = self.lambda_name_to_string(
|
||||
func_name,
|
||||
arg_layouts.iter().copied(),
|
||||
None,
|
||||
*ret_layout,
|
||||
|
@ -1913,6 +1986,7 @@ trait Backend<'a> {
|
|||
);
|
||||
|
||||
fn build_fn_pointer(&mut self, dst: &Symbol, fn_name: String);
|
||||
fn build_data_pointer(&mut self, dst: &Symbol, data_name: String);
|
||||
|
||||
/// Move a returned value into `dst`
|
||||
fn move_return_value(&mut self, dst: &Symbol, ret_layout: &InLayout<'a>);
|
||||
|
@ -1964,6 +2038,15 @@ trait Backend<'a> {
|
|||
/// build_num_mul stores `src1 * src2` into dst.
|
||||
fn build_num_mul(&mut self, dst: &Symbol, src1: &Symbol, src2: &Symbol, layout: &InLayout<'a>);
|
||||
|
||||
/// build_num_mul_wrap stores `src1 * src2` into dst.
|
||||
fn build_num_mul_wrap(
|
||||
&mut self,
|
||||
dst: &Symbol,
|
||||
src1: &Symbol,
|
||||
src2: &Symbol,
|
||||
layout: &InLayout<'a>,
|
||||
);
|
||||
|
||||
/// build_num_mul stores `src1 / src2` into dst.
|
||||
fn build_num_div(&mut self, dst: &Symbol, src1: &Symbol, src2: &Symbol, layout: &InLayout<'a>);
|
||||
|
||||
|
@ -2048,6 +2131,14 @@ trait Backend<'a> {
|
|||
/// build_not stores the result of `!src` into dst.
|
||||
fn build_not(&mut self, dst: &Symbol, src: &Symbol, arg_layout: &InLayout<'a>);
|
||||
|
||||
fn build_num_cmp(
|
||||
&mut self,
|
||||
dst: &Symbol,
|
||||
src1: &Symbol,
|
||||
src2: &Symbol,
|
||||
arg_layout: &InLayout<'a>,
|
||||
);
|
||||
|
||||
/// build_num_lt stores the result of `src1 < src2` into dst.
|
||||
fn build_num_lt(
|
||||
&mut self,
|
||||
|
@ -2187,7 +2278,7 @@ trait Backend<'a> {
|
|||
/// build_refcount_getptr loads the pointer to the reference count of src into dst.
|
||||
fn build_ptr_cast(&mut self, dst: &Symbol, src: &Symbol);
|
||||
|
||||
fn build_ptr_write(
|
||||
fn build_ptr_store(
|
||||
&mut self,
|
||||
sym: Symbol,
|
||||
ptr: Symbol,
|
||||
|
@ -2195,6 +2286,12 @@ trait Backend<'a> {
|
|||
element_layout: InLayout<'a>,
|
||||
);
|
||||
|
||||
fn build_ptr_load(&mut self, sym: Symbol, ptr: Symbol, element_layout: InLayout<'a>);
|
||||
|
||||
fn build_ptr_clear_tag_id(&mut self, sym: Symbol, ptr: Symbol);
|
||||
|
||||
fn build_alloca(&mut self, sym: Symbol, value: Option<Symbol>, element_layout: InLayout<'a>);
|
||||
|
||||
/// literal_map gets the map from symbol to literal and layout, used for lazy loading and literal folding.
|
||||
fn literal_map(&mut self) -> &mut MutMap<Symbol, (*const Literal<'a>, *const InLayout<'a>)>;
|
||||
|
||||
|
@ -2272,6 +2369,16 @@ trait Backend<'a> {
|
|||
union_layout: &UnionLayout<'a>,
|
||||
);
|
||||
|
||||
/// load_union_at_index loads into `sym` the value at `index` for `tag_id`.
|
||||
fn load_union_field_ptr_at_index(
|
||||
&mut self,
|
||||
sym: &Symbol,
|
||||
structure: &Symbol,
|
||||
tag_id: TagIdIntType,
|
||||
index: u64,
|
||||
union_layout: &UnionLayout<'a>,
|
||||
);
|
||||
|
||||
/// get_tag_id loads the tag id from a the union.
|
||||
fn get_tag_id(&mut self, sym: &Symbol, structure: &Symbol, union_layout: &UnionLayout<'a>);
|
||||
|
||||
|
|
|
@ -74,6 +74,23 @@ pub fn build_module<'a, 'r>(
|
|||
),
|
||||
)
|
||||
}
|
||||
Triple {
|
||||
architecture: TargetArch::X86_64,
|
||||
binary_format: TargetBF::Coff,
|
||||
..
|
||||
} if cfg!(feature = "target-x86_64") => {
|
||||
let backend = new_backend_64bit::<
|
||||
x86_64::X86_64GeneralReg,
|
||||
x86_64::X86_64FloatReg,
|
||||
x86_64::X86_64Assembler,
|
||||
x86_64::X86_64WindowsFastcall,
|
||||
>(env, TargetInfo::default_x86_64(), interns, layout_interner);
|
||||
build_object(
|
||||
procedures,
|
||||
backend,
|
||||
Object::new(BinaryFormat::Coff, Architecture::X86_64, Endianness::Little),
|
||||
)
|
||||
}
|
||||
Triple {
|
||||
architecture: TargetArch::Aarch64(_),
|
||||
binary_format: TargetBF::Elf,
|
||||
|
@ -118,6 +135,111 @@ pub fn build_module<'a, 'r>(
|
|||
}
|
||||
}
|
||||
|
||||
fn define_setlongjmp_buffer(output: &mut Object) -> SymbolId {
|
||||
let bss_section = output.section_id(StandardSection::Data);
|
||||
|
||||
// 8 registers + 3 words for a RocStr
|
||||
// TODO 50 is the wrong size here, look at implementation and put correct value in here
|
||||
const SIZE: usize = (8 + 50) * core::mem::size_of::<u64>();
|
||||
|
||||
let symbol = Symbol {
|
||||
name: b"setlongjmp_buffer".to_vec(),
|
||||
value: 0,
|
||||
size: SIZE as u64,
|
||||
kind: SymbolKind::Data,
|
||||
scope: SymbolScope::Dynamic,
|
||||
weak: false,
|
||||
section: SymbolSection::Section(bss_section),
|
||||
flags: SymbolFlags::None,
|
||||
};
|
||||
|
||||
let symbol_id = output.add_symbol(symbol);
|
||||
output.add_symbol_data(symbol_id, bss_section, &[0x00; SIZE], 8);
|
||||
|
||||
symbol_id
|
||||
}
|
||||
|
||||
fn generate_setjmp<'a, B: Backend<'a>>(backend: &mut B, output: &mut Object) {
|
||||
let text_section = output.section_id(StandardSection::Text);
|
||||
let proc_symbol = Symbol {
|
||||
name: b"roc_setjmp".to_vec(),
|
||||
value: 0,
|
||||
size: 0,
|
||||
kind: SymbolKind::Text,
|
||||
scope: SymbolScope::Dynamic,
|
||||
weak: false,
|
||||
section: SymbolSection::Section(text_section),
|
||||
flags: SymbolFlags::None,
|
||||
};
|
||||
let proc_id = output.add_symbol(proc_symbol);
|
||||
let proc_data = backend.build_roc_setjmp();
|
||||
|
||||
output.add_symbol_data(proc_id, text_section, proc_data, 16);
|
||||
}
|
||||
|
||||
fn generate_longjmp<'a, B: Backend<'a>>(backend: &mut B, output: &mut Object) {
|
||||
let text_section = output.section_id(StandardSection::Text);
|
||||
let proc_symbol = Symbol {
|
||||
name: b"roc_longjmp".to_vec(),
|
||||
value: 0,
|
||||
size: 0,
|
||||
kind: SymbolKind::Text,
|
||||
scope: SymbolScope::Dynamic,
|
||||
weak: false,
|
||||
section: SymbolSection::Section(text_section),
|
||||
flags: SymbolFlags::None,
|
||||
};
|
||||
let proc_id = output.add_symbol(proc_symbol);
|
||||
let proc_data = backend.build_roc_longjmp();
|
||||
|
||||
output.add_symbol_data(proc_id, text_section, proc_data, 16);
|
||||
}
|
||||
|
||||
// a roc_panic to be used in tests; relies on setjmp/longjmp
|
||||
fn generate_roc_panic<'a, B: Backend<'a>>(backend: &mut B, output: &mut Object) {
|
||||
let text_section = output.section_id(StandardSection::Text);
|
||||
let proc_symbol = Symbol {
|
||||
name: b"roc_panic".to_vec(),
|
||||
value: 0,
|
||||
size: 0,
|
||||
kind: SymbolKind::Text,
|
||||
scope: SymbolScope::Dynamic,
|
||||
weak: false,
|
||||
section: SymbolSection::Section(text_section),
|
||||
flags: SymbolFlags::None,
|
||||
};
|
||||
let proc_id = output.add_symbol(proc_symbol);
|
||||
let (proc_data, relocs) = backend.build_roc_panic();
|
||||
|
||||
let proc_offset = output.add_symbol_data(proc_id, text_section, proc_data, 16);
|
||||
|
||||
for r in relocs {
|
||||
let relocation = match r {
|
||||
Relocation::LinkedData { offset, name } => {
|
||||
if let Some(sym_id) = output.symbol_id(name.as_bytes()) {
|
||||
write::Relocation {
|
||||
offset: offset + proc_offset,
|
||||
size: 32,
|
||||
kind: RelocationKind::GotRelative,
|
||||
encoding: RelocationEncoding::Generic,
|
||||
symbol: sym_id,
|
||||
addend: -4,
|
||||
}
|
||||
} else {
|
||||
internal_error!("failed to find data symbol for {:?}", name);
|
||||
}
|
||||
}
|
||||
Relocation::LocalData { .. }
|
||||
| Relocation::LinkedFunction { .. }
|
||||
| Relocation::JmpToReturn { .. } => {
|
||||
unreachable!("not currently created by build_roc_panic")
|
||||
}
|
||||
};
|
||||
|
||||
output.add_relocation(text_section, relocation).unwrap();
|
||||
}
|
||||
}
|
||||
|
||||
fn generate_wrapper<'a, B: Backend<'a>>(
|
||||
backend: &mut B,
|
||||
output: &mut Object,
|
||||
|
@ -190,6 +312,12 @@ fn build_object<'a, B: Backend<'a>>(
|
|||
);
|
||||
*/
|
||||
|
||||
define_setlongjmp_buffer(&mut output);
|
||||
|
||||
generate_roc_panic(&mut backend, &mut output);
|
||||
generate_setjmp(&mut backend, &mut output);
|
||||
generate_longjmp(&mut backend, &mut output);
|
||||
|
||||
if backend.env().mode.generate_allocators() {
|
||||
generate_wrapper(
|
||||
&mut backend,
|
||||
|
@ -209,12 +337,7 @@ fn build_object<'a, B: Backend<'a>>(
|
|||
"roc_dealloc".into(),
|
||||
"free".into(),
|
||||
);
|
||||
generate_wrapper(
|
||||
&mut backend,
|
||||
&mut output,
|
||||
"roc_panic".into(),
|
||||
"roc_builtins.utils.test_panic".into(),
|
||||
);
|
||||
|
||||
// Extra symbols only required on unix systems.
|
||||
if matches!(output.format(), BinaryFormat::Elf | BinaryFormat::MachO) {
|
||||
generate_wrapper(
|
||||
|
@ -230,6 +353,15 @@ fn build_object<'a, B: Backend<'a>>(
|
|||
"roc_shm_open".into(),
|
||||
"shm_open".into(),
|
||||
);
|
||||
} else if matches!(output.format(), BinaryFormat::Coff) {
|
||||
// TODO figure out why this symbol is required, it should not be required
|
||||
// Without this it does not build on Windows
|
||||
generate_wrapper(
|
||||
&mut backend,
|
||||
&mut output,
|
||||
"roc_getppid".into(),
|
||||
"malloc".into(),
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -245,6 +377,18 @@ fn build_object<'a, B: Backend<'a>>(
|
|||
let exposed_proc = build_exposed_proc(&mut backend, &proc);
|
||||
let exposed_generic_proc = build_exposed_generic_proc(&mut backend, &proc);
|
||||
|
||||
let (module_id, layout_interner, interns, code_gen_help, _) =
|
||||
backend.module_interns_helpers_mut();
|
||||
|
||||
let ident_ids = interns.all_ident_ids.get_mut(&module_id).unwrap();
|
||||
|
||||
let test_helper = roc_mono::code_gen_help::test_helper(
|
||||
code_gen_help,
|
||||
ident_ids,
|
||||
layout_interner,
|
||||
&proc,
|
||||
);
|
||||
|
||||
#[cfg(debug_assertions)]
|
||||
{
|
||||
let module_id = exposed_generic_proc.name.name().module_id();
|
||||
|
@ -256,6 +400,18 @@ fn build_object<'a, B: Backend<'a>>(
|
|||
module_id.register_debug_idents(ident_ids);
|
||||
}
|
||||
|
||||
// println!("{}", test_helper.to_pretty(backend.interner(), 200, true));
|
||||
|
||||
build_proc_symbol(
|
||||
&mut output,
|
||||
&mut layout_ids,
|
||||
&mut procs,
|
||||
&mut backend,
|
||||
layout,
|
||||
test_helper,
|
||||
Exposed::TestMain,
|
||||
);
|
||||
|
||||
build_proc_symbol(
|
||||
&mut output,
|
||||
&mut layout_ids,
|
||||
|
@ -343,8 +499,8 @@ fn build_object<'a, B: Backend<'a>>(
|
|||
for ((sym, layout), proc) in helper_symbols_and_layouts.into_iter().zip(helper_procs) {
|
||||
debug_assert_eq!(sym, proc.name.name());
|
||||
|
||||
let fn_name = backend.function_symbol_to_string(
|
||||
sym,
|
||||
let fn_name = backend.lambda_name_to_string(
|
||||
LambdaName::no_niche(sym),
|
||||
layout.arguments.iter().copied(),
|
||||
None,
|
||||
layout.result,
|
||||
|
@ -443,7 +599,7 @@ fn build_exposed_proc<'a, B: Backend<'a>>(backend: &mut B, proc: &Proc<'a>) -> P
|
|||
closure_data_layout: None,
|
||||
ret_layout: proc.ret_layout,
|
||||
is_self_recursive: roc_mono::ir::SelfRecursive::NotSelfRecursive,
|
||||
host_exposed_layouts: roc_mono::ir::HostExposedLayouts::NotHostExposed,
|
||||
is_erased: proc.is_erased,
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -464,7 +620,7 @@ fn build_exposed_generic_proc<'a, B: Backend<'a>>(backend: &mut B, proc: &Proc<'
|
|||
|
||||
let box_layout = backend
|
||||
.interner_mut()
|
||||
.insert_direct_no_semantic(roc_mono::layout::LayoutRepr::Boxed(proc.ret_layout));
|
||||
.insert_direct_no_semantic(roc_mono::layout::LayoutRepr::Ptr(proc.ret_layout));
|
||||
|
||||
let mut args = bumpalo::collections::Vec::new_in(arena);
|
||||
args.extend(proc.args);
|
||||
|
@ -485,7 +641,7 @@ fn build_exposed_generic_proc<'a, B: Backend<'a>>(backend: &mut B, proc: &Proc<'
|
|||
|
||||
let box_write = Call {
|
||||
call_type: roc_mono::ir::CallType::LowLevel {
|
||||
op: roc_module::low_level::LowLevel::PtrWrite,
|
||||
op: roc_module::low_level::LowLevel::PtrStore,
|
||||
update_mode: UpdateModeId::BACKEND_DUMMY,
|
||||
},
|
||||
arguments: arena.alloc([arg_generic, s1]),
|
||||
|
@ -524,7 +680,7 @@ fn build_exposed_generic_proc<'a, B: Backend<'a>>(backend: &mut B, proc: &Proc<'
|
|||
closure_data_layout: None,
|
||||
ret_layout: roc_mono::layout::Layout::UNIT,
|
||||
is_self_recursive: roc_mono::ir::SelfRecursive::NotSelfRecursive,
|
||||
host_exposed_layouts: roc_mono::ir::HostExposedLayouts::NotHostExposed,
|
||||
is_erased: proc.is_erased,
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -533,6 +689,7 @@ enum Exposed {
|
|||
ExposedGeneric,
|
||||
Exposed,
|
||||
NotExposed,
|
||||
TestMain,
|
||||
}
|
||||
|
||||
fn build_proc_symbol<'a, B: Backend<'a>>(
|
||||
|
@ -559,12 +716,13 @@ fn build_proc_symbol<'a, B: Backend<'a>>(
|
|||
Exposed::Exposed => layout_ids
|
||||
.get_toplevel(sym, &layout)
|
||||
.to_exposed_symbol_string(sym, backend.interns()),
|
||||
Exposed::NotExposed => backend.function_symbol_to_string(
|
||||
sym,
|
||||
Exposed::NotExposed => backend.lambda_name_to_string(
|
||||
proc.name,
|
||||
layout.arguments.iter().copied(),
|
||||
None,
|
||||
layout.result,
|
||||
),
|
||||
Exposed::TestMain => String::from("test_main"),
|
||||
};
|
||||
|
||||
let proc_symbol = Symbol {
|
||||
|
@ -575,7 +733,7 @@ fn build_proc_symbol<'a, B: Backend<'a>>(
|
|||
// TODO: Depending on whether we are building a static or dynamic lib, this should change.
|
||||
// We should use Dynamic -> anyone, Linkage -> static link, Compilation -> this module only.
|
||||
scope: match exposed {
|
||||
Exposed::ExposedGeneric | Exposed::Exposed => SymbolScope::Dynamic,
|
||||
Exposed::ExposedGeneric | Exposed::Exposed | Exposed::TestMain => SymbolScope::Dynamic,
|
||||
Exposed::NotExposed => SymbolScope::Linkage,
|
||||
},
|
||||
weak: false,
|
||||
|
@ -605,7 +763,7 @@ fn build_proc<'a, B: Backend<'a>>(
|
|||
let elfreloc = match reloc {
|
||||
Relocation::LocalData { offset, data } => {
|
||||
let data_symbol = write::Symbol {
|
||||
name: format!("{}.data{}", fn_name, local_data_index)
|
||||
name: format!("{fn_name}.data{local_data_index}")
|
||||
.as_bytes()
|
||||
.to_vec(),
|
||||
value: 0,
|
||||
|
|
Some files were not shown because too many files have changed in this diff Show more
Loading…
Add table
Add a link
Reference in a new issue