Implement List.find

`List.find : List elem, (elem -> Bool) -> Result elem [ NotFound ]*`
behaves as follows:

```
>>> List.find [1, 2, 3] (\n -> n > 2)
Ok 2
>>> List.find [1, 2, 3] (\n -> n > 4)
Err NotFound
```

We implement this as builtin in two phases. First, we call out to a
pure-llvm-lowlevel `ListFindUnsafe` that returns a record indicating
whether a satisfying element was found, and the value of that element
(the value is all null bytes if the element wasn't found). Then, we lift
that record to a `Result` via a standard construction of the can AST.

Closes #1909
This commit is contained in:
ayazhafiz 2021-11-07 20:56:46 -05:00
parent 35df58c18f
commit f65b174ab5
16 changed files with 417 additions and 8 deletions

View file

@ -107,6 +107,8 @@ pub fn builtin_defs_map(symbol: Symbol, var_store: &mut VarStore) -> Option<Def>
LIST_WALK_UNTIL => list_walk_until,
LIST_SORT_WITH => list_sort_with,
LIST_ANY => list_any,
LIST_FIND => list_find,
DICT_TEST_HASH => dict_hash_test_only,
DICT_LEN => dict_len,
DICT_EMPTY => dict_empty,
DICT_SINGLE => dict_single,
@ -2724,6 +2726,92 @@ fn list_any(symbol: Symbol, var_store: &mut VarStore) -> Def {
lowlevel_2(symbol, LowLevel::ListAny, var_store)
}
/// List.find : List elem, (elem -> Bool) -> Result elem [ NotFound ]*
fn list_find(symbol: Symbol, var_store: &mut VarStore) -> Def {
let list = Symbol::ARG_1;
let find_predicate = Symbol::ARG_2;
let find_result = Symbol::LIST_FIND_RESULT;
let t_list = var_store.fresh();
let t_pred_fn = var_store.fresh();
let t_bool = var_store.fresh();
let t_found = var_store.fresh();
let t_value = var_store.fresh();
let t_ret = var_store.fresh();
let t_find_result = var_store.fresh();
let t_ext_var1 = var_store.fresh();
let t_ext_var2 = var_store.fresh();
// ListFindUnsafe returns { value: elem, found: Bool }.
// When `found` is true, the value was found. Otherwise `List.find` should return `Err ...`
let find_result_def = Def {
annotation: None,
expr_var: t_find_result,
loc_expr: no_region(RunLowLevel {
op: LowLevel::ListFindUnsafe,
args: vec![(t_list, Var(list)), (t_pred_fn, Var(find_predicate))],
ret_var: t_find_result,
}),
loc_pattern: no_region(Pattern::Identifier(find_result)),
pattern_vars: Default::default(),
};
let get_value = Access {
record_var: t_find_result,
ext_var: t_ext_var1,
field_var: t_value,
loc_expr: Box::new(no_region(Var(find_result))),
field: "value".into(),
};
let get_found = Access {
record_var: t_find_result,
ext_var: t_ext_var2,
field_var: t_found,
loc_expr: Box::new(no_region(Var(find_result))),
field: "found".into(),
};
let make_ok = tag("Ok", vec![get_value], var_store);
let make_err = tag(
"Err",
vec![tag("NotFound", Vec::new(), var_store)],
var_store,
);
let inspect = If {
cond_var: t_bool,
branch_var: t_ret,
branches: vec![(
// if-condition
no_region(get_found),
no_region(make_ok),
)],
final_else: Box::new(no_region(make_err)),
};
let body = LetNonRec(
Box::new(find_result_def),
Box::new(no_region(inspect)),
t_ret,
);
defn(
symbol,
vec![(t_list, Symbol::ARG_1), (t_pred_fn, Symbol::ARG_2)],
var_store,
body,
t_ret,
)
}
/// Dict.hashTestOnly : k, v -> Nat
fn dict_hash_test_only(symbol: Symbol, var_store: &mut VarStore) -> Def {
lowlevel_2(symbol, LowLevel::Hash, var_store)
}
/// Dict.len : Dict * * -> Nat
fn dict_len(symbol: Symbol, var_store: &mut VarStore) -> Def {
let arg1_var = var_store.fresh();