use std::mem::take;
use anyhow::{Result, bail};
use super::bytecode::{BuiltinId, MethodName};
use super::methods::make_ordering;
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 (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 name.id == BuiltinId::CloneFrom {
let src = ctx.stack[s..s + argc]
.first()
.cloned()
.unwrap_or(Value::Unit);
ctx.stack[base + recv] = src;
return Ok(ctx.set_opt(dst, Value::Unit));
}
if matches!(name.id, BuiltinId::Take)
&& argc == 0
&& matches!(&ctx.stack[base + recv], Value::Enum { enum_name, .. } if &**enum_name == "Option")
{
let old = take(&mut ctx.stack[base + recv]);
ctx.stack[base + recv] = Value::none();
return Ok(ctx.set_opt(dst, old));
}
if name.text == "replace"
&& argc == 1
&& matches!(&ctx.stack[base + recv], Value::Enum { enum_name, .. } if &**enum_name == "Option")
{
let new = ctx.stack[s..s + argc]
.first()
.cloned()
.unwrap_or(Value::Unit);
let old = take(&mut ctx.stack[base + recv]);
ctx.stack[base + recv] = Value::some(new);
return Ok(ctx.set_opt(dst, old));
}
if matches!(&*name.text, "make_ascii_uppercase" | "make_ascii_lowercase")
&& ascii_case_fast(ctx, recv, name)
{
return Ok(ctx.set_opt(dst, Value::Unit));
}
if matches!(name.id, BuiltinId::Push | BuiltinId::PushStr)
&& matches!(ctx.stack[base + recv], Value::Str(_))
{
let arg = ctx.stack[s..s + argc]
.first()
.cloned()
.unwrap_or(Value::Unit);
if let Value::Str(text) = &mut ctx.stack[base + recv] {
match (&name.id, &arg) {
(BuiltinId::Push, Value::Char(c)) => text.push(*c),
(BuiltinId::PushStr, Value::Str(other)) => text.push_str(other),
(BuiltinId::PushStr, other) => text.push_str(&other.display()),
_ => {}
}
}
return Ok(ctx.set_opt(dst, Value::Unit));
}
if vm.methods.is_empty()
&& matches!(
name.id,
BuiltinId::Copied | BuiltinId::Unwrap | BuiltinId::UnwrapOr
)
&& let Some(v) = option_fast(ctx, recv, name, s, argc)
{
return Ok(ctx.set_opt(dst, v));
}
if matches!(name.id, BuiltinId::ToString | BuiltinId::Clone)
&& let Value::Str(v) = &ctx.stack[base + recv]
{
let v = Value::Str(v.clone());
return Ok(ctx.set_opt(dst, v));
}
if let Some(v) = int_cmp_fast(ctx, recv, name, s, argc) {
return Ok(ctx.set_opt(dst, v));
}
if let Some(v) = map_fast(ctx, recv, name, s, argc, dst)? {
return Ok(ctx.set_opt(dst, v));
}
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 ascii_case_fast(ctx: &mut StepCtx, recv: usize, name: &MethodName) -> bool {
let upper = &*name.text == "make_ascii_uppercase";
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 false,
};
*slot = new;
true
}
fn int_cmp_fast(
ctx: &StepCtx,
recv: usize,
name: &MethodName,
s: usize,
argc: usize,
) -> Option<Value> {
if argc == 1
&& name.text == "cmp"
&& 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,
name: &MethodName,
s: usize,
argc: usize,
) -> Option<Value> {
let choice = match &ctx.stack[ctx.base + recv] {
Value::Enum {
enum_name, variant, ..
} => {
if matches!(name.id, BuiltinId::Copied) {
i32::from(&**enum_name == "Option")
} else if !matches!(&**enum_name, "Option" | "Result") {
0
} else if matches!(&**variant, "Some" | "Ok") {
2
} else if matches!(name.id, BuiltinId::UnwrapOr) {
3
} else {
0
}
}
_ => 0,
};
match choice {
1 => Some(ctx.stack[ctx.base + recv].clone()),
2 => match &ctx.stack[ctx.base + recv] {
Value::Enum { data, .. } => Some(data.lock().first().cloned().unwrap_or(Value::Unit)),
_ => unreachable!(),
},
3 => Some(if argc > 0 {
take(&mut ctx.stack[s])
} else {
Value::Unit
}),
_ => None,
}
}
fn map_fast(
ctx: &mut StepCtx,
recv: usize,
name: &MethodName,
s: usize,
argc: usize,
dst: u16,
) -> Result<Option<Value>> {
let base = ctx.base;
if !matches!(
name.id,
BuiltinId::Get | BuiltinId::Insert | BuiltinId::ContainsKey
) || !matches!(ctx.stack[base + recv], Value::Map(_, MapKind::Map))
|| argc < 1
|| base + recv >= s
|| name.text == "get_mut"
{
return Ok(None);
}
let (lo, hi) = ctx.stack.split_at_mut(s);
let Value::Map(m, _) = &lo[base + recv] else {
unreachable!()
};
let v = if name.id == BuiltinId::Insert {
let Some(k) = take(&mut hi[0]).into_key() else {
bail!("invalid map key")
};
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 {
let Some(k) = hi[0].as_key() else {
bail!("invalid map key")
};
if matches!(name.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(),
}
}
};
Ok(Some(v))
}
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 {
text: "get".into(),
id: BuiltinId::Get,
scalar: None,
};
let opt = ctx.vm.eval_method(&recv_v, &get, &mut [key_v])?;
let v = match opt {
Value::Enum { variant, data, .. } if &*variant == "Some" => {
data.lock().first().cloned().unwrap_or(Value::Unit)
}
_ => ctx.get(default).clone(),
};
Ok(ctx.set(dst, v))
}