use std::collections::{HashMap, VecDeque};
use std::fmt::Display;
use std::sync::Arc;
use compact_str::{CompactString, ToCompactString};
use indexmap::IndexMap;
use num_traits::ToPrimitive;
use crate::interpreter::RuntimeErrorKind;
use crate::list::NumbatList;
use crate::prefix_transformer::Transformer;
use crate::span::Span;
use crate::typechecker::TypeChecker;
use crate::typechecker::type_scheme::TypeScheme;
use crate::typed_ast::StructInfo;
use crate::{
ffi::{self, Arg, ArityRange, Callable, ForeignFunction},
interpreter::{InterpreterResult, PrintFunction, Result, RuntimeError},
markup::Markup,
math,
number::Number,
prefix::Prefix,
pretty_print::FormatOptions,
quantity::{Quantity, QuantityError},
unit::Unit,
unit_registry::{UnitMetadata, UnitRegistry},
value::{FunctionReference, Value},
};
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
#[repr(u8)]
pub enum Op {
LoadConstant,
ApplyPrefix,
SetUnitConstant,
GetLocal,
GetUpvalue,
GetLastResult,
Negate,
Factorial,
Add,
Subtract,
Multiply,
Divide,
Power,
ConvertTo,
LessThan,
GreaterThan,
LessOrEqual,
GreatorOrEqual,
Equal,
NotEqual,
LogicalAnd,
LogicalOr,
LogicalNeg,
AddToDateTime,
SubFromDateTime,
DiffDateTime,
JumpIfFalse,
Jump,
Call,
FFICallFunction,
FFICallProcedure,
CallCallable,
PrintString,
JoinString,
BuildStructInstance,
AccessStructField,
BuildList,
Return,
}
impl Op {
fn num_operands(self) -> usize {
match self {
Op::FFICallProcedure => 3,
Op::SetUnitConstant | Op::Call | Op::FFICallFunction | Op::BuildStructInstance => 2,
Op::LoadConstant
| Op::ApplyPrefix
| Op::GetLocal
| Op::GetUpvalue
| Op::PrintString
| Op::JoinString
| Op::JumpIfFalse
| Op::Jump
| Op::CallCallable
| Op::AccessStructField
| Op::BuildList => 1,
Op::Negate
| Op::Factorial
| Op::Add
| Op::AddToDateTime
| Op::Subtract
| Op::SubFromDateTime
| Op::DiffDateTime
| Op::Multiply
| Op::Divide
| Op::Power
| Op::ConvertTo
| Op::LessThan
| Op::GreaterThan
| Op::LessOrEqual
| Op::GreatorOrEqual
| Op::Equal
| Op::NotEqual
| Op::LogicalAnd
| Op::LogicalOr
| Op::LogicalNeg
| Op::Return
| Op::GetLastResult => 0,
}
}
fn to_string(self) -> &'static str {
match self {
Op::LoadConstant => "LoadConstant",
Op::ApplyPrefix => "ApplyPrefix",
Op::SetUnitConstant => "SetUnitConstant",
Op::GetLocal => "GetLocal",
Op::GetUpvalue => "GetUpvalue",
Op::GetLastResult => "GetLastResult",
Op::Negate => "Negate",
Op::Factorial => "Factorial",
Op::Add => "Add",
Op::AddToDateTime => "AddDateTime",
Op::Subtract => "Subtract",
Op::SubFromDateTime => "SubDateTime",
Op::DiffDateTime => "DiffDateTime",
Op::Multiply => "Multiply",
Op::Divide => "Divide",
Op::Power => "Power",
Op::ConvertTo => "ConvertTo",
Op::LessThan => "LessThan",
Op::GreaterThan => "GreaterThan",
Op::LessOrEqual => "LessOrEqual",
Op::GreatorOrEqual => "GreatorOrEqual",
Op::Equal => "Equal",
Op::NotEqual => "NotEqual",
Op::LogicalAnd => "LogicalAnd",
Op::LogicalOr => "LogicalOr",
Op::LogicalNeg => "LogicalNeg",
Op::JumpIfFalse => "JumpIfFalse",
Op::Jump => "Jump",
Op::Call => "Call",
Op::FFICallFunction => "FFICallFunction",
Op::FFICallProcedure => "FFICallProcedure",
Op::CallCallable => "CallCallable",
Op::PrintString => "PrintString",
Op::JoinString => "JoinString",
Op::Return => "Return",
Op::BuildStructInstance => "BuildStructInstance",
Op::AccessStructField => "AccessStructField",
Op::BuildList => "BuildList",
}
}
}
#[derive(Clone, Debug)]
pub enum Constant {
Scalar(f64),
Unit(Unit),
Boolean(bool),
String(CompactString),
FunctionReference(FunctionReference),
FormatSpecifiers(Option<CompactString>),
}
impl Constant {
fn to_value(&self) -> Value {
match self {
Constant::Scalar(n) => Value::Quantity(Quantity::from_scalar(*n)),
Constant::Unit(u) => Value::Quantity(Quantity::from_unit(u.clone())),
Constant::Boolean(b) => Value::Boolean(*b),
Constant::String(s) => Value::String(s.clone()),
Constant::FunctionReference(inner) => Value::FunctionReference(inner.clone()),
Constant::FormatSpecifiers(s) => Value::FormatSpecifiers(s.clone()),
}
}
}
impl Display for Constant {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
match self {
Constant::Scalar(n) => write!(f, "{n}"),
Constant::Unit(unit) => write!(f, "{unit}"),
Constant::Boolean(val) => write!(f, "{val}"),
Constant::String(val) => write!(f, "\"{val}\""),
Constant::FunctionReference(inner) => write!(f, "{inner}"),
Constant::FormatSpecifiers(_) => write!(f, "<format specfiers>"),
}
}
}
#[derive(Clone)]
struct CallFrame {
function_idx: usize,
ip: usize,
fp: usize,
}
impl CallFrame {
fn root() -> Self {
CallFrame {
function_idx: 0,
ip: 0,
fp: 0,
}
}
}
pub struct ExecutionContext<'a> {
pub print_fn: &'a mut PrintFunction,
pub unit_name_to_constant_idx: &'a HashMap<CompactString, u16>,
pub prefix_transformer: &'a Transformer,
pub typechecker: &'a TypeChecker,
}
#[derive(Clone)]
pub struct FfiCallArg {
pub span: Span,
pub type_: TypeScheme,
}
#[derive(Clone)]
pub struct FfiCallArgs {
pub args: Vec<FfiCallArg>,
pub return_type: Option<TypeScheme>,
}
#[derive(Clone)]
pub struct Vm {
bytecode: Vec<(CompactString, Vec<u8>, Vec<Span>)>,
current_chunk_index: usize,
pub constants: Vec<Constant>,
struct_infos: IndexMap<CompactString, Arc<StructInfo>>,
prefixes: Vec<Prefix>,
strings: Vec<Markup>,
unit_information: Vec<(CompactString, UnitMetadata)>,
last_result: Option<Value>,
ffi_callables: IndexMap<&'static str, &'static ForeignFunction>,
ffi_call_args: Vec<FfiCallArgs>,
frames: Vec<CallFrame>,
stack: Vec<Value>,
debug: bool,
pub unit_registry: UnitRegistry,
}
impl Vm {
pub fn new() -> Self {
Self {
bytecode: vec![("<main>".into(), vec![], vec![])],
current_chunk_index: 0,
constants: vec![],
struct_infos: IndexMap::new(),
prefixes: vec![],
strings: vec![],
unit_information: vec![],
last_result: None,
ffi_callables: ffi::procedures()
.iter()
.map(|(kind, ff)| (kind.name(), ff))
.collect(),
ffi_call_args: vec![],
frames: vec![CallFrame::root()],
stack: vec![],
debug: false,
unit_registry: UnitRegistry::new(),
}
}
pub fn set_debug(&mut self, activate: bool) {
self.debug = activate;
}
pub(crate) fn runtime_error(&self, kind: RuntimeErrorKind) -> RuntimeError {
RuntimeError {
kind,
backtrace: self.backtrace(),
}
}
pub fn backtrace(&self) -> Vec<(CompactString, Span)> {
self.frames
.iter()
.rev()
.map(|cf| {
(
self.bytecode[cf.function_idx].0.clone(),
self.bytecode[cf.function_idx].2[cf.ip.saturating_sub(1)],
)
})
.collect()
}
fn current_chunk_mut(&mut self) -> (&mut Vec<u8>, &mut Vec<Span>) {
let current = &mut self.bytecode[self.current_chunk_index];
(&mut current.1, &mut current.2)
}
fn push_u16(chunk: &mut Vec<u8>, data: u16) {
let arg_bytes = data.to_le_bytes();
chunk.push(arg_bytes[0]);
chunk.push(arg_bytes[1]);
}
pub fn add_op(&mut self, op: Op, span: Span) {
let (bytecode, spans) = self.current_chunk_mut();
bytecode.push(op as u8);
spans.push(span);
}
pub fn add_op1(&mut self, op: Op, arg: u16, span: Span) {
let (bytecode, spans) = self.current_chunk_mut();
bytecode.push(op as u8);
Self::push_u16(bytecode, arg);
spans.extend(std::iter::repeat_n(span, 3));
}
pub(crate) fn add_op2(&mut self, op: Op, arg1: u16, arg2: u16, span: Span) {
let (bytecode, spans) = self.current_chunk_mut();
bytecode.push(op as u8);
Self::push_u16(bytecode, arg1);
Self::push_u16(bytecode, arg2);
spans.extend(std::iter::repeat_n(span, 5));
}
pub(crate) fn add_op3(&mut self, op: Op, arg1: u16, arg2: u16, arg3: u16, span: Span) {
let (bytecode, spans) = self.current_chunk_mut();
bytecode.push(op as u8);
Self::push_u16(bytecode, arg1);
Self::push_u16(bytecode, arg2);
Self::push_u16(bytecode, arg3);
spans.extend(std::iter::repeat_n(span, 7));
}
pub fn current_offset(&self) -> u16 {
self.bytecode[self.current_chunk_index].1.len() as u16
}
pub fn patch_u16_value_at(&mut self, offset: u16, arg: u16) {
let offset = offset as usize;
let (bytecode, _spans) = self.current_chunk_mut();
bytecode[offset] = (arg & 0xff) as u8;
bytecode[offset + 1] = ((arg >> 8) & 0xff) as u8;
}
pub fn add_constant(&mut self, constant: Constant) -> u16 {
self.constants.push(constant);
assert!(self.constants.len() <= u16::MAX as usize);
(self.constants.len() - 1) as u16 }
pub fn add_struct_info(&mut self, struct_info: &StructInfo) -> usize {
let e = self.struct_infos.entry(struct_info.name.clone());
let idx = e.index();
e.or_insert_with(|| Arc::new(struct_info.clone()));
idx
}
pub fn get_structinfo_idx(&self, name: &str) -> Option<usize> {
self.struct_infos.get_index_of(name)
}
pub fn add_prefix(&mut self, prefix: Prefix) -> u16 {
if let Some(idx) = self.prefixes.iter().position(|p| p == &prefix) {
idx as u16
} else {
self.prefixes.push(prefix);
assert!(self.constants.len() <= u16::MAX as usize);
(self.prefixes.len() - 1) as u16 }
}
pub fn add_unit_information(&mut self, unit_name: &str, metadata: UnitMetadata) -> u16 {
if let Some(idx) = self
.unit_information
.iter()
.position(|(name, _)| name == unit_name)
{
return idx as u16;
}
self.unit_information
.push((unit_name.to_compact_string(), metadata));
assert!(self.unit_information.len() <= u16::MAX as usize);
(self.unit_information.len() - 1) as u16 }
pub(crate) fn begin_function(&mut self, name: &str) {
self.bytecode.push((name.into(), vec![], vec![]));
self.current_chunk_index = self.bytecode.len() - 1
}
pub(crate) fn end_function(&mut self) {
self.current_chunk_index = 0;
}
pub(crate) fn get_function_idx(&self, name: &str) -> u16 {
let rev_position = self
.bytecode
.iter()
.rev()
.position(|(n, _, _)| n == name)
.unwrap();
let position = self.bytecode.len() - 1 - rev_position;
assert!(position <= u16::MAX as usize);
position as u16
}
pub(crate) fn add_foreign_function(&mut self, name: &str, arity: ArityRange) {
let (key, ff) = ffi::functions().get_key_value(name).unwrap();
assert!(ff.arity == arity);
self.ffi_callables.insert(key, ff);
}
pub(crate) fn get_ffi_callable_idx(&self, name: &str) -> Option<u16> {
let position = self.ffi_callables.get_index_of(name)?;
assert!(position <= u16::MAX as usize);
Some(position as u16)
}
pub fn simplify_quantity(
&self,
q: &Quantity,
unit_name_to_constant_idx: &HashMap<CompactString, u16>,
) -> Quantity {
q.full_simplify_with_registry(&self.unit_registry, |name| {
unit_name_to_constant_idx
.get(name)
.and_then(|idx| self.constants.get(*idx as usize))
.and_then(|constant| match constant {
Constant::Unit(u) => Some(u.clone()),
_ => None,
})
})
}
pub(crate) fn add_ffi_call_args(&mut self, call_args: FfiCallArgs) -> u16 {
let idx = self.ffi_call_args.len();
self.ffi_call_args.push(call_args);
assert!(self.ffi_call_args.len() <= u16::MAX as usize);
idx as u16
}
pub fn disassemble(&self) {
if !self.debug {
return;
}
eprintln!();
eprintln!(".CONSTANTS");
for (idx, constant) in self.constants.iter().enumerate() {
eprintln!(" {idx:04} {constant}");
}
eprintln!(".IDENTIFIERS");
for (idx, identifier) in self.unit_information.iter().enumerate() {
eprintln!(" {:04} {}", idx, identifier.0);
}
for (idx, (function_name, bytecode, _spans)) in self.bytecode.iter().enumerate() {
eprintln!(".CODE {idx} ({function_name})");
let mut offset = 0;
while offset < bytecode.len() {
let this_offset = offset;
let op = bytecode[offset];
offset += 1;
let op = unsafe { std::mem::transmute::<u8, Op>(op) };
let mut operands: Vec<u16> = vec![];
for _ in 0..op.num_operands() {
let operand =
u16::from_le_bytes(bytecode[offset..(offset + 2)].try_into().unwrap());
operands.push(operand);
offset += 2;
}
let operands_str = operands
.iter()
.map(u16::to_compact_string)
.collect::<Vec<_>>()
.join(" ");
eprint!(
" {:04} {:<13} {}",
this_offset,
op.to_string(),
operands_str,
);
if op == Op::LoadConstant {
eprint!(" (value: {})", self.constants[operands[0] as usize]);
} else if op == Op::Call {
eprint!(
" ({}, num_args={})",
self.bytecode[operands[0] as usize].0, operands[1] as usize
);
}
eprintln!();
}
}
eprintln!();
}
fn current_frame(&self) -> &CallFrame {
self.frames.last().expect("Call stack is not empty")
}
fn current_frame_mut(&mut self) -> &mut CallFrame {
self.frames.last_mut().expect("Call stack is not empty")
}
fn read_byte(&mut self) -> u8 {
let frame = self.current_frame();
let byte = self.bytecode[frame.function_idx].1[frame.ip];
self.current_frame_mut().ip += 1;
byte
}
fn read_u16(&mut self) -> u16 {
let bytes = [self.read_byte(), self.read_byte()];
u16::from_le_bytes(bytes)
}
fn push_quantity(&mut self, quantity: Quantity) {
self.stack.push(Value::Quantity(quantity));
}
fn push_bool(&mut self, boolean: bool) {
self.stack.push(Value::Boolean(boolean));
}
fn push(&mut self, value: Value) {
self.stack.push(value);
}
#[track_caller]
fn pop_quantity(&mut self) -> Quantity {
match self.pop() {
Value::Quantity(q) => q,
_ => panic!("Expected quantity to be on the top of the stack"),
}
}
#[track_caller]
fn pop_bool(&mut self) -> bool {
self.pop().unsafe_as_bool()
}
#[track_caller]
fn pop_datetime(&mut self) -> jiff::Zoned {
match self.pop() {
Value::DateTime(q) => q,
_ => panic!("Expected datetime to be on the top of the stack"),
}
}
#[track_caller]
fn pop(&mut self) -> Value {
self.stack.pop().expect("stack should not be empty")
}
pub fn run(&mut self, ctx: &mut ExecutionContext) -> Result<InterpreterResult> {
let old_stack = self.stack.clone();
let result = self.run_without_cleanup(ctx);
if result.is_err() {
self.stack = old_stack;
self.frames.clear();
self.frames.push(CallFrame::root());
self.frames[0].ip = self.bytecode[0].1.len();
}
result
}
fn is_at_the_end(&self) -> bool {
self.current_frame().ip >= self.bytecode[self.current_frame().function_idx].1.len()
}
fn run_without_cleanup(&mut self, ctx: &mut ExecutionContext) -> Result<InterpreterResult> {
let mut result_last_statement = None;
while !self.is_at_the_end() {
self.debug();
let op = unsafe { std::mem::transmute::<u8, Op>(self.read_byte()) };
match op {
Op::LoadConstant => {
let constant_idx = self.read_u16();
self.stack
.push(self.constants[constant_idx as usize].to_value());
}
Op::ApplyPrefix => {
let quantity = self.pop_quantity();
let prefix_idx = self.read_u16();
let prefix = self.prefixes[prefix_idx as usize];
self.push_quantity(Quantity::new(
*quantity.unsafe_value(),
quantity.unit().clone().with_prefix(prefix),
));
}
Op::SetUnitConstant => {
let unit_information_idx = self.read_u16();
let constant_idx = self.read_u16();
let conversion_value = self.pop_quantity();
let (unit_name, metadata) =
&self.unit_information[unit_information_idx as usize];
let defining_unit = conversion_value.unit();
let (base_unit_representation, _) = defining_unit.to_base_unit_representation();
self.unit_registry
.add_derived_unit(unit_name, &base_unit_representation, metadata.clone())
.map_err(|e| self.runtime_error(RuntimeErrorKind::UnitRegistryError(e)))?;
self.constants[constant_idx as usize] = Constant::Unit(Unit::new_derived(
unit_name.clone(),
metadata.canonical_name.clone(),
*conversion_value.unsafe_value(),
defining_unit.clone(),
));
}
Op::GetLocal => {
let slot_idx = self.read_u16() as usize;
let stack_idx = self.current_frame().fp + slot_idx;
self.push(self.stack[stack_idx].clone());
}
Op::GetUpvalue => {
let stack_idx = self.read_u16() as usize;
self.push(self.stack[stack_idx].clone());
}
Op::GetLastResult => {
self.push(self.last_result.as_ref().unwrap().clone());
}
op @ (Op::Add
| Op::Subtract
| Op::Multiply
| Op::Divide
| Op::Power
| Op::ConvertTo) => {
let rhs = self.pop_quantity();
let lhs = self.pop_quantity();
let result = match op {
Op::Add => &lhs + &rhs,
Op::Subtract => &lhs - &rhs,
Op::Multiply => Ok(lhs * rhs),
Op::Divide => Ok(lhs
.checked_div(rhs)
.ok_or_else(|| self.runtime_error(RuntimeErrorKind::DivisionByZero))?),
Op::Power => Ok(lhs
.checked_power(rhs)
.map_err(|e| self.runtime_error(RuntimeErrorKind::QuantityError(e)))?
.ok_or_else(|| self.runtime_error(RuntimeErrorKind::DivisionByZero))?),
Op::ConvertTo => lhs
.convert_to(rhs.unit())
.map(|q| q.no_simplify().with_conversion_target(rhs)),
_ => unreachable!(),
};
self.push_quantity(
result
.map_err(|e| self.runtime_error(RuntimeErrorKind::QuantityError(e)))?,
);
}
op @ (Op::AddToDateTime | Op::SubFromDateTime) => {
let rhs = self.pop_quantity();
let lhs = self.pop_datetime();
let base = rhs.to_base_unit_representation();
let seconds_f64 = base.unsafe_value().to_f64();
let seconds_i64 = seconds_f64
.to_i64()
.ok_or_else(|| self.runtime_error(RuntimeErrorKind::DurationOutOfRange))?;
let span = jiff::Span::new()
.try_seconds(seconds_i64)
.map_err(|_| self.runtime_error(RuntimeErrorKind::DurationOutOfRange))?
.nanoseconds((seconds_f64.fract() * 1_000_000_000f64).round() as i64);
self.push(Value::DateTime(match op {
Op::AddToDateTime => lhs.checked_add(span).map_err(|_| {
self.runtime_error(RuntimeErrorKind::DateTimeOutOfRange)
})?,
Op::SubFromDateTime => lhs.checked_sub(span).map_err(|_| {
self.runtime_error(RuntimeErrorKind::DateTimeOutOfRange)
})?,
_ => unreachable!(),
}));
}
Op::DiffDateTime => {
let unit = self.pop_quantity();
let rhs = self.pop_datetime();
let lhs = self.pop_datetime();
let duration = lhs
.since(&rhs)
.map_err(|_| self.runtime_error(RuntimeErrorKind::DateTimeOutOfRange))?;
let duration = duration
.total(jiff::Unit::Second)
.map_err(|_| self.runtime_error(RuntimeErrorKind::DurationOutOfRange))?;
let ret = Value::Quantity(Quantity::new(
Number::from_f64(duration),
unit.unit().clone(),
));
self.push(ret);
}
op @ (Op::LessThan | Op::GreaterThan | Op::LessOrEqual | Op::GreatorOrEqual) => {
use crate::quantity::QuantityOrdering;
use std::cmp::Ordering;
let rhs = self.pop_quantity();
let lhs = self.pop_quantity();
let result = match lhs.partial_cmp_preserve_nan(&rhs) {
QuantityOrdering::IncompatibleUnits => {
return Err(Box::new(self.runtime_error(
RuntimeErrorKind::QuantityError(QuantityError::IncompatibleUnits(
lhs.unit().clone(),
rhs.unit().clone(),
)),
)));
}
QuantityOrdering::NanOperand => false,
QuantityOrdering::Ok(Ordering::Less) => {
matches!(op, Op::LessThan | Op::LessOrEqual)
}
QuantityOrdering::Ok(Ordering::Equal) => {
matches!(op, Op::LessOrEqual | Op::GreatorOrEqual)
}
QuantityOrdering::Ok(Ordering::Greater) => {
matches!(op, Op::GreaterThan | Op::GreatorOrEqual)
}
};
self.push(Value::Boolean(result));
}
op @ (Op::Equal | Op::NotEqual) => {
let rhs = self.pop();
let lhs = self.pop();
let result = match op {
Op::Equal => lhs == rhs,
Op::NotEqual => lhs != rhs,
_ => unreachable!(),
};
self.push(Value::Boolean(result));
}
op @ (Op::LogicalAnd | Op::LogicalOr) => {
let rhs = self.pop_bool();
let lhs = self.pop_bool();
let result = match op {
Op::LogicalAnd => lhs && rhs,
Op::LogicalOr => lhs || rhs,
_ => unreachable!(),
};
self.push_bool(result);
}
Op::LogicalNeg => {
let rhs = self.pop_bool();
self.push_bool(!rhs);
}
Op::Negate => {
let rhs = self.pop_quantity();
self.push_quantity(-rhs);
}
Op::Factorial => {
let lhs = self
.pop_quantity()
.as_scalar()
.expect("Expected factorial operand to be scalar")
.to_f64();
let order = self.read_u16();
if lhs < 0. {
return Err(Box::new(
self.runtime_error(RuntimeErrorKind::FactorialOfNegativeNumber),
));
} else if lhs.fract() != 0. {
return Err(Box::new(
self.runtime_error(RuntimeErrorKind::FactorialOfNonInteger),
));
}
self.push_quantity(Quantity::from_scalar(math::factorial(lhs, order)));
}
Op::JumpIfFalse => {
let offset = self.read_u16() as usize;
if !self.pop_bool() {
self.current_frame_mut().ip += offset;
}
}
Op::Jump => {
let offset = self.read_u16() as usize;
self.current_frame_mut().ip += offset;
}
Op::Call => {
let function_idx = self.read_u16() as usize;
let num_args = self.read_u16() as usize;
self.frames.push(CallFrame {
function_idx,
ip: 0,
fp: self.stack.len() - num_args,
})
}
Op::FFICallFunction | Op::FFICallProcedure => {
let function_idx = self.read_u16() as usize;
let num_args = self.read_u16() as usize;
let call_args_idx = self.read_u16() as usize;
let foreign_function = &self.ffi_callables[function_idx];
debug_assert!(foreign_function.arity.contains(&num_args));
let ffi_call_args = self.ffi_call_args[call_args_idx].clone();
let mut args = VecDeque::new();
for i in 0..num_args {
let call_arg = &ffi_call_args.args[num_args - 1 - i];
args.push_front(Arg {
value: self.pop(),
type_: call_arg.type_.clone(),
span: call_arg.span,
});
}
let (proc_name, _) = self.ffi_callables.get_index(function_idx).unwrap();
if *proc_name == "print" {
for arg in args.iter_mut() {
if let Value::Quantity(q) = &arg.value {
let simplified =
self.simplify_quantity(q, ctx.unit_name_to_constant_idx);
arg.value = Value::Quantity(simplified);
}
}
}
match &self.ffi_callables[function_idx].callable {
Callable::Function(function) => {
let return_type = ffi_call_args
.return_type
.as_ref()
.expect("FFI functions must have a return type");
let mut ffi_ctx = ffi::FfiContext::new(ctx, &self.constants);
let result = (function)(&mut ffi_ctx, args, return_type)
.map_err(|e| self.runtime_error(*e))?;
self.push(result);
}
Callable::Procedure(procedure) => {
let result = (procedure)(ctx, args);
match result {
std::ops::ControlFlow::Continue(()) => {}
std::ops::ControlFlow::Break(runtime_error) => {
return Err(Box::new(self.runtime_error(runtime_error)));
}
}
}
}
}
Op::CallCallable => {
let num_args = self.read_u16() as usize;
let call_args_idx = self.read_u16() as usize;
let callable = self.pop();
match callable.unsafe_as_function_reference() {
FunctionReference::Normal(ref name) => {
let function_idx = self.get_function_idx(name) as usize;
self.frames.push(CallFrame {
function_idx,
ip: 0,
fp: self.stack.len() - num_args,
})
}
FunctionReference::Foreign(ref name) => {
let function_idx = self
.get_ffi_callable_idx(name)
.expect("Foreign function exists")
as usize;
let ffi_call_args = self.ffi_call_args[call_args_idx].clone();
let mut args = VecDeque::new();
for i in 0..num_args {
let call_arg = &ffi_call_args.args[num_args - 1 - i];
args.push_front(Arg {
value: self.pop(),
type_: call_arg.type_.clone(),
span: call_arg.span,
});
}
match &self.ffi_callables[function_idx].callable {
Callable::Function(function) => {
let return_type = ffi_call_args
.return_type
.as_ref()
.expect("FFI functions must have a return type");
let mut ffi_ctx = ffi::FfiContext::new(ctx, &self.constants);
let result = (function)(&mut ffi_ctx, args, return_type)
.map_err(|e| self.runtime_error(*e))?;
self.push(result);
}
Callable::Procedure(..) => unreachable!(
"Foreign procedures can not be targeted by a function reference"
),
}
}
FunctionReference::TzConversion(tz_name) => {
let dt = self.pop_datetime();
let tz = jiff::tz::TimeZone::get(&tz_name).map_err(|_| {
self.runtime_error(RuntimeErrorKind::UnknownTimezone(
tz_name.to_string(),
))
})?;
let dt = dt.with_time_zone(tz);
self.push(Value::DateTime(dt));
}
}
}
Op::PrintString => {
let s_idx = self.read_u16() as usize;
let s = &self.strings[s_idx];
self.print(ctx, s);
}
Op::JoinString => {
let num_parts = self.read_u16() as usize;
let mut joined = CompactString::with_capacity(num_parts);
let to_str = |value, vm: &Self, ctx: &ExecutionContext| match value {
Value::Quantity(q) => vm
.simplify_quantity(&q, ctx.unit_name_to_constant_idx)
.to_compact_string(),
Value::Boolean(b) => b.to_compact_string(),
Value::String(s) => s.to_compact_string(),
Value::DateTime(dt) => {
crate::datetime::to_string(&dt, &FormatOptions::default())
}
Value::FunctionReference(r) => r.to_compact_string(),
s @ Value::StructInstance(..) => s.to_compact_string(),
l @ Value::List(_) => l.to_compact_string(),
Value::FormatSpecifiers(_) => unreachable!(),
};
let map_strfmt_error_to_runtime_error = |this: &Self, err| match err {
strfmt::FmtError::Invalid(s) => {
this.runtime_error(RuntimeErrorKind::InvalidFormatSpecifiers(s))
}
strfmt::FmtError::TypeError(s) => {
this.runtime_error(RuntimeErrorKind::InvalidTypeForFormatSpecifiers(s))
}
strfmt::FmtError::KeyError(_) => unreachable!(),
};
for _ in 0..num_parts {
let part = match self.pop() {
Value::FormatSpecifiers(Some(specifiers)) => match self.pop() {
Value::Quantity(q) => {
let q =
self.simplify_quantity(&q, ctx.unit_name_to_constant_idx);
let mut vars = HashMap::new();
vars.insert(
CompactString::const_new("value"),
q.unsafe_value().to_f64(),
);
let mut str =
strfmt::strfmt(&format!("{{value{specifiers}}}"), &vars)
.map(CompactString::from)
.map_err(|e| {
map_strfmt_error_to_runtime_error(self, e)
})?;
let unit_str = q.unit().to_compact_string();
if !unit_str.is_empty() {
str += " ";
str += &unit_str;
}
str
}
value => {
let mut vars = HashMap::new();
vars.insert(
"value".to_owned(),
to_str(value, self, ctx).to_string(),
);
strfmt::strfmt(&format!("{{value{specifiers}}}"), &vars)
.map(CompactString::from)
.map_err(|e| map_strfmt_error_to_runtime_error(self, e))?
}
},
Value::FormatSpecifiers(None) => to_str(self.pop(), self, ctx),
v => to_str(v, self, ctx),
};
joined = part + &joined; }
self.push(Value::String(joined))
}
Op::Return => {
if self.frames.len() == 1 {
let return_value = self.pop();
self.last_result = Some(return_value.clone());
result_last_statement = Some(return_value);
} else {
let discarded_frame = self.frames.pop().unwrap();
let return_value = self.stack.pop().unwrap();
while self.stack.len() > discarded_frame.fp {
self.stack.pop();
}
self.stack.push(return_value);
}
}
Op::BuildStructInstance => {
let info_idx = self.read_u16();
let (_, struct_info) = self
.struct_infos
.get_index(info_idx as usize)
.expect("Missing struct metadata");
let struct_info = Arc::clone(struct_info);
let num_args = self.read_u16();
let mut content = Vec::with_capacity(num_args as usize);
for _ in 0..num_args {
content.push(self.pop());
}
self.stack.push(Value::StructInstance(struct_info, content));
}
Op::AccessStructField => {
let field_idx = self.read_u16();
let mut fields = self.pop().unsafe_as_struct_fields();
let value = fields.swap_remove(field_idx as usize);
self.stack.push(value);
}
Op::BuildList => {
let length = self.read_u16();
let mut list = NumbatList::with_capacity(length as usize);
for _ in 0..length {
list.push_front(self.pop());
}
self.stack.push(list.into());
}
}
}
if let Some(value) = result_last_statement {
Ok(InterpreterResult::Value(value))
} else {
Ok(InterpreterResult::Continue)
}
}
pub fn debug(&self) {
if !self.debug {
return;
}
let frame = self.current_frame();
eprint!(
"FRAME = {}, IP = {}, ",
self.bytecode[frame.function_idx].0, frame.ip
);
eprintln!(
"Stack: [{}]",
self.stack
.iter()
.map(|x| x.to_string())
.collect::<Vec<_>>()
.join("] [")
);
}
pub fn add_string(&mut self, m: Markup) -> u16 {
self.strings.push(m);
assert!(self.strings.len() <= u16::MAX as usize);
(self.strings.len() - 1) as u16 }
fn print(&self, ctx: &mut ExecutionContext, m: &Markup) {
(ctx.print_fn)(m);
}
}
#[test]
fn vm_basic() {
let mut vm = Vm::new();
vm.add_constant(Constant::Scalar(42.0));
vm.add_constant(Constant::Scalar(1.0));
vm.add_op1(Op::LoadConstant, 0, Span::dummy());
vm.add_op1(Op::LoadConstant, 1, Span::dummy());
vm.add_op(Op::Add, Span::dummy());
vm.add_op(Op::Return, Span::dummy());
let mut print_fn = |_: &Markup| {};
let unit_name_to_constant_idx = HashMap::new();
let prefix_transformer = Transformer::new();
let typechecker = TypeChecker::default();
let mut ctx = ExecutionContext {
print_fn: &mut print_fn,
unit_name_to_constant_idx: &unit_name_to_constant_idx,
prefix_transformer: &prefix_transformer,
typechecker: &typechecker,
};
assert_eq!(
vm.run(&mut ctx).unwrap(),
InterpreterResult::Value(Value::Quantity(Quantity::from_scalar(42.0 + 1.0)))
);
}