cas_compiler/instruction.rs
1use cas_compute::numerical::value::Value;
2use cas_parser::parser::{ast::range::RangeKind, token::op::{BinOpKind, UnaryOpKind}};
3use crate::{item::Symbol, Label};
4use std::ops::Range;
5
6/// Kinds of bytecode instructions emitted by the compiler.
7///
8/// Bytecode can be thought of as a significantly high-level assembly language. Each instruction
9/// corresponds to a single operation that can be executed by the virtual machine, which itself can
10/// be thought of as a simple CPU.
11///
12/// During compilation, the compiler will associate most instructions with spans of the source code
13/// that they originated from. This is meant for debugging and to report high quality error
14/// messages for the user during runtime. The `spans` field of the [`Instruction`] struct contains
15/// this information.
16///
17/// See the `Span information` section of each variant for how to interpret the spans.
18#[derive(Debug, Clone, PartialEq)]
19pub enum InstructionKind {
20 /// Initializes key VM registers for a call to a user-defined function.
21 ///
22 /// This is a special instruction used in user function argument validation, and is compiled in
23 /// as the first instruction of a user function.
24 ///
25 /// The fields of this instruction are:
26 ///
27 /// - `fn_name`: The name of the function being executed.
28 /// - `fn_signature`: The signature of the function being executed.
29 /// - `num_params`: The total number of parameters in the function signature.
30 /// - `num_default_params`: The number of default parameters in the function signature.
31 InitFunc(String, String, usize, usize),
32
33 /// Checks if there are enough arguments on the stack to start executing the user function
34 /// body, or throws a runtime error if there are too many arguments.
35 ///
36 /// This is a special instruction used in user function argument validation, and is compiled in
37 /// just before compiling a default argument expression. The specific order of operations is:
38 ///
39 /// - Compare the number of currently available arguments (set in the argument counter) to the
40 /// number of arguments expected by the function.
41 /// - If they are equal, the function is ready to be executed. The VM will then assign the
42 /// given values to its internal variables representing the function parameters and execute
43 /// the function body, jumping around as needed.
44 /// - If there are more arguments than expected, an error is thrown.
45 /// - Otherwise, fall-through to the default argument expression.
46 /// - The default argument expression is compiled.
47 CheckExecReady,
48
49 /// Increments the argument counter by 1.
50 ///
51 /// This is a special instruction used in user function argument validation, and is compiled in
52 /// after each default argument expression.
53 NextArg,
54
55 /// Checks if there are missing or extra arguments by the time the function body is reached,
56 /// throwing a runtime error if so.
57 ///
58 /// This is a special instruction used in user function argument validation, and is compiled in
59 /// after all default argument expressions and before the function body.
60 ErrorIfMissingArgs,
61
62 /// Load a constant value (one known at compile time) onto the stack.
63 LoadConst(Value),
64
65 /// Create a new list with the specified number of elements pulled from the stack.
66 CreateList(usize),
67
68 /// Create a new list by repeating the top value on the stack `count` times.
69 ///
70 /// The value to repeat and the count are specified by the second-to-top and the top values on
71 /// the stack, respectively.
72 CreateListRepeat,
73
74 /// Create a new range with the start and end values on the stack.
75 CreateRange(RangeKind),
76
77 /// Load a value stored in a variable onto the stack.
78 LoadVar(Symbol),
79
80 /// Store the top value on the stack in the current stack frame. This value is **not** removed
81 /// from the stack.
82 ///
83 /// This behavior is important since assignment expressions can be used as subexpressions in
84 /// larger expressions.
85 StoreVar(usize),
86
87 /// Store the top value on the stack in the current stack frame. This value **is** removed from
88 /// the stack.
89 ///
90 /// This is used for assignment statements, where the value being assigned is not used in any
91 /// further expressions.
92 AssignVar(usize),
93
94 /// Store the top value on the stack in the list at the index. The list and index are then both
95 /// removed from the stack, while the value **is retained**.
96 ///
97 /// The value, list, and index are specified by the third-to-top, second-to-top, and top values
98 /// on the stack, respectively.
99 StoreIndexed,
100
101 /// Load the value at the specified index in the list onto the stack. The list and index are
102 /// then both removed from the stack.
103 ///
104 /// The list and index are specified by the second-to-top and the top values on the stack,
105 /// respectively.
106 LoadIndexed,
107
108 /// Drops the top value from the stack.
109 Drop,
110
111 /// Performs the binary operation on the second-to-top and top stack values.
112 Binary(BinOpKind),
113
114 /// Performs the unary operation on the top stack value.
115 Unary(UnaryOpKind),
116
117 /// Call the function at the top of the value stack, passing the specified number of arguments.
118 ///
119 /// Arguments are passed to the function via the value stack. The function will be popped from
120 /// the stack first, followed by the arguments in reverse order.
121 ///
122 /// # Span information
123 ///
124 /// ```rust,ignore
125 /// [
126 /// 0: outer_span[0], // span including the function name to the opening parenthesis
127 /// 1: outer_span[1], // closing parenthesis
128 /// 2: arg1,
129 /// 3: arg2,
130 /// ...
131 /// ]
132 /// ```
133 Call(usize),
134
135 /// Computes the `n`th numerical derivative of the function at the top of the stack.
136 ///
137 /// The number of arguments included in the call is specified by the `usize` value. It is
138 /// important to note that CalcScript **does not** support derivatives of non-unary functions.
139 /// The purpose of this field is to provide a way to discover violations of this rule at
140 /// runtime, when the actual function can be resolved, and report corresponding errors.
141 CallDerivative(u8, usize),
142
143 /// Returns from the current function.
144 Return,
145
146 /// Jump to the specified label.
147 Jump(Label),
148
149 /// Jumps to the specified label if the top value on the stack is the boolean `true`.
150 ///
151 /// This will result in an error if the top value is not a boolean.
152 JumpIfTrue(Label),
153
154 /// Jumps to the specified label if the top value on the stack is the boolean `false`.
155 ///
156 /// This will result in an error if the top value is not a boolean.
157 JumpIfFalse(Label),
158}
159
160/// Represents a single instruction in the bytecode, along with its associated metadata, such as
161/// source code spans.
162#[derive(Debug, Clone, PartialEq)]
163pub struct Instruction {
164 /// The kind of instruction.
165 pub kind: InstructionKind,
166
167 /// The span(s) of the source code that this instruction originated from.
168 pub spans: Vec<Range<usize>>,
169}
170
171impl From<InstructionKind> for Instruction {
172 fn from(kind: InstructionKind) -> Self {
173 Self { kind, spans: Vec::new() }
174 }
175}