-
Notifications
You must be signed in to change notification settings - Fork 0
Expand file tree
/
Copy pathevaluate.jai
More file actions
402 lines (310 loc) · 16.2 KB
/
evaluate.jai
File metadata and controls
402 lines (310 loc) · 16.2 KB
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
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
/*
evaluate.jai
As an alternative to execute_node, which pushes results on the script stack, evalaute_node will allocate the intermediate values in temproary storage.
In the future, it may be ideal to re-implement execute_node in terms of evaluate_node, using hint_storage to stick the results on the script stack instead of doing so by default
Ultimately, this should simplify execute_node while also offering multiple ways of interfacing with lead sheets expressions.
*/
evaluate_expression :: (script: *Script, expr: string) -> (Any, bool) {
root := parse_expression_string(script, expr);
if root == null return NULL_ANY, false;
if !typecheck_node(script, root, .IS_STATEMENT_ROOT) return NULL_ANY, false;
value := evaluate_node(script, root);
if has_error(script) return NULL_ANY, false;
return value, true;
}
evaluate_statement :: (script: *Script, expr: string, store_global_declarations := false) -> (Any, bool) {
root := parse_statement_string(script, expr);
if root == null {
log("root was null");
return NULL_ANY, false;
}
if !typecheck_node(script, root, .IS_STATEMENT_ROOT) return NULL_ANY, false;
value := evaluate_node(script, root);
if has_error(script) return NULL_ANY, false;
if store_global_declarations {
if root.node_type == Node_Declaration {
array_add(*script.global_declarations, xx root);
}
}
return value, true;
}
evaluate_node :: (
script: *Script,
node: *Node,
hint_storage: *void = null,
explicit_call := false
) -> Any {
dprint("evaluating node %: %\n", node, node.node_type);
dprint_push_indent();
if script.evaluate_callback {
result := script.evaluate_callback(script, node, hint_storage, explicit_call);
if has_error(script) return NULL_ANY;
if result.type != null return result;
}
// If no hint_storage is provided, then we allocate in temp storage.
// The caller should always be aware of this and call the function accordingly!
result := Any.{ node.value_type, hint_storage };
if result.value_pointer == null {
result.value_pointer = Dynamic_New(node.value_type,, temp);
}
if node.node_type == {
case Node_Directive;
directive := node.(*Node_Directive);
if directive.runtime_node {
result = evaluate_node(script, directive.runtime_node, hint_storage);
}
case Node_Literal;
literal := node.(*Node_Literal);
if literal.flags & .IS_LVALUE then assert(literal.flags & .IS_MALLEABLE == .IS_MALLEABLE || literal.literal_type == .ANY);
if #complete literal.literal_type == {
case .ANY; result = literal.any;
case .STRING; result = literal.text;
case .BOOLEAN; result = to_any(*literal.number);
case .NUMBER; result = to_any(*literal.number);
case .STRUCT;
ti_struct := literal.value_type.(*Type_Info_Struct);
assert(ti_struct.type == .STRUCT);
for literal.aggr.expressions {
member := ti_struct.members[it_index];
member_ptr := result.value_pointer + member.offset_in_bytes;
member_any := evaluate_node(script, it, member_ptr);
}
}
case Node_Identifier;
identifier := node.(*Node_Identifier);
if #complete identifier.identifier_type == {
case .DECLARATION;
declaration := identifier.declaration;
result = Any.{ identifier.value_type, declaration.value_pointer };
dprint("returning internal variable with value % at %\n", result, result.value_pointer);
case .EXTERNAL_VARIABLE;
variable := *script.variables[identifier.index];
result = get_value(variable);
dprint("returning external variable with value % at %\n", result, result.value_pointer);
case .EXTERNAL_PROCEDURE;
procedure := *script.procedures[identifier.index];
result = to_any(procedure);
// dprint("returning external procedure with type % at %\n", as_type(result.type), procedure.pointer);
assert(identifier.flags & .IS_LVALUE == 0);
case .LITERAL;
result = evaluate_node(script, identifier.literal, hint_storage);
case .TYPE;
result = script.type_table[identifier.index].type;
case; #through;
case .STRUCT_MEMBER; #through; // we don't evaluate this case since the parent Node_Dot will just grab the struct member offset manually.
case .UNRESOLVED;
assert(false, "invalid case %", identifier.identifier_type);
}
case Node_Operation;
operation := node.(*Node_Operation);
_operator := get_operator(script, operation);
if operation.flags & .OVERLOAD {
result = evaluate_node(script, operation.overload_procedure, hint_storage);
} else {
left_any := evaluate_node(script, operation.left);
if has_error(script) return NULL_ANY;
right_any: Any;
if !is_unary(_operator) {
right_any = evaluate_node(script, operation.right);
if has_error(script) return NULL_ANY;
}
if operation.name == "=" {
// special case: operator = is always a simple memcpy
assert(operation.left.flags & .IS_LVALUE != 0);
memcpy(left_any.value_pointer, right_any.value_pointer, right_any.type.runtime_size);
result = left_any;
} else {
assert(operation.value_type == result.type);
if !execute_builtin_operation(operation.builtin_operation_index, left_any.value_pointer, right_any.value_pointer, result.value_pointer) {
set_general_error(script, "Failed while trying to execute builtin operation, operator index: %", operation.builtin_operation_index);
return NULL_ANY;
}
}
dprint("returning result of binary operation % % % -> % %\n", left_any, operation.name, right_any, as_type(result.type), result);
}
case Node_Procedure_Call;
procedure_call := node.(*Node_Procedure_Call);
proc_any := evaluate_node(script, procedure_call.procedure_expression);
if has_error(script) return NULL_ANY;
assert(proc_any.type.type == .PROCEDURE);
dprint("proc_type: %\n", as_type(proc_any.type));
arguments := NewArray(procedure_call.arguments.count, Any,, temp);
for procedure_call.arguments {
arguments[it_index] = evaluate_node(script, it);
if has_error(script) return NULL_ANY;
}
proc_ptr := proc_any.value_pointer.(**void).*;
proc_info := proc_any.type.(*Type_Info_Procedure);
result_array: [] Any;
if result.type != xx void then result_array = .[ result ];
#if USING_DYNCALL {
if !do_dyncall(script.dyncall_vm, proc_any, arguments, result_array) {
set_general_error(script, "Failed while trying to make dyncall.");
return NULL_ANY;
}
} else {
if !try_calling_procedure_with_wrapper(proc_info, proc_ptr, arguments, result_array) {
set_general_error(script, "Unable to find wrapper for procedure of type: %", as_type(proc_info));
return NULL_ANY;
}
}
dprint("returning result of procedure call: %\n", result);
case Node_Cast;
node_cast := node.(*Node_Cast);
// TODO: I don't like that this is called left_any, since that strongly implies that it's the result when in fact it is the value we have yet to convert.
left_any := evaluate_node(script, node_cast.value);
if !Convert.any_to_any(result, left_any) {
set_general_error(script, "Failed in call to Convert.any_to_any in Node_Cast: % -> %", as_type(left_any.type), as_type(result.type));
return NULL_ANY;
}
case Node_Dot;
dot := node.(*Node_Dot);
if dot.left == null {
assert(node.value_type.type == .ENUM || node.value_type.type == .STRUCT);
result = evaluate_node(script, dot.right);
} else {
left_type := dot.left.value_type;
if left_type.type == {
case .POINTER;
// left side can only be a pointer if it is a pointer to a struct
// this is to allow implicit dereference on pointers to structs for member access
pointer_info := left_type.(*Type_Info_Pointer);
assert(pointer_info.pointer_to.type == .STRUCT);
#through;
case .STRUCT;
struct_any := evaluate_node(script, dot.left);
if has_error(script) return NULL_ANY;
// do implicit dereference if needed
struct_any = dereference_any_pointer(struct_any);
result = Any.{ dot.value_type, struct_any.value_pointer + dot.right.(*Node_Identifier).member.offset_in_bytes };
dprint("struct value ptr: %\n", struct_any.value_pointer);
dprint("member value ptr: %\n", result.value_pointer);
case .TYPE;
result = evaluate_node(script, dot.right, hint_storage);
if has_error(script) return NULL_ANY;
}
}
case Node_Subscript;
subscript := node.(*Node_Subscript);
base_any := evaluate_node(script, subscript.base_expression);
if has_error(script) return NULL_ANY;
index_any := evaluate_node(script, subscript.indexing_expression);
if has_error(script) return NULL_ANY;
ti_array := subscript.base_expression.value_type.(*Type_Info_Array);
// get index as s64
// TODO: maybe improve this. for bytecode, could do an instruction for inplace type cast
index_as_s64, ok := Convert.any_to(s64, index_any); assert(ok);
array_count, array_data := get_array_count_and_data(base_any.value_pointer, xx base_any.type);
if index_as_s64 < 0 || index_as_s64 >= array_count {
set_general_error(script, "Array index '%' was out of bounds! Max index is %.", index_as_s64, array_count-1);
return NULL_ANY;
}
result = Any.{ node.value_type, array_data + index_as_s64 * node.value_type.runtime_size };
dprint("returning result of indexing operation: %\n", result);
case Node_Declaration;
assert(hint_storage == null);
declaration := node.(*Node_Declaration);
l_value := Any.{ declaration.value_type, declaration.value_pointer };
if declaration.init_expression {
evaluate_node(script, declaration.init_expression, l_value.value_pointer);
if has_error(script) return NULL_ANY;
} else {
memset(l_value.value_pointer, 0, l_value.type.runtime_size);
}
case Node_Block;
assert(hint_storage == null);
block := node.(*Node_Block);
if block.name && !explicit_call return NULL_ANY;
for block.statements {
dprint("executing statement #%\n", it_index);
evaluate_node(script, it);
if has_error(script) return NULL_ANY;
}
case Node_If_Statement;
assert(hint_storage == null);
if_statement := node.(*Node_If_Statement);
condition := evaluate_node(script, if_statement.condition);
if has_error(script) return NULL_ANY;
dprint("condition: % %\n", as_type(condition.type), condition);
condition_as_bool: bool;
Convert.any_to_bool(condition_as_bool, condition);
if condition_as_bool {
evaluate_node(script, if_statement.statement);
if has_error(script) return NULL_ANY;
}
case Node_While_Loop;
assert(hint_storage == null);
while_loop := node.(*Node_While_Loop);
while loop := true {
condition := evaluate_node(script, while_loop.condition);
if has_error(script) return NULL_ANY;
dprint("condition: % %\n", as_type(condition.type), condition);
condition_as_bool: bool;
Convert.any_to_bool(condition_as_bool, condition);
if !condition_as_bool break loop;
evaluate_node(script, while_loop.statement);
if has_error(script) return NULL_ANY;
}
case Node_For_Loop;
assert(hint_storage == null);
for_loop := node.(*Node_For_Loop);
if for_loop.control_type == {
case .ARRAY;
array := evaluate_node(script, for_loop.array_expression);
if has_error(script) return NULL_ANY;
ti_array := for_loop.array_expression.value_type.(*Type_Info_Array);
array_count, array_data := get_array_count_and_data(array.value_pointer, xx array.type);
// these will be accessed by iterator and iterator_index nodes during iteration
it_index: int;
for_loop.it_decl.value_pointer = array_data;
for_loop.it_index_decl.value_pointer = *it_index;
while it_index < array_count {
defer {
for_loop.it_decl.value_pointer += ti_array.element_type.runtime_size;
it_index += 1;
}
evaluate_node(script, for_loop.statement);
if has_error(script) return NULL_ANY;
}
case .RANGE;
lower := evaluate_node(script, for_loop.range.lower);
if has_error(script) return NULL_ANY;
upper := evaluate_node(script, for_loop.range.upper);
if has_error(script) return NULL_ANY;
control_type := for_loop.range.lower.value_type;
upper_as_s64: int;
lower_as_s64: int;
Convert.any_to_int(upper_as_s64, upper);
Convert.any_to_int(lower_as_s64, lower);
for lower_as_s64..upper_as_s64 {
_it: u64 = 0; // just need 8 bytes of zeros. we will just use this space as dst for dynamically typed int value for iterator
Convert.any_to_any(Any.{ control_type, *_it }, it);
for_loop.it_decl.value_pointer = *_it;
// TODO: for now we just don't set it_index.
// we should probably prevent using it_index for range-based loops in typecheck
evaluate_node(script, for_loop.statement);
if has_error(script) return NULL_ANY;
}
case .LIST;
_it_index: int;
for_loop.it_index_decl.value_pointer = *_it_index;
for for_loop.list {
_it_index = it_index;
it_any := evaluate_node(script, it);
if has_error(script) return NULL_ANY;
for_loop.it_decl.value_pointer = it_any.value_pointer;
evaluate_node(script, for_loop.statement);
if has_error(script) return NULL_ANY;
}
}
case;
set_general_error(script, "Unhandled node type '%' in evaluate_node.", node.node_type);
}
// copy result to hint_storage if need be
// this may occur for certain small values where we just set result from another Any
// doing it this way just prevents us having to do the memcpy in each individual case
if hint_storage != null && result.value_pointer != hint_storage {
memcpy(hint_storage, result.value_pointer, result.type.runtime_size);
}
return result;
}