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}