1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
use std::collections::HashMap;

use serde::{Deserialize, Serialize};

use crate::resolver::{Resolver, UpValue};
use crate::scanner::{Scanner, Token};
use crate::value::Value;

// todo: fix comments
#[derive(PartialEq, PartialOrd, Debug, Clone, Copy, Serialize, Deserialize)]
pub enum OpCode {
    OpReturn,
    OpPop,

    // Index of the String name for this variable name in the identifiers vec
    OpDefineGlobal(usize),
    // ^
    OpGetGlobal(usize),
    // ^
    OpSetGlobal(usize),
    // ^
    OpGetSuper(usize),
    //  module_index, string name, arity
    OpCallGlobal(usize, usize, usize), // A combination of OpCall and OpGetGlobal

    OpGetModuleVar(usize, usize),

    // Index on the stack
    OpGetLocal(usize),

    OpSetLocal(usize),
    // ^
    /// Combines a GetProperty and a Call. Contains the exact same information. First usize is the index for the property name, second is for the arity, third is for the module index
    OpInvoke(usize, usize, usize),

    // WIP
    OpImport(usize),
    OpGetProperty(usize),
    // Index of the String name for this variable name in the identifiers vec corresponding with the property name
    OpSetProperty(usize),
    // ^
    // Optimization note: Is there any way to resolve properties at compile time? Phoenix allows arbitrary properties to be added at any time, so I don't believe it's possible
    OpGetUpvalue(usize),
    // upvalue index for a closure
    OpSetUpvalue(usize),
    // Wraps the top value of the stack (must be a PhoenixFunction) in a PhoenixClosure, capturing the appropriate UpValues at the same time
    OpClosure,

    // Jump ip offset
    OpJump(usize),
    OpJumpIfFalse(usize),
    // Jump backwards by offset
    OpLoop(usize),
    // Arity, module index
    OpCall(usize, usize),
    // Index into the classes vec for the ClassChunk object
    OpClass(usize),
    // Index of the constant we want to retrieve
    OpConstant(usize),

    OpNil,
    OpTrue,
    OpFalse,

    OpNegate,
    OpNot,

    OpAdd,
    OpSubtract,
    OpMultiply,
    OpDivide,
    OpEqual,
    OpGreater,
    OpLess,

    OpPrint,
    // get element of list at index
    OpGetIndex,
    // set element of list at index
    OpSetIndex,
    // create a new list with the last n elements on the stack
    OpCreateList(usize),
    // Push the string on the stack to the heap and replace it with a pointer to the heap
    OpCreateString,
    /// Creates a hashmap of the last 2n items, where the first value is the key and the second one is the value
    OpCreateHashMap(usize),
    /// Waits until enter is pressed before continuing
    OpWait,
}

#[derive(Debug, Clone, Copy, PartialEq, Serialize, Deserialize)]
pub struct Instr {
    pub op_code: OpCode,
    pub line_num: usize,
}

#[derive(Debug, Clone, Serialize, Deserialize, PartialEq)]
pub struct Chunk {
    pub code: Vec<Instr>,
}

impl Default for Chunk {
    fn default() -> Self {
        Self::new()
    }
}

impl Chunk {
    pub fn write_instruction(&mut self, instruction: Instr) {
        self.code.push(instruction);
    }

    pub fn new() -> Chunk {
        Chunk { code: Vec::new() }
    }
}

#[derive(Debug, Clone, Copy, PartialEq, Serialize, Deserialize)]
pub enum FunctionType {
    Function,
    Script,
    Method,
    Initializer,
}

/// Compile time representation of a function, ie its code, name, resolved closure information
#[derive(Debug, Clone, Serialize, Deserialize, PartialEq)]
pub struct FunctionChunk {
    pub chunk: Chunk,
    pub name: Option<String>,
    // None for the top level script
    pub arity: usize,
    pub fn_type: FunctionType,
    pub upvalues: Option<Vec<UpValue>>, // None while the function is being defined and for functions without upvalues. If the function does have upvalues, this field must be set and must be binded with an OpClosure
}

impl FunctionChunk {
    pub fn new(name: Option<String>, arity: usize, fn_type: FunctionType) -> FunctionChunk {
        FunctionChunk {
            chunk: Chunk::new(),
            name,
            arity,
            fn_type,
            upvalues: None,
        }
    }

    pub fn set_upvalues(&mut self, upvalues: Vec<UpValue>) {
        self.upvalues = Some(upvalues);
    }
}

/// Compile time repr of a class
#[derive(Debug, Clone, Serialize, Deserialize, PartialEq)]
pub struct ClassChunk {
    pub name: String,
    pub methods: HashMap<usize, usize>,
    pub superclass: Option<usize>,
    pub has_init: bool,
}

impl ClassChunk {
    pub fn new(name: String) -> ClassChunk {
        ClassChunk {
            name,
            methods: HashMap::new(),
            superclass: None,
            has_init: false,
        }
    }
}

/// Compile time repr of an imported module
#[derive(Debug, Clone)]
pub struct CompilerModuleChunk {
    /// The name of the module
    pub name: String,
    /// Whether or not this is the main module
    pub main: bool,
    /// The chunk of code for the module
    pub chunk: Chunk,
    pub scanner: Scanner,
    pub tokens: Vec<Token>,
    pub constants: Vec<Value>,
    pub functions: Vec<FunctionChunk>,
    pub classes: Vec<ClassChunk>,
    pub(crate) current_function: usize,
    pub current_class: Option<usize>,
    /// Which FunctionChunk should the the compiler return to after. Acts as a stack
    pub parent_functions: Vec<usize>,
    /// Manages the slots for the local variables and upvalues, represented as a Vec of individual ResolverNodes
    pub resolver: Resolver,
    pub identifier_constants: Vec<String>,
    // for now we ignore the init functions in modules
    pub has_init: bool,
}

impl CompilerModuleChunk {
    pub fn new(main: bool, name: String, file: String, code: String) -> CompilerModuleChunk {
        CompilerModuleChunk {
            main,
            name,
            chunk: Chunk::new(),
            functions: Vec::new(),
            constants: Vec::new(),
            classes: Vec::new(),
            current_function: 0,
            current_class: None,
            parent_functions: vec![],
            resolver: Resolver::new(),
            identifier_constants: vec![],
            has_init: false,
            scanner: Scanner::new(file, code, 0),
            tokens: vec![],
        }
    }
}

/// Compile time repr of an imported module, but without the scanner, instead with the file name
#[derive(Debug, Clone, Serialize, Deserialize, PartialEq)]
pub struct ModuleChunk {
    /// The name of the module
    pub name: String,
    /// Whether or not this is the main module
    pub main: bool,
    /// The file name of the module
    pub file: String,
    pub constants: Vec<Value>,
    pub identifiers: Vec<String>,
    pub functions: Vec<FunctionChunk>,
    pub classes: Vec<ClassChunk>,
    // for now we ignore the init functions in modules
    pub has_init: bool,
}

impl ModuleChunk {
    pub fn new(main: bool, name: String, file: String) -> ModuleChunk {
        ModuleChunk {
            main,
            name,
            functions: Vec::new(),
            constants: Vec::new(),
            classes: Vec::new(),
            has_init: false,
            file,
            identifiers: vec![],
        }
    }

    pub fn from(module: CompilerModuleChunk) -> ModuleChunk {
        ModuleChunk {
            main: module.main,
            name: module.name,
            functions: module.functions,
            constants: module.constants,
            classes: module.classes,
            has_init: module.has_init,
            file: module.scanner.file,
            identifiers: module.identifier_constants,
        }
    }
}