use std::mem::take;
use anyhow::{Result, bail};
use super::bytecode::{BuiltinId, MethodName};
use super::discard::{discard, take_discarded};
use super::enum_def::{EnumKind, OK, SOME};
use super::methods::{make_ordering, str_grow};
use super::value::{MapKind, Value};
use super::vm_step::{Flow, StepCtx};
pub(super) fn method_op(
ctx: &mut StepCtx,
dst: u16,
recv: u16,
name: u16,
abase: u16,
argc: u16,
) -> Result<Flow> {
let flow = method_call(ctx, dst, recv, name, abase, argc)?;
let parked = take_discarded();
if ctx.vm.has_drop {
let owned = ctx.cur.names[name as usize].owned;
for parked in parked {
if owned || !parked.payload {
ctx.vm.run_user_drop(parked.value)?;
}
}
}
Ok(flow)
}
fn method_call(
ctx: &mut StepCtx,
dst: u16,
recv: u16,
name: u16,
abase: u16,
argc: u16,
) -> Result<Flow> {
let (vm, cur, base) = (ctx.vm, ctx.cur, ctx.base);
let (recv, abase, argc) = (recv as usize, abase as usize, argc as usize);
let name = &cur.names[name as usize];
let s = base + abase;
if let Some(v) = builtin_fast(ctx, recv, name, s, argc, dst) {
return Ok(ctx.set_opt(dst, v));
}
if let BuiltinId::GetOrInsert | BuiltinId::GetOrInsertWith = name.id
&& argc == 1
&& is_option(ctx, recv)
{
let v = option_get_or_insert(ctx, recv, name.id, s)?;
return Ok(ctx.set_opt(dst, v));
}
if matches!(ctx.stack[base + recv], Value::Int(_) | Value::IntW(..))
&& ctx.stack[s..s + argc]
.iter()
.all(|arg| matches!(arg, Value::Int(_) | Value::IntW(..)))
&& let Some(result) = crate::interpreter::bridge::int_method(
&ctx.stack[base + recv],
name,
&ctx.stack[s..s + argc],
)
{
return Ok(ctx.set_opt(dst, result?));
}
let v = if argc == 0 {
vm.eval_method(&ctx.stack[base + recv].clone(), name, &mut [])?
} else if base + recv < s {
let (lo, hi) = ctx.stack.split_at_mut(s);
vm.eval_method(&lo[base + recv], name, &mut hi[..argc])?
} else {
let recv_v = ctx.stack[base + recv].clone();
vm.eval_method(&recv_v, name, &mut ctx.stack[s..s + argc])?
};
Ok(ctx.set_opt(dst, v))
}
fn builtin_fast(
ctx: &mut StepCtx,
recv: usize,
name: &MethodName,
s: usize,
argc: usize,
dst: u16,
) -> Option<Value> {
let base = ctx.base;
match name.id {
BuiltinId::CloneFrom if argc == 1 => Some(clone_from(ctx, recv, s)),
BuiltinId::Take if argc == 0 && is_option(ctx, recv) => Some(option_take(ctx, recv)),
BuiltinId::Replace if argc == 1 && is_option(ctx, recv) => {
Some(option_replace(ctx, recv, s))
}
BuiltinId::MakeAsciiUppercase => ascii_case(ctx, recv, true),
BuiltinId::MakeAsciiLowercase => ascii_case(ctx, recv, false),
BuiltinId::Push | BuiltinId::PushStr
if argc == 1 && matches!(ctx.stack[base + recv], Value::Str(_)) =>
{
str_push(ctx, recv, name.id, s)
}
BuiltinId::WriteAll | BuiltinId::WriteStr | BuiltinId::WriteFmt
if argc == 1 && matches!(ctx.stack[base + recv], Value::Str(_)) =>
{
str_push(ctx, recv, BuiltinId::PushStr, s).map(|_| Value::ok(Value::Unit))
}
BuiltinId::Clear
if argc == 0 && name.place && matches!(ctx.stack[base + recv], Value::Str(_)) =>
{
ctx.stack[base + recv] = Value::str(String::new());
Some(Value::Unit)
}
BuiltinId::Copied | BuiltinId::Cloned | BuiltinId::Unwrap | BuiltinId::UnwrapOr
if ctx.vm.impls.is_empty() =>
{
option_fast(ctx, recv, name.id, s, argc)
}
BuiltinId::ToString | BuiltinId::Clone => match &ctx.stack[base + recv] {
Value::Str(v) => Some(Value::Str(v.clone())),
_ => None,
},
BuiltinId::Cmp if argc == 1 => int_cmp(ctx, recv, s),
BuiltinId::Get | BuiltinId::Insert | BuiltinId::ContainsKey => {
map_fast(ctx, recv, name.id, s, argc, dst)
}
_ => None,
}
}
fn is_option(ctx: &StepCtx, recv: usize) -> bool {
ctx.stack[ctx.base + recv].is_enum_kind(EnumKind::Option)
}
fn clone_from(ctx: &mut StepCtx, recv: usize, s: usize) -> Value {
let src = ctx.stack[s].clone();
ctx.stack[ctx.base + recv] = src;
Value::Unit
}
fn option_get_or_insert(ctx: &mut StepCtx, recv: usize, id: BuiltinId, s: usize) -> Result<Value> {
let slot = ctx.base + recv;
let is_none = matches!(
&ctx.stack[slot],
Value::Enum { variant, .. } if *variant == super::enum_def::NONE
);
if is_none {
let filler = ctx.stack[s].clone();
let value = if id == BuiltinId::GetOrInsert {
filler
} else {
let Value::Closure(clo) = filler else {
bail!("get_or_insert_with takes a function");
};
ctx.vm.call_closure_data(&clo, &[])?
};
ctx.stack[slot] = Value::some(value);
}
match &ctx.stack[slot] {
Value::Enum { data, .. } => Ok(Value::Ref(std::sync::Arc::new(
super::value::ValueRef::vec_element(data.clone(), 0),
))),
_ => bail!("get_or_insert needs an Option receiver"),
}
}
fn option_take(ctx: &mut StepCtx, recv: usize) -> Value {
let old = take(&mut ctx.stack[ctx.base + recv]);
ctx.stack[ctx.base + recv] = Value::none();
old
}
fn option_replace(ctx: &mut StepCtx, recv: usize, s: usize) -> Value {
let new = ctx.stack[s].clone();
let old = take(&mut ctx.stack[ctx.base + recv]);
ctx.stack[ctx.base + recv] = Value::some(new);
old
}
fn ascii_case(ctx: &mut StepCtx, recv: usize, upper: bool) -> Option<Value> {
let slot = &mut ctx.stack[ctx.base + recv];
let new = match &*slot {
Value::Str(text) => Value::str(if upper {
text.to_ascii_uppercase()
} else {
text.to_ascii_lowercase()
}),
Value::Char(c) => Value::Char(if upper {
c.to_ascii_uppercase()
} else {
c.to_ascii_lowercase()
}),
_ => return None,
};
*slot = new;
Some(Value::Unit)
}
fn str_push(ctx: &mut StepCtx, recv: usize, id: BuiltinId, s: usize) -> Option<Value> {
let arg = ctx.stack[s].clone();
let Value::Str(text) = &mut ctx.stack[ctx.base + recv] else {
return None;
};
str_grow(text, id, &arg).ok()?;
Some(Value::Unit)
}
fn int_cmp(ctx: &StepCtx, recv: usize, s: usize) -> Option<Value> {
if let Value::Int(a) = ctx.stack[ctx.base + recv]
&& let Value::Int(b) = ctx.stack[s]
{
return Some(make_ordering(a.cmp(&b)));
}
None
}
fn option_fast(
ctx: &mut StepCtx,
recv: usize,
id: BuiltinId,
s: usize,
argc: usize,
) -> Option<Value> {
let (kind, success) = match &ctx.stack[ctx.base + recv] {
Value::Enum { def, variant, .. } => match def.kind {
EnumKind::Option => (EnumKind::Option, *variant == SOME),
EnumKind::Result => (EnumKind::Result, *variant == OK),
_ => return None,
},
_ => return None,
};
match id {
BuiltinId::Copied | BuiltinId::Cloned if kind == EnumKind::Option => {
Some(ctx.stack[ctx.base + recv].deep_clone())
}
BuiltinId::Unwrap | BuiltinId::UnwrapOr if success => {
let Value::Enum { data, .. } = &ctx.stack[ctx.base + recv] else {
return None;
};
let payload = data.lock().first().cloned();
if id == BuiltinId::UnwrapOr && argc == 1 {
discard(take(&mut ctx.stack[s]));
}
payload
}
BuiltinId::UnwrapOr if argc == 1 => Some(take(&mut ctx.stack[s])),
_ => None,
}
}
fn map_fast(
ctx: &mut StepCtx,
recv: usize,
id: BuiltinId,
s: usize,
argc: usize,
dst: u16,
) -> Option<Value> {
let base = ctx.base;
if !matches!(ctx.stack[base + recv], Value::Map(_, MapKind::Map))
|| argc < 1
|| base + recv >= s
{
return None;
}
let (lo, hi) = ctx.stack.split_at_mut(s);
let Value::Map(m, _) = &lo[base + recv] else {
unreachable!()
};
let k = hi[0].as_key()?;
Some(if id == BuiltinId::Insert {
let val = if argc > 1 {
take(&mut hi[1])
} else {
Value::Unit
};
let old = m.lock().insert(k, val);
if dst == u16::MAX {
Value::Unit
} else {
match old {
Some(old) => Value::some(old),
None => Value::none(),
}
}
} else if id == BuiltinId::ContainsKey {
Value::Bool(m.lock().get(&k).is_some())
} else {
match m.lock().get(&k).cloned() {
Some(v) => Value::some(v),
None => Value::none(),
}
})
}
pub(super) fn get_or_default(
ctx: &mut StepCtx,
dst: u16,
recv: u16,
key: u16,
default: u16,
) -> Result<Flow> {
let recv_v = ctx.get(recv).clone();
let key_v = ctx.get(key).clone();
let get = MethodName::builtin(BuiltinId::Get);
let opt = ctx.vm.eval_method(&recv_v, &get, &mut [key_v])?;
let v = match opt.some_payload() {
Some(found) => {
let unused = ctx.take(default);
if ctx.vm.has_drop {
ctx.vm.run_user_drop(unused)?;
}
found
}
None => ctx.get(default).clone(),
};
Ok(ctx.set(dst, v))
}