mirror of
https://github.com/roc-lang/roc.git
synced 2025-09-28 06:14:46 +00:00
Merge remote-tracking branch 'origin/can-builtins-simplify' into list-range
This commit is contained in:
commit
f2c144f58c
55 changed files with 2303 additions and 1059 deletions
|
@ -4,6 +4,8 @@ const RocResult = utils.RocResult;
|
||||||
const mem = std.mem;
|
const mem = std.mem;
|
||||||
const Allocator = mem.Allocator;
|
const Allocator = mem.Allocator;
|
||||||
|
|
||||||
|
const TAG_WIDTH = 8;
|
||||||
|
|
||||||
const EqFn = fn (?[*]u8, ?[*]u8) callconv(.C) bool;
|
const EqFn = fn (?[*]u8, ?[*]u8) callconv(.C) bool;
|
||||||
const Opaque = ?[*]u8;
|
const Opaque = ?[*]u8;
|
||||||
|
|
||||||
|
@ -502,6 +504,49 @@ pub fn listWalkBackwards(list: RocList, stepper: Opaque, stepper_caller: Caller2
|
||||||
utils.decref(std.heap.c_allocator, alignment, list.bytes, data_bytes);
|
utils.decref(std.heap.c_allocator, alignment, list.bytes, data_bytes);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
pub fn listWalkUntil(list: RocList, stepper: Opaque, stepper_caller: Caller2, accum: Opaque, alignment: usize, element_width: usize, accum_width: usize, dec: Dec, output: Opaque) callconv(.C) void {
|
||||||
|
// [ Continue a, Stop a ]
|
||||||
|
const CONTINUE: usize = 0;
|
||||||
|
|
||||||
|
if (accum_width == 0) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (list.isEmpty()) {
|
||||||
|
@memcpy(output orelse unreachable, accum orelse unreachable, accum_width);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
const alloc: [*]u8 = @ptrCast([*]u8, std.heap.c_allocator.alloc(u8, TAG_WIDTH + accum_width) catch unreachable);
|
||||||
|
|
||||||
|
@memcpy(alloc + TAG_WIDTH, accum orelse unreachable, accum_width);
|
||||||
|
|
||||||
|
if (list.bytes) |source_ptr| {
|
||||||
|
var i: usize = 0;
|
||||||
|
const size = list.len();
|
||||||
|
while (i < size) : (i += 1) {
|
||||||
|
const element = source_ptr + i * element_width;
|
||||||
|
stepper_caller(stepper, element, alloc + TAG_WIDTH, alloc);
|
||||||
|
|
||||||
|
const usizes: [*]usize = @ptrCast([*]usize, @alignCast(8, alloc));
|
||||||
|
if (usizes[0] != 0) {
|
||||||
|
// decrement refcount of the remaining items
|
||||||
|
i += 1;
|
||||||
|
while (i < size) : (i += 1) {
|
||||||
|
dec(source_ptr + i * element_width);
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@memcpy(output orelse unreachable, alloc + TAG_WIDTH, accum_width);
|
||||||
|
std.heap.c_allocator.free(alloc[0 .. TAG_WIDTH + accum_width]);
|
||||||
|
|
||||||
|
const data_bytes = list.len() * element_width;
|
||||||
|
utils.decref(std.heap.c_allocator, alignment, list.bytes, data_bytes);
|
||||||
|
}
|
||||||
|
|
||||||
// List.contains : List k, k -> Bool
|
// List.contains : List k, k -> Bool
|
||||||
pub fn listContains(list: RocList, key: Opaque, key_width: usize, is_eq: EqFn) callconv(.C) bool {
|
pub fn listContains(list: RocList, key: Opaque, key_width: usize, is_eq: EqFn) callconv(.C) bool {
|
||||||
if (list.bytes) |source_ptr| {
|
if (list.bytes) |source_ptr| {
|
||||||
|
|
|
@ -12,6 +12,7 @@ comptime {
|
||||||
exportListFn(list.listMapWithIndex, "map_with_index");
|
exportListFn(list.listMapWithIndex, "map_with_index");
|
||||||
exportListFn(list.listKeepIf, "keep_if");
|
exportListFn(list.listKeepIf, "keep_if");
|
||||||
exportListFn(list.listWalk, "walk");
|
exportListFn(list.listWalk, "walk");
|
||||||
|
exportListFn(list.listWalkUntil, "walkUntil");
|
||||||
exportListFn(list.listWalkBackwards, "walk_backwards");
|
exportListFn(list.listWalkBackwards, "walk_backwards");
|
||||||
exportListFn(list.listKeepOks, "keep_oks");
|
exportListFn(list.listKeepOks, "keep_oks");
|
||||||
exportListFn(list.listKeepErrs, "keep_errs");
|
exportListFn(list.listKeepErrs, "keep_errs");
|
||||||
|
|
|
@ -195,7 +195,7 @@ interface List2
|
||||||
## * 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.fold, and #List.keepIf would all need to traverse every element in the list and build up the result from scratch. These operations are all
|
## * 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.fold, 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.
|
## * 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!
|
## * 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!
|
||||||
List elem : @List elem
|
List elem : [ @List elem ]
|
||||||
|
|
||||||
## Initialize
|
## Initialize
|
||||||
|
|
||||||
|
|
|
@ -1,7 +1,11 @@
|
||||||
interface Set2
|
interface Set
|
||||||
exposes [ empty, isEmpty, len, add, drop, map ]
|
exposes [ Set, empty, isEmpty, len, add, drop, map ]
|
||||||
imports []
|
imports []
|
||||||
|
|
||||||
|
## Set
|
||||||
|
|
||||||
|
## A Set is an unordered collection of unique elements.
|
||||||
|
Set elem : [ @Set elem ]
|
||||||
|
|
||||||
## An empty set.
|
## An empty set.
|
||||||
empty : Set *
|
empty : Set *
|
||||||
|
|
|
@ -70,6 +70,7 @@ pub const LIST_KEEP_IF: &str = "roc_builtins.list.keep_if";
|
||||||
pub const LIST_KEEP_OKS: &str = "roc_builtins.list.keep_oks";
|
pub const LIST_KEEP_OKS: &str = "roc_builtins.list.keep_oks";
|
||||||
pub const LIST_KEEP_ERRS: &str = "roc_builtins.list.keep_errs";
|
pub const LIST_KEEP_ERRS: &str = "roc_builtins.list.keep_errs";
|
||||||
pub const LIST_WALK: &str = "roc_builtins.list.walk";
|
pub const LIST_WALK: &str = "roc_builtins.list.walk";
|
||||||
|
pub const LIST_WALK_UNTIL: &str = "roc_builtins.list.walkUntil";
|
||||||
pub const LIST_WALK_BACKWARDS: &str = "roc_builtins.list.walk_backwards";
|
pub const LIST_WALK_BACKWARDS: &str = "roc_builtins.list.walk_backwards";
|
||||||
pub const LIST_CONTAINS: &str = "roc_builtins.list.contains";
|
pub const LIST_CONTAINS: &str = "roc_builtins.list.contains";
|
||||||
pub const LIST_REPEAT: &str = "roc_builtins.list.repeat";
|
pub const LIST_REPEAT: &str = "roc_builtins.list.repeat";
|
||||||
|
|
|
@ -771,6 +771,34 @@ pub fn types() -> MutMap<Symbol, (SolvedType, Region)> {
|
||||||
),
|
),
|
||||||
);
|
);
|
||||||
|
|
||||||
|
fn until_type(content: SolvedType) -> SolvedType {
|
||||||
|
// [ LT, EQ, GT ]
|
||||||
|
SolvedType::TagUnion(
|
||||||
|
vec![
|
||||||
|
(TagName::Global("Continue".into()), vec![content.clone()]),
|
||||||
|
(TagName::Global("Stop".into()), vec![content]),
|
||||||
|
],
|
||||||
|
Box::new(SolvedType::EmptyTagUnion),
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
// walkUntil : List elem, (elem -> accum -> [ Continue accum, Stop accum ]), accum -> accum
|
||||||
|
add_type(
|
||||||
|
Symbol::LIST_WALK_UNTIL,
|
||||||
|
top_level_function(
|
||||||
|
vec![
|
||||||
|
list_type(flex(TVAR1)),
|
||||||
|
closure(
|
||||||
|
vec![flex(TVAR1), flex(TVAR2)],
|
||||||
|
TVAR3,
|
||||||
|
Box::new(until_type(flex(TVAR2))),
|
||||||
|
),
|
||||||
|
flex(TVAR2),
|
||||||
|
],
|
||||||
|
Box::new(flex(TVAR2)),
|
||||||
|
),
|
||||||
|
);
|
||||||
|
|
||||||
// keepIf : List elem, (elem -> Bool) -> List elem
|
// keepIf : List elem, (elem -> Bool) -> List elem
|
||||||
add_type(
|
add_type(
|
||||||
Symbol::LIST_KEEP_IF,
|
Symbol::LIST_KEEP_IF,
|
||||||
|
|
|
@ -2,7 +2,7 @@ use crate::def::Def;
|
||||||
use crate::expr::Expr::*;
|
use crate::expr::Expr::*;
|
||||||
use crate::expr::{Expr, Recursive, WhenBranch};
|
use crate::expr::{Expr, Recursive, WhenBranch};
|
||||||
use crate::pattern::Pattern;
|
use crate::pattern::Pattern;
|
||||||
use roc_collections::all::{MutMap, SendMap};
|
use roc_collections::all::SendMap;
|
||||||
use roc_module::ident::TagName;
|
use roc_module::ident::TagName;
|
||||||
use roc_module::low_level::LowLevel;
|
use roc_module::low_level::LowLevel;
|
||||||
use roc_module::operator::CalledVia;
|
use roc_module::operator::CalledVia;
|
||||||
|
@ -10,23 +10,6 @@ use roc_module::symbol::Symbol;
|
||||||
use roc_region::all::{Located, Region};
|
use roc_region::all::{Located, Region};
|
||||||
use roc_types::subs::{VarStore, Variable};
|
use roc_types::subs::{VarStore, Variable};
|
||||||
|
|
||||||
macro_rules! defs {
|
|
||||||
(@single $($x:tt)*) => (());
|
|
||||||
(@count $($rest:expr),*) => (<[()]>::len(&[$(defs!(@single $rest)),*]));
|
|
||||||
|
|
||||||
($var_store:expr; $($key:expr => $func:expr,)+) => { defs!($var_store; $($key => $func),+) };
|
|
||||||
($var_store:expr; $($key:expr => $func:expr),*) => {
|
|
||||||
{
|
|
||||||
let _cap = defs!(@count $($key),*);
|
|
||||||
let mut _map = ::std::collections::HashMap::with_capacity_and_hasher(_cap, roc_collections::all::default_hasher());
|
|
||||||
$(
|
|
||||||
let _ = _map.insert($key, $func($key, $var_store));
|
|
||||||
)*
|
|
||||||
_map
|
|
||||||
}
|
|
||||||
};
|
|
||||||
}
|
|
||||||
|
|
||||||
macro_rules! macro_magic {
|
macro_rules! macro_magic {
|
||||||
(@single $($x:tt)*) => (());
|
(@single $($x:tt)*) => (());
|
||||||
(@count $($rest:expr),*) => (<[()]>::len(&[$(matches!(@single $rest)),*]));
|
(@count $($rest:expr),*) => (<[()]>::len(&[$(matches!(@single $rest)),*]));
|
||||||
|
@ -44,6 +27,23 @@ macro_rules! macro_magic {
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Some builtins cannot be constructed in code gen alone, and need to be defined
|
||||||
|
/// as separate Roc defs. For example, List.get has this type:
|
||||||
|
///
|
||||||
|
/// List.get : List elem, Int -> Result elem [ OutOfBounds ]*
|
||||||
|
///
|
||||||
|
/// Because this returns an open tag union for its Err type, it's not possible
|
||||||
|
/// for code gen to return a hardcoded value for OutOfBounds. For example,
|
||||||
|
/// if this Result unifies to [ Foo, OutOfBounds ] then OutOfBOunds will
|
||||||
|
/// get assigned the number 1 (because Foo got 0 alphabetically), whereas
|
||||||
|
/// if it unifies to [ OutOfBounds, Qux ] then OutOfBounds will get the number 0.
|
||||||
|
///
|
||||||
|
/// Getting these numbers right requires having List.get participate in the
|
||||||
|
/// normal type-checking and monomorphization processes. As such, this function
|
||||||
|
/// returns a normal def for List.get, which performs a bounds check and then
|
||||||
|
/// delegates to the compiler-internal List.getUnsafe function to do the actual
|
||||||
|
/// lookup (if the bounds check passed). That internal function is hardcoded in code gen,
|
||||||
|
/// which works fine because it doesn't involve any open tag unions.
|
||||||
pub fn builtin_defs_map(symbol: Symbol, var_store: &mut VarStore) -> Option<Def> {
|
pub fn builtin_defs_map(symbol: Symbol, var_store: &mut VarStore) -> Option<Def> {
|
||||||
debug_assert!(symbol.is_builtin());
|
debug_assert!(symbol.is_builtin());
|
||||||
|
|
||||||
|
@ -90,6 +90,7 @@ pub fn builtin_defs_map(symbol: Symbol, var_store: &mut VarStore) -> Option<Def>
|
||||||
LIST_RANGE => list_range,
|
LIST_RANGE => list_range,
|
||||||
LIST_WALK => list_walk,
|
LIST_WALK => list_walk,
|
||||||
LIST_WALK_BACKWARDS => list_walk_backwards,
|
LIST_WALK_BACKWARDS => list_walk_backwards,
|
||||||
|
LIST_WALK_UNTIL => list_walk_until,
|
||||||
DICT_TEST_HASH => dict_hash_test_only,
|
DICT_TEST_HASH => dict_hash_test_only,
|
||||||
DICT_LEN => dict_len,
|
DICT_LEN => dict_len,
|
||||||
DICT_EMPTY => dict_empty,
|
DICT_EMPTY => dict_empty,
|
||||||
|
@ -172,145 +173,6 @@ pub fn builtin_defs_map(symbol: Symbol, var_store: &mut VarStore) -> Option<Def>
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Some builtins cannot be constructed in code gen alone, and need to be defined
|
|
||||||
/// as separate Roc defs. For example, List.get has this type:
|
|
||||||
///
|
|
||||||
/// List.get : List elem, Int -> Result elem [ OutOfBounds ]*
|
|
||||||
///
|
|
||||||
/// Because this returns an open tag union for its Err type, it's not possible
|
|
||||||
/// for code gen to return a hardcoded value for OutOfBounds. For example,
|
|
||||||
/// if this Result unifies to [ Foo, OutOfBounds ] then OutOfBOunds will
|
|
||||||
/// get assigned the number 1 (because Foo got 0 alphabetically), whereas
|
|
||||||
/// if it unifies to [ OutOfBounds, Qux ] then OutOfBounds will get the number 0.
|
|
||||||
///
|
|
||||||
/// Getting these numbers right requires having List.get participate in the
|
|
||||||
/// normal type-checking and monomorphization processes. As such, this function
|
|
||||||
/// returns a normal def for List.get, which performs a bounds check and then
|
|
||||||
/// delegates to the compiler-internal List.getUnsafe function to do the actual
|
|
||||||
/// lookup (if the bounds check passed). That internal function is hardcoded in code gen,
|
|
||||||
/// which works fine because it doesn't involve any open tag unions.
|
|
||||||
pub fn builtin_defs(var_store: &mut VarStore) -> MutMap<Symbol, Def> {
|
|
||||||
defs! { var_store;
|
|
||||||
Symbol::BOOL_EQ => bool_eq,
|
|
||||||
Symbol::BOOL_NEQ => bool_neq,
|
|
||||||
Symbol::BOOL_AND => bool_and,
|
|
||||||
Symbol::BOOL_OR => bool_or,
|
|
||||||
Symbol::BOOL_NOT => bool_not,
|
|
||||||
Symbol::STR_CONCAT => str_concat,
|
|
||||||
Symbol::STR_JOIN_WITH => str_join_with,
|
|
||||||
Symbol::STR_SPLIT => str_split,
|
|
||||||
Symbol::STR_IS_EMPTY => str_is_empty,
|
|
||||||
Symbol::STR_STARTS_WITH => str_starts_with,
|
|
||||||
Symbol::STR_ENDS_WITH => str_ends_with,
|
|
||||||
Symbol::STR_COUNT_GRAPHEMES => str_count_graphemes,
|
|
||||||
Symbol::STR_FROM_INT => str_from_int,
|
|
||||||
Symbol::STR_FROM_UTF8 => str_from_utf8,
|
|
||||||
Symbol::STR_TO_BYTES => str_to_bytes,
|
|
||||||
Symbol::STR_FROM_FLOAT=> str_from_float,
|
|
||||||
Symbol::LIST_LEN => list_len,
|
|
||||||
Symbol::LIST_GET => list_get,
|
|
||||||
Symbol::LIST_SET => list_set,
|
|
||||||
Symbol::LIST_APPEND => list_append,
|
|
||||||
Symbol::LIST_FIRST => list_first,
|
|
||||||
Symbol::LIST_LAST => list_last,
|
|
||||||
Symbol::LIST_IS_EMPTY => list_is_empty,
|
|
||||||
Symbol::LIST_SINGLE => list_single,
|
|
||||||
Symbol::LIST_REPEAT => list_repeat,
|
|
||||||
Symbol::LIST_REVERSE => list_reverse,
|
|
||||||
Symbol::LIST_CONCAT => list_concat,
|
|
||||||
Symbol::LIST_CONTAINS => list_contains,
|
|
||||||
Symbol::LIST_SUM => list_sum,
|
|
||||||
Symbol::LIST_PRODUCT => list_product,
|
|
||||||
Symbol::LIST_PREPEND => list_prepend,
|
|
||||||
Symbol::LIST_JOIN => list_join,
|
|
||||||
Symbol::LIST_MAP => list_map,
|
|
||||||
Symbol::LIST_MAP2 => list_map2,
|
|
||||||
Symbol::LIST_MAP3 => list_map3,
|
|
||||||
Symbol::LIST_MAP_WITH_INDEX => list_map_with_index,
|
|
||||||
Symbol::LIST_KEEP_IF => list_keep_if,
|
|
||||||
Symbol::LIST_KEEP_OKS => list_keep_oks,
|
|
||||||
Symbol::LIST_KEEP_ERRS=> list_keep_errs,
|
|
||||||
Symbol::LIST_RANGE => list_range,
|
|
||||||
Symbol::LIST_WALK => list_walk,
|
|
||||||
Symbol::LIST_WALK_BACKWARDS => list_walk_backwards,
|
|
||||||
Symbol::DICT_TEST_HASH => dict_hash_test_only,
|
|
||||||
Symbol::DICT_LEN => dict_len,
|
|
||||||
Symbol::DICT_EMPTY => dict_empty,
|
|
||||||
Symbol::DICT_SINGLE => dict_single,
|
|
||||||
Symbol::DICT_INSERT => dict_insert,
|
|
||||||
Symbol::DICT_REMOVE => dict_remove,
|
|
||||||
Symbol::DICT_GET => dict_get,
|
|
||||||
Symbol::DICT_CONTAINS => dict_contains,
|
|
||||||
Symbol::DICT_KEYS => dict_keys,
|
|
||||||
Symbol::DICT_VALUES => dict_values,
|
|
||||||
Symbol::DICT_UNION=> dict_union,
|
|
||||||
Symbol::DICT_INTERSECTION=> dict_intersection,
|
|
||||||
Symbol::DICT_DIFFERENCE=> dict_difference,
|
|
||||||
Symbol::DICT_WALK=> dict_walk,
|
|
||||||
Symbol::SET_EMPTY => set_empty,
|
|
||||||
Symbol::SET_LEN => set_len,
|
|
||||||
Symbol::SET_SINGLE => set_single,
|
|
||||||
Symbol::SET_UNION=> set_union,
|
|
||||||
Symbol::SET_INTERSECTION=> set_intersection,
|
|
||||||
Symbol::SET_DIFFERENCE=> set_difference,
|
|
||||||
Symbol::SET_TO_LIST => set_to_list,
|
|
||||||
Symbol::SET_FROM_LIST => set_from_list,
|
|
||||||
Symbol::SET_INSERT => set_insert,
|
|
||||||
Symbol::SET_REMOVE => set_remove,
|
|
||||||
Symbol::SET_CONTAINS => set_contains,
|
|
||||||
Symbol::SET_WALK => set_walk,
|
|
||||||
Symbol::NUM_ADD => num_add,
|
|
||||||
Symbol::NUM_ADD_CHECKED => num_add_checked,
|
|
||||||
Symbol::NUM_ADD_WRAP => num_add_wrap,
|
|
||||||
Symbol::NUM_SUB => num_sub,
|
|
||||||
Symbol::NUM_MUL => num_mul,
|
|
||||||
Symbol::NUM_GT => num_gt,
|
|
||||||
Symbol::NUM_GTE => num_gte,
|
|
||||||
Symbol::NUM_LT => num_lt,
|
|
||||||
Symbol::NUM_LTE => num_lte,
|
|
||||||
Symbol::NUM_COMPARE => num_compare,
|
|
||||||
Symbol::NUM_SIN => num_sin,
|
|
||||||
Symbol::NUM_COS => num_cos,
|
|
||||||
Symbol::NUM_TAN => num_tan,
|
|
||||||
Symbol::NUM_DIV_FLOAT => num_div_float,
|
|
||||||
Symbol::NUM_DIV_INT => num_div_int,
|
|
||||||
Symbol::NUM_ABS => num_abs,
|
|
||||||
Symbol::NUM_NEG => num_neg,
|
|
||||||
Symbol::NUM_REM => num_rem,
|
|
||||||
Symbol::NUM_IS_MULTIPLE_OF => num_is_multiple_of,
|
|
||||||
Symbol::NUM_SQRT => num_sqrt,
|
|
||||||
Symbol::NUM_LOG => num_log,
|
|
||||||
Symbol::NUM_ROUND => num_round,
|
|
||||||
Symbol::NUM_IS_ODD => num_is_odd,
|
|
||||||
Symbol::NUM_IS_EVEN => num_is_even,
|
|
||||||
Symbol::NUM_IS_ZERO => num_is_zero,
|
|
||||||
Symbol::NUM_IS_POSITIVE => num_is_positive,
|
|
||||||
Symbol::NUM_IS_NEGATIVE => num_is_negative,
|
|
||||||
Symbol::NUM_TO_FLOAT => num_to_float,
|
|
||||||
Symbol::NUM_POW => num_pow,
|
|
||||||
Symbol::NUM_CEILING => num_ceiling,
|
|
||||||
Symbol::NUM_POW_INT => num_pow_int,
|
|
||||||
Symbol::NUM_FLOOR => num_floor,
|
|
||||||
Symbol::NUM_ATAN => num_atan,
|
|
||||||
Symbol::NUM_ACOS => num_acos,
|
|
||||||
Symbol::NUM_ASIN => num_asin,
|
|
||||||
Symbol::NUM_MAX_INT => num_max_int,
|
|
||||||
Symbol::NUM_MIN_INT => num_min_int,
|
|
||||||
Symbol::NUM_BITWISE_AND => num_bitwise_and,
|
|
||||||
Symbol::NUM_BITWISE_XOR => num_bitwise_xor,
|
|
||||||
Symbol::NUM_BITWISE_OR => num_bitwise_or,
|
|
||||||
Symbol::NUM_SHIFT_LEFT => num_shift_left_by,
|
|
||||||
Symbol::NUM_SHIFT_RIGHT => num_shift_right_by,
|
|
||||||
Symbol::NUM_SHIFT_RIGHT_ZERO_FILL => num_shift_right_zf_by,
|
|
||||||
Symbol::NUM_INT_CAST=> num_int_cast,
|
|
||||||
Symbol::NUM_MAX_I128=> num_max_i128,
|
|
||||||
Symbol::RESULT_MAP => result_map,
|
|
||||||
Symbol::RESULT_MAP_ERR => result_map_err,
|
|
||||||
Symbol::RESULT_AFTER => result_after,
|
|
||||||
Symbol::RESULT_WITH_DEFAULT => result_with_default,
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
fn lowlevel_1(symbol: Symbol, op: LowLevel, var_store: &mut VarStore) -> Def {
|
fn lowlevel_1(symbol: Symbol, op: LowLevel, var_store: &mut VarStore) -> Def {
|
||||||
let arg1_var = var_store.fresh();
|
let arg1_var = var_store.fresh();
|
||||||
let ret_var = var_store.fresh();
|
let ret_var = var_store.fresh();
|
||||||
|
@ -2096,70 +1958,101 @@ fn list_join(symbol: Symbol, var_store: &mut VarStore) -> Def {
|
||||||
|
|
||||||
/// List.walk : List elem, (elem -> accum -> accum), accum -> accum
|
/// List.walk : List elem, (elem -> accum -> accum), accum -> accum
|
||||||
fn list_walk(symbol: Symbol, var_store: &mut VarStore) -> Def {
|
fn list_walk(symbol: Symbol, var_store: &mut VarStore) -> Def {
|
||||||
|
lowlevel_3(symbol, LowLevel::ListWalk, var_store)
|
||||||
|
}
|
||||||
|
|
||||||
|
/// List.walkBackwards : List elem, (elem -> accum -> accum), accum -> accum
|
||||||
|
fn list_walk_backwards(symbol: Symbol, var_store: &mut VarStore) -> Def {
|
||||||
|
lowlevel_3(symbol, LowLevel::ListWalkBackwards, var_store)
|
||||||
|
}
|
||||||
|
|
||||||
|
/// List.walkUntil : List elem, (elem, accum -> [ Continue accum, Stop accum ]), accum -> accum
|
||||||
|
fn list_walk_until(symbol: Symbol, var_store: &mut VarStore) -> Def {
|
||||||
|
lowlevel_3(symbol, LowLevel::ListWalkUntil, var_store)
|
||||||
|
}
|
||||||
|
|
||||||
|
/// List.sum : List (Num a) -> Num a
|
||||||
|
fn list_sum(symbol: Symbol, var_store: &mut VarStore) -> Def {
|
||||||
|
let num_var = var_store.fresh();
|
||||||
|
let ret_var = num_var;
|
||||||
let list_var = var_store.fresh();
|
let list_var = var_store.fresh();
|
||||||
let func_var = var_store.fresh();
|
let closure_var = var_store.fresh();
|
||||||
let accum_var = var_store.fresh();
|
|
||||||
|
|
||||||
let body = RunLowLevel {
|
let body = RunLowLevel {
|
||||||
op: LowLevel::ListWalk,
|
op: LowLevel::ListWalk,
|
||||||
args: vec![
|
args: vec![
|
||||||
(list_var, Var(Symbol::ARG_1)),
|
(list_var, Var(Symbol::ARG_1)),
|
||||||
(func_var, Var(Symbol::ARG_2)),
|
(closure_var, list_sum_add(num_var, var_store)),
|
||||||
(accum_var, Var(Symbol::ARG_3)),
|
(num_var, Num(var_store.fresh(), 0)),
|
||||||
],
|
],
|
||||||
ret_var: accum_var,
|
ret_var,
|
||||||
};
|
};
|
||||||
|
|
||||||
defn(
|
defn(
|
||||||
symbol,
|
symbol,
|
||||||
vec![
|
vec![(list_var, Symbol::ARG_1)],
|
||||||
(list_var, Symbol::ARG_1),
|
|
||||||
(func_var, Symbol::ARG_2),
|
|
||||||
(accum_var, Symbol::ARG_3),
|
|
||||||
],
|
|
||||||
var_store,
|
var_store,
|
||||||
body,
|
body,
|
||||||
accum_var,
|
ret_var,
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
/// List.walkBackwards : List elem, (elem -> accum -> accum), accum -> accum
|
fn list_sum_add(num_var: Variable, var_store: &mut VarStore) -> Expr {
|
||||||
fn list_walk_backwards(symbol: Symbol, var_store: &mut VarStore) -> Def {
|
|
||||||
let list_var = var_store.fresh();
|
|
||||||
let func_var = var_store.fresh();
|
|
||||||
let accum_var = var_store.fresh();
|
|
||||||
|
|
||||||
let body = RunLowLevel {
|
let body = RunLowLevel {
|
||||||
op: LowLevel::ListWalkBackwards,
|
op: LowLevel::NumAdd,
|
||||||
args: vec![
|
args: vec![(num_var, Var(Symbol::ARG_3)), (num_var, Var(Symbol::ARG_4))],
|
||||||
(list_var, Var(Symbol::ARG_1)),
|
ret_var: num_var,
|
||||||
(func_var, Var(Symbol::ARG_2)),
|
|
||||||
(accum_var, Var(Symbol::ARG_3)),
|
|
||||||
],
|
|
||||||
ret_var: accum_var,
|
|
||||||
};
|
};
|
||||||
|
|
||||||
defn(
|
defn_help(
|
||||||
symbol,
|
Symbol::LIST_SUM_ADD,
|
||||||
vec![
|
vec![(num_var, Symbol::ARG_3), (num_var, Symbol::ARG_4)],
|
||||||
(list_var, Symbol::ARG_1),
|
|
||||||
(func_var, Symbol::ARG_2),
|
|
||||||
(accum_var, Symbol::ARG_3),
|
|
||||||
],
|
|
||||||
var_store,
|
var_store,
|
||||||
body,
|
body,
|
||||||
accum_var,
|
num_var,
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
/// List.sum : List (Num a) -> Num a
|
|
||||||
fn list_sum(symbol: Symbol, var_store: &mut VarStore) -> Def {
|
|
||||||
lowlevel_1(symbol, LowLevel::ListSum, var_store)
|
|
||||||
}
|
|
||||||
|
|
||||||
/// List.product : List (Num a) -> Num a
|
/// List.product : List (Num a) -> Num a
|
||||||
fn list_product(symbol: Symbol, var_store: &mut VarStore) -> Def {
|
fn list_product(symbol: Symbol, var_store: &mut VarStore) -> Def {
|
||||||
lowlevel_1(symbol, LowLevel::ListProduct, var_store)
|
let num_var = var_store.fresh();
|
||||||
|
let ret_var = num_var;
|
||||||
|
let list_var = var_store.fresh();
|
||||||
|
let closure_var = var_store.fresh();
|
||||||
|
|
||||||
|
let body = RunLowLevel {
|
||||||
|
op: LowLevel::ListWalk,
|
||||||
|
args: vec![
|
||||||
|
(list_var, Var(Symbol::ARG_1)),
|
||||||
|
(closure_var, list_product_mul(num_var, var_store)),
|
||||||
|
(num_var, Num(var_store.fresh(), 1)),
|
||||||
|
],
|
||||||
|
ret_var,
|
||||||
|
};
|
||||||
|
|
||||||
|
defn(
|
||||||
|
symbol,
|
||||||
|
vec![(list_var, Symbol::ARG_1)],
|
||||||
|
var_store,
|
||||||
|
body,
|
||||||
|
ret_var,
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
fn list_product_mul(num_var: Variable, var_store: &mut VarStore) -> Expr {
|
||||||
|
let body = RunLowLevel {
|
||||||
|
op: LowLevel::NumMul,
|
||||||
|
args: vec![(num_var, Var(Symbol::ARG_3)), (num_var, Var(Symbol::ARG_4))],
|
||||||
|
ret_var: num_var,
|
||||||
|
};
|
||||||
|
|
||||||
|
defn_help(
|
||||||
|
Symbol::LIST_PRODUCT_MUL,
|
||||||
|
vec![(num_var, Symbol::ARG_3), (num_var, Symbol::ARG_4)],
|
||||||
|
var_store,
|
||||||
|
body,
|
||||||
|
num_var,
|
||||||
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
/// List.keepIf : List elem, (elem -> Bool) -> List elem
|
/// List.keepIf : List elem, (elem -> Bool) -> List elem
|
||||||
|
@ -3388,24 +3281,7 @@ fn defn(
|
||||||
body: Expr,
|
body: Expr,
|
||||||
ret_var: Variable,
|
ret_var: Variable,
|
||||||
) -> Def {
|
) -> Def {
|
||||||
use crate::pattern::Pattern::*;
|
let expr = defn_help(fn_name, args, var_store, body, ret_var);
|
||||||
|
|
||||||
let closure_args = args
|
|
||||||
.into_iter()
|
|
||||||
.map(|(var, symbol)| (var, no_region(Identifier(symbol))))
|
|
||||||
.collect();
|
|
||||||
|
|
||||||
let expr = Closure {
|
|
||||||
function_type: var_store.fresh(),
|
|
||||||
closure_type: var_store.fresh(),
|
|
||||||
closure_ext_var: var_store.fresh(),
|
|
||||||
return_type: ret_var,
|
|
||||||
name: fn_name,
|
|
||||||
captured_symbols: Vec::new(),
|
|
||||||
recursive: Recursive::NotRecursive,
|
|
||||||
arguments: closure_args,
|
|
||||||
loc_body: Box::new(no_region(body)),
|
|
||||||
};
|
|
||||||
|
|
||||||
Def {
|
Def {
|
||||||
loc_pattern: Located {
|
loc_pattern: Located {
|
||||||
|
@ -3421,3 +3297,31 @@ fn defn(
|
||||||
annotation: None,
|
annotation: None,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[inline(always)]
|
||||||
|
fn defn_help(
|
||||||
|
fn_name: Symbol,
|
||||||
|
args: Vec<(Variable, Symbol)>,
|
||||||
|
var_store: &mut VarStore,
|
||||||
|
body: Expr,
|
||||||
|
ret_var: Variable,
|
||||||
|
) -> Expr {
|
||||||
|
use crate::pattern::Pattern::*;
|
||||||
|
|
||||||
|
let closure_args = args
|
||||||
|
.into_iter()
|
||||||
|
.map(|(var, symbol)| (var, no_region(Identifier(symbol))))
|
||||||
|
.collect();
|
||||||
|
|
||||||
|
Closure {
|
||||||
|
function_type: var_store.fresh(),
|
||||||
|
closure_type: var_store.fresh(),
|
||||||
|
closure_ext_var: var_store.fresh(),
|
||||||
|
return_type: ret_var,
|
||||||
|
name: fn_name,
|
||||||
|
captured_symbols: Vec::new(),
|
||||||
|
recursive: Recursive::NotRecursive,
|
||||||
|
arguments: closure_args,
|
||||||
|
loc_body: Box::new(no_region(body)),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
|
@ -1,5 +1,5 @@
|
||||||
use crate::annotation::IntroducedVariables;
|
use crate::annotation::IntroducedVariables;
|
||||||
use crate::builtins::builtin_defs;
|
use crate::builtins::builtin_defs_map;
|
||||||
use crate::def::{can_defs_with_return, Def};
|
use crate::def::{can_defs_with_return, Def};
|
||||||
use crate::env::Env;
|
use crate::env::Env;
|
||||||
use crate::num::{
|
use crate::num::{
|
||||||
|
@ -1380,7 +1380,7 @@ pub fn inline_calls(var_store: &mut VarStore, scope: &mut Scope, expr: Expr) ->
|
||||||
let (fn_var, loc_expr, closure_var, expr_var) = *boxed_tuple;
|
let (fn_var, loc_expr, closure_var, expr_var) = *boxed_tuple;
|
||||||
|
|
||||||
match loc_expr.value {
|
match loc_expr.value {
|
||||||
Var(symbol) if symbol.is_builtin() => match builtin_defs(var_store).get(&symbol) {
|
Var(symbol) if symbol.is_builtin() => match builtin_defs_map(symbol, var_store) {
|
||||||
Some(Def {
|
Some(Def {
|
||||||
loc_expr:
|
loc_expr:
|
||||||
Located {
|
Located {
|
||||||
|
@ -1395,7 +1395,7 @@ pub fn inline_calls(var_store: &mut VarStore, scope: &mut Scope, expr: Expr) ->
|
||||||
},
|
},
|
||||||
..
|
..
|
||||||
}) => {
|
}) => {
|
||||||
debug_assert_eq!(*recursive, Recursive::NotRecursive);
|
debug_assert_eq!(recursive, Recursive::NotRecursive);
|
||||||
|
|
||||||
// Since this is a canonicalized Expr, we should have
|
// Since this is a canonicalized Expr, we should have
|
||||||
// already detected any arity mismatches and replaced this
|
// already detected any arity mismatches and replaced this
|
||||||
|
@ -1403,7 +1403,7 @@ pub fn inline_calls(var_store: &mut VarStore, scope: &mut Scope, expr: Expr) ->
|
||||||
debug_assert_eq!(params.len(), args.len());
|
debug_assert_eq!(params.len(), args.len());
|
||||||
|
|
||||||
// Start with the function's body as the answer.
|
// Start with the function's body as the answer.
|
||||||
let mut loc_answer = *boxed_body.clone();
|
let mut loc_answer = *boxed_body;
|
||||||
|
|
||||||
// Wrap the body in one LetNonRec for each argument,
|
// Wrap the body in one LetNonRec for each argument,
|
||||||
// such that at the end we have all the arguments in
|
// such that at the end we have all the arguments in
|
||||||
|
|
|
@ -7,8 +7,8 @@ use crate::llvm::build_hash::generic_hash;
|
||||||
use crate::llvm::build_list::{
|
use crate::llvm::build_list::{
|
||||||
allocate_list, empty_list, empty_polymorphic_list, list_append, list_concat, list_contains,
|
allocate_list, empty_list, empty_polymorphic_list, list_append, list_concat, list_contains,
|
||||||
list_get_unsafe, list_join, list_keep_errs, list_keep_if, list_keep_oks, list_len, list_map,
|
list_get_unsafe, list_join, list_keep_errs, list_keep_if, list_keep_oks, list_len, list_map,
|
||||||
list_map2, list_map3, list_map_with_index, list_prepend, list_product, list_range, list_repeat,
|
list_map2, list_map3, list_map_with_index, list_prepend, list_range, list_repeat, list_reverse,
|
||||||
list_reverse, list_set, list_single, list_sum, list_walk, list_walk_backwards,
|
list_set, list_single, list_walk_help,
|
||||||
};
|
};
|
||||||
use crate::llvm::build_str::{
|
use crate::llvm::build_str::{
|
||||||
str_concat, str_count_graphemes, str_ends_with, str_from_float, str_from_int, str_from_utf8,
|
str_concat, str_count_graphemes, str_ends_with, str_from_float, str_from_int, str_from_utf8,
|
||||||
|
@ -3893,71 +3893,30 @@ fn run_low_level<'a, 'ctx, 'env>(
|
||||||
|
|
||||||
list_range(env, *builtin, low.into_int_value(), high.into_int_value())
|
list_range(env, *builtin, low.into_int_value(), high.into_int_value())
|
||||||
}
|
}
|
||||||
ListWalk => {
|
ListWalk => list_walk_help(
|
||||||
debug_assert_eq!(args.len(), 3);
|
|
||||||
|
|
||||||
let (list, list_layout) = load_symbol_and_layout(scope, &args[0]);
|
|
||||||
|
|
||||||
let (func, func_layout) = load_symbol_and_layout(scope, &args[1]);
|
|
||||||
|
|
||||||
let (default, default_layout) = load_symbol_and_layout(scope, &args[2]);
|
|
||||||
|
|
||||||
match list_layout {
|
|
||||||
Layout::Builtin(Builtin::EmptyList) => default,
|
|
||||||
Layout::Builtin(Builtin::List(_, element_layout)) => list_walk(
|
|
||||||
env,
|
env,
|
||||||
layout_ids,
|
layout_ids,
|
||||||
|
scope,
|
||||||
parent,
|
parent,
|
||||||
list,
|
args,
|
||||||
element_layout,
|
crate::llvm::build_list::ListWalk::Walk,
|
||||||
func,
|
|
||||||
func_layout,
|
|
||||||
default,
|
|
||||||
default_layout,
|
|
||||||
),
|
),
|
||||||
_ => unreachable!("invalid list layout"),
|
ListWalkUntil => list_walk_help(
|
||||||
}
|
|
||||||
}
|
|
||||||
ListWalkBackwards => {
|
|
||||||
// List.walkBackwards : List elem, (elem -> accum -> accum), accum -> accum
|
|
||||||
debug_assert_eq!(args.len(), 3);
|
|
||||||
|
|
||||||
let (list, list_layout) = load_symbol_and_layout(scope, &args[0]);
|
|
||||||
|
|
||||||
let (func, func_layout) = load_symbol_and_layout(scope, &args[1]);
|
|
||||||
|
|
||||||
let (default, default_layout) = load_symbol_and_layout(scope, &args[2]);
|
|
||||||
|
|
||||||
match list_layout {
|
|
||||||
Layout::Builtin(Builtin::EmptyList) => default,
|
|
||||||
Layout::Builtin(Builtin::List(_, element_layout)) => list_walk_backwards(
|
|
||||||
env,
|
env,
|
||||||
layout_ids,
|
layout_ids,
|
||||||
|
scope,
|
||||||
parent,
|
parent,
|
||||||
list,
|
args,
|
||||||
element_layout,
|
crate::llvm::build_list::ListWalk::WalkUntil,
|
||||||
func,
|
),
|
||||||
func_layout,
|
ListWalkBackwards => list_walk_help(
|
||||||
default,
|
env,
|
||||||
default_layout,
|
layout_ids,
|
||||||
|
scope,
|
||||||
|
parent,
|
||||||
|
args,
|
||||||
|
crate::llvm::build_list::ListWalk::WalkBackwards,
|
||||||
),
|
),
|
||||||
_ => unreachable!("invalid list layout"),
|
|
||||||
}
|
|
||||||
}
|
|
||||||
ListSum => {
|
|
||||||
debug_assert_eq!(args.len(), 1);
|
|
||||||
|
|
||||||
let list = load_symbol(scope, &args[0]);
|
|
||||||
|
|
||||||
list_sum(env, parent, list, layout)
|
|
||||||
}
|
|
||||||
ListProduct => {
|
|
||||||
debug_assert_eq!(args.len(), 1);
|
|
||||||
|
|
||||||
let list = load_symbol(scope, &args[0]);
|
|
||||||
|
|
||||||
list_product(env, parent, list, layout)
|
|
||||||
}
|
|
||||||
ListAppend => {
|
ListAppend => {
|
||||||
// List.append : List elem, elem -> List elem
|
// List.append : List elem, elem -> List elem
|
||||||
debug_assert_eq!(args.len(), 2);
|
debug_assert_eq!(args.len(), 2);
|
||||||
|
|
|
@ -4,7 +4,7 @@ use crate::llvm::bitcode::{
|
||||||
call_bitcode_fn, call_void_bitcode_fn,
|
call_bitcode_fn, call_void_bitcode_fn,
|
||||||
};
|
};
|
||||||
use crate::llvm::build::{
|
use crate::llvm::build::{
|
||||||
allocate_with_refcount_help, build_num_binop, cast_basic_basic, complex_bitcast, Env, InPlace,
|
allocate_with_refcount_help, cast_basic_basic, complex_bitcast, Env, InPlace,
|
||||||
};
|
};
|
||||||
use crate::llvm::convert::{basic_type_from_layout, collection, get_ptr_type};
|
use crate::llvm::convert::{basic_type_from_layout, collection, get_ptr_type};
|
||||||
use crate::llvm::refcounting::{
|
use crate::llvm::refcounting::{
|
||||||
|
@ -713,169 +713,34 @@ pub fn list_len<'ctx>(
|
||||||
.into_int_value()
|
.into_int_value()
|
||||||
}
|
}
|
||||||
|
|
||||||
/// List.sum : List (Num a) -> Num a
|
pub enum ListWalk {
|
||||||
pub fn list_sum<'a, 'ctx, 'env>(
|
Walk,
|
||||||
env: &Env<'a, 'ctx, 'env>,
|
WalkBackwards,
|
||||||
parent: FunctionValue<'ctx>,
|
WalkUntil,
|
||||||
list: BasicValueEnum<'ctx>,
|
WalkBackwardsUntil,
|
||||||
default_layout: &Layout<'a>,
|
|
||||||
) -> BasicValueEnum<'ctx> {
|
|
||||||
let ctx = env.context;
|
|
||||||
let builder = env.builder;
|
|
||||||
|
|
||||||
let list_wrapper = list.into_struct_value();
|
|
||||||
let len = list_len(env.builder, list_wrapper);
|
|
||||||
|
|
||||||
let accum_type = basic_type_from_layout(env.arena, ctx, default_layout, env.ptr_bytes);
|
|
||||||
let accum_alloca = builder.build_alloca(accum_type, "alloca_walk_right_accum");
|
|
||||||
|
|
||||||
let default: BasicValueEnum = match accum_type {
|
|
||||||
BasicTypeEnum::IntType(int_type) => int_type.const_zero().into(),
|
|
||||||
BasicTypeEnum::FloatType(float_type) => float_type.const_zero().into(),
|
|
||||||
_ => unreachable!(""),
|
|
||||||
};
|
|
||||||
|
|
||||||
builder.build_store(accum_alloca, default);
|
|
||||||
|
|
||||||
let then_block = ctx.append_basic_block(parent, "then");
|
|
||||||
let cont_block = ctx.append_basic_block(parent, "branchcont");
|
|
||||||
|
|
||||||
let condition = builder.build_int_compare(
|
|
||||||
IntPredicate::UGT,
|
|
||||||
len,
|
|
||||||
ctx.i64_type().const_zero(),
|
|
||||||
"list_non_empty",
|
|
||||||
);
|
|
||||||
|
|
||||||
builder.build_conditional_branch(condition, then_block, cont_block);
|
|
||||||
|
|
||||||
builder.position_at_end(then_block);
|
|
||||||
|
|
||||||
let elem_ptr_type = get_ptr_type(&accum_type, AddressSpace::Generic);
|
|
||||||
let list_ptr = load_list_ptr(builder, list_wrapper, elem_ptr_type);
|
|
||||||
|
|
||||||
let walk_right_loop = |_, elem: BasicValueEnum<'ctx>| {
|
|
||||||
// load current accumulator
|
|
||||||
let current = builder.build_load(accum_alloca, "retrieve_accum");
|
|
||||||
|
|
||||||
let new_current = build_num_binop(
|
|
||||||
env,
|
|
||||||
parent,
|
|
||||||
current,
|
|
||||||
default_layout,
|
|
||||||
elem,
|
|
||||||
default_layout,
|
|
||||||
roc_module::low_level::LowLevel::NumAdd,
|
|
||||||
);
|
|
||||||
|
|
||||||
builder.build_store(accum_alloca, new_current);
|
|
||||||
};
|
|
||||||
|
|
||||||
incrementing_elem_loop(
|
|
||||||
builder,
|
|
||||||
ctx,
|
|
||||||
parent,
|
|
||||||
list_ptr,
|
|
||||||
len,
|
|
||||||
"#index",
|
|
||||||
walk_right_loop,
|
|
||||||
);
|
|
||||||
|
|
||||||
builder.build_unconditional_branch(cont_block);
|
|
||||||
|
|
||||||
builder.position_at_end(cont_block);
|
|
||||||
|
|
||||||
builder.build_load(accum_alloca, "load_final_acum")
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/// List.product : List (Num a) -> Num a
|
pub fn list_walk_help<'a, 'ctx, 'env>(
|
||||||
pub fn list_product<'a, 'ctx, 'env>(
|
|
||||||
env: &Env<'a, 'ctx, 'env>,
|
|
||||||
parent: FunctionValue<'ctx>,
|
|
||||||
list: BasicValueEnum<'ctx>,
|
|
||||||
default_layout: &Layout<'a>,
|
|
||||||
) -> BasicValueEnum<'ctx> {
|
|
||||||
let ctx = env.context;
|
|
||||||
let builder = env.builder;
|
|
||||||
|
|
||||||
let list_wrapper = list.into_struct_value();
|
|
||||||
let len = list_len(env.builder, list_wrapper);
|
|
||||||
|
|
||||||
let accum_type = basic_type_from_layout(env.arena, ctx, default_layout, env.ptr_bytes);
|
|
||||||
let accum_alloca = builder.build_alloca(accum_type, "alloca_walk_right_accum");
|
|
||||||
|
|
||||||
let default: BasicValueEnum = match accum_type {
|
|
||||||
BasicTypeEnum::IntType(int_type) => int_type.const_int(1, false).into(),
|
|
||||||
BasicTypeEnum::FloatType(float_type) => float_type.const_float(1.0).into(),
|
|
||||||
_ => unreachable!(""),
|
|
||||||
};
|
|
||||||
|
|
||||||
builder.build_store(accum_alloca, default);
|
|
||||||
|
|
||||||
let then_block = ctx.append_basic_block(parent, "then");
|
|
||||||
let cont_block = ctx.append_basic_block(parent, "branchcont");
|
|
||||||
|
|
||||||
let condition = builder.build_int_compare(
|
|
||||||
IntPredicate::UGT,
|
|
||||||
len,
|
|
||||||
ctx.i64_type().const_zero(),
|
|
||||||
"list_non_empty",
|
|
||||||
);
|
|
||||||
|
|
||||||
builder.build_conditional_branch(condition, then_block, cont_block);
|
|
||||||
|
|
||||||
builder.position_at_end(then_block);
|
|
||||||
|
|
||||||
let elem_ptr_type = get_ptr_type(&accum_type, AddressSpace::Generic);
|
|
||||||
let list_ptr = load_list_ptr(builder, list_wrapper, elem_ptr_type);
|
|
||||||
|
|
||||||
let walk_right_loop = |_, elem: BasicValueEnum<'ctx>| {
|
|
||||||
// load current accumulator
|
|
||||||
let current = builder.build_load(accum_alloca, "retrieve_accum");
|
|
||||||
|
|
||||||
let new_current = build_num_binop(
|
|
||||||
env,
|
|
||||||
parent,
|
|
||||||
current,
|
|
||||||
default_layout,
|
|
||||||
elem,
|
|
||||||
default_layout,
|
|
||||||
roc_module::low_level::LowLevel::NumMul,
|
|
||||||
);
|
|
||||||
|
|
||||||
builder.build_store(accum_alloca, new_current);
|
|
||||||
};
|
|
||||||
|
|
||||||
incrementing_elem_loop(
|
|
||||||
builder,
|
|
||||||
ctx,
|
|
||||||
parent,
|
|
||||||
list_ptr,
|
|
||||||
len,
|
|
||||||
"#index",
|
|
||||||
walk_right_loop,
|
|
||||||
);
|
|
||||||
|
|
||||||
builder.build_unconditional_branch(cont_block);
|
|
||||||
|
|
||||||
builder.position_at_end(cont_block);
|
|
||||||
|
|
||||||
builder.build_load(accum_alloca, "load_final_acum")
|
|
||||||
}
|
|
||||||
|
|
||||||
/// List.walk : List elem, (elem -> accum -> accum), accum -> accum
|
|
||||||
pub fn list_walk<'a, 'ctx, 'env>(
|
|
||||||
env: &Env<'a, 'ctx, 'env>,
|
env: &Env<'a, 'ctx, 'env>,
|
||||||
layout_ids: &mut LayoutIds<'a>,
|
layout_ids: &mut LayoutIds<'a>,
|
||||||
|
scope: &crate::llvm::build::Scope<'a, 'ctx>,
|
||||||
parent: FunctionValue<'ctx>,
|
parent: FunctionValue<'ctx>,
|
||||||
list: BasicValueEnum<'ctx>,
|
args: &[roc_module::symbol::Symbol],
|
||||||
element_layout: &Layout<'a>,
|
variant: ListWalk,
|
||||||
func: BasicValueEnum<'ctx>,
|
|
||||||
func_layout: &Layout<'a>,
|
|
||||||
default: BasicValueEnum<'ctx>,
|
|
||||||
default_layout: &Layout<'a>,
|
|
||||||
) -> BasicValueEnum<'ctx> {
|
) -> BasicValueEnum<'ctx> {
|
||||||
list_walk_generic(
|
use crate::llvm::build::load_symbol_and_layout;
|
||||||
|
|
||||||
|
debug_assert_eq!(args.len(), 3);
|
||||||
|
|
||||||
|
let (list, list_layout) = load_symbol_and_layout(scope, &args[0]);
|
||||||
|
|
||||||
|
let (func, func_layout) = load_symbol_and_layout(scope, &args[1]);
|
||||||
|
|
||||||
|
let (default, default_layout) = load_symbol_and_layout(scope, &args[2]);
|
||||||
|
|
||||||
|
match list_layout {
|
||||||
|
Layout::Builtin(Builtin::EmptyList) => default,
|
||||||
|
Layout::Builtin(Builtin::List(_, element_layout)) => list_walk_generic(
|
||||||
env,
|
env,
|
||||||
layout_ids,
|
layout_ids,
|
||||||
parent,
|
parent,
|
||||||
|
@ -885,34 +750,10 @@ pub fn list_walk<'a, 'ctx, 'env>(
|
||||||
func_layout,
|
func_layout,
|
||||||
default,
|
default,
|
||||||
default_layout,
|
default_layout,
|
||||||
&bitcode::LIST_WALK,
|
variant,
|
||||||
)
|
),
|
||||||
}
|
_ => unreachable!("invalid list layout"),
|
||||||
|
}
|
||||||
/// List.walkBackwards : List elem, (elem -> accum -> accum), accum -> accum
|
|
||||||
pub fn list_walk_backwards<'a, 'ctx, 'env>(
|
|
||||||
env: &Env<'a, 'ctx, 'env>,
|
|
||||||
layout_ids: &mut LayoutIds<'a>,
|
|
||||||
parent: FunctionValue<'ctx>,
|
|
||||||
list: BasicValueEnum<'ctx>,
|
|
||||||
element_layout: &Layout<'a>,
|
|
||||||
func: BasicValueEnum<'ctx>,
|
|
||||||
func_layout: &Layout<'a>,
|
|
||||||
default: BasicValueEnum<'ctx>,
|
|
||||||
default_layout: &Layout<'a>,
|
|
||||||
) -> BasicValueEnum<'ctx> {
|
|
||||||
list_walk_generic(
|
|
||||||
env,
|
|
||||||
layout_ids,
|
|
||||||
parent,
|
|
||||||
list,
|
|
||||||
element_layout,
|
|
||||||
func,
|
|
||||||
func_layout,
|
|
||||||
default,
|
|
||||||
default_layout,
|
|
||||||
&bitcode::LIST_WALK_BACKWARDS,
|
|
||||||
)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
fn list_walk_generic<'a, 'ctx, 'env>(
|
fn list_walk_generic<'a, 'ctx, 'env>(
|
||||||
|
@ -925,10 +766,17 @@ fn list_walk_generic<'a, 'ctx, 'env>(
|
||||||
func_layout: &Layout<'a>,
|
func_layout: &Layout<'a>,
|
||||||
default: BasicValueEnum<'ctx>,
|
default: BasicValueEnum<'ctx>,
|
||||||
default_layout: &Layout<'a>,
|
default_layout: &Layout<'a>,
|
||||||
zig_function: &str,
|
variant: ListWalk,
|
||||||
) -> BasicValueEnum<'ctx> {
|
) -> BasicValueEnum<'ctx> {
|
||||||
let builder = env.builder;
|
let builder = env.builder;
|
||||||
|
|
||||||
|
let zig_function = match variant {
|
||||||
|
ListWalk::Walk => bitcode::LIST_WALK,
|
||||||
|
ListWalk::WalkBackwards => bitcode::LIST_WALK_BACKWARDS,
|
||||||
|
ListWalk::WalkUntil => bitcode::LIST_WALK_UNTIL,
|
||||||
|
ListWalk::WalkBackwardsUntil => todo!(),
|
||||||
|
};
|
||||||
|
|
||||||
let u8_ptr = env.context.i8_type().ptr_type(AddressSpace::Generic);
|
let u8_ptr = env.context.i8_type().ptr_type(AddressSpace::Generic);
|
||||||
|
|
||||||
let list_i128 = complex_bitcast(env.builder, list, env.context.i128_type().into(), "to_i128");
|
let list_i128 = complex_bitcast(env.builder, list, env.context.i128_type().into(), "to_i128");
|
||||||
|
@ -961,6 +809,8 @@ fn list_walk_generic<'a, 'ctx, 'env>(
|
||||||
|
|
||||||
let result_ptr = env.builder.build_alloca(default.get_type(), "result");
|
let result_ptr = env.builder.build_alloca(default.get_type(), "result");
|
||||||
|
|
||||||
|
match variant {
|
||||||
|
ListWalk::Walk | ListWalk::WalkBackwards => {
|
||||||
call_void_bitcode_fn(
|
call_void_bitcode_fn(
|
||||||
env,
|
env,
|
||||||
&[
|
&[
|
||||||
|
@ -976,6 +826,27 @@ fn list_walk_generic<'a, 'ctx, 'env>(
|
||||||
],
|
],
|
||||||
zig_function,
|
zig_function,
|
||||||
);
|
);
|
||||||
|
}
|
||||||
|
ListWalk::WalkUntil | ListWalk::WalkBackwardsUntil => {
|
||||||
|
let dec_element_fn = build_dec_wrapper(env, layout_ids, element_layout);
|
||||||
|
call_void_bitcode_fn(
|
||||||
|
env,
|
||||||
|
&[
|
||||||
|
list_i128,
|
||||||
|
env.builder
|
||||||
|
.build_bitcast(transform_ptr, u8_ptr, "to_opaque"),
|
||||||
|
stepper_caller.into(),
|
||||||
|
env.builder.build_bitcast(default_ptr, u8_ptr, "to_u8_ptr"),
|
||||||
|
alignment_iv.into(),
|
||||||
|
element_width.into(),
|
||||||
|
default_width.into(),
|
||||||
|
dec_element_fn.as_global_value().as_pointer_value().into(),
|
||||||
|
env.builder.build_bitcast(result_ptr, u8_ptr, "to_opaque"),
|
||||||
|
],
|
||||||
|
zig_function,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
env.builder.build_load(result_ptr, "load_result")
|
env.builder.build_load(result_ptr, "load_result")
|
||||||
}
|
}
|
||||||
|
|
|
@ -111,12 +111,16 @@ fn generate_module_doc<'a>(
|
||||||
},
|
},
|
||||||
|
|
||||||
Alias {
|
Alias {
|
||||||
name: _,
|
name,
|
||||||
vars: _,
|
vars: _,
|
||||||
ann: _,
|
ann: _,
|
||||||
} =>
|
} => {
|
||||||
// TODO
|
let entry = DocEntry {
|
||||||
{
|
name: name.value.to_string(),
|
||||||
|
docs: before_comments_or_new_lines.and_then(comments_or_new_lines_to_docs),
|
||||||
|
};
|
||||||
|
acc.push(entry);
|
||||||
|
|
||||||
(acc, None)
|
(acc, None)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -139,7 +143,9 @@ fn comments_or_new_lines_to_docs<'a>(
|
||||||
docs.push_str(doc_str);
|
docs.push_str(doc_str);
|
||||||
docs.push('\n');
|
docs.push('\n');
|
||||||
}
|
}
|
||||||
Newline | LineComment(_) => {}
|
Newline | LineComment(_) => {
|
||||||
|
docs = String::new();
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
if docs.is_empty() {
|
if docs.is_empty() {
|
||||||
|
|
|
@ -3773,12 +3773,7 @@ fn make_specializations<'a>(
|
||||||
// TODO: for now this final specialization pass is sequential,
|
// TODO: for now this final specialization pass is sequential,
|
||||||
// with no parallelization at all. We should try to parallelize
|
// with no parallelization at all. We should try to parallelize
|
||||||
// this, but doing so will require a redesign of Procs.
|
// this, but doing so will require a redesign of Procs.
|
||||||
procs = roc_mono::ir::specialize_all(
|
procs = roc_mono::ir::specialize_all(&mut mono_env, procs, &mut layout_cache);
|
||||||
&mut mono_env,
|
|
||||||
procs,
|
|
||||||
&mut layout_cache,
|
|
||||||
// &finished_info.vars_by_symbol,
|
|
||||||
);
|
|
||||||
|
|
||||||
let external_specializations_requested = procs.externals_we_need.clone();
|
let external_specializations_requested = procs.externals_we_need.clone();
|
||||||
let procedures = procs.get_specialized_procs_without_rc(mono_env.arena);
|
let procedures = procs.get_specialized_procs_without_rc(mono_env.arena);
|
||||||
|
|
|
@ -33,9 +33,8 @@ pub enum LowLevel {
|
||||||
ListMapWithIndex,
|
ListMapWithIndex,
|
||||||
ListKeepIf,
|
ListKeepIf,
|
||||||
ListWalk,
|
ListWalk,
|
||||||
|
ListWalkUntil,
|
||||||
ListWalkBackwards,
|
ListWalkBackwards,
|
||||||
ListSum,
|
|
||||||
ListProduct,
|
|
||||||
ListKeepOks,
|
ListKeepOks,
|
||||||
ListKeepErrs,
|
ListKeepErrs,
|
||||||
DictSize,
|
DictSize,
|
||||||
|
|
|
@ -915,7 +915,10 @@ define_builtins! {
|
||||||
24 LIST_MAP2: "map2"
|
24 LIST_MAP2: "map2"
|
||||||
25 LIST_MAP3: "map3"
|
25 LIST_MAP3: "map3"
|
||||||
26 LIST_PRODUCT: "product"
|
26 LIST_PRODUCT: "product"
|
||||||
27 LIST_RANGE: "range"
|
27 LIST_SUM_ADD: "#sumadd"
|
||||||
|
28 LIST_PRODUCT_MUL: "#productmul"
|
||||||
|
29 LIST_WALK_UNTIL: "walkUntil"
|
||||||
|
30 LIST_RANGE: "range"
|
||||||
}
|
}
|
||||||
5 RESULT: "Result" => {
|
5 RESULT: "Result" => {
|
||||||
0 RESULT_RESULT: "Result" imported // the Result.Result type alias
|
0 RESULT_RESULT: "Result" imported // the Result.Result type alias
|
||||||
|
|
|
@ -655,10 +655,10 @@ pub fn lowlevel_borrow_signature(arena: &Bump, op: LowLevel) -> &[bool] {
|
||||||
ListMap3 => arena.alloc_slice_copy(&[owned, owned, owned, irrelevant]),
|
ListMap3 => arena.alloc_slice_copy(&[owned, owned, owned, irrelevant]),
|
||||||
ListKeepIf | ListKeepOks | ListKeepErrs => arena.alloc_slice_copy(&[owned, borrowed]),
|
ListKeepIf | ListKeepOks | ListKeepErrs => arena.alloc_slice_copy(&[owned, borrowed]),
|
||||||
ListContains => arena.alloc_slice_copy(&[borrowed, irrelevant]),
|
ListContains => arena.alloc_slice_copy(&[borrowed, irrelevant]),
|
||||||
ListWalk => arena.alloc_slice_copy(&[owned, irrelevant, owned]),
|
|
||||||
ListWalkBackwards => arena.alloc_slice_copy(&[owned, irrelevant, owned]),
|
|
||||||
ListRange => arena.alloc_slice_copy(&[irrelevant, irrelevant]),
|
ListRange => arena.alloc_slice_copy(&[irrelevant, irrelevant]),
|
||||||
ListSum | ListProduct => arena.alloc_slice_copy(&[borrowed]),
|
ListWalk | ListWalkUntil | ListWalkBackwards => {
|
||||||
|
arena.alloc_slice_copy(&[owned, irrelevant, owned])
|
||||||
|
}
|
||||||
|
|
||||||
// TODO when we have lists with capacity (if ever)
|
// TODO when we have lists with capacity (if ever)
|
||||||
// List.append should own its first argument
|
// List.append should own its first argument
|
||||||
|
|
|
@ -441,7 +441,7 @@ fn expand_and_cancel<'a>(env: &mut Env<'a, '_>, stmt: &'a Stmt<'a>) -> &'a Stmt<
|
||||||
structure,
|
structure,
|
||||||
index,
|
index,
|
||||||
field_layouts,
|
field_layouts,
|
||||||
..
|
wrapped,
|
||||||
} => {
|
} => {
|
||||||
let entry = env
|
let entry = env
|
||||||
.alias_map
|
.alias_map
|
||||||
|
@ -450,6 +450,15 @@ fn expand_and_cancel<'a>(env: &mut Env<'a, '_>, stmt: &'a Stmt<'a>) -> &'a Stmt<
|
||||||
|
|
||||||
entry.insert(*index, symbol);
|
entry.insert(*index, symbol);
|
||||||
|
|
||||||
|
// fixes https://github.com/rtfeldman/roc/issues/1099
|
||||||
|
if matches!(
|
||||||
|
wrapped,
|
||||||
|
Wrapped::SingleElementRecord | Wrapped::RecordOrSingleTagUnion
|
||||||
|
) {
|
||||||
|
env.layout_map
|
||||||
|
.insert(*structure, Layout::Struct(field_layouts));
|
||||||
|
}
|
||||||
|
|
||||||
// if the field is a struct, we know its constructor too!
|
// if the field is a struct, we know its constructor too!
|
||||||
let field_layout = &field_layouts[*index as usize];
|
let field_layout = &field_layouts[*index as usize];
|
||||||
env.try_insert_struct_info(symbol, field_layout);
|
env.try_insert_struct_info(symbol, field_layout);
|
||||||
|
|
|
@ -558,13 +558,7 @@ impl<'a> Procs<'a> {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
Err(error) => {
|
Err(error) => {
|
||||||
let error_msg = format!(
|
panic!("TODO generate a RuntimeError message for {:?}", error);
|
||||||
"TODO generate a RuntimeError message for {:?}",
|
|
||||||
error
|
|
||||||
);
|
|
||||||
self.runtime_errors
|
|
||||||
.insert(symbol, env.arena.alloc(error_msg));
|
|
||||||
panic!();
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -671,11 +665,7 @@ impl<'a> Procs<'a> {
|
||||||
self.specialized.insert((symbol, layout), Done(proc));
|
self.specialized.insert((symbol, layout), Done(proc));
|
||||||
}
|
}
|
||||||
Err(error) => {
|
Err(error) => {
|
||||||
let error_msg =
|
panic!("TODO generate a RuntimeError message for {:?}", error);
|
||||||
format!("TODO generate a RuntimeError message for {:?}", error);
|
|
||||||
self.runtime_errors
|
|
||||||
.insert(symbol, env.arena.alloc(error_msg));
|
|
||||||
panic!();
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -699,7 +689,6 @@ fn add_pending<'a>(
|
||||||
#[derive(Default)]
|
#[derive(Default)]
|
||||||
pub struct Specializations<'a> {
|
pub struct Specializations<'a> {
|
||||||
by_symbol: MutMap<Symbol, MutMap<Layout<'a>, Proc<'a>>>,
|
by_symbol: MutMap<Symbol, MutMap<Layout<'a>, Proc<'a>>>,
|
||||||
runtime_errors: MutSet<Symbol>,
|
|
||||||
}
|
}
|
||||||
|
|
||||||
impl<'a> Specializations<'a> {
|
impl<'a> Specializations<'a> {
|
||||||
|
@ -715,32 +704,15 @@ impl<'a> Specializations<'a> {
|
||||||
!procs_by_layout.contains_key(&layout) || procs_by_layout.get(&layout) == Some(&proc)
|
!procs_by_layout.contains_key(&layout) || procs_by_layout.get(&layout) == Some(&proc)
|
||||||
);
|
);
|
||||||
|
|
||||||
// We shouldn't already have a runtime error recorded for this symbol
|
|
||||||
debug_assert!(!self.runtime_errors.contains(&symbol));
|
|
||||||
|
|
||||||
procs_by_layout.insert(layout, proc);
|
procs_by_layout.insert(layout, proc);
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn runtime_error(&mut self, symbol: Symbol) {
|
|
||||||
// We shouldn't already have a normal proc recorded for this symbol
|
|
||||||
debug_assert!(!self.by_symbol.contains_key(&symbol));
|
|
||||||
|
|
||||||
self.runtime_errors.insert(symbol);
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn into_owned(self) -> (MutMap<Symbol, MutMap<Layout<'a>, Proc<'a>>>, MutSet<Symbol>) {
|
|
||||||
(self.by_symbol, self.runtime_errors)
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn len(&self) -> usize {
|
pub fn len(&self) -> usize {
|
||||||
let runtime_errors: usize = self.runtime_errors.len();
|
self.by_symbol.len()
|
||||||
let specializations: usize = self.by_symbol.len();
|
|
||||||
|
|
||||||
runtime_errors + specializations
|
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn is_empty(&self) -> bool {
|
pub fn is_empty(&self) -> bool {
|
||||||
self.len() == 0
|
self.by_symbol.is_empty()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -1696,13 +1668,15 @@ pub fn specialize_all<'a>(
|
||||||
Ok((proc, layout)) => {
|
Ok((proc, layout)) => {
|
||||||
procs.specialized.insert((name, layout), Done(proc));
|
procs.specialized.insert((name, layout), Done(proc));
|
||||||
}
|
}
|
||||||
Err(error) => {
|
Err(SpecializeFailure {
|
||||||
let error_msg = env.arena.alloc(format!(
|
problem: _,
|
||||||
"TODO generate a RuntimeError message for {:?}",
|
attempted_layout,
|
||||||
error
|
}) => {
|
||||||
));
|
let proc = generate_runtime_error_function(env, name, attempted_layout);
|
||||||
|
|
||||||
procs.runtime_errors.insert(name, error_msg);
|
procs
|
||||||
|
.specialized
|
||||||
|
.insert((name, attempted_layout), Done(proc));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -1758,13 +1732,14 @@ pub fn specialize_all<'a>(
|
||||||
procs.specialized.insert((name, layout), Done(proc));
|
procs.specialized.insert((name, layout), Done(proc));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
Err(error) => {
|
Err(SpecializeFailure {
|
||||||
let error_msg = env.arena.alloc(format!(
|
attempted_layout, ..
|
||||||
"TODO generate a RuntimeError message for {:?}",
|
}) => {
|
||||||
error
|
let proc = generate_runtime_error_function(env, name, attempted_layout);
|
||||||
));
|
|
||||||
|
|
||||||
procs.runtime_errors.insert(name, error_msg);
|
procs
|
||||||
|
.specialized
|
||||||
|
.insert((name, attempted_layout), Done(proc));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -1774,6 +1749,47 @@ pub fn specialize_all<'a>(
|
||||||
procs
|
procs
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fn generate_runtime_error_function<'a>(
|
||||||
|
env: &mut Env<'a, '_>,
|
||||||
|
name: Symbol,
|
||||||
|
layout: Layout<'a>,
|
||||||
|
) -> Proc<'a> {
|
||||||
|
let (arg_layouts, ret_layout) = match layout {
|
||||||
|
Layout::FunctionPointer(a, r) => (a, *r),
|
||||||
|
_ => (&[] as &[_], layout),
|
||||||
|
};
|
||||||
|
|
||||||
|
let mut args = Vec::with_capacity_in(arg_layouts.len(), env.arena);
|
||||||
|
|
||||||
|
for arg in arg_layouts {
|
||||||
|
args.push((*arg, env.unique_symbol()));
|
||||||
|
}
|
||||||
|
|
||||||
|
let mut msg = bumpalo::collections::string::String::with_capacity_in(80, env.arena);
|
||||||
|
use std::fmt::Write;
|
||||||
|
write!(
|
||||||
|
&mut msg,
|
||||||
|
"The {:?} function could not be generated, likely due to a type error.",
|
||||||
|
name
|
||||||
|
)
|
||||||
|
.unwrap();
|
||||||
|
|
||||||
|
eprintln!("emitted runtime error function {:?}", &msg);
|
||||||
|
|
||||||
|
let runtime_error = Stmt::RuntimeError(msg.into_bump_str());
|
||||||
|
|
||||||
|
Proc {
|
||||||
|
name,
|
||||||
|
args: args.into_bump_slice(),
|
||||||
|
body: runtime_error,
|
||||||
|
closure_data_layout: None,
|
||||||
|
ret_layout,
|
||||||
|
is_self_recursive: SelfRecursive::NotSelfRecursive,
|
||||||
|
must_own_arguments: false,
|
||||||
|
host_exposed_layouts: HostExposedLayouts::NotHostExposed,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
fn specialize_external<'a>(
|
fn specialize_external<'a>(
|
||||||
env: &mut Env<'a, '_>,
|
env: &mut Env<'a, '_>,
|
||||||
procs: &mut Procs<'a>,
|
procs: &mut Procs<'a>,
|
||||||
|
@ -2270,6 +2286,14 @@ fn build_specialized_proc<'a>(
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[derive(Debug)]
|
||||||
|
struct SpecializeFailure<'a> {
|
||||||
|
/// The layout we attempted to create
|
||||||
|
attempted_layout: Layout<'a>,
|
||||||
|
/// The problem we ran into while creating it
|
||||||
|
problem: LayoutProblem,
|
||||||
|
}
|
||||||
|
|
||||||
fn specialize<'a>(
|
fn specialize<'a>(
|
||||||
env: &mut Env<'a, '_>,
|
env: &mut Env<'a, '_>,
|
||||||
procs: &mut Procs<'a>,
|
procs: &mut Procs<'a>,
|
||||||
|
@ -2277,7 +2301,7 @@ fn specialize<'a>(
|
||||||
layout_cache: &mut LayoutCache<'a>,
|
layout_cache: &mut LayoutCache<'a>,
|
||||||
pending: PendingSpecialization,
|
pending: PendingSpecialization,
|
||||||
partial_proc: PartialProc<'a>,
|
partial_proc: PartialProc<'a>,
|
||||||
) -> Result<(Proc<'a>, Layout<'a>), LayoutProblem> {
|
) -> Result<(Proc<'a>, Layout<'a>), SpecializeFailure<'a>> {
|
||||||
let PendingSpecialization {
|
let PendingSpecialization {
|
||||||
solved_type,
|
solved_type,
|
||||||
host_exposed_aliases,
|
host_exposed_aliases,
|
||||||
|
@ -2321,7 +2345,7 @@ fn specialize_solved_type<'a>(
|
||||||
solved_type: SolvedType,
|
solved_type: SolvedType,
|
||||||
host_exposed_aliases: MutMap<Symbol, SolvedType>,
|
host_exposed_aliases: MutMap<Symbol, SolvedType>,
|
||||||
partial_proc: PartialProc<'a>,
|
partial_proc: PartialProc<'a>,
|
||||||
) -> Result<(Proc<'a>, Layout<'a>), LayoutProblem> {
|
) -> Result<(Proc<'a>, Layout<'a>), SpecializeFailure<'a>> {
|
||||||
// add the specializations that other modules require of us
|
// add the specializations that other modules require of us
|
||||||
use roc_solve::solve::instantiate_rigids;
|
use roc_solve::solve::instantiate_rigids;
|
||||||
|
|
||||||
|
@ -2330,6 +2354,10 @@ fn specialize_solved_type<'a>(
|
||||||
|
|
||||||
let fn_var = introduce_solved_type_to_subs(env, &solved_type);
|
let fn_var = introduce_solved_type_to_subs(env, &solved_type);
|
||||||
|
|
||||||
|
let attempted_layout = layout_cache
|
||||||
|
.from_var(&env.arena, fn_var, env.subs)
|
||||||
|
.unwrap_or_else(|err| panic!("TODO handle invalid function {:?}", err));
|
||||||
|
|
||||||
// make sure rigid variables in the annotation are converted to flex variables
|
// make sure rigid variables in the annotation are converted to flex variables
|
||||||
instantiate_rigids(env.subs, partial_proc.annotation);
|
instantiate_rigids(env.subs, partial_proc.annotation);
|
||||||
|
|
||||||
|
@ -2353,17 +2381,25 @@ fn specialize_solved_type<'a>(
|
||||||
|
|
||||||
match specialized {
|
match specialized {
|
||||||
Ok(proc) => {
|
Ok(proc) => {
|
||||||
let layout = layout_cache
|
// when successful, the layout after unification should be the layout before unification
|
||||||
|
debug_assert_eq!(
|
||||||
|
attempted_layout,
|
||||||
|
layout_cache
|
||||||
.from_var(&env.arena, fn_var, env.subs)
|
.from_var(&env.arena, fn_var, env.subs)
|
||||||
.unwrap_or_else(|err| panic!("TODO handle invalid function {:?}", err));
|
.unwrap_or_else(|err| panic!("TODO handle invalid function {:?}", err))
|
||||||
|
);
|
||||||
|
|
||||||
env.subs.rollback_to(snapshot);
|
env.subs.rollback_to(snapshot);
|
||||||
layout_cache.rollback_to(cache_snapshot);
|
layout_cache.rollback_to(cache_snapshot);
|
||||||
Ok((proc, layout))
|
Ok((proc, attempted_layout))
|
||||||
}
|
}
|
||||||
Err(error) => {
|
Err(error) => {
|
||||||
env.subs.rollback_to(snapshot);
|
env.subs.rollback_to(snapshot);
|
||||||
layout_cache.rollback_to(cache_snapshot);
|
layout_cache.rollback_to(cache_snapshot);
|
||||||
Err(error)
|
Err(SpecializeFailure {
|
||||||
|
problem: error,
|
||||||
|
attempted_layout,
|
||||||
|
})
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -2803,11 +2839,69 @@ pub fn with_hole<'a>(
|
||||||
variant_var,
|
variant_var,
|
||||||
name: tag_name,
|
name: tag_name,
|
||||||
arguments: args,
|
arguments: args,
|
||||||
..
|
ext_var,
|
||||||
} => {
|
} => {
|
||||||
use crate::layout::UnionVariant::*;
|
use crate::layout::UnionVariant::*;
|
||||||
let arena = env.arena;
|
let arena = env.arena;
|
||||||
|
|
||||||
|
let desc = env.subs.get_without_compacting(variant_var);
|
||||||
|
|
||||||
|
if let Content::Structure(FlatType::Func(arg_vars, _, ret_var)) = desc.content {
|
||||||
|
let mut loc_pattern_args = vec![];
|
||||||
|
let mut loc_expr_args = vec![];
|
||||||
|
|
||||||
|
let proc_symbol = env.unique_symbol();
|
||||||
|
|
||||||
|
for arg_var in arg_vars {
|
||||||
|
let arg_symbol = env.unique_symbol();
|
||||||
|
|
||||||
|
let loc_pattern =
|
||||||
|
Located::at_zero(roc_can::pattern::Pattern::Identifier(arg_symbol));
|
||||||
|
|
||||||
|
let loc_expr = Located::at_zero(roc_can::expr::Expr::Var(arg_symbol));
|
||||||
|
|
||||||
|
loc_pattern_args.push((arg_var, loc_pattern));
|
||||||
|
loc_expr_args.push((arg_var, loc_expr));
|
||||||
|
}
|
||||||
|
|
||||||
|
let loc_body = Located::at_zero(roc_can::expr::Expr::Tag {
|
||||||
|
variant_var: ret_var,
|
||||||
|
name: tag_name,
|
||||||
|
arguments: loc_expr_args,
|
||||||
|
ext_var,
|
||||||
|
});
|
||||||
|
|
||||||
|
let inserted = procs.insert_anonymous(
|
||||||
|
env,
|
||||||
|
proc_symbol,
|
||||||
|
variant_var,
|
||||||
|
loc_pattern_args,
|
||||||
|
loc_body,
|
||||||
|
CapturedSymbols::None,
|
||||||
|
ret_var,
|
||||||
|
layout_cache,
|
||||||
|
);
|
||||||
|
|
||||||
|
match inserted {
|
||||||
|
Ok(layout) => {
|
||||||
|
return Stmt::Let(
|
||||||
|
assigned,
|
||||||
|
call_by_pointer(env, procs, proc_symbol, layout),
|
||||||
|
layout,
|
||||||
|
hole,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
Err(runtime_error) => {
|
||||||
|
return Stmt::RuntimeError(env.arena.alloc(format!(
|
||||||
|
"RuntimeError {} line {} {:?}",
|
||||||
|
file!(),
|
||||||
|
line!(),
|
||||||
|
runtime_error,
|
||||||
|
)));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
let res_variant = crate::layout::union_sorted_tags(env.arena, variant_var, env.subs);
|
let res_variant = crate::layout::union_sorted_tags(env.arena, variant_var, env.subs);
|
||||||
|
|
||||||
let variant = match res_variant {
|
let variant = match res_variant {
|
||||||
|
@ -5893,6 +5987,20 @@ fn call_by_name<'a>(
|
||||||
|
|
||||||
// Register a pending_specialization for this function
|
// Register a pending_specialization for this function
|
||||||
match layout_cache.from_var(env.arena, fn_var, env.subs) {
|
match layout_cache.from_var(env.arena, fn_var, env.subs) {
|
||||||
|
Err(LayoutProblem::UnresolvedTypeVar(var)) => {
|
||||||
|
let msg = format!(
|
||||||
|
"Hit an unresolved type variable {:?} when creating a layout for {:?} (var {:?})",
|
||||||
|
var, proc_name, fn_var
|
||||||
|
);
|
||||||
|
Stmt::RuntimeError(env.arena.alloc(msg))
|
||||||
|
}
|
||||||
|
Err(LayoutProblem::Erroneous) => {
|
||||||
|
let msg = format!(
|
||||||
|
"Hit an erroneous type when creating a layout for {:?}",
|
||||||
|
proc_name
|
||||||
|
);
|
||||||
|
Stmt::RuntimeError(env.arena.alloc(msg))
|
||||||
|
}
|
||||||
Ok(layout) => {
|
Ok(layout) => {
|
||||||
// Build the CallByName node
|
// Build the CallByName node
|
||||||
let arena = env.arena;
|
let arena = env.arena;
|
||||||
|
@ -6028,123 +6136,42 @@ fn call_by_name<'a>(
|
||||||
"\n\n{:?}\n\n{:?}",
|
"\n\n{:?}\n\n{:?}",
|
||||||
full_layout, layout
|
full_layout, layout
|
||||||
);
|
);
|
||||||
let function_layout =
|
|
||||||
FunctionLayouts::from_layout(env.arena, layout);
|
|
||||||
|
|
||||||
procs.specialized.remove(&(proc_name, full_layout));
|
call_specialized_proc(
|
||||||
|
|
||||||
procs
|
|
||||||
.specialized
|
|
||||||
.insert((proc_name, function_layout.full), Done(proc));
|
|
||||||
|
|
||||||
if field_symbols.is_empty() {
|
|
||||||
debug_assert!(loc_args.is_empty());
|
|
||||||
|
|
||||||
// This happens when we return a function, e.g.
|
|
||||||
//
|
|
||||||
// foo = Num.add
|
|
||||||
//
|
|
||||||
// Even though the layout (and type) are functions,
|
|
||||||
// there are no arguments. This confuses our IR,
|
|
||||||
// and we have to fix it here.
|
|
||||||
match full_layout {
|
|
||||||
Layout::Closure(_, closure_layout, _) => {
|
|
||||||
let call = self::Call {
|
|
||||||
call_type: CallType::ByName {
|
|
||||||
name: proc_name,
|
|
||||||
ret_layout: function_layout.result,
|
|
||||||
full_layout: function_layout.full,
|
|
||||||
arg_layouts: function_layout.arguments,
|
|
||||||
},
|
|
||||||
arguments: field_symbols,
|
|
||||||
};
|
|
||||||
|
|
||||||
// in the case of a closure specifically, we
|
|
||||||
// have to create a custom layout, to make sure
|
|
||||||
// the closure data is part of the layout
|
|
||||||
let closure_struct_layout = Layout::Struct(
|
|
||||||
env.arena.alloc([
|
|
||||||
function_layout.full,
|
|
||||||
closure_layout
|
|
||||||
.as_block_of_memory_layout(),
|
|
||||||
]),
|
|
||||||
);
|
|
||||||
|
|
||||||
build_call(
|
|
||||||
env,
|
|
||||||
call,
|
|
||||||
assigned,
|
|
||||||
closure_struct_layout,
|
|
||||||
hole,
|
|
||||||
)
|
|
||||||
}
|
|
||||||
_ => {
|
|
||||||
let call = self::Call {
|
|
||||||
call_type: CallType::ByName {
|
|
||||||
name: proc_name,
|
|
||||||
ret_layout: function_layout.result,
|
|
||||||
full_layout: function_layout.full,
|
|
||||||
arg_layouts: function_layout.arguments,
|
|
||||||
},
|
|
||||||
arguments: field_symbols,
|
|
||||||
};
|
|
||||||
|
|
||||||
build_call(
|
|
||||||
env,
|
|
||||||
call,
|
|
||||||
assigned,
|
|
||||||
function_layout.full,
|
|
||||||
hole,
|
|
||||||
)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
debug_assert_eq!(
|
|
||||||
function_layout.arguments.len(),
|
|
||||||
field_symbols.len(),
|
|
||||||
"scroll up a bit for background"
|
|
||||||
);
|
|
||||||
let call = self::Call {
|
|
||||||
call_type: CallType::ByName {
|
|
||||||
name: proc_name,
|
|
||||||
ret_layout: function_layout.result,
|
|
||||||
full_layout: function_layout.full,
|
|
||||||
arg_layouts: function_layout.arguments,
|
|
||||||
},
|
|
||||||
arguments: field_symbols,
|
|
||||||
};
|
|
||||||
|
|
||||||
let iter = loc_args
|
|
||||||
.into_iter()
|
|
||||||
.rev()
|
|
||||||
.zip(field_symbols.iter().rev());
|
|
||||||
|
|
||||||
let result = build_call(
|
|
||||||
env,
|
|
||||||
call,
|
|
||||||
assigned,
|
|
||||||
function_layout.result,
|
|
||||||
hole,
|
|
||||||
);
|
|
||||||
|
|
||||||
assign_to_symbols(
|
|
||||||
env,
|
env,
|
||||||
procs,
|
procs,
|
||||||
|
proc_name,
|
||||||
|
proc,
|
||||||
|
layout,
|
||||||
|
field_symbols,
|
||||||
|
loc_args,
|
||||||
layout_cache,
|
layout_cache,
|
||||||
iter,
|
assigned,
|
||||||
result,
|
hole,
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
}
|
Err(SpecializeFailure {
|
||||||
Err(error) => {
|
attempted_layout,
|
||||||
let error_msg = env.arena.alloc(format!(
|
problem: _,
|
||||||
"TODO generate a RuntimeError message for {:?}",
|
}) => {
|
||||||
error
|
let proc = generate_runtime_error_function(
|
||||||
));
|
env,
|
||||||
|
proc_name,
|
||||||
|
attempted_layout,
|
||||||
|
);
|
||||||
|
|
||||||
procs.runtime_errors.insert(proc_name, error_msg);
|
call_specialized_proc(
|
||||||
|
env,
|
||||||
Stmt::RuntimeError(error_msg)
|
procs,
|
||||||
|
proc_name,
|
||||||
|
proc,
|
||||||
|
layout,
|
||||||
|
field_symbols,
|
||||||
|
loc_args,
|
||||||
|
layout_cache,
|
||||||
|
assigned,
|
||||||
|
hole,
|
||||||
|
)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -6191,12 +6218,7 @@ fn call_by_name<'a>(
|
||||||
}
|
}
|
||||||
|
|
||||||
None => {
|
None => {
|
||||||
// This must have been a runtime error.
|
unreachable!("Proc name {:?} is invalid", proc_name)
|
||||||
match procs.runtime_errors.get(&proc_name) {
|
|
||||||
Some(error) => Stmt::RuntimeError(
|
|
||||||
env.arena.alloc(format!("runtime error {:?}", error)),
|
|
||||||
),
|
|
||||||
None => unreachable!("Proc name {:?} is invalid", proc_name),
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -6204,20 +6226,96 @@ fn call_by_name<'a>(
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
Err(LayoutProblem::UnresolvedTypeVar(var)) => {
|
}
|
||||||
let msg = format!(
|
|
||||||
"Hit an unresolved type variable {:?} when creating a layout for {:?} (var {:?})",
|
#[allow(clippy::too_many_arguments)]
|
||||||
var, proc_name, fn_var
|
fn call_specialized_proc<'a>(
|
||||||
|
env: &mut Env<'a, '_>,
|
||||||
|
procs: &mut Procs<'a>,
|
||||||
|
proc_name: Symbol,
|
||||||
|
proc: Proc<'a>,
|
||||||
|
layout: Layout<'a>,
|
||||||
|
field_symbols: &'a [Symbol],
|
||||||
|
loc_args: std::vec::Vec<(Variable, Located<roc_can::expr::Expr>)>,
|
||||||
|
layout_cache: &mut LayoutCache<'a>,
|
||||||
|
assigned: Symbol,
|
||||||
|
hole: &'a Stmt<'a>,
|
||||||
|
) -> Stmt<'a> {
|
||||||
|
let function_layout = FunctionLayouts::from_layout(env.arena, layout);
|
||||||
|
|
||||||
|
procs.specialized.remove(&(proc_name, layout));
|
||||||
|
|
||||||
|
procs
|
||||||
|
.specialized
|
||||||
|
.insert((proc_name, function_layout.full), Done(proc));
|
||||||
|
|
||||||
|
if field_symbols.is_empty() {
|
||||||
|
debug_assert!(loc_args.is_empty());
|
||||||
|
|
||||||
|
// This happens when we return a function, e.g.
|
||||||
|
//
|
||||||
|
// foo = Num.add
|
||||||
|
//
|
||||||
|
// Even though the layout (and type) are functions,
|
||||||
|
// there are no arguments. This confuses our IR,
|
||||||
|
// and we have to fix it here.
|
||||||
|
match layout {
|
||||||
|
Layout::Closure(_, closure_layout, _) => {
|
||||||
|
let call = self::Call {
|
||||||
|
call_type: CallType::ByName {
|
||||||
|
name: proc_name,
|
||||||
|
ret_layout: function_layout.result,
|
||||||
|
full_layout: function_layout.full,
|
||||||
|
arg_layouts: function_layout.arguments,
|
||||||
|
},
|
||||||
|
arguments: field_symbols,
|
||||||
|
};
|
||||||
|
|
||||||
|
// in the case of a closure specifically, we
|
||||||
|
// have to create a custom layout, to make sure
|
||||||
|
// the closure data is part of the layout
|
||||||
|
let closure_struct_layout = Layout::Struct(env.arena.alloc([
|
||||||
|
function_layout.full,
|
||||||
|
closure_layout.as_block_of_memory_layout(),
|
||||||
|
]));
|
||||||
|
|
||||||
|
build_call(env, call, assigned, closure_struct_layout, hole)
|
||||||
|
}
|
||||||
|
_ => {
|
||||||
|
let call = self::Call {
|
||||||
|
call_type: CallType::ByName {
|
||||||
|
name: proc_name,
|
||||||
|
ret_layout: function_layout.result,
|
||||||
|
full_layout: function_layout.full,
|
||||||
|
arg_layouts: function_layout.arguments,
|
||||||
|
},
|
||||||
|
arguments: field_symbols,
|
||||||
|
};
|
||||||
|
|
||||||
|
build_call(env, call, assigned, function_layout.full, hole)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
debug_assert_eq!(
|
||||||
|
function_layout.arguments.len(),
|
||||||
|
field_symbols.len(),
|
||||||
|
"scroll up a bit for background"
|
||||||
);
|
);
|
||||||
Stmt::RuntimeError(env.arena.alloc(msg))
|
let call = self::Call {
|
||||||
}
|
call_type: CallType::ByName {
|
||||||
Err(LayoutProblem::Erroneous) => {
|
name: proc_name,
|
||||||
let msg = format!(
|
ret_layout: function_layout.result,
|
||||||
"Hit an erroneous type when creating a layout for {:?}",
|
full_layout: function_layout.full,
|
||||||
proc_name
|
arg_layouts: function_layout.arguments,
|
||||||
);
|
},
|
||||||
Stmt::RuntimeError(env.arena.alloc(msg))
|
arguments: field_symbols,
|
||||||
}
|
};
|
||||||
|
|
||||||
|
let iter = loc_args.into_iter().rev().zip(field_symbols.iter().rev());
|
||||||
|
|
||||||
|
let result = build_call(env, call, assigned, function_layout.result, hole);
|
||||||
|
|
||||||
|
assign_to_symbols(env, procs, layout_cache, iter, result)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -341,13 +341,13 @@ mod test_mono {
|
||||||
"#,
|
"#,
|
||||||
indoc!(
|
indoc!(
|
||||||
r#"
|
r#"
|
||||||
procedure Num.46 (#Attr.2):
|
procedure Num.47 (#Attr.2):
|
||||||
let Test.3 = lowlevel NumRound #Attr.2;
|
let Test.3 = lowlevel NumRound #Attr.2;
|
||||||
ret Test.3;
|
ret Test.3;
|
||||||
|
|
||||||
procedure Test.0 ():
|
procedure Test.0 ():
|
||||||
let Test.2 = 3.6f64;
|
let Test.2 = 3.6f64;
|
||||||
let Test.1 = CallByName Num.46 Test.2;
|
let Test.1 = CallByName Num.47 Test.2;
|
||||||
ret Test.1;
|
ret Test.1;
|
||||||
"#
|
"#
|
||||||
),
|
),
|
||||||
|
|
|
@ -250,10 +250,7 @@ fn loc_possibly_negative_or_negated_term<'a>(
|
||||||
]
|
]
|
||||||
}
|
}
|
||||||
|
|
||||||
fn fail_expr_start_e<'a, T>() -> impl Parser<'a, T, EExpr<'a>>
|
fn fail_expr_start_e<'a, T: 'a>() -> impl Parser<'a, T, EExpr<'a>> {
|
||||||
where
|
|
||||||
T: 'a,
|
|
||||||
{
|
|
||||||
|_arena, state: State<'a>| Err((NoProgress, EExpr::Start(state.line, state.column), state))
|
|_arena, state: State<'a>| Err((NoProgress, EExpr::Start(state.line, state.column), state))
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -1027,7 +1024,7 @@ fn parse_expr_operator<'a>(
|
||||||
|
|
||||||
match expr_to_pattern_help(arena, &call.value) {
|
match expr_to_pattern_help(arena, &call.value) {
|
||||||
Ok(good) => {
|
Ok(good) => {
|
||||||
let (_, mut ann_type, state) = specialize(
|
let parser = specialize(
|
||||||
EExpr::Type,
|
EExpr::Type,
|
||||||
space0_before_e(
|
space0_before_e(
|
||||||
type_annotation::located_help(indented_more),
|
type_annotation::located_help(indented_more),
|
||||||
|
@ -1035,22 +1032,29 @@ fn parse_expr_operator<'a>(
|
||||||
Type::TSpace,
|
Type::TSpace,
|
||||||
Type::TIndentStart,
|
Type::TIndentStart,
|
||||||
),
|
),
|
||||||
)
|
);
|
||||||
.parse(arena, state)?;
|
|
||||||
|
|
||||||
|
match parser.parse(arena, state) {
|
||||||
|
Err((_, fail, state)) => return Err((MadeProgress, fail, state)),
|
||||||
|
Ok((_, mut ann_type, state)) => {
|
||||||
// put the spaces from after the operator in front of the call
|
// put the spaces from after the operator in front of the call
|
||||||
if !spaces_after_operator.is_empty() {
|
if !spaces_after_operator.is_empty() {
|
||||||
ann_type = arena
|
ann_type = arena.alloc(ann_type.value).with_spaces_before(
|
||||||
.alloc(ann_type.value)
|
spaces_after_operator,
|
||||||
.with_spaces_before(spaces_after_operator, ann_type.region);
|
ann_type.region,
|
||||||
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
let alias_region = Region::span_across(&call.region, &ann_type.region);
|
let alias_region =
|
||||||
|
Region::span_across(&call.region, &ann_type.region);
|
||||||
|
|
||||||
let alias = Def::Annotation(Located::at(expr_region, good), ann_type);
|
let alias =
|
||||||
|
Def::Annotation(Located::at(expr_region, good), ann_type);
|
||||||
|
|
||||||
(&*arena.alloc(Located::at(alias_region, alias)), state)
|
(&*arena.alloc(Located::at(alias_region, alias)), state)
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
Err(_) => {
|
Err(_) => {
|
||||||
// this `:` likely occured inline; treat it as an invalid operator
|
// this `:` likely occured inline; treat it as an invalid operator
|
||||||
let fail = EExpr::BadOperator(
|
let fail = EExpr::BadOperator(
|
||||||
|
|
|
@ -45,6 +45,10 @@ fn tag_union_type<'a>(min_indent: u16) -> impl Parser<'a, TypeAnnotation<'a>, TT
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fn fail_type_start<'a, T: 'a>() -> impl Parser<'a, T, Type<'a>> {
|
||||||
|
|_arena, state: State<'a>| Err((NoProgress, Type::TStart(state.line, state.column), state))
|
||||||
|
}
|
||||||
|
|
||||||
fn term<'a>(min_indent: u16) -> impl Parser<'a, Located<TypeAnnotation<'a>>, Type<'a>> {
|
fn term<'a>(min_indent: u16) -> impl Parser<'a, Located<TypeAnnotation<'a>>, Type<'a>> {
|
||||||
map_with_arena!(
|
map_with_arena!(
|
||||||
and!(
|
and!(
|
||||||
|
@ -54,7 +58,8 @@ fn term<'a>(min_indent: u16) -> impl Parser<'a, Located<TypeAnnotation<'a>>, Typ
|
||||||
loc!(specialize(Type::TRecord, record_type(min_indent))),
|
loc!(specialize(Type::TRecord, record_type(min_indent))),
|
||||||
loc!(specialize(Type::TTagUnion, tag_union_type(min_indent))),
|
loc!(specialize(Type::TTagUnion, tag_union_type(min_indent))),
|
||||||
loc!(applied_type(min_indent)),
|
loc!(applied_type(min_indent)),
|
||||||
loc!(parse_type_variable)
|
loc!(parse_type_variable),
|
||||||
|
fail_type_start(),
|
||||||
),
|
),
|
||||||
// Inline alias notation, e.g. [ Nil, Cons a (List a) ] as List a
|
// Inline alias notation, e.g. [ Nil, Cons a (List a) ] as List a
|
||||||
one_of![
|
one_of![
|
||||||
|
|
|
@ -1559,6 +1559,9 @@ fn to_pattern_report<'a>(
|
||||||
EPattern::Record(record, row, col) => {
|
EPattern::Record(record, row, col) => {
|
||||||
to_precord_report(alloc, filename, &record, *row, *col)
|
to_precord_report(alloc, filename, &record, *row, *col)
|
||||||
}
|
}
|
||||||
|
EPattern::PInParens(inparens, row, col) => {
|
||||||
|
to_pattern_in_parens_report(alloc, filename, &inparens, *row, *col)
|
||||||
|
}
|
||||||
_ => todo!("unhandled parse error: {:?}", parse_problem),
|
_ => todo!("unhandled parse error: {:?}", parse_problem),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -1808,6 +1811,143 @@ fn to_precord_report<'a>(
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fn to_pattern_in_parens_report<'a>(
|
||||||
|
alloc: &'a RocDocAllocator<'a>,
|
||||||
|
filename: PathBuf,
|
||||||
|
parse_problem: &roc_parse::parser::PInParens<'a>,
|
||||||
|
start_row: Row,
|
||||||
|
start_col: Col,
|
||||||
|
) -> Report<'a> {
|
||||||
|
use roc_parse::parser::PInParens;
|
||||||
|
|
||||||
|
match *parse_problem {
|
||||||
|
PInParens::Open(row, col) => {
|
||||||
|
// `Open` case is for exhaustiveness, this case shouldn not be reachable practically.
|
||||||
|
let surroundings = Region::from_rows_cols(start_row, start_col, row, col);
|
||||||
|
let region = Region::from_row_col(row, col);
|
||||||
|
|
||||||
|
let doc = alloc.stack(vec![
|
||||||
|
alloc.reflow(
|
||||||
|
r"I just started parsing a pattern in parentheses, but I got stuck here:",
|
||||||
|
),
|
||||||
|
alloc.region_with_subregion(surroundings, region),
|
||||||
|
alloc.concat(vec![
|
||||||
|
alloc.reflow(r"A pattern in parentheses looks like "),
|
||||||
|
alloc.parser_suggestion("(Ok 32)"),
|
||||||
|
alloc.reflow(r" or "),
|
||||||
|
alloc.parser_suggestion("(\"hello\")"),
|
||||||
|
alloc.reflow(" so I was expecting to see an expression next."),
|
||||||
|
]),
|
||||||
|
]);
|
||||||
|
|
||||||
|
Report {
|
||||||
|
filename,
|
||||||
|
doc,
|
||||||
|
title: "UNFINISHED PARENTHESES".to_string(),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
PInParens::End(row, col) => {
|
||||||
|
let surroundings = Region::from_rows_cols(start_row, start_col, row, col);
|
||||||
|
let region = Region::from_row_col(row, col);
|
||||||
|
|
||||||
|
let doc = alloc.stack(vec![
|
||||||
|
alloc.reflow("I am partway through parsing a pattern in parentheses, but I got stuck here:"),
|
||||||
|
alloc.region_with_subregion(surroundings, region),
|
||||||
|
alloc.concat(vec![
|
||||||
|
alloc.reflow(
|
||||||
|
r"I was expecting to see a closing parenthesis before this, so try adding a ",
|
||||||
|
),
|
||||||
|
alloc.parser_suggestion(")"),
|
||||||
|
alloc.reflow(" and see if that helps?"),
|
||||||
|
]),
|
||||||
|
]);
|
||||||
|
|
||||||
|
Report {
|
||||||
|
filename,
|
||||||
|
doc,
|
||||||
|
title: "UNFINISHED PARENTHESES".to_string(),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
PInParens::Pattern(pattern, row, col) => {
|
||||||
|
to_pattern_report(alloc, filename, pattern, row, col)
|
||||||
|
}
|
||||||
|
|
||||||
|
PInParens::IndentOpen(row, col) => {
|
||||||
|
let surroundings = Region::from_rows_cols(start_row, start_col, row, col);
|
||||||
|
let region = Region::from_row_col(row, col);
|
||||||
|
|
||||||
|
let doc = alloc.stack(vec![
|
||||||
|
alloc.reflow(
|
||||||
|
r"I just started parsing a pattern in parentheses, but I got stuck here:",
|
||||||
|
),
|
||||||
|
alloc.region_with_subregion(surroundings, region),
|
||||||
|
record_patterns_look_like(alloc),
|
||||||
|
note_for_record_pattern_indent(alloc),
|
||||||
|
]);
|
||||||
|
|
||||||
|
Report {
|
||||||
|
filename,
|
||||||
|
doc,
|
||||||
|
title: "UNFINISHED PARENTHESES".to_string(),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
PInParens::IndentEnd(row, col) => {
|
||||||
|
match next_line_starts_with_close_parenthesis(alloc.src_lines, row.saturating_sub(1)) {
|
||||||
|
Some((curly_row, curly_col)) => {
|
||||||
|
let surroundings =
|
||||||
|
Region::from_rows_cols(start_row, start_col, curly_row, curly_col);
|
||||||
|
let region = Region::from_row_col(curly_row, curly_col);
|
||||||
|
|
||||||
|
let doc = alloc.stack(vec![
|
||||||
|
alloc.reflow(
|
||||||
|
"I am partway through parsing a pattern in parentheses, but I got stuck here:",
|
||||||
|
),
|
||||||
|
alloc.region_with_subregion(surroundings, region),
|
||||||
|
alloc.concat(vec![
|
||||||
|
alloc.reflow("I need this parenthesis to be indented more. Try adding more spaces before it!"),
|
||||||
|
]),
|
||||||
|
]);
|
||||||
|
|
||||||
|
Report {
|
||||||
|
filename,
|
||||||
|
doc,
|
||||||
|
title: "NEED MORE INDENTATION".to_string(),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
None => {
|
||||||
|
let surroundings = Region::from_rows_cols(start_row, start_col, row, col);
|
||||||
|
let region = Region::from_row_col(row, col);
|
||||||
|
|
||||||
|
let doc = alloc.stack(vec![
|
||||||
|
alloc.reflow(
|
||||||
|
r"I am partway through parsing a pattern in parentheses, but I got stuck here:",
|
||||||
|
),
|
||||||
|
alloc.region_with_subregion(surroundings, region),
|
||||||
|
alloc.concat(vec![
|
||||||
|
alloc.reflow("I was expecting to see a closing parenthesis "),
|
||||||
|
alloc.reflow("before this, so try adding a "),
|
||||||
|
alloc.parser_suggestion(")"),
|
||||||
|
alloc.reflow(" and see if that helps?"),
|
||||||
|
]),
|
||||||
|
note_for_record_pattern_indent(alloc),
|
||||||
|
]);
|
||||||
|
|
||||||
|
Report {
|
||||||
|
filename,
|
||||||
|
doc,
|
||||||
|
title: "UNFINISHED PARENTHESES".to_string(),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
PInParens::Space(error, row, col) => to_space_report(alloc, filename, &error, row, col),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
fn to_type_report<'a>(
|
fn to_type_report<'a>(
|
||||||
alloc: &'a RocDocAllocator<'a>,
|
alloc: &'a RocDocAllocator<'a>,
|
||||||
filename: PathBuf,
|
filename: PathBuf,
|
||||||
|
@ -1854,7 +1994,13 @@ fn to_type_report<'a>(
|
||||||
let doc = alloc.stack(vec![
|
let doc = alloc.stack(vec![
|
||||||
alloc.reflow(r"I just started parsing a type, but I got stuck here:"),
|
alloc.reflow(r"I just started parsing a type, but I got stuck here:"),
|
||||||
alloc.region_with_subregion(surroundings, region),
|
alloc.region_with_subregion(surroundings, region),
|
||||||
alloc.note("I may be confused by indentation"),
|
alloc.concat(vec![
|
||||||
|
alloc.reflow(r"I am expecting a type next, like "),
|
||||||
|
alloc.parser_suggestion("Bool"),
|
||||||
|
alloc.reflow(r" or "),
|
||||||
|
alloc.parser_suggestion("List a"),
|
||||||
|
alloc.reflow("."),
|
||||||
|
]),
|
||||||
]);
|
]);
|
||||||
|
|
||||||
Report {
|
Report {
|
||||||
|
@ -3253,22 +3399,24 @@ pub fn starts_with_keyword(rest_of_line: &str, keyword: &str) -> bool {
|
||||||
}
|
}
|
||||||
|
|
||||||
fn next_line_starts_with_close_curly(source_lines: &[&str], row: Row) -> Option<(Row, Col)> {
|
fn next_line_starts_with_close_curly(source_lines: &[&str], row: Row) -> Option<(Row, Col)> {
|
||||||
match source_lines.get(row as usize + 1) {
|
next_line_starts_with_char(source_lines, row, '}')
|
||||||
None => None,
|
}
|
||||||
|
|
||||||
Some(line) => {
|
fn next_line_starts_with_close_parenthesis(source_lines: &[&str], row: Row) -> Option<(Row, Col)> {
|
||||||
let spaces_dropped = line.trim_start_matches(' ');
|
next_line_starts_with_char(source_lines, row, ')')
|
||||||
match spaces_dropped.chars().next() {
|
|
||||||
Some('}') => Some((row + 1, (line.len() - spaces_dropped.len()) as u16)),
|
|
||||||
_ => None,
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
fn next_line_starts_with_close_square_bracket(
|
fn next_line_starts_with_close_square_bracket(
|
||||||
source_lines: &[&str],
|
source_lines: &[&str],
|
||||||
row: Row,
|
row: Row,
|
||||||
|
) -> Option<(Row, Col)> {
|
||||||
|
next_line_starts_with_char(source_lines, row, ']')
|
||||||
|
}
|
||||||
|
|
||||||
|
fn next_line_starts_with_char(
|
||||||
|
source_lines: &[&str],
|
||||||
|
row: Row,
|
||||||
|
character: char,
|
||||||
) -> Option<(Row, Col)> {
|
) -> Option<(Row, Col)> {
|
||||||
match source_lines.get(row as usize + 1) {
|
match source_lines.get(row as usize + 1) {
|
||||||
None => None,
|
None => None,
|
||||||
|
@ -3276,7 +3424,9 @@ fn next_line_starts_with_close_square_bracket(
|
||||||
Some(line) => {
|
Some(line) => {
|
||||||
let spaces_dropped = line.trim_start_matches(' ');
|
let spaces_dropped = line.trim_start_matches(' ');
|
||||||
match spaces_dropped.chars().next() {
|
match spaces_dropped.chars().next() {
|
||||||
Some(']') => Some((row + 1, (line.len() - spaces_dropped.len()) as u16)),
|
Some(c) if c == character => {
|
||||||
|
Some((row + 1, (line.len() - spaces_dropped.len()) as u16))
|
||||||
|
}
|
||||||
_ => None,
|
_ => None,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -4502,12 +4502,14 @@ mod test_reporting {
|
||||||
),
|
),
|
||||||
indoc!(
|
indoc!(
|
||||||
r#"
|
r#"
|
||||||
── BAD TYPE VARIABLE ───────────────────────────────────────────────────────────
|
── UNFINISHED TYPE ─────────────────────────────────────────────────────────────
|
||||||
|
|
||||||
I am expecting a type variable, but I got stuck here:
|
I just started parsing a type, but I got stuck here:
|
||||||
|
|
||||||
1│ f : (
|
1│ f : (
|
||||||
^
|
^
|
||||||
|
|
||||||
|
I am expecting a type next, like Bool or List a.
|
||||||
"#
|
"#
|
||||||
),
|
),
|
||||||
)
|
)
|
||||||
|
@ -4610,9 +4612,7 @@ mod test_reporting {
|
||||||
}
|
}
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
#[ignore]
|
|
||||||
fn type_apply_stray_dot() {
|
fn type_apply_stray_dot() {
|
||||||
// TODO good message
|
|
||||||
report_problem_as(
|
report_problem_as(
|
||||||
indoc!(
|
indoc!(
|
||||||
r#"
|
r#"
|
||||||
|
@ -4621,16 +4621,14 @@ mod test_reporting {
|
||||||
),
|
),
|
||||||
indoc!(
|
indoc!(
|
||||||
r#"
|
r#"
|
||||||
── UNFINISHED PARENTHESES ──────────────────────────────────────────────────────
|
── UNFINISHED TYPE ─────────────────────────────────────────────────────────────
|
||||||
|
|
||||||
I am partway through parsing a type in parentheses, but I got stuck
|
I just started parsing a type, but I got stuck here:
|
||||||
here:
|
|
||||||
|
|
||||||
1│ f : ( I64
|
1│ f : .
|
||||||
^
|
^
|
||||||
|
|
||||||
I was expecting to see a closing parenthesis before this, so try
|
I am expecting a type next, like Bool or List a.
|
||||||
adding a ) and see if that helps?
|
|
||||||
"#
|
"#
|
||||||
),
|
),
|
||||||
)
|
)
|
||||||
|
@ -6052,4 +6050,162 @@ mod test_reporting {
|
||||||
),
|
),
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn applied_tag_function() {
|
||||||
|
report_problem_as(
|
||||||
|
indoc!(
|
||||||
|
r#"
|
||||||
|
x : List [ Foo Str ]
|
||||||
|
x = List.map [ 1, 2 ] Foo
|
||||||
|
|
||||||
|
x
|
||||||
|
"#
|
||||||
|
),
|
||||||
|
indoc!(
|
||||||
|
r#"
|
||||||
|
── TYPE MISMATCH ───────────────────────────────────────────────────────────────
|
||||||
|
|
||||||
|
Something is off with the body of the `x` definition:
|
||||||
|
|
||||||
|
1│ x : List [ Foo Str ]
|
||||||
|
2│ x = List.map [ 1, 2 ] Foo
|
||||||
|
^^^^^^^^^^^^^^^^^^^^^
|
||||||
|
|
||||||
|
This `map` call produces:
|
||||||
|
|
||||||
|
List [ Foo Num a ]
|
||||||
|
|
||||||
|
But the type annotation on `x` says it should be:
|
||||||
|
|
||||||
|
List [ Foo Str ]
|
||||||
|
"#
|
||||||
|
),
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn pattern_in_parens_open() {
|
||||||
|
report_problem_as(
|
||||||
|
indoc!(
|
||||||
|
r#"
|
||||||
|
\( a
|
||||||
|
"#
|
||||||
|
),
|
||||||
|
indoc!(
|
||||||
|
r#"
|
||||||
|
── UNFINISHED PARENTHESES ──────────────────────────────────────────────────────
|
||||||
|
|
||||||
|
I am partway through parsing a pattern in parentheses, but I got stuck
|
||||||
|
here:
|
||||||
|
|
||||||
|
1│ \( a
|
||||||
|
^
|
||||||
|
|
||||||
|
I was expecting to see a closing parenthesis before this, so try
|
||||||
|
adding a ) and see if that helps?
|
||||||
|
"#
|
||||||
|
),
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn pattern_in_parens_end_comma() {
|
||||||
|
report_problem_as(
|
||||||
|
indoc!(
|
||||||
|
r#"
|
||||||
|
\( a,
|
||||||
|
"#
|
||||||
|
),
|
||||||
|
indoc!(
|
||||||
|
r#"
|
||||||
|
── UNFINISHED PARENTHESES ──────────────────────────────────────────────────────
|
||||||
|
|
||||||
|
I am partway through parsing a pattern in parentheses, but I got stuck
|
||||||
|
here:
|
||||||
|
|
||||||
|
1│ \( a,
|
||||||
|
^
|
||||||
|
|
||||||
|
I was expecting to see a closing parenthesis before this, so try
|
||||||
|
adding a ) and see if that helps?
|
||||||
|
"#
|
||||||
|
),
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn pattern_in_parens_end() {
|
||||||
|
report_problem_as(
|
||||||
|
indoc!(
|
||||||
|
r#"
|
||||||
|
\( a
|
||||||
|
"#
|
||||||
|
),
|
||||||
|
indoc!(
|
||||||
|
r#"
|
||||||
|
── UNFINISHED PARENTHESES ──────────────────────────────────────────────────────
|
||||||
|
|
||||||
|
I am partway through parsing a pattern in parentheses, but I got stuck
|
||||||
|
here:
|
||||||
|
|
||||||
|
1│ \( a
|
||||||
|
^
|
||||||
|
|
||||||
|
I was expecting to see a closing parenthesis before this, so try
|
||||||
|
adding a ) and see if that helps?
|
||||||
|
"#
|
||||||
|
),
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn pattern_in_parens_indent_end() {
|
||||||
|
report_problem_as(
|
||||||
|
indoc!(
|
||||||
|
r#"
|
||||||
|
x = \( a
|
||||||
|
)
|
||||||
|
"#
|
||||||
|
),
|
||||||
|
indoc!(
|
||||||
|
r#"
|
||||||
|
── NEED MORE INDENTATION ───────────────────────────────────────────────────────
|
||||||
|
|
||||||
|
I am partway through parsing a pattern in parentheses, but I got stuck
|
||||||
|
here:
|
||||||
|
|
||||||
|
1│ x = \( a
|
||||||
|
2│ )
|
||||||
|
^
|
||||||
|
|
||||||
|
I need this parenthesis to be indented more. Try adding more spaces
|
||||||
|
before it!
|
||||||
|
"#
|
||||||
|
),
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn pattern_in_parens_indent_open() {
|
||||||
|
report_problem_as(
|
||||||
|
indoc!(
|
||||||
|
r#"
|
||||||
|
\(
|
||||||
|
"#
|
||||||
|
),
|
||||||
|
indoc!(
|
||||||
|
r#"
|
||||||
|
── UNFINISHED PATTERN ──────────────────────────────────────────────────────────
|
||||||
|
|
||||||
|
I just started parsing a pattern, but I got stuck here:
|
||||||
|
|
||||||
|
1│ \(
|
||||||
|
^
|
||||||
|
|
||||||
|
Note: I may be confused by indentation
|
||||||
|
"#
|
||||||
|
),
|
||||||
|
)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -566,6 +566,107 @@ mod solve_expr {
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn applied_tag() {
|
||||||
|
infer_eq_without_problem(
|
||||||
|
indoc!(
|
||||||
|
r#"
|
||||||
|
List.map [ "a", "b" ] \elem -> Foo elem
|
||||||
|
"#
|
||||||
|
),
|
||||||
|
"List [ Foo Str ]*",
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Tests (TagUnion, Func)
|
||||||
|
#[test]
|
||||||
|
fn applied_tag_function() {
|
||||||
|
infer_eq_without_problem(
|
||||||
|
indoc!(
|
||||||
|
r#"
|
||||||
|
foo = Foo
|
||||||
|
|
||||||
|
foo "hi"
|
||||||
|
"#
|
||||||
|
),
|
||||||
|
"[ Foo Str ]*",
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Tests (TagUnion, Func)
|
||||||
|
#[test]
|
||||||
|
fn applied_tag_function_list_map() {
|
||||||
|
infer_eq_without_problem(
|
||||||
|
indoc!(
|
||||||
|
r#"
|
||||||
|
List.map [ "a", "b" ] Foo
|
||||||
|
"#
|
||||||
|
),
|
||||||
|
"List [ Foo Str ]*",
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Tests (TagUnion, Func)
|
||||||
|
#[test]
|
||||||
|
fn applied_tag_function_list() {
|
||||||
|
infer_eq_without_problem(
|
||||||
|
indoc!(
|
||||||
|
r#"
|
||||||
|
[ \x -> Bar x, Foo ]
|
||||||
|
"#
|
||||||
|
),
|
||||||
|
"List (a -> [ Bar a, Foo a ]*)",
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Tests (Func, TagUnion)
|
||||||
|
#[test]
|
||||||
|
fn applied_tag_function_list_other_way() {
|
||||||
|
infer_eq_without_problem(
|
||||||
|
indoc!(
|
||||||
|
r#"
|
||||||
|
[ Foo, \x -> Bar x ]
|
||||||
|
"#
|
||||||
|
),
|
||||||
|
"List (a -> [ Bar a, Foo a ]*)",
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Tests (Func, TagUnion)
|
||||||
|
#[test]
|
||||||
|
fn applied_tag_function_record() {
|
||||||
|
infer_eq_without_problem(
|
||||||
|
indoc!(
|
||||||
|
r#"
|
||||||
|
foo = Foo
|
||||||
|
|
||||||
|
{
|
||||||
|
x: [ foo, Foo ],
|
||||||
|
y: [ foo, \x -> Foo x ],
|
||||||
|
z: [ foo, \x,y -> Foo x y ]
|
||||||
|
}
|
||||||
|
"#
|
||||||
|
),
|
||||||
|
"{ x : List [ Foo ]*, y : List (a -> [ Foo a ]*), z : List (b, c -> [ Foo b c ]*) }",
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Tests (TagUnion, Func)
|
||||||
|
#[test]
|
||||||
|
fn applied_tag_function_with_annotation() {
|
||||||
|
infer_eq_without_problem(
|
||||||
|
indoc!(
|
||||||
|
r#"
|
||||||
|
x : List [ Foo I64 ]
|
||||||
|
x = List.map [ 1, 2 ] Foo
|
||||||
|
|
||||||
|
x
|
||||||
|
"#
|
||||||
|
),
|
||||||
|
"List [ Foo I64 ]",
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn def_2_arg_closure() {
|
fn def_2_arg_closure() {
|
||||||
infer_eq(
|
infer_eq(
|
||||||
|
|
|
@ -320,6 +320,32 @@ fn list_walk_substraction() {
|
||||||
assert_evals_to!(r#"List.walk [ 1, 2 ] Num.sub 1"#, 2, i64);
|
assert_evals_to!(r#"List.walk [ 1, 2 ] Num.sub 1"#, 2, i64);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn list_walk_until_sum() {
|
||||||
|
assert_evals_to!(
|
||||||
|
r#"List.walkUntil [ 1, 2 ] (\a,b -> Continue (a + b)) 0"#,
|
||||||
|
3,
|
||||||
|
i64
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn list_walk_until_even_prefix_sum() {
|
||||||
|
assert_evals_to!(
|
||||||
|
r#"
|
||||||
|
helper = \a, b ->
|
||||||
|
if Num.isEven a then
|
||||||
|
Continue (a + b)
|
||||||
|
|
||||||
|
else
|
||||||
|
Stop b
|
||||||
|
|
||||||
|
List.walkUntil [ 2, 4, 8, 9 ] helper 0"#,
|
||||||
|
2 + 4 + 8,
|
||||||
|
i64
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn list_keep_if_empty_list_of_int() {
|
fn list_keep_if_empty_list_of_int() {
|
||||||
assert_evals_to!(
|
assert_evals_to!(
|
||||||
|
|
|
@ -404,6 +404,7 @@ mod gen_num {
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
fn f64_sqrt_zero() {
|
fn f64_sqrt_zero() {
|
||||||
assert_evals_to!(
|
assert_evals_to!(
|
||||||
indoc!(
|
indoc!(
|
||||||
|
|
|
@ -2276,3 +2276,23 @@ fn function_malformed_pattern() {
|
||||||
i64
|
i64
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
#[should_panic(expected = "Hit an erroneous type when creating a layout for")]
|
||||||
|
fn call_invalid_layout() {
|
||||||
|
assert_llvm_evals_to!(
|
||||||
|
indoc!(
|
||||||
|
r#"
|
||||||
|
f : I64 -> I64
|
||||||
|
f = \x -> x
|
||||||
|
|
||||||
|
f {}
|
||||||
|
"#
|
||||||
|
),
|
||||||
|
3,
|
||||||
|
i64,
|
||||||
|
|x| x,
|
||||||
|
false,
|
||||||
|
true
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
|
@ -3,6 +3,7 @@
|
||||||
use crate::assert_evals_to;
|
use crate::assert_evals_to;
|
||||||
use crate::assert_llvm_evals_to;
|
use crate::assert_llvm_evals_to;
|
||||||
use indoc::indoc;
|
use indoc::indoc;
|
||||||
|
use roc_std::{RocList, RocStr};
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn applied_tag_nothing_ir() {
|
fn applied_tag_nothing_ir() {
|
||||||
|
@ -974,3 +975,61 @@ fn newtype_wrapper() {
|
||||||
|x: &i64| *x
|
|x: &i64| *x
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn applied_tag_function() {
|
||||||
|
assert_evals_to!(
|
||||||
|
indoc!(
|
||||||
|
r#"
|
||||||
|
x : List [ Foo Str ]
|
||||||
|
x = List.map [ "a", "b" ] Foo
|
||||||
|
|
||||||
|
x
|
||||||
|
"#
|
||||||
|
),
|
||||||
|
RocList::from_slice(&[
|
||||||
|
RocStr::from_slice("a".as_bytes()),
|
||||||
|
RocStr::from_slice("b".as_bytes())
|
||||||
|
]),
|
||||||
|
RocList<RocStr>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn applied_tag_function_result() {
|
||||||
|
assert_evals_to!(
|
||||||
|
indoc!(
|
||||||
|
r#"
|
||||||
|
x : List (Result Str *)
|
||||||
|
x = List.map [ "a", "b" ] Ok
|
||||||
|
|
||||||
|
x
|
||||||
|
"#
|
||||||
|
),
|
||||||
|
RocList::from_slice(&[
|
||||||
|
(1, RocStr::from_slice("a".as_bytes())),
|
||||||
|
(1, RocStr::from_slice("b".as_bytes()))
|
||||||
|
]),
|
||||||
|
RocList<(i64, RocStr)>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn applied_tag_function_linked_list() {
|
||||||
|
assert_evals_to!(
|
||||||
|
indoc!(
|
||||||
|
r#"
|
||||||
|
ConsList a : [ Nil, Cons a (ConsList a) ]
|
||||||
|
|
||||||
|
x : List (ConsList Str)
|
||||||
|
x = List.map2 [ "a", "b" ] [ Nil, Cons "c" Nil ] Cons
|
||||||
|
|
||||||
|
when List.first x is
|
||||||
|
Ok (Cons "a" Nil) -> 1
|
||||||
|
_ -> 0
|
||||||
|
"#
|
||||||
|
),
|
||||||
|
1,
|
||||||
|
i64
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
|
@ -37,6 +37,7 @@ pub fn helper<'a>(
|
||||||
src: &str,
|
src: &str,
|
||||||
stdlib: &'a roc_builtins::std::StdLib,
|
stdlib: &'a roc_builtins::std::StdLib,
|
||||||
leak: bool,
|
leak: bool,
|
||||||
|
ignore_problems: bool,
|
||||||
context: &'a inkwell::context::Context,
|
context: &'a inkwell::context::Context,
|
||||||
) -> (&'static str, String, Library) {
|
) -> (&'static str, String, Library) {
|
||||||
use roc_gen::llvm::build::{build_proc, build_proc_header, Scope};
|
use roc_gen::llvm::build::{build_proc, build_proc_header, Scope};
|
||||||
|
@ -170,7 +171,7 @@ pub fn helper<'a>(
|
||||||
println!("{}", lines.join("\n"));
|
println!("{}", lines.join("\n"));
|
||||||
|
|
||||||
// only crash at this point if there were no delayed_errors
|
// only crash at this point if there were no delayed_errors
|
||||||
if delayed_errors.is_empty() {
|
if delayed_errors.is_empty() && !ignore_problems {
|
||||||
assert_eq!(0, 1, "Mistakes were made");
|
assert_eq!(0, 1, "Mistakes were made");
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -331,7 +332,7 @@ pub fn helper<'a>(
|
||||||
|
|
||||||
#[macro_export]
|
#[macro_export]
|
||||||
macro_rules! assert_llvm_evals_to {
|
macro_rules! assert_llvm_evals_to {
|
||||||
($src:expr, $expected:expr, $ty:ty, $transform:expr, $leak:expr) => {
|
($src:expr, $expected:expr, $ty:ty, $transform:expr, $leak:expr, $ignore_problems:expr) => {
|
||||||
use bumpalo::Bump;
|
use bumpalo::Bump;
|
||||||
use inkwell::context::Context;
|
use inkwell::context::Context;
|
||||||
use roc_gen::run_jit_function;
|
use roc_gen::run_jit_function;
|
||||||
|
@ -343,7 +344,7 @@ macro_rules! assert_llvm_evals_to {
|
||||||
let stdlib = arena.alloc(roc_builtins::std::standard_stdlib());
|
let stdlib = arena.alloc(roc_builtins::std::standard_stdlib());
|
||||||
|
|
||||||
let (main_fn_name, errors, lib) =
|
let (main_fn_name, errors, lib) =
|
||||||
$crate::helpers::eval::helper(&arena, $src, stdlib, $leak, &context);
|
$crate::helpers::eval::helper(&arena, $src, stdlib, $leak, $ignore_problems, &context);
|
||||||
|
|
||||||
let transform = |success| {
|
let transform = |success| {
|
||||||
let expected = $expected;
|
let expected = $expected;
|
||||||
|
@ -354,7 +355,7 @@ macro_rules! assert_llvm_evals_to {
|
||||||
};
|
};
|
||||||
|
|
||||||
($src:expr, $expected:expr, $ty:ty, $transform:expr) => {
|
($src:expr, $expected:expr, $ty:ty, $transform:expr) => {
|
||||||
assert_llvm_evals_to!($src, $expected, $ty, $transform, true);
|
assert_llvm_evals_to!($src, $expected, $ty, $transform, true, false);
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -375,7 +376,7 @@ macro_rules! assert_evals_to {
|
||||||
// parsing the source, so that there's no chance their passing
|
// parsing the source, so that there's no chance their passing
|
||||||
// or failing depends on leftover state from the previous one.
|
// or failing depends on leftover state from the previous one.
|
||||||
{
|
{
|
||||||
assert_llvm_evals_to!($src, $expected, $ty, $transform, $leak);
|
assert_llvm_evals_to!($src, $expected, $ty, $transform, $leak, false);
|
||||||
}
|
}
|
||||||
{
|
{
|
||||||
// NOTE at the moment, the optimized tests do the same thing
|
// NOTE at the moment, the optimized tests do the same thing
|
||||||
|
@ -392,7 +393,7 @@ macro_rules! assert_non_opt_evals_to {
|
||||||
($src:expr, $expected:expr, $ty:ty, $transform:expr) => {
|
($src:expr, $expected:expr, $ty:ty, $transform:expr) => {
|
||||||
// Same as above, except with an additional transformation argument.
|
// Same as above, except with an additional transformation argument.
|
||||||
{
|
{
|
||||||
assert_llvm_evals_to!($src, $expected, $ty, $transform, true);
|
assert_llvm_evals_to!($src, $expected, $ty, $transform, true, false);
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
($src:expr, $expected:expr, $ty:ty, $transform:expr, $leak:expr) => {{
|
($src:expr, $expected:expr, $ty:ty, $transform:expr, $leak:expr) => {{
|
||||||
|
|
|
@ -1,4 +1,6 @@
|
||||||
use roc_collections::all::{get_shared, relative_complement, union, MutMap, SendSet};
|
use roc_collections::all::{
|
||||||
|
default_hasher, get_shared, relative_complement, union, MutMap, SendSet,
|
||||||
|
};
|
||||||
use roc_module::ident::{Lowercase, TagName};
|
use roc_module::ident::{Lowercase, TagName};
|
||||||
use roc_module::symbol::Symbol;
|
use roc_module::symbol::Symbol;
|
||||||
use roc_types::boolean_algebra::Bool;
|
use roc_types::boolean_algebra::Bool;
|
||||||
|
@ -1069,6 +1071,12 @@ fn unify_flat_type(
|
||||||
problems
|
problems
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
(TagUnion(tags, ext), Func(args, closure, ret)) if tags.len() == 1 => {
|
||||||
|
unify_tag_union_and_func(tags, args, subs, pool, ctx, ext, ret, closure, true)
|
||||||
|
}
|
||||||
|
(Func(args, closure, ret), TagUnion(tags, ext)) if tags.len() == 1 => {
|
||||||
|
unify_tag_union_and_func(tags, args, subs, pool, ctx, ext, ret, closure, false)
|
||||||
|
}
|
||||||
(other1, other2) => mismatch!(
|
(other1, other2) => mismatch!(
|
||||||
"Trying to unify two flat types that are incompatible: {:?} ~ {:?}",
|
"Trying to unify two flat types that are incompatible: {:?} ~ {:?}",
|
||||||
other1,
|
other1,
|
||||||
|
@ -1250,3 +1258,54 @@ fn is_recursion_var(subs: &Subs, var: Variable) -> bool {
|
||||||
Content::RecursionVar { .. }
|
Content::RecursionVar { .. }
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[allow(clippy::too_many_arguments, clippy::ptr_arg)]
|
||||||
|
fn unify_tag_union_and_func(
|
||||||
|
tags: &MutMap<TagName, Vec<Variable>>,
|
||||||
|
args: &Vec<Variable>,
|
||||||
|
subs: &mut Subs,
|
||||||
|
pool: &mut Pool,
|
||||||
|
ctx: &Context,
|
||||||
|
ext: &Variable,
|
||||||
|
ret: &Variable,
|
||||||
|
closure: &Variable,
|
||||||
|
left: bool,
|
||||||
|
) -> Outcome {
|
||||||
|
use FlatType::*;
|
||||||
|
|
||||||
|
let (tag_name, payload) = tags.iter().next().unwrap();
|
||||||
|
|
||||||
|
if payload.is_empty() {
|
||||||
|
let mut new_tags = MutMap::with_capacity_and_hasher(1, default_hasher());
|
||||||
|
|
||||||
|
new_tags.insert(tag_name.clone(), args.to_owned());
|
||||||
|
|
||||||
|
let content = Structure(TagUnion(new_tags, *ext));
|
||||||
|
|
||||||
|
let new_tag_union_var = fresh(subs, pool, ctx, content);
|
||||||
|
|
||||||
|
let problems = if left {
|
||||||
|
unify_pool(subs, pool, new_tag_union_var, *ret)
|
||||||
|
} else {
|
||||||
|
unify_pool(subs, pool, *ret, new_tag_union_var)
|
||||||
|
};
|
||||||
|
|
||||||
|
if problems.is_empty() {
|
||||||
|
let desc = if left {
|
||||||
|
subs.get(ctx.second)
|
||||||
|
} else {
|
||||||
|
subs.get(ctx.first)
|
||||||
|
};
|
||||||
|
|
||||||
|
subs.union(ctx.first, ctx.second, desc);
|
||||||
|
}
|
||||||
|
|
||||||
|
problems
|
||||||
|
} else {
|
||||||
|
mismatch!(
|
||||||
|
"Trying to unify two flat types that are incompatible: {:?} ~ {:?}",
|
||||||
|
TagUnion(tags.clone(), *ext),
|
||||||
|
Func(args.to_owned(), *closure, *ret)
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
3
docs/tests/fixtures/Interface.roc
vendored
3
docs/tests/fixtures/Interface.roc
vendored
|
@ -2,6 +2,9 @@ interface Test
|
||||||
exposes [ singleline, multiline, multiparagraph, codeblock ]
|
exposes [ singleline, multiline, multiparagraph, codeblock ]
|
||||||
imports []
|
imports []
|
||||||
|
|
||||||
|
## This is a block
|
||||||
|
Block : [ @Block ]
|
||||||
|
|
||||||
## Single line documentation.
|
## Single line documentation.
|
||||||
singleline : Bool -> Bool
|
singleline : Bool -> Bool
|
||||||
|
|
||||||
|
|
|
@ -1,3 +1,6 @@
|
||||||
|
#[macro_use]
|
||||||
|
extern crate pretty_assertions;
|
||||||
|
|
||||||
use roc_docs::{documentation_to_template_data, files_to_documentations, ModuleEntry};
|
use roc_docs::{documentation_to_template_data, files_to_documentations, ModuleEntry};
|
||||||
use std::path::PathBuf;
|
use std::path::PathBuf;
|
||||||
|
|
||||||
|
@ -20,6 +23,10 @@ mod test_docs {
|
||||||
};
|
};
|
||||||
|
|
||||||
let expected_entries = vec![
|
let expected_entries = vec![
|
||||||
|
ModuleEntry {
|
||||||
|
name: "Block".to_string(),
|
||||||
|
docs: "<p>This is a block</p>\n".to_string(),
|
||||||
|
},
|
||||||
ModuleEntry {
|
ModuleEntry {
|
||||||
name: "singleline".to_string(),
|
name: "singleline".to_string(),
|
||||||
docs: "<p>Single line documentation.</p>\n".to_string(),
|
docs: "<p>Single line documentation.</p>\n".to_string(),
|
||||||
|
|
|
@ -78,7 +78,7 @@ e.g. you have a test `calculate_sum_test` that only uses the function `add`, whe
|
||||||
* Searchbar for examples/docs. With permission search strings could be shared with the platform/package authors so they know exactly what their users are struggling with.
|
* Searchbar for examples/docs. With permission search strings could be shared with the platform/package authors so they know exactly what their users are struggling with.
|
||||||
* Show productivity/feature tips on startup. Show link to page with all tips. Allow not seeing tips next time.
|
* Show productivity/feature tips on startup. Show link to page with all tips. Allow not seeing tips next time.
|
||||||
* Search friendly editor docs inside the editor. Offer to send search string to Roc maintainers when no results, or if no results were clicked.
|
* Search friendly editor docs inside the editor. Offer to send search string to Roc maintainers when no results, or if no results were clicked.
|
||||||
* File history timeline view. Show timeline with commits that changed this file, the number of lines added and deleted as well as which user made the changes.
|
* File history timeline view. Show timeline with commits that changed this file, the number of lines added and deleted as well as which user made the changes. Arrow navigation should allow you to quickly view different versions of the file.
|
||||||
* Suggested quick fixes should be directly visible and clickable. Not like in vs code where you put the caret on an error until a lightbulb appears in the margin which you have to click for the fixes to apppear, after which you click to apply the fix you want :( .
|
* Suggested quick fixes should be directly visible and clickable. Not like in vs code where you put the caret on an error until a lightbulb appears in the margin which you have to click for the fixes to apppear, after which you click to apply the fix you want :( .
|
||||||
|
|
||||||
#### Autocomplete
|
#### Autocomplete
|
||||||
|
|
|
@ -1,4 +1,4 @@
|
||||||
use crate::editor::slow_pool::SlowNodeId;
|
use crate::editor::slow_pool::MarkNodeId;
|
||||||
use colored::*;
|
use colored::*;
|
||||||
use snafu::{Backtrace, ErrorCompat, NoneError, ResultExt, Snafu};
|
use snafu::{Backtrace, ErrorCompat, NoneError, ResultExt, Snafu};
|
||||||
|
|
||||||
|
@ -15,7 +15,7 @@ pub enum EdError {
|
||||||
node_id
|
node_id
|
||||||
))]
|
))]
|
||||||
CaretNotFound {
|
CaretNotFound {
|
||||||
node_id: SlowNodeId,
|
node_id: MarkNodeId,
|
||||||
backtrace: Backtrace,
|
backtrace: Backtrace,
|
||||||
},
|
},
|
||||||
|
|
||||||
|
@ -31,25 +31,65 @@ pub enum EdError {
|
||||||
))]
|
))]
|
||||||
ClipboardInitFailed { err_msg: String },
|
ClipboardInitFailed { err_msg: String },
|
||||||
|
|
||||||
|
#[snafu(display(
|
||||||
|
"ExpectedTextNode: the function {} expected a Text node, got {} instead.",
|
||||||
|
function_name,
|
||||||
|
node_type
|
||||||
|
))]
|
||||||
|
ExpectedTextNode {
|
||||||
|
function_name: String,
|
||||||
|
node_type: String,
|
||||||
|
backtrace: Backtrace,
|
||||||
|
},
|
||||||
|
|
||||||
#[snafu(display("GetContentOnNestedNode: tried to get string content from Nested MarkupNode. Can only get content from Text or Blank nodes."))]
|
#[snafu(display("GetContentOnNestedNode: tried to get string content from Nested MarkupNode. Can only get content from Text or Blank nodes."))]
|
||||||
GetContentOnNestedNode { backtrace: Backtrace },
|
GetContentOnNestedNode { backtrace: Backtrace },
|
||||||
|
|
||||||
|
#[snafu(display(
|
||||||
|
"IndexOfFailed: Element {} was not found in collection {}.",
|
||||||
|
elt_str,
|
||||||
|
collection_str
|
||||||
|
))]
|
||||||
|
IndexOfFailed {
|
||||||
|
elt_str: String,
|
||||||
|
collection_str: String,
|
||||||
|
backtrace: Backtrace,
|
||||||
|
},
|
||||||
|
|
||||||
#[snafu(display("KeyNotFound: key {} was not found in HashMap.", key_str,))]
|
#[snafu(display("KeyNotFound: key {} was not found in HashMap.", key_str,))]
|
||||||
KeyNotFound {
|
KeyNotFound {
|
||||||
key_str: String,
|
key_str: String,
|
||||||
backtrace: Backtrace,
|
backtrace: Backtrace,
|
||||||
},
|
},
|
||||||
|
|
||||||
#[snafu(display("NestedNodeWithoutChildren: tried to retrieve child from Nested MarkupNode with id {} but it had no children.", node_id))]
|
#[snafu(display(
|
||||||
NestedNodeWithoutChildren {
|
"MissingParent: MarkupNode with id {} should have a parent but there was none.",
|
||||||
node_id: SlowNodeId,
|
node_id
|
||||||
|
))]
|
||||||
|
MissingParent {
|
||||||
|
node_id: MarkNodeId,
|
||||||
backtrace: Backtrace,
|
backtrace: Backtrace,
|
||||||
},
|
},
|
||||||
|
|
||||||
#[snafu(display("NestedNodeMissingChild: expected to find child with id {} in Nested MarkupNode, but it was missing. Id's of the children are {:?}.", node_id, children_ids))]
|
#[snafu(display("NestedNodeMissingChild: expected to find child with id {} in Nested MarkupNode, but it was missing. Id's of the children are {:?}.", node_id, children_ids))]
|
||||||
NestedNodeMissingChild {
|
NestedNodeMissingChild {
|
||||||
node_id: SlowNodeId,
|
node_id: MarkNodeId,
|
||||||
children_ids: Vec<SlowNodeId>,
|
children_ids: Vec<MarkNodeId>,
|
||||||
|
backtrace: Backtrace,
|
||||||
|
},
|
||||||
|
|
||||||
|
#[snafu(display(
|
||||||
|
"NestedNodeRequired: required a Nested node at this position, node was a {}.",
|
||||||
|
node_type
|
||||||
|
))]
|
||||||
|
NestedNodeRequired {
|
||||||
|
node_type: String,
|
||||||
|
backtrace: Backtrace,
|
||||||
|
},
|
||||||
|
|
||||||
|
#[snafu(display("NestedNodeWithoutChildren: tried to retrieve child from Nested MarkupNode with id {} but it had no children.", node_id))]
|
||||||
|
NestedNodeWithoutChildren {
|
||||||
|
node_id: MarkNodeId,
|
||||||
backtrace: Backtrace,
|
backtrace: Backtrace,
|
||||||
},
|
},
|
||||||
|
|
||||||
|
@ -72,6 +112,9 @@ pub enum EdError {
|
||||||
#[snafu(display("ParseError: Failed to parse AST: SyntaxError: {}.", syntax_err))]
|
#[snafu(display("ParseError: Failed to parse AST: SyntaxError: {}.", syntax_err))]
|
||||||
ParseError { syntax_err: String },
|
ParseError { syntax_err: String },
|
||||||
|
|
||||||
|
#[snafu(display("RecordWithoutFields: expected record to have at least one field because it is not an EmpyRecord."))]
|
||||||
|
RecordWithoutFields { backtrace: Backtrace },
|
||||||
|
|
||||||
#[snafu(display("UIError: {}", msg))]
|
#[snafu(display("UIError: {}", msg))]
|
||||||
UIErrorBacktrace { msg: String, backtrace: Backtrace },
|
UIErrorBacktrace { msg: String, backtrace: Backtrace },
|
||||||
}
|
}
|
||||||
|
|
52
editor/src/editor/grid_node_map.rs
Normal file
52
editor/src/editor/grid_node_map.rs
Normal file
|
@ -0,0 +1,52 @@
|
||||||
|
use crate::editor::slow_pool::MarkNodeId;
|
||||||
|
use crate::ui::text::text_pos::TextPos;
|
||||||
|
use crate::ui::ui_error::UIResult;
|
||||||
|
use crate::ui::util::{slice_get, slice_get_mut};
|
||||||
|
|
||||||
|
#[derive(Debug)]
|
||||||
|
pub struct GridNodeMap {
|
||||||
|
pub lines: Vec<Vec<MarkNodeId>>,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl GridNodeMap {
|
||||||
|
pub fn new() -> GridNodeMap {
|
||||||
|
GridNodeMap {
|
||||||
|
lines: vec![vec![]],
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn add_to_line(&mut self, line: usize, len: usize, node_id: MarkNodeId) -> UIResult<()> {
|
||||||
|
let line_ref = slice_get_mut(line, &mut self.lines)?;
|
||||||
|
let mut new_cols_vec: Vec<MarkNodeId> = std::iter::repeat(node_id).take(len).collect();
|
||||||
|
|
||||||
|
line_ref.append(&mut new_cols_vec);
|
||||||
|
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn insert_between_line(
|
||||||
|
&mut self,
|
||||||
|
line: usize,
|
||||||
|
index: usize,
|
||||||
|
len: usize,
|
||||||
|
node_id: MarkNodeId,
|
||||||
|
) -> UIResult<()> {
|
||||||
|
let line_ref = slice_get_mut(line, &mut self.lines)?;
|
||||||
|
let new_cols_vec: Vec<MarkNodeId> = std::iter::repeat(node_id).take(len).collect();
|
||||||
|
|
||||||
|
line_ref.splice(index..index, new_cols_vec);
|
||||||
|
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
|
||||||
|
/*pub fn new_line(&mut self) {
|
||||||
|
self.lines.push(vec![])
|
||||||
|
}*/
|
||||||
|
|
||||||
|
pub fn get_id_at_row_col(&self, caret_pos: TextPos) -> UIResult<MarkNodeId> {
|
||||||
|
let line = slice_get(caret_pos.line, &self.lines)?;
|
||||||
|
let node_id = slice_get(caret_pos.column, line)?;
|
||||||
|
|
||||||
|
Ok(*node_id)
|
||||||
|
}
|
||||||
|
}
|
|
@ -1,8 +1,9 @@
|
||||||
use super::keyboard_input;
|
use super::keyboard_input;
|
||||||
use super::style::CODE_TXT_XY;
|
use super::style::CODE_TXT_XY;
|
||||||
use crate::editor::ed_error::print_ui_err;
|
use crate::editor::ed_error::print_ui_err;
|
||||||
|
use crate::editor::mvc::ed_view;
|
||||||
|
use crate::editor::mvc::ed_view::RenderedWgpu;
|
||||||
use crate::editor::resources::strings::NOTHING_OPENED;
|
use crate::editor::resources::strings::NOTHING_OPENED;
|
||||||
use crate::editor::slow_pool::SlowPool;
|
|
||||||
use crate::editor::{
|
use crate::editor::{
|
||||||
config::Config,
|
config::Config,
|
||||||
ed_error::print_err,
|
ed_error::print_err,
|
||||||
|
@ -132,7 +133,6 @@ fn run_event_loop(file_path_opt: Option<&Path>) -> Result<(), Box<dyn Error>> {
|
||||||
let mut env_pool = Pool::with_capacity(1024);
|
let mut env_pool = Pool::with_capacity(1024);
|
||||||
let env_arena = Bump::new();
|
let env_arena = Bump::new();
|
||||||
let code_arena = Bump::new();
|
let code_arena = Bump::new();
|
||||||
let mut markup_node_pool = SlowPool::new();
|
|
||||||
|
|
||||||
let mut var_store = VarStore::default();
|
let mut var_store = VarStore::default();
|
||||||
let dep_idents = IdentIds::exposed_builtins(8);
|
let dep_idents = IdentIds::exposed_builtins(8);
|
||||||
|
@ -173,13 +173,7 @@ fn run_event_loop(file_path_opt: Option<&Path>) -> Result<(), Box<dyn Error>> {
|
||||||
};
|
};
|
||||||
|
|
||||||
let ed_model_opt = {
|
let ed_model_opt = {
|
||||||
let ed_model_res = ed_model::init_model(
|
let ed_model_res = ed_model::init_model(&code_str, file_path, env, &code_arena);
|
||||||
&code_str,
|
|
||||||
file_path,
|
|
||||||
env,
|
|
||||||
&code_arena,
|
|
||||||
&mut markup_node_pool,
|
|
||||||
);
|
|
||||||
|
|
||||||
match ed_model_res {
|
match ed_model_res {
|
||||||
Ok(mut ed_model) => {
|
Ok(mut ed_model) => {
|
||||||
|
@ -194,6 +188,8 @@ fn run_event_loop(file_path_opt: Option<&Path>) -> Result<(), Box<dyn Error>> {
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
|
let mut rendered_wgpu_opt: Option<RenderedWgpu> = None;
|
||||||
|
|
||||||
let mut app_model = AppModel::init(ed_model_opt);
|
let mut app_model = AppModel::init(ed_model_opt);
|
||||||
|
|
||||||
let mut keyboard_modifiers = ModifiersState::empty();
|
let mut keyboard_modifiers = ModifiersState::empty();
|
||||||
|
@ -298,21 +294,25 @@ fn run_event_loop(file_path_opt: Option<&Path>) -> Result<(), Box<dyn Error>> {
|
||||||
.output;
|
.output;
|
||||||
|
|
||||||
if let Some(ref mut ed_model) = app_model.ed_model_opt {
|
if let Some(ref mut ed_model) = app_model.ed_model_opt {
|
||||||
// TODO only calculate if markup_root has changed
|
if rendered_wgpu_opt.is_none() || ed_model.dirty {
|
||||||
let text_and_rects_res = super::mvc::ed_view::model_to_wgpu(
|
let rendered_wgpu_res =
|
||||||
ed_model,
|
ed_view::model_to_wgpu(ed_model, &size, CODE_TXT_XY.into(), &config);
|
||||||
&size,
|
|
||||||
CODE_TXT_XY.into(),
|
|
||||||
&config,
|
|
||||||
&markup_node_pool,
|
|
||||||
);
|
|
||||||
|
|
||||||
match text_and_rects_res {
|
match rendered_wgpu_res {
|
||||||
Ok((text_section, rects)) => {
|
Ok(rendered_wgpu) => rendered_wgpu_opt = Some(rendered_wgpu),
|
||||||
glyph_brush.queue(text_section);
|
Err(e) => print_err(&e),
|
||||||
|
}
|
||||||
|
|
||||||
|
ed_model.dirty = false;
|
||||||
|
}
|
||||||
|
|
||||||
|
if let Some(ref rendered_wgpu) = rendered_wgpu_opt {
|
||||||
|
let borrowed_text = rendered_wgpu.text.to_borrowed();
|
||||||
|
|
||||||
|
glyph_brush.queue(borrowed_text);
|
||||||
|
|
||||||
draw_all_rects(
|
draw_all_rects(
|
||||||
&rects,
|
&rendered_wgpu.rects,
|
||||||
&mut encoder,
|
&mut encoder,
|
||||||
&frame.view,
|
&frame.view,
|
||||||
&gpu_device,
|
&gpu_device,
|
||||||
|
@ -320,8 +320,6 @@ fn run_event_loop(file_path_opt: Option<&Path>) -> Result<(), Box<dyn Error>> {
|
||||||
&ed_theme,
|
&ed_theme,
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
Err(e) => print_err(&e),
|
|
||||||
}
|
|
||||||
} else {
|
} else {
|
||||||
queue_no_file_text(
|
queue_no_file_text(
|
||||||
&size,
|
&size,
|
||||||
|
|
|
@ -1,5 +1,9 @@
|
||||||
use super::attribute::Attributes;
|
use super::attribute::Attributes;
|
||||||
use crate::editor::slow_pool::SlowNodeId;
|
use crate::editor::ed_error::EdResult;
|
||||||
|
use crate::editor::ed_error::ExpectedTextNode;
|
||||||
|
use crate::editor::ed_error::GetContentOnNestedNode;
|
||||||
|
use crate::editor::ed_error::NestedNodeRequired;
|
||||||
|
use crate::editor::slow_pool::MarkNodeId;
|
||||||
use crate::editor::slow_pool::SlowPool;
|
use crate::editor::slow_pool::SlowPool;
|
||||||
use crate::editor::syntax_highlight::HighlightStyle;
|
use crate::editor::syntax_highlight::HighlightStyle;
|
||||||
use crate::lang::{
|
use crate::lang::{
|
||||||
|
@ -13,36 +17,131 @@ use bumpalo::Bump;
|
||||||
pub enum MarkupNode {
|
pub enum MarkupNode {
|
||||||
Nested {
|
Nested {
|
||||||
ast_node_id: NodeId<Expr2>,
|
ast_node_id: NodeId<Expr2>,
|
||||||
children_ids: Vec<SlowNodeId>,
|
children_ids: Vec<MarkNodeId>,
|
||||||
parent_id_opt: Option<SlowNodeId>,
|
parent_id_opt: Option<MarkNodeId>,
|
||||||
},
|
},
|
||||||
Text {
|
Text {
|
||||||
content: String,
|
content: String,
|
||||||
ast_node_id: NodeId<Expr2>,
|
ast_node_id: NodeId<Expr2>,
|
||||||
syn_high_style: HighlightStyle,
|
syn_high_style: HighlightStyle,
|
||||||
attributes: Attributes,
|
attributes: Attributes,
|
||||||
parent_id_opt: Option<SlowNodeId>,
|
parent_id_opt: Option<MarkNodeId>,
|
||||||
},
|
},
|
||||||
Blank {
|
Blank {
|
||||||
ast_node_id: NodeId<Expr2>,
|
ast_node_id: NodeId<Expr2>,
|
||||||
attributes: Attributes,
|
attributes: Attributes,
|
||||||
syn_high_style: HighlightStyle,
|
syn_high_style: HighlightStyle, // TODO remove HighlightStyle, this is always HighlightStyle::Blank
|
||||||
parent_id_opt: Option<SlowNodeId>,
|
parent_id_opt: Option<MarkNodeId>,
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
|
|
||||||
pub const BLANK_PLACEHOLDER: &str = " ";
|
impl MarkupNode {
|
||||||
|
pub fn get_ast_node_id(&self) -> NodeId<Expr2> {
|
||||||
|
match self {
|
||||||
|
MarkupNode::Nested { ast_node_id, .. } => *ast_node_id,
|
||||||
|
MarkupNode::Text { ast_node_id, .. } => *ast_node_id,
|
||||||
|
MarkupNode::Blank { ast_node_id, .. } => *ast_node_id,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn get_parent_id_opt(&self) -> Option<MarkNodeId> {
|
||||||
|
match self {
|
||||||
|
MarkupNode::Nested { parent_id_opt, .. } => *parent_id_opt,
|
||||||
|
MarkupNode::Text { parent_id_opt, .. } => *parent_id_opt,
|
||||||
|
MarkupNode::Blank { parent_id_opt, .. } => *parent_id_opt,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn get_children_ids(&self) -> Vec<MarkNodeId> {
|
||||||
|
match self {
|
||||||
|
MarkupNode::Nested { children_ids, .. } => children_ids.to_vec(),
|
||||||
|
MarkupNode::Text { .. } => vec![],
|
||||||
|
MarkupNode::Blank { .. } => vec![],
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn get_sibling_ids(&self, markup_node_pool: &SlowPool) -> Vec<MarkNodeId> {
|
||||||
|
if let Some(parent_id) = self.get_parent_id_opt() {
|
||||||
|
let parent_node = markup_node_pool.get(parent_id);
|
||||||
|
|
||||||
|
parent_node.get_children_ids()
|
||||||
|
} else {
|
||||||
|
vec![]
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// can't be &str, this creates borrowing issues
|
||||||
|
pub fn get_content(&self) -> EdResult<String> {
|
||||||
|
match self {
|
||||||
|
MarkupNode::Nested { .. } => GetContentOnNestedNode {}.fail(),
|
||||||
|
MarkupNode::Text { content, .. } => Ok(content.clone()),
|
||||||
|
MarkupNode::Blank { .. } => Ok(BLANK_PLACEHOLDER.to_owned()),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn get_content_mut(&mut self) -> EdResult<&mut String> {
|
||||||
|
match self {
|
||||||
|
MarkupNode::Nested { .. } => ExpectedTextNode {
|
||||||
|
function_name: "set_content".to_owned(),
|
||||||
|
node_type: self.node_type_as_string(),
|
||||||
|
}
|
||||||
|
.fail(),
|
||||||
|
MarkupNode::Text { content, .. } => Ok(content),
|
||||||
|
MarkupNode::Blank { .. } => ExpectedTextNode {
|
||||||
|
function_name: "set_content".to_owned(),
|
||||||
|
node_type: self.node_type_as_string(),
|
||||||
|
}
|
||||||
|
.fail(),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn add_child_at_index(&mut self, index: usize, child_id: MarkNodeId) -> EdResult<()> {
|
||||||
|
if let MarkupNode::Nested { children_ids, .. } = self {
|
||||||
|
children_ids.splice(index..index, vec![child_id]);
|
||||||
|
} else {
|
||||||
|
NestedNodeRequired {
|
||||||
|
node_type: self.node_type_as_string(),
|
||||||
|
}
|
||||||
|
.fail()?;
|
||||||
|
}
|
||||||
|
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn node_type_as_string(&self) -> String {
|
||||||
|
let type_str = match self {
|
||||||
|
MarkupNode::Nested { .. } => "Nested",
|
||||||
|
MarkupNode::Text { .. } => "Text",
|
||||||
|
MarkupNode::Blank { .. } => "Blank",
|
||||||
|
};
|
||||||
|
|
||||||
|
type_str.to_owned()
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn is_blank(&self) -> bool {
|
||||||
|
matches!(self, MarkupNode::Blank { .. })
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn is_nested(&self) -> bool {
|
||||||
|
matches!(self, MarkupNode::Nested { .. })
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
fn get_string<'a>(env: &Env<'a>, pool_str: &PoolStr) -> String {
|
fn get_string<'a>(env: &Env<'a>, pool_str: &PoolStr) -> String {
|
||||||
pool_str.as_str(env.pool).to_owned()
|
pool_str.as_str(env.pool).to_owned()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
pub const BLANK_PLACEHOLDER: &str = " ";
|
||||||
|
pub const LEFT_ACCOLADE: &str = "{ ";
|
||||||
|
pub const RIGHT_ACCOLADE: &str = " }";
|
||||||
|
pub const COLON: &str = ": ";
|
||||||
|
|
||||||
fn new_markup_node(
|
fn new_markup_node(
|
||||||
text: String,
|
text: String,
|
||||||
node_id: NodeId<Expr2>,
|
node_id: NodeId<Expr2>,
|
||||||
highlight_style: HighlightStyle,
|
highlight_style: HighlightStyle,
|
||||||
markup_node_pool: &mut SlowPool,
|
markup_node_pool: &mut SlowPool,
|
||||||
) -> SlowNodeId {
|
) -> MarkNodeId {
|
||||||
let node = MarkupNode::Text {
|
let node = MarkupNode::Text {
|
||||||
content: text,
|
content: text,
|
||||||
ast_node_id: node_id,
|
ast_node_id: node_id,
|
||||||
|
@ -60,7 +159,7 @@ pub fn expr2_to_markup<'a, 'b>(
|
||||||
env: &mut Env<'b>,
|
env: &mut Env<'b>,
|
||||||
expr2: &Expr2,
|
expr2: &Expr2,
|
||||||
markup_node_pool: &mut SlowPool,
|
markup_node_pool: &mut SlowPool,
|
||||||
) -> SlowNodeId {
|
) -> MarkNodeId {
|
||||||
// TODO find way to add current expr2 to pool
|
// TODO find way to add current expr2 to pool
|
||||||
let node_id = env.pool.add(Expr2::Blank);
|
let node_id = env.pool.add(Expr2::Blank);
|
||||||
|
|
||||||
|
@ -134,7 +233,7 @@ pub fn expr2_to_markup<'a, 'b>(
|
||||||
}
|
}
|
||||||
Expr2::Record { fields, .. } => {
|
Expr2::Record { fields, .. } => {
|
||||||
let mut children_ids = vec![new_markup_node(
|
let mut children_ids = vec![new_markup_node(
|
||||||
"{ ".to_string(),
|
LEFT_ACCOLADE.to_string(),
|
||||||
node_id,
|
node_id,
|
||||||
HighlightStyle::Bracket,
|
HighlightStyle::Bracket,
|
||||||
markup_node_pool,
|
markup_node_pool,
|
||||||
|
@ -155,7 +254,7 @@ pub fn expr2_to_markup<'a, 'b>(
|
||||||
));
|
));
|
||||||
|
|
||||||
children_ids.push(new_markup_node(
|
children_ids.push(new_markup_node(
|
||||||
": ".to_string(),
|
COLON.to_string(),
|
||||||
node_id,
|
node_id,
|
||||||
HighlightStyle::Operator,
|
HighlightStyle::Operator,
|
||||||
markup_node_pool,
|
markup_node_pool,
|
||||||
|
@ -174,7 +273,7 @@ pub fn expr2_to_markup<'a, 'b>(
|
||||||
}
|
}
|
||||||
|
|
||||||
children_ids.push(new_markup_node(
|
children_ids.push(new_markup_node(
|
||||||
" }".to_string(),
|
RIGHT_ACCOLADE.to_string(),
|
||||||
node_id,
|
node_id,
|
||||||
HighlightStyle::Bracket,
|
HighlightStyle::Bracket,
|
||||||
markup_node_pool,
|
markup_node_pool,
|
||||||
|
@ -198,7 +297,7 @@ pub fn expr2_to_markup<'a, 'b>(
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn set_parent_for_all(markup_node_id: SlowNodeId, markup_node_pool: &mut SlowPool) {
|
pub fn set_parent_for_all(markup_node_id: MarkNodeId, markup_node_pool: &mut SlowPool) {
|
||||||
let node = markup_node_pool.get(markup_node_id);
|
let node = markup_node_pool.get(markup_node_id);
|
||||||
|
|
||||||
if let MarkupNode::Nested {
|
if let MarkupNode::Nested {
|
||||||
|
@ -217,17 +316,17 @@ pub fn set_parent_for_all(markup_node_id: SlowNodeId, markup_node_pool: &mut Slo
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn set_parent_for_all_helper(
|
pub fn set_parent_for_all_helper(
|
||||||
markup_node_id: SlowNodeId,
|
markup_node_id: MarkNodeId,
|
||||||
parent_node_id: SlowNodeId,
|
parent_node_id: MarkNodeId,
|
||||||
markup_node_pool: &mut SlowPool,
|
markup_node_pool: &mut SlowPool,
|
||||||
) {
|
) {
|
||||||
let node = markup_node_pool.get_mut(markup_node_id);
|
let node = markup_node_pool.get_mut(markup_node_id);
|
||||||
|
|
||||||
match node {
|
match node {
|
||||||
MarkupNode::Nested {
|
MarkupNode::Nested {
|
||||||
ast_node_id: _,
|
|
||||||
children_ids,
|
children_ids,
|
||||||
parent_id_opt,
|
parent_id_opt,
|
||||||
|
..
|
||||||
} => {
|
} => {
|
||||||
*parent_id_opt = Some(parent_node_id);
|
*parent_id_opt = Some(parent_node_id);
|
||||||
|
|
||||||
|
@ -238,18 +337,7 @@ pub fn set_parent_for_all_helper(
|
||||||
set_parent_for_all_helper(child_id, markup_node_id, markup_node_pool);
|
set_parent_for_all_helper(child_id, markup_node_id, markup_node_pool);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
MarkupNode::Text {
|
MarkupNode::Text { parent_id_opt, .. } => *parent_id_opt = Some(parent_node_id),
|
||||||
content: _,
|
MarkupNode::Blank { parent_id_opt, .. } => *parent_id_opt = Some(parent_node_id),
|
||||||
ast_node_id: _,
|
|
||||||
syn_high_style: _,
|
|
||||||
attributes: _,
|
|
||||||
parent_id_opt,
|
|
||||||
} => *parent_id_opt = Some(parent_node_id),
|
|
||||||
MarkupNode::Blank {
|
|
||||||
ast_node_id: _,
|
|
||||||
attributes: _,
|
|
||||||
syn_high_style: _,
|
|
||||||
parent_id_opt,
|
|
||||||
} => *parent_id_opt = Some(parent_node_id),
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,6 +1,7 @@
|
||||||
mod code_lines;
|
mod code_lines;
|
||||||
mod config;
|
mod config;
|
||||||
mod ed_error;
|
mod ed_error;
|
||||||
|
mod grid_node_map;
|
||||||
mod keyboard_input;
|
mod keyboard_input;
|
||||||
pub mod main;
|
pub mod main;
|
||||||
mod markup;
|
mod markup;
|
||||||
|
|
|
@ -1,4 +1,5 @@
|
||||||
use super::app_model::AppModel;
|
use super::app_model::AppModel;
|
||||||
|
use super::ed_update;
|
||||||
use crate::editor::ed_error::EdResult;
|
use crate::editor::ed_error::EdResult;
|
||||||
use crate::ui::text::lines::SelectableLines;
|
use crate::ui::text::lines::SelectableLines;
|
||||||
use crate::window::keyboard_input::from_winit;
|
use crate::window::keyboard_input::from_winit;
|
||||||
|
@ -50,10 +51,10 @@ pub fn pass_keydown_to_focused(
|
||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn handle_new_char(_received_char: &char, app_model: &mut AppModel) -> EdResult<()> {
|
pub fn handle_new_char(received_char: &char, app_model: &mut AppModel) -> EdResult<()> {
|
||||||
if let Some(ref mut ed_model) = app_model.ed_model_opt {
|
if let Some(ref mut ed_model) = app_model.ed_model_opt {
|
||||||
if ed_model.has_focus {
|
if ed_model.has_focus {
|
||||||
unimplemented!("TODO");
|
ed_update::handle_new_char(received_char, ed_model)?;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -1,5 +1,6 @@
|
||||||
use crate::editor::code_lines::CodeLines;
|
use crate::editor::code_lines::CodeLines;
|
||||||
use crate::editor::slow_pool::{SlowNodeId, SlowPool};
|
use crate::editor::grid_node_map::GridNodeMap;
|
||||||
|
use crate::editor::slow_pool::{MarkNodeId, SlowPool};
|
||||||
use crate::editor::syntax_highlight::HighlightStyle;
|
use crate::editor::syntax_highlight::HighlightStyle;
|
||||||
use crate::editor::{
|
use crate::editor::{
|
||||||
ed_error::EdError::ParseError,
|
ed_error::EdError::ParseError,
|
||||||
|
@ -10,34 +11,31 @@ use crate::editor::{
|
||||||
use crate::graphics::primitives::rect::Rect;
|
use crate::graphics::primitives::rect::Rect;
|
||||||
use crate::lang::ast::Expr2;
|
use crate::lang::ast::Expr2;
|
||||||
use crate::lang::expr::{str_to_expr2, Env};
|
use crate::lang::expr::{str_to_expr2, Env};
|
||||||
|
use crate::lang::pool::NodeId;
|
||||||
use crate::lang::scope::Scope;
|
use crate::lang::scope::Scope;
|
||||||
use crate::ui::text::caret_w_select::CaretWSelect;
|
use crate::ui::text::caret_w_select::CaretWSelect;
|
||||||
use crate::ui::text::lines::MoveCaretFun;
|
|
||||||
use crate::ui::text::selection::validate_raw_sel;
|
|
||||||
use crate::ui::text::selection::RawSelection;
|
|
||||||
use crate::ui::text::selection::Selection;
|
|
||||||
use crate::ui::text::text_pos::TextPos;
|
|
||||||
use crate::ui::text::{lines, lines::Lines, lines::SelectableLines};
|
|
||||||
use crate::ui::ui_error::UIResult;
|
|
||||||
use crate::window::keyboard_input::Modifiers;
|
|
||||||
use bumpalo::collections::String as BumpString;
|
use bumpalo::collections::String as BumpString;
|
||||||
use bumpalo::Bump;
|
use bumpalo::Bump;
|
||||||
use nonempty::NonEmpty;
|
use nonempty::NonEmpty;
|
||||||
use roc_region::all::Region;
|
use roc_region::all::Region;
|
||||||
use std::path::Path;
|
use std::path::Path;
|
||||||
use winit::event::VirtualKeyCode;
|
|
||||||
use VirtualKeyCode::*;
|
|
||||||
|
|
||||||
#[derive(Debug)]
|
#[derive(Debug)]
|
||||||
pub struct EdModel<'a> {
|
pub struct EdModel<'a> {
|
||||||
pub module: EdModule<'a>,
|
pub module: EdModule<'a>,
|
||||||
pub file_path: &'a Path,
|
pub file_path: &'a Path,
|
||||||
pub code_lines: CodeLines,
|
pub code_lines: CodeLines,
|
||||||
pub markup_root_id: SlowNodeId,
|
// allows us to map window coordinates to MarkNodeId's
|
||||||
|
pub grid_node_map: GridNodeMap,
|
||||||
|
pub markup_root_id: MarkNodeId,
|
||||||
|
pub markup_node_pool: SlowPool,
|
||||||
|
// contains single char dimensions, used to calculate line height, column width...
|
||||||
pub glyph_dim_rect_opt: Option<Rect>,
|
pub glyph_dim_rect_opt: Option<Rect>,
|
||||||
pub has_focus: bool,
|
pub has_focus: bool,
|
||||||
// Option<SlowNodeId>: MarkupNode that corresponds to caret position, Option because this SlowNodeId is only calculated when it needs to be used.
|
// Option<MarkNodeId>: MarkupNode that corresponds to caret position, Option because this MarkNodeId is only calculated when it needs to be used.
|
||||||
pub caret_w_select_vec: NonEmpty<(CaretWSelect, Option<SlowNodeId>)>,
|
pub caret_w_select_vec: NonEmpty<(CaretWSelect, Option<MarkNodeId>)>,
|
||||||
|
// EdModel is dirty if it has changed since the previous render.
|
||||||
|
pub dirty: bool,
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn init_model<'a>(
|
pub fn init_model<'a>(
|
||||||
|
@ -45,11 +43,11 @@ pub fn init_model<'a>(
|
||||||
file_path: &'a Path,
|
file_path: &'a Path,
|
||||||
env: Env<'a>,
|
env: Env<'a>,
|
||||||
code_arena: &'a Bump,
|
code_arena: &'a Bump,
|
||||||
markup_node_pool: &mut SlowPool,
|
|
||||||
) -> EdResult<EdModel<'a>> {
|
) -> EdResult<EdModel<'a>> {
|
||||||
let mut module = EdModule::new(&code_str, env, code_arena)?;
|
let mut module = EdModule::new(&code_str, env, code_arena)?;
|
||||||
// TODO fix moving issue and insert module.ast_root into pool
|
|
||||||
let ast_root_id = module.env.pool.add(Expr2::Blank);
|
let ast_root_id = module.ast_root_id;
|
||||||
|
let mut markup_node_pool = SlowPool::new();
|
||||||
|
|
||||||
let markup_root_id = if code_str.is_empty() {
|
let markup_root_id = if code_str.is_empty() {
|
||||||
let blank_root = MarkupNode::Blank {
|
let blank_root = MarkupNode::Blank {
|
||||||
|
@ -63,196 +61,36 @@ pub fn init_model<'a>(
|
||||||
|
|
||||||
markup_node_pool.add(blank_root)
|
markup_node_pool.add(blank_root)
|
||||||
} else {
|
} else {
|
||||||
let temp_markup_root_id = expr2_to_markup(
|
let ast_root = &module.env.pool.get(ast_root_id);
|
||||||
code_arena,
|
|
||||||
&mut module.env,
|
let temp_markup_root_id =
|
||||||
&module.ast_root,
|
expr2_to_markup(code_arena, &mut module.env, ast_root, &mut markup_node_pool);
|
||||||
markup_node_pool,
|
set_parent_for_all(temp_markup_root_id, &mut markup_node_pool);
|
||||||
);
|
|
||||||
set_parent_for_all(temp_markup_root_id, markup_node_pool);
|
|
||||||
|
|
||||||
temp_markup_root_id
|
temp_markup_root_id
|
||||||
};
|
};
|
||||||
|
|
||||||
|
let code_lines = EdModel::build_code_lines_from_markup(markup_root_id, &markup_node_pool)?;
|
||||||
|
let grid_node_map = EdModel::build_node_map_from_markup(markup_root_id, &markup_node_pool)?;
|
||||||
|
|
||||||
Ok(EdModel {
|
Ok(EdModel {
|
||||||
module,
|
module,
|
||||||
file_path,
|
file_path,
|
||||||
code_lines: CodeLines::from_str(code_str),
|
code_lines,
|
||||||
|
grid_node_map,
|
||||||
markup_root_id,
|
markup_root_id,
|
||||||
|
markup_node_pool,
|
||||||
glyph_dim_rect_opt: None,
|
glyph_dim_rect_opt: None,
|
||||||
has_focus: true,
|
has_focus: true,
|
||||||
caret_w_select_vec: NonEmpty::new((CaretWSelect::default(), None)),
|
caret_w_select_vec: NonEmpty::new((CaretWSelect::default(), None)),
|
||||||
|
dirty: true,
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
impl<'a> EdModel<'a> {
|
|
||||||
pub fn move_caret(
|
|
||||||
&mut self,
|
|
||||||
move_fun: MoveCaretFun<CodeLines>,
|
|
||||||
modifiers: &Modifiers,
|
|
||||||
) -> UIResult<()> {
|
|
||||||
for caret_tup in self.caret_w_select_vec.iter_mut() {
|
|
||||||
caret_tup.0 = move_fun(&self.code_lines, caret_tup.0, modifiers)?;
|
|
||||||
caret_tup.1 = None;
|
|
||||||
}
|
|
||||||
|
|
||||||
Ok(())
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
impl<'a> SelectableLines for EdModel<'a> {
|
|
||||||
fn get_caret(self) -> TextPos {
|
|
||||||
self.caret_w_select_vec.first().0.caret_pos
|
|
||||||
}
|
|
||||||
|
|
||||||
// keeps active selection
|
|
||||||
fn set_caret(&mut self, caret_pos: TextPos) {
|
|
||||||
let caret_tup = self.caret_w_select_vec.first_mut();
|
|
||||||
caret_tup.0.caret_pos = caret_pos;
|
|
||||||
caret_tup.1 = None;
|
|
||||||
}
|
|
||||||
|
|
||||||
fn move_caret_left(&mut self, modifiers: &Modifiers) -> UIResult<()> {
|
|
||||||
let move_fun: MoveCaretFun<CodeLines> = lines::move_caret_left;
|
|
||||||
EdModel::move_caret(self, move_fun, modifiers)?;
|
|
||||||
|
|
||||||
Ok(())
|
|
||||||
}
|
|
||||||
|
|
||||||
fn move_caret_right(&mut self, modifiers: &Modifiers) -> UIResult<()> {
|
|
||||||
let move_fun: MoveCaretFun<CodeLines> = lines::move_caret_right;
|
|
||||||
EdModel::move_caret(self, move_fun, modifiers)?;
|
|
||||||
|
|
||||||
Ok(())
|
|
||||||
}
|
|
||||||
|
|
||||||
fn move_caret_up(&mut self, modifiers: &Modifiers) -> UIResult<()> {
|
|
||||||
let move_fun: MoveCaretFun<CodeLines> = lines::move_caret_up;
|
|
||||||
EdModel::move_caret(self, move_fun, modifiers)?;
|
|
||||||
|
|
||||||
Ok(())
|
|
||||||
}
|
|
||||||
|
|
||||||
fn move_caret_down(&mut self, modifiers: &Modifiers) -> UIResult<()> {
|
|
||||||
let move_fun: MoveCaretFun<CodeLines> = lines::move_caret_down;
|
|
||||||
EdModel::move_caret(self, move_fun, modifiers)?;
|
|
||||||
|
|
||||||
Ok(())
|
|
||||||
}
|
|
||||||
|
|
||||||
fn move_caret_home(&mut self, modifiers: &Modifiers) -> UIResult<()> {
|
|
||||||
let move_fun: MoveCaretFun<CodeLines> = lines::move_caret_home;
|
|
||||||
EdModel::move_caret(self, move_fun, modifiers)?;
|
|
||||||
|
|
||||||
Ok(())
|
|
||||||
}
|
|
||||||
|
|
||||||
fn move_caret_end(&mut self, modifiers: &Modifiers) -> UIResult<()> {
|
|
||||||
let move_fun: MoveCaretFun<CodeLines> = lines::move_caret_end;
|
|
||||||
EdModel::move_caret(self, move_fun, modifiers)?;
|
|
||||||
|
|
||||||
Ok(())
|
|
||||||
}
|
|
||||||
|
|
||||||
fn get_selection(&self) -> Option<Selection> {
|
|
||||||
self.caret_w_select_vec.first().0.selection_opt
|
|
||||||
}
|
|
||||||
|
|
||||||
fn is_selection_active(&self) -> bool {
|
|
||||||
self.get_selection().is_some()
|
|
||||||
}
|
|
||||||
|
|
||||||
fn get_selected_str(&self) -> UIResult<Option<String>> {
|
|
||||||
if let Some(selection) = self.get_selection() {
|
|
||||||
let start_line_index = selection.start_pos.line;
|
|
||||||
let start_col = selection.start_pos.column;
|
|
||||||
let end_line_index = selection.end_pos.line;
|
|
||||||
let end_col = selection.end_pos.column;
|
|
||||||
|
|
||||||
if start_line_index == end_line_index {
|
|
||||||
let line_ref = self.code_lines.get_line(start_line_index)?;
|
|
||||||
|
|
||||||
Ok(Some(line_ref[start_col..end_col].to_string()))
|
|
||||||
} else {
|
|
||||||
let full_str = String::new();
|
|
||||||
|
|
||||||
// TODO
|
|
||||||
Ok(Some(full_str))
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
Ok(None)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
fn set_raw_sel(&mut self, raw_sel: RawSelection) -> UIResult<()> {
|
|
||||||
self.caret_w_select_vec.first_mut().0.selection_opt = Some(validate_raw_sel(raw_sel)?);
|
|
||||||
|
|
||||||
Ok(())
|
|
||||||
}
|
|
||||||
|
|
||||||
fn set_sel_none(&mut self) {
|
|
||||||
self.caret_w_select_vec.first_mut().0.selection_opt = None;
|
|
||||||
}
|
|
||||||
|
|
||||||
fn set_caret_w_sel(&mut self, caret_w_sel: CaretWSelect) {
|
|
||||||
self.caret_w_select_vec.first_mut().0 = caret_w_sel;
|
|
||||||
}
|
|
||||||
|
|
||||||
fn select_all(&mut self) -> UIResult<()> {
|
|
||||||
if self.code_lines.nr_of_chars() > 0 {
|
|
||||||
let last_pos = self.last_text_pos()?;
|
|
||||||
|
|
||||||
self.set_raw_sel(RawSelection {
|
|
||||||
start_pos: TextPos { line: 0, column: 0 },
|
|
||||||
end_pos: last_pos,
|
|
||||||
})?;
|
|
||||||
|
|
||||||
self.set_caret(last_pos);
|
|
||||||
}
|
|
||||||
|
|
||||||
Ok(())
|
|
||||||
}
|
|
||||||
|
|
||||||
fn last_text_pos(&self) -> UIResult<TextPos> {
|
|
||||||
let nr_of_lines = self.code_lines.lines.len();
|
|
||||||
let last_line_index = nr_of_lines - 1;
|
|
||||||
let last_line = self.code_lines.get_line(last_line_index)?;
|
|
||||||
|
|
||||||
Ok(TextPos {
|
|
||||||
line: self.code_lines.lines.len() - 1,
|
|
||||||
column: last_line.len(),
|
|
||||||
})
|
|
||||||
}
|
|
||||||
|
|
||||||
fn handle_key_down(
|
|
||||||
&mut self,
|
|
||||||
modifiers: &Modifiers,
|
|
||||||
virtual_keycode: VirtualKeyCode,
|
|
||||||
) -> UIResult<()> {
|
|
||||||
match virtual_keycode {
|
|
||||||
Left => self.move_caret_left(modifiers),
|
|
||||||
Up => self.move_caret_up(modifiers),
|
|
||||||
Right => self.move_caret_right(modifiers),
|
|
||||||
Down => self.move_caret_down(modifiers),
|
|
||||||
|
|
||||||
A => {
|
|
||||||
if modifiers.ctrl {
|
|
||||||
self.select_all()
|
|
||||||
} else {
|
|
||||||
Ok(())
|
|
||||||
}
|
|
||||||
}
|
|
||||||
Home => self.move_caret_home(modifiers),
|
|
||||||
End => self.move_caret_end(modifiers),
|
|
||||||
_ => Ok(()),
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
#[derive(Debug)]
|
#[derive(Debug)]
|
||||||
pub struct EdModule<'a> {
|
pub struct EdModule<'a> {
|
||||||
pub env: Env<'a>,
|
pub env: Env<'a>,
|
||||||
pub ast_root: Expr2,
|
pub ast_root_id: NodeId<Expr2>,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl<'a> EdModule<'a> {
|
impl<'a> EdModule<'a> {
|
||||||
|
@ -265,19 +103,19 @@ impl<'a> EdModule<'a> {
|
||||||
let expr2_result = str_to_expr2(&ast_arena, &code_str, &mut env, &mut scope, region);
|
let expr2_result = str_to_expr2(&ast_arena, &code_str, &mut env, &mut scope, region);
|
||||||
|
|
||||||
match expr2_result {
|
match expr2_result {
|
||||||
Ok((expr2, _output)) => Ok(EdModule {
|
Ok((expr2, _output)) => {
|
||||||
env,
|
let ast_root_id = env.pool.add(expr2);
|
||||||
ast_root: expr2,
|
|
||||||
}),
|
Ok(EdModule { env, ast_root_id })
|
||||||
|
}
|
||||||
Err(err) => Err(ParseError {
|
Err(err) => Err(ParseError {
|
||||||
syntax_err: format!("{:?}", err),
|
syntax_err: format!("{:?}", err),
|
||||||
}),
|
}),
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
Ok(EdModule {
|
let ast_root_id = env.pool.add(Expr2::Blank);
|
||||||
env,
|
|
||||||
ast_root: Expr2::Blank,
|
Ok(EdModule { env, ast_root_id })
|
||||||
})
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
420
editor/src/editor/mvc/ed_update.rs
Normal file
420
editor/src/editor/mvc/ed_update.rs
Normal file
|
@ -0,0 +1,420 @@
|
||||||
|
use crate::editor::code_lines::CodeLines;
|
||||||
|
use crate::editor::ed_error::EdResult;
|
||||||
|
use crate::editor::grid_node_map::GridNodeMap;
|
||||||
|
use crate::editor::markup::attribute::Attributes;
|
||||||
|
use crate::editor::markup::nodes;
|
||||||
|
use crate::editor::markup::nodes::MarkupNode;
|
||||||
|
use crate::editor::mvc::ed_model::EdModel;
|
||||||
|
use crate::editor::mvc::record_update::start_new_record;
|
||||||
|
use crate::editor::mvc::record_update::update_record_colon;
|
||||||
|
use crate::editor::mvc::record_update::update_record_field;
|
||||||
|
use crate::editor::slow_pool::MarkNodeId;
|
||||||
|
use crate::editor::slow_pool::SlowPool;
|
||||||
|
use crate::editor::syntax_highlight::HighlightStyle;
|
||||||
|
use crate::editor::util::index_of;
|
||||||
|
use crate::lang::ast::Expr2;
|
||||||
|
use crate::lang::pool::{NodeId, PoolStr, PoolVec};
|
||||||
|
use crate::ui::text::caret_w_select::CaretWSelect;
|
||||||
|
use crate::ui::text::lines::MoveCaretFun;
|
||||||
|
use crate::ui::text::selection::validate_raw_sel;
|
||||||
|
use crate::ui::text::selection::RawSelection;
|
||||||
|
use crate::ui::text::selection::Selection;
|
||||||
|
use crate::ui::text::text_pos::TextPos;
|
||||||
|
use crate::ui::text::{lines, lines::Lines, lines::SelectableLines};
|
||||||
|
use crate::ui::ui_error::UIResult;
|
||||||
|
use crate::ui::util::is_newline;
|
||||||
|
use crate::window::keyboard_input::Modifiers;
|
||||||
|
use winit::event::VirtualKeyCode;
|
||||||
|
use VirtualKeyCode::*;
|
||||||
|
|
||||||
|
impl<'a> EdModel<'a> {
|
||||||
|
pub fn move_caret(
|
||||||
|
&mut self,
|
||||||
|
move_fun: MoveCaretFun<CodeLines>,
|
||||||
|
modifiers: &Modifiers,
|
||||||
|
) -> UIResult<()> {
|
||||||
|
for caret_tup in self.caret_w_select_vec.iter_mut() {
|
||||||
|
caret_tup.0 = move_fun(&self.code_lines, caret_tup.0, modifiers)?;
|
||||||
|
caret_tup.1 = None;
|
||||||
|
}
|
||||||
|
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
|
||||||
|
// TODO delete once we use `GridNodeMap` for everything that now uses `CodeLines`.
|
||||||
|
// disregards EdModel.code_lines because the caller knows the resulting caret position will be valid.
|
||||||
|
// allows us to prevent multiple updates to EdModel.code_lines
|
||||||
|
pub fn simple_move_carets_right(&mut self) {
|
||||||
|
for caret_tup in self.caret_w_select_vec.iter_mut() {
|
||||||
|
caret_tup.0.caret_pos.column += 1;
|
||||||
|
caret_tup.1 = None;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn build_node_map_from_markup(
|
||||||
|
markup_root_id: MarkNodeId,
|
||||||
|
markup_node_pool: &SlowPool,
|
||||||
|
) -> EdResult<GridNodeMap> {
|
||||||
|
let mut grid_node_map = GridNodeMap::new();
|
||||||
|
|
||||||
|
EdModel::build_grid_node_map(markup_root_id, &mut grid_node_map, markup_node_pool)?;
|
||||||
|
|
||||||
|
Ok(grid_node_map)
|
||||||
|
}
|
||||||
|
|
||||||
|
fn build_grid_node_map(
|
||||||
|
node_id: MarkNodeId,
|
||||||
|
grid_node_map: &mut GridNodeMap,
|
||||||
|
markup_node_pool: &SlowPool,
|
||||||
|
) -> EdResult<()> {
|
||||||
|
let node = markup_node_pool.get(node_id);
|
||||||
|
|
||||||
|
if node.is_nested() {
|
||||||
|
for child_id in node.get_children_ids() {
|
||||||
|
EdModel::build_grid_node_map(child_id, grid_node_map, markup_node_pool)?;
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
let node_content_str = node.get_content()?;
|
||||||
|
|
||||||
|
grid_node_map.add_to_line(0, node_content_str.len(), node_id)?;
|
||||||
|
}
|
||||||
|
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn build_code_lines_from_markup(
|
||||||
|
markup_root_id: MarkNodeId,
|
||||||
|
markup_node_pool: &SlowPool,
|
||||||
|
) -> EdResult<CodeLines> {
|
||||||
|
let mut all_code_string = String::new();
|
||||||
|
|
||||||
|
EdModel::build_markup_string(markup_root_id, &mut all_code_string, markup_node_pool)?;
|
||||||
|
|
||||||
|
let code_lines = CodeLines::from_str(&all_code_string);
|
||||||
|
|
||||||
|
Ok(code_lines)
|
||||||
|
}
|
||||||
|
|
||||||
|
fn build_markup_string(
|
||||||
|
node_id: MarkNodeId,
|
||||||
|
all_code_string: &mut String,
|
||||||
|
markup_node_pool: &SlowPool,
|
||||||
|
) -> EdResult<()> {
|
||||||
|
let node = markup_node_pool.get(node_id);
|
||||||
|
|
||||||
|
if node.is_nested() {
|
||||||
|
for child_id in node.get_children_ids() {
|
||||||
|
EdModel::build_markup_string(child_id, all_code_string, markup_node_pool)?;
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
let node_content_str = node.get_content()?;
|
||||||
|
|
||||||
|
all_code_string.push_str(&node_content_str);
|
||||||
|
}
|
||||||
|
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<'a> SelectableLines for EdModel<'a> {
|
||||||
|
fn get_caret(&self) -> TextPos {
|
||||||
|
self.caret_w_select_vec.first().0.caret_pos
|
||||||
|
}
|
||||||
|
|
||||||
|
// keeps active selection
|
||||||
|
fn set_caret(&mut self, caret_pos: TextPos) {
|
||||||
|
let caret_tup = self.caret_w_select_vec.first_mut();
|
||||||
|
caret_tup.0.caret_pos = caret_pos;
|
||||||
|
caret_tup.1 = None;
|
||||||
|
}
|
||||||
|
|
||||||
|
fn move_caret_left(&mut self, modifiers: &Modifiers) -> UIResult<()> {
|
||||||
|
let move_fun: MoveCaretFun<CodeLines> = lines::move_caret_left;
|
||||||
|
EdModel::move_caret(self, move_fun, modifiers)?;
|
||||||
|
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
|
||||||
|
fn move_caret_right(&mut self, modifiers: &Modifiers) -> UIResult<()> {
|
||||||
|
let move_fun: MoveCaretFun<CodeLines> = lines::move_caret_right;
|
||||||
|
EdModel::move_caret(self, move_fun, modifiers)?;
|
||||||
|
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
|
||||||
|
fn move_caret_up(&mut self, modifiers: &Modifiers) -> UIResult<()> {
|
||||||
|
let move_fun: MoveCaretFun<CodeLines> = lines::move_caret_up;
|
||||||
|
EdModel::move_caret(self, move_fun, modifiers)?;
|
||||||
|
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
|
||||||
|
fn move_caret_down(&mut self, modifiers: &Modifiers) -> UIResult<()> {
|
||||||
|
let move_fun: MoveCaretFun<CodeLines> = lines::move_caret_down;
|
||||||
|
EdModel::move_caret(self, move_fun, modifiers)?;
|
||||||
|
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
|
||||||
|
fn move_caret_home(&mut self, modifiers: &Modifiers) -> UIResult<()> {
|
||||||
|
let move_fun: MoveCaretFun<CodeLines> = lines::move_caret_home;
|
||||||
|
EdModel::move_caret(self, move_fun, modifiers)?;
|
||||||
|
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
|
||||||
|
fn move_caret_end(&mut self, modifiers: &Modifiers) -> UIResult<()> {
|
||||||
|
let move_fun: MoveCaretFun<CodeLines> = lines::move_caret_end;
|
||||||
|
EdModel::move_caret(self, move_fun, modifiers)?;
|
||||||
|
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
|
||||||
|
fn get_selection(&self) -> Option<Selection> {
|
||||||
|
self.caret_w_select_vec.first().0.selection_opt
|
||||||
|
}
|
||||||
|
|
||||||
|
fn is_selection_active(&self) -> bool {
|
||||||
|
self.get_selection().is_some()
|
||||||
|
}
|
||||||
|
|
||||||
|
fn get_selected_str(&self) -> UIResult<Option<String>> {
|
||||||
|
if let Some(selection) = self.get_selection() {
|
||||||
|
let start_line_index = selection.start_pos.line;
|
||||||
|
let start_col = selection.start_pos.column;
|
||||||
|
let end_line_index = selection.end_pos.line;
|
||||||
|
let end_col = selection.end_pos.column;
|
||||||
|
|
||||||
|
if start_line_index == end_line_index {
|
||||||
|
let line_ref = self.code_lines.get_line(start_line_index)?;
|
||||||
|
|
||||||
|
Ok(Some(line_ref[start_col..end_col].to_string()))
|
||||||
|
} else {
|
||||||
|
let full_str = String::new();
|
||||||
|
|
||||||
|
// TODO
|
||||||
|
Ok(Some(full_str))
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
Ok(None)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn set_raw_sel(&mut self, raw_sel: RawSelection) -> UIResult<()> {
|
||||||
|
self.caret_w_select_vec.first_mut().0.selection_opt = Some(validate_raw_sel(raw_sel)?);
|
||||||
|
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
|
||||||
|
fn set_sel_none(&mut self) {
|
||||||
|
self.caret_w_select_vec.first_mut().0.selection_opt = None;
|
||||||
|
}
|
||||||
|
|
||||||
|
fn set_caret_w_sel(&mut self, caret_w_sel: CaretWSelect) {
|
||||||
|
self.caret_w_select_vec.first_mut().0 = caret_w_sel;
|
||||||
|
}
|
||||||
|
|
||||||
|
fn select_all(&mut self) -> UIResult<()> {
|
||||||
|
if self.code_lines.nr_of_chars() > 0 {
|
||||||
|
let last_pos = self.last_text_pos()?;
|
||||||
|
|
||||||
|
self.set_raw_sel(RawSelection {
|
||||||
|
start_pos: TextPos { line: 0, column: 0 },
|
||||||
|
end_pos: last_pos,
|
||||||
|
})?;
|
||||||
|
|
||||||
|
self.set_caret(last_pos);
|
||||||
|
}
|
||||||
|
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
|
||||||
|
fn last_text_pos(&self) -> UIResult<TextPos> {
|
||||||
|
let nr_of_lines = self.code_lines.lines.len();
|
||||||
|
let last_line_index = nr_of_lines - 1;
|
||||||
|
let last_line = self.code_lines.get_line(last_line_index)?;
|
||||||
|
|
||||||
|
Ok(TextPos {
|
||||||
|
line: self.code_lines.lines.len() - 1,
|
||||||
|
column: last_line.len(),
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
fn handle_key_down(
|
||||||
|
&mut self,
|
||||||
|
modifiers: &Modifiers,
|
||||||
|
virtual_keycode: VirtualKeyCode,
|
||||||
|
) -> UIResult<()> {
|
||||||
|
match virtual_keycode {
|
||||||
|
Left => self.move_caret_left(modifiers),
|
||||||
|
Up => self.move_caret_up(modifiers),
|
||||||
|
Right => self.move_caret_right(modifiers),
|
||||||
|
Down => self.move_caret_down(modifiers),
|
||||||
|
|
||||||
|
A => {
|
||||||
|
if modifiers.ctrl {
|
||||||
|
self.select_all()
|
||||||
|
} else {
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
}
|
||||||
|
Home => self.move_caret_home(modifiers),
|
||||||
|
End => self.move_caret_end(modifiers),
|
||||||
|
_ => Ok(()),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub struct NodeContext<'a> {
|
||||||
|
pub old_caret_pos: TextPos,
|
||||||
|
pub curr_mark_node_id: MarkNodeId,
|
||||||
|
pub curr_mark_node: &'a MarkupNode,
|
||||||
|
pub parent_id_opt: Option<MarkNodeId>,
|
||||||
|
pub ast_node_id: NodeId<Expr2>,
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn get_node_context<'a>(ed_model: &'a EdModel) -> EdResult<NodeContext<'a>> {
|
||||||
|
let old_caret_pos = ed_model.get_caret();
|
||||||
|
let curr_mark_node_id = ed_model
|
||||||
|
.grid_node_map
|
||||||
|
.get_id_at_row_col(ed_model.get_caret())?;
|
||||||
|
let curr_mark_node = ed_model.markup_node_pool.get(curr_mark_node_id);
|
||||||
|
let parent_id_opt = curr_mark_node.get_parent_id_opt();
|
||||||
|
let ast_node_id = curr_mark_node.get_ast_node_id();
|
||||||
|
|
||||||
|
Ok(NodeContext {
|
||||||
|
old_caret_pos,
|
||||||
|
curr_mark_node_id,
|
||||||
|
curr_mark_node,
|
||||||
|
parent_id_opt,
|
||||||
|
ast_node_id,
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn handle_new_char(received_char: &char, ed_model: &mut EdModel) -> EdResult<()> {
|
||||||
|
// TODO set all selections to none
|
||||||
|
// TODO nested records
|
||||||
|
|
||||||
|
ed_model.dirty = true;
|
||||||
|
|
||||||
|
match received_char {
|
||||||
|
'{' => {
|
||||||
|
start_new_record(ed_model)?;
|
||||||
|
}
|
||||||
|
':' => {
|
||||||
|
// TODO set up Dict if previous char is '{'
|
||||||
|
|
||||||
|
update_record_colon(ed_model)?;
|
||||||
|
}
|
||||||
|
'\u{8}' | '\u{7f}' => {
|
||||||
|
// On Linux, '\u{8}' is backspace,
|
||||||
|
// On macOS '\u{7f}'.
|
||||||
|
|
||||||
|
unimplemented!("TODO implement backspace");
|
||||||
|
}
|
||||||
|
ch if is_newline(ch) => {
|
||||||
|
unimplemented!("TODO implement newline");
|
||||||
|
}
|
||||||
|
'\u{1}' // Ctrl + A
|
||||||
|
| '\u{3}' // Ctrl + C
|
||||||
|
| '\u{16}' // Ctrl + V
|
||||||
|
| '\u{18}' // Ctrl + X
|
||||||
|
| '\u{e000}'..='\u{f8ff}' // http://www.unicode.org/faq/private_use.html
|
||||||
|
| '\u{f0000}'..='\u{ffffd}' // ^
|
||||||
|
| '\u{100000}'..='\u{10fffd}' // ^
|
||||||
|
=> {
|
||||||
|
// chars that can be ignored
|
||||||
|
ed_model.dirty = false;
|
||||||
|
}
|
||||||
|
ch => {
|
||||||
|
let old_caret_pos = ed_model.get_caret();
|
||||||
|
let curr_mark_node_id = ed_model.grid_node_map.get_id_at_row_col(ed_model.get_caret())?;
|
||||||
|
let curr_mark_node = ed_model.markup_node_pool.get(curr_mark_node_id);
|
||||||
|
let parent_id_opt = curr_mark_node.get_parent_id_opt();
|
||||||
|
let ast_node_id = curr_mark_node.get_ast_node_id();
|
||||||
|
|
||||||
|
let ast_node_ref = ed_model.module.env.pool.get(ast_node_id);
|
||||||
|
|
||||||
|
if let Some(parent_id) = parent_id_opt {
|
||||||
|
let sibling_ids = curr_mark_node.get_sibling_ids(&ed_model.markup_node_pool);
|
||||||
|
|
||||||
|
let new_child_index = index_of(curr_mark_node_id, &sibling_ids)? + 1;
|
||||||
|
|
||||||
|
match ast_node_ref {
|
||||||
|
Expr2::EmptyRecord => {
|
||||||
|
if curr_mark_node.get_content()? == nodes::LEFT_ACCOLADE {
|
||||||
|
|
||||||
|
// update Markup
|
||||||
|
let record_field_str = &ch.to_string();
|
||||||
|
|
||||||
|
let record_field_node = MarkupNode::Text {
|
||||||
|
content: record_field_str.to_owned(),
|
||||||
|
ast_node_id,
|
||||||
|
syn_high_style: HighlightStyle::RecordField,
|
||||||
|
attributes: Attributes::new(),
|
||||||
|
parent_id_opt: Some(parent_id),
|
||||||
|
};
|
||||||
|
|
||||||
|
let record_field_node_id = ed_model.markup_node_pool.add(record_field_node);
|
||||||
|
|
||||||
|
let parent = ed_model.markup_node_pool.get_mut(parent_id);
|
||||||
|
parent.add_child_at_index(new_child_index, record_field_node_id)?;
|
||||||
|
|
||||||
|
// update caret
|
||||||
|
ed_model.simple_move_carets_right();
|
||||||
|
|
||||||
|
// update GridNodeMap
|
||||||
|
ed_model.grid_node_map.insert_between_line(
|
||||||
|
old_caret_pos.line,
|
||||||
|
old_caret_pos.column + 1,
|
||||||
|
record_field_str.len(),
|
||||||
|
record_field_node_id,
|
||||||
|
)?;
|
||||||
|
|
||||||
|
// update AST
|
||||||
|
let record_var = ed_model.module.env.var_store.fresh();
|
||||||
|
let field_name = PoolStr::new(record_field_str, &mut ed_model.module.env.pool);
|
||||||
|
let field_var = ed_model.module.env.var_store.fresh();
|
||||||
|
//TODO actually check if field_str belongs to a previously defined variable
|
||||||
|
let field_val = Expr2::InvalidLookup(
|
||||||
|
PoolStr::new(record_field_str, ed_model.module.env.pool)
|
||||||
|
);
|
||||||
|
let field_val_id = ed_model.module.env.pool.add(field_val);
|
||||||
|
let first_field = (field_name, field_var, field_val_id);
|
||||||
|
|
||||||
|
let fields = PoolVec::new(
|
||||||
|
vec![first_field].into_iter(),
|
||||||
|
&mut ed_model.module.env.pool,
|
||||||
|
);
|
||||||
|
|
||||||
|
let new_ast_node =
|
||||||
|
Expr2::Record {
|
||||||
|
record_var,
|
||||||
|
fields,
|
||||||
|
};
|
||||||
|
|
||||||
|
ed_model.module.env.pool.set(ast_node_id, new_ast_node);
|
||||||
|
} else {
|
||||||
|
unimplemented!("TODO handle this else")
|
||||||
|
}
|
||||||
|
},
|
||||||
|
Expr2::Record { record_var:_, fields } => {
|
||||||
|
update_record_field(
|
||||||
|
&ch.to_string(),
|
||||||
|
old_caret_pos,
|
||||||
|
curr_mark_node_id,
|
||||||
|
new_child_index,
|
||||||
|
fields,
|
||||||
|
ed_model,
|
||||||
|
)?;
|
||||||
|
},
|
||||||
|
other => {
|
||||||
|
unimplemented!("TODO implement updating of Expr2 {:?}.", other)
|
||||||
|
},
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
Ok(())
|
||||||
|
}
|
|
@ -1,9 +1,7 @@
|
||||||
use super::ed_model::EdModel;
|
use super::ed_model::EdModel;
|
||||||
use crate::editor::code_lines::CodeLines;
|
|
||||||
use crate::editor::config::Config;
|
use crate::editor::config::Config;
|
||||||
use crate::editor::ed_error::EdResult;
|
use crate::editor::ed_error::EdResult;
|
||||||
use crate::editor::render_ast::build_code_graphics;
|
use crate::editor::render_ast::build_code_graphics;
|
||||||
use crate::editor::slow_pool::SlowPool;
|
|
||||||
use crate::graphics::primitives::rect::Rect;
|
use crate::graphics::primitives::rect::Rect;
|
||||||
use crate::ui::text::caret_w_select::make_caret_rect;
|
use crate::ui::text::caret_w_select::make_caret_rect;
|
||||||
use crate::ui::text::caret_w_select::CaretWSelect;
|
use crate::ui::text::caret_w_select::CaretWSelect;
|
||||||
|
@ -12,33 +10,30 @@ use cgmath::Vector2;
|
||||||
use snafu::OptionExt;
|
use snafu::OptionExt;
|
||||||
use winit::dpi::PhysicalSize;
|
use winit::dpi::PhysicalSize;
|
||||||
|
|
||||||
|
#[derive(Debug)]
|
||||||
|
pub struct RenderedWgpu {
|
||||||
|
pub text: glyph_brush::OwnedSection,
|
||||||
|
pub rects: Vec<Rect>,
|
||||||
|
}
|
||||||
|
|
||||||
// create text and rectangles based on EdModel's markup_root
|
// create text and rectangles based on EdModel's markup_root
|
||||||
pub fn model_to_wgpu<'a>(
|
pub fn model_to_wgpu<'a>(
|
||||||
ed_model: &'a mut EdModel,
|
ed_model: &'a mut EdModel,
|
||||||
size: &PhysicalSize<u32>,
|
size: &PhysicalSize<u32>,
|
||||||
txt_coords: Vector2<f32>,
|
txt_coords: Vector2<f32>,
|
||||||
config: &Config,
|
config: &Config,
|
||||||
markup_node_pool: &'a SlowPool,
|
) -> EdResult<RenderedWgpu> {
|
||||||
) -> EdResult<(wgpu_glyph::Section<'a>, Vec<Rect>)> {
|
|
||||||
let glyph_dim_rect = ed_model.glyph_dim_rect_opt.context(MissingGlyphDims {})?;
|
let glyph_dim_rect = ed_model.glyph_dim_rect_opt.context(MissingGlyphDims {})?;
|
||||||
|
|
||||||
let (section, mut rects) = build_code_graphics(
|
let (section, mut rects) = build_code_graphics(
|
||||||
markup_node_pool.get(ed_model.markup_root_id),
|
ed_model.markup_node_pool.get(ed_model.markup_root_id),
|
||||||
size,
|
size,
|
||||||
txt_coords,
|
txt_coords,
|
||||||
config,
|
config,
|
||||||
glyph_dim_rect,
|
glyph_dim_rect,
|
||||||
markup_node_pool,
|
&ed_model.markup_node_pool,
|
||||||
)?;
|
)?;
|
||||||
|
|
||||||
let mut all_code_string = String::new();
|
|
||||||
|
|
||||||
for txt in section.text.iter() {
|
|
||||||
all_code_string.push_str(txt.text);
|
|
||||||
}
|
|
||||||
|
|
||||||
ed_model.code_lines = CodeLines::from_str(&all_code_string);
|
|
||||||
|
|
||||||
let caret_w_sel_vec = ed_model
|
let caret_w_sel_vec = ed_model
|
||||||
.caret_w_select_vec
|
.caret_w_select_vec
|
||||||
.iter()
|
.iter()
|
||||||
|
@ -50,7 +45,10 @@ pub fn model_to_wgpu<'a>(
|
||||||
|
|
||||||
rects.append(&mut sel_rects);
|
rects.append(&mut sel_rects);
|
||||||
|
|
||||||
Ok((section, rects))
|
Ok(RenderedWgpu {
|
||||||
|
text: section,
|
||||||
|
rects,
|
||||||
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn build_selection_graphics(
|
pub fn build_selection_graphics(
|
||||||
|
@ -69,7 +67,7 @@ pub fn build_selection_graphics(
|
||||||
|
|
||||||
let top_left_x = txt_coords.x + caret_col * char_width;
|
let top_left_x = txt_coords.x + caret_col * char_width;
|
||||||
|
|
||||||
let top_left_y = txt_coords.y + caret_row * char_height;
|
let top_left_y = txt_coords.y + caret_row * char_height + 0.1 * char_height;
|
||||||
|
|
||||||
rects.push(make_caret_rect(
|
rects.push(make_caret_rect(
|
||||||
top_left_x,
|
top_left_x,
|
||||||
|
|
|
@ -1,4 +1,6 @@
|
||||||
pub mod app_model;
|
pub mod app_model;
|
||||||
pub mod app_update;
|
pub mod app_update;
|
||||||
pub mod ed_model;
|
pub mod ed_model;
|
||||||
|
pub mod ed_update;
|
||||||
pub mod ed_view;
|
pub mod ed_view;
|
||||||
|
mod record_update;
|
||||||
|
|
233
editor/src/editor/mvc/record_update.rs
Normal file
233
editor/src/editor/mvc/record_update.rs
Normal file
|
@ -0,0 +1,233 @@
|
||||||
|
use crate::editor::ed_error::EdResult;
|
||||||
|
use crate::editor::ed_error::MissingParent;
|
||||||
|
use crate::editor::ed_error::RecordWithoutFields;
|
||||||
|
use crate::editor::markup::attribute::Attributes;
|
||||||
|
use crate::editor::markup::nodes;
|
||||||
|
use crate::editor::markup::nodes::MarkupNode;
|
||||||
|
use crate::editor::mvc::ed_model::EdModel;
|
||||||
|
use crate::editor::mvc::ed_update::get_node_context;
|
||||||
|
use crate::editor::mvc::ed_update::NodeContext;
|
||||||
|
use crate::editor::slow_pool::MarkNodeId;
|
||||||
|
use crate::editor::syntax_highlight::HighlightStyle;
|
||||||
|
use crate::editor::util::index_of;
|
||||||
|
use crate::lang::ast::Expr2;
|
||||||
|
use crate::lang::pool::NodeId;
|
||||||
|
use crate::lang::pool::PoolStr;
|
||||||
|
use crate::lang::pool::PoolVec;
|
||||||
|
use crate::ui::text::text_pos::TextPos;
|
||||||
|
use roc_types::subs::Variable;
|
||||||
|
use snafu::OptionExt;
|
||||||
|
|
||||||
|
pub fn start_new_record(ed_model: &mut EdModel) -> EdResult<()> {
|
||||||
|
let NodeContext {
|
||||||
|
old_caret_pos,
|
||||||
|
curr_mark_node_id,
|
||||||
|
curr_mark_node,
|
||||||
|
parent_id_opt,
|
||||||
|
ast_node_id,
|
||||||
|
} = get_node_context(&ed_model)?;
|
||||||
|
|
||||||
|
let is_blank_node = curr_mark_node.is_blank();
|
||||||
|
|
||||||
|
let ast_pool = &mut ed_model.module.env.pool;
|
||||||
|
let expr2_node = Expr2::EmptyRecord;
|
||||||
|
|
||||||
|
let mark_node_pool = &mut ed_model.markup_node_pool;
|
||||||
|
|
||||||
|
ast_pool.set(ast_node_id, expr2_node);
|
||||||
|
|
||||||
|
let left_bracket_node = MarkupNode::Text {
|
||||||
|
content: nodes::LEFT_ACCOLADE.to_owned(),
|
||||||
|
ast_node_id,
|
||||||
|
syn_high_style: HighlightStyle::Bracket,
|
||||||
|
attributes: Attributes::new(),
|
||||||
|
parent_id_opt: Some(curr_mark_node_id),
|
||||||
|
};
|
||||||
|
|
||||||
|
let left_bracket_node_id = mark_node_pool.add(left_bracket_node);
|
||||||
|
|
||||||
|
let right_bracket_node = MarkupNode::Text {
|
||||||
|
content: nodes::RIGHT_ACCOLADE.to_owned(),
|
||||||
|
ast_node_id,
|
||||||
|
syn_high_style: HighlightStyle::Bracket,
|
||||||
|
attributes: Attributes::new(),
|
||||||
|
parent_id_opt: Some(curr_mark_node_id),
|
||||||
|
};
|
||||||
|
|
||||||
|
let right_bracket_node_id = mark_node_pool.add(right_bracket_node);
|
||||||
|
|
||||||
|
let nested_node = MarkupNode::Nested {
|
||||||
|
ast_node_id,
|
||||||
|
children_ids: vec![left_bracket_node_id, right_bracket_node_id],
|
||||||
|
parent_id_opt,
|
||||||
|
};
|
||||||
|
|
||||||
|
if is_blank_node {
|
||||||
|
mark_node_pool.replace_node(curr_mark_node_id, nested_node);
|
||||||
|
|
||||||
|
for _ in 0..nodes::LEFT_ACCOLADE.len() {
|
||||||
|
ed_model.simple_move_carets_right();
|
||||||
|
}
|
||||||
|
|
||||||
|
// update GridNodeMap
|
||||||
|
ed_model.grid_node_map.add_to_line(
|
||||||
|
old_caret_pos.line,
|
||||||
|
nodes::LEFT_ACCOLADE.len(),
|
||||||
|
left_bracket_node_id,
|
||||||
|
)?;
|
||||||
|
|
||||||
|
ed_model.grid_node_map.add_to_line(
|
||||||
|
old_caret_pos.line,
|
||||||
|
nodes::RIGHT_ACCOLADE.len(),
|
||||||
|
right_bracket_node_id,
|
||||||
|
)?;
|
||||||
|
}
|
||||||
|
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn update_record_field(
|
||||||
|
new_input: &str,
|
||||||
|
old_caret_pos: TextPos,
|
||||||
|
curr_mark_node_id: MarkNodeId,
|
||||||
|
new_child_index: usize,
|
||||||
|
record_fields: &PoolVec<(PoolStr, Variable, NodeId<Expr2>)>,
|
||||||
|
ed_model: &mut EdModel,
|
||||||
|
) -> EdResult<()> {
|
||||||
|
if new_child_index == 2 {
|
||||||
|
// update MarkupNode
|
||||||
|
let curr_mark_node_mut = ed_model.markup_node_pool.get_mut(curr_mark_node_id);
|
||||||
|
let content_str_mut = curr_mark_node_mut.get_content_mut()?;
|
||||||
|
content_str_mut.push_str(new_input);
|
||||||
|
|
||||||
|
// update caret
|
||||||
|
for _ in 0..new_input.len() {
|
||||||
|
ed_model.simple_move_carets_right();
|
||||||
|
}
|
||||||
|
|
||||||
|
// update GridNodeMap
|
||||||
|
ed_model.grid_node_map.insert_between_line(
|
||||||
|
old_caret_pos.line,
|
||||||
|
old_caret_pos.column,
|
||||||
|
new_input.len(),
|
||||||
|
curr_mark_node_id,
|
||||||
|
)?;
|
||||||
|
|
||||||
|
// update AST Node
|
||||||
|
let first_field = record_fields
|
||||||
|
.iter(ed_model.module.env.pool)
|
||||||
|
.next()
|
||||||
|
.with_context(|| RecordWithoutFields {})?;
|
||||||
|
|
||||||
|
let mut new_field_name = String::new();
|
||||||
|
|
||||||
|
first_field.0.as_str(ed_model.module.env.pool).to_string();
|
||||||
|
|
||||||
|
new_field_name.push_str(new_input);
|
||||||
|
|
||||||
|
let new_pool_str = PoolStr::new(&new_field_name, &mut ed_model.module.env.pool);
|
||||||
|
|
||||||
|
let first_field_mut = record_fields
|
||||||
|
.iter_mut(ed_model.module.env.pool)
|
||||||
|
.next()
|
||||||
|
.with_context(|| RecordWithoutFields {})?;
|
||||||
|
|
||||||
|
first_field_mut.0 = new_pool_str;
|
||||||
|
|
||||||
|
Ok(())
|
||||||
|
} else {
|
||||||
|
unimplemented!("TODO implement updating of other fields of record.")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn update_record_colon(ed_model: &mut EdModel) -> EdResult<()> {
|
||||||
|
let NodeContext {
|
||||||
|
old_caret_pos,
|
||||||
|
curr_mark_node_id,
|
||||||
|
curr_mark_node,
|
||||||
|
parent_id_opt,
|
||||||
|
ast_node_id,
|
||||||
|
} = get_node_context(&ed_model)?;
|
||||||
|
|
||||||
|
if let Some(parent_id) = parent_id_opt {
|
||||||
|
let sibling_ids = curr_mark_node.get_sibling_ids(&ed_model.markup_node_pool);
|
||||||
|
|
||||||
|
let new_child_index = index_of(curr_mark_node_id, &sibling_ids)? + 1;
|
||||||
|
|
||||||
|
let ast_node_ref = ed_model.module.env.pool.get(ast_node_id);
|
||||||
|
|
||||||
|
match ast_node_ref {
|
||||||
|
Expr2::Record {
|
||||||
|
record_var: _,
|
||||||
|
fields,
|
||||||
|
} => {
|
||||||
|
// update Markup
|
||||||
|
let record_colon = nodes::COLON;
|
||||||
|
|
||||||
|
let record_colon_node = MarkupNode::Text {
|
||||||
|
content: record_colon.to_owned(),
|
||||||
|
ast_node_id,
|
||||||
|
syn_high_style: HighlightStyle::Operator,
|
||||||
|
attributes: Attributes::new(),
|
||||||
|
parent_id_opt: Some(parent_id),
|
||||||
|
};
|
||||||
|
|
||||||
|
let record_colon_node_id = ed_model.markup_node_pool.add(record_colon_node);
|
||||||
|
ed_model
|
||||||
|
.markup_node_pool
|
||||||
|
.get_mut(parent_id)
|
||||||
|
.add_child_at_index(new_child_index, record_colon_node_id)?;
|
||||||
|
|
||||||
|
let record_blank_node = MarkupNode::Blank {
|
||||||
|
ast_node_id,
|
||||||
|
syn_high_style: HighlightStyle::Blank,
|
||||||
|
attributes: Attributes::new(),
|
||||||
|
parent_id_opt: Some(parent_id),
|
||||||
|
};
|
||||||
|
|
||||||
|
let record_blank_node_id = ed_model.markup_node_pool.add(record_blank_node);
|
||||||
|
ed_model
|
||||||
|
.markup_node_pool
|
||||||
|
.get_mut(parent_id)
|
||||||
|
.add_child_at_index(new_child_index + 1, record_blank_node_id)?;
|
||||||
|
|
||||||
|
// update caret
|
||||||
|
for _ in 0..record_colon.len() {
|
||||||
|
ed_model.simple_move_carets_right();
|
||||||
|
}
|
||||||
|
|
||||||
|
// update GridNodeMap
|
||||||
|
ed_model.grid_node_map.add_to_line(
|
||||||
|
old_caret_pos.line,
|
||||||
|
nodes::COLON.len(),
|
||||||
|
record_colon_node_id,
|
||||||
|
)?;
|
||||||
|
|
||||||
|
ed_model.grid_node_map.add_to_line(
|
||||||
|
old_caret_pos.line,
|
||||||
|
nodes::BLANK_PLACEHOLDER.len(),
|
||||||
|
record_blank_node_id,
|
||||||
|
)?;
|
||||||
|
|
||||||
|
// update AST node
|
||||||
|
let new_field_val = Expr2::Blank;
|
||||||
|
let new_field_val_id = ed_model.module.env.pool.add(new_field_val);
|
||||||
|
|
||||||
|
let first_field_mut = fields
|
||||||
|
.iter_mut(ed_model.module.env.pool)
|
||||||
|
.next()
|
||||||
|
.with_context(|| RecordWithoutFields {})?;
|
||||||
|
|
||||||
|
first_field_mut.2 = new_field_val_id;
|
||||||
|
}
|
||||||
|
other => unimplemented!("TODO implement updating of Expr2 {:?}.", other),
|
||||||
|
}
|
||||||
|
|
||||||
|
Ok(())
|
||||||
|
} else {
|
||||||
|
MissingParent {
|
||||||
|
node_id: curr_mark_node_id,
|
||||||
|
}
|
||||||
|
.fail()
|
||||||
|
}
|
||||||
|
}
|
|
@ -15,7 +15,7 @@ pub fn build_code_graphics<'a>(
|
||||||
config: &Config,
|
config: &Config,
|
||||||
glyph_dim_rect: Rect,
|
glyph_dim_rect: Rect,
|
||||||
markup_node_pool: &'a SlowPool,
|
markup_node_pool: &'a SlowPool,
|
||||||
) -> EdResult<(wgpu_glyph::Section<'a>, Vec<Rect>)> {
|
) -> EdResult<(glyph_brush::OwnedSection, Vec<Rect>)> {
|
||||||
let area_bounds = (size.width as f32, size.height as f32);
|
let area_bounds = (size.width as f32, size.height as f32);
|
||||||
let layout = wgpu_glyph::Layout::default().h_align(wgpu_glyph::HorizontalAlign::Left);
|
let layout = wgpu_glyph::Layout::default().h_align(wgpu_glyph::HorizontalAlign::Left);
|
||||||
|
|
||||||
|
@ -47,8 +47,8 @@ fn markup_to_wgpu<'a>(
|
||||||
markup_node: &'a MarkupNode,
|
markup_node: &'a MarkupNode,
|
||||||
code_style: &CodeStyle,
|
code_style: &CodeStyle,
|
||||||
markup_node_pool: &'a SlowPool,
|
markup_node_pool: &'a SlowPool,
|
||||||
) -> EdResult<(Vec<wgpu_glyph::Text<'a>>, Vec<Rect>)> {
|
) -> EdResult<(Vec<glyph_brush::OwnedText>, Vec<Rect>)> {
|
||||||
let mut wgpu_texts: Vec<wgpu_glyph::Text<'a>> = Vec::new();
|
let mut wgpu_texts: Vec<glyph_brush::OwnedText> = Vec::new();
|
||||||
let mut rects: Vec<Rect> = Vec::new();
|
let mut rects: Vec<Rect> = Vec::new();
|
||||||
|
|
||||||
let mut txt_row_col = (0, 0);
|
let mut txt_row_col = (0, 0);
|
||||||
|
@ -68,7 +68,7 @@ fn markup_to_wgpu<'a>(
|
||||||
// TODO use text_row
|
// TODO use text_row
|
||||||
fn markup_to_wgpu_helper<'a>(
|
fn markup_to_wgpu_helper<'a>(
|
||||||
markup_node: &'a MarkupNode,
|
markup_node: &'a MarkupNode,
|
||||||
wgpu_texts: &mut Vec<wgpu_glyph::Text<'a>>,
|
wgpu_texts: &mut Vec<glyph_brush::OwnedText>,
|
||||||
rects: &mut Vec<Rect>,
|
rects: &mut Vec<Rect>,
|
||||||
code_style: &CodeStyle,
|
code_style: &CodeStyle,
|
||||||
txt_row_col: &mut (usize, usize),
|
txt_row_col: &mut (usize, usize),
|
||||||
|
@ -101,7 +101,7 @@ fn markup_to_wgpu_helper<'a>(
|
||||||
} => {
|
} => {
|
||||||
let highlight_color = map_get(&code_style.ed_theme.syntax_high_map, &syn_high_style)?;
|
let highlight_color = map_get(&code_style.ed_theme.syntax_high_map, &syn_high_style)?;
|
||||||
|
|
||||||
let glyph_text = wgpu_glyph::Text::new(&content)
|
let glyph_text = glyph_brush::OwnedText::new(content)
|
||||||
.with_color(colors::to_slice(*highlight_color))
|
.with_color(colors::to_slice(*highlight_color))
|
||||||
.with_scale(code_style.font_size);
|
.with_scale(code_style.font_size);
|
||||||
|
|
||||||
|
@ -114,22 +114,25 @@ fn markup_to_wgpu_helper<'a>(
|
||||||
syn_high_style,
|
syn_high_style,
|
||||||
parent_id_opt: _,
|
parent_id_opt: _,
|
||||||
} => {
|
} => {
|
||||||
let glyph_text = wgpu_glyph::Text::new(BLANK_PLACEHOLDER)
|
let glyph_text = glyph_brush::OwnedText::new(BLANK_PLACEHOLDER)
|
||||||
.with_color(colors::to_slice(colors::WHITE))
|
.with_color(colors::to_slice(colors::WHITE))
|
||||||
.with_scale(code_style.font_size);
|
.with_scale(code_style.font_size);
|
||||||
|
|
||||||
let highlight_color = map_get(&code_style.ed_theme.syntax_high_map, &syn_high_style)?;
|
let highlight_color = map_get(&code_style.ed_theme.syntax_high_map, &syn_high_style)?;
|
||||||
|
|
||||||
|
let char_width = code_style.glyph_dim_rect.width;
|
||||||
|
let char_height = code_style.glyph_dim_rect.height;
|
||||||
|
|
||||||
let hole_rect = Rect {
|
let hole_rect = Rect {
|
||||||
top_left_coords: (
|
top_left_coords: (
|
||||||
code_style.txt_coords.x
|
code_style.txt_coords.x + (txt_row_col.1 as f32) * char_width,
|
||||||
+ (txt_row_col.0 as f32) * code_style.glyph_dim_rect.height,
|
|
||||||
code_style.txt_coords.y
|
code_style.txt_coords.y
|
||||||
+ (txt_row_col.1 as f32) * code_style.glyph_dim_rect.width,
|
+ (txt_row_col.0 as f32) * char_height
|
||||||
|
+ 0.1 * char_height,
|
||||||
)
|
)
|
||||||
.into(),
|
.into(),
|
||||||
width: code_style.glyph_dim_rect.width,
|
width: char_width,
|
||||||
height: code_style.glyph_dim_rect.height,
|
height: char_height,
|
||||||
color: *highlight_color,
|
color: *highlight_color,
|
||||||
};
|
};
|
||||||
rects.push(hole_rect);
|
rects.push(hole_rect);
|
||||||
|
|
|
@ -1,6 +1,6 @@
|
||||||
use crate::editor::markup::nodes::MarkupNode;
|
use crate::editor::markup::nodes::MarkupNode;
|
||||||
|
|
||||||
pub type SlowNodeId = usize;
|
pub type MarkNodeId = usize;
|
||||||
|
|
||||||
#[derive(Debug)]
|
#[derive(Debug)]
|
||||||
pub struct SlowPool {
|
pub struct SlowPool {
|
||||||
|
@ -12,7 +12,7 @@ impl SlowPool {
|
||||||
SlowPool { nodes: Vec::new() }
|
SlowPool { nodes: Vec::new() }
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn add(&mut self, node: MarkupNode) -> SlowNodeId {
|
pub fn add(&mut self, node: MarkupNode) -> MarkNodeId {
|
||||||
let id = self.nodes.len();
|
let id = self.nodes.len();
|
||||||
|
|
||||||
self.nodes.push(node);
|
self.nodes.push(node);
|
||||||
|
@ -20,13 +20,17 @@ impl SlowPool {
|
||||||
id
|
id
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn get(&self, node_id: usize) -> &MarkupNode {
|
pub fn get(&self, node_id: MarkNodeId) -> &MarkupNode {
|
||||||
// unwrap because Pool doesn't return Result either
|
// unwrap because Pool doesn't return Result either
|
||||||
self.nodes.get(node_id).unwrap()
|
self.nodes.get(node_id).unwrap()
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn get_mut(&mut self, node_id: usize) -> &mut MarkupNode {
|
pub fn get_mut(&mut self, node_id: MarkNodeId) -> &mut MarkupNode {
|
||||||
// unwrap because Pool doesn't return Result either
|
// unwrap because Pool doesn't return Result either
|
||||||
self.nodes.get_mut(node_id).unwrap()
|
self.nodes.get_mut(node_id).unwrap()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
pub fn replace_node(&mut self, node_id: MarkNodeId, new_node: MarkupNode) {
|
||||||
|
self.nodes[node_id] = new_node;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,4 +1,5 @@
|
||||||
use super::ed_error::{EdResult, KeyNotFound};
|
use super::ed_error::{EdResult, KeyNotFound};
|
||||||
|
use crate::editor::ed_error::IndexOfFailed;
|
||||||
use snafu::OptionExt;
|
use snafu::OptionExt;
|
||||||
use std::collections::HashMap;
|
use std::collections::HashMap;
|
||||||
|
|
||||||
|
@ -13,3 +14,20 @@ pub fn map_get<'a, K: ::std::fmt::Debug + std::hash::Hash + std::cmp::Eq, V>(
|
||||||
|
|
||||||
Ok(value)
|
Ok(value)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
pub fn index_of<T: ::std::fmt::Debug + std::cmp::Eq>(elt: T, slice: &[T]) -> EdResult<usize> {
|
||||||
|
let index = slice
|
||||||
|
.iter()
|
||||||
|
.position(|slice_elt| *slice_elt == elt)
|
||||||
|
.with_context(|| {
|
||||||
|
let elt_str = format!("{:?}", elt);
|
||||||
|
let collection_str = format!("{:?}", slice);
|
||||||
|
|
||||||
|
IndexOfFailed {
|
||||||
|
elt_str,
|
||||||
|
collection_str,
|
||||||
|
}
|
||||||
|
})?;
|
||||||
|
|
||||||
|
Ok(index)
|
||||||
|
}
|
||||||
|
|
|
@ -84,12 +84,12 @@ fn section_from_text<'a>(
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn section_from_glyph_text(
|
pub fn section_from_glyph_text(
|
||||||
text: Vec<wgpu_glyph::Text>,
|
text: Vec<glyph_brush::OwnedText>,
|
||||||
screen_position: (f32, f32),
|
screen_position: (f32, f32),
|
||||||
area_bounds: (f32, f32),
|
area_bounds: (f32, f32),
|
||||||
layout: wgpu_glyph::Layout<wgpu_glyph::BuiltInLineBreaker>,
|
layout: wgpu_glyph::Layout<wgpu_glyph::BuiltInLineBreaker>,
|
||||||
) -> wgpu_glyph::Section {
|
) -> glyph_brush::OwnedSection {
|
||||||
Section {
|
glyph_brush::OwnedSection {
|
||||||
screen_position,
|
screen_position,
|
||||||
bounds: area_bounds,
|
bounds: area_bounds,
|
||||||
layout,
|
layout,
|
||||||
|
|
|
@ -99,6 +99,7 @@ pub enum Expr2 {
|
||||||
Str(PoolStr), // 8B
|
Str(PoolStr), // 8B
|
||||||
// Lookups
|
// Lookups
|
||||||
Var(Symbol), // 8B
|
Var(Symbol), // 8B
|
||||||
|
InvalidLookup(PoolStr), // 8B
|
||||||
|
|
||||||
List {
|
List {
|
||||||
list_var: Variable, // 4B - required for uniqueness of the list
|
list_var: Variable, // 4B - required for uniqueness of the list
|
||||||
|
|
|
@ -352,7 +352,7 @@ impl<'a, T: 'a + Sized> PoolVec<T> {
|
||||||
self.pool_list_iter(pool)
|
self.pool_list_iter(pool)
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn iter_mut(&mut self, pool: &'a Pool) -> impl ExactSizeIterator<Item = &'a mut T> {
|
pub fn iter_mut(&self, pool: &'a mut Pool) -> impl ExactSizeIterator<Item = &'a mut T> {
|
||||||
self.pool_list_iter_mut(pool)
|
self.pool_list_iter_mut(pool)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -119,7 +119,7 @@ impl Lines for BigTextArea {
|
||||||
}
|
}
|
||||||
|
|
||||||
impl SelectableLines for BigTextArea {
|
impl SelectableLines for BigTextArea {
|
||||||
fn get_caret(self) -> TextPos {
|
fn get_caret(&self) -> TextPos {
|
||||||
self.caret_w_select.caret_pos
|
self.caret_w_select.caret_pos
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -30,7 +30,7 @@ pub trait Lines {
|
||||||
}
|
}
|
||||||
|
|
||||||
pub trait SelectableLines {
|
pub trait SelectableLines {
|
||||||
fn get_caret(self) -> TextPos;
|
fn get_caret(&self) -> TextPos;
|
||||||
|
|
||||||
fn set_caret(&mut self, caret_pos: TextPos);
|
fn set_caret(&mut self, caret_pos: TextPos);
|
||||||
|
|
||||||
|
|
|
@ -8,7 +8,7 @@ pub fn is_newline(char_ref: &char) -> bool {
|
||||||
newline_codes.contains(char_ref)
|
newline_codes.contains(char_ref)
|
||||||
}
|
}
|
||||||
|
|
||||||
// replace vec method that return Option with one that return Result and proper Error
|
// replace slice method that return Option with one that return Result and proper Error
|
||||||
pub fn slice_get<T>(index: usize, slice: &[T]) -> UIResult<&<usize as SliceIndex<[T]>>::Output> {
|
pub fn slice_get<T>(index: usize, slice: &[T]) -> UIResult<&<usize as SliceIndex<[T]>>::Output> {
|
||||||
let elt_ref = slice.get(index).context(OutOfBounds {
|
let elt_ref = slice.get(index).context(OutOfBounds {
|
||||||
index,
|
index,
|
||||||
|
@ -18,3 +18,18 @@ pub fn slice_get<T>(index: usize, slice: &[T]) -> UIResult<&<usize as SliceIndex
|
||||||
|
|
||||||
Ok(elt_ref)
|
Ok(elt_ref)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
pub fn slice_get_mut<T>(
|
||||||
|
index: usize,
|
||||||
|
slice: &mut [T],
|
||||||
|
) -> UIResult<&mut <usize as SliceIndex<[T]>>::Output> {
|
||||||
|
let slice_len = slice.len();
|
||||||
|
|
||||||
|
let elt_ref = slice.get_mut(index).context(OutOfBounds {
|
||||||
|
index,
|
||||||
|
collection_name: "Slice",
|
||||||
|
len: slice_len,
|
||||||
|
})?;
|
||||||
|
|
||||||
|
Ok(elt_ref)
|
||||||
|
}
|
||||||
|
|
|
@ -7,6 +7,7 @@ platform folkertdev/foo
|
||||||
effects fx.Effect
|
effects fx.Effect
|
||||||
{
|
{
|
||||||
putLine : Str -> Effect {},
|
putLine : Str -> Effect {},
|
||||||
|
putInt : I64 -> Effect {},
|
||||||
getInt : Effect { value: I64, errorCode: [ A, B ], isError: Bool }
|
getInt : Effect { value: I64, errorCode: [ A, B ], isError: Bool }
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -1,5 +1,5 @@
|
||||||
interface Task
|
interface Task
|
||||||
exposes [ Task, succeed, fail, after, map, putLine, getInt ]
|
exposes [ Task, succeed, fail, after, map, putLine, putInt, getInt ]
|
||||||
imports [ fx.Effect ]
|
imports [ fx.Effect ]
|
||||||
|
|
||||||
|
|
||||||
|
@ -32,6 +32,9 @@ map = \effect, transform ->
|
||||||
putLine : Str -> Task {} *
|
putLine : Str -> Task {} *
|
||||||
putLine = \line -> Effect.map (Effect.putLine line) (\_ -> Ok {})
|
putLine = \line -> Effect.map (Effect.putLine line) (\_ -> Ok {})
|
||||||
|
|
||||||
|
putInt : I64 -> Task {} *
|
||||||
|
putInt = \line -> Effect.map (Effect.putInt line) (\_ -> Ok {})
|
||||||
|
|
||||||
getInt : Task I64 []
|
getInt : Task I64 []
|
||||||
getInt =
|
getInt =
|
||||||
Effect.after Effect.getInt \{ isError, value, errorCode } ->
|
Effect.after Effect.getInt \{ isError, value, errorCode } ->
|
||||||
|
|
|
@ -103,6 +103,16 @@ fn call_the_closure(function_pointer: *const u8, closure_data_pointer: [*]u8) vo
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
pub export fn roc_fx_putInt(int: i64) i64 {
|
||||||
|
const stdout = std.io.getStdOut().writer();
|
||||||
|
|
||||||
|
stdout.print("{d}", .{int}) catch unreachable;
|
||||||
|
|
||||||
|
stdout.print("\n", .{}) catch unreachable;
|
||||||
|
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
pub export fn roc_fx_putLine(rocPath: str.RocStr) i64 {
|
pub export fn roc_fx_putLine(rocPath: str.RocStr) i64 {
|
||||||
const stdout = std.io.getStdOut().writer();
|
const stdout = std.io.getStdOut().writer();
|
||||||
|
|
||||||
|
|
Loading…
Add table
Add a link
Reference in a new issue