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}