Merge branch 'trunk' of github.com:rtfeldman/roc into builtin-sortby

This commit is contained in:
Eric Henry 2021-04-03 10:03:45 -04:00
commit 8e36b5797b
60 changed files with 2917 additions and 963 deletions

View File

@ -35,7 +35,6 @@ pub fn gen_and_eval<'a>(
let src_str: &str = unsafe { from_utf8_unchecked(src) };
let stdlib = roc_builtins::std::standard_stdlib();
let stdlib_mode = stdlib.mode;
let filename = PathBuf::from("REPL.roc");
let src_dir = Path::new("fake/test/path");
@ -219,12 +218,7 @@ pub fn gen_and_eval<'a>(
if fn_val.verify(true) {
function_pass.run_on(&fn_val);
} else {
use roc_builtins::std::Mode;
let mode = match stdlib_mode {
Mode::Uniqueness => "OPTIMIZED",
Mode::Standard => "NON-OPTIMIZED",
};
let mode = "NON-OPTIMIZED";
eprintln!(
"\n\nFunction {:?} failed LLVM verification in {} build. Its content was:\n",

View File

@ -4,6 +4,8 @@ const RocResult = utils.RocResult;
const mem = std.mem;
const Allocator = mem.Allocator;
const TAG_WIDTH = 8;
const EqFn = fn (?[*]u8, ?[*]u8) callconv(.C) bool;
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);
}
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
pub fn listContains(list: RocList, key: Opaque, key_width: usize, is_eq: EqFn) callconv(.C) bool {
if (list.bytes) |source_ptr| {
@ -557,3 +602,89 @@ pub fn listAppend(list: RocList, alignment: usize, element: Opaque, element_widt
return output;
}
pub fn listRange(width: utils.IntWidth, low: Opaque, high: Opaque) callconv(.C) RocList {
const allocator = std.heap.c_allocator;
const IntWidth = utils.IntWidth;
switch (width) {
IntWidth.U8 => {
return helper1(allocator, u8, low, high);
},
IntWidth.U16 => {
return helper1(allocator, u16, low, high);
},
IntWidth.U32 => {
return helper1(allocator, u32, low, high);
},
IntWidth.U64 => {
return helper1(allocator, u64, low, high);
},
IntWidth.U128 => {
return helper1(allocator, u128, low, high);
},
IntWidth.I8 => {
return helper1(allocator, i8, low, high);
},
IntWidth.I16 => {
return helper1(allocator, i16, low, high);
},
IntWidth.I32 => {
return helper1(allocator, i32, low, high);
},
IntWidth.I64 => {
return helper1(allocator, i64, low, high);
},
IntWidth.I128 => {
return helper1(allocator, i128, low, high);
},
IntWidth.Usize => {
return helper1(allocator, usize, low, high);
},
}
}
fn helper1(allocator: *Allocator, comptime T: type, low: Opaque, high: Opaque) RocList {
const ptr1 = @ptrCast(*T, @alignCast(@alignOf(T), low));
const ptr2 = @ptrCast(*T, @alignCast(@alignOf(T), high));
return listRangeHelp(allocator, T, ptr1.*, ptr2.*);
}
fn listRangeHelp(allocator: *Allocator, comptime T: type, low: T, high: T) RocList {
const Order = std.math.Order;
switch (std.math.order(low, high)) {
Order.gt => {
return RocList.empty();
},
Order.eq => {
const list = RocList.allocate(allocator, @alignOf(usize), 1, @sizeOf(T));
const buffer = @ptrCast([*]T, @alignCast(@alignOf(T), list.bytes orelse unreachable));
buffer[0] = low;
return list;
},
Order.lt => {
const length: usize = @intCast(usize, high - low);
const list = RocList.allocate(allocator, @alignOf(usize), length, @sizeOf(T));
const buffer = @ptrCast([*]T, @alignCast(@alignOf(T), list.bytes orelse unreachable));
var i: usize = 0;
var current = low;
while (i < length) {
buffer[i] = current;
i += 1;
current += 1;
}
return list;
},
}
}

View File

@ -12,12 +12,14 @@ comptime {
exportListFn(list.listMapWithIndex, "map_with_index");
exportListFn(list.listKeepIf, "keep_if");
exportListFn(list.listWalk, "walk");
exportListFn(list.listWalkUntil, "walkUntil");
exportListFn(list.listWalkBackwards, "walk_backwards");
exportListFn(list.listKeepOks, "keep_oks");
exportListFn(list.listKeepErrs, "keep_errs");
exportListFn(list.listContains, "contains");
exportListFn(list.listRepeat, "repeat");
exportListFn(list.listAppend, "append");
exportListFn(list.listRange, "range");
}
// Dict Module

View File

@ -4,6 +4,58 @@ const Allocator = std.mem.Allocator;
const REFCOUNT_ONE_ISIZE: comptime isize = std.math.minInt(isize);
pub const REFCOUNT_ONE: usize = @bitCast(usize, REFCOUNT_ONE_ISIZE);
pub const IntWidth = enum(u8) {
U8,
U16,
U32,
U64,
U128,
I8,
I16,
I32,
I64,
I128,
Usize,
};
pub fn intWidth(width: IntWidth) anytype {
switch (width) {
IntWidth.U8 => {
return u8;
},
IntWidth.U16 => {
return u16;
},
IntWidth.U32 => {
return u32;
},
IntWidth.U64 => {
return u64;
},
IntWidth.U128 => {
return u128;
},
IntWidth.I8 => {
return i8;
},
IntWidth.I16 => {
return i16;
},
IntWidth.I32 => {
return i32;
},
IntWidth.I64 => {
return i64;
},
IntWidth.I128 => {
return i128;
},
IntWidth.Usize => {
return usize;
},
}
}
pub fn decref(
allocator: *Allocator,
alignment: usize,

View File

@ -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
## * 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!
List elem : @List elem
List elem : [ @List elem ]
## Initialize

View File

@ -1,7 +1,11 @@
interface Set2
exposes [ empty, isEmpty, len, add, drop, map ]
interface Set
exposes [ Set, empty, isEmpty, len, add, drop, map ]
imports []
## Set
## A Set is an unordered collection of unique elements.
Set elem : [ @Set elem ]
## An empty set.
empty : Set *

View File

@ -70,8 +70,10 @@ 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_ERRS: &str = "roc_builtins.list.keep_errs";
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_CONTAINS: &str = "roc_builtins.list.contains";
pub const LIST_REPEAT: &str = "roc_builtins.list.repeat";
pub const LIST_APPEND: &str = "roc_builtins.list.append";
pub const LIST_RANGE: &str = "roc_builtins.list.range";
pub const LIST_SORT_WITH: &str = "roc_builtins.list.sort_with";

View File

@ -34,22 +34,14 @@ macro_rules! let_tvars {
};
}
#[derive(Clone, Copy, Debug)]
pub enum Mode {
Standard,
Uniqueness,
}
#[derive(Debug, Clone)]
pub struct StdLib {
pub mode: Mode,
pub types: MutMap<Symbol, (SolvedType, Region)>,
pub applies: MutSet<Symbol>,
}
pub fn standard_stdlib() -> StdLib {
StdLib {
mode: Mode::Standard,
types: types(),
applies: vec![
Symbol::LIST_LIST,
@ -771,6 +763,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
add_type(
Symbol::LIST_KEEP_IF,
@ -799,7 +819,7 @@ pub fn types() -> MutMap<Symbol, (SolvedType, Region)> {
)
});
// keepOks : List before, (before -> Result * after) -> List after
// keepErrs: List before, (before -> Result * after) -> List after
add_type(Symbol::LIST_KEEP_ERRS, {
let_tvars! { star, cvar, before, after};
top_level_function(
@ -815,6 +835,14 @@ pub fn types() -> MutMap<Symbol, (SolvedType, Region)> {
)
});
// range : Int a, Int a -> List (Int a)
add_type(Symbol::LIST_RANGE, {
top_level_function(
vec![int_type(flex(TVAR1)), int_type(flex(TVAR1))],
Box::new(list_type(int_type(flex(TVAR1)))),
)
});
// map : List before, (before -> after) -> List after
add_type(
Symbol::LIST_MAP,

View File

@ -2,7 +2,7 @@ use crate::def::Def;
use crate::expr::Expr::*;
use crate::expr::{Expr, Recursive, WhenBranch};
use crate::pattern::Pattern;
use roc_collections::all::{MutMap, SendMap};
use roc_collections::all::SendMap;
use roc_module::ident::TagName;
use roc_module::low_level::LowLevel;
use roc_module::operator::CalledVia;
@ -10,23 +10,6 @@ use roc_module::symbol::Symbol;
use roc_region::all::{Located, Region};
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 {
(@single $($x:tt)*) => (());
(@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> {
debug_assert!(symbol.is_builtin());
@ -87,8 +87,10 @@ pub fn builtin_defs_map(symbol: Symbol, var_store: &mut VarStore) -> Option<Def>
LIST_KEEP_IF => list_keep_if,
LIST_KEEP_OKS => list_keep_oks,
LIST_KEEP_ERRS=> list_keep_errs,
LIST_RANGE => list_range,
LIST_WALK => list_walk,
LIST_WALK_BACKWARDS => list_walk_backwards,
LIST_WALK_UNTIL => list_walk_until,
LIST_SORT_WITH => list_sort_with,
DICT_TEST_HASH => dict_hash_test_only,
DICT_LEN => dict_len,
@ -172,144 +174,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_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 {
let arg1_var = var_store.fresh();
let ret_var = var_store.fresh();
@ -2095,70 +1959,101 @@ fn list_join(symbol: Symbol, var_store: &mut VarStore) -> Def {
/// List.walk : List elem, (elem -> accum -> accum), accum -> accum
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 func_var = var_store.fresh();
let accum_var = var_store.fresh();
let closure_var = var_store.fresh();
let body = RunLowLevel {
op: LowLevel::ListWalk,
args: vec![
(list_var, Var(Symbol::ARG_1)),
(func_var, Var(Symbol::ARG_2)),
(accum_var, Var(Symbol::ARG_3)),
(closure_var, list_sum_add(num_var, var_store)),
(num_var, Num(var_store.fresh(), 0)),
],
ret_var: accum_var,
ret_var,
};
defn(
symbol,
vec![
(list_var, Symbol::ARG_1),
(func_var, Symbol::ARG_2),
(accum_var, Symbol::ARG_3),
],
vec![(list_var, Symbol::ARG_1)],
var_store,
body,
accum_var,
ret_var,
)
}
/// List.walkBackwards : List elem, (elem -> accum -> accum), accum -> accum
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();
fn list_sum_add(num_var: Variable, var_store: &mut VarStore) -> Expr {
let body = RunLowLevel {
op: LowLevel::ListWalkBackwards,
args: vec![
(list_var, Var(Symbol::ARG_1)),
(func_var, Var(Symbol::ARG_2)),
(accum_var, Var(Symbol::ARG_3)),
],
ret_var: accum_var,
op: LowLevel::NumAdd,
args: vec![(num_var, Var(Symbol::ARG_3)), (num_var, Var(Symbol::ARG_4))],
ret_var: num_var,
};
defn(
symbol,
vec![
(list_var, Symbol::ARG_1),
(func_var, Symbol::ARG_2),
(accum_var, Symbol::ARG_3),
],
defn_help(
Symbol::LIST_SUM_ADD,
vec![(num_var, Symbol::ARG_3), (num_var, Symbol::ARG_4)],
var_store,
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
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
@ -2199,6 +2094,11 @@ fn list_keep_errs(symbol: Symbol, var_store: &mut VarStore) -> Def {
lowlevel_2(symbol, LowLevel::ListKeepErrs, var_store)
}
/// List.keepErrs: List before, (before -> Result * after) -> List after
fn list_range(symbol: Symbol, var_store: &mut VarStore) -> Def {
lowlevel_2(symbol, LowLevel::ListRange, var_store)
}
/// List.map : List before, (before -> after) -> List after
fn list_map(symbol: Symbol, var_store: &mut VarStore) -> Def {
lowlevel_2(symbol, LowLevel::ListMap, var_store)
@ -2225,7 +2125,7 @@ fn list_sort_with(symbol: Symbol, var_store: &mut VarStore) -> Def {
}
/// Dict.hashTestOnly : k, v -> Nat
pub fn dict_hash_test_only(symbol: Symbol, var_store: &mut VarStore) -> Def {
fn dict_hash_test_only(symbol: Symbol, var_store: &mut VarStore) -> Def {
lowlevel_2(symbol, LowLevel::Hash, var_store)
}
@ -3387,24 +3287,7 @@ fn defn(
body: Expr,
ret_var: Variable,
) -> Def {
use crate::pattern::Pattern::*;
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)),
};
let expr = defn_help(fn_name, args, var_store, body, ret_var);
Def {
loc_pattern: Located {
@ -3420,3 +3303,31 @@ fn defn(
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)),
}
}

View File

@ -1,5 +1,5 @@
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::env::Env;
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;
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 {
loc_expr:
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
// 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());
// 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,
// such that at the end we have all the arguments in

View File

@ -1,5 +1,5 @@
use crate::expr::constrain_decls;
use roc_builtins::std::{Mode, StdLib};
use roc_builtins::std::StdLib;
use roc_can::constraint::{Constraint, LetConstraint};
use roc_can::module::ModuleOutput;
use roc_collections::all::{MutMap, MutSet, SendMap};
@ -22,15 +22,7 @@ pub struct ConstrainedModule {
pub constraint: Constraint,
}
pub fn constrain_module(
module: &ModuleOutput,
home: ModuleId,
mode: Mode,
// TODO remove parameter
_var_store: &mut VarStore,
) -> Constraint {
use Mode::*;
pub fn constrain_module(module: &ModuleOutput, home: ModuleId) -> Constraint {
let mut send_aliases = SendMap::default();
for (symbol, alias) in module.aliases.iter() {
@ -39,10 +31,7 @@ pub fn constrain_module(
let decls = &module.declarations;
match mode {
Standard => constrain_decls(home, decls),
Uniqueness => constrain_decls(home, decls),
}
constrain_decls(home, decls)
}
#[derive(Debug, Clone)]

View File

@ -7,8 +7,8 @@ use crate::llvm::build_hash::generic_hash;
use crate::llvm::build_list::{
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_map2, list_map3, list_map_with_index, list_prepend, list_product, list_repeat,
list_reverse, list_set, list_single, list_sort_with, list_sum, list_walk, list_walk_backwards,
list_map2, list_map3, list_map_with_index, list_prepend, list_repeat, list_reverse, list_set,
list_single, list_sort_with,
};
use crate::llvm::build_str::{
str_concat, str_count_graphemes, str_ends_with, str_from_float, str_from_int, str_from_utf8,
@ -3879,71 +3879,44 @@ fn run_low_level<'a, 'ctx, 'env>(
list_contains(env, layout_ids, elem, elem_layout, list)
}
ListWalk => {
debug_assert_eq!(args.len(), 3);
ListRange => {
// List.contains : List elem, elem -> Bool
debug_assert_eq!(args.len(), 2);
let (list, list_layout) = load_symbol_and_layout(scope, &args[0]);
let (low, low_layout) = load_symbol_and_layout(scope, &args[0]);
let high = load_symbol(scope, &args[1]);
let (func, func_layout) = load_symbol_and_layout(scope, &args[1]);
let builtin = match low_layout {
Layout::Builtin(builtin) => builtin,
_ => unreachable!(),
};
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,
layout_ids,
parent,
list,
element_layout,
func,
func_layout,
default,
default_layout,
),
_ => unreachable!("invalid list layout"),
}
}
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,
layout_ids,
parent,
list,
element_layout,
func,
func_layout,
default,
default_layout,
),
_ => 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)
list_range(env, *builtin, low.into_int_value(), high.into_int_value())
}
ListWalk => list_walk_help(
env,
layout_ids,
scope,
parent,
args,
crate::llvm::build_list::ListWalk::Walk,
),
ListWalkUntil => list_walk_help(
env,
layout_ids,
scope,
parent,
args,
crate::llvm::build_list::ListWalk::WalkUntil,
),
ListWalkBackwards => list_walk_help(
env,
layout_ids,
scope,
parent,
args,
crate::llvm::build_list::ListWalk::WalkBackwards,
),
ListAppend => {
// List.append : List elem, elem -> List elem
debug_assert_eq!(args.len(), 2);

View File

@ -4,7 +4,7 @@ use crate::llvm::bitcode::{
call_bitcode_fn, call_void_bitcode_fn,
};
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::refcounting::{
@ -713,206 +713,47 @@ pub fn list_len<'ctx>(
.into_int_value()
}
/// List.sum : List (Num a) -> Num a
pub fn list_sum<'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_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")
pub enum ListWalk {
Walk,
WalkBackwards,
WalkUntil,
WalkBackwardsUntil,
}
/// List.product : List (Num a) -> Num a
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>(
pub fn list_walk_help<'a, 'ctx, 'env>(
env: &Env<'a, 'ctx, 'env>,
layout_ids: &mut LayoutIds<'a>,
scope: &crate::llvm::build::Scope<'a, 'ctx>,
parent: FunctionValue<'ctx>,
list: BasicValueEnum<'ctx>,
element_layout: &Layout<'a>,
func: BasicValueEnum<'ctx>,
func_layout: &Layout<'a>,
default: BasicValueEnum<'ctx>,
default_layout: &Layout<'a>,
args: &[roc_module::symbol::Symbol],
variant: ListWalk,
) -> BasicValueEnum<'ctx> {
list_walk_generic(
env,
layout_ids,
parent,
list,
element_layout,
func,
func_layout,
default,
default_layout,
&bitcode::LIST_WALK,
)
}
use crate::llvm::build::load_symbol_and_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,
)
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,
layout_ids,
parent,
list,
element_layout,
func,
func_layout,
default,
default_layout,
variant,
),
_ => unreachable!("invalid list layout"),
}
}
fn list_walk_generic<'a, 'ctx, 'env>(
@ -925,10 +766,17 @@ fn list_walk_generic<'a, 'ctx, 'env>(
func_layout: &Layout<'a>,
default: BasicValueEnum<'ctx>,
default_layout: &Layout<'a>,
zig_function: &str,
variant: ListWalk,
) -> BasicValueEnum<'ctx> {
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 list_i128 = complex_bitcast(env.builder, list, env.context.i128_type().into(), "to_i128");
@ -961,25 +809,121 @@ fn list_walk_generic<'a, 'ctx, 'env>(
let result_ptr = env.builder.build_alloca(default.get_type(), "result");
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(),
env.builder.build_bitcast(result_ptr, u8_ptr, "to_opaque"),
],
zig_function,
);
match variant {
ListWalk::Walk | ListWalk::WalkBackwards => {
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(),
env.builder.build_bitcast(result_ptr, u8_ptr, "to_opaque"),
],
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")
}
#[allow(dead_code)]
#[repr(u8)]
enum IntWidth {
U8,
U16,
U32,
U64,
U128,
I8,
I16,
I32,
I64,
I128,
Usize,
}
impl From<roc_mono::layout::Builtin<'_>> for IntWidth {
fn from(builtin: Builtin) -> Self {
use IntWidth::*;
match builtin {
Builtin::Int128 => I128,
Builtin::Int64 => I64,
Builtin::Int32 => I32,
Builtin::Int16 => I16,
Builtin::Int8 => I8,
Builtin::Usize => Usize,
_ => unreachable!(),
}
}
}
/// List.range : Int a, Int a -> List (Int a)
pub fn list_range<'a, 'ctx, 'env>(
env: &Env<'a, 'ctx, 'env>,
builtin: Builtin<'a>,
low: IntValue<'ctx>,
high: IntValue<'ctx>,
) -> BasicValueEnum<'ctx> {
let builder = env.builder;
let u8_ptr = env.context.i8_type().ptr_type(AddressSpace::Generic);
let low_ptr = builder.build_alloca(low.get_type(), "low_ptr");
env.builder.build_store(low_ptr, low);
let high_ptr = builder.build_alloca(high.get_type(), "high_ptr");
env.builder.build_store(high_ptr, high);
let int_width = env
.context
.i8_type()
.const_int(IntWidth::from(builtin) as u64, false)
.into();
let output = call_bitcode_fn(
env,
&[
int_width,
env.builder.build_bitcast(low_ptr, u8_ptr, "to_u8_ptr"),
env.builder.build_bitcast(high_ptr, u8_ptr, "to_u8_ptr"),
],
&bitcode::LIST_RANGE,
);
complex_bitcast(
env.builder,
output,
collection(env.context, env.ptr_bytes).into(),
"from_i128",
)
}
/// List.contains : List elem, elem -> Bool
pub fn list_contains<'a, 'ctx, 'env>(
env: &Env<'a, 'ctx, 'env>,

View File

@ -29,7 +29,6 @@ pub fn helper<'a>(
) -> (String, Vec<roc_problem::can::Problem>, Library) {
use std::path::{Path, PathBuf};
//let stdlib_mode = stdlib.mode;
let dir = tempdir().unwrap();
let filename = PathBuf::from("Test.roc");
let src_dir = Path::new("fake/test/path");

View File

@ -111,12 +111,16 @@ fn generate_module_doc<'a>(
},
Alias {
name: _,
name,
vars: _,
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)
}
@ -139,7 +143,9 @@ fn comments_or_new_lines_to_docs<'a>(
docs.push_str(doc_str);
docs.push('\n');
}
Newline | LineComment(_) => {}
Newline | LineComment(_) => {
docs = String::new();
}
}
}
if docs.is_empty() {

View File

@ -4,7 +4,7 @@ use crossbeam::channel::{bounded, Sender};
use crossbeam::deque::{Injector, Stealer, Worker};
use crossbeam::thread;
use parking_lot::Mutex;
use roc_builtins::std::{Mode, StdLib};
use roc_builtins::std::StdLib;
use roc_can::constraint::Constraint;
use roc_can::def::{Declaration, Def};
use roc_can::module::{canonicalize_module_defs, Module};
@ -427,7 +427,6 @@ fn start_phase<'a>(module_id: ModuleId, phase: Phase, state: &mut State<'a>) ->
module_ids: Arc::clone(&state.arc_modules),
shorthands: Arc::clone(&state.arc_shorthands),
ident_ids_by_module: Arc::clone(&state.ident_ids_by_module),
mode: state.stdlib.mode,
}
}
Phase::Parse => {
@ -504,7 +503,6 @@ fn start_phase<'a>(module_id: ModuleId, phase: Phase, state: &mut State<'a>) ->
dep_idents,
exposed_symbols,
module_ids,
mode: state.stdlib.mode,
aliases,
}
}
@ -946,7 +944,6 @@ enum BuildTask<'a> {
module_ids: Arc<Mutex<PackageModuleIds<'a>>>,
shorthands: Arc<Mutex<MutMap<&'a str, PackageOrPath<'a>>>>,
ident_ids_by_module: Arc<Mutex<MutMap<ModuleId, IdentIds>>>,
mode: Mode,
},
Parse {
header: ModuleHeader<'a>,
@ -955,7 +952,6 @@ enum BuildTask<'a> {
parsed: ParsedModule<'a>,
module_ids: ModuleIds,
dep_idents: MutMap<ModuleId, IdentIds>,
mode: Mode,
exposed_symbols: MutSet<Symbol>,
aliases: MutMap<Symbol, Alias>,
},
@ -1052,7 +1048,7 @@ where
{
use LoadResult::*;
let load_start = LoadStart::from_path(arena, filename, stdlib.mode)?;
let load_start = LoadStart::from_path(arena, filename)?;
match load(
arena,
@ -1083,7 +1079,7 @@ where
{
use LoadResult::*;
let load_start = LoadStart::from_path(arena, filename, stdlib.mode)?;
let load_start = LoadStart::from_path(arena, filename)?;
match load(
arena,
@ -1116,7 +1112,7 @@ where
{
use LoadResult::*;
let load_start = LoadStart::from_str(arena, filename, src, stdlib.mode)?;
let load_start = LoadStart::from_str(arena, filename, src)?;
match load(
arena,
@ -1141,11 +1137,7 @@ struct LoadStart<'a> {
}
impl<'a> LoadStart<'a> {
pub fn from_path(
arena: &'a Bump,
filename: PathBuf,
mode: Mode,
) -> Result<Self, LoadingProblem<'a>> {
pub fn from_path(arena: &'a Bump, filename: PathBuf) -> Result<Self, LoadingProblem<'a>> {
let arc_modules = Arc::new(Mutex::new(PackageModuleIds::default()));
let root_exposed_ident_ids = IdentIds::exposed_builtins(0);
let ident_ids_by_module = Arc::new(Mutex::new(root_exposed_ident_ids));
@ -1162,7 +1154,6 @@ impl<'a> LoadStart<'a> {
Arc::clone(&arc_modules),
Arc::clone(&ident_ids_by_module),
root_start_time,
mode,
)?
};
@ -1178,7 +1169,6 @@ impl<'a> LoadStart<'a> {
arena: &'a Bump,
filename: PathBuf,
src: &'a str,
mode: Mode,
) -> Result<Self, LoadingProblem<'a>> {
let arc_modules = Arc::new(Mutex::new(PackageModuleIds::default()));
let root_exposed_ident_ids = IdentIds::exposed_builtins(0);
@ -1195,7 +1185,6 @@ impl<'a> LoadStart<'a> {
Arc::clone(&arc_modules),
Arc::clone(&ident_ids_by_module),
root_start_time,
mode,
)?
};
@ -2223,7 +2212,6 @@ fn load_pkg_config<'a>(
app_module_id: ModuleId,
module_ids: Arc<Mutex<PackageModuleIds<'a>>>,
ident_ids_by_module: Arc<Mutex<MutMap<ModuleId, IdentIds>>>,
mode: Mode,
) -> Result<Msg<'a>, LoadingProblem<'a>> {
let module_start_time = SystemTime::now();
@ -2289,7 +2277,6 @@ fn load_pkg_config<'a>(
header.effects.effect_shortname,
module_ids,
ident_ids_by_module,
mode,
header,
effect_module_timing,
)
@ -2318,7 +2305,6 @@ fn load_module<'a>(
module_ids: Arc<Mutex<PackageModuleIds<'a>>>,
arc_shorthands: Arc<Mutex<MutMap<&'a str, PackageOrPath<'a>>>>,
ident_ids_by_module: Arc<Mutex<MutMap<ModuleId, IdentIds>>>,
mode: Mode,
) -> Result<(ModuleId, Msg<'a>), LoadingProblem<'a>> {
let module_start_time = SystemTime::now();
let mut filename = PathBuf::new();
@ -2367,7 +2353,6 @@ fn load_module<'a>(
module_ids,
ident_ids_by_module,
module_start_time,
mode,
)
}
@ -2406,7 +2391,6 @@ fn parse_header<'a>(
opt_shorthand: Option<&'a str>,
module_ids: Arc<Mutex<PackageModuleIds<'a>>>,
ident_ids_by_module: Arc<Mutex<MutMap<ModuleId, IdentIds>>>,
mode: Mode,
src_bytes: &'a [u8],
start_time: SystemTime,
) -> Result<(ModuleId, Msg<'a>), LoadingProblem<'a>> {
@ -2526,7 +2510,6 @@ fn parse_header<'a>(
module_id,
module_ids,
ident_ids_by_module,
mode,
)?;
Ok((
@ -2565,7 +2548,6 @@ fn parse_header<'a>(
&"",
module_ids,
ident_ids_by_module,
mode,
header,
module_timing,
)),
@ -2585,7 +2567,6 @@ fn load_filename<'a>(
module_ids: Arc<Mutex<PackageModuleIds<'a>>>,
ident_ids_by_module: Arc<Mutex<MutMap<ModuleId, IdentIds>>>,
module_start_time: SystemTime,
mode: Mode,
) -> Result<(ModuleId, Msg<'a>), LoadingProblem<'a>> {
let file_io_start = SystemTime::now();
let file = fs::read(&filename);
@ -2600,7 +2581,6 @@ fn load_filename<'a>(
opt_shorthand,
module_ids,
ident_ids_by_module,
mode,
arena.alloc(bytes),
module_start_time,
),
@ -2621,7 +2601,6 @@ fn load_from_str<'a>(
module_ids: Arc<Mutex<PackageModuleIds<'a>>>,
ident_ids_by_module: Arc<Mutex<MutMap<ModuleId, IdentIds>>>,
module_start_time: SystemTime,
mode: Mode,
) -> Result<(ModuleId, Msg<'a>), LoadingProblem<'a>> {
let file_io_start = SystemTime::now();
let file_io_duration = file_io_start.elapsed().unwrap();
@ -2634,7 +2613,6 @@ fn load_from_str<'a>(
None,
module_ids,
ident_ids_by_module,
mode,
src.as_bytes(),
module_start_time,
)
@ -3266,7 +3244,6 @@ fn fabricate_effects_module<'a>(
shorthand: &'a str,
module_ids: Arc<Mutex<PackageModuleIds<'a>>>,
ident_ids_by_module: Arc<Mutex<MutMap<ModuleId, IdentIds>>>,
mode: Mode,
header: PlatformHeader<'a>,
module_timing: ModuleTiming,
) -> (ModuleId, Msg<'a>) {
@ -3467,7 +3444,7 @@ fn fabricate_effects_module<'a>(
references: MutSet::default(),
};
let constraint = constrain_module(&module_output, module_id, mode, &mut var_store);
let constraint = constrain_module(&module_output, module_id);
let module = Module {
module_id,
@ -3547,7 +3524,6 @@ fn canonicalize_and_constrain<'a, F>(
dep_idents: MutMap<ModuleId, IdentIds>,
exposed_symbols: MutSet<Symbol>,
aliases: MutMap<Symbol, Alias>,
mode: Mode,
parsed: ParsedModule<'a>,
look_up_builtins: F,
) -> Result<Msg<'a>, LoadingProblem<'a>>
@ -3599,7 +3575,7 @@ where
match canonicalized {
Ok(module_output) => {
let constraint = constrain_module(&module_output, module_id, mode, &mut var_store);
let constraint = constrain_module(&module_output, module_id);
let module = Module {
module_id,
@ -4041,7 +4017,6 @@ where
module_ids,
shorthands,
ident_ids_by_module,
mode,
} => load_module(
arena,
src_dir,
@ -4049,7 +4024,6 @@ where
module_ids,
shorthands,
ident_ids_by_module,
mode,
)
.map(|(_, msg)| msg),
Parse { header } => parse(arena, header),
@ -4057,7 +4031,6 @@ where
parsed,
module_ids,
dep_idents,
mode,
exposed_symbols,
aliases,
} => canonicalize_and_constrain(
@ -4066,7 +4039,6 @@ where
dep_idents,
exposed_symbols,
aliases,
mode,
parsed,
look_up_builtins,
),

View File

@ -26,15 +26,15 @@ pub enum LowLevel {
ListAppend,
ListPrepend,
ListJoin,
ListRange,
ListMap,
ListMap2,
ListMap3,
ListMapWithIndex,
ListKeepIf,
ListWalk,
ListWalkUntil,
ListWalkBackwards,
ListSum,
ListProduct,
ListKeepOks,
ListKeepErrs,
ListSortWith,

View File

@ -915,7 +915,11 @@ define_builtins! {
24 LIST_MAP2: "map2"
25 LIST_MAP3: "map3"
26 LIST_PRODUCT: "product"
27 LIST_SORT_WITH: "sortWith"
27 LIST_SUM_ADD: "#sumadd"
28 LIST_PRODUCT_MUL: "#productmul"
29 LIST_WALK_UNTIL: "walkUntil"
30 LIST_RANGE: "range"
31 LIST_SORT_WITH: "sortWith"
}
5 RESULT: "Result" => {
0 RESULT_RESULT: "Result" imported // the Result.Result type alias

View File

@ -655,9 +655,10 @@ pub fn lowlevel_borrow_signature(arena: &Bump, op: LowLevel) -> &[bool] {
ListMap3 => arena.alloc_slice_copy(&[owned, owned, owned, irrelevant]),
ListKeepIf | ListKeepOks | ListKeepErrs => arena.alloc_slice_copy(&[owned, borrowed]),
ListContains => arena.alloc_slice_copy(&[borrowed, irrelevant]),
ListWalk => arena.alloc_slice_copy(&[owned, irrelevant, owned]),
ListWalkBackwards => arena.alloc_slice_copy(&[owned, irrelevant, owned]),
ListSum | ListProduct => arena.alloc_slice_copy(&[borrowed]),
ListRange => arena.alloc_slice_copy(&[irrelevant, irrelevant]),
ListWalk | ListWalkUntil | ListWalkBackwards => {
arena.alloc_slice_copy(&[owned, irrelevant, owned])
}
ListSortWith => arena.alloc_slice_copy(&[owned, irrelevant]),
// TODO when we have lists with capacity (if ever)

View File

@ -441,7 +441,7 @@ fn expand_and_cancel<'a>(env: &mut Env<'a, '_>, stmt: &'a Stmt<'a>) -> &'a Stmt<
structure,
index,
field_layouts,
..
wrapped,
} => {
let entry = env
.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);
// 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!
let field_layout = &field_layouts[*index as usize];
env.try_insert_struct_info(symbol, field_layout);

View File

@ -2839,11 +2839,69 @@ pub fn with_hole<'a>(
variant_var,
name: tag_name,
arguments: args,
..
ext_var,
} => {
use crate::layout::UnionVariant::*;
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 variant = match res_variant {
@ -6017,10 +6075,7 @@ fn call_by_name<'a>(
// exactly once.
match &mut procs.pending_specializations {
Some(pending_specializations) => {
let is_imported = assigned.module_id() != proc_name.module_id();
// builtins are currently (re)defined in each module, so not really imported
let is_builtin = proc_name.is_builtin();
if is_imported && !is_builtin {
if env.is_imported_symbol(proc_name) {
add_needed_external(procs, env, original_fn_var, proc_name);
} else {
// register the pending specialization, so this gets code genned later

View File

@ -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>>
where
T: 'a,
{
fn fail_expr_start_e<'a, T: 'a>() -> impl Parser<'a, T, EExpr<'a>> {
|_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) {
Ok(good) => {
let (_, mut ann_type, state) = specialize(
let parser = specialize(
EExpr::Type,
space0_before_e(
type_annotation::located_help(indented_more),
@ -1035,21 +1032,28 @@ fn parse_expr_operator<'a>(
Type::TSpace,
Type::TIndentStart,
),
)
.parse(arena, state)?;
);
// put the spaces from after the operator in front of the call
if !spaces_after_operator.is_empty() {
ann_type = arena
.alloc(ann_type.value)
.with_spaces_before(spaces_after_operator, ann_type.region);
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
if !spaces_after_operator.is_empty() {
ann_type = arena.alloc(ann_type.value).with_spaces_before(
spaces_after_operator,
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);
(&*arena.alloc(Located::at(alias_region, alias)), state)
}
}
let alias_region = Region::span_across(&call.region, &ann_type.region);
let alias = Def::Annotation(Located::at(expr_region, good), ann_type);
(&*arena.alloc(Located::at(alias_region, alias)), state)
}
Err(_) => {
// this `:` likely occured inline; treat it as an invalid operator

View File

@ -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>> {
map_with_arena!(
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::TTagUnion, tag_union_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
one_of![

View File

@ -1559,6 +1559,9 @@ fn to_pattern_report<'a>(
EPattern::Record(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),
}
}
@ -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>(
alloc: &'a RocDocAllocator<'a>,
filename: PathBuf,
@ -1854,7 +1994,13 @@ fn to_type_report<'a>(
let doc = alloc.stack(vec![
alloc.reflow(r"I just started parsing a type, but I got stuck here:"),
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 {
@ -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)> {
match source_lines.get(row as usize + 1) {
None => None,
next_line_starts_with_char(source_lines, row, '}')
}
Some(line) => {
let spaces_dropped = line.trim_start_matches(' ');
match spaces_dropped.chars().next() {
Some('}') => Some((row + 1, (line.len() - spaces_dropped.len()) as u16)),
_ => None,
}
}
}
fn next_line_starts_with_close_parenthesis(source_lines: &[&str], row: Row) -> Option<(Row, Col)> {
next_line_starts_with_char(source_lines, row, ')')
}
fn next_line_starts_with_close_square_bracket(
source_lines: &[&str],
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)> {
match source_lines.get(row as usize + 1) {
None => None,
@ -3276,7 +3424,9 @@ fn next_line_starts_with_close_square_bracket(
Some(line) => {
let spaces_dropped = line.trim_start_matches(' ');
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,
}
}

View File

@ -4502,12 +4502,14 @@ mod test_reporting {
),
indoc!(
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 : (
^
I am expecting a type next, like Bool or List a.
"#
),
)
@ -4610,9 +4612,7 @@ mod test_reporting {
}
#[test]
#[ignore]
fn type_apply_stray_dot() {
// TODO good message
report_problem_as(
indoc!(
r#"
@ -4621,16 +4621,14 @@ mod test_reporting {
),
indoc!(
r#"
UNFINISHED PARENTHESES
UNFINISHED TYPE
I am partway through parsing a type in parentheses, but I got stuck
here:
I just started parsing a type, but I got stuck here:
1 f : ( I64
^
1 f : .
^
I was expecting to see a closing parenthesis before this, so try
adding a ) and see if that helps?
I am expecting a type next, like Bool or List a.
"#
),
)
@ -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
"#
),
)
}
}

View File

@ -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]
fn def_2_arg_closure() {
infer_eq(

View File

@ -320,6 +320,32 @@ fn list_walk_substraction() {
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]
fn list_keep_if_empty_list_of_int() {
assert_evals_to!(
@ -1810,3 +1836,22 @@ fn cleanup_because_exception() {
RocList<bool>
);
}
#[test]
fn list_range() {
assert_evals_to!(
indoc!("List.range 0 -1"),
RocList::from_slice(&[]),
RocList<i64>
);
assert_evals_to!(
indoc!("List.range 0 0"),
RocList::from_slice(&[0]),
RocList<i64>
);
assert_evals_to!(
indoc!("List.range 0 5"),
RocList::from_slice(&[0, 1, 2, 3, 4]),
RocList<i64>
);
}

View File

@ -3,6 +3,7 @@
use crate::assert_evals_to;
use crate::assert_llvm_evals_to;
use indoc::indoc;
use roc_std::{RocList, RocStr};
#[test]
fn applied_tag_nothing_ir() {
@ -974,3 +975,61 @@ fn newtype_wrapper() {
|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
);
}

View File

@ -1,7 +1,7 @@
use libloading::Library;
use roc_build::link::module_to_dylib;
use roc_build::program::FunctionIterator;
use roc_can::builtins::{builtin_defs_map, dict_hash_test_only};
use roc_can::builtins::builtin_defs_map;
use roc_can::def::Def;
use roc_collections::all::{MutMap, MutSet};
use roc_module::symbol::Symbol;
@ -20,13 +20,7 @@ fn promote_expr_to_module(src: &str) -> String {
buffer
}
pub fn test_builtin_defs(symbol: Symbol, var_store: &mut VarStore) -> Option<Def> {
match builtin_defs_map(symbol, var_store) {
Some(def) => Some(def),
None => match symbol {
Symbol::DICT_TEST_HASH => Some(dict_hash_test_only(symbol, var_store)),
_ => None,
},
}
builtin_defs_map(symbol, var_store)
}
// this is not actually dead code, but only used by cfg_test modules
@ -43,7 +37,6 @@ pub fn helper<'a>(
use roc_gen::llvm::build::{build_proc, build_proc_header, Scope};
use std::path::{Path, PathBuf};
let stdlib_mode = stdlib.mode;
let filename = PathBuf::from("Test.roc");
let src_dir = Path::new("fake/test/path");
@ -273,12 +266,7 @@ pub fn helper<'a>(
if fn_val.verify(true) {
function_pass.run_on(&fn_val);
} else {
use roc_builtins::std::Mode;
let mode = match stdlib_mode {
Mode::Uniqueness => "OPTIMIZED",
Mode::Standard => "NON-OPTIMIZED",
};
let mode = "NON-OPTIMIZED";
eprintln!(
"\n\nFunction {:?} failed LLVM verification in {} build. Its content was:\n",

View File

@ -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::symbol::Symbol;
use roc_types::boolean_algebra::Bool;
@ -1069,6 +1071,12 @@ fn unify_flat_type(
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!(
"Trying to unify two flat types that are incompatible: {:?} ~ {:?}",
other1,
@ -1250,3 +1258,54 @@ fn is_recursion_var(subs: &Subs, var: Variable) -> bool {
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)
)
}
}

View File

@ -2,6 +2,9 @@ interface Test
exposes [ singleline, multiline, multiparagraph, codeblock ]
imports []
## This is a block
Block : [ @Block ]
## Single line documentation.
singleline : Bool -> Bool

View File

@ -1,3 +1,6 @@
#[macro_use]
extern crate pretty_assertions;
use roc_docs::{documentation_to_template_data, files_to_documentations, ModuleEntry};
use std::path::PathBuf;
@ -20,6 +23,10 @@ mod test_docs {
};
let expected_entries = vec![
ModuleEntry {
name: "Block".to_string(),
docs: "<p>This is a block</p>\n".to_string(),
},
ModuleEntry {
name: "singleline".to_string(),
docs: "<p>Single line documentation.</p>\n".to_string(),

View File

@ -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.
* 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.
* 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 :( .
#### Autocomplete

View File

@ -1,8 +1,10 @@
use crate::ui::text::lines::Lines;
use crate::ui::ui_error::UIResult;
use crate::ui::util::slice_get;
use crate::ui::util::slice_get_mut;
use bumpalo::collections::String as BumpString;
use bumpalo::Bump;
use std::fmt;
#[derive(Debug)]
pub struct CodeLines {
@ -17,6 +19,31 @@ impl CodeLines {
nr_of_chars: code_str.len(),
}
}
pub fn insert_between_line(
&mut self,
line_nr: usize,
index: usize,
new_str: &str,
) -> UIResult<()> {
let line_ref = slice_get_mut(line_nr, &mut self.lines)?;
line_ref.insert_str(index, new_str);
self.nr_of_chars += new_str.len();
Ok(())
}
pub fn del_at_line(&mut self, line_nr: usize, index: usize) -> UIResult<()> {
let line_ref = slice_get_mut(line_nr, &mut self.lines)?;
line_ref.remove(index);
self.nr_of_chars -= 1;
Ok(())
}
}
//TODO use rust's split_inclusive once it's no longer unstable
@ -70,7 +97,29 @@ impl Lines for CodeLines {
lines
}
fn is_last_line(&self, line_nr: usize) -> bool {
line_nr == self.nr_of_lines() - 1
}
fn last_char(&self, line_nr: usize) -> UIResult<Option<char>> {
Ok(self.get_line(line_nr)?.chars().last())
}
}
impl fmt::Display for CodeLines {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
for row in &self.lines {
let row_str = row
.chars()
.map(|code_char| format!("'{}'", code_char))
.collect::<Vec<String>>()
.join(", ");
write!(f, "\n{}", row_str)?;
}
write!(f, " (code_lines)")?;
Ok(())
}
}

View File

@ -1,4 +1,4 @@
use crate::editor::slow_pool::SlowNodeId;
use crate::editor::slow_pool::MarkNodeId;
use colored::*;
use snafu::{Backtrace, ErrorCompat, NoneError, ResultExt, Snafu};
@ -15,7 +15,7 @@ pub enum EdError {
node_id
))]
CaretNotFound {
node_id: SlowNodeId,
node_id: MarkNodeId,
backtrace: Backtrace,
},
@ -31,25 +31,65 @@ pub enum EdError {
))]
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."))]
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,))]
KeyNotFound {
key_str: 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: SlowNodeId,
#[snafu(display(
"MissingParent: MarkupNode with id {} should have a parent but there was none.",
node_id
))]
MissingParent {
node_id: MarkNodeId,
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))]
NestedNodeMissingChild {
node_id: SlowNodeId,
children_ids: Vec<SlowNodeId>,
node_id: MarkNodeId,
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,
},
@ -72,6 +112,9 @@ pub enum EdError {
#[snafu(display("ParseError: Failed to parse AST: SyntaxError: {}.", syntax_err))]
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))]
UIErrorBacktrace { msg: String, backtrace: Backtrace },
}

View File

@ -0,0 +1,93 @@
use crate::editor::ed_error::EdResult;
use crate::editor::slow_pool::MarkNodeId;
use crate::editor::util::index_of;
use crate::ui::text::text_pos::TextPos;
use crate::ui::ui_error::UIResult;
use crate::ui::util::{slice_get, slice_get_mut};
use std::fmt;
#[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_nr: usize, len: usize, node_id: MarkNodeId) -> UIResult<()> {
let line_ref = slice_get_mut(line_nr, &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_nr: usize,
index: usize,
len: usize,
node_id: MarkNodeId,
) -> UIResult<()> {
let line_ref = slice_get_mut(line_nr, &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 del_at_line(&mut self, line_nr: usize, index: usize) -> UIResult<()> {
let line_ref = slice_get_mut(line_nr, &mut self.lines)?;
line_ref.remove(index);
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)
}
pub fn get_offset_to_node_id(
&self,
caret_pos: TextPos,
node_id: MarkNodeId,
) -> EdResult<usize> {
let line = slice_get(caret_pos.line, &self.lines)?;
let first_node_index = index_of(node_id, line)?;
Ok(caret_pos.column - first_node_index)
}
}
impl fmt::Display for GridNodeMap {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
for row in &self.lines {
let row_str = row
.iter()
.map(|mark_node_id| format!(" {} ", mark_node_id))
.collect::<Vec<String>>()
.join(", ");
write!(f, "{}", row_str)?;
}
write!(f, " (grid_node_map)")?;
Ok(())
}
}

View File

@ -42,6 +42,8 @@ pub fn handle_keydown(
A | Home | End => pass_keydown_to_focused(&modifiers, virtual_keycode, app_model)?,
F11 => pass_keydown_to_focused(&modifiers, virtual_keycode, app_model)?,
_ => (),
}

View File

@ -1,8 +1,9 @@
use super::keyboard_input;
use super::style::CODE_TXT_XY;
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::slow_pool::SlowPool;
use crate::editor::{
config::Config,
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 env_arena = Bump::new();
let code_arena = Bump::new();
let mut markup_node_pool = SlowPool::new();
let mut var_store = VarStore::default();
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_res = ed_model::init_model(
&code_str,
file_path,
env,
&code_arena,
&mut markup_node_pool,
);
let ed_model_res = ed_model::init_model(&code_str, file_path, env, &code_arena);
match ed_model_res {
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 keyboard_modifiers = ModifiersState::empty();
@ -298,29 +294,33 @@ fn run_event_loop(file_path_opt: Option<&Path>) -> Result<(), Box<dyn Error>> {
.output;
if let Some(ref mut ed_model) = app_model.ed_model_opt {
// TODO only calculate if markup_root has changed
let text_and_rects_res = super::mvc::ed_view::model_to_wgpu(
ed_model,
&size,
CODE_TXT_XY.into(),
&config,
&markup_node_pool,
);
if rendered_wgpu_opt.is_none() || ed_model.dirty {
let rendered_wgpu_res =
ed_view::model_to_wgpu(ed_model, &size, CODE_TXT_XY.into(), &config);
match text_and_rects_res {
Ok((text_section, rects)) => {
glyph_brush.queue(text_section);
draw_all_rects(
&rects,
&mut encoder,
&frame.view,
&gpu_device,
&rect_resources,
&ed_theme,
)
match rendered_wgpu_res {
Ok(rendered_wgpu) => rendered_wgpu_opt = Some(rendered_wgpu),
Err(e) => print_err(&e),
}
Err(e) => print_err(&e),
ed_model.dirty = false;
}
if let Some(ref rendered_wgpu) = rendered_wgpu_opt {
for text_section in &rendered_wgpu.text_sections {
let borrowed_text = text_section.to_borrowed();
glyph_brush.queue(borrowed_text);
}
draw_all_rects(
&rendered_wgpu.rects,
&mut encoder,
&frame.view,
&gpu_device,
&rect_resources,
&ed_theme,
)
}
} else {
queue_no_file_text(

View File

@ -1,5 +1,9 @@
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::syntax_highlight::HighlightStyle;
use crate::lang::{
@ -8,41 +12,138 @@ use crate::lang::{
pool::{NodeId, PoolStr},
};
use bumpalo::Bump;
use std::fmt;
#[derive(Debug)]
pub enum MarkupNode {
Nested {
ast_node_id: NodeId<Expr2>,
children_ids: Vec<SlowNodeId>,
parent_id_opt: Option<SlowNodeId>,
children_ids: Vec<MarkNodeId>,
parent_id_opt: Option<MarkNodeId>,
},
Text {
content: String,
ast_node_id: NodeId<Expr2>,
syn_high_style: HighlightStyle,
attributes: Attributes,
parent_id_opt: Option<SlowNodeId>,
parent_id_opt: Option<MarkNodeId>,
},
Blank {
ast_node_id: NodeId<Expr2>,
attributes: Attributes,
syn_high_style: HighlightStyle,
parent_id_opt: Option<SlowNodeId>,
syn_high_style: HighlightStyle, // TODO remove HighlightStyle, this is always HighlightStyle::Blank
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 {
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 = ": ";
pub const STRING_QUOTES: &str = "\"\"";
fn new_markup_node(
text: String,
node_id: NodeId<Expr2>,
highlight_style: HighlightStyle,
markup_node_pool: &mut SlowPool,
) -> SlowNodeId {
) -> MarkNodeId {
let node = MarkupNode::Text {
content: text,
ast_node_id: node_id,
@ -60,7 +161,7 @@ pub fn expr2_to_markup<'a, 'b>(
env: &mut Env<'b>,
expr2: &Expr2,
markup_node_pool: &mut SlowPool,
) -> SlowNodeId {
) -> MarkNodeId {
// TODO find way to add current expr2 to pool
let node_id = env.pool.add(Expr2::Blank);
@ -134,7 +235,7 @@ pub fn expr2_to_markup<'a, 'b>(
}
Expr2::Record { fields, .. } => {
let mut children_ids = vec![new_markup_node(
"{ ".to_string(),
LEFT_ACCOLADE.to_string(),
node_id,
HighlightStyle::Bracket,
markup_node_pool,
@ -155,7 +256,7 @@ pub fn expr2_to_markup<'a, 'b>(
));
children_ids.push(new_markup_node(
": ".to_string(),
COLON.to_string(),
node_id,
HighlightStyle::Operator,
markup_node_pool,
@ -174,7 +275,7 @@ pub fn expr2_to_markup<'a, 'b>(
}
children_ids.push(new_markup_node(
" }".to_string(),
RIGHT_ACCOLADE.to_string(),
node_id,
HighlightStyle::Bracket,
markup_node_pool,
@ -198,7 +299,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);
if let MarkupNode::Nested {
@ -217,17 +318,17 @@ pub fn set_parent_for_all(markup_node_id: SlowNodeId, markup_node_pool: &mut Slo
}
pub fn set_parent_for_all_helper(
markup_node_id: SlowNodeId,
parent_node_id: SlowNodeId,
markup_node_id: MarkNodeId,
parent_node_id: MarkNodeId,
markup_node_pool: &mut SlowPool,
) {
let node = markup_node_pool.get_mut(markup_node_id);
match node {
MarkupNode::Nested {
ast_node_id: _,
children_ids,
parent_id_opt,
..
} => {
*parent_id_opt = Some(parent_node_id);
@ -238,18 +339,53 @@ pub fn set_parent_for_all_helper(
set_parent_for_all_helper(child_id, markup_node_id, markup_node_pool);
}
}
MarkupNode::Text {
content: _,
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),
MarkupNode::Text { parent_id_opt, .. } => *parent_id_opt = Some(parent_node_id),
MarkupNode::Blank { parent_id_opt, .. } => *parent_id_opt = Some(parent_node_id),
}
}
impl fmt::Display for MarkupNode {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
writeln!(
f,
"{} ({})",
self.node_type_as_string(),
self.get_content().unwrap_or_else(|_| "".to_string())
)
}
}
pub fn tree_as_string(root_node_id: MarkNodeId, mark_node_pool: &SlowPool) -> String {
let mut full_string = "\n\n(mark_node_tree)\n".to_owned();
let node = mark_node_pool.get(root_node_id);
full_string.push_str(&format!("{}", node));
tree_as_string_helper(node, 1, &mut full_string, mark_node_pool);
full_string
}
fn tree_as_string_helper(
node: &MarkupNode,
level: usize,
tree_string: &mut String,
mark_node_pool: &SlowPool,
) {
for child_id in node.get_children_ids() {
let mut full_str = std::iter::repeat("|--- ")
.take(level)
.collect::<Vec<&str>>()
.join("")
.to_owned();
let child = mark_node_pool.get(child_id);
full_str.push_str(&format!("{}", child));
tree_string.push_str(&full_str);
tree_as_string_helper(child, level + 1, tree_string, mark_node_pool);
}
}

View File

@ -1,11 +1,13 @@
mod code_lines;
mod config;
mod ed_error;
mod grid_node_map;
mod keyboard_input;
pub mod main;
mod markup;
mod mvc;
mod render_ast;
mod render_debug;
mod resources;
mod slow_pool;
mod style;

View File

@ -1,4 +1,5 @@
use super::app_model::AppModel;
use super::ed_update;
use crate::editor::ed_error::EdResult;
use crate::ui::text::lines::SelectableLines;
use crate::window::keyboard_input::from_winit;
@ -50,10 +51,10 @@ pub fn pass_keydown_to_focused(
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 ed_model.has_focus {
unimplemented!("TODO");
ed_update::handle_new_char(received_char, ed_model)?;
}
}

View File

@ -1,5 +1,6 @@
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::{
ed_error::EdError::ParseError,
@ -10,34 +11,32 @@ use crate::editor::{
use crate::graphics::primitives::rect::Rect;
use crate::lang::ast::Expr2;
use crate::lang::expr::{str_to_expr2, Env};
use crate::lang::pool::NodeId;
use crate::lang::scope::Scope;
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::Bump;
use nonempty::NonEmpty;
use roc_region::all::Region;
use std::path::Path;
use winit::event::VirtualKeyCode;
use VirtualKeyCode::*;
#[derive(Debug)]
pub struct EdModel<'a> {
pub module: EdModule<'a>,
pub file_path: &'a Path,
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 has_focus: bool,
// Option<SlowNodeId>: MarkupNode that corresponds to caret position, Option because this SlowNodeId is only calculated when it needs to be used.
pub caret_w_select_vec: NonEmpty<(CaretWSelect, Option<SlowNodeId>)>,
// 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<MarkNodeId>)>,
pub show_debug_view: bool,
// EdModel is dirty if it has changed since the previous render.
pub dirty: bool,
}
pub fn init_model<'a>(
@ -45,11 +44,11 @@ pub fn init_model<'a>(
file_path: &'a Path,
env: Env<'a>,
code_arena: &'a Bump,
markup_node_pool: &mut SlowPool,
) -> EdResult<EdModel<'a>> {
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 blank_root = MarkupNode::Blank {
@ -63,196 +62,37 @@ pub fn init_model<'a>(
markup_node_pool.add(blank_root)
} else {
let temp_markup_root_id = expr2_to_markup(
code_arena,
&mut module.env,
&module.ast_root,
markup_node_pool,
);
set_parent_for_all(temp_markup_root_id, markup_node_pool);
let ast_root = &module.env.pool.get(ast_root_id);
let temp_markup_root_id =
expr2_to_markup(code_arena, &mut module.env, ast_root, &mut markup_node_pool);
set_parent_for_all(temp_markup_root_id, &mut markup_node_pool);
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 {
module,
file_path,
code_lines: CodeLines::from_str(code_str),
code_lines,
grid_node_map,
markup_root_id,
markup_node_pool,
glyph_dim_rect_opt: None,
has_focus: true,
caret_w_select_vec: NonEmpty::new((CaretWSelect::default(), None)),
show_debug_view: false,
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)]
pub struct EdModule<'a> {
pub env: Env<'a>,
pub ast_root: Expr2,
pub ast_root_id: NodeId<Expr2>,
}
impl<'a> EdModule<'a> {
@ -265,19 +105,19 @@ impl<'a> EdModule<'a> {
let expr2_result = str_to_expr2(&ast_arena, &code_str, &mut env, &mut scope, region);
match expr2_result {
Ok((expr2, _output)) => Ok(EdModule {
env,
ast_root: expr2,
}),
Ok((expr2, _output)) => {
let ast_root_id = env.pool.add(expr2);
Ok(EdModule { env, ast_root_id })
}
Err(err) => Err(ParseError {
syntax_err: format!("{:?}", err),
}),
}
} else {
Ok(EdModule {
env,
ast_root: Expr2::Blank,
})
let ast_root_id = env.pool.add(Expr2::Blank);
Ok(EdModule { env, ast_root_id })
}
}
}

View File

@ -0,0 +1,426 @@
use crate::editor::code_lines::CodeLines;
use crate::editor::ed_error::EdResult;
use crate::editor::grid_node_map::GridNodeMap;
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_new_record;
use crate::editor::mvc::record_update::update_record_colon;
use crate::editor::mvc::record_update::update_record_field;
use crate::editor::mvc::string_update::start_new_string;
use crate::editor::mvc::string_update::update_small_string;
use crate::editor::mvc::string_update::update_string;
use crate::editor::slow_pool::MarkNodeId;
use crate::editor::slow_pool::SlowPool;
use crate::lang::ast::Expr2;
use crate::lang::pool::NodeId;
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<()> {
self.dirty = true;
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(())
}
// 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(())
}
// updates grid_node_map and code_lines but nothing else.
pub fn insert_between_line(
&mut self,
line_nr: usize,
index: usize,
new_str: &str,
node_id: MarkNodeId,
) -> UIResult<()> {
self.grid_node_map
.insert_between_line(line_nr, index, new_str.len(), node_id)?;
self.code_lines.insert_between_line(line_nr, index, new_str)
}
// updates grid_node_map and code_lines but nothing else.
pub fn del_at_line(&mut self, line_nr: usize, index: usize) -> UIResult<()> {
self.grid_node_map.del_at_line(line_nr, index)?;
self.code_lines.del_at_line(line_nr, index)
}
}
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),
F11 => {
self.show_debug_view = !self.show_debug_view;
self.dirty = true;
Ok(())
}
_ => 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)?;
}
'"' => {
start_new_string(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(old_caret_pos)?;
let curr_mark_node = ed_model.markup_node_pool.get(curr_mark_node_id);
let prev_mark_node_id_opt =
if old_caret_pos.column > 0 {
let prev_mark_node_id = ed_model.grid_node_map.get_id_at_row_col(
TextPos {
line: old_caret_pos.line,
column: old_caret_pos.column - 1,
}
)?;
Some(prev_mark_node_id)
} else {
None
};
let ast_node_id = curr_mark_node.get_ast_node_id();
let ast_node_ref = ed_model.module.env.pool.get(ast_node_id);
match ast_node_ref {
Expr2::EmptyRecord => {
let sibling_ids = curr_mark_node.get_sibling_ids(&ed_model.markup_node_pool);
update_new_record(&ch.to_string(), prev_mark_node_id_opt, sibling_ids, ed_model)?;
},
Expr2::Record { record_var:_, fields } => {
if let Some(prev_mark_node_id) = prev_mark_node_id_opt {
let prev_mark_node = ed_model.markup_node_pool.get(prev_mark_node_id);
let prev_node_content = prev_mark_node.get_content()?;
let node_to_update_id =
if prev_node_content == nodes::LEFT_ACCOLADE {
curr_mark_node_id
} else {
// caret is already past field so we need the previous MarkupNode
prev_mark_node_id
};
update_record_field(
&ch.to_string(),
old_caret_pos,
node_to_update_id,
fields,
ed_model,
)?;
}
},
Expr2::SmallStr(array_str) => {
update_small_string(ch, array_str, ed_model)?;
},
Expr2::Str(old_pool_str) => {
update_string(&ch.to_string(), old_pool_str, ed_model)?;
},
other => {
unimplemented!("TODO implement updating of Expr2 {:?}.", other)
},
}
}
}
Ok(())
}

View File

@ -1,9 +1,8 @@
use super::ed_model::EdModel;
use crate::editor::code_lines::CodeLines;
use crate::editor::config::Config;
use crate::editor::ed_error::EdResult;
use crate::editor::render_ast::build_code_graphics;
use crate::editor::slow_pool::SlowPool;
use crate::editor::render_debug::build_debug_graphics;
use crate::graphics::primitives::rect::Rect;
use crate::ui::text::caret_w_select::make_caret_rect;
use crate::ui::text::caret_w_select::CaretWSelect;
@ -12,32 +11,33 @@ use cgmath::Vector2;
use snafu::OptionExt;
use winit::dpi::PhysicalSize;
#[derive(Debug)]
pub struct RenderedWgpu {
pub text_sections: Vec<glyph_brush::OwnedSection>,
pub rects: Vec<Rect>,
}
// create text and rectangles based on EdModel's markup_root
pub fn model_to_wgpu<'a>(
ed_model: &'a mut EdModel,
size: &PhysicalSize<u32>,
txt_coords: Vector2<f32>,
config: &Config,
markup_node_pool: &'a SlowPool,
) -> EdResult<(wgpu_glyph::Section<'a>, Vec<Rect>)> {
) -> EdResult<RenderedWgpu> {
let glyph_dim_rect = ed_model.glyph_dim_rect_opt.context(MissingGlyphDims {})?;
let (section, mut rects) = build_code_graphics(
markup_node_pool.get(ed_model.markup_root_id),
let mut all_text_sections = Vec::new();
let (code_section, mut rects) = build_code_graphics(
ed_model.markup_node_pool.get(ed_model.markup_root_id),
size,
txt_coords,
config,
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);
all_text_sections.push(code_section);
let caret_w_sel_vec = ed_model
.caret_w_select_vec
@ -50,7 +50,14 @@ pub fn model_to_wgpu<'a>(
rects.append(&mut sel_rects);
Ok((section, rects))
if ed_model.show_debug_view {
all_text_sections.push(build_debug_graphics(size, txt_coords, config, ed_model)?);
}
Ok(RenderedWgpu {
text_sections: all_text_sections,
rects,
})
}
pub fn build_selection_graphics(
@ -69,7 +76,7 @@ pub fn build_selection_graphics(
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(
top_left_x,

View File

@ -1,4 +1,7 @@
pub mod app_model;
pub mod app_update;
pub mod ed_model;
pub mod ed_update;
pub mod ed_view;
mod record_update;
mod string_update;

View File

@ -0,0 +1,323 @@
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, PoolStr, 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), // current node will be replace with nested one
};
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), // current node will be replace with nested one
};
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);
// remove data corresponding to Blank node
ed_model.del_at_line(old_caret_pos.line, old_caret_pos.column)?;
for _ in 0..nodes::LEFT_ACCOLADE.len() {
ed_model.simple_move_carets_right();
}
// update GridNodeMap and CodeLines
ed_model.insert_between_line(
old_caret_pos.line,
old_caret_pos.column,
nodes::LEFT_ACCOLADE,
left_bracket_node_id,
)?;
ed_model.insert_between_line(
old_caret_pos.line,
old_caret_pos.column + nodes::LEFT_ACCOLADE.len(),
nodes::RIGHT_ACCOLADE,
right_bracket_node_id,
)?;
}
Ok(())
}
pub fn update_new_record(
new_input: &str,
prev_mark_node_id_opt: Option<MarkNodeId>,
sibling_ids: Vec<MarkNodeId>,
ed_model: &mut EdModel,
) -> EdResult<()> {
let prev_mark_node_opt = prev_mark_node_id_opt
.map(|prev_mark_node_id| ed_model.markup_node_pool.get(prev_mark_node_id));
if let Some(prev_mark_node) = prev_mark_node_opt {
let NodeContext {
old_caret_pos,
curr_mark_node_id,
curr_mark_node: _,
parent_id_opt,
ast_node_id,
} = get_node_context(&ed_model)?;
if prev_mark_node.get_content()? == nodes::LEFT_ACCOLADE {
// update Markup
let record_field_node = MarkupNode::Text {
content: new_input.to_owned(),
ast_node_id,
syn_high_style: HighlightStyle::RecordField,
attributes: Attributes::new(),
parent_id_opt,
};
let record_field_node_id = ed_model.markup_node_pool.add(record_field_node);
if let Some(parent_id) = parent_id_opt {
let parent = ed_model.markup_node_pool.get_mut(parent_id);
let new_child_index = index_of(curr_mark_node_id, &sibling_ids)?;
parent.add_child_at_index(new_child_index, record_field_node_id)?;
} else {
MissingParent {
node_id: curr_mark_node_id,
}
.fail()?
}
// update caret
ed_model.simple_move_carets_right();
// update GridNodeMap and CodeLines
ed_model.insert_between_line(
old_caret_pos.line,
old_caret_pos.column,
new_input,
record_field_node_id,
)?;
// update AST
let record_var = ed_model.module.env.var_store.fresh();
let field_name = PoolStr::new(new_input, &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(new_input, 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);
}
}
Ok(())
}
pub fn update_record_field(
new_input: &str,
old_caret_pos: TextPos,
curr_mark_node_id: MarkNodeId,
record_fields: &PoolVec<(PoolStr, Variable, NodeId<Expr2>)>,
ed_model: &mut EdModel,
) -> EdResult<()> {
// 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()?;
let node_caret_offset = ed_model
.grid_node_map
.get_offset_to_node_id(old_caret_pos, curr_mark_node_id)?;
content_str_mut.insert_str(node_caret_offset, new_input);
// update caret
for _ in 0..new_input.len() {
ed_model.simple_move_carets_right();
}
// update GridNodeMap and CodeLines
ed_model.insert_between_line(
old_caret_pos.line,
old_caret_pos.column,
new_input,
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();
// -push old field name
new_field_name.push_str(first_field.0.as_str(ed_model.module.env.pool));
new_field_name.push_str(new_input);
// -clone to prevent borrow issues
let field_val_id = first_field.2;
let new_pool_str = PoolStr::new(&new_field_name, &mut ed_model.module.env.pool);
if let Expr2::InvalidLookup(_) = ed_model.module.env.pool.get(field_val_id) {
ed_model
.module
.env
.pool
.set(field_val_id, Expr2::InvalidLookup(new_pool_str));
}
let first_field_mut = record_fields
.iter_mut(ed_model.module.env.pool)
.next()
.with_context(|| RecordWithoutFields {})?;
// -update field name
first_field_mut.0 = new_pool_str;
Ok(())
}
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)?;
let ast_node_ref = ed_model.module.env.pool.get(ast_node_id);
match ast_node_ref {
Expr2::Record {
record_var: _,
fields,
} => {
// 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;
// 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: new_field_val_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 and CodeLines
ed_model.insert_between_line(
old_caret_pos.line,
old_caret_pos.column,
nodes::COLON,
record_colon_node_id,
)?;
ed_model.insert_between_line(
old_caret_pos.line,
old_caret_pos.column + nodes::COLON.len(),
nodes::BLANK_PLACEHOLDER,
record_blank_node_id,
)?;
}
other => unimplemented!("TODO implement updating of Expr2 {:?}.", other),
}
Ok(())
} else {
MissingParent {
node_id: curr_mark_node_id,
}
.fail()
}
}

View File

@ -0,0 +1,157 @@
use crate::editor::ed_error::EdResult;
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::syntax_highlight::HighlightStyle;
use crate::lang::ast::ArrString;
use crate::lang::ast::Expr2;
use crate::lang::pool::PoolStr;
pub fn update_small_string(
new_char: &char,
old_array_str: &ArrString,
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 new_input = &new_char.to_string();
// update markup
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()?;
let node_caret_offset = ed_model
.grid_node_map
.get_offset_to_node_id(old_caret_pos, curr_mark_node_id)?;
content_str_mut.insert_str(node_caret_offset, new_input);
// update GridNodeMap and CodeLines
ed_model.insert_between_line(
old_caret_pos.line,
old_caret_pos.column,
new_input,
curr_mark_node_id,
)?;
if old_array_str.len() < ArrString::capacity() {
if let Expr2::SmallStr(ref mut mut_array_str) =
ed_model.module.env.pool.get_mut(ast_node_id)
{
// safe because we checked the length
unsafe {
mut_array_str.push_unchecked(*new_char);
}
} else {
unreachable!()
}
} else {
let mut new_str = old_array_str.as_str().to_owned();
new_str.push(*new_char);
let new_ast_node = Expr2::Str(PoolStr::new(&new_str, ed_model.module.env.pool));
ed_model.module.env.pool.set(ast_node_id, new_ast_node);
}
// update caret
for _ in 0..new_input.len() {
ed_model.simple_move_carets_right();
}
Ok(())
}
pub fn update_string(
new_input: &str,
old_pool_str: &PoolStr,
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)?;
// update markup
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()?;
let node_caret_offset = ed_model
.grid_node_map
.get_offset_to_node_id(old_caret_pos, curr_mark_node_id)?;
content_str_mut.insert_str(node_caret_offset, new_input);
// update GridNodeMap and CodeLines
ed_model.insert_between_line(
old_caret_pos.line,
old_caret_pos.column,
new_input,
curr_mark_node_id,
)?;
// update ast
let mut new_string = old_pool_str.as_str(ed_model.module.env.pool).to_owned();
new_string.push_str(new_input);
let new_pool_str = PoolStr::new(&new_string, &mut ed_model.module.env.pool);
let new_ast_node = Expr2::Str(new_pool_str);
ed_model.module.env.pool.set(ast_node_id, new_ast_node);
// update caret
ed_model.simple_move_carets_right();
Ok(())
}
pub fn start_new_string(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 curr_mark_node.is_blank() {
let new_expr2_node = Expr2::SmallStr(arraystring::ArrayString::new());
ed_model.module.env.pool.set(ast_node_id, new_expr2_node);
let new_string_node = MarkupNode::Text {
content: nodes::STRING_QUOTES.to_owned(),
ast_node_id,
syn_high_style: HighlightStyle::String,
attributes: Attributes::new(),
parent_id_opt,
};
ed_model
.markup_node_pool
.replace_node(curr_mark_node_id, new_string_node);
// remove data corresponding to Blank node
ed_model.del_at_line(old_caret_pos.line, old_caret_pos.column)?;
// update GridNodeMap and CodeLines
ed_model.insert_between_line(
old_caret_pos.line,
old_caret_pos.column,
nodes::STRING_QUOTES,
curr_mark_node_id,
)?;
ed_model.simple_move_carets_right();
}
Ok(())
}

View File

@ -15,7 +15,7 @@ pub fn build_code_graphics<'a>(
config: &Config,
glyph_dim_rect: Rect,
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 layout = wgpu_glyph::Layout::default().h_align(wgpu_glyph::HorizontalAlign::Left);
@ -47,8 +47,8 @@ fn markup_to_wgpu<'a>(
markup_node: &'a MarkupNode,
code_style: &CodeStyle,
markup_node_pool: &'a SlowPool,
) -> EdResult<(Vec<wgpu_glyph::Text<'a>>, Vec<Rect>)> {
let mut wgpu_texts: Vec<wgpu_glyph::Text<'a>> = Vec::new();
) -> EdResult<(Vec<glyph_brush::OwnedText>, Vec<Rect>)> {
let mut wgpu_texts: Vec<glyph_brush::OwnedText> = Vec::new();
let mut rects: Vec<Rect> = Vec::new();
let mut txt_row_col = (0, 0);
@ -68,7 +68,7 @@ fn markup_to_wgpu<'a>(
// TODO use text_row
fn markup_to_wgpu_helper<'a>(
markup_node: &'a MarkupNode,
wgpu_texts: &mut Vec<wgpu_glyph::Text<'a>>,
wgpu_texts: &mut Vec<glyph_brush::OwnedText>,
rects: &mut Vec<Rect>,
code_style: &CodeStyle,
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 glyph_text = wgpu_glyph::Text::new(&content)
let glyph_text = glyph_brush::OwnedText::new(content)
.with_color(colors::to_slice(*highlight_color))
.with_scale(code_style.font_size);
@ -114,22 +114,25 @@ fn markup_to_wgpu_helper<'a>(
syn_high_style,
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_scale(code_style.font_size);
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 {
top_left_coords: (
code_style.txt_coords.x
+ (txt_row_col.0 as f32) * code_style.glyph_dim_rect.height,
code_style.txt_coords.x + (txt_row_col.1 as f32) * char_width,
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(),
width: code_style.glyph_dim_rect.width,
height: code_style.glyph_dim_rect.height,
width: char_width,
height: char_height,
color: *highlight_color,
};
rects.push(hole_rect);

View File

@ -0,0 +1,64 @@
use crate::editor::ed_error::EdResult;
use crate::editor::markup::nodes::tree_as_string;
use crate::editor::mvc::ed_model::EdModel;
use crate::graphics::colors;
use crate::graphics::colors::from_hsb;
use crate::graphics::primitives::text as gr_text;
use crate::lang::ast::expr2_to_string;
use cgmath::Vector2;
use winit::dpi::PhysicalSize;
use crate::editor::config::Config;
pub fn build_debug_graphics(
size: &PhysicalSize<u32>,
txt_coords: Vector2<f32>,
config: &Config,
ed_model: &EdModel,
) -> EdResult<glyph_brush::OwnedSection> {
let area_bounds = (size.width as f32, size.height as f32);
let layout = wgpu_glyph::Layout::default().h_align(wgpu_glyph::HorizontalAlign::Left);
let debug_txt_coords: Vector2<f32> = (txt_coords.x, txt_coords.y * 6.0).into();
let grid_node_map_text = glyph_brush::OwnedText::new(format!("{}", ed_model.grid_node_map))
.with_color(colors::to_slice(from_hsb(20, 41, 100)))
.with_scale(config.code_font_size);
let code_lines_text = glyph_brush::OwnedText::new(format!("{}", ed_model.code_lines))
.with_color(colors::to_slice(from_hsb(0, 49, 96)))
.with_scale(config.code_font_size);
let mark_node_tree_text = glyph_brush::OwnedText::new(tree_as_string(
ed_model.markup_root_id,
&ed_model.markup_node_pool,
))
.with_color(colors::to_slice(from_hsb(266, 31, 96)))
.with_scale(config.code_font_size);
let mark_node_pool_text = glyph_brush::OwnedText::new(format!("{}", ed_model.markup_node_pool))
.with_color(colors::to_slice(from_hsb(110, 45, 82)))
.with_scale(config.code_font_size);
let ast_node_text = glyph_brush::OwnedText::new(format!(
"\n\n(ast_root)\n{}",
expr2_to_string(ed_model.module.ast_root_id, ed_model.module.env.pool)
))
.with_color(colors::to_slice(from_hsb(211, 80, 100)))
.with_scale(config.code_font_size);
let section = gr_text::section_from_glyph_text(
vec![
grid_node_map_text,
code_lines_text,
mark_node_tree_text,
mark_node_pool_text,
ast_node_text,
],
debug_txt_coords.into(),
area_bounds,
layout,
);
Ok(section)
}

View File

@ -1,6 +1,7 @@
use crate::editor::markup::nodes::MarkupNode;
use std::fmt;
pub type SlowNodeId = usize;
pub type MarkNodeId = usize;
#[derive(Debug)]
pub struct SlowPool {
@ -12,7 +13,7 @@ impl SlowPool {
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();
self.nodes.push(node);
@ -20,13 +21,35 @@ impl SlowPool {
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
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
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;
}
}
impl fmt::Display for SlowPool {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
write!(f, "\n\n(mark_node_pool)\n")?;
for (index, node) in self.nodes.iter().enumerate() {
writeln!(
f,
"{}: {} ({})",
index,
node.node_type_as_string(),
node.get_content().unwrap_or_else(|_| "".to_string()),
)?;
}
Ok(())
}
}

View File

@ -1,4 +1,5 @@
use super::ed_error::{EdResult, KeyNotFound};
use crate::editor::ed_error::IndexOfFailed;
use snafu::OptionExt;
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)
}
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)
}

View File

@ -84,12 +84,12 @@ fn section_from_text<'a>(
}
pub fn section_from_glyph_text(
text: Vec<wgpu_glyph::Text>,
text: Vec<glyph_brush::OwnedText>,
screen_position: (f32, f32),
area_bounds: (f32, f32),
layout: wgpu_glyph::Layout<wgpu_glyph::BuiltInLineBreaker>,
) -> wgpu_glyph::Section {
Section {
) -> glyph_brush::OwnedSection {
glyph_brush::OwnedSection {
screen_position,
bounds: area_bounds,
layout,

View File

@ -1,4 +1,5 @@
use crate::lang::pattern::{Pattern2, PatternId};
use crate::lang::pool::Pool;
use crate::lang::pool::{NodeId, PoolStr, PoolVec, ShallowClone};
use crate::lang::types::{Type2, TypeId};
use arraystring::{typenum::U30, ArrayString};
@ -58,6 +59,8 @@ fn size_of_intval() {
assert_eq!(std::mem::size_of::<IntVal>(), 16);
}
pub type ArrString = ArrayString<U30>;
/// An Expr that fits in 32B.
/// It has a 1B discriminant and variants which hold payloads of at most 31B.
#[derive(Debug)]
@ -94,11 +97,12 @@ pub enum Expr2 {
text: PoolStr, // 8B
},
/// string literals of length up to 30B
SmallStr(ArrayString<U30>), // 31B
SmallStr(ArrString), // 31B
/// string literals of length 31B or more
Str(PoolStr), // 8B
// Lookups
Var(Symbol), // 8B
Var(Symbol), // 8B
InvalidLookup(PoolStr), // 8B
List {
list_var: Variable, // 4B - required for uniqueness of the list
@ -298,6 +302,83 @@ pub struct WhenBranch {
pub type ExprId = NodeId<Expr2>;
pub fn expr2_to_string(node_id: NodeId<Expr2>, pool: &Pool) -> String {
let mut full_string = String::new();
expr2_to_string_helper(node_id, 0, pool, &mut full_string);
full_string
}
fn get_spacing(indent_level: usize) -> String {
std::iter::repeat(" ")
.take(indent_level)
.collect::<Vec<&str>>()
.join("")
}
fn expr2_to_string_helper(
node_id: NodeId<Expr2>,
indent_level: usize,
pool: &Pool,
out_string: &mut String,
) {
let expr2 = pool.get(node_id);
out_string.push_str(&get_spacing(indent_level));
match expr2 {
Expr2::SmallStr(arr_string) => out_string.push_str(&format!(
"{}{}{}",
"SmallStr(\"",
arr_string.as_str(),
"\")",
)),
Expr2::Str(pool_str) => {
out_string.push_str(&format!("{}{}{}", "Str(\"", pool_str.as_str(pool), "\")",))
}
Expr2::Blank => out_string.push_str("Blank"),
Expr2::EmptyRecord => out_string.push_str("EmptyRecord"),
Expr2::Record { record_var, fields } => {
out_string.push_str("Record:\n");
out_string.push_str(&format!(
"{}Var({:?})\n",
get_spacing(indent_level + 1),
record_var
));
out_string.push_str(&format!("{}fields: [\n", get_spacing(indent_level + 1)));
let mut first_child = true;
for (pool_str, var, val_node_id) in fields.iter(pool) {
if !first_child {
out_string.push_str(", ")
} else {
first_child = false;
}
out_string.push_str(&format!(
"{}({}, Var({:?}), Expr2(\n",
get_spacing(indent_level + 2),
pool_str.as_str(pool),
var,
));
expr2_to_string_helper(*val_node_id, indent_level + 3, pool, out_string);
out_string.push_str(&format!("{})\n", get_spacing(indent_level + 2)));
}
out_string.push_str(&format!("{}]\n", get_spacing(indent_level + 1)));
}
Expr2::InvalidLookup(pool_str) => {
out_string.push_str(&format!("InvalidLookup({})", pool_str.as_str(pool)));
}
other => todo!("Implement for {:?}", other),
}
out_string.push('\n');
}
#[test]
fn size_of_expr() {
assert_eq!(std::mem::size_of::<Expr2>(), crate::lang::pool::NODE_BYTES);

View File

@ -205,7 +205,7 @@ impl Drop for Pool {
}
/// A string containing at most 2^32 pool-allocated bytes.
#[derive(Debug)]
#[derive(Debug, Copy, Clone)]
pub struct PoolStr {
first_node_id: NodeId<()>,
len: u32,
@ -352,7 +352,7 @@ impl<'a, T: 'a + Sized> PoolVec<T> {
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)
}

View File

@ -113,13 +113,17 @@ impl Lines for BigTextArea {
lines
}
fn is_last_line(&self, line_nr: usize) -> bool {
line_nr == self.nr_of_lines() - 1
}
fn last_char(&self, line_nr: usize) -> UIResult<Option<char>> {
Ok(self.get_line(line_nr)?.chars().last())
}
}
impl SelectableLines for BigTextArea {
fn get_caret(self) -> TextPos {
fn get_caret(&self) -> TextPos {
self.caret_w_select.caret_pos
}

View File

@ -26,11 +26,13 @@ pub trait Lines {
fn all_lines<'a>(&self, arena: &'a Bump) -> BumpString<'a>;
fn is_last_line(&self, line_nr: usize) -> bool;
fn last_char(&self, line_nr: usize) -> UIResult<Option<char>>;
}
pub trait SelectableLines {
fn get_caret(self) -> TextPos;
fn get_caret(&self) -> TextPos;
fn set_caret(&mut self, caret_pos: TextPos);
@ -175,20 +177,17 @@ pub fn move_caret_right<T: Lines>(
None => unreachable!(),
}
} else {
let curr_line = lines.get_line(old_line_nr)?;
let curr_line_len = lines.line_len(old_line_nr)?;
let is_last_line = lines.is_last_line(old_line_nr);
if let Some(last_char) = curr_line.chars().last() {
if is_newline(&last_char) {
if old_col_nr + 1 > curr_line.len() - 1 {
(old_line_nr + 1, 0)
} else {
(old_line_nr, old_col_nr + 1)
}
} else if old_col_nr < curr_line.len() {
(old_line_nr, old_col_nr + 1)
if !is_last_line {
if old_col_nr + 1 > curr_line_len - 1 {
(old_line_nr + 1, 0)
} else {
(old_line_nr, old_col_nr)
(old_line_nr, old_col_nr + 1)
}
} else if old_col_nr < curr_line_len {
(old_line_nr, old_col_nr + 1)
} else {
(old_line_nr, old_col_nr)
}
@ -322,17 +321,15 @@ pub fn move_caret_down<T: Lines>(
(old_line_nr, curr_line_len)
} else {
let next_line = lines.get_line(old_line_nr + 1)?;
let next_line_index = old_line_nr + 1;
let next_line_len = lines.line_len(next_line_index)?;
let is_last_line = lines.is_last_line(next_line_index);
if next_line.len() <= old_col_nr {
if let Some(last_char) = next_line.chars().last() {
if is_newline(&last_char) {
(old_line_nr + 1, next_line.len() - 1)
} else {
(old_line_nr + 1, next_line.len())
}
if next_line_len <= old_col_nr {
if !is_last_line {
(old_line_nr + 1, next_line_len - 1)
} else {
(old_line_nr + 1, 0)
(old_line_nr + 1, next_line_len)
}
} else {
(old_line_nr + 1, old_col_nr)

View File

@ -8,7 +8,7 @@ pub fn is_newline(char_ref: &char) -> bool {
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> {
let elt_ref = slice.get(index).context(OutOfBounds {
index,
@ -18,3 +18,18 @@ pub fn slice_get<T>(index: usize, slice: &[T]) -> UIResult<&<usize as SliceIndex
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)
}

View File

@ -7,6 +7,7 @@ platform folkertdev/foo
effects fx.Effect
{
putLine : Str -> Effect {},
putInt : I64 -> Effect {},
getInt : Effect { value: I64, errorCode: [ A, B ], isError: Bool }
}

View File

@ -1,5 +1,5 @@
interface Task
exposes [ Task, succeed, fail, after, map, putLine, getInt ]
exposes [ Task, succeed, fail, after, map, putLine, putInt, getInt ]
imports [ fx.Effect ]
@ -32,6 +32,9 @@ map = \effect, transform ->
putLine : Str -> Task {} *
putLine = \line -> Effect.map (Effect.putLine line) (\_ -> Ok {})
putInt : I64 -> Task {} *
putInt = \line -> Effect.map (Effect.putInt line) (\_ -> Ok {})
getInt : Task I64 []
getInt =
Effect.after Effect.getInt \{ isError, value, errorCode } ->

View File

@ -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 {
const stdout = std.io.getStdOut().writer();