use crate::atom::{Atom, AtomTable};
use crate::native::{NativeContinuation, ProcessContext};
use super::lists_bifs::list_from_vec;
use crate::term::Term;
use crate::term::boxed::{Closure, Cons, Map};
use crate::term::compare;
#[derive(Clone, Debug)]
pub enum MapsHofState {
Fold {
fun: Term,
entries: Vec<(Term, Term)>,
index: usize,
},
Filter {
fun: Term,
entries: Vec<(Term, Term)>,
index: usize,
kept: Vec<(Term, Term)>,
},
Map {
fun: Term,
entries: Vec<(Term, Term)>,
index: usize,
mapped: Vec<(Term, Term)>,
},
MergeWith {
fun: Term,
pending: Vec<(Term, Term, Term)>,
entries: Vec<(Term, Term)>,
index: usize,
},
UpdateWith {
remaining: Vec<(Term, Term)>,
updated: Vec<(Term, Term)>,
},
}
impl MapsHofState {
pub(crate) fn for_each_term(&self, f: &mut dyn FnMut(Term)) {
let visit_pairs = |pairs: &[(Term, Term)], f: &mut dyn FnMut(Term)| {
for (key, value) in pairs {
f(*key);
f(*value);
}
};
match self {
Self::Fold { fun, entries, .. } => {
f(*fun);
visit_pairs(entries, f);
}
Self::Filter {
fun, entries, kept, ..
} => {
f(*fun);
visit_pairs(entries, f);
visit_pairs(kept, f);
}
Self::Map {
fun,
entries,
mapped,
..
} => {
f(*fun);
visit_pairs(entries, f);
visit_pairs(mapped, f);
}
Self::MergeWith {
fun,
pending,
entries,
..
} => {
f(*fun);
for (key, left, right) in pending {
f(*key);
f(*left);
f(*right);
}
visit_pairs(entries, f);
}
Self::UpdateWith { remaining, updated } => {
visit_pairs(remaining, f);
visit_pairs(updated, f);
}
}
}
pub(crate) fn for_each_term_mut(&mut self, f: &mut dyn FnMut(&mut Term)) {
let visit_pairs = |pairs: &mut [(Term, Term)], f: &mut dyn FnMut(&mut Term)| {
for (key, value) in pairs {
f(key);
f(value);
}
};
match self {
Self::Fold { fun, entries, .. } => {
f(fun);
visit_pairs(entries, f);
}
Self::Filter {
fun, entries, kept, ..
} => {
f(fun);
visit_pairs(entries, f);
visit_pairs(kept, f);
}
Self::Map {
fun,
entries,
mapped,
..
} => {
f(fun);
visit_pairs(entries, f);
visit_pairs(mapped, f);
}
Self::MergeWith {
fun,
pending,
entries,
..
} => {
f(fun);
for (key, left, right) in pending {
f(key);
f(left);
f(right);
}
visit_pairs(entries, f);
}
Self::UpdateWith { remaining, updated } => {
visit_pairs(remaining, f);
visit_pairs(updated, f);
}
}
}
}
pub fn bif_maps_put(args: &[Term], context: &mut ProcessContext) -> Result<Term, Term> {
let [key, value, map_term] = args else {
return Err(badarg());
};
let atom_table = context.atom_table_arc().ok_or_else(badarg)?;
let mut entries = map_entries(*map_term)?;
set_entry(&mut entries, *key, *value, atom_table.as_ref());
make_sorted_map(context, &entries, atom_table.as_ref())
}
pub fn bif_maps_find(args: &[Term], context: &mut ProcessContext) -> Result<Term, Term> {
let [key, map_term] = args else {
return Err(badarg());
};
let map = Map::new(*map_term).ok_or_else(badarg)?;
if map.get(*key).is_none() {
return Ok(Term::atom(Atom::ERROR));
}
context.with_rooted(&[*key, *map_term], |context, roots| {
context.ensure_heap_space(3)?;
let key = context.rooted(roots, 0)?;
let map_term = context.rooted(roots, 1)?;
let map = Map::new(map_term).ok_or_else(badarg)?;
let value = map.get(key).ok_or_else(badarg)?;
context.alloc_tuple_prereserved(&[Term::atom(Atom::OK), value])
})
}
pub fn bif_maps_keys(args: &[Term], context: &mut ProcessContext) -> Result<Term, Term> {
let [map_term] = args else {
return Err(badarg());
};
let map = Map::new(*map_term).ok_or_else(badarg)?;
let keys: Result<Vec<_>, _> = (0..map.len())
.map(|index| map.key(index).ok_or_else(badarg))
.collect();
list_from_vec(&keys?, context)
}
pub fn bif_maps_values(args: &[Term], context: &mut ProcessContext) -> Result<Term, Term> {
let [map_term] = args else {
return Err(badarg());
};
let map = Map::new(*map_term).ok_or_else(badarg)?;
let values: Result<Vec<_>, _> = (0..map.len())
.map(|index| map.value(index).ok_or_else(badarg))
.collect();
list_from_vec(&values?, context)
}
pub fn bif_maps_to_list(args: &[Term], context: &mut ProcessContext) -> Result<Term, Term> {
let [map_term] = args else {
return Err(badarg());
};
let pairs = map_entries(*map_term)?;
if pairs.is_empty() {
return Ok(Term::NIL);
}
{
let process = context.process_mut().ok_or_else(badarg)?;
process.set_x_reg(0, *map_term);
}
context.ensure_heap_space(pairs.len() * (3 + 2))?;
let map_term = context.process_mut().ok_or_else(badarg)?.x_reg(0);
let pairs = map_entries(map_term)?;
let mut result = Term::NIL;
for (key, value) in pairs.into_iter().rev() {
let tuple = context.alloc_tuple_prereserved(&[key, value])?;
result = context.alloc_cons_prereserved(tuple, result)?;
}
Ok(result)
}
pub fn bif_maps_fold(args: &[Term], context: &mut ProcessContext) -> Result<Term, Term> {
let [fun, acc, map_term] = args else {
return Err(badarg());
};
ensure_fun_arity(*fun, 3)?;
let entries = map_entries(*map_term)?;
if entries.is_empty() {
return Ok(*acc);
}
let (key, value) = entries[0];
context.set_continuation_trampoline(
*fun,
vec![key, value, *acc],
NativeContinuation::Maps(MapsHofState::Fold {
fun: *fun,
entries,
index: 1,
}),
);
Ok(Term::NIL)
}
pub fn bif_maps_filter(args: &[Term], context: &mut ProcessContext) -> Result<Term, Term> {
let [fun, map_term] = args else {
return Err(badarg());
};
ensure_fun_arity(*fun, 2)?;
let entries = map_entries(*map_term)?;
if entries.is_empty() {
let atom_table = context.atom_table_arc().ok_or_else(badarg)?;
return make_sorted_map(context, &[], atom_table.as_ref());
}
let (key, value) = entries[0];
context.set_continuation_trampoline(
*fun,
vec![key, value],
NativeContinuation::Maps(MapsHofState::Filter {
fun: *fun,
entries,
index: 1,
kept: Vec::new(),
}),
);
Ok(Term::NIL)
}
pub fn bif_maps_map(args: &[Term], context: &mut ProcessContext) -> Result<Term, Term> {
let [fun, map_term] = args else {
return Err(badarg());
};
ensure_fun_arity(*fun, 2)?;
let entries = map_entries(*map_term)?;
if entries.is_empty() {
let atom_table = context.atom_table_arc().ok_or_else(badarg)?;
return make_sorted_map(context, &[], atom_table.as_ref());
}
let (key, value) = entries[0];
context.set_continuation_trampoline(
*fun,
vec![key, value],
NativeContinuation::Maps(MapsHofState::Map {
fun: *fun,
entries,
index: 1,
mapped: Vec::new(),
}),
);
Ok(Term::NIL)
}
pub fn bif_maps_merge_with(args: &[Term], context: &mut ProcessContext) -> Result<Term, Term> {
let [fun, map1_term, map2_term] = args else {
return Err(badarg());
};
ensure_fun_arity(*fun, 3)?;
let atom_table = context.atom_table_arc().ok_or_else(badarg)?;
let map1_entries = map_entries(*map1_term)?;
let map2_entries = map_entries(*map2_term)?;
let mut entries = map1_entries.clone();
let mut collisions = Vec::new();
for (key, value2) in map2_entries {
if let Some((_, value1)) = entries
.iter()
.find(|(existing_key, _)| *existing_key == key)
{
collisions.push((key, *value1, value2));
} else {
entries.push((key, value2));
}
}
sort_entries_by_key(&mut entries, atom_table.as_ref());
if collisions.is_empty() {
return make_sorted_map(context, &entries, atom_table.as_ref());
}
let (key, value1, value2) = collisions[0];
context.set_continuation_trampoline(
*fun,
vec![key, value1, value2],
NativeContinuation::Maps(MapsHofState::MergeWith {
fun: *fun,
pending: collisions,
entries,
index: 1,
}),
);
Ok(Term::NIL)
}
pub fn bif_maps_update_with(args: &[Term], context: &mut ProcessContext) -> Result<Term, Term> {
let [key, fun, init, map_term] = args else {
return Err(badarg());
};
ensure_fun_arity(*fun, 1)?;
let atom_table = context.atom_table_arc().ok_or_else(badarg)?;
let entries = map_entries(*map_term)?;
let Some(position) = entries.iter().position(|(entry_key, _)| entry_key == key) else {
let mut with_init = entries;
with_init.push((*key, *init));
return make_sorted_map(context, &with_init, atom_table.as_ref());
};
let (existing_key, existing_value) = entries[position];
let remaining = entries[position + 1..].to_vec();
let updated = entries[..position].to_vec();
context.set_continuation_trampoline(
*fun,
vec![existing_value],
NativeContinuation::Maps(MapsHofState::UpdateWith {
remaining,
updated: [updated, vec![(existing_key, Term::NIL)]].concat(),
}),
);
Ok(Term::NIL)
}
pub fn bif_maps_with(args: &[Term], context: &mut ProcessContext) -> Result<Term, Term> {
let [keys_term, map_term] = args else {
return Err(badarg());
};
let atom_table = context.atom_table_arc().ok_or_else(badarg)?;
let keys = list_to_vec(*keys_term)?;
let entries = map_entries(*map_term)?;
let kept: Vec<_> = entries
.into_iter()
.filter(|(key, _)| keys.iter().any(|wanted| wanted == key))
.collect();
make_sorted_map(context, &kept, atom_table.as_ref())
}
pub fn bif_maps_without(args: &[Term], context: &mut ProcessContext) -> Result<Term, Term> {
let [keys_term, map_term] = args else {
return Err(badarg());
};
let atom_table = context.atom_table_arc().ok_or_else(badarg)?;
let keys = list_to_vec(*keys_term)?;
let entries = map_entries(*map_term)?;
let kept: Vec<_> = entries
.into_iter()
.filter(|(key, _)| !keys.iter().any(|removed| removed == key))
.collect();
make_sorted_map(context, &kept, atom_table.as_ref())
}
pub fn resume_maps_continuation(
state: MapsHofState,
closure_result: Term,
context: &mut ProcessContext,
) -> Result<ContinuationStep, Term> {
match state {
MapsHofState::Fold {
fun,
entries,
index,
} => {
if let Some((key, value)) = entries.get(index).copied() {
Ok(ContinuationStep::Call {
fun,
args: vec![key, value, closure_result],
continuation: NativeContinuation::Maps(MapsHofState::Fold {
fun,
entries,
index: index + 1,
}),
})
} else {
Ok(ContinuationStep::Done(closure_result))
}
}
MapsHofState::Filter {
fun,
entries,
index,
mut kept,
} => {
if is_true(closure_result)? {
let previous = index.checked_sub(1).ok_or_else(badarg)?;
kept.push(entries[previous]);
}
if let Some((key, value)) = entries.get(index).copied() {
Ok(ContinuationStep::Call {
fun,
args: vec![key, value],
continuation: NativeContinuation::Maps(MapsHofState::Filter {
fun,
entries,
index: index + 1,
kept,
}),
})
} else {
Ok(ContinuationStep::Done(make_map_from_entries(
context, &kept,
)?))
}
}
MapsHofState::Map {
fun,
entries,
index,
mut mapped,
} => {
let previous = index.checked_sub(1).ok_or_else(badarg)?;
let previous_key = entries
.get(previous)
.map(|(key, _)| *key)
.ok_or_else(badarg)?;
mapped.push((previous_key, closure_result));
if let Some((key, value)) = entries.get(index).copied() {
Ok(ContinuationStep::Call {
fun,
args: vec![key, value],
continuation: NativeContinuation::Maps(MapsHofState::Map {
fun,
entries,
index: index + 1,
mapped,
}),
})
} else {
Ok(ContinuationStep::Done(make_map_from_entries(
context, &mapped,
)?))
}
}
MapsHofState::MergeWith {
fun,
pending,
mut entries,
index,
} => {
let current_key = pending
.get(index - 1)
.map(|(key, _, _)| *key)
.ok_or_else(badarg)?;
set_entry_unsorted(&mut entries, current_key, closure_result);
if let Some((key, value1, value2)) = pending.get(index).copied() {
Ok(ContinuationStep::Call {
fun,
args: vec![key, value1, value2],
continuation: NativeContinuation::Maps(MapsHofState::MergeWith {
fun,
pending,
entries,
index: index + 1,
}),
})
} else {
Ok(ContinuationStep::Done(make_map_from_entries(
context, &entries,
)?))
}
}
MapsHofState::UpdateWith {
mut remaining,
mut updated,
} => {
if let Some(last) = updated.last_mut() {
last.1 = closure_result;
} else {
return Err(badarg());
}
updated.append(&mut remaining);
Ok(ContinuationStep::Done(make_map_from_entries(
context, &updated,
)?))
}
}
}
pub enum ContinuationStep {
Call {
fun: Term,
args: Vec<Term>,
continuation: NativeContinuation,
},
Done(Term),
}
fn ensure_fun_arity(fun: Term, arity: u8) -> Result<(), Term> {
let closure = Closure::new(fun).ok_or_else(badarg)?;
if closure.arity() == arity {
Ok(())
} else {
Err(Term::atom(Atom::BADARITY))
}
}
fn is_true(term: Term) -> Result<bool, Term> {
if term == Term::atom(Atom::TRUE) {
Ok(true)
} else if term == Term::atom(Atom::FALSE) {
Ok(false)
} else {
Err(badarg())
}
}
fn map_entries(term: Term) -> Result<Vec<(Term, Term)>, Term> {
let map = Map::new(term).ok_or_else(badarg)?;
(0..map.len())
.map(|index| {
Ok((
map.key(index).ok_or_else(badarg)?,
map.value(index).ok_or_else(badarg)?,
))
})
.collect()
}
fn set_entry(entries: &mut Vec<(Term, Term)>, key: Term, value: Term, atom_table: &AtomTable) {
set_entry_unsorted(entries, key, value);
sort_entries_by_key(entries, atom_table);
}
fn set_entry_unsorted(entries: &mut Vec<(Term, Term)>, key: Term, value: Term) {
if let Some(existing) = entries.iter_mut().find(|(entry_key, _)| *entry_key == key) {
existing.1 = value;
} else {
entries.push((key, value));
}
}
fn make_sorted_map(
context: &mut ProcessContext,
entries: &[(Term, Term)],
atom_table: &AtomTable,
) -> Result<Term, Term> {
let mut sorted = entries.to_vec();
sort_entries_by_key(&mut sorted, atom_table);
make_map_from_entries(context, &sorted)
}
fn sort_entries_by_key(entries: &mut [(Term, Term)], atom_table: &AtomTable) {
entries.sort_by(|(left, _), (right, _)| compare::cmp(*left, *right, atom_table));
}
fn make_map_from_entries(
context: &mut ProcessContext,
entries: &[(Term, Term)],
) -> Result<Term, Term> {
let mut flat = Vec::with_capacity(entries.len() * 2);
for (key, value) in entries {
flat.push(*key);
flat.push(*value);
}
context.with_rooted(&flat, |context, roots| {
context.ensure_heap_space(2 + entries.len() * 2)?;
let mut keys = Vec::with_capacity(entries.len());
let mut values = Vec::with_capacity(entries.len());
for index in 0..entries.len() {
keys.push(context.rooted(roots, index * 2)?);
values.push(context.rooted(roots, index * 2 + 1)?);
}
context.alloc_map_prereserved(&keys, &values)
})
}
fn list_to_vec(term: Term) -> Result<Vec<Term>, Term> {
let mut elements = Vec::new();
let mut current = term;
while !current.is_nil() {
let cons = Cons::new(current).ok_or_else(badarg)?;
elements.push(cons.head());
current = cons.tail();
}
Ok(elements)
}
fn badarg() -> Term {
Term::atom(Atom::BADARG)
}