use std::cell::Cell;
use std::collections::HashMap;
use crate::evaluator::{eval_compiled, CompiledExpr, EvaluatorError};
use crate::value::JValue;
#[derive(Debug, Clone)]
pub(crate) enum Instr {
PushConst(u16),
PushExplicitNull,
PushUndefined,
PushData,
GetField(u16),
GetDataField(u16),
GetVar(u16),
GetVarField {
var_idx: u16,
field_idx: u16,
},
Add(bool, bool),
Sub(bool, bool),
Mul(bool, bool),
Div(bool, bool),
Mod(bool, bool),
CmpEq,
CmpNe,
CmpLt(bool, bool),
CmpLe(bool, bool),
CmpGt(bool, bool),
CmpGe(bool, bool),
#[allow(dead_code)]
And,
#[allow(dead_code)]
Or,
Not,
Negate,
Concat,
Jump(i16),
JumpIfFalsy(i16),
JumpIfTruthy(i16),
JumpIfUndef(i16),
Pop,
MakeArray(u16),
MakeObject(u16),
BlockEnd(u16),
CallBuiltin {
name_idx: u16,
arg_count: u8,
},
FilterByBytecode(u16),
EvalFallback(u16),
Return,
}
#[derive(Debug, Clone)]
pub(crate) struct BytecodeProgram {
pub instrs: Vec<Instr>,
pub const_pool: Vec<JValue>,
pub string_pool: Vec<String>,
pub shape_cache: Vec<Cell<Option<usize>>>,
pub fallback_exprs: Vec<CompiledExpr>,
pub sub_programs: Vec<BytecodeProgram>,
}
impl BytecodeProgram {
pub(crate) fn alloc_shape_cache(n: usize) -> Vec<Cell<Option<usize>>> {
(0..n).map(|_| Cell::new(None)).collect()
}
}
pub(crate) struct Vm<'prog> {
prog: &'prog BytecodeProgram,
stack: Vec<JValue>,
}
impl<'prog> Vm<'prog> {
pub(crate) fn new(prog: &'prog BytecodeProgram) -> Self {
Vm {
prog,
stack: Vec::with_capacity(16),
}
}
#[inline]
pub(crate) fn run(
&mut self,
data: &JValue,
vars: Option<&HashMap<&str, &JValue>>,
) -> Result<JValue, EvaluatorError> {
run_inner(self.prog, data, vars, &mut self.stack)
}
}
fn run_inner(
prog: &BytecodeProgram,
data: &JValue,
vars: Option<&HashMap<&str, &JValue>>,
stack: &mut Vec<JValue>,
) -> Result<JValue, EvaluatorError> {
use crate::evaluator::{
call_pure_builtin_by_name, compiled_arithmetic, compiled_concat, compiled_equal,
compiled_is_truthy, compiled_ordered_cmp, CompiledArithOp,
};
let instrs = &prog.instrs;
let const_pool = &prog.const_pool;
let string_pool = &prog.string_pool;
let shape_cache = &prog.shape_cache;
let fallback_exprs = &prog.fallback_exprs;
let sub_programs = &prog.sub_programs;
let mut ip: usize = 0;
loop {
match &instrs[ip] {
Instr::PushConst(idx) => {
stack.push(const_pool[*idx as usize].clone());
ip += 1;
}
Instr::PushExplicitNull => {
stack.push(JValue::Null);
ip += 1;
}
Instr::PushUndefined => {
stack.push(JValue::Undefined);
ip += 1;
}
Instr::PushData => {
stack.push(data.clone());
ip += 1;
}
Instr::GetField(idx) => {
let val = stack.pop().unwrap_or(JValue::Undefined);
let field = &string_pool[*idx as usize];
stack.push(get_field_cached(&val, field, &shape_cache[ip]));
ip += 1;
}
Instr::GetDataField(idx) => {
let field = &string_pool[*idx as usize];
stack.push(get_field_cached(data, field, &shape_cache[ip]));
ip += 1;
}
Instr::GetVar(idx) => {
let name = &string_pool[*idx as usize];
let v = match vars {
Some(m) => m.get(name.as_str()).copied().cloned(),
None => None,
}
.unwrap_or(JValue::Undefined);
stack.push(v);
ip += 1;
}
Instr::GetVarField { var_idx, field_idx } => {
let var_name = &string_pool[*var_idx as usize];
let obj = match vars {
Some(m) => m.get(var_name.as_str()).copied().cloned(),
None => None,
}
.unwrap_or(JValue::Undefined);
let field = &string_pool[*field_idx as usize];
stack.push(get_field_cached(&obj, field, &shape_cache[ip]));
ip += 1;
}
Instr::Add(lhs_en, rhs_en) => {
let rhs = stack.pop().unwrap_or(JValue::Undefined);
let lhs = stack.pop().unwrap_or(JValue::Undefined);
stack.push(compiled_arithmetic(
CompiledArithOp::Add,
&lhs,
&rhs,
*lhs_en,
*rhs_en,
)?);
ip += 1;
}
Instr::Sub(lhs_en, rhs_en) => {
let rhs = stack.pop().unwrap_or(JValue::Undefined);
let lhs = stack.pop().unwrap_or(JValue::Undefined);
stack.push(compiled_arithmetic(
CompiledArithOp::Sub,
&lhs,
&rhs,
*lhs_en,
*rhs_en,
)?);
ip += 1;
}
Instr::Mul(lhs_en, rhs_en) => {
let rhs = stack.pop().unwrap_or(JValue::Undefined);
let lhs = stack.pop().unwrap_or(JValue::Undefined);
stack.push(compiled_arithmetic(
CompiledArithOp::Mul,
&lhs,
&rhs,
*lhs_en,
*rhs_en,
)?);
ip += 1;
}
Instr::Div(lhs_en, rhs_en) => {
let rhs = stack.pop().unwrap_or(JValue::Undefined);
let lhs = stack.pop().unwrap_or(JValue::Undefined);
stack.push(compiled_arithmetic(
CompiledArithOp::Div,
&lhs,
&rhs,
*lhs_en,
*rhs_en,
)?);
ip += 1;
}
Instr::Mod(lhs_en, rhs_en) => {
let rhs = stack.pop().unwrap_or(JValue::Undefined);
let lhs = stack.pop().unwrap_or(JValue::Undefined);
stack.push(compiled_arithmetic(
CompiledArithOp::Mod,
&lhs,
&rhs,
*lhs_en,
*rhs_en,
)?);
ip += 1;
}
Instr::CmpEq => {
let rhs = stack.pop().unwrap_or(JValue::Undefined);
let lhs = stack.pop().unwrap_or(JValue::Undefined);
stack.push(compiled_equal(&lhs, &rhs));
ip += 1;
}
Instr::CmpNe => {
let rhs = stack.pop().unwrap_or(JValue::Undefined);
let lhs = stack.pop().unwrap_or(JValue::Undefined);
let eq = compiled_equal(&lhs, &rhs);
let ne = match eq {
JValue::Bool(b) => JValue::Bool(!b),
other => other,
};
stack.push(ne);
ip += 1;
}
Instr::CmpLt(lhs_en, rhs_en) => {
let rhs = stack.pop().unwrap_or(JValue::Undefined);
let lhs = stack.pop().unwrap_or(JValue::Undefined);
stack.push(compiled_ordered_cmp(
&lhs,
&rhs,
*lhs_en,
*rhs_en,
|a, b| a < b,
|a, b| a < b,
)?);
ip += 1;
}
Instr::CmpLe(lhs_en, rhs_en) => {
let rhs = stack.pop().unwrap_or(JValue::Undefined);
let lhs = stack.pop().unwrap_or(JValue::Undefined);
stack.push(compiled_ordered_cmp(
&lhs,
&rhs,
*lhs_en,
*rhs_en,
|a, b| a <= b,
|a, b| a <= b,
)?);
ip += 1;
}
Instr::CmpGt(lhs_en, rhs_en) => {
let rhs = stack.pop().unwrap_or(JValue::Undefined);
let lhs = stack.pop().unwrap_or(JValue::Undefined);
stack.push(compiled_ordered_cmp(
&lhs,
&rhs,
*lhs_en,
*rhs_en,
|a, b| a > b,
|a, b| a > b,
)?);
ip += 1;
}
Instr::CmpGe(lhs_en, rhs_en) => {
let rhs = stack.pop().unwrap_or(JValue::Undefined);
let lhs = stack.pop().unwrap_or(JValue::Undefined);
stack.push(compiled_ordered_cmp(
&lhs,
&rhs,
*lhs_en,
*rhs_en,
|a, b| a >= b,
|a, b| a >= b,
)?);
ip += 1;
}
Instr::And => {
let rhs = stack.pop().unwrap_or(JValue::Undefined);
let lhs = stack.pop().unwrap_or(JValue::Undefined);
if !compiled_is_truthy(&lhs) {
stack.push(JValue::Bool(false));
} else {
stack.push(JValue::Bool(compiled_is_truthy(&rhs)));
}
ip += 1;
}
Instr::Or => {
let rhs = stack.pop().unwrap_or(JValue::Undefined);
let lhs = stack.pop().unwrap_or(JValue::Undefined);
if compiled_is_truthy(&lhs) {
stack.push(JValue::Bool(true));
} else {
stack.push(JValue::Bool(compiled_is_truthy(&rhs)));
}
ip += 1;
}
Instr::Not => {
let val = stack.pop().unwrap_or(JValue::Undefined);
stack.push(JValue::Bool(!compiled_is_truthy(&val)));
ip += 1;
}
Instr::Negate => {
let val = stack.pop().unwrap_or(JValue::Undefined);
match val {
JValue::Number(n) => stack.push(JValue::Number(-n)),
JValue::Undefined => stack.push(JValue::Undefined),
_ => {
return Err(EvaluatorError::TypeError(
"D1002: Cannot negate non-number value".to_string(),
))
}
}
ip += 1;
}
Instr::Concat => {
let rhs = stack.pop().unwrap_or(JValue::Undefined);
let lhs = stack.pop().unwrap_or(JValue::Undefined);
stack.push(compiled_concat(lhs, rhs)?);
ip += 1;
}
Instr::Jump(offset) => {
ip = (ip as isize + 1 + *offset as isize) as usize;
}
Instr::JumpIfFalsy(offset) => {
let val = stack.pop().unwrap_or(JValue::Undefined);
if !compiled_is_truthy(&val) {
ip = (ip as isize + 1 + *offset as isize) as usize;
} else {
ip += 1;
}
}
Instr::JumpIfTruthy(offset) => {
let val = stack.pop().unwrap_or(JValue::Undefined);
if compiled_is_truthy(&val) {
ip = (ip as isize + 1 + *offset as isize) as usize;
} else {
ip += 1;
}
}
Instr::JumpIfUndef(offset) => {
let is_undef = stack.last().map_or(true, |v| v.is_undefined());
if is_undef {
ip = (ip as isize + 1 + *offset as isize) as usize;
} else {
ip += 1;
}
}
Instr::Pop => {
stack.pop();
ip += 1;
}
Instr::MakeArray(n) => {
let n = *n as usize;
let start = stack.len().saturating_sub(n);
let raw: Vec<JValue> = stack.drain(start..).collect();
let mut elems: Vec<JValue> = Vec::with_capacity(raw.len());
for v in raw {
match v {
JValue::Undefined => {}
JValue::Array(arr) => elems.extend(arr.iter().cloned()),
other => elems.push(other),
}
}
stack.push(JValue::array(elems));
ip += 1;
}
Instr::MakeObject(n) => {
let n = *n as usize;
let start = stack.len().saturating_sub(n * 2);
let pairs: Vec<JValue> = stack.drain(start..).collect();
let mut obj = indexmap::IndexMap::new();
for chunk in pairs.chunks(2) {
if let [JValue::String(k), val] = chunk {
if !val.is_undefined() {
obj.insert(k.to_string(), val.clone());
}
}
}
stack.push(JValue::Object(std::rc::Rc::new(obj)));
ip += 1;
}
Instr::BlockEnd(n) => {
let n = *n as usize;
if n > 1 && stack.len() >= n {
let last = stack.pop().unwrap();
let start = stack.len().saturating_sub(n - 1);
stack.drain(start..);
stack.push(last);
}
ip += 1;
}
Instr::CallBuiltin {
name_idx,
arg_count,
} => {
let name = &string_pool[*name_idx as usize];
let n = *arg_count as usize;
let start = stack.len().saturating_sub(n);
let args: Vec<JValue> = stack.drain(start..).collect();
stack.push(call_pure_builtin_by_name(name, &args, data)?);
ip += 1;
}
Instr::FilterByBytecode(idx) => {
let sub_prog = &sub_programs[*idx as usize];
let src = stack.pop().unwrap_or(JValue::Undefined);
let mut sub_stack: Vec<JValue> = Vec::with_capacity(8);
let result = match src {
JValue::Array(arr) => {
let mut kept: Vec<JValue> = Vec::with_capacity(arr.len());
for item in arr.iter() {
let test = run_inner(sub_prog, item, vars, &mut sub_stack)?;
if compiled_is_truthy(&test) {
kept.push(item.clone());
}
}
match kept.len() {
0 => JValue::Undefined,
1 => kept.pop().unwrap(),
_ => JValue::array(kept),
}
}
JValue::Undefined => JValue::Undefined,
other => {
let test = run_inner(sub_prog, &other, vars, &mut sub_stack)?;
if compiled_is_truthy(&test) {
other
} else {
JValue::Undefined
}
}
};
stack.push(result);
ip += 1;
}
Instr::EvalFallback(idx) => {
let expr = &fallback_exprs[*idx as usize];
stack.push(eval_compiled(expr, data, vars)?);
ip += 1;
}
Instr::Return => {
break;
}
}
}
Ok(stack.pop().unwrap_or(JValue::Undefined))
}
#[inline]
fn get_field_cached(val: &JValue, field: &str, cache: &Cell<Option<usize>>) -> JValue {
match val {
JValue::Object(obj) => {
if let Some(idx) = cache.get() {
if let Some(v) = obj.get_index(idx) {
if v.0 == field {
return v.1.clone();
}
}
}
if let Some(v) = obj.get(field) {
if let Some(idx) = obj.get_index_of(field) {
cache.set(Some(idx));
}
v.clone()
} else {
JValue::Undefined
}
}
JValue::Array(arr) => {
let mut results: Vec<JValue> = Vec::with_capacity(arr.len());
for item in arr.iter() {
let v = get_field_cached(item, field, cache);
match v {
JValue::Undefined => {}
JValue::Array(inner) => results.extend(inner.iter().cloned()),
other => results.push(other),
}
}
match results.len() {
0 => JValue::Undefined,
1 => results.pop().unwrap(),
_ => JValue::array(results),
}
}
_ => JValue::Undefined,
}
}