gnostr_xq/vm/
bytecode.rs

1use std::fmt::{Debug, Formatter};
2
3use crate::{
4    vm::{Address, Result, ScopeId, ScopedSlot},
5    Value,
6};
7
8#[derive(Clone, Eq, PartialEq)]
9pub struct NamedFunction<F: Clone + ?Sized> {
10    pub name: &'static str,
11    pub func: F,
12}
13
14#[derive(Debug, Copy, Clone, Hash, Ord, PartialOrd, Eq, PartialEq)]
15pub(crate) struct ClosureAddress(pub(crate) Address);
16
17pub type NamedFn0 = NamedFunction<fn(Value) -> Result<Value>>;
18pub type NamedFn1 = NamedFunction<fn(Value, Value) -> Result<Value>>;
19pub type NamedFn2 = NamedFunction<fn(Value, Value, Value) -> Result<Value>>;
20
21impl<F: Clone> Debug for NamedFunction<F> {
22    fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result {
23        f.write_fmt(format_args!("Intrinsic {}", self.name))
24    }
25}
26
27/// Byte code of the virtual machine.
28#[derive(Debug, Clone, PartialEq, Eq)]
29pub(crate) enum ByteCode {
30    /// Just to prevent a pc overrun...
31    Unreachable,
32    /// This byte code is used temporarily while compiling,
33    /// e.g. to replace to a jump to a not-yet-emitted address.
34    PlaceHolder,
35    /// Does nothing. Can be useful if we want to patch byte code for optimization.
36    Nop,
37    /// Pushes an immediate value to the top of the stack.
38    Push(Value),
39    /// Pops and discard the top of the stack.
40    /// # Panics
41    /// Panics if the stack was empty.
42    Pop,
43    /// Duplicates the top of the stack and pushes it.
44    /// # Panics
45    /// Panics if the stack was empty.
46    Dup,
47    /// Swaps the top 2 values of the stack.
48    /// # Panics
49    /// Panics if the stack had less than 2 elements.
50    Swap,
51    /// Exactly equals to Pop + Push(Value).
52    /// # Panics
53    /// Panics it the stack was empty.
54    Const(Value),
55    /// Loads the current value of the slot, and pushes a copy of that to the stack.
56    /// # Panics
57    /// Panics if the slot didn't have a value.
58    Load(ScopedSlot),
59    /// Pops a value from the stack, and store the value to the slot.
60    /// # Panics
61    /// Panics if the stack was empty.
62    Store(ScopedSlot),
63    /// Pushes the closure (= program address) to the closure stack.
64    PushClosure(ClosureAddress),
65    /// Pops a closure (= program address) from the closure stack the closure, and stores to a closure slot.
66    /// # Panics
67    /// Panics if the closure stack was empty.
68    StoreClosure(ScopedSlot),
69    /// Pops three values, `value`, `key` and `obj` from the stack, set `obj[key] = value` and push the new obj.
70    /// # Panics
71    /// Panics if the stack had less than 3 elements.
72    AppendObject,
73    /// Pops a value from the stack, and append to the array stored in the slot.
74    /// # Panics
75    /// Panics if the stack was empty, the slot didn't have a value or the value in the slot was not an array.
76    Append(ScopedSlot),
77
78    /// Pops a value `value`, and another value `index` from the stack, and pushes `value[index]` to the stack.
79    /// # Panics
80    /// Panics if the stack had less than 2 elements.
81    Index,
82    /// Pops an element from the stack and use it as a `value`.
83    /// Then pops an element from the stack if `end` was true. Then pops an element if `start` was true.
84    /// Pushes the slice `value[start?:end?]` to the stack.
85    /// # Panics
86    /// Panics if the stack didn't have enough elements.
87    Slice {
88        start: bool,
89        end: bool,
90    },
91    /// Pops a value and run forks with each "value" of it pushed to the stack.
92    /// # Panics
93    /// Panics if the stack was empty.
94    Each,
95    /// Pops an element `path` from the stack, another element `value` from the stack, and pushes an element
96    /// at the `path` of `value`.
97    /// # Panics
98    /// Panics if the stack had less than 2 elements.
99    Access,
100
101    EnterPathTracking,
102    ExitPathTracking,
103    EnterNonPathTracking,
104    ExitNonPathTracking,
105
106    /// Pushes a fork that runs from `fork_pc` to the fork stack.
107    Fork {
108        fork_pc: Address,
109    },
110    /// Pushes a fork to the fork stack.
111    /// When the fork starts, it checks if the current state is error state, and discards the fork and continues from the next fork if it wasn't.
112    /// If it was the error state and has `catch_pc`, it pushes the error message to the stack, clear error state and run again from `catch_pc`.
113    /// If it was the error state and `catch_pc` was [Option::None], it discards the error and continue from the next fork.
114    ForkTryBegin {
115        catch_pc: Option<Address>,
116    },
117    /// Pushes a mark that let fork-search procedure to ignore the next [Self::ForkTryBegin] fork.
118    ForkTryEnd,
119    /// Pushes a mark that let fork-search procedure to run from the fork if the current state is an error state,
120    /// or otherwise ignore the fork.
121    /// Note that [Self::ForkTryEnd] doesn't take this [Self::ForkAlt] into account.
122    ForkAlt {
123        fork_pc: Address,
124    },
125    /// Pushes a fork that catches break-type error for the same scope/slot, and swallow the error to continue from the next fork.
126    ForkLabel(ScopedSlot),
127    /// Search for the fork emitted by the corresponding [Self::ForkLabel].
128    Break(ScopedSlot),
129    /// Discard the current fork, and continues from the next fork.
130    Backtrack,
131    /// Change the current pc to the given address.
132    Jump(Address),
133    /// Pops a value from the stack, jumps to the address if it wasn't [crate::intrinsic::truthy()].
134    /// # Panics
135    /// Panics if the stack was empty.
136    JumpUnless(Address),
137    /// Lookup the closure stored in the closure slot, and invokes it with setting the next return address as the `return_address`.
138    /// # Panics
139    /// Panics if the call pc was already set, i.e. no [Self::NewFrame] was called after the previous [Self::CallClosure]/[Self::Call]/[Self::TailCall]/[Self::TailCallClosure].
140    CallClosure(ScopedSlot),
141    /// Calls function in the given address, and invokes it with setting the next address as the `return_address`.
142    /// # Panics
143    /// Panics if the call pc was already set, i.e. no [Self::NewFrame] was called after the previous Call bytecode family.
144    Call(Address),
145    /// Abandon the current frame, obtain the return address from there and set that as `return_address`, and calls the function as [Self::Call].
146    /// # Panics
147    /// Panics if the call pc was already set, i.e. no [Self::NewFrame] was called after the previous Call bytecode family.
148    TailCall(Address),
149    /// _Don't_ abandon the current frame, obtain the return address from the current frame and set that as `return_address`,
150    /// and calls the function. A flag will be set on the next [Self::NewFrame] to indicate that the [Self::Ret] that pops the frame
151    /// should also pop the next (i.e. the current as of the [Self::CallDoubleRet]) frame.
152    CallChainRet(Address),
153    /// This is to [Self::CallClosure] what [Self::TailCall] is to [Self::Call].
154    /// # Panics
155    /// Panics if the call pc was already set, i.e. no [Self::NewFrame] was called after the previous Call bytecode family.
156    TailCallClosure(ScopedSlot),
157    /// Creates a frame with the scope id, variable slots, closure slots, label slots, and the call pc specified in the previous Call bytecode family.
158    /// # Panics
159    /// Panics if this was not preceded by Call bytecode family
160    NewFrame {
161        id: ScopeId,
162        variable_cnt: usize,
163        closure_cnt: usize,
164        label_cnt: usize,
165    },
166    /// Discards the current frame, and start from the return address.
167    /// # Panics
168    /// Panics if the frame stack was empty.
169    Ret,
170    /// Pops a value from the stack and output it.
171    /// # Panics
172    /// Panics if the stack was empty.
173    Output,
174    /// Pops a value from the stack
175    /// If there's no more input, produce a [QueryExecutionError::NoMoreInputError] instead.
176    Input,
177
178    /// Pops a value `context` from the stack, invokes the function with the arg `context`, and pushes the resulting value to the stack.
179    /// # Panics
180    /// Panics if the stack was empty, or the invoked function panicked.
181    Intrinsic0(NamedFn0),
182    /// Pops a value `arg1` from the stack, pops another value `context` from the stack,
183    /// and invokes the function with the arg `context, arg1`, and pushes the resulting value to the stack.
184    /// # Panics
185    /// Panics if the stack had less than 2 elements, or the invoked function panicked.
186    Intrinsic1(NamedFn1),
187    /// Pops a value `arg2` from the stack, pops another value `arg1` from the stack, and another value `context` from the stack,
188    /// and invokes the function with the arg `context, arg1, arg2`, and pushes the resulting value to the stack.
189    /// # Panics
190    /// Panics if the stack had less than 3 elements, or the invoked function panicked.
191    Intrinsic2(NamedFn2),
192}
193
194#[derive(Debug, Clone)]
195pub struct Program {
196    pub(crate) code: Vec<ByteCode>,
197    pub(crate) entry_point: Address,
198}
199
200impl Program {
201    pub(crate) fn fetch_code(&self, pc: Address) -> Option<&ByteCode> {
202        self.code.get(pc.0)
203    }
204}