use crate::atom::{Atom, AtomTable};
use crate::native::ProcessContext;
use crate::term::Term;
use crate::term::binary_ref::BinaryRef;
use crate::term::boxed::{Cons, Tuple};
use crate::term::compare;
use crate::term::format::format_term;
pub fn bif_io_put_chars_1(args: &[Term], context: &mut ProcessContext) -> Result<Term, Term> {
let [chars] = args else {
return Err(badarg());
};
write_iodata(*chars, context)?;
Ok(Term::atom(Atom::OK))
}
pub fn bif_io_put_chars_2(args: &[Term], context: &mut ProcessContext) -> Result<Term, Term> {
let [_device, chars] = args else {
return Err(badarg());
};
write_iodata(*chars, context)?;
Ok(Term::atom(Atom::OK))
}
pub fn bif_io_format_3(args: &[Term], context: &mut ProcessContext) -> Result<Term, Term> {
let [_device, format, arguments] = args else {
return Err(badarg());
};
let bytes = format_bytes(*format, *arguments, context)?;
context.io_sink().write(&bytes);
Ok(Term::atom(Atom::OK))
}
pub fn bif_io_format_2(args: &[Term], context: &mut ProcessContext) -> Result<Term, Term> {
let [format, arguments] = args else {
return Err(badarg());
};
let target = context.group_leader()?;
let bytes = format_bytes(*format, *arguments, context)?;
let chars = context.alloc_binary(&bytes)?;
send_put_chars(target, chars, context)
}
pub fn bif_io_get_line_1(args: &[Term], context: &mut ProcessContext) -> Result<Term, Term> {
let [prompt] = args else {
return Err(badarg());
};
let target = context.group_leader()?;
let prompt_bytes = iodata_bytes(*prompt)?;
let prompt_bin = context.alloc_binary(&prompt_bytes)?;
let request = io_request_tuple(context, "get_line", prompt_bin)?;
send_io_request_and_wait(target, request, context)
}
pub fn bif_io_setopts_2(args: &[Term], context: &mut ProcessContext) -> Result<Term, Term> {
let _ = context;
let [_device, _options] = args else {
return Err(badarg());
};
Ok(Term::atom(Atom::OK))
}
pub fn bif_io_lib_format_2(args: &[Term], context: &mut ProcessContext) -> Result<Term, Term> {
let [format, arguments] = args else {
return Err(badarg());
};
let bytes = format_bytes(*format, *arguments, context)?;
context.alloc_binary(&bytes)
}
fn format_bytes(format: Term, arguments: Term, context: &ProcessContext) -> Result<Vec<u8>, Term> {
let format = iodata_bytes(format)?;
let arguments = list_terms(arguments)?;
let mut out = Vec::with_capacity(format.len());
let mut arg_index = 0usize;
let mut index = 0usize;
while index < format.len() {
if format[index] != b'~' {
out.push(format[index]);
index += 1;
continue;
}
index += 1;
let Some(&directive) = format.get(index) else {
return Err(badarg());
};
match directive {
b's' => {
let arg = next_arg(&arguments, &mut arg_index)?;
out.extend_from_slice(&binary_bytes(arg)?);
}
b'p' | b'w' => {
let arg = next_arg(&arguments, &mut arg_index)?;
out.extend_from_slice(render_term(arg, context).as_bytes());
}
b'n' => out.push(b'\n'),
b'~' => out.push(b'~'),
_ => return Err(badarg()),
}
index += 1;
}
if arg_index != arguments.len() {
return Err(badarg());
}
Ok(out)
}
fn next_arg(arguments: &[Term], index: &mut usize) -> Result<Term, Term> {
let term = arguments.get(*index).copied().ok_or_else(badarg)?;
*index += 1;
Ok(term)
}
fn write_iodata(term: Term, context: &ProcessContext) -> Result<(), Term> {
let bytes = iodata_bytes(term)?;
context.io_sink().write(&bytes);
Ok(())
}
fn iodata_bytes(term: Term) -> Result<Vec<u8>, Term> {
let mut bytes = Vec::new();
collect_iodata(term, &mut bytes)?;
Ok(bytes)
}
fn collect_iodata(term: Term, out: &mut Vec<u8>) -> Result<(), Term> {
if term.is_nil() {
return Ok(());
}
if let Some(binary) = BinaryRef::new(term) {
out.extend_from_slice(binary.as_bytes());
return Ok(());
}
if let Some(byte) = term
.as_small_int()
.and_then(|value| u8::try_from(value).ok())
{
out.push(byte);
return Ok(());
}
let cons = Cons::new(term).ok_or_else(badarg)?;
collect_iodata(cons.head(), out)?;
collect_iodata(cons.tail(), out)
}
fn list_terms(term: Term) -> Result<Vec<Term>, Term> {
let mut terms = Vec::new();
let mut current = term;
loop {
if current.is_nil() {
return Ok(terms);
}
let cons = Cons::new(current).ok_or_else(badarg)?;
terms.push(cons.head());
current = cons.tail();
}
}
fn render_term(term: Term, context: &ProcessContext) -> String {
let fallback = AtomTable::with_common_atoms();
let atom_table = context.atom_table().unwrap_or(&fallback);
format_term(term, atom_table)
}
fn binary_bytes(term: Term) -> Result<Vec<u8>, Term> {
BinaryRef::new(term)
.map(|binary| binary.as_bytes().to_vec())
.ok_or_else(badarg)
}
fn send_put_chars(target: Term, chars: Term, context: &mut ProcessContext) -> Result<Term, Term> {
let request = io_request_tuple(context, "put_chars", chars)?;
send_io_request_and_wait(target, request, context)
}
fn io_request_tuple(
context: &mut ProcessContext,
request_atom: &str,
data: Term,
) -> Result<Term, Term> {
let (request_tag, unicode) = {
let table = context.atom_table().ok_or_else(badarg)?;
(table.intern(request_atom), table.intern("unicode"))
};
context.alloc_tuple(&[Term::atom(request_tag), Term::atom(unicode), data])
}
fn send_io_request_and_wait(
target: Term,
request: Term,
context: &mut ProcessContext,
) -> Result<Term, Term> {
let target_pid = target.as_pid().ok_or_else(badarg)?;
let caller_pid = context.pid().ok_or_else(badarg)?;
let io_request_atom = context
.atom_table()
.ok_or_else(badarg)?
.intern("io_request");
context.ensure_heap_space(2 + 5)?;
let reply_ref = context.alloc_reference_prereserved(reply_ref_id(caller_pid))?;
if let Some(result) = take_io_reply(reply_ref, context)? {
return Ok(result);
}
let message = context.alloc_tuple_prereserved(&[
Term::atom(io_request_atom),
Term::pid(caller_pid),
reply_ref,
request,
])?;
let sent = {
let facility = context.io_message_facility().ok_or_else(badarg)?;
facility.send_message(caller_pid, target_pid, message)
};
if !sent {
return error_tuple(context, Atom::NOPROC);
}
take_io_reply(reply_ref, context)?.map_or_else(
|| {
context.request_suspend(None);
Ok(Term::atom(Atom::OK))
},
Ok,
)
}
fn take_io_reply(reply_ref: Term, context: &mut ProcessContext) -> Result<Option<Term>, Term> {
let io_reply = context.atom_table().ok_or_else(badarg)?.intern("io_reply");
let Some(select) = context.select_facility() else {
return Err(badarg());
};
for index in 0..select.message_count() {
let Some(message) = select.peek_message(index) else {
continue;
};
let Some(tuple) = Tuple::new(message) else {
continue;
};
if tuple.arity() != 3 || tuple.get(0) != Some(Term::atom(io_reply)) {
continue;
}
let Some(reply_as) = tuple.get(1) else {
continue;
};
if !compare::exact_eq(reply_as, reply_ref) {
continue;
}
let result = tuple.get(2).ok_or_else(badarg)?;
select.remove_message(index);
return Ok(Some(result));
}
Ok(None)
}
fn reply_ref_id(pid: u64) -> u64 {
pid
}
fn error_tuple(context: &mut ProcessContext, reason: Atom) -> Result<Term, Term> {
context.alloc_tuple(&[Term::atom(Atom::ERROR), Term::atom(reason)])
}
fn badarg() -> Term {
Term::atom(Atom::BADARG)
}