spore_vm/val/
bytecode.rs

1use std::sync::Arc;
2
3use compact_str::CompactString;
4
5use crate::parser::span::Span;
6
7use super::{NativeFunction, Symbol, UnsafeVal};
8
9/// Contains a set of instructions for the Spore VM to evaluate.
10#[derive(Clone, Debug, Default, PartialEq)]
11pub struct ByteCode {
12    /// The name of the function.
13    pub name: CompactString,
14    /// The number of arguments for the bytecode.
15    pub arg_count: usize,
16    /// The number of space reserved for local bindings.
17    pub local_bindings: usize,
18    /// The instructions for the bytecode.
19    pub instructions: Arc<[Instruction]>,
20    /// The source code for the bytecode.
21    pub source: Option<Arc<str>>,
22    /// The span containing the instruction code from `source`.
23    pub instruction_source: Box<[Span]>,
24}
25
26impl ByteCode {
27    /// Create bytecode that calls `function` with the top `arg_count` args in the stack.
28    pub fn new_native_function_call(
29        name: &str,
30        func: NativeFunction,
31        arg_count: usize,
32    ) -> ByteCode {
33        ByteCode {
34            name: name.into(),
35            arg_count: 0,
36            local_bindings: 0,
37            instructions: Arc::new([Instruction::EvalNative { func, arg_count }]),
38            source: None,
39            instruction_source: Box::default(),
40        }
41    }
42
43    /// Iterate over all values referenced by the bytecode.
44    pub fn values(&self) -> impl '_ + Iterator<Item = UnsafeVal> {
45        self.instructions
46            .iter()
47            .flat_map(|instruction| match instruction {
48                Instruction::PushConst(v) => Some(*v),
49                Instruction::PushCurrentFunction => None,
50                Instruction::Pop(_) => None,
51                Instruction::GetArg(_) => None,
52                Instruction::BindArg(_) => None,
53                Instruction::Deref(_) => None,
54                Instruction::Define(_) => None,
55                Instruction::Eval(_) => None,
56                Instruction::EvalNative { .. } => None,
57                Instruction::JumpIf(_) => None,
58                Instruction::Jump(_) => None,
59                Instruction::Return => None,
60            })
61    }
62}
63
64/// An instruction for the VM to execute.
65#[derive(Clone, Debug, PartialEq)]
66pub enum Instruction {
67    /// Push a constant onto the stack.
68    PushConst(UnsafeVal),
69    /// Push the current function onto the stack.
70    PushCurrentFunction,
71    /// Pop the top `n` elements in the stack.
72    Pop(usize),
73    /// Get the nth argument from the start of the continuation's stack.
74    GetArg(usize),
75    /// Bind the top argument to the nth argument in the stack.
76    BindArg(usize),
77    /// Get the value of a symbol at push it onto the stack.
78    Deref(Symbol),
79    /// Pop the top value of the stack and assign it to the given symbol.
80    Define(Symbol),
81    /// Pop the top `n` values of the stack. The deepmost value should be function with the rest of
82    /// the values acting as the arguments.
83    Eval(usize),
84    /// Pop the top `n` values of the stack. The deepmost value should be function with the rest of
85    /// the values acting as the arguments.
86    EvalNative {
87        func: NativeFunction,
88        arg_count: usize,
89    },
90    /// Pop the top value of the stack. If it is `true`, then jump `n` instructions.
91    JumpIf(usize),
92    /// Jump `n` instructions.
93    Jump(usize),
94    /// Return from the current function.
95    Return,
96}
97
98#[cfg(test)]
99mod tests {
100    use super::*;
101    use std::mem::size_of;
102
103    #[test]
104    fn struct_sizes_are_small_enough() {
105        assert_eq!(size_of::<Instruction>(), 3 * size_of::<usize>());
106    }
107}