Line | Count | Source (jump to first uncovered line) |
1 | | /* |
2 | | Copyright 2015 Google Inc. All rights reserved. |
3 | | |
4 | | Licensed under the Apache License, Version 2.0 (the "License"); |
5 | | you may not use this file except in compliance with the License. |
6 | | You may obtain a copy of the License at |
7 | | |
8 | | http://www.apache.org/licenses/LICENSE-2.0 |
9 | | |
10 | | Unless required by applicable law or agreed to in writing, software |
11 | | distributed under the License is distributed on an "AS IS" BASIS, |
12 | | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. |
13 | | See the License for the specific language governing permissions and |
14 | | limitations under the License. |
15 | | */ |
16 | | |
17 | | #include <cassert> |
18 | | #include <cmath> |
19 | | |
20 | | #include <memory> |
21 | | #include <set> |
22 | | #include <string> |
23 | | |
24 | | #include "desugarer.h" |
25 | | #include "json.h" |
26 | | #include "json.hpp" |
27 | | #include "md5.h" |
28 | | #include "parser.h" |
29 | | #include "ryml_std.hpp" // include this before any other ryml header |
30 | | #include "ryml.hpp" |
31 | | #include "state.h" |
32 | | #include "static_analysis.h" |
33 | | #include "string_utils.h" |
34 | | #include "vm.h" |
35 | | |
36 | | /** Macro that dumps an error and aborts the program regardless of |
37 | | * whether NDEBUG is defined. This should be used to mark codepaths |
38 | | * as unreachable. |
39 | | */ |
40 | | #define JSONNET_UNREACHABLE() \ |
41 | 0 | do { \ |
42 | 0 | std::cerr << __FILE__ << ":" << __LINE__ \ |
43 | 0 | << ": INTERNAL ERROR: reached unreachable code path" \ |
44 | 0 | << std::endl; \ |
45 | 0 | abort(); \ |
46 | 0 | } while (0) |
47 | | |
48 | | using json = nlohmann::json; |
49 | | |
50 | | namespace { |
51 | | |
52 | | /** Turn a path e.g. "/a/b/c" into a dir, e.g. "/a/b/". If there is no path returns "". |
53 | | */ |
54 | | std::string dir_name(const std::string &path) |
55 | 20 | { |
56 | 20 | size_t last_slash = path.rfind('/'); |
57 | 20 | if (last_slash != std::string::npos) { |
58 | 0 | return path.substr(0, last_slash + 1); |
59 | 0 | } |
60 | 20 | return ""; |
61 | 20 | } |
62 | | |
63 | | /** Stack frames. |
64 | | * |
65 | | * Of these, FRAME_CALL is the most special, as it is the only frame the stack |
66 | | * trace (for errors) displays. |
67 | | */ |
68 | | enum FrameKind { |
69 | | FRAME_APPLY_TARGET, // e in e(...) |
70 | | FRAME_BINARY_LEFT, // a in a + b |
71 | | FRAME_BINARY_RIGHT, // b in a + b |
72 | | FRAME_BINARY_OP, // a + b, with a and b already calculated |
73 | | FRAME_BUILTIN_FILTER, // When executing std.filter, used to hold intermediate state. |
74 | | FRAME_BUILTIN_FORCE_THUNKS, // When forcing builtin args, holds intermediate state. |
75 | | FRAME_CALL, // Used any time we have switched location in user code. |
76 | | FRAME_ERROR, // e in error e |
77 | | FRAME_IF, // e in if e then a else b |
78 | | FRAME_IN_SUPER_ELEMENT, // e in 'e in super' |
79 | | FRAME_INDEX_TARGET, // e in e[x] |
80 | | FRAME_INDEX_INDEX, // e in x[e] |
81 | | FRAME_INVARIANTS, // Caches the thunks that need to be executed one at a time. |
82 | | FRAME_LOCAL, // Stores thunk bindings as we execute e in local ...; e |
83 | | FRAME_OBJECT, // Stores intermediate state as we execute es in { [e]: ..., [e]: ... } |
84 | | FRAME_OBJECT_COMP_ARRAY, // e in {f:a for x in e] |
85 | | FRAME_OBJECT_COMP_ELEMENT, // Stores intermediate state when building object |
86 | | FRAME_STRING_CONCAT, // Stores intermediate state while co-ercing objects |
87 | | FRAME_SUPER_INDEX, // e in super[e] |
88 | | FRAME_UNARY, // e in -e |
89 | | FRAME_BUILTIN_JOIN_STRINGS, // When executing std.join over strings, used to hold intermediate state. |
90 | | FRAME_BUILTIN_JOIN_ARRAYS, // When executing std.join over arrays, used to hold intermediate state. |
91 | | FRAME_BUILTIN_DECODE_UTF8, // When executing std.decodeUTF8, used to hold intermediate state. |
92 | | }; |
93 | | |
94 | | /** A frame on the stack. |
95 | | * |
96 | | * Every time a subterm is evaluated, we first push a new stack frame to |
97 | | * store the continuation. |
98 | | * |
99 | | * The stack frame is a bit like a tagged union, except not as memory |
100 | | * efficient. The set of member variables that are actually used depends on |
101 | | * the value of the member variable kind. |
102 | | * |
103 | | * If the stack frame is of kind FRAME_CALL, then it counts towards the |
104 | | * maximum number of stack frames allowed. Other stack frames are not |
105 | | * counted. This is because FRAME_CALL exists where there is a branch in |
106 | | * the code, e.g. the forcing of a thunk, evaluation of a field, calling a |
107 | | * function, etc. |
108 | | * |
109 | | * The stack is used to mark objects during garbage |
110 | | * collection, so HeapObjects not referred to from the stack may be |
111 | | * prematurely collected. |
112 | | */ |
113 | | struct Frame { |
114 | | /** Tag (tagged union). */ |
115 | | FrameKind kind; |
116 | | |
117 | | /** The code we were executing before. */ |
118 | | const AST *ast; |
119 | | |
120 | | /** The location of the code we were executing before. |
121 | | * |
122 | | * location == ast->location when ast != nullptr |
123 | | */ |
124 | | LocationRange location; |
125 | | |
126 | | /** Reuse this stack frame for the purpose of tail call optimization. */ |
127 | | bool tailCall; |
128 | | |
129 | | /** Used for a variety of purposes. */ |
130 | | Value val; |
131 | | |
132 | | /** Used for a variety of purposes. */ |
133 | | Value val2; |
134 | | |
135 | | /** Used for a variety of purposes. */ |
136 | | DesugaredObject::Fields::const_iterator fit; |
137 | | |
138 | | /** Used for a variety of purposes. */ |
139 | | std::map<const Identifier *, HeapSimpleObject::Field> objectFields; |
140 | | |
141 | | /** Used for a variety of purposes. */ |
142 | | unsigned elementId; |
143 | | |
144 | | /** Used for a variety of purposes. */ |
145 | | std::map<const Identifier *, HeapThunk *> elements; |
146 | | |
147 | | /** Used for a variety of purposes. */ |
148 | | std::vector<HeapThunk *> thunks; |
149 | | |
150 | | /** Used for accumulating a joined string. */ |
151 | | UString str; |
152 | | bool first; |
153 | | |
154 | | /** Used for accumulating bytes */ |
155 | | std::string bytes; |
156 | | |
157 | | /** The context is used in error messages to attempt to find a reasonable name for the |
158 | | * object, function, or thunk value being executed. If it is a thunk, it is filled |
159 | | * with the value when the frame terminates. |
160 | | */ |
161 | | HeapEntity *context; |
162 | | |
163 | | /** The lexically nearest object we are in, or nullptr. Note |
164 | | * that this is not the same as context, because we could be inside a function, |
165 | | * inside an object and then context would be the function, but self would still point |
166 | | * to the object. |
167 | | */ |
168 | | HeapObject *self; |
169 | | |
170 | | /** The "super" level of self. Sometimes, we look upwards in the |
171 | | * inheritance tree, e.g. via an explicit use of super, or because a given field |
172 | | * has been inherited. When evaluating a field from one of these super objects, |
173 | | * we need to bind self to the concrete object (so self must point |
174 | | * there) but uses of super should be resolved relative to the object whose |
175 | | * field we are evaluating. Thus, we keep a second field for that. This is |
176 | | * usually 0, unless we are evaluating a super object's field. |
177 | | */ |
178 | | unsigned offset; |
179 | | |
180 | | /** A set of variables introduced at this point. */ |
181 | | BindingFrame bindings; |
182 | | |
183 | | Frame(const FrameKind &kind, const AST *ast) |
184 | | : kind(kind), |
185 | | ast(ast), |
186 | | location(ast->location), |
187 | | tailCall(false), |
188 | | elementId(0), |
189 | | context(NULL), |
190 | | self(NULL), |
191 | | offset(0) |
192 | 1.24M | { |
193 | 1.24M | val.t = Value::NULL_TYPE; |
194 | 1.24M | val2.t = Value::NULL_TYPE; |
195 | 1.24M | } |
196 | | |
197 | | Frame(const FrameKind &kind, const LocationRange &location) |
198 | | : kind(kind), |
199 | | ast(nullptr), |
200 | | location(location), |
201 | | tailCall(false), |
202 | | elementId(0), |
203 | | context(NULL), |
204 | | self(NULL), |
205 | | offset(0) |
206 | 3.84M | { |
207 | 3.84M | val.t = Value::NULL_TYPE; |
208 | 3.84M | val2.t = Value::NULL_TYPE; |
209 | 3.84M | } |
210 | | |
211 | | /** Mark everything visible from this frame. */ |
212 | | void mark(Heap &heap) const |
213 | 125k | { |
214 | 125k | heap.markFrom(val); |
215 | 125k | heap.markFrom(val2); |
216 | 125k | if (context) |
217 | 26.2k | heap.markFrom(context); |
218 | 125k | if (self) |
219 | 25.1k | heap.markFrom(self); |
220 | 125k | for (const auto &bind : bindings) |
221 | 100k | heap.markFrom(bind.second); |
222 | 125k | for (const auto &el : elements) |
223 | 0 | heap.markFrom(el.second); |
224 | 125k | for (const auto &th : thunks) |
225 | 520k | heap.markFrom(th); |
226 | 125k | } |
227 | | |
228 | | bool isCall(void) const |
229 | 11.2M | { |
230 | 11.2M | return kind == FRAME_CALL; |
231 | 11.2M | } |
232 | | }; |
233 | | |
234 | | /** The stack holds all the stack frames and manages the stack frame limit. */ |
235 | | class Stack { |
236 | | /** How many call frames are on the stack. */ |
237 | | unsigned calls; |
238 | | |
239 | | /** How many call frames should be allowed before aborting the program. */ |
240 | | unsigned limit; |
241 | | |
242 | | /** The stack frames. */ |
243 | | std::vector<Frame> stack; |
244 | | |
245 | | public: |
246 | 214 | Stack(unsigned limit) : calls(0), limit(limit) {} |
247 | | |
248 | 214 | ~Stack(void) {} |
249 | | |
250 | | unsigned size(void) |
251 | 8.93M | { |
252 | 8.93M | return stack.size(); |
253 | 8.93M | } |
254 | | |
255 | | /** Search for the closest variable in scope that matches the given name. */ |
256 | | HeapThunk *lookUpVar(const Identifier *id) |
257 | 10.8M | { |
258 | 14.6M | for (int i = stack.size() - 1; i >= 0; --i) { |
259 | 14.6M | const auto &binds = stack[i].bindings; |
260 | 14.6M | auto it = binds.find(id); |
261 | 14.6M | if (it != binds.end()) { |
262 | 10.8M | return it->second; |
263 | 10.8M | } |
264 | 3.74M | if (stack[i].isCall()) |
265 | 0 | break; |
266 | 3.74M | } |
267 | 0 | return nullptr; |
268 | 10.8M | } |
269 | | |
270 | | /** Mark everything visible from the stack (any frame). */ |
271 | | void mark(Heap &heap) |
272 | 1.64k | { |
273 | 125k | for (const auto &f : stack) { |
274 | 125k | f.mark(heap); |
275 | 125k | } |
276 | 1.64k | } |
277 | | |
278 | | Frame &top(void) |
279 | 33.7M | { |
280 | 33.7M | return stack.back(); |
281 | 33.7M | } |
282 | | |
283 | | const Frame &top(void) const |
284 | 0 | { |
285 | 0 | return stack.back(); |
286 | 0 | } |
287 | | |
288 | | void pop(void) |
289 | 5.03M | { |
290 | 5.03M | if (top().isCall()) |
291 | 3.57M | calls--; |
292 | 5.03M | stack.pop_back(); |
293 | 5.03M | } |
294 | | |
295 | | /** Attempt to find a name for a given heap entity. This may not be possible, but we try |
296 | | * reasonably hard. We look in the bindings for a variable in the closest scope that |
297 | | * happens to point at the entity in question. Otherwise, the best we can do is use its |
298 | | * type. |
299 | | */ |
300 | | std::string getName(unsigned from_here, const HeapEntity *e) |
301 | 3.55k | { |
302 | 3.55k | std::string name; |
303 | 7.35k | for (int i = from_here - 1; i >= 0; --i) { |
304 | 7.27k | const auto &f = stack[i]; |
305 | 7.27k | for (const auto &pair : f.bindings) { |
306 | 5.63k | HeapThunk *thunk = pair.second; |
307 | 5.63k | if (!thunk->filled) |
308 | 1.26k | continue; |
309 | 4.37k | if (!thunk->content.isHeap()) |
310 | 278 | continue; |
311 | 4.09k | if (e != thunk->content.v.h) |
312 | 3.66k | continue; |
313 | 430 | name = encode_utf8(pair.first->name); |
314 | 430 | } |
315 | | // Do not go into the next call frame, keep local reasoning. |
316 | 7.27k | if (f.isCall()) |
317 | 3.47k | break; |
318 | 7.27k | } |
319 | | |
320 | 3.55k | if (name == "") |
321 | 3.12k | name = "anonymous"; |
322 | 3.55k | if (dynamic_cast<const HeapObject *>(e)) { |
323 | 2.56k | return "object <" + name + ">"; |
324 | 2.56k | } else if (auto *thunk = dynamic_cast<const HeapThunk *>(e)) { |
325 | 644 | if (thunk->name == nullptr) { |
326 | 120 | return ""; // Argument of builtin, or root (since top level functions). |
327 | 524 | } else { |
328 | 524 | return "thunk <" + encode_utf8(thunk->name->name) + ">"; |
329 | 524 | } |
330 | 644 | } else { |
331 | 340 | const auto *func = static_cast<const HeapClosure *>(e); |
332 | 340 | if (func->body == nullptr) { |
333 | 0 | return "builtin function <" + func->builtinName + ">"; |
334 | 0 | } |
335 | 340 | return "function <" + name + ">"; |
336 | 340 | } |
337 | 3.55k | } |
338 | | |
339 | | /** Dump the stack. |
340 | | * |
341 | | * This is useful to help debug the VM in gdb. It is virtual to stop it |
342 | | * being removed by the compiler. |
343 | | */ |
344 | | virtual void dump(void) |
345 | 0 | { |
346 | 0 | for (std::size_t i = 0; i < stack.size(); ++i) { |
347 | 0 | std::cout << "stack[" << i << "] = " << stack[i].location << " (" << stack[i].kind |
348 | 0 | << ")" << std::endl; |
349 | 0 | } |
350 | 0 | std::cout << std::endl; |
351 | 0 | } |
352 | | |
353 | | /** Creates the error object for throwing, and also populates it with the stack trace. |
354 | | */ |
355 | | RuntimeError makeError(const LocationRange &loc, const std::string &msg) |
356 | 81 | { |
357 | 81 | std::vector<TraceFrame> stack_trace; |
358 | 81 | stack_trace.push_back(TraceFrame(loc)); |
359 | 10.4k | for (int i = stack.size() - 1; i >= 0; --i) { |
360 | 10.3k | const auto &f = stack[i]; |
361 | 10.3k | if (f.isCall()) { |
362 | 3.55k | if (f.context != nullptr) { |
363 | | // Give the last line a name. |
364 | 3.55k | stack_trace[stack_trace.size() - 1].name = getName(i, f.context); |
365 | 3.55k | } |
366 | 3.55k | if (f.location.isSet() || f.location.file.length() > 0) |
367 | 3.19k | stack_trace.push_back(TraceFrame(f.location)); |
368 | 3.55k | } |
369 | 10.3k | } |
370 | 81 | return RuntimeError(stack_trace, msg); |
371 | 81 | } |
372 | | |
373 | | /** New (non-call) frame. */ |
374 | | template <class... Args> |
375 | | void newFrame(Args... args) |
376 | 1.49M | { |
377 | 1.49M | stack.emplace_back(args...); |
378 | 1.49M | } vm.cpp:void (anonymous namespace)::Stack::newFrame<(anonymous namespace)::FrameKind, AST const*>((anonymous namespace)::FrameKind, AST const*) Line | Count | Source | 376 | 1.24M | { | 377 | 1.24M | stack.emplace_back(args...); | 378 | 1.24M | } |
vm.cpp:void (anonymous namespace)::Stack::newFrame<(anonymous namespace)::FrameKind, LocationRange>((anonymous namespace)::FrameKind, LocationRange) Line | Count | Source | 376 | 251k | { | 377 | 251k | stack.emplace_back(args...); | 378 | 251k | } |
|
379 | | |
380 | | /** If there is a tailstrict annotated frame followed by some locals, pop them all. */ |
381 | | void tailCallTrimStack(void) |
382 | 3.59M | { |
383 | 3.61M | for (int i = stack.size() - 1; i >= 0; --i) { |
384 | 3.61M | switch (stack[i].kind) { |
385 | 2.92M | case FRAME_CALL: { |
386 | 2.92M | if (!stack[i].tailCall || stack[i].thunks.size() > 0) { |
387 | 2.90M | return; |
388 | 2.90M | } |
389 | | // Remove all stack frames including this one. |
390 | 57.2k | while (stack.size() > unsigned(i)) |
391 | 38.4k | stack.pop_back(); |
392 | 18.8k | calls--; |
393 | 18.8k | return; |
394 | 2.92M | } break; |
395 | | |
396 | 23.7k | case FRAME_LOCAL: break; |
397 | | |
398 | 668k | default: return; |
399 | 3.61M | } |
400 | 3.61M | } |
401 | 3.59M | } |
402 | | |
403 | | /** New call frame. */ |
404 | | void newCall(const LocationRange &loc, HeapEntity *context, HeapObject *self, unsigned offset, |
405 | | const BindingFrame &up_values) |
406 | 3.59M | { |
407 | 3.59M | tailCallTrimStack(); |
408 | 3.59M | if (calls >= limit) { |
409 | 6 | throw makeError(loc, "max stack frames exceeded."); |
410 | 6 | } |
411 | 3.59M | stack.emplace_back(FRAME_CALL, loc); |
412 | 3.59M | calls++; |
413 | 3.59M | top().context = context; |
414 | 3.59M | top().self = self; |
415 | 3.59M | top().offset = offset; |
416 | 3.59M | top().bindings = up_values; |
417 | 3.59M | top().tailCall = false; |
418 | | |
419 | 3.59M | #ifndef NDEBUG |
420 | 3.59M | for (const auto &bind : up_values) { |
421 | 936k | if (bind.second == nullptr) { |
422 | 0 | std::cerr << "INTERNAL ERROR: No binding for variable " |
423 | 0 | << encode_utf8(bind.first->name) << std::endl; |
424 | 0 | std::abort(); |
425 | 0 | } |
426 | 936k | } |
427 | 3.59M | #endif |
428 | 3.59M | } |
429 | | |
430 | | /** Look up the stack to find the self binding. */ |
431 | | void getSelfBinding(HeapObject *&self, unsigned &offset) |
432 | 724k | { |
433 | 724k | self = nullptr; |
434 | 724k | offset = 0; |
435 | 2.46M | for (int i = stack.size() - 1; i >= 0; --i) { |
436 | 2.46M | if (stack[i].isCall()) { |
437 | 723k | self = stack[i].self; |
438 | 723k | offset = stack[i].offset; |
439 | 723k | return; |
440 | 723k | } |
441 | 2.46M | } |
442 | 724k | } |
443 | | |
444 | | /** Look up the stack to see if we're running assertions for this object. */ |
445 | | bool alreadyExecutingInvariants(HeapObject *self) |
446 | 251k | { |
447 | 48.5M | for (int i = stack.size() - 1; i >= 0; --i) { |
448 | 48.3M | if (stack[i].kind == FRAME_INVARIANTS) { |
449 | 2.45M | if (stack[i].self == self) |
450 | 0 | return true; |
451 | 2.45M | } |
452 | 48.3M | } |
453 | 251k | return false; |
454 | 251k | } |
455 | | }; |
456 | | |
457 | | /** Typedef to save some typing. */ |
458 | | typedef std::map<std::string, VmExt> ExtMap; |
459 | | |
460 | | /** Typedef to save some typing. */ |
461 | | typedef std::map<std::string, std::string> StrMap; |
462 | | |
463 | | class Interpreter; |
464 | | |
465 | | typedef const AST *(Interpreter::*BuiltinFunc)(const LocationRange &loc, |
466 | | const std::vector<Value> &args); |
467 | | |
468 | | /** Holds the intermediate state during execution and implements the necessary functions to |
469 | | * implement the semantics of the language. |
470 | | * |
471 | | * The garbage collector used is a simple stop-the-world mark and sweep collector. It runs upon |
472 | | * memory allocation if the heap is large enough and has grown enough since the last collection. |
473 | | * All reachable entities have their mark field incremented. Then all entities with the old |
474 | | * mark are removed from the heap. |
475 | | */ |
476 | | class Interpreter { |
477 | | /** The heap. */ |
478 | | Heap heap; |
479 | | |
480 | | /** The value last computed. */ |
481 | | Value scratch; |
482 | | |
483 | | /** The stack. */ |
484 | | Stack stack; |
485 | | |
486 | | /** Used to create ASTs if needed. |
487 | | * |
488 | | * This is used at import time, and in a few other cases. |
489 | | */ |
490 | | Allocator *alloc; |
491 | | |
492 | | /** Used to "name" thunks created to cache imports. */ |
493 | | const Identifier *idImport; |
494 | | |
495 | | /** Used to "name" thunks created on the inside of an array. */ |
496 | | const Identifier *idArrayElement; |
497 | | |
498 | | /** Used to "name" thunks created to execute invariants. */ |
499 | | const Identifier *idInvariant; |
500 | | |
501 | | /** Placehodler name for internal AST. */ |
502 | | const Identifier *idInternal; |
503 | | |
504 | | /** Used to "name" thunks created to convert JSON to Jsonnet objects. */ |
505 | | const Identifier *idJsonObjVar; |
506 | | |
507 | | const Identifier *idEmpty; |
508 | | |
509 | | /** Used to refer to idJsonObjVar. */ |
510 | | const AST *jsonObjVar; |
511 | | |
512 | | /* Standard Library AST */ |
513 | | const DesugaredObject *stdlibAST; |
514 | | HeapObject *stdObject; |
515 | | |
516 | | struct ImportCacheValue { |
517 | | std::string foundHere; |
518 | | std::string content; |
519 | | /** Thunk to store cached result of execution. |
520 | | * |
521 | | * Null if this file was only ever successfully imported with importstr. |
522 | | */ |
523 | | HeapThunk *thunk; |
524 | | }; |
525 | | |
526 | | /** Cache for imported Jsonnet files. */ |
527 | | std::map<std::pair<std::string, UString>, ImportCacheValue *> cachedImports; |
528 | | |
529 | | /** External variables for std.extVar. */ |
530 | | ExtMap externalVars; |
531 | | |
532 | | /** The callback used for loading imported files. */ |
533 | | VmNativeCallbackMap nativeCallbacks; |
534 | | |
535 | | /** The callback used for loading imported files. */ |
536 | | JsonnetImportCallback *importCallback; |
537 | | |
538 | | /** User context pointer for the import callback. */ |
539 | | void *importCallbackContext; |
540 | | |
541 | | /** Builtin functions by name. */ |
542 | | typedef std::map<std::string, BuiltinFunc> BuiltinMap; |
543 | | BuiltinMap builtins; |
544 | | |
545 | | /** Source values by name. Source values are values (usually functions) |
546 | | * implemented as Jsonnet source which we use internally in the interpreter. |
547 | | * In a sense they are the opposite of builtins. */ |
548 | | typedef std::map<std::string, HeapThunk *> SourceFuncMap; |
549 | | SourceFuncMap sourceVals; |
550 | | /* Just for memory management. */ |
551 | | std::vector<std::unique_ptr<Identifier>> sourceFuncIds; |
552 | | |
553 | | RuntimeError makeError(const LocationRange &loc, const std::string &msg) |
554 | 75 | { |
555 | 75 | return stack.makeError(loc, msg); |
556 | 75 | } |
557 | | |
558 | | /** Create an object on the heap, maybe collect garbage. |
559 | | * \param T Something under HeapEntity |
560 | | * \returns The new object |
561 | | */ |
562 | | template <class T, class... Args> |
563 | | T *makeHeap(Args &&... args) |
564 | 7.85M | { |
565 | 7.85M | T *r = heap.makeEntity<T, Args...>(std::forward<Args>(args)...); |
566 | 7.85M | if (heap.checkHeap()) { // Do a GC cycle? |
567 | | // Avoid the object we just made being collected. |
568 | 1.64k | heap.markFrom(r); |
569 | | |
570 | | // Mark from the stack. |
571 | 1.64k | stack.mark(heap); |
572 | | |
573 | | // Mark from the scratch register |
574 | 1.64k | heap.markFrom(scratch); |
575 | | |
576 | | // Mark from cached imports |
577 | 1.64k | for (const auto &pair : cachedImports) { |
578 | 0 | HeapThunk *thunk = pair.second->thunk; |
579 | 0 | if (thunk != nullptr) |
580 | 0 | heap.markFrom(thunk); |
581 | 0 | } |
582 | | |
583 | 208k | for (const auto &sourceVal : sourceVals) { |
584 | 208k | heap.markFrom(sourceVal.second); |
585 | 208k | } |
586 | | |
587 | | // Delete unreachable objects. |
588 | 1.64k | heap.sweep(); |
589 | 1.64k | } |
590 | 7.85M | return r; |
591 | 7.85M | } Unexecuted instantiation: vm.cpp:(anonymous namespace)::HeapThunk* (anonymous namespace)::Interpreter::makeHeap<(anonymous namespace)::HeapThunk, Identifier const*&, (anonymous namespace)::HeapObject* const&, unsigned int const&, AST const* const&>(Identifier const*&, (anonymous namespace)::HeapObject* const&, unsigned int const&, AST const* const&) Unexecuted instantiation: vm.cpp:(anonymous namespace)::HeapThunk* (anonymous namespace)::Interpreter::makeHeap<(anonymous namespace)::HeapThunk, Identifier const* const&, decltype(nullptr), int, decltype(nullptr)>(Identifier const* const&, decltype(nullptr)&&, int&&, decltype(nullptr)&&) vm.cpp:(anonymous namespace)::HeapArray* (anonymous namespace)::Interpreter::makeHeap<(anonymous namespace)::HeapArray, std::__1::vector<(anonymous namespace)::HeapThunk*, std::__1::allocator<(anonymous namespace)::HeapThunk*> > const&>(std::__1::vector<(anonymous namespace)::HeapThunk*, std::__1::allocator<(anonymous namespace)::HeapThunk*> > const&) Line | Count | Source | 564 | 925 | { | 565 | 925 | T *r = heap.makeEntity<T, Args...>(std::forward<Args>(args)...); | 566 | 925 | if (heap.checkHeap()) { // Do a GC cycle? | 567 | | // Avoid the object we just made being collected. | 568 | 0 | heap.markFrom(r); | 569 | | | 570 | | // Mark from the stack. | 571 | 0 | stack.mark(heap); | 572 | | | 573 | | // Mark from the scratch register | 574 | 0 | heap.markFrom(scratch); | 575 | | | 576 | | // Mark from cached imports | 577 | 0 | for (const auto &pair : cachedImports) { | 578 | 0 | HeapThunk *thunk = pair.second->thunk; | 579 | 0 | if (thunk != nullptr) | 580 | 0 | heap.markFrom(thunk); | 581 | 0 | } | 582 | |
| 583 | 0 | for (const auto &sourceVal : sourceVals) { | 584 | 0 | heap.markFrom(sourceVal.second); | 585 | 0 | } | 586 | | | 587 | | // Delete unreachable objects. | 588 | 0 | heap.sweep(); | 589 | 0 | } | 590 | 925 | return r; | 591 | 925 | } |
vm.cpp:(anonymous namespace)::HeapString* (anonymous namespace)::Interpreter::makeHeap<(anonymous namespace)::HeapString, std::__1::basic_string<char32_t, std::__1::char_traits<char32_t>, std::__1::allocator<char32_t> > const&>(std::__1::basic_string<char32_t, std::__1::char_traits<char32_t>, std::__1::allocator<char32_t> > const&) Line | Count | Source | 564 | 787k | { | 565 | 787k | T *r = heap.makeEntity<T, Args...>(std::forward<Args>(args)...); | 566 | 787k | if (heap.checkHeap()) { // Do a GC cycle? | 567 | | // Avoid the object we just made being collected. | 568 | 459 | heap.markFrom(r); | 569 | | | 570 | | // Mark from the stack. | 571 | 459 | stack.mark(heap); | 572 | | | 573 | | // Mark from the scratch register | 574 | 459 | heap.markFrom(scratch); | 575 | | | 576 | | // Mark from cached imports | 577 | 459 | for (const auto &pair : cachedImports) { | 578 | 0 | HeapThunk *thunk = pair.second->thunk; | 579 | 0 | if (thunk != nullptr) | 580 | 0 | heap.markFrom(thunk); | 581 | 0 | } | 582 | | | 583 | 58.2k | for (const auto &sourceVal : sourceVals) { | 584 | 58.2k | heap.markFrom(sourceVal.second); | 585 | 58.2k | } | 586 | | | 587 | | // Delete unreachable objects. | 588 | 459 | heap.sweep(); | 589 | 459 | } | 590 | 787k | return r; | 591 | 787k | } |
vm.cpp:(anonymous namespace)::HeapThunk* (anonymous namespace)::Interpreter::makeHeap<(anonymous namespace)::HeapThunk, Identifier const*&, decltype(nullptr), int, decltype(nullptr)>(Identifier const*&, decltype(nullptr)&&, int&&, decltype(nullptr)&&) Line | Count | Source | 564 | 68 | { | 565 | 68 | T *r = heap.makeEntity<T, Args...>(std::forward<Args>(args)...); | 566 | 68 | if (heap.checkHeap()) { // Do a GC cycle? | 567 | | // Avoid the object we just made being collected. | 568 | 0 | heap.markFrom(r); | 569 | | | 570 | | // Mark from the stack. | 571 | 0 | stack.mark(heap); | 572 | | | 573 | | // Mark from the scratch register | 574 | 0 | heap.markFrom(scratch); | 575 | | | 576 | | // Mark from cached imports | 577 | 0 | for (const auto &pair : cachedImports) { | 578 | 0 | HeapThunk *thunk = pair.second->thunk; | 579 | 0 | if (thunk != nullptr) | 580 | 0 | heap.markFrom(thunk); | 581 | 0 | } | 582 | |
| 583 | 0 | for (const auto &sourceVal : sourceVals) { | 584 | 0 | heap.markFrom(sourceVal.second); | 585 | 0 | } | 586 | | | 587 | | // Delete unreachable objects. | 588 | 0 | heap.sweep(); | 589 | 0 | } | 590 | 68 | return r; | 591 | 68 | } |
vm.cpp:(anonymous namespace)::HeapClosure* (anonymous namespace)::Interpreter::makeHeap<(anonymous namespace)::HeapClosure, std::__1::map<Identifier const*, (anonymous namespace)::HeapThunk*, std::__1::less<Identifier const*>, std::__1::allocator<std::__1::pair<Identifier const* const, (anonymous namespace)::HeapThunk*> > >, decltype(nullptr), int, std::__1::vector<(anonymous namespace)::HeapClosure::Param, std::__1::allocator<(anonymous namespace)::HeapClosure::Param> > const&, AST*&, std::__1::basic_string<char, std::__1::char_traits<char>, std::__1::allocator<char> > const&>(std::__1::map<Identifier const*, (anonymous namespace)::HeapThunk*, std::__1::less<Identifier const*>, std::__1::allocator<std::__1::pair<Identifier const* const, (anonymous namespace)::HeapThunk*> > >&&, decltype(nullptr)&&, int&&, std::__1::vector<(anonymous namespace)::HeapClosure::Param, std::__1::allocator<(anonymous namespace)::HeapClosure::Param> > const&, AST*&, std::__1::basic_string<char, std::__1::char_traits<char>, std::__1::allocator<char> > const&) Line | Count | Source | 564 | 206k | { | 565 | 206k | T *r = heap.makeEntity<T, Args...>(std::forward<Args>(args)...); | 566 | 206k | if (heap.checkHeap()) { // Do a GC cycle? | 567 | | // Avoid the object we just made being collected. | 568 | 105 | heap.markFrom(r); | 569 | | | 570 | | // Mark from the stack. | 571 | 105 | stack.mark(heap); | 572 | | | 573 | | // Mark from the scratch register | 574 | 105 | heap.markFrom(scratch); | 575 | | | 576 | | // Mark from cached imports | 577 | 105 | for (const auto &pair : cachedImports) { | 578 | 0 | HeapThunk *thunk = pair.second->thunk; | 579 | 0 | if (thunk != nullptr) | 580 | 0 | heap.markFrom(thunk); | 581 | 0 | } | 582 | | | 583 | 13.3k | for (const auto &sourceVal : sourceVals) { | 584 | 13.3k | heap.markFrom(sourceVal.second); | 585 | 13.3k | } | 586 | | | 587 | | // Delete unreachable objects. | 588 | 105 | heap.sweep(); | 589 | 105 | } | 590 | 206k | return r; | 591 | 206k | } |
Unexecuted instantiation: vm.cpp:(anonymous namespace)::HeapComprehensionObject* (anonymous namespace)::Interpreter::makeHeap<(anonymous namespace)::HeapComprehensionObject, std::__1::map<Identifier const*, (anonymous namespace)::HeapThunk*, std::__1::less<Identifier const*>, std::__1::allocator<std::__1::pair<Identifier const* const, (anonymous namespace)::HeapThunk*> > >&, AST const*&, Identifier const*&, std::__1::map<Identifier const*, (anonymous namespace)::HeapThunk*, std::__1::less<Identifier const*>, std::__1::allocator<std::__1::pair<Identifier const* const, (anonymous namespace)::HeapThunk*> > >&>(std::__1::map<Identifier const*, (anonymous namespace)::HeapThunk*, std::__1::less<Identifier const*>, std::__1::allocator<std::__1::pair<Identifier const* const, (anonymous namespace)::HeapThunk*> > >&, AST const*&, Identifier const*&, std::__1::map<Identifier const*, (anonymous namespace)::HeapThunk*, std::__1::less<Identifier const*>, std::__1::allocator<std::__1::pair<Identifier const* const, (anonymous namespace)::HeapThunk*> > >&) vm.cpp:(anonymous namespace)::HeapThunk* (anonymous namespace)::Interpreter::makeHeap<(anonymous namespace)::HeapThunk, decltype(nullptr), decltype(nullptr), int, AST const*>(decltype(nullptr)&&, decltype(nullptr)&&, int&&, AST const*&&) Line | Count | Source | 564 | 214 | { | 565 | 214 | T *r = heap.makeEntity<T, Args...>(std::forward<Args>(args)...); | 566 | 214 | if (heap.checkHeap()) { // Do a GC cycle? | 567 | | // Avoid the object we just made being collected. | 568 | 0 | heap.markFrom(r); | 569 | | | 570 | | // Mark from the stack. | 571 | 0 | stack.mark(heap); | 572 | | | 573 | | // Mark from the scratch register | 574 | 0 | heap.markFrom(scratch); | 575 | | | 576 | | // Mark from cached imports | 577 | 0 | for (const auto &pair : cachedImports) { | 578 | 0 | HeapThunk *thunk = pair.second->thunk; | 579 | 0 | if (thunk != nullptr) | 580 | 0 | heap.markFrom(thunk); | 581 | 0 | } | 582 | |
| 583 | 0 | for (const auto &sourceVal : sourceVals) { | 584 | 0 | heap.markFrom(sourceVal.second); | 585 | 0 | } | 586 | | | 587 | | // Delete unreachable objects. | 588 | 0 | heap.sweep(); | 589 | 0 | } | 590 | 214 | return r; | 591 | 214 | } |
vm.cpp:(anonymous namespace)::HeapThunk* (anonymous namespace)::Interpreter::makeHeap<(anonymous namespace)::HeapThunk, Identifier*, (anonymous namespace)::HeapObject*&, int, AST* const&>(Identifier*&&, (anonymous namespace)::HeapObject*&, int&&, AST* const&) Line | Count | Source | 564 | 27.1k | { | 565 | 27.1k | T *r = heap.makeEntity<T, Args...>(std::forward<Args>(args)...); | 566 | 27.1k | if (heap.checkHeap()) { // Do a GC cycle? | 567 | | // Avoid the object we just made being collected. | 568 | 0 | heap.markFrom(r); | 569 | | | 570 | | // Mark from the stack. | 571 | 0 | stack.mark(heap); | 572 | | | 573 | | // Mark from the scratch register | 574 | 0 | heap.markFrom(scratch); | 575 | | | 576 | | // Mark from cached imports | 577 | 0 | for (const auto &pair : cachedImports) { | 578 | 0 | HeapThunk *thunk = pair.second->thunk; | 579 | 0 | if (thunk != nullptr) | 580 | 0 | heap.markFrom(thunk); | 581 | 0 | } | 582 | |
| 583 | 0 | for (const auto &sourceVal : sourceVals) { | 584 | 0 | heap.markFrom(sourceVal.second); | 585 | 0 | } | 586 | | | 587 | | // Delete unreachable objects. | 588 | 0 | heap.sweep(); | 589 | 0 | } | 590 | 27.1k | return r; | 591 | 27.1k | } |
vm.cpp:(anonymous namespace)::HeapThunk* (anonymous namespace)::Interpreter::makeHeap<(anonymous namespace)::HeapThunk, Identifier const*&, (anonymous namespace)::HeapObject*&, unsigned int&, AST* const&>(Identifier const*&, (anonymous namespace)::HeapObject*&, unsigned int&, AST* const&) Line | Count | Source | 564 | 6.13M | { | 565 | 6.13M | T *r = heap.makeEntity<T, Args...>(std::forward<Args>(args)...); | 566 | 6.13M | if (heap.checkHeap()) { // Do a GC cycle? | 567 | | // Avoid the object we just made being collected. | 568 | 741 | heap.markFrom(r); | 569 | | | 570 | | // Mark from the stack. | 571 | 741 | stack.mark(heap); | 572 | | | 573 | | // Mark from the scratch register | 574 | 741 | heap.markFrom(scratch); | 575 | | | 576 | | // Mark from cached imports | 577 | 741 | for (const auto &pair : cachedImports) { | 578 | 0 | HeapThunk *thunk = pair.second->thunk; | 579 | 0 | if (thunk != nullptr) | 580 | 0 | heap.markFrom(thunk); | 581 | 0 | } | 582 | | | 583 | 94.1k | for (const auto &sourceVal : sourceVals) { | 584 | 94.1k | heap.markFrom(sourceVal.second); | 585 | 94.1k | } | 586 | | | 587 | | // Delete unreachable objects. | 588 | 741 | heap.sweep(); | 589 | 741 | } | 590 | 6.13M | return r; | 591 | 6.13M | } |
vm.cpp:(anonymous namespace)::HeapClosure* (anonymous namespace)::Interpreter::makeHeap<(anonymous namespace)::HeapClosure, std::__1::map<Identifier const*, (anonymous namespace)::HeapThunk*, std::__1::less<Identifier const*>, std::__1::allocator<std::__1::pair<Identifier const* const, (anonymous namespace)::HeapThunk*> > > const&, (anonymous namespace)::HeapObject*&, unsigned int&, std::__1::vector<(anonymous namespace)::HeapClosure::Param, std::__1::allocator<(anonymous namespace)::HeapClosure::Param> > const&, AST*&, char const (&) [1]>(std::__1::map<Identifier const*, (anonymous namespace)::HeapThunk*, std::__1::less<Identifier const*>, std::__1::allocator<std::__1::pair<Identifier const* const, (anonymous namespace)::HeapThunk*> > > const&, (anonymous namespace)::HeapObject*&, unsigned int&, std::__1::vector<(anonymous namespace)::HeapClosure::Param, std::__1::allocator<(anonymous namespace)::HeapClosure::Param> > const&, AST*&, char const (&) [1]) Line | Count | Source | 564 | 37.9k | { | 565 | 37.9k | T *r = heap.makeEntity<T, Args...>(std::forward<Args>(args)...); | 566 | 37.9k | if (heap.checkHeap()) { // Do a GC cycle? | 567 | | // Avoid the object we just made being collected. | 568 | 3 | heap.markFrom(r); | 569 | | | 570 | | // Mark from the stack. | 571 | 3 | stack.mark(heap); | 572 | | | 573 | | // Mark from the scratch register | 574 | 3 | heap.markFrom(scratch); | 575 | | | 576 | | // Mark from cached imports | 577 | 3 | for (const auto &pair : cachedImports) { | 578 | 0 | HeapThunk *thunk = pair.second->thunk; | 579 | 0 | if (thunk != nullptr) | 580 | 0 | heap.markFrom(thunk); | 581 | 0 | } | 582 | | | 583 | 381 | for (const auto &sourceVal : sourceVals) { | 584 | 381 | heap.markFrom(sourceVal.second); | 585 | 381 | } | 586 | | | 587 | | // Delete unreachable objects. | 588 | 3 | heap.sweep(); | 589 | 3 | } | 590 | 37.9k | return r; | 591 | 37.9k | } |
Unexecuted instantiation: vm.cpp:(anonymous namespace)::HeapThunk* (anonymous namespace)::Interpreter::makeHeap<(anonymous namespace)::HeapThunk, Identifier const*&, decltype(nullptr), int, AST*&>(Identifier const*&, decltype(nullptr)&&, int&&, AST*&) vm.cpp:(anonymous namespace)::HeapThunk* (anonymous namespace)::Interpreter::makeHeap<(anonymous namespace)::HeapThunk, Identifier const* const&, (anonymous namespace)::HeapObject*&, unsigned int&, AST* const&>(Identifier const* const&, (anonymous namespace)::HeapObject*&, unsigned int&, AST* const&) Line | Count | Source | 564 | 323k | { | 565 | 323k | T *r = heap.makeEntity<T, Args...>(std::forward<Args>(args)...); | 566 | 323k | if (heap.checkHeap()) { // Do a GC cycle? | 567 | | // Avoid the object we just made being collected. | 568 | 235 | heap.markFrom(r); | 569 | | | 570 | | // Mark from the stack. | 571 | 235 | stack.mark(heap); | 572 | | | 573 | | // Mark from the scratch register | 574 | 235 | heap.markFrom(scratch); | 575 | | | 576 | | // Mark from cached imports | 577 | 235 | for (const auto &pair : cachedImports) { | 578 | 0 | HeapThunk *thunk = pair.second->thunk; | 579 | 0 | if (thunk != nullptr) | 580 | 0 | heap.markFrom(thunk); | 581 | 0 | } | 582 | | | 583 | 29.8k | for (const auto &sourceVal : sourceVals) { | 584 | 29.8k | heap.markFrom(sourceVal.second); | 585 | 29.8k | } | 586 | | | 587 | | // Delete unreachable objects. | 588 | 235 | heap.sweep(); | 589 | 235 | } | 590 | 323k | return r; | 591 | 323k | } |
vm.cpp:(anonymous namespace)::HeapSimpleObject* (anonymous namespace)::Interpreter::makeHeap<(anonymous namespace)::HeapSimpleObject, std::__1::map<Identifier const*, (anonymous namespace)::HeapThunk*, std::__1::less<Identifier const*>, std::__1::allocator<std::__1::pair<Identifier const* const, (anonymous namespace)::HeapThunk*> > >&, std::__1::map<Identifier const*, (anonymous namespace)::HeapSimpleObject::Field, std::__1::less<Identifier const*>, std::__1::allocator<std::__1::pair<Identifier const* const, (anonymous namespace)::HeapSimpleObject::Field> > >&, std::__1::list<AST*, std::__1::allocator<AST*> >&>(std::__1::map<Identifier const*, (anonymous namespace)::HeapThunk*, std::__1::less<Identifier const*>, std::__1::allocator<std::__1::pair<Identifier const* const, (anonymous namespace)::HeapThunk*> > >&, std::__1::map<Identifier const*, (anonymous namespace)::HeapSimpleObject::Field, std::__1::less<Identifier const*>, std::__1::allocator<std::__1::pair<Identifier const* const, (anonymous namespace)::HeapSimpleObject::Field> > >&, std::__1::list<AST*, std::__1::allocator<AST*> >&) Line | Count | Source | 564 | 26.4k | { | 565 | 26.4k | T *r = heap.makeEntity<T, Args...>(std::forward<Args>(args)...); | 566 | 26.4k | if (heap.checkHeap()) { // Do a GC cycle? | 567 | | // Avoid the object we just made being collected. | 568 | 21 | heap.markFrom(r); | 569 | | | 570 | | // Mark from the stack. | 571 | 21 | stack.mark(heap); | 572 | | | 573 | | // Mark from the scratch register | 574 | 21 | heap.markFrom(scratch); | 575 | | | 576 | | // Mark from cached imports | 577 | 21 | for (const auto &pair : cachedImports) { | 578 | 0 | HeapThunk *thunk = pair.second->thunk; | 579 | 0 | if (thunk != nullptr) | 580 | 0 | heap.markFrom(thunk); | 581 | 0 | } | 582 | | | 583 | 2.66k | for (const auto &sourceVal : sourceVals) { | 584 | 2.66k | heap.markFrom(sourceVal.second); | 585 | 2.66k | } | 586 | | | 587 | | // Delete unreachable objects. | 588 | 21 | heap.sweep(); | 589 | 21 | } | 590 | 26.4k | return r; | 591 | 26.4k | } |
Unexecuted instantiation: vm.cpp:(anonymous namespace)::HeapThunk* (anonymous namespace)::Interpreter::makeHeap<(anonymous namespace)::HeapThunk, Identifier const*&, (anonymous namespace)::HeapObject*&, unsigned int&, AST const* const&>(Identifier const*&, (anonymous namespace)::HeapObject*&, unsigned int&, AST const* const&) vm.cpp:(anonymous namespace)::HeapExtendedObject* (anonymous namespace)::Interpreter::makeHeap<(anonymous namespace)::HeapExtendedObject, (anonymous namespace)::HeapObject*&, (anonymous namespace)::HeapObject*&>((anonymous namespace)::HeapObject*&, (anonymous namespace)::HeapObject*&) Line | Count | Source | 564 | 11.4k | { | 565 | 11.4k | T *r = heap.makeEntity<T, Args...>(std::forward<Args>(args)...); | 566 | 11.4k | if (heap.checkHeap()) { // Do a GC cycle? | 567 | | // Avoid the object we just made being collected. | 568 | 1 | heap.markFrom(r); | 569 | | | 570 | | // Mark from the stack. | 571 | 1 | stack.mark(heap); | 572 | | | 573 | | // Mark from the scratch register | 574 | 1 | heap.markFrom(scratch); | 575 | | | 576 | | // Mark from cached imports | 577 | 1 | for (const auto &pair : cachedImports) { | 578 | 0 | HeapThunk *thunk = pair.second->thunk; | 579 | 0 | if (thunk != nullptr) | 580 | 0 | heap.markFrom(thunk); | 581 | 0 | } | 582 | | | 583 | 127 | for (const auto &sourceVal : sourceVals) { | 584 | 127 | heap.markFrom(sourceVal.second); | 585 | 127 | } | 586 | | | 587 | | // Delete unreachable objects. | 588 | 1 | heap.sweep(); | 589 | 1 | } | 590 | 11.4k | return r; | 591 | 11.4k | } |
vm.cpp:(anonymous namespace)::HeapThunk* (anonymous namespace)::Interpreter::makeHeap<(anonymous namespace)::HeapThunk, Identifier const*&, (anonymous namespace)::HeapObject*&, unsigned int&, AST*&>(Identifier const*&, (anonymous namespace)::HeapObject*&, unsigned int&, AST*&) Line | Count | Source | 564 | 298k | { | 565 | 298k | T *r = heap.makeEntity<T, Args...>(std::forward<Args>(args)...); | 566 | 298k | if (heap.checkHeap()) { // Do a GC cycle? | 567 | | // Avoid the object we just made being collected. | 568 | 79 | heap.markFrom(r); | 569 | | | 570 | | // Mark from the stack. | 571 | 79 | stack.mark(heap); | 572 | | | 573 | | // Mark from the scratch register | 574 | 79 | heap.markFrom(scratch); | 575 | | | 576 | | // Mark from cached imports | 577 | 79 | for (const auto &pair : cachedImports) { | 578 | 0 | HeapThunk *thunk = pair.second->thunk; | 579 | 0 | if (thunk != nullptr) | 580 | 0 | heap.markFrom(thunk); | 581 | 0 | } | 582 | | | 583 | 10.0k | for (const auto &sourceVal : sourceVals) { | 584 | 10.0k | heap.markFrom(sourceVal.second); | 585 | 10.0k | } | 586 | | | 587 | | // Delete unreachable objects. | 588 | 79 | heap.sweep(); | 589 | 79 | } | 590 | 298k | return r; | 591 | 298k | } |
Unexecuted instantiation: vm.cpp:(anonymous namespace)::HeapComprehensionObject* (anonymous namespace)::Interpreter::makeHeap<(anonymous namespace)::HeapComprehensionObject, std::__1::map<Identifier const*, (anonymous namespace)::HeapThunk*, std::__1::less<Identifier const*>, std::__1::allocator<std::__1::pair<Identifier const* const, (anonymous namespace)::HeapThunk*> > >&, AST*&, Identifier const*&, std::__1::map<Identifier const*, (anonymous namespace)::HeapThunk*, std::__1::less<Identifier const*>, std::__1::allocator<std::__1::pair<Identifier const* const, (anonymous namespace)::HeapThunk*> > >&>(std::__1::map<Identifier const*, (anonymous namespace)::HeapThunk*, std::__1::less<Identifier const*>, std::__1::allocator<std::__1::pair<Identifier const* const, (anonymous namespace)::HeapThunk*> > >&, AST*&, Identifier const*&, std::__1::map<Identifier const*, (anonymous namespace)::HeapThunk*, std::__1::less<Identifier const*>, std::__1::allocator<std::__1::pair<Identifier const* const, (anonymous namespace)::HeapThunk*> > >&) |
592 | | |
593 | | Value makeBoolean(bool v) |
594 | 206k | { |
595 | 206k | Value r; |
596 | 206k | r.t = Value::BOOLEAN; |
597 | 206k | r.v.b = v; |
598 | 206k | return r; |
599 | 206k | } |
600 | | |
601 | | Value makeNumber(double v) |
602 | 2.75M | { |
603 | 2.75M | Value r; |
604 | 2.75M | r.t = Value::NUMBER; |
605 | 2.75M | r.v.d = v; |
606 | 2.75M | return r; |
607 | 2.75M | } |
608 | | |
609 | | Value makeNumberCheck(const LocationRange &loc, double v) |
610 | 2.72M | { |
611 | 2.72M | if (std::isnan(v)) { |
612 | 0 | throw makeError(loc, "not a number"); |
613 | 0 | } |
614 | 2.72M | if (std::isinf(v)) { |
615 | 0 | throw makeError(loc, "overflow"); |
616 | 0 | } |
617 | 2.72M | return makeNumber(v); |
618 | 2.72M | } |
619 | | |
620 | | Value makeNull(void) |
621 | 3.49k | { |
622 | 3.49k | Value r; |
623 | 3.49k | r.t = Value::NULL_TYPE; |
624 | 3.49k | return r; |
625 | 3.49k | } |
626 | | |
627 | | Value makeArray(const std::vector<HeapThunk *> &v) |
628 | 925 | { |
629 | 925 | Value r; |
630 | 925 | r.t = Value::ARRAY; |
631 | 925 | r.v.h = makeHeap<HeapArray>(v); |
632 | 925 | return r; |
633 | 925 | } |
634 | | |
635 | | Value makeClosure(const BindingFrame &env, HeapObject *self, unsigned offset, |
636 | | const HeapClosure::Params ¶ms, AST *body) |
637 | 37.9k | { |
638 | 37.9k | Value r; |
639 | 37.9k | r.t = Value::FUNCTION; |
640 | 37.9k | r.v.h = makeHeap<HeapClosure>(env, self, offset, params, body, ""); |
641 | 37.9k | return r; |
642 | 37.9k | } |
643 | | |
644 | | Value makeNativeBuiltin(const std::string &name, const std::vector<std::string> ¶ms) |
645 | 0 | { |
646 | 0 | HeapClosure::Params hc_params; |
647 | 0 | for (const auto &p : params) { |
648 | 0 | hc_params.emplace_back(alloc->makeIdentifier(decode_utf8(p)), nullptr); |
649 | 0 | } |
650 | 0 | return makeBuiltin(name, hc_params); |
651 | 0 | } |
652 | | |
653 | | Value makeBuiltin(const std::string &name, const HeapClosure::Params ¶ms) |
654 | 206k | { |
655 | 206k | AST *body = nullptr; |
656 | 206k | Value r; |
657 | 206k | r.t = Value::FUNCTION; |
658 | 206k | r.v.h = makeHeap<HeapClosure>(BindingFrame(), nullptr, 0, params, body, name); |
659 | 206k | return r; |
660 | 206k | } |
661 | | |
662 | | template <class T, class... Args> |
663 | | Value makeObject(Args... args) |
664 | 37.8k | { |
665 | 37.8k | Value r; |
666 | 37.8k | r.t = Value::OBJECT; |
667 | 37.8k | r.v.h = makeHeap<T>(args...); |
668 | 37.8k | return r; |
669 | 37.8k | } Unexecuted instantiation: vm.cpp:(anonymous namespace)::Value (anonymous namespace)::Interpreter::makeObject<(anonymous namespace)::HeapComprehensionObject, std::__1::map<Identifier const*, (anonymous namespace)::HeapThunk*, std::__1::less<Identifier const*>, std::__1::allocator<std::__1::pair<Identifier const* const, (anonymous namespace)::HeapThunk*> > >, AST const*, Identifier const*, std::__1::map<Identifier const*, (anonymous namespace)::HeapThunk*, std::__1::less<Identifier const*>, std::__1::allocator<std::__1::pair<Identifier const* const, (anonymous namespace)::HeapThunk*> > > >(std::__1::map<Identifier const*, (anonymous namespace)::HeapThunk*, std::__1::less<Identifier const*>, std::__1::allocator<std::__1::pair<Identifier const* const, (anonymous namespace)::HeapThunk*> > >, AST const*, Identifier const*, std::__1::map<Identifier const*, (anonymous namespace)::HeapThunk*, std::__1::less<Identifier const*>, std::__1::allocator<std::__1::pair<Identifier const* const, (anonymous namespace)::HeapThunk*> > >) vm.cpp:(anonymous namespace)::Value (anonymous namespace)::Interpreter::makeObject<(anonymous namespace)::HeapSimpleObject, std::__1::map<Identifier const*, (anonymous namespace)::HeapThunk*, std::__1::less<Identifier const*>, std::__1::allocator<std::__1::pair<Identifier const* const, (anonymous namespace)::HeapThunk*> > >, std::__1::map<Identifier const*, (anonymous namespace)::HeapSimpleObject::Field, std::__1::less<Identifier const*>, std::__1::allocator<std::__1::pair<Identifier const* const, (anonymous namespace)::HeapSimpleObject::Field> > >, std::__1::list<AST*, std::__1::allocator<AST*> > >(std::__1::map<Identifier const*, (anonymous namespace)::HeapThunk*, std::__1::less<Identifier const*>, std::__1::allocator<std::__1::pair<Identifier const* const, (anonymous namespace)::HeapThunk*> > >, std::__1::map<Identifier const*, (anonymous namespace)::HeapSimpleObject::Field, std::__1::less<Identifier const*>, std::__1::allocator<std::__1::pair<Identifier const* const, (anonymous namespace)::HeapSimpleObject::Field> > >, std::__1::list<AST*, std::__1::allocator<AST*> >) Line | Count | Source | 664 | 26.4k | { | 665 | 26.4k | Value r; | 666 | 26.4k | r.t = Value::OBJECT; | 667 | 26.4k | r.v.h = makeHeap<T>(args...); | 668 | 26.4k | return r; | 669 | 26.4k | } |
vm.cpp:(anonymous namespace)::Value (anonymous namespace)::Interpreter::makeObject<(anonymous namespace)::HeapExtendedObject, (anonymous namespace)::HeapObject*, (anonymous namespace)::HeapObject*>((anonymous namespace)::HeapObject*, (anonymous namespace)::HeapObject*) Line | Count | Source | 664 | 11.4k | { | 665 | 11.4k | Value r; | 666 | 11.4k | r.t = Value::OBJECT; | 667 | 11.4k | r.v.h = makeHeap<T>(args...); | 668 | 11.4k | return r; | 669 | 11.4k | } |
Unexecuted instantiation: vm.cpp:(anonymous namespace)::Value (anonymous namespace)::Interpreter::makeObject<(anonymous namespace)::HeapComprehensionObject, std::__1::map<Identifier const*, (anonymous namespace)::HeapThunk*, std::__1::less<Identifier const*>, std::__1::allocator<std::__1::pair<Identifier const* const, (anonymous namespace)::HeapThunk*> > >, AST*, Identifier const*, std::__1::map<Identifier const*, (anonymous namespace)::HeapThunk*, std::__1::less<Identifier const*>, std::__1::allocator<std::__1::pair<Identifier const* const, (anonymous namespace)::HeapThunk*> > > >(std::__1::map<Identifier const*, (anonymous namespace)::HeapThunk*, std::__1::less<Identifier const*>, std::__1::allocator<std::__1::pair<Identifier const* const, (anonymous namespace)::HeapThunk*> > >, AST*, Identifier const*, std::__1::map<Identifier const*, (anonymous namespace)::HeapThunk*, std::__1::less<Identifier const*>, std::__1::allocator<std::__1::pair<Identifier const* const, (anonymous namespace)::HeapThunk*> > >) |
670 | | |
671 | | Value makeString(const UString &v) |
672 | 787k | { |
673 | 787k | Value r; |
674 | 787k | r.t = Value::STRING; |
675 | 787k | r.v.h = makeHeap<HeapString>(v); |
676 | 787k | return r; |
677 | 787k | } |
678 | | |
679 | | /** Auxiliary function of objectIndex. |
680 | | * |
681 | | * Traverse the object's tree from right to left, looking for an object |
682 | | * with the given field. Call with offset initially set to 0. |
683 | | * |
684 | | * \param f The field we're looking for. |
685 | | * \param start_from Step over this many leaves first. |
686 | | * \param counter Return the level of "super" that contained the field. |
687 | | * \returns The first object with the field, or nullptr if it could not be found. |
688 | | */ |
689 | | HeapLeafObject *findObject(const Identifier *f, HeapObject *curr, unsigned start_from, |
690 | | unsigned &counter) |
691 | 4.24M | { |
692 | 4.24M | if (auto *ext = dynamic_cast<HeapExtendedObject *>(curr)) { |
693 | 1.99M | auto *r = findObject(f, ext->right, start_from, counter); |
694 | 1.99M | if (r) |
695 | 8.03k | return r; |
696 | 1.98M | auto *l = findObject(f, ext->left, start_from, counter); |
697 | 1.98M | if (l) |
698 | 1.95M | return l; |
699 | 2.24M | } else { |
700 | 2.24M | if (counter >= start_from) { |
701 | 308k | if (auto *simp = dynamic_cast<HeapSimpleObject *>(curr)) { |
702 | 308k | auto it = simp->fields.find(f); |
703 | 308k | if (it != simp->fields.end()) { |
704 | 259k | return simp; |
705 | 259k | } |
706 | 308k | } else if (auto *comp = dynamic_cast<HeapComprehensionObject *>(curr)) { |
707 | 0 | auto it = comp->compValues.find(f); |
708 | 0 | if (it != comp->compValues.end()) { |
709 | 0 | return comp; |
710 | 0 | } |
711 | 0 | } |
712 | 308k | } |
713 | 1.98M | counter++; |
714 | 1.98M | } |
715 | 2.01M | return nullptr; |
716 | 4.24M | } |
717 | | |
718 | | typedef std::map<const Identifier *, ObjectField::Hide> IdHideMap; |
719 | | |
720 | | /** Auxiliary function. |
721 | | */ |
722 | | IdHideMap objectFieldsAux(const HeapObject *obj_) |
723 | 35.5k | { |
724 | 35.5k | IdHideMap r; |
725 | 35.5k | if (auto *obj = dynamic_cast<const HeapSimpleObject *>(obj_)) { |
726 | 21.6k | for (const auto &f : obj->fields) { |
727 | 21.6k | r[f.first] = f.second.hide; |
728 | 21.6k | } |
729 | | |
730 | 20.4k | } else if (auto *obj = dynamic_cast<const HeapExtendedObject *>(obj_)) { |
731 | 15.0k | r = objectFieldsAux(obj->right); |
732 | 91.4k | for (const auto &pair : objectFieldsAux(obj->left)) { |
733 | 91.4k | auto it = r.find(pair.first); |
734 | 91.4k | if (it == r.end()) { |
735 | | // First time it is seen |
736 | 75.5k | r[pair.first] = pair.second; |
737 | 75.5k | } else if (it->second == ObjectField::INHERIT) { |
738 | | // Seen before, but with inherited visibility so use new visibility |
739 | 15.8k | r[pair.first] = pair.second; |
740 | 15.8k | } |
741 | 91.4k | } |
742 | | |
743 | 15.0k | } else if (auto *obj = dynamic_cast<const HeapComprehensionObject *>(obj_)) { |
744 | 0 | for (const auto &f : obj->compValues) |
745 | 0 | r[f.first] = ObjectField::VISIBLE; |
746 | 0 | } |
747 | 35.5k | return r; |
748 | 35.5k | } |
749 | | |
750 | | /** Auxiliary function. |
751 | | */ |
752 | | std::set<const Identifier *> objectFields(const HeapObject *obj_, bool manifesting) |
753 | 5.46k | { |
754 | 5.46k | std::set<const Identifier *> r; |
755 | 5.73k | for (const auto &pair : objectFieldsAux(obj_)) { |
756 | 5.73k | if (!manifesting || pair.second != ObjectField::HIDDEN) |
757 | 5.73k | r.insert(pair.first); |
758 | 5.73k | } |
759 | 5.46k | return r; |
760 | 5.46k | } |
761 | | |
762 | | /** Import another Jsonnet file. |
763 | | * |
764 | | * If the file has already been imported, then use that version. This maintains |
765 | | * referential transparency in the case of writes to disk during execution. The |
766 | | * cache holds a thunk in order to cache the resulting value of execution. |
767 | | * |
768 | | * \param loc Location of the import statement. |
769 | | * \param file Path to the filename. |
770 | | */ |
771 | | HeapThunk *import(const LocationRange &loc, const LiteralString *file) |
772 | 20 | { |
773 | 20 | ImportCacheValue *input = importString(loc, file); |
774 | 20 | if (input->thunk == nullptr) { |
775 | 0 | Tokens tokens = jsonnet_lex(input->foundHere, input->content.c_str()); |
776 | 0 | AST *expr = jsonnet_parse(alloc, tokens); |
777 | 0 | jsonnet_desugar(alloc, expr, nullptr); |
778 | 0 | jsonnet_static_analysis(expr); |
779 | | // If no errors then populate cache. |
780 | 0 | auto *thunk = makeHeap<HeapThunk>(idImport, nullptr, 0, expr); |
781 | 0 | input->thunk = thunk; |
782 | 0 | } |
783 | 20 | return input->thunk; |
784 | 20 | } |
785 | | |
786 | | /** Import a file as a string. |
787 | | * |
788 | | * If the file has already been imported, then use that version. This maintains |
789 | | * referential transparency in the case of writes to disk during execution. |
790 | | * |
791 | | * \param loc Location of the import statement. |
792 | | * \param file Path to the filename. |
793 | | * \param found_here If non-null, used to store the actual path of the file |
794 | | */ |
795 | | ImportCacheValue *importString(const LocationRange &loc, const LiteralString *file) |
796 | 20 | { |
797 | 20 | std::string dir = dir_name(loc.file); |
798 | | |
799 | 20 | const UString &path = file->value; |
800 | | |
801 | 20 | std::pair<std::string, UString> key(dir, path); |
802 | 20 | ImportCacheValue *cached_value = cachedImports[key]; |
803 | 20 | if (cached_value != nullptr) |
804 | 0 | return cached_value; |
805 | | |
806 | 20 | int success = 0; |
807 | 20 | char *found_here_cptr; |
808 | 20 | char *content = importCallback(importCallbackContext, |
809 | 20 | dir.c_str(), |
810 | 20 | encode_utf8(path).c_str(), |
811 | 20 | &found_here_cptr, |
812 | 20 | &success); |
813 | | |
814 | 20 | std::string input(content); |
815 | 20 | ::free(content); |
816 | | |
817 | 20 | if (!success) { |
818 | 20 | std::string epath = encode_utf8(jsonnet_string_escape(path, false)); |
819 | 20 | std::string msg = "couldn't open import \"" + epath + "\": "; |
820 | 20 | msg += input; |
821 | 20 | throw makeError(loc, msg); |
822 | 20 | } |
823 | | |
824 | 0 | auto *input_ptr = new ImportCacheValue(); |
825 | 0 | input_ptr->foundHere = found_here_cptr; |
826 | 0 | input_ptr->content = input; |
827 | 0 | input_ptr->thunk = nullptr; // May be filled in later by import(). |
828 | 0 | ::free(found_here_cptr); |
829 | 0 | cachedImports[key] = input_ptr; |
830 | 0 | return input_ptr; |
831 | 20 | } |
832 | | |
833 | | /** Capture the required variables from the environment. */ |
834 | | BindingFrame capture(const std::vector<const Identifier *> &free_vars) |
835 | 6.54M | { |
836 | 6.54M | BindingFrame env; |
837 | 10.1M | for (auto fv : free_vars) { |
838 | 10.1M | auto *th = stack.lookUpVar(fv); |
839 | 10.1M | env[fv] = th; |
840 | 10.1M | } |
841 | 6.54M | return env; |
842 | 6.54M | } |
843 | | |
844 | | /** Count the number of leaves in the tree. |
845 | | * |
846 | | * \param obj The root of the tree. |
847 | | * \returns The number of leaves. |
848 | | */ |
849 | | unsigned countLeaves(HeapObject *obj) |
850 | 7.78M | { |
851 | 7.78M | if (auto *ext = dynamic_cast<HeapExtendedObject *>(obj)) { |
852 | 3.88M | return countLeaves(ext->left) + countLeaves(ext->right); |
853 | 3.89M | } else { |
854 | | // Must be a HeapLeafObject. |
855 | 3.89M | return 1; |
856 | 3.89M | } |
857 | 7.78M | } |
858 | | |
859 | 214 | void prepareSourceValThunks() { |
860 | 27.1k | for (const auto &field : stdlibAST->fields) { |
861 | 27.1k | AST *nameAST = field.name; |
862 | 27.1k | if (nameAST->type != AST_LITERAL_STRING) { |
863 | | // Skip any fields without a known name. |
864 | 0 | continue; |
865 | 0 | } |
866 | 27.1k | UString name = dynamic_cast<LiteralString *>(nameAST)->value; |
867 | | |
868 | 27.1k | sourceFuncIds.emplace_back(new Identifier(name)); |
869 | 27.1k | auto *th = makeHeap<HeapThunk>(sourceFuncIds.back().get(), stdObject, 0, field.body); |
870 | 27.1k | sourceVals[encode_utf8(name)] = th; |
871 | 27.1k | } |
872 | 214 | } |
873 | | |
874 | | public: |
875 | | /** Create a new interpreter. |
876 | | * |
877 | | * \param loc The location range of the file to be executed. |
878 | | */ |
879 | | Interpreter(Allocator *alloc, const ExtMap &ext_vars, unsigned max_stack, double gc_min_objects, |
880 | | double gc_growth_trigger, const VmNativeCallbackMap &native_callbacks, |
881 | | JsonnetImportCallback *import_callback, void *import_callback_context) |
882 | | |
883 | | : heap(gc_min_objects, gc_growth_trigger), |
884 | | stack(max_stack), |
885 | | alloc(alloc), |
886 | | idImport(alloc->makeIdentifier(U"import")), |
887 | | idArrayElement(alloc->makeIdentifier(U"array_element")), |
888 | | idInvariant(alloc->makeIdentifier(U"object_assert")), |
889 | | idInternal(alloc->makeIdentifier(U"__internal__")), |
890 | | idJsonObjVar(alloc->makeIdentifier(U"_")), |
891 | | idEmpty(alloc->makeIdentifier(U"")), |
892 | | jsonObjVar(alloc->make<Var>(LocationRange(), Fodder{}, idJsonObjVar)), |
893 | | externalVars(ext_vars), |
894 | | nativeCallbacks(native_callbacks), |
895 | | importCallback(import_callback), |
896 | | importCallbackContext(import_callback_context) |
897 | 214 | { |
898 | 214 | scratch = makeNull(); |
899 | 214 | builtins["makeArray"] = &Interpreter::builtinMakeArray; |
900 | 214 | builtins["pow"] = &Interpreter::builtinPow; |
901 | 214 | builtins["floor"] = &Interpreter::builtinFloor; |
902 | 214 | builtins["ceil"] = &Interpreter::builtinCeil; |
903 | 214 | builtins["sqrt"] = &Interpreter::builtinSqrt; |
904 | 214 | builtins["sin"] = &Interpreter::builtinSin; |
905 | 214 | builtins["cos"] = &Interpreter::builtinCos; |
906 | 214 | builtins["tan"] = &Interpreter::builtinTan; |
907 | 214 | builtins["asin"] = &Interpreter::builtinAsin; |
908 | 214 | builtins["acos"] = &Interpreter::builtinAcos; |
909 | 214 | builtins["atan"] = &Interpreter::builtinAtan; |
910 | 214 | builtins["type"] = &Interpreter::builtinType; |
911 | 214 | builtins["filter"] = &Interpreter::builtinFilter; |
912 | 214 | builtins["objectHasEx"] = &Interpreter::builtinObjectHasEx; |
913 | 214 | builtins["length"] = &Interpreter::builtinLength; |
914 | 214 | builtins["objectFieldsEx"] = &Interpreter::builtinObjectFieldsEx; |
915 | 214 | builtins["codepoint"] = &Interpreter::builtinCodepoint; |
916 | 214 | builtins["char"] = &Interpreter::builtinChar; |
917 | 214 | builtins["log"] = &Interpreter::builtinLog; |
918 | 214 | builtins["exp"] = &Interpreter::builtinExp; |
919 | 214 | builtins["mantissa"] = &Interpreter::builtinMantissa; |
920 | 214 | builtins["exponent"] = &Interpreter::builtinExponent; |
921 | 214 | builtins["modulo"] = &Interpreter::builtinModulo; |
922 | 214 | builtins["extVar"] = &Interpreter::builtinExtVar; |
923 | 214 | builtins["primitiveEquals"] = &Interpreter::builtinPrimitiveEquals; |
924 | 214 | builtins["native"] = &Interpreter::builtinNative; |
925 | 214 | builtins["md5"] = &Interpreter::builtinMd5; |
926 | 214 | builtins["trace"] = &Interpreter::builtinTrace; |
927 | 214 | builtins["splitLimit"] = &Interpreter::builtinSplitLimit; |
928 | 214 | builtins["substr"] = &Interpreter::builtinSubstr; |
929 | 214 | builtins["range"] = &Interpreter::builtinRange; |
930 | 214 | builtins["strReplace"] = &Interpreter::builtinStrReplace; |
931 | 214 | builtins["asciiLower"] = &Interpreter::builtinAsciiLower; |
932 | 214 | builtins["asciiUpper"] = &Interpreter::builtinAsciiUpper; |
933 | 214 | builtins["join"] = &Interpreter::builtinJoin; |
934 | 214 | builtins["parseJson"] = &Interpreter::builtinParseJson; |
935 | 214 | builtins["parseYaml"] = &Interpreter::builtinParseYaml; |
936 | 214 | builtins["encodeUTF8"] = &Interpreter::builtinEncodeUTF8; |
937 | 214 | builtins["decodeUTF8"] = &Interpreter::builtinDecodeUTF8; |
938 | | |
939 | 214 | DesugaredObject *stdlib = makeStdlibAST(alloc, "__internal__"); |
940 | 214 | jsonnet_static_analysis(stdlib); |
941 | 214 | stdlibAST = stdlib; // stdlibAST is const, so we need to do analysis before this assignment |
942 | 214 | auto stdThunk = makeHeap<HeapThunk>(nullptr, nullptr, 0, static_cast<const AST*>(stdlibAST)); |
943 | 214 | stack.newCall(stdThunk->body->location, stdThunk, stdThunk->self, stdThunk->offset, stdThunk->upValues); |
944 | 214 | evaluate(stdThunk->body, 0); |
945 | 214 | stdObject = dynamic_cast<HeapObject*>(scratch.v.h); |
946 | 214 | prepareSourceValThunks(); |
947 | 214 | } |
948 | | |
949 | | |
950 | | /** Clean up the heap, stack, stash, and builtin function ASTs. */ |
951 | | ~Interpreter() |
952 | 214 | { |
953 | 214 | for (const auto &pair : cachedImports) { |
954 | 20 | delete pair.second; |
955 | 20 | } |
956 | 214 | } |
957 | | |
958 | | const Value &getScratchRegister(void) |
959 | 0 | { |
960 | 0 | return scratch; |
961 | 0 | } |
962 | | |
963 | | void setScratchRegister(const Value &v) |
964 | 0 | { |
965 | 0 | scratch = v; |
966 | 0 | } |
967 | | |
968 | | /** Raise an error if the arguments aren't the expected types. */ |
969 | | void validateBuiltinArgs(const LocationRange &loc, const std::string &name, |
970 | | const std::vector<Value> &args, const std::vector<Value::Type> params) |
971 | 280 | { |
972 | 280 | if (args.size() == params.size()) { |
973 | 639 | for (std::size_t i = 0; i < args.size(); ++i) { |
974 | 359 | if (args[i].t != params[i]) |
975 | 0 | goto bad; |
976 | 359 | } |
977 | 280 | return; |
978 | 280 | } |
979 | 0 | bad:; |
980 | 0 | std::stringstream ss; |
981 | 0 | ss << "Builtin function " + name + " expected ("; |
982 | 0 | const char *prefix = ""; |
983 | 0 | for (auto p : params) { |
984 | 0 | ss << prefix << type_str(p); |
985 | 0 | prefix = ", "; |
986 | 0 | } |
987 | 0 | ss << ") but got ("; |
988 | 0 | prefix = ""; |
989 | 0 | for (auto a : args) { |
990 | 0 | ss << prefix << type_str(a); |
991 | 0 | prefix = ", "; |
992 | 0 | } |
993 | 0 | ss << ")"; |
994 | 0 | throw makeError(loc, ss.str()); |
995 | 280 | } |
996 | | |
997 | | const AST *builtinMakeArray(const LocationRange &loc, const std::vector<Value> &args) |
998 | 0 | { |
999 | 0 | Frame &f = stack.top(); |
1000 | 0 | validateBuiltinArgs(loc, "makeArray", args, {Value::NUMBER, Value::FUNCTION}); |
1001 | 0 | long sz = long(args[0].v.d); |
1002 | 0 | if (sz < 0) { |
1003 | 0 | std::stringstream ss; |
1004 | 0 | ss << "makeArray requires size >= 0, got " << sz; |
1005 | 0 | throw makeError(loc, ss.str()); |
1006 | 0 | } |
1007 | 0 | auto *func = static_cast<const HeapClosure *>(args[1].v.h); |
1008 | 0 | std::vector<HeapThunk *> elements; |
1009 | 0 | if (func->params.size() != 1) { |
1010 | 0 | std::stringstream ss; |
1011 | 0 | ss << "makeArray function must take 1 param, got: " << func->params.size(); |
1012 | 0 | throw makeError(loc, ss.str()); |
1013 | 0 | } |
1014 | 0 | elements.resize(sz); |
1015 | 0 | for (long i = 0; i < sz; ++i) { |
1016 | 0 | auto *th = makeHeap<HeapThunk>(idArrayElement, func->self, func->offset, func->body); |
1017 | | // The next line stops the new thunks from being GCed. |
1018 | 0 | f.thunks.push_back(th); |
1019 | 0 | th->upValues = func->upValues; |
1020 | |
|
1021 | 0 | auto *el = makeHeap<HeapThunk>(func->params[0].id, nullptr, 0, nullptr); |
1022 | 0 | el->fill(makeNumber(i)); // i guaranteed not to be inf/NaN |
1023 | 0 | th->upValues[func->params[0].id] = el; |
1024 | 0 | elements[i] = th; |
1025 | 0 | } |
1026 | 0 | scratch = makeArray(elements); |
1027 | 0 | return nullptr; |
1028 | 0 | } |
1029 | | |
1030 | | const AST *builtinPow(const LocationRange &loc, const std::vector<Value> &args) |
1031 | 0 | { |
1032 | 0 | validateBuiltinArgs(loc, "pow", args, {Value::NUMBER, Value::NUMBER}); |
1033 | 0 | scratch = makeNumberCheck(loc, std::pow(args[0].v.d, args[1].v.d)); |
1034 | 0 | return nullptr; |
1035 | 0 | } |
1036 | | |
1037 | | const AST *builtinFloor(const LocationRange &loc, const std::vector<Value> &args) |
1038 | 208 | { |
1039 | 208 | validateBuiltinArgs(loc, "floor", args, {Value::NUMBER}); |
1040 | 208 | scratch = makeNumberCheck(loc, std::floor(args[0].v.d)); |
1041 | 208 | return nullptr; |
1042 | 208 | } |
1043 | | |
1044 | | const AST *builtinCeil(const LocationRange &loc, const std::vector<Value> &args) |
1045 | 0 | { |
1046 | 0 | validateBuiltinArgs(loc, "ceil", args, {Value::NUMBER}); |
1047 | 0 | scratch = makeNumberCheck(loc, std::ceil(args[0].v.d)); |
1048 | 0 | return nullptr; |
1049 | 0 | } |
1050 | | |
1051 | | const AST *builtinSqrt(const LocationRange &loc, const std::vector<Value> &args) |
1052 | 0 | { |
1053 | 0 | validateBuiltinArgs(loc, "sqrt", args, {Value::NUMBER}); |
1054 | 0 | scratch = makeNumberCheck(loc, std::sqrt(args[0].v.d)); |
1055 | 0 | return nullptr; |
1056 | 0 | } |
1057 | | |
1058 | | const AST *builtinSin(const LocationRange &loc, const std::vector<Value> &args) |
1059 | 0 | { |
1060 | 0 | validateBuiltinArgs(loc, "sin", args, {Value::NUMBER}); |
1061 | 0 | scratch = makeNumberCheck(loc, std::sin(args[0].v.d)); |
1062 | 0 | return nullptr; |
1063 | 0 | } |
1064 | | |
1065 | | const AST *builtinCos(const LocationRange &loc, const std::vector<Value> &args) |
1066 | 0 | { |
1067 | 0 | validateBuiltinArgs(loc, "cos", args, {Value::NUMBER}); |
1068 | 0 | scratch = makeNumberCheck(loc, std::cos(args[0].v.d)); |
1069 | 0 | return nullptr; |
1070 | 0 | } |
1071 | | |
1072 | | const AST *builtinTan(const LocationRange &loc, const std::vector<Value> &args) |
1073 | 0 | { |
1074 | 0 | validateBuiltinArgs(loc, "tan", args, {Value::NUMBER}); |
1075 | 0 | scratch = makeNumberCheck(loc, std::tan(args[0].v.d)); |
1076 | 0 | return nullptr; |
1077 | 0 | } |
1078 | | |
1079 | | const AST *builtinAsin(const LocationRange &loc, const std::vector<Value> &args) |
1080 | 0 | { |
1081 | 0 | validateBuiltinArgs(loc, "asin", args, {Value::NUMBER}); |
1082 | 0 | scratch = makeNumberCheck(loc, std::asin(args[0].v.d)); |
1083 | 0 | return nullptr; |
1084 | 0 | } |
1085 | | |
1086 | | const AST *builtinAcos(const LocationRange &loc, const std::vector<Value> &args) |
1087 | 0 | { |
1088 | 0 | validateBuiltinArgs(loc, "acos", args, {Value::NUMBER}); |
1089 | 0 | scratch = makeNumberCheck(loc, std::acos(args[0].v.d)); |
1090 | 0 | return nullptr; |
1091 | 0 | } |
1092 | | |
1093 | | const AST *builtinAtan(const LocationRange &loc, const std::vector<Value> &args) |
1094 | 0 | { |
1095 | 0 | validateBuiltinArgs(loc, "atan", args, {Value::NUMBER}); |
1096 | 0 | scratch = makeNumberCheck(loc, std::atan(args[0].v.d)); |
1097 | 0 | return nullptr; |
1098 | 0 | } |
1099 | | |
1100 | | const AST *builtinType(const LocationRange &, const std::vector<Value> &args) |
1101 | 71.0k | { |
1102 | 71.0k | switch (args[0].t) { |
1103 | 149 | case Value::NULL_TYPE: scratch = makeString(U"null"); return nullptr; |
1104 | | |
1105 | 4.57k | case Value::BOOLEAN: scratch = makeString(U"boolean"); return nullptr; |
1106 | | |
1107 | 1.15k | case Value::NUMBER: scratch = makeString(U"number"); return nullptr; |
1108 | | |
1109 | 50 | case Value::ARRAY: scratch = makeString(U"array"); return nullptr; |
1110 | | |
1111 | 0 | case Value::FUNCTION: scratch = makeString(U"function"); return nullptr; |
1112 | | |
1113 | 8.47k | case Value::OBJECT: scratch = makeString(U"object"); return nullptr; |
1114 | | |
1115 | 56.6k | case Value::STRING: scratch = makeString(U"string"); return nullptr; |
1116 | 71.0k | } |
1117 | 0 | return nullptr; // Quiet, compiler. |
1118 | 71.0k | } |
1119 | | |
1120 | | const AST *builtinFilter(const LocationRange &loc, const std::vector<Value> &args) |
1121 | 0 | { |
1122 | 0 | Frame &f = stack.top(); |
1123 | 0 | validateBuiltinArgs(loc, "filter", args, {Value::FUNCTION, Value::ARRAY}); |
1124 | 0 | auto *func = static_cast<HeapClosure *>(args[0].v.h); |
1125 | 0 | auto *arr = static_cast<HeapArray *>(args[1].v.h); |
1126 | 0 | if (func->params.size() != 1) { |
1127 | 0 | throw makeError(loc, "filter function takes 1 parameter."); |
1128 | 0 | } |
1129 | 0 | if (arr->elements.size() == 0) { |
1130 | 0 | scratch = makeArray({}); |
1131 | 0 | } else { |
1132 | 0 | f.kind = FRAME_BUILTIN_FILTER; |
1133 | 0 | f.val = args[0]; |
1134 | 0 | f.val2 = args[1]; |
1135 | 0 | f.thunks.clear(); |
1136 | 0 | f.elementId = 0; |
1137 | |
|
1138 | 0 | auto *thunk = arr->elements[f.elementId]; |
1139 | 0 | BindingFrame bindings = func->upValues; |
1140 | 0 | bindings[func->params[0].id] = thunk; |
1141 | 0 | stack.newCall(loc, func, func->self, func->offset, bindings); |
1142 | 0 | return func->body; |
1143 | 0 | } |
1144 | 0 | return nullptr; |
1145 | 0 | } |
1146 | | |
1147 | | const AST *builtinObjectHasEx(const LocationRange &loc, const std::vector<Value> &args) |
1148 | 7 | { |
1149 | 7 | validateBuiltinArgs( |
1150 | 7 | loc, "objectHasEx", args, {Value::OBJECT, Value::STRING, Value::BOOLEAN}); |
1151 | 7 | const auto *obj = static_cast<const HeapObject *>(args[0].v.h); |
1152 | 7 | const auto *str = static_cast<const HeapString *>(args[1].v.h); |
1153 | 7 | bool include_hidden = args[2].v.b; |
1154 | 7 | bool found = false; |
1155 | 82 | for (const auto &field : objectFields(obj, !include_hidden)) { |
1156 | 82 | if (field->name == str->value) { |
1157 | 6 | found = true; |
1158 | 6 | break; |
1159 | 6 | } |
1160 | 82 | } |
1161 | 7 | scratch = makeBoolean(found); |
1162 | 7 | return nullptr; |
1163 | 7 | } |
1164 | | |
1165 | | const AST *builtinLength(const LocationRange &loc, const std::vector<Value> &args) |
1166 | 21.3k | { |
1167 | 21.3k | if (args.size() != 1) { |
1168 | 0 | throw makeError(loc, "length takes 1 parameter."); |
1169 | 0 | } |
1170 | 21.3k | HeapEntity *e = args[0].v.h; |
1171 | 21.3k | switch (args[0].t) { |
1172 | 0 | case Value::OBJECT: { |
1173 | 0 | auto fields = objectFields(static_cast<HeapObject *>(e), true); |
1174 | 0 | scratch = makeNumber(fields.size()); |
1175 | 0 | } break; |
1176 | | |
1177 | 597 | case Value::ARRAY: |
1178 | 597 | scratch = makeNumber(static_cast<HeapArray *>(e)->elements.size()); |
1179 | 597 | break; |
1180 | | |
1181 | 20.7k | case Value::STRING: |
1182 | 20.7k | scratch = makeNumber(static_cast<HeapString *>(e)->value.length()); |
1183 | 20.7k | break; |
1184 | | |
1185 | 0 | case Value::FUNCTION: |
1186 | 0 | scratch = makeNumber(static_cast<HeapClosure *>(e)->params.size()); |
1187 | 0 | break; |
1188 | | |
1189 | 0 | default: |
1190 | 0 | throw makeError(loc, |
1191 | 0 | "length operates on strings, objects, " |
1192 | 0 | "and arrays, got " + |
1193 | 0 | type_str(args[0])); |
1194 | 21.3k | } |
1195 | 21.3k | return nullptr; |
1196 | 21.3k | } |
1197 | | |
1198 | | const AST *builtinObjectFieldsEx(const LocationRange &loc, const std::vector<Value> &args) |
1199 | 40 | { |
1200 | 40 | validateBuiltinArgs(loc, "objectFieldsEx", args, {Value::OBJECT, Value::BOOLEAN}); |
1201 | 40 | const auto *obj = static_cast<HeapObject *>(args[0].v.h); |
1202 | 40 | bool include_hidden = args[1].v.b; |
1203 | | // Stash in a set first to sort them. |
1204 | 40 | std::set<UString> fields; |
1205 | 68 | for (const auto &field : objectFields(obj, !include_hidden)) { |
1206 | 68 | fields.insert(field->name); |
1207 | 68 | } |
1208 | 40 | scratch = makeArray({}); |
1209 | 40 | auto &elements = static_cast<HeapArray *>(scratch.v.h)->elements; |
1210 | 68 | for (const auto &field : fields) { |
1211 | 68 | auto *th = makeHeap<HeapThunk>(idArrayElement, nullptr, 0, nullptr); |
1212 | 68 | elements.push_back(th); |
1213 | 68 | th->fill(makeString(field)); |
1214 | 68 | } |
1215 | 40 | return nullptr; |
1216 | 40 | } |
1217 | | |
1218 | | const AST *builtinCodepoint(const LocationRange &loc, const std::vector<Value> &args) |
1219 | 0 | { |
1220 | 0 | validateBuiltinArgs(loc, "codepoint", args, {Value::STRING}); |
1221 | 0 | const UString &str = static_cast<HeapString *>(args[0].v.h)->value; |
1222 | 0 | if (str.length() != 1) { |
1223 | 0 | std::stringstream ss; |
1224 | 0 | ss << "codepoint takes a string of length 1, got length " << str.length(); |
1225 | 0 | throw makeError(loc, ss.str()); |
1226 | 0 | } |
1227 | 0 | char32_t c = static_cast<HeapString *>(args[0].v.h)->value[0]; |
1228 | 0 | scratch = makeNumber((unsigned long)(c)); |
1229 | 0 | return nullptr; |
1230 | 0 | } |
1231 | | |
1232 | | const AST *builtinChar(const LocationRange &loc, const std::vector<Value> &args) |
1233 | 0 | { |
1234 | 0 | validateBuiltinArgs(loc, "char", args, {Value::NUMBER}); |
1235 | 0 | long l = long(args[0].v.d); |
1236 | 0 | if (l < 0) { |
1237 | 0 | std::stringstream ss; |
1238 | 0 | ss << "codepoints must be >= 0, got " << l; |
1239 | 0 | throw makeError(loc, ss.str()); |
1240 | 0 | } |
1241 | 0 | if (l >= JSONNET_CODEPOINT_MAX) { |
1242 | 0 | std::stringstream ss; |
1243 | 0 | ss << "invalid unicode codepoint, got " << l; |
1244 | 0 | throw makeError(loc, ss.str()); |
1245 | 0 | } |
1246 | 0 | char32_t c = l; |
1247 | 0 | scratch = makeString(UString(&c, 1)); |
1248 | 0 | return nullptr; |
1249 | 0 | } |
1250 | | |
1251 | | const AST *builtinLog(const LocationRange &loc, const std::vector<Value> &args) |
1252 | 0 | { |
1253 | 0 | validateBuiltinArgs(loc, "log", args, {Value::NUMBER}); |
1254 | 0 | scratch = makeNumberCheck(loc, std::log(args[0].v.d)); |
1255 | 0 | return nullptr; |
1256 | 0 | } |
1257 | | |
1258 | | const AST *builtinExp(const LocationRange &loc, const std::vector<Value> &args) |
1259 | 0 | { |
1260 | 0 | validateBuiltinArgs(loc, "exp", args, {Value::NUMBER}); |
1261 | 0 | scratch = makeNumberCheck(loc, std::exp(args[0].v.d)); |
1262 | 0 | return nullptr; |
1263 | 0 | } |
1264 | | |
1265 | | const AST *builtinMantissa(const LocationRange &loc, const std::vector<Value> &args) |
1266 | 0 | { |
1267 | 0 | validateBuiltinArgs(loc, "mantissa", args, {Value::NUMBER}); |
1268 | 0 | int exp; |
1269 | 0 | double m = std::frexp(args[0].v.d, &exp); |
1270 | 0 | scratch = makeNumberCheck(loc, m); |
1271 | 0 | return nullptr; |
1272 | 0 | } |
1273 | | |
1274 | | const AST *builtinExponent(const LocationRange &loc, const std::vector<Value> &args) |
1275 | 0 | { |
1276 | 0 | validateBuiltinArgs(loc, "exponent", args, {Value::NUMBER}); |
1277 | 0 | int exp; |
1278 | 0 | std::frexp(args[0].v.d, &exp); |
1279 | 0 | scratch = makeNumberCheck(loc, exp); |
1280 | 0 | return nullptr; |
1281 | 0 | } |
1282 | | |
1283 | | const AST *builtinModulo(const LocationRange &loc, const std::vector<Value> &args) |
1284 | 25 | { |
1285 | 25 | validateBuiltinArgs(loc, "modulo", args, {Value::NUMBER, Value::NUMBER}); |
1286 | 25 | double a = args[0].v.d; |
1287 | 25 | double b = args[1].v.d; |
1288 | 25 | if (b == 0) |
1289 | 0 | throw makeError(loc, "division by zero."); |
1290 | 25 | scratch = makeNumberCheck(loc, std::fmod(a, b)); |
1291 | 25 | return nullptr; |
1292 | 25 | } |
1293 | | |
1294 | | const AST *builtinExtVar(const LocationRange &loc, const std::vector<Value> &args) |
1295 | 0 | { |
1296 | 0 | validateBuiltinArgs(loc, "extVar", args, {Value::STRING}); |
1297 | 0 | const UString &var = static_cast<HeapString *>(args[0].v.h)->value; |
1298 | 0 | std::string var8 = encode_utf8(var); |
1299 | 0 | auto it = externalVars.find(var8); |
1300 | 0 | if (it == externalVars.end()) { |
1301 | 0 | std::string msg = "undefined external variable: " + var8; |
1302 | 0 | throw makeError(loc, msg); |
1303 | 0 | } |
1304 | 0 | const VmExt &ext = it->second; |
1305 | 0 | if (ext.isCode) { |
1306 | 0 | std::string filename = "<extvar:" + var8 + ">"; |
1307 | 0 | Tokens tokens = jsonnet_lex(filename, ext.data.c_str()); |
1308 | 0 | AST *expr = jsonnet_parse(alloc, tokens); |
1309 | 0 | jsonnet_desugar(alloc, expr, nullptr); |
1310 | 0 | jsonnet_static_analysis(expr); |
1311 | 0 | stack.pop(); |
1312 | 0 | return expr; |
1313 | 0 | } else { |
1314 | 0 | scratch = makeString(decode_utf8(ext.data)); |
1315 | 0 | return nullptr; |
1316 | 0 | } |
1317 | 0 | } |
1318 | | |
1319 | | const AST *builtinPrimitiveEquals(const LocationRange &loc, const std::vector<Value> &args) |
1320 | 113k | { |
1321 | 113k | if (args.size() != 2) { |
1322 | 0 | std::stringstream ss; |
1323 | 0 | ss << "primitiveEquals takes 2 parameters, got " << args.size(); |
1324 | 0 | throw makeError(loc, ss.str()); |
1325 | 0 | } |
1326 | 113k | if (args[0].t != args[1].t) { |
1327 | 0 | scratch = makeBoolean(false); |
1328 | 0 | return nullptr; |
1329 | 0 | } |
1330 | 113k | bool r; |
1331 | 113k | switch (args[0].t) { |
1332 | 108 | case Value::BOOLEAN: r = args[0].v.b == args[1].v.b; break; |
1333 | | |
1334 | 146 | case Value::NUMBER: r = args[0].v.d == args[1].v.d; break; |
1335 | | |
1336 | 113k | case Value::STRING: |
1337 | 113k | r = static_cast<HeapString *>(args[0].v.h)->value == |
1338 | 113k | static_cast<HeapString *>(args[1].v.h)->value; |
1339 | 113k | break; |
1340 | | |
1341 | 18 | case Value::NULL_TYPE: r = true; break; |
1342 | | |
1343 | 0 | case Value::FUNCTION: throw makeError(loc, "cannot test equality of functions"); break; |
1344 | | |
1345 | 0 | default: |
1346 | 0 | throw makeError(loc, |
1347 | 0 | "primitiveEquals operates on primitive " |
1348 | 0 | "types, got " + |
1349 | 0 | type_str(args[0])); |
1350 | 113k | } |
1351 | 113k | scratch = makeBoolean(r); |
1352 | 113k | return nullptr; |
1353 | 113k | } |
1354 | | |
1355 | | const AST *builtinNative(const LocationRange &loc, const std::vector<Value> &args) |
1356 | 0 | { |
1357 | 0 | validateBuiltinArgs(loc, "native", args, {Value::STRING}); |
1358 | |
|
1359 | 0 | std::string builtin_name = encode_utf8(static_cast<HeapString *>(args[0].v.h)->value); |
1360 | |
|
1361 | 0 | VmNativeCallbackMap::const_iterator nit = nativeCallbacks.find(builtin_name); |
1362 | 0 | if (nit == nativeCallbacks.end()) { |
1363 | 0 | scratch = makeNull(); |
1364 | 0 | } else { |
1365 | 0 | const VmNativeCallback &cb = nit->second; |
1366 | 0 | scratch = makeNativeBuiltin(builtin_name, cb.params); |
1367 | 0 | } |
1368 | 0 | return nullptr; |
1369 | 0 | } |
1370 | | |
1371 | | const AST *builtinMd5(const LocationRange &loc, const std::vector<Value> &args) |
1372 | 0 | { |
1373 | 0 | validateBuiltinArgs(loc, "md5", args, {Value::STRING}); |
1374 | |
|
1375 | 0 | std::string value = encode_utf8(static_cast<HeapString *>(args[0].v.h)->value); |
1376 | |
|
1377 | 0 | scratch = makeString(decode_utf8(md5(value))); |
1378 | 0 | return nullptr; |
1379 | 0 | } |
1380 | | |
1381 | | const AST *builtinEncodeUTF8(const LocationRange &loc, const std::vector<Value> &args) |
1382 | 0 | { |
1383 | 0 | validateBuiltinArgs(loc, "encodeUTF8", args, {Value::STRING}); |
1384 | |
|
1385 | 0 | std::string byteString = encode_utf8(static_cast<HeapString *>(args[0].v.h)->value); |
1386 | |
|
1387 | 0 | scratch = makeArray({}); |
1388 | 0 | auto &elements = static_cast<HeapArray *>(scratch.v.h)->elements; |
1389 | 0 | for (const auto c : byteString) { |
1390 | 0 | auto *th = makeHeap<HeapThunk>(idArrayElement, nullptr, 0, nullptr); |
1391 | 0 | elements.push_back(th); |
1392 | 0 | th->fill(makeNumber(uint8_t(c))); |
1393 | 0 | } |
1394 | 0 | return nullptr; |
1395 | 0 | } |
1396 | | |
1397 | | const AST *decodeUTF8(void) |
1398 | 0 | { |
1399 | 0 | Frame &f = stack.top(); |
1400 | 0 | const auto& elements = static_cast<HeapArray*>(f.val.v.h)->elements; |
1401 | 0 | while (f.elementId < elements.size()) { |
1402 | 0 | auto *th = elements[f.elementId]; |
1403 | 0 | if (th->filled) { |
1404 | 0 | auto b = th->content; |
1405 | 0 | if (b.t != Value::NUMBER) { |
1406 | 0 | std::stringstream ss; |
1407 | 0 | ss << "Element " << f.elementId << " of the provided array was not a number"; |
1408 | 0 | throw makeError(stack.top().location, ss.str()); |
1409 | 0 | } else { |
1410 | 0 | double d = b.v.d; |
1411 | 0 | if (d < 0 || d > 255 || d != int(d)) { |
1412 | 0 | std::stringstream ss; |
1413 | 0 | ss << "Element " << f.elementId << " of the provided array was not an integer in range [0,255]"; |
1414 | 0 | throw makeError(stack.top().location, ss.str()); |
1415 | 0 | } |
1416 | 0 | f.bytes.push_back(uint8_t(d)); |
1417 | 0 | } |
1418 | 0 | f.elementId++; |
1419 | 0 | } else { |
1420 | 0 | stack.newCall(f.location, th, th->self, th->offset, th->upValues); |
1421 | 0 | return th->body; |
1422 | 0 | } |
1423 | 0 | } |
1424 | 0 | scratch = makeString(decode_utf8(f.bytes)); |
1425 | 0 | return nullptr; |
1426 | 0 | } |
1427 | | |
1428 | | const AST *builtinDecodeUTF8(const LocationRange &loc, const std::vector<Value> &args) |
1429 | 0 | { |
1430 | 0 | validateBuiltinArgs(loc, "decodeUTF8", args, {Value::ARRAY}); |
1431 | |
|
1432 | 0 | Frame &f = stack.top(); |
1433 | 0 | f.kind = FRAME_BUILTIN_DECODE_UTF8; |
1434 | 0 | f.val = args[0]; // arr |
1435 | 0 | f.bytes.clear(); |
1436 | 0 | f.elementId = 0; |
1437 | 0 | return decodeUTF8(); |
1438 | 0 | } |
1439 | | |
1440 | | const AST *builtinTrace(const LocationRange &loc, const std::vector<Value> &args) |
1441 | 0 | { |
1442 | 0 | if(args[0].t != Value::STRING) { |
1443 | 0 | std::stringstream ss; |
1444 | 0 | ss << "Builtin function trace expected string as first parameter but " |
1445 | 0 | << "got " << type_str(args[0].t); |
1446 | 0 | throw makeError(loc, ss.str()); |
1447 | 0 | } |
1448 | | |
1449 | 0 | std::string str = encode_utf8(static_cast<HeapString *>(args[0].v.h)->value); |
1450 | 0 | std::cerr << "TRACE: " << loc.file << ":" << loc.begin.line << " " << str |
1451 | 0 | << std::endl; |
1452 | |
|
1453 | 0 | scratch = args[1]; |
1454 | 0 | return nullptr; |
1455 | 0 | } |
1456 | | |
1457 | | const AST *builtinSplitLimit(const LocationRange &loc, const std::vector<Value> &args) |
1458 | 0 | { |
1459 | 0 | validateBuiltinArgs(loc, "splitLimit", args, {Value::STRING, Value::STRING, Value::NUMBER}); |
1460 | 0 | const auto *str = static_cast<const HeapString *>(args[0].v.h); |
1461 | 0 | const auto *c = static_cast<const HeapString *>(args[1].v.h); |
1462 | 0 | long maxsplits = long(args[2].v.d); |
1463 | 0 | unsigned start = 0; |
1464 | 0 | unsigned test = 0; |
1465 | 0 | scratch = makeArray({}); |
1466 | 0 | auto &elements = static_cast<HeapArray *>(scratch.v.h)->elements; |
1467 | 0 | while (test < str->value.size() && (maxsplits == -1 || |
1468 | 0 | size_t(maxsplits) > elements.size())) { |
1469 | 0 | if (c->value == str->value.substr(test, c->value.size())) { |
1470 | 0 | auto *th = makeHeap<HeapThunk>(idArrayElement, nullptr, 0, nullptr); |
1471 | 0 | elements.push_back(th); |
1472 | 0 | th->fill(makeString(str->value.substr(start, test - start))); |
1473 | 0 | start = test + c->value.size(); |
1474 | 0 | test = start; |
1475 | 0 | } else { |
1476 | 0 | ++test; |
1477 | 0 | } |
1478 | 0 | } |
1479 | 0 | auto *th = makeHeap<HeapThunk>(idArrayElement, nullptr, 0, nullptr); |
1480 | 0 | elements.push_back(th); |
1481 | 0 | th->fill(makeString(str->value.substr(start))); |
1482 | |
|
1483 | 0 | return nullptr; |
1484 | 0 | } |
1485 | | |
1486 | | const AST *builtinSubstr(const LocationRange &loc, const std::vector<Value> &args) |
1487 | 0 | { |
1488 | 0 | validateBuiltinArgs(loc, "substr", args, {Value::STRING, Value::NUMBER, Value::NUMBER}); |
1489 | 0 | const auto *str = static_cast<const HeapString *>(args[0].v.h); |
1490 | 0 | long from = long(args[1].v.d); |
1491 | 0 | long len = long(args[2].v.d); |
1492 | 0 | if (from < 0) { |
1493 | 0 | std::stringstream ss; |
1494 | 0 | ss << "substr second parameter should be greater than zero, got " << from; |
1495 | 0 | throw makeError(loc, ss.str()); |
1496 | 0 | } |
1497 | 0 | if (len < 0) { |
1498 | 0 | std::stringstream ss; |
1499 | 0 | ss << "substr third parameter should be greater than zero, got " << len; |
1500 | 0 | throw makeError(loc, ss.str()); |
1501 | 0 | } |
1502 | 0 | if (static_cast<unsigned long>(from) > str->value.size()) { |
1503 | 0 | scratch = makeString(UString()); |
1504 | 0 | return nullptr; |
1505 | 0 | } |
1506 | 0 | if (size_t(len + from) > str->value.size()) { |
1507 | 0 | len = str->value.size() - from; |
1508 | 0 | } |
1509 | 0 | scratch = makeString(str->value.substr(from, len)); |
1510 | 0 | return nullptr; |
1511 | 0 | } |
1512 | | |
1513 | | const AST *builtinRange(const LocationRange &loc, const std::vector<Value> &args) |
1514 | 0 | { |
1515 | 0 | validateBuiltinArgs(loc, "range", args, {Value::NUMBER, Value::NUMBER}); |
1516 | 0 | long from = long(args[0].v.d); |
1517 | 0 | long to = long(args[1].v.d); |
1518 | 0 | long len = to - from + 1; |
1519 | 0 | scratch = makeArray({}); |
1520 | 0 | if (len > 0) { |
1521 | 0 | auto &elements = static_cast<HeapArray *>(scratch.v.h)->elements; |
1522 | 0 | for (int i = 0; i < len; ++i) { |
1523 | 0 | auto *th = makeHeap<HeapThunk>(idArrayElement, nullptr, 0, nullptr); |
1524 | 0 | elements.push_back(th); |
1525 | 0 | th->fill(makeNumber(from + i)); |
1526 | 0 | } |
1527 | 0 | } |
1528 | 0 | return nullptr; |
1529 | 0 | } |
1530 | | |
1531 | | const AST *builtinStrReplace(const LocationRange &loc, const std::vector<Value> &args) |
1532 | 0 | { |
1533 | 0 | validateBuiltinArgs(loc, "strReplace", args, {Value::STRING, Value::STRING, Value::STRING}); |
1534 | 0 | const auto *str = static_cast<const HeapString *>(args[0].v.h); |
1535 | 0 | const auto *from = static_cast<const HeapString *>(args[1].v.h); |
1536 | 0 | const auto *to = static_cast<const HeapString *>(args[2].v.h); |
1537 | 0 | if (from->value.empty()) { |
1538 | 0 | throw makeError(loc, "'from' string must not be zero length."); |
1539 | 0 | } |
1540 | 0 | UString new_str(str->value); |
1541 | 0 | UString::size_type pos = 0; |
1542 | 0 | while (pos < new_str.size()) { |
1543 | 0 | auto index = new_str.find(from->value, pos); |
1544 | 0 | if (index == new_str.npos) { |
1545 | 0 | break; |
1546 | 0 | } |
1547 | 0 | new_str.replace(index, from->value.size(), to->value); |
1548 | 0 | pos = index + to->value.size(); |
1549 | 0 | } |
1550 | 0 | scratch = makeString(new_str); |
1551 | 0 | return nullptr; |
1552 | 0 | } |
1553 | | |
1554 | | const AST *builtinAsciiLower(const LocationRange &loc, const std::vector<Value> &args) |
1555 | 0 | { |
1556 | 0 | validateBuiltinArgs(loc, "asciiLower", args, {Value::STRING}); |
1557 | 0 | const auto *str = static_cast<const HeapString *>(args[0].v.h); |
1558 | 0 | UString new_str(str->value); |
1559 | 0 | for (size_t i = 0; i < new_str.size(); ++i) { |
1560 | 0 | if (new_str[i] >= 'A' && new_str[i] <= 'Z') { |
1561 | 0 | new_str[i] = new_str[i] - 'A' + 'a'; |
1562 | 0 | } |
1563 | 0 | } |
1564 | 0 | scratch = makeString(new_str); |
1565 | 0 | return nullptr; |
1566 | 0 | } |
1567 | | |
1568 | | const AST *builtinAsciiUpper(const LocationRange &loc, const std::vector<Value> &args) |
1569 | 0 | { |
1570 | 0 | validateBuiltinArgs(loc, "asciiUpper", args, {Value::STRING}); |
1571 | 0 | const auto *str = static_cast<const HeapString *>(args[0].v.h); |
1572 | 0 | UString new_str(str->value); |
1573 | 0 | for (size_t i = 0; i < new_str.size(); ++i) { |
1574 | 0 | if (new_str[i] >= 'a' && new_str[i] <= 'z') { |
1575 | 0 | new_str[i] = new_str[i] - 'a' + 'A'; |
1576 | 0 | } |
1577 | 0 | } |
1578 | 0 | scratch = makeString(new_str); |
1579 | 0 | return nullptr; |
1580 | 0 | } |
1581 | | |
1582 | | const AST *builtinParseJson(const LocationRange &loc, const std::vector<Value> &args) |
1583 | 0 | { |
1584 | 0 | validateBuiltinArgs(loc, "parseJson", args, {Value::STRING}); |
1585 | |
|
1586 | 0 | std::string value = encode_utf8(static_cast<HeapString *>(args[0].v.h)->value); |
1587 | |
|
1588 | 0 | try { |
1589 | 0 | auto j = json::parse(value); |
1590 | |
|
1591 | 0 | bool filled; |
1592 | 0 | otherJsonToHeap(j, filled, scratch); |
1593 | 0 | } catch (const json::parse_error &e) { |
1594 | 0 | throw makeError(loc, e.what()); |
1595 | 0 | } |
1596 | | |
1597 | 0 | return nullptr; |
1598 | 0 | } |
1599 | | |
1600 | | const AST *builtinParseYaml(const LocationRange &loc, const std::vector<Value> &args) |
1601 | 0 | { |
1602 | 0 | validateBuiltinArgs(loc, "parseYaml", args, {Value::STRING}); |
1603 | |
|
1604 | 0 | std::string value = encode_utf8(static_cast<HeapString *>(args[0].v.h)->value); |
1605 | |
|
1606 | 0 | ryml::Tree tree = treeFromString(value); |
1607 | |
|
1608 | 0 | json j; |
1609 | 0 | if (tree.is_stream(tree.root_id())) { |
1610 | | // Split into individual yaml documents |
1611 | 0 | std::stringstream ss; |
1612 | 0 | ss << tree; |
1613 | 0 | std::vector<std::string> v = split(ss.str(), "---\n"); |
1614 | | |
1615 | | // Convert yaml to json and push onto json array |
1616 | 0 | ryml::Tree doc; |
1617 | 0 | for (std::size_t i = 0; i < v.size(); ++i) { |
1618 | 0 | if (!v[i].empty()) { |
1619 | 0 | doc = treeFromString(v[i]); |
1620 | 0 | j.push_back(yamlTreeToJson(doc)); |
1621 | 0 | } |
1622 | 0 | } |
1623 | 0 | } else { |
1624 | 0 | j = yamlTreeToJson(tree); |
1625 | 0 | } |
1626 | |
|
1627 | 0 | bool filled; |
1628 | |
|
1629 | 0 | otherJsonToHeap(j, filled, scratch); |
1630 | |
|
1631 | 0 | return nullptr; |
1632 | 0 | } |
1633 | | |
1634 | 0 | const ryml::Tree treeFromString(const std::string& s) { |
1635 | 0 | return ryml::parse(c4::to_csubstr(s)); |
1636 | 0 | } |
1637 | | |
1638 | 0 | const std::vector<std::string> split(const std::string& s, const std::string& delimiter) { |
1639 | 0 | size_t pos_start = 0, pos_end, delim_len = delimiter.length(); |
1640 | 0 | std::string token; |
1641 | 0 | std::vector<std::string> res; |
1642 | |
|
1643 | 0 | while ((pos_end = s.find(delimiter, pos_start)) != std::string::npos) { |
1644 | 0 | token = s.substr(pos_start, pos_end - pos_start); |
1645 | 0 | pos_start = pos_end + delim_len; |
1646 | 0 | res.push_back(token); |
1647 | 0 | } |
1648 | |
|
1649 | 0 | res.push_back(s.substr(pos_start)); |
1650 | 0 | return res; |
1651 | 0 | } |
1652 | | |
1653 | 0 | const json yamlTreeToJson(const ryml::Tree& tree) { |
1654 | 0 | std::ostringstream jsonStream; |
1655 | 0 | jsonStream << ryml::as_json(tree); |
1656 | 0 | return json::parse(jsonStream.str()); |
1657 | 0 | } |
1658 | | |
1659 | 0 | void otherJsonToHeap(const json &v, bool &filled, Value &attach) { |
1660 | | // In order to not anger the garbage collector, assign to attach immediately after |
1661 | | // making the heap object. |
1662 | 0 | switch (v.type()) { |
1663 | 0 | case json::value_t::string: |
1664 | 0 | attach = makeString(decode_utf8(v.get<std::string>())); |
1665 | 0 | filled = true; |
1666 | 0 | break; |
1667 | | |
1668 | 0 | case json::value_t::boolean: |
1669 | 0 | attach = makeBoolean(v.get<bool>()); |
1670 | 0 | filled = true; |
1671 | 0 | break; |
1672 | | |
1673 | 0 | case json::value_t::number_integer: |
1674 | 0 | case json::value_t::number_unsigned: |
1675 | 0 | case json::value_t::number_float: |
1676 | 0 | attach = makeNumber(v.get<double>()); |
1677 | 0 | filled = true; |
1678 | 0 | break; |
1679 | | |
1680 | 0 | case json::value_t::null: |
1681 | 0 | attach = makeNull(); |
1682 | 0 | filled = true; |
1683 | 0 | break; |
1684 | | |
1685 | 0 | case json::value_t::array:{ |
1686 | 0 | attach = makeArray(std::vector<HeapThunk *>{}); |
1687 | 0 | filled = true; |
1688 | 0 | auto *arr = static_cast<HeapArray *>(attach.v.h); |
1689 | 0 | for (size_t i = 0; i < v.size(); ++i) { |
1690 | 0 | arr->elements.push_back(makeHeap<HeapThunk>(idArrayElement, nullptr, 0, nullptr)); |
1691 | 0 | otherJsonToHeap(v[i], arr->elements[i]->filled, arr->elements[i]->content); |
1692 | 0 | } |
1693 | 0 | } break; |
1694 | | |
1695 | 0 | case json::value_t::object: { |
1696 | 0 | attach = makeObject<HeapComprehensionObject>( |
1697 | 0 | BindingFrame{}, jsonObjVar, idJsonObjVar, BindingFrame{}); |
1698 | 0 | filled = true; |
1699 | 0 | auto *obj = static_cast<HeapComprehensionObject *>(attach.v.h); |
1700 | 0 | for (auto it = v.begin(); it != v.end(); ++it) { |
1701 | 0 | auto *thunk = makeHeap<HeapThunk>(idJsonObjVar, nullptr, 0, nullptr); |
1702 | 0 | obj->compValues[alloc->makeIdentifier(decode_utf8(it.key()))] = thunk; |
1703 | 0 | otherJsonToHeap(it.value(), thunk->filled, thunk->content); |
1704 | 0 | } |
1705 | 0 | } break; |
1706 | | |
1707 | 0 | case json::value_t::discarded: { |
1708 | 0 | abort(); |
1709 | 0 | } |
1710 | 0 | } |
1711 | 0 | } |
1712 | | |
1713 | | void joinString(bool &first, UString &running, const Value &sep, unsigned idx, const Value &elt) |
1714 | 0 | { |
1715 | 0 | if (elt.t == Value::NULL_TYPE) { |
1716 | 0 | return; |
1717 | 0 | } |
1718 | 0 | if (elt.t != Value::STRING) { |
1719 | 0 | std::stringstream ss; |
1720 | 0 | ss << "expected string but arr[" << idx << "] was " << type_str(elt); |
1721 | 0 | throw makeError(stack.top().location, ss.str()); |
1722 | 0 | } |
1723 | 0 | if (!first) { |
1724 | 0 | running.append(static_cast<HeapString *>(sep.v.h)->value); |
1725 | 0 | } |
1726 | 0 | first = false; |
1727 | 0 | running.append(static_cast<HeapString *>(elt.v.h)->value); |
1728 | 0 | } |
1729 | | |
1730 | | const AST *joinStrings(void) |
1731 | 0 | { |
1732 | 0 | Frame &f = stack.top(); |
1733 | 0 | const auto& elements = static_cast<HeapArray*>(f.val2.v.h)->elements; |
1734 | 0 | while (f.elementId < elements.size()) { |
1735 | 0 | auto *th = elements[f.elementId]; |
1736 | 0 | if (th->filled) { |
1737 | 0 | joinString(f.first, f.str, f.val, f.elementId, th->content); |
1738 | 0 | f.elementId++; |
1739 | 0 | } else { |
1740 | 0 | stack.newCall(f.location, th, th->self, th->offset, th->upValues); |
1741 | 0 | return th->body; |
1742 | 0 | } |
1743 | 0 | } |
1744 | 0 | scratch = makeString(f.str); |
1745 | 0 | return nullptr; |
1746 | 0 | } |
1747 | | |
1748 | | void joinArray(bool &first, std::vector<HeapThunk*> &running, const Value &sep, unsigned idx, |
1749 | | const Value &elt) |
1750 | 0 | { |
1751 | 0 | if (elt.t == Value::NULL_TYPE) { |
1752 | 0 | return; |
1753 | 0 | } |
1754 | 0 | if (elt.t != Value::ARRAY) { |
1755 | 0 | std::stringstream ss; |
1756 | 0 | ss << "expected array but arr[" << idx << "] was " << type_str(elt); |
1757 | 0 | throw makeError(stack.top().location, ss.str()); |
1758 | 0 | } |
1759 | 0 | if (!first) { |
1760 | 0 | auto& elts = static_cast<HeapArray *>(sep.v.h)->elements; |
1761 | 0 | running.insert(running.end(), elts.begin(), elts.end()); |
1762 | 0 | } |
1763 | 0 | first = false; |
1764 | 0 | auto& elts = static_cast<HeapArray *>(elt.v.h)->elements; |
1765 | 0 | running.insert(running.end(), elts.begin(), elts.end()); |
1766 | 0 | } |
1767 | | |
1768 | | const AST *joinArrays(void) |
1769 | 0 | { |
1770 | 0 | Frame &f = stack.top(); |
1771 | 0 | const auto& elements = static_cast<HeapArray*>(f.val2.v.h)->elements; |
1772 | 0 | while (f.elementId < elements.size()) { |
1773 | 0 | auto *th = elements[f.elementId]; |
1774 | 0 | if (th->filled) { |
1775 | 0 | joinArray(f.first, f.thunks, f.val, f.elementId, th->content); |
1776 | 0 | f.elementId++; |
1777 | 0 | } else { |
1778 | 0 | stack.newCall(f.location, th, th->self, th->offset, th->upValues); |
1779 | 0 | return th->body; |
1780 | 0 | } |
1781 | 0 | } |
1782 | 0 | scratch = makeArray(f.thunks); |
1783 | 0 | return nullptr; |
1784 | 0 | } |
1785 | | |
1786 | | const AST *builtinJoin(const LocationRange &loc, const std::vector<Value> &args) |
1787 | 0 | { |
1788 | 0 | if (args[0].t != Value::ARRAY && args[0].t != Value::STRING) { |
1789 | 0 | std::stringstream ss; |
1790 | 0 | ss << "join first parameter should be string or array, got " << type_str(args[0]); |
1791 | 0 | throw makeError(loc, ss.str()); |
1792 | 0 | } |
1793 | 0 | if (args[1].t != Value::ARRAY) { |
1794 | 0 | std::stringstream ss; |
1795 | 0 | ss << "join second parameter should be array, got " << type_str(args[1]); |
1796 | 0 | throw makeError(loc, ss.str()); |
1797 | 0 | } |
1798 | 0 | Frame &f = stack.top(); |
1799 | 0 | if (args[0].t == Value::STRING) { |
1800 | 0 | f.kind = FRAME_BUILTIN_JOIN_STRINGS; |
1801 | 0 | f.val = args[0]; // sep |
1802 | 0 | f.val2 = args[1]; // arr |
1803 | 0 | f.str.clear(); |
1804 | 0 | f.first = true; |
1805 | 0 | f.elementId = 0; |
1806 | 0 | return joinStrings(); |
1807 | 0 | } else { |
1808 | 0 | f.kind = FRAME_BUILTIN_JOIN_ARRAYS; |
1809 | 0 | f.val = args[0]; // sep |
1810 | 0 | f.val2 = args[1]; // arr |
1811 | 0 | f.thunks.clear(); |
1812 | 0 | f.first = true; |
1813 | 0 | f.elementId = 0; |
1814 | 0 | return joinArrays(); |
1815 | 0 | } |
1816 | 0 | } |
1817 | | |
1818 | | void jsonToHeap(const std::unique_ptr<JsonnetJsonValue> &v, bool &filled, Value &attach) |
1819 | 0 | { |
1820 | | // In order to not anger the garbage collector, assign to attach immediately after |
1821 | | // making the heap object. |
1822 | 0 | switch (v->kind) { |
1823 | 0 | case JsonnetJsonValue::STRING: |
1824 | 0 | attach = makeString(decode_utf8(v->string)); |
1825 | 0 | filled = true; |
1826 | 0 | break; |
1827 | | |
1828 | 0 | case JsonnetJsonValue::BOOL: |
1829 | 0 | attach = makeBoolean(v->number != 0.0); |
1830 | 0 | filled = true; |
1831 | 0 | break; |
1832 | | |
1833 | 0 | case JsonnetJsonValue::NUMBER: |
1834 | 0 | attach = makeNumber(v->number); |
1835 | 0 | filled = true; |
1836 | 0 | break; |
1837 | | |
1838 | 0 | case JsonnetJsonValue::NULL_KIND: |
1839 | 0 | attach = makeNull(); |
1840 | 0 | filled = true; |
1841 | 0 | break; |
1842 | | |
1843 | 0 | case JsonnetJsonValue::ARRAY: { |
1844 | 0 | attach = makeArray(std::vector<HeapThunk *>{}); |
1845 | 0 | filled = true; |
1846 | 0 | auto *arr = static_cast<HeapArray *>(attach.v.h); |
1847 | 0 | for (size_t i = 0; i < v->elements.size(); ++i) { |
1848 | 0 | arr->elements.push_back( |
1849 | 0 | makeHeap<HeapThunk>(idArrayElement, nullptr, 0, nullptr)); |
1850 | 0 | jsonToHeap(v->elements[i], arr->elements[i]->filled, arr->elements[i]->content); |
1851 | 0 | } |
1852 | 0 | } break; |
1853 | | |
1854 | 0 | case JsonnetJsonValue::OBJECT: { |
1855 | 0 | attach = makeObject<HeapComprehensionObject>( |
1856 | 0 | BindingFrame{}, jsonObjVar, idJsonObjVar, BindingFrame{}); |
1857 | 0 | filled = true; |
1858 | 0 | auto *obj = static_cast<HeapComprehensionObject *>(attach.v.h); |
1859 | 0 | for (const auto &pair : v->fields) { |
1860 | 0 | auto *thunk = makeHeap<HeapThunk>(idJsonObjVar, nullptr, 0, nullptr); |
1861 | 0 | obj->compValues[alloc->makeIdentifier(decode_utf8(pair.first))] = thunk; |
1862 | 0 | jsonToHeap(pair.second, thunk->filled, thunk->content); |
1863 | 0 | } |
1864 | 0 | } break; |
1865 | 0 | } |
1866 | 0 | } |
1867 | | |
1868 | | UString toString(const LocationRange &loc) |
1869 | 5.00k | { |
1870 | 5.00k | return manifestJson(loc, false, U""); |
1871 | 5.00k | } |
1872 | | |
1873 | | /** Recursively collect an object's invariants. |
1874 | | * |
1875 | | * \param curr |
1876 | | * \param self |
1877 | | * \param offset |
1878 | | * \param thunks |
1879 | | */ |
1880 | | void objectInvariants(HeapObject *curr, HeapObject *self, unsigned &counter, |
1881 | | std::vector<HeapThunk *> &thunks) |
1882 | 281k | { |
1883 | 281k | if (auto *ext = dynamic_cast<HeapExtendedObject *>(curr)) { |
1884 | 14.9k | objectInvariants(ext->right, self, counter, thunks); |
1885 | 14.9k | objectInvariants(ext->left, self, counter, thunks); |
1886 | 266k | } else { |
1887 | 266k | if (auto *simp = dynamic_cast<HeapSimpleObject *>(curr)) { |
1888 | 298k | for (AST *assert : simp->asserts) { |
1889 | 298k | auto *el_th = makeHeap<HeapThunk>(idInvariant, self, counter, assert); |
1890 | 298k | el_th->upValues = simp->upValues; |
1891 | 298k | thunks.push_back(el_th); |
1892 | 298k | } |
1893 | 266k | } |
1894 | 266k | counter++; |
1895 | 266k | } |
1896 | 281k | } |
1897 | | |
1898 | | /** Index an object's field. |
1899 | | * |
1900 | | * \param loc Location where the e.f occurred. |
1901 | | * \param obj The target |
1902 | | * \param f The field |
1903 | | */ |
1904 | | const AST *objectIndex(const LocationRange &loc, HeapObject *obj, const Identifier *f, |
1905 | | unsigned offset) |
1906 | 255k | { |
1907 | 255k | unsigned found_at = 0; |
1908 | 255k | HeapObject *self = obj; |
1909 | 255k | HeapLeafObject *found = findObject(f, obj, offset, found_at); |
1910 | 255k | if (found == nullptr) { |
1911 | 0 | throw makeError(loc, "field does not exist: " + encode_utf8(f->name)); |
1912 | 0 | } |
1913 | 255k | if (auto *simp = dynamic_cast<HeapSimpleObject *>(found)) { |
1914 | 255k | auto it = simp->fields.find(f); |
1915 | 255k | const AST *body = it->second.body; |
1916 | | |
1917 | 255k | stack.newCall(loc, simp, self, found_at, simp->upValues); |
1918 | 255k | return body; |
1919 | 255k | } else { |
1920 | | // If a HeapLeafObject is not HeapSimpleObject, it must be HeapComprehensionObject. |
1921 | 0 | auto *comp = static_cast<HeapComprehensionObject *>(found); |
1922 | 0 | auto it = comp->compValues.find(f); |
1923 | 0 | auto *th = it->second; |
1924 | 0 | BindingFrame binds = comp->upValues; |
1925 | 0 | binds[comp->id] = th; |
1926 | 0 | stack.newCall(loc, comp, self, found_at, binds); |
1927 | 0 | return comp->value; |
1928 | 0 | } |
1929 | 255k | } |
1930 | | |
1931 | | void runInvariants(const LocationRange &loc, HeapObject *self) |
1932 | 5.51k | { |
1933 | 5.51k | if (stack.alreadyExecutingInvariants(self)) |
1934 | 0 | return; |
1935 | | |
1936 | 5.51k | unsigned counter = 0; |
1937 | 5.51k | unsigned initial_stack_size = stack.size(); |
1938 | 5.51k | stack.newFrame(FRAME_INVARIANTS, loc); |
1939 | 5.51k | std::vector<HeapThunk *> &thunks = stack.top().thunks; |
1940 | 5.51k | objectInvariants(self, self, counter, thunks); |
1941 | 5.51k | if (thunks.size() == 0) { |
1942 | 5.34k | stack.pop(); |
1943 | 5.34k | return; |
1944 | 5.34k | } |
1945 | 172 | HeapThunk *thunk = thunks[0]; |
1946 | 172 | stack.top().elementId = 1; |
1947 | 172 | stack.top().self = self; |
1948 | 172 | stack.newCall(loc, thunk, thunk->self, thunk->offset, thunk->upValues); |
1949 | 172 | evaluate(thunk->body, initial_stack_size); |
1950 | 172 | } |
1951 | | |
1952 | | /** Call a sourceVal function with given arguments. |
1953 | | * |
1954 | | * This function requires all arguments to be positional. It also does not |
1955 | | * support default arguments. This is intended to be used internally so, |
1956 | | * error checking is also skipped. |
1957 | | */ |
1958 | 0 | const AST *callSourceVal(const AST *ast, HeapThunk *sourceVal, std::vector<HeapThunk*> args) { |
1959 | 0 | assert(sourceVal != nullptr); |
1960 | 0 | assert(sourceVal->filled); |
1961 | 0 | assert(sourceVal->content.t == Value::FUNCTION); |
1962 | 0 | auto *func = static_cast<HeapClosure *>(sourceVal->content.v.h); |
1963 | 0 | BindingFrame up_values = func->upValues; |
1964 | 0 | for (size_t i = 0; i < args.size(); ++i) { |
1965 | 0 | up_values.insert({func->params[i].id, args[i]}); |
1966 | 0 | } |
1967 | 0 | stack.newCall(ast->location, func, func->self, func->offset, up_values); |
1968 | 0 | return func->body; |
1969 | 0 | } |
1970 | | |
1971 | | /** Evaluate the given AST to a value. |
1972 | | * |
1973 | | * Rather than call itself recursively, this function maintains a separate stack of |
1974 | | * partially-evaluated constructs. First, the AST is handled depending on its type. If |
1975 | | * this cannot be completed without evaluating another AST (e.g. a sub expression) then a |
1976 | | * frame is pushed onto the stack containing the partial state, and the code jumps back to |
1977 | | * the beginning of this function. Once there are no more ASTs to evaluate, the code |
1978 | | * executes the second part of the function to unwind the stack. If the stack cannot be |
1979 | | * completely unwound without evaluating an AST then it jumps back to the beginning of the |
1980 | | * function again. The process terminates when the AST has been processed and the stack is |
1981 | | * the same size it was at the beginning of the call to evaluate. |
1982 | | */ |
1983 | | void evaluate(const AST *ast_, unsigned initial_stack_size) |
1984 | 2.64M | { |
1985 | 5.47M | recurse: |
1986 | | |
1987 | 5.47M | switch (ast_->type) { |
1988 | 273k | case AST_APPLY: { |
1989 | 273k | const auto &ast = *static_cast<const Apply *>(ast_); |
1990 | 273k | stack.newFrame(FRAME_APPLY_TARGET, ast_); |
1991 | 273k | ast_ = ast.target; |
1992 | 273k | goto recurse; |
1993 | 0 | } break; |
1994 | | |
1995 | 690 | case AST_ARRAY: { |
1996 | 690 | const auto &ast = *static_cast<const Array *>(ast_); |
1997 | 690 | HeapObject *self; |
1998 | 690 | unsigned offset; |
1999 | 690 | stack.getSelfBinding(self, offset); |
2000 | 690 | scratch = makeArray({}); |
2001 | 690 | auto &elements = static_cast<HeapArray *>(scratch.v.h)->elements; |
2002 | 5.64M | for (const auto &el : ast.elements) { |
2003 | 5.64M | auto *el_th = makeHeap<HeapThunk>(idArrayElement, self, offset, el.expr); |
2004 | 5.64M | el_th->upValues = capture(el.expr->freeVariables); |
2005 | 5.64M | elements.push_back(el_th); |
2006 | 5.64M | } |
2007 | 690 | } break; |
2008 | | |
2009 | 117k | case AST_BINARY: { |
2010 | 117k | const auto &ast = *static_cast<const Binary *>(ast_); |
2011 | 117k | stack.newFrame(FRAME_BINARY_LEFT, ast_); |
2012 | 117k | ast_ = ast.left; |
2013 | 117k | goto recurse; |
2014 | 0 | } break; |
2015 | | |
2016 | 206k | case AST_BUILTIN_FUNCTION: { |
2017 | 206k | const auto &ast = *static_cast<const BuiltinFunction *>(ast_); |
2018 | 206k | HeapClosure::Params params; |
2019 | 206k | params.reserve(ast.params.size()); |
2020 | 320k | for (const auto &p : ast.params) { |
2021 | | // None of the builtins have default args. |
2022 | 320k | params.emplace_back(p, nullptr); |
2023 | 320k | } |
2024 | 206k | scratch = makeBuiltin(ast.name, params); |
2025 | 206k | } break; |
2026 | | |
2027 | 156k | case AST_CONDITIONAL: { |
2028 | 156k | const auto &ast = *static_cast<const Conditional *>(ast_); |
2029 | 156k | stack.newFrame(FRAME_IF, ast_); |
2030 | 156k | ast_ = ast.cond; |
2031 | 156k | goto recurse; |
2032 | 0 | } break; |
2033 | | |
2034 | 1.33k | case AST_ERROR: { |
2035 | 1.33k | const auto &ast = *static_cast<const Error *>(ast_); |
2036 | 1.33k | stack.newFrame(FRAME_ERROR, ast_); |
2037 | 1.33k | ast_ = ast.expr; |
2038 | 1.33k | goto recurse; |
2039 | 0 | } break; |
2040 | | |
2041 | 37.9k | case AST_FUNCTION: { |
2042 | 37.9k | const auto &ast = *static_cast<const Function *>(ast_); |
2043 | 37.9k | auto env = capture(ast.freeVariables); |
2044 | 37.9k | HeapObject *self; |
2045 | 37.9k | unsigned offset; |
2046 | 37.9k | stack.getSelfBinding(self, offset); |
2047 | 37.9k | HeapClosure::Params params; |
2048 | 37.9k | params.reserve(ast.params.size()); |
2049 | 76.7k | for (const auto &p : ast.params) { |
2050 | 76.7k | params.emplace_back(p.id, p.expr); |
2051 | 76.7k | } |
2052 | 37.9k | scratch = makeClosure(env, self, offset, params, ast.body); |
2053 | 37.9k | } break; |
2054 | | |
2055 | 20 | case AST_IMPORT: { |
2056 | 20 | const auto &ast = *static_cast<const Import *>(ast_); |
2057 | 20 | HeapThunk *thunk = import(ast.location, ast.file); |
2058 | 20 | if (thunk->filled) { |
2059 | 0 | scratch = thunk->content; |
2060 | 20 | } else { |
2061 | 20 | stack.newCall(ast.location, thunk, thunk->self, thunk->offset, thunk->upValues); |
2062 | 20 | ast_ = thunk->body; |
2063 | 20 | goto recurse; |
2064 | 20 | } |
2065 | 20 | } break; |
2066 | | |
2067 | 0 | case AST_IMPORTSTR: { |
2068 | 0 | const auto &ast = *static_cast<const Importstr *>(ast_); |
2069 | 0 | const ImportCacheValue *value = importString(ast.location, ast.file); |
2070 | 0 | scratch = makeString(decode_utf8(value->content)); |
2071 | 0 | } break; |
2072 | | |
2073 | 7.10k | case AST_IN_SUPER: { |
2074 | 7.10k | const auto &ast = *static_cast<const InSuper *>(ast_); |
2075 | 7.10k | stack.newFrame(FRAME_IN_SUPER_ELEMENT, ast_); |
2076 | 7.10k | ast_ = ast.element; |
2077 | 7.10k | goto recurse; |
2078 | 20 | } break; |
2079 | | |
2080 | 267k | case AST_INDEX: { |
2081 | 267k | const auto &ast = *static_cast<const Index *>(ast_); |
2082 | 267k | stack.newFrame(FRAME_INDEX_TARGET, ast_); |
2083 | 267k | ast_ = ast.target; |
2084 | 267k | goto recurse; |
2085 | 20 | } break; |
2086 | | |
2087 | 143k | case AST_LOCAL: { |
2088 | 143k | const auto &ast = *static_cast<const Local *>(ast_); |
2089 | 143k | stack.newFrame(FRAME_LOCAL, ast_); |
2090 | 143k | Frame &f = stack.top(); |
2091 | | // First build all the thunks and bind them. |
2092 | 143k | HeapObject *self; |
2093 | 143k | unsigned offset; |
2094 | 143k | stack.getSelfBinding(self, offset); |
2095 | 323k | for (const auto &bind : ast.binds) { |
2096 | | // Note that these 2 lines must remain separate to avoid the GC running |
2097 | | // when bindings has a nullptr for key bind.first. |
2098 | 323k | auto *th = makeHeap<HeapThunk>(bind.var, self, offset, bind.body); |
2099 | 323k | f.bindings[bind.var] = th; |
2100 | 323k | } |
2101 | | // Now capture the environment (including the new thunks, to make cycles). |
2102 | 323k | for (const auto &bind : ast.binds) { |
2103 | 323k | auto *thunk = f.bindings[bind.var]; |
2104 | 323k | thunk->upValues = capture(bind.body->freeVariables); |
2105 | 323k | } |
2106 | 143k | ast_ = ast.body; |
2107 | 143k | goto recurse; |
2108 | 20 | } break; |
2109 | | |
2110 | 13.3k | case AST_LITERAL_BOOLEAN: { |
2111 | 13.3k | const auto &ast = *static_cast<const LiteralBoolean *>(ast_); |
2112 | 13.3k | scratch = makeBoolean(ast.value); |
2113 | 13.3k | } break; |
2114 | | |
2115 | 2.69M | case AST_LITERAL_NUMBER: { |
2116 | 2.69M | const auto &ast = *static_cast<const LiteralNumber *>(ast_); |
2117 | 2.69M | scratch = makeNumberCheck(ast_->location, ast.value); |
2118 | 2.69M | } break; |
2119 | | |
2120 | 662k | case AST_LITERAL_STRING: { |
2121 | 662k | const auto &ast = *static_cast<const LiteralString *>(ast_); |
2122 | 662k | scratch = makeString(ast.value); |
2123 | 662k | } break; |
2124 | | |
2125 | 3.27k | case AST_LITERAL_NULL: { |
2126 | 3.27k | scratch = makeNull(); |
2127 | 3.27k | } break; |
2128 | | |
2129 | 26.4k | case AST_DESUGARED_OBJECT: { |
2130 | 26.4k | const auto &ast = *static_cast<const DesugaredObject *>(ast_); |
2131 | 26.4k | if (ast.fields.empty()) { |
2132 | 8.61k | auto env = capture(ast.freeVariables); |
2133 | 8.61k | std::map<const Identifier *, HeapSimpleObject::Field> fields; |
2134 | 8.61k | scratch = makeObject<HeapSimpleObject>(env, fields, ast.asserts); |
2135 | 17.8k | } else { |
2136 | 17.8k | auto env = capture(ast.freeVariables); |
2137 | 17.8k | stack.newFrame(FRAME_OBJECT, ast_); |
2138 | 17.8k | auto fit = ast.fields.begin(); |
2139 | 17.8k | stack.top().fit = fit; |
2140 | 17.8k | ast_ = fit->name; |
2141 | 17.8k | goto recurse; |
2142 | 17.8k | } |
2143 | 26.4k | } break; |
2144 | | |
2145 | 8.61k | case AST_OBJECT_COMPREHENSION_SIMPLE: { |
2146 | 0 | const auto &ast = *static_cast<const ObjectComprehensionSimple *>(ast_); |
2147 | 0 | stack.newFrame(FRAME_OBJECT_COMP_ARRAY, ast_); |
2148 | 0 | ast_ = ast.array; |
2149 | 0 | goto recurse; |
2150 | 26.4k | } break; |
2151 | | |
2152 | 36.3k | case AST_SELF: { |
2153 | 36.3k | scratch.t = Value::OBJECT; |
2154 | 36.3k | HeapObject *self; |
2155 | 36.3k | unsigned offset; |
2156 | 36.3k | stack.getSelfBinding(self, offset); |
2157 | 36.3k | scratch.v.h = self; |
2158 | 36.3k | } break; |
2159 | | |
2160 | 3.90k | case AST_SUPER_INDEX: { |
2161 | 3.90k | const auto &ast = *static_cast<const SuperIndex *>(ast_); |
2162 | 3.90k | stack.newFrame(FRAME_SUPER_INDEX, ast_); |
2163 | 3.90k | ast_ = ast.index; |
2164 | 3.90k | goto recurse; |
2165 | 26.4k | } break; |
2166 | | |
2167 | 46.5k | case AST_UNARY: { |
2168 | 46.5k | const auto &ast = *static_cast<const Unary *>(ast_); |
2169 | 46.5k | stack.newFrame(FRAME_UNARY, ast_); |
2170 | 46.5k | ast_ = ast.expr; |
2171 | 46.5k | goto recurse; |
2172 | 26.4k | } break; |
2173 | | |
2174 | 775k | case AST_VAR: { |
2175 | 775k | const auto &ast = *static_cast<const Var *>(ast_); |
2176 | 775k | auto *thunk = stack.lookUpVar(ast.id); |
2177 | 775k | if (thunk == nullptr) { |
2178 | 0 | std::cerr << "INTERNAL ERROR: Could not bind variable: " |
2179 | 0 | << encode_utf8(ast.id->name) << " at " |
2180 | 0 | << ast.location << std::endl; |
2181 | 0 | std::abort(); |
2182 | 0 | } |
2183 | 775k | if (thunk->filled) { |
2184 | 616k | scratch = thunk->content; |
2185 | 616k | } else { |
2186 | 159k | stack.newCall(ast.location, thunk, thunk->self, thunk->offset, thunk->upValues); |
2187 | 159k | ast_ = thunk->body; |
2188 | 159k | goto recurse; |
2189 | 159k | } |
2190 | 775k | } break; |
2191 | | |
2192 | 616k | default: |
2193 | 0 | std::cerr << "INTERNAL ERROR: Unknown AST: " << ast_->type << std::endl; |
2194 | 0 | std::abort(); |
2195 | 5.47M | } |
2196 | | |
2197 | | // To evaluate another AST, set ast to it, then goto recurse. |
2198 | | // To pop, exit the switch or goto popframe |
2199 | | // To change the frame and re-enter the switch, goto replaceframe |
2200 | 6.03M | while (stack.size() > initial_stack_size) { |
2201 | 3.38M | Frame &f = stack.top(); |
2202 | 3.38M | switch (f.kind) { |
2203 | 273k | case FRAME_APPLY_TARGET: { |
2204 | 273k | const auto &ast = *static_cast<const Apply *>(f.ast); |
2205 | 273k | if (scratch.t != Value::FUNCTION) { |
2206 | 0 | throw makeError(ast.location, |
2207 | 0 | "only functions can be called, got " + type_str(scratch)); |
2208 | 0 | } |
2209 | 273k | auto *func = static_cast<HeapClosure *>(scratch.v.h); |
2210 | | |
2211 | 273k | std::set<const Identifier *> params_needed; |
2212 | 495k | for (const auto ¶m : func->params) { |
2213 | 495k | params_needed.insert(param.id); |
2214 | 495k | } |
2215 | | |
2216 | | // Create thunks for arguments. |
2217 | 273k | std::vector<HeapThunk *> positional_args; |
2218 | 273k | BindingFrame args; |
2219 | 273k | bool got_named = false; |
2220 | 769k | for (std::size_t i = 0; i < ast.args.size(); ++i) { |
2221 | 495k | const auto &arg = ast.args[i]; |
2222 | | |
2223 | 495k | const Identifier *name; |
2224 | 495k | if (arg.id != nullptr) { |
2225 | 0 | got_named = true; |
2226 | 0 | name = arg.id; |
2227 | 495k | } else { |
2228 | 495k | if (got_named) { |
2229 | 0 | std::stringstream ss; |
2230 | 0 | ss << "internal error: got positional param after named at index " |
2231 | 0 | << i; |
2232 | 0 | throw makeError(ast.location, ss.str()); |
2233 | 0 | } |
2234 | 495k | if (i >= func->params.size()) { |
2235 | 0 | std::stringstream ss; |
2236 | 0 | ss << "too many args, function has " << func->params.size() |
2237 | 0 | << " parameter(s)"; |
2238 | 0 | throw makeError(ast.location, ss.str()); |
2239 | 0 | } |
2240 | 495k | name = func->params[i].id; |
2241 | 495k | } |
2242 | | // Special case for builtin functions -- leave identifier blank for |
2243 | | // them in the thunk. This removes the thunk frame from the stacktrace. |
2244 | 495k | const Identifier *name_ = func->body == nullptr ? nullptr : name; |
2245 | 495k | HeapObject *self; |
2246 | 495k | unsigned offset; |
2247 | 495k | stack.getSelfBinding(self, offset); |
2248 | 495k | auto *thunk = makeHeap<HeapThunk>(name_, self, offset, arg.expr); |
2249 | 495k | thunk->upValues = capture(arg.expr->freeVariables); |
2250 | | // While making the thunks, keep them in a frame to avoid premature garbage |
2251 | | // collection. |
2252 | 495k | f.thunks.push_back(thunk); |
2253 | 495k | if (args.find(name) != args.end()) { |
2254 | 0 | std::stringstream ss; |
2255 | 0 | ss << "binding parameter a second time: " << encode_utf8(name->name); |
2256 | 0 | throw makeError(ast.location, ss.str()); |
2257 | 0 | } |
2258 | 495k | args[name] = thunk; |
2259 | 495k | if (params_needed.find(name) == params_needed.end()) { |
2260 | 0 | std::stringstream ss; |
2261 | 0 | ss << "function has no parameter " << encode_utf8(name->name); |
2262 | 0 | throw makeError(ast.location, ss.str()); |
2263 | 0 | } |
2264 | 495k | } |
2265 | | |
2266 | | // For any func params for which there was no arg, create a thunk for those and |
2267 | | // bind the default argument. Allow default thunks to see other params. If no |
2268 | | // default argument than raise an error. |
2269 | | |
2270 | | // Raise errors for unbound params, create thunks (but don't fill in upvalues). |
2271 | | // This is a subset of f.thunks, so will not get garbage collected. |
2272 | 273k | std::vector<HeapThunk *> def_arg_thunks; |
2273 | 495k | for (const auto ¶m : func->params) { |
2274 | 495k | if (args.find(param.id) != args.end()) |
2275 | 495k | continue; |
2276 | 0 | if (param.def == nullptr) { |
2277 | 0 | std::stringstream ss; |
2278 | 0 | ss << "function parameter " << encode_utf8(param.id->name) |
2279 | 0 | << " not bound in call."; |
2280 | 0 | throw makeError(ast.location, ss.str()); |
2281 | 0 | } |
2282 | | |
2283 | | // Special case for builtin functions -- leave identifier blank for |
2284 | | // them in the thunk. This removes the thunk frame from the stacktrace. |
2285 | 0 | const Identifier *name_ = func->body == nullptr ? nullptr : param.id; |
2286 | 0 | auto *thunk = |
2287 | 0 | makeHeap<HeapThunk>(name_, func->self, func->offset, param.def); |
2288 | 0 | f.thunks.push_back(thunk); |
2289 | 0 | def_arg_thunks.push_back(thunk); |
2290 | 0 | args[param.id] = thunk; |
2291 | 0 | } |
2292 | | |
2293 | 273k | BindingFrame up_values = func->upValues; |
2294 | 273k | up_values.insert(args.begin(), args.end()); |
2295 | | |
2296 | | // Fill in upvalues |
2297 | 273k | for (HeapThunk *thunk : def_arg_thunks) { |
2298 | 0 | thunk->upValues = up_values; |
2299 | 0 | } |
2300 | | |
2301 | | // Cache these, because pop will invalidate them. |
2302 | 273k | std::vector<HeapThunk *> thunks_copy = f.thunks; |
2303 | | |
2304 | 273k | const AST *f_ast = f.ast; |
2305 | 273k | stack.pop(); |
2306 | | |
2307 | 273k | if (func->body == nullptr) { |
2308 | | // Built-in function. |
2309 | | // Give nullptr for self because no one looking at this frame will |
2310 | | // attempt to bind to self (it's native code). |
2311 | 206k | stack.newFrame(FRAME_BUILTIN_FORCE_THUNKS, f_ast); |
2312 | 206k | stack.top().thunks = thunks_copy; |
2313 | 206k | stack.top().val = scratch; |
2314 | 206k | goto replaceframe; |
2315 | 206k | } else { |
2316 | | // User defined function. |
2317 | 67.6k | stack.newCall(ast.location, func, func->self, func->offset, up_values); |
2318 | 67.6k | if (ast.tailstrict) { |
2319 | 53.8k | stack.top().tailCall = true; |
2320 | 53.8k | if (thunks_copy.size() == 0) { |
2321 | | // No need to force thunks, proceed straight to body. |
2322 | 0 | ast_ = func->body; |
2323 | 0 | goto recurse; |
2324 | 53.8k | } else { |
2325 | | // The check for args.size() > 0 |
2326 | 53.8k | stack.top().thunks = thunks_copy; |
2327 | 53.8k | stack.top().val = scratch; |
2328 | 53.8k | goto replaceframe; |
2329 | 53.8k | } |
2330 | 53.8k | } else { |
2331 | 13.7k | ast_ = func->body; |
2332 | 13.7k | goto recurse; |
2333 | 13.7k | } |
2334 | 67.6k | } |
2335 | 273k | } break; |
2336 | | |
2337 | 114k | case FRAME_BINARY_LEFT: { |
2338 | 114k | const auto &ast = *static_cast<const Binary *>(f.ast); |
2339 | 114k | const Value &lhs = scratch; |
2340 | 114k | if (lhs.t == Value::BOOLEAN) { |
2341 | | // Handle short-cut semantics |
2342 | 1.05k | switch (ast.op) { |
2343 | 429 | case BOP_AND: { |
2344 | 429 | if (!lhs.v.b) { |
2345 | 207 | scratch = makeBoolean(false); |
2346 | 207 | goto popframe; |
2347 | 207 | } |
2348 | 429 | } break; |
2349 | | |
2350 | 618 | case BOP_OR: { |
2351 | 618 | if (lhs.v.b) { |
2352 | 6 | scratch = makeBoolean(true); |
2353 | 6 | goto popframe; |
2354 | 6 | } |
2355 | 618 | } break; |
2356 | | |
2357 | 612 | default:; |
2358 | 1.05k | } |
2359 | 1.05k | } |
2360 | 114k | stack.top().kind = FRAME_BINARY_RIGHT; |
2361 | 114k | stack.top().val = lhs; |
2362 | 114k | ast_ = ast.right; |
2363 | 114k | goto recurse; |
2364 | 114k | } break; |
2365 | | |
2366 | 114k | case FRAME_BINARY_RIGHT: { |
2367 | 114k | stack.top().val2 = scratch; |
2368 | 114k | stack.top().kind = FRAME_BINARY_OP; |
2369 | 114k | } |
2370 | | // Falls through. |
2371 | 114k | case FRAME_BINARY_OP: { |
2372 | 114k | const auto &ast = *static_cast<const Binary *>(f.ast); |
2373 | 114k | const Value &lhs = stack.top().val; |
2374 | 114k | const Value &rhs = stack.top().val2; |
2375 | | |
2376 | | // Handle cases where the LHS and RHS are not the same type. |
2377 | 114k | if (lhs.t == Value::STRING || rhs.t == Value::STRING) { |
2378 | 34.0k | if (ast.op == BOP_PLUS) { |
2379 | | // Handle co-ercions for string processing. |
2380 | 33.8k | stack.top().kind = FRAME_STRING_CONCAT; |
2381 | 33.8k | stack.top().val2 = rhs; |
2382 | 33.8k | goto replaceframe; |
2383 | 33.8k | } |
2384 | 34.0k | } |
2385 | 80.8k | switch (ast.op) { |
2386 | | // Equality can be used when the types don't match. |
2387 | 0 | case BOP_MANIFEST_EQUAL: |
2388 | 0 | std::cerr << "INTERNAL ERROR: Equals not desugared" << std::endl; |
2389 | 0 | abort(); |
2390 | | |
2391 | | // Equality can be used when the types don't match. |
2392 | 0 | case BOP_MANIFEST_UNEQUAL: |
2393 | 0 | std::cerr << "INTERNAL ERROR: Notequals not desugared" << std::endl; |
2394 | 0 | abort(); |
2395 | | |
2396 | | // e in e |
2397 | 34 | case BOP_IN: { |
2398 | 34 | if (lhs.t != Value::STRING) { |
2399 | 12 | throw makeError(ast.location, |
2400 | 12 | "the left hand side of the 'in' operator should be " |
2401 | 12 | "a string, got " + |
2402 | 12 | type_str(lhs)); |
2403 | 12 | } |
2404 | 22 | auto *field = static_cast<HeapString *>(lhs.v.h); |
2405 | 22 | switch (rhs.t) { |
2406 | 22 | case Value::OBJECT: { |
2407 | 22 | auto *obj = static_cast<HeapObject *>(rhs.v.h); |
2408 | 22 | auto *fid = alloc->makeIdentifier(field->value); |
2409 | 22 | unsigned unused_found_at = 0; |
2410 | 22 | bool in = findObject(fid, obj, 0, unused_found_at); |
2411 | 22 | scratch = makeBoolean(in); |
2412 | 22 | } break; |
2413 | | |
2414 | 0 | default: |
2415 | 0 | throw makeError( |
2416 | 0 | ast.location, |
2417 | 0 | "the right hand side of the 'in' operator should be" |
2418 | 0 | " an object, got " + |
2419 | 0 | type_str(rhs)); |
2420 | 22 | } |
2421 | 22 | goto popframe; |
2422 | 22 | } |
2423 | | |
2424 | 80.8k | default:; |
2425 | 80.8k | } |
2426 | | // Everything else requires matching types. |
2427 | 80.8k | if (lhs.t != rhs.t) { |
2428 | 0 | throw makeError(ast.location, |
2429 | 0 | "binary operator " + bop_string(ast.op) + |
2430 | 0 | " requires " |
2431 | 0 | "matching types, got " + |
2432 | 0 | type_str(lhs) + " and " + type_str(rhs) + "."); |
2433 | 0 | } |
2434 | 80.8k | switch (lhs.t) { |
2435 | 195 | case Value::ARRAY: |
2436 | 195 | if (ast.op == BOP_PLUS) { |
2437 | 195 | auto *arr_l = static_cast<HeapArray *>(lhs.v.h); |
2438 | 195 | auto *arr_r = static_cast<HeapArray *>(rhs.v.h); |
2439 | 195 | std::vector<HeapThunk *> elements; |
2440 | 195 | for (auto *el : arr_l->elements) |
2441 | 214 | elements.push_back(el); |
2442 | 195 | for (auto *el : arr_r->elements) |
2443 | 297 | elements.push_back(el); |
2444 | 195 | scratch = makeArray(elements); |
2445 | 195 | } else if (ast.op == BOP_LESS || ast.op == BOP_LESS_EQ || ast.op == BOP_GREATER || ast.op == BOP_GREATER_EQ) { |
2446 | 0 | HeapThunk *func; |
2447 | 0 | switch(ast.op) { |
2448 | 0 | case BOP_LESS: |
2449 | 0 | func = sourceVals["__array_less"]; |
2450 | 0 | break; |
2451 | 0 | case BOP_LESS_EQ: |
2452 | 0 | func = sourceVals["__array_less_or_equal"]; |
2453 | 0 | break; |
2454 | 0 | case BOP_GREATER: |
2455 | 0 | func = sourceVals["__array_greater"]; |
2456 | 0 | break; |
2457 | 0 | case BOP_GREATER_EQ: |
2458 | 0 | func = sourceVals["__array_greater_or_equal"]; |
2459 | 0 | break; |
2460 | 0 | default: |
2461 | 0 | JSONNET_UNREACHABLE(); |
2462 | 0 | } |
2463 | 0 | if (!func->filled) { |
2464 | 0 | stack.newCall(ast.location, func, func->self, func->offset, func->upValues); |
2465 | 0 | ast_ = func->body; |
2466 | 0 | goto recurse; |
2467 | 0 | } |
2468 | 0 | auto *lhs_th = makeHeap<HeapThunk>(idInternal, f.self, f.offset, ast.left); |
2469 | 0 | lhs_th->fill(lhs); |
2470 | 0 | f.thunks.push_back(lhs_th); |
2471 | 0 | auto *rhs_th = makeHeap<HeapThunk>(idInternal, f.self, f.offset, ast.right); |
2472 | 0 | rhs_th->fill(rhs); |
2473 | 0 | f.thunks.push_back(rhs_th); |
2474 | 0 | const AST *orig_ast = ast_; |
2475 | 0 | stack.pop(); |
2476 | 0 | ast_ = callSourceVal(orig_ast, func, {lhs_th, rhs_th}); |
2477 | 0 | goto recurse; |
2478 | 0 | } else { |
2479 | 0 | throw makeError(ast.location, |
2480 | 0 | "binary operator " + bop_string(ast.op) + |
2481 | 0 | " does not operate on arrays."); |
2482 | 0 | } |
2483 | 195 | break; |
2484 | | |
2485 | 834 | case Value::BOOLEAN: |
2486 | 834 | switch (ast.op) { |
2487 | 222 | case BOP_AND: scratch = makeBoolean(lhs.v.b && rhs.v.b); break; |
2488 | | |
2489 | 612 | case BOP_OR: scratch = makeBoolean(lhs.v.b || rhs.v.b); break; |
2490 | | |
2491 | 0 | default: |
2492 | 0 | throw makeError(ast.location, |
2493 | 0 | "binary operator " + bop_string(ast.op) + |
2494 | 0 | " does not operate on booleans."); |
2495 | 834 | } |
2496 | 834 | break; |
2497 | | |
2498 | 68.1k | case Value::NUMBER: |
2499 | 68.1k | switch (ast.op) { |
2500 | 22.2k | case BOP_PLUS: |
2501 | 22.2k | scratch = makeNumberCheck(ast.location, lhs.v.d + rhs.v.d); |
2502 | 22.2k | break; |
2503 | | |
2504 | 14.2k | case BOP_MINUS: |
2505 | 14.2k | scratch = makeNumberCheck(ast.location, lhs.v.d - rhs.v.d); |
2506 | 14.2k | break; |
2507 | | |
2508 | 353 | case BOP_MULT: |
2509 | 353 | scratch = makeNumberCheck(ast.location, lhs.v.d * rhs.v.d); |
2510 | 353 | break; |
2511 | | |
2512 | 30 | case BOP_DIV: |
2513 | 30 | if (rhs.v.d == 0) |
2514 | 0 | throw makeError(ast.location, "division by zero."); |
2515 | 30 | scratch = makeNumberCheck(ast.location, lhs.v.d / rhs.v.d); |
2516 | 30 | break; |
2517 | | |
2518 | | // No need to check doubles made from longs |
2519 | | |
2520 | 0 | case BOP_SHIFT_L: { |
2521 | 0 | if (rhs.v.d < 0) |
2522 | 0 | throw makeError(ast.location, "shift by negative exponent."); |
2523 | 0 | int64_t long_l = lhs.v.d; |
2524 | 0 | int64_t long_r = rhs.v.d; |
2525 | 0 | long_r = long_r % 64; |
2526 | 0 | scratch = makeNumber(long_l << long_r); |
2527 | 0 | } break; |
2528 | | |
2529 | 0 | case BOP_SHIFT_R: { |
2530 | 0 | if (rhs.v.d < 0) |
2531 | 0 | throw makeError(ast.location, "shift by negative exponent."); |
2532 | 0 | int64_t long_l = lhs.v.d; |
2533 | 0 | int64_t long_r = rhs.v.d; |
2534 | 0 | long_r = long_r % 64; |
2535 | 0 | scratch = makeNumber(long_l >> long_r); |
2536 | 0 | } break; |
2537 | | |
2538 | 0 | case BOP_BITWISE_AND: { |
2539 | 0 | int64_t long_l = lhs.v.d; |
2540 | 0 | int64_t long_r = rhs.v.d; |
2541 | 0 | scratch = makeNumber(long_l & long_r); |
2542 | 0 | } break; |
2543 | | |
2544 | 0 | case BOP_BITWISE_XOR: { |
2545 | 0 | int64_t long_l = lhs.v.d; |
2546 | 0 | int64_t long_r = rhs.v.d; |
2547 | 0 | scratch = makeNumber(long_l ^ long_r); |
2548 | 0 | } break; |
2549 | | |
2550 | 0 | case BOP_BITWISE_OR: { |
2551 | 0 | int64_t long_l = lhs.v.d; |
2552 | 0 | int64_t long_r = rhs.v.d; |
2553 | 0 | scratch = makeNumber(long_l | long_r); |
2554 | 0 | } break; |
2555 | | |
2556 | 9.80k | case BOP_LESS_EQ: scratch = makeBoolean(lhs.v.d <= rhs.v.d); break; |
2557 | | |
2558 | 19.5k | case BOP_GREATER_EQ: |
2559 | 19.5k | scratch = makeBoolean(lhs.v.d >= rhs.v.d); |
2560 | 19.5k | break; |
2561 | | |
2562 | 1.70k | case BOP_LESS: scratch = makeBoolean(lhs.v.d < rhs.v.d); break; |
2563 | | |
2564 | 188 | case BOP_GREATER: scratch = makeBoolean(lhs.v.d > rhs.v.d); break; |
2565 | | |
2566 | 0 | default: |
2567 | 0 | throw makeError(ast.location, |
2568 | 0 | "binary operator " + bop_string(ast.op) + |
2569 | 0 | " does not operate on numbers."); |
2570 | 68.1k | } |
2571 | 68.1k | break; |
2572 | | |
2573 | 68.1k | case Value::FUNCTION: |
2574 | 0 | throw makeError(ast.location, |
2575 | 0 | "binary operator " + bop_string(ast.op) + |
2576 | 0 | " does not operate on functions."); |
2577 | | |
2578 | 0 | case Value::NULL_TYPE: |
2579 | 0 | throw makeError(ast.location, |
2580 | 0 | "binary operator " + bop_string(ast.op) + |
2581 | 0 | " does not operate on null."); |
2582 | | |
2583 | 11.4k | case Value::OBJECT: { |
2584 | 11.4k | if (ast.op != BOP_PLUS) { |
2585 | 0 | throw makeError(ast.location, |
2586 | 0 | "binary operator " + bop_string(ast.op) + |
2587 | 0 | " does not operate on objects."); |
2588 | 0 | } |
2589 | 11.4k | auto *lhs_obj = static_cast<HeapObject *>(lhs.v.h); |
2590 | 11.4k | auto *rhs_obj = static_cast<HeapObject *>(rhs.v.h); |
2591 | 11.4k | scratch = makeObject<HeapExtendedObject>(lhs_obj, rhs_obj); |
2592 | 11.4k | } break; |
2593 | | |
2594 | 214 | case Value::STRING: { |
2595 | 214 | const UString &lhs_str = static_cast<HeapString *>(lhs.v.h)->value; |
2596 | 214 | const UString &rhs_str = static_cast<HeapString *>(rhs.v.h)->value; |
2597 | 214 | switch (ast.op) { |
2598 | 0 | case BOP_PLUS: scratch = makeString(lhs_str + rhs_str); break; |
2599 | | |
2600 | 6 | case BOP_LESS_EQ: scratch = makeBoolean(lhs_str <= rhs_str); break; |
2601 | | |
2602 | 21 | case BOP_GREATER_EQ: |
2603 | 21 | scratch = makeBoolean(lhs_str >= rhs_str); |
2604 | 21 | break; |
2605 | | |
2606 | 3 | case BOP_LESS: scratch = makeBoolean(lhs_str < rhs_str); break; |
2607 | | |
2608 | 184 | case BOP_GREATER: scratch = makeBoolean(lhs_str > rhs_str); break; |
2609 | | |
2610 | 0 | default: |
2611 | 0 | throw makeError(ast.location, |
2612 | 0 | "binary operator " + bop_string(ast.op) + |
2613 | 0 | " does not operate on strings."); |
2614 | 214 | } |
2615 | 214 | } break; |
2616 | 80.8k | } |
2617 | 80.8k | } break; |
2618 | | |
2619 | 80.8k | case FRAME_BUILTIN_FILTER: { |
2620 | 0 | const auto &ast = *static_cast<const Apply *>(f.ast); |
2621 | 0 | auto *func = static_cast<HeapClosure *>(f.val.v.h); |
2622 | 0 | auto *arr = static_cast<HeapArray *>(f.val2.v.h); |
2623 | 0 | if (scratch.t != Value::BOOLEAN) { |
2624 | 0 | throw makeError( |
2625 | 0 | ast.location, |
2626 | 0 | "filter function must return boolean, got: " + type_str(scratch)); |
2627 | 0 | } |
2628 | 0 | if (scratch.v.b) |
2629 | 0 | f.thunks.push_back(arr->elements[f.elementId]); |
2630 | 0 | f.elementId++; |
2631 | | // Iterate through arr, calling the function on each. |
2632 | 0 | if (f.elementId == arr->elements.size()) { |
2633 | 0 | scratch = makeArray(f.thunks); |
2634 | 0 | } else { |
2635 | 0 | auto *thunk = arr->elements[f.elementId]; |
2636 | 0 | BindingFrame bindings = func->upValues; |
2637 | 0 | bindings[func->params[0].id] = thunk; |
2638 | 0 | stack.newCall(ast.location, func, func->self, func->offset, bindings); |
2639 | 0 | ast_ = func->body; |
2640 | 0 | goto recurse; |
2641 | 0 | } |
2642 | 0 | } break; |
2643 | | |
2644 | 526k | case FRAME_BUILTIN_FORCE_THUNKS: { |
2645 | 526k | const auto &ast = *static_cast<const Apply *>(f.ast); |
2646 | 526k | auto *func = static_cast<HeapClosure *>(f.val.v.h); |
2647 | 526k | if (f.elementId == f.thunks.size()) { |
2648 | | // All thunks forced, now the builtin implementations. |
2649 | 206k | const LocationRange &loc = ast.location; |
2650 | 206k | const std::string &builtin_name = func->builtinName; |
2651 | 206k | std::vector<Value> args; |
2652 | 319k | for (auto *th : f.thunks) { |
2653 | 319k | args.push_back(th->content); |
2654 | 319k | } |
2655 | 206k | BuiltinMap::const_iterator bit = builtins.find(builtin_name); |
2656 | 206k | if (bit != builtins.end()) { |
2657 | 206k | const AST *new_ast = (this->*bit->second)(loc, args); |
2658 | 206k | if (new_ast != nullptr) { |
2659 | 0 | ast_ = new_ast; |
2660 | 0 | goto recurse; |
2661 | 0 | } |
2662 | 206k | break; |
2663 | 206k | } |
2664 | 0 | VmNativeCallbackMap::const_iterator nit = |
2665 | 0 | nativeCallbacks.find(builtin_name); |
2666 | | // TODO(dcunnin): Support arrays. |
2667 | | // TODO(dcunnin): Support objects. |
2668 | 0 | std::vector<JsonnetJsonValue> args2; |
2669 | 0 | for (const Value &arg : args) { |
2670 | 0 | switch (arg.t) { |
2671 | 0 | case Value::STRING: |
2672 | 0 | args2.emplace_back( |
2673 | 0 | JsonnetJsonValue::STRING, |
2674 | 0 | encode_utf8(static_cast<HeapString *>(arg.v.h)->value), |
2675 | 0 | 0); |
2676 | 0 | break; |
2677 | | |
2678 | 0 | case Value::BOOLEAN: |
2679 | 0 | args2.emplace_back( |
2680 | 0 | JsonnetJsonValue::BOOL, "", arg.v.b ? 1.0 : 0.0); |
2681 | 0 | break; |
2682 | | |
2683 | 0 | case Value::NUMBER: |
2684 | 0 | args2.emplace_back(JsonnetJsonValue::NUMBER, "", arg.v.d); |
2685 | 0 | break; |
2686 | | |
2687 | 0 | case Value::NULL_TYPE: |
2688 | 0 | args2.emplace_back(JsonnetJsonValue::NULL_KIND, "", 0); |
2689 | 0 | break; |
2690 | | |
2691 | 0 | default: |
2692 | 0 | throw makeError(ast.location, |
2693 | 0 | "native extensions can only take primitives."); |
2694 | 0 | } |
2695 | 0 | } |
2696 | 0 | std::vector<const JsonnetJsonValue *> args3; |
2697 | 0 | for (size_t i = 0; i < args2.size(); ++i) { |
2698 | 0 | args3.push_back(&args2[i]); |
2699 | 0 | } |
2700 | 0 | if (nit == nativeCallbacks.end()) { |
2701 | 0 | throw makeError(ast.location, |
2702 | 0 | "unrecognized builtin name: " + builtin_name); |
2703 | 0 | } |
2704 | 0 | const VmNativeCallback &cb = nit->second; |
2705 | |
|
2706 | 0 | int succ; |
2707 | 0 | std::unique_ptr<JsonnetJsonValue> r(cb.cb(cb.ctx, args3.data(), &succ)); |
2708 | |
|
2709 | 0 | if (succ) { |
2710 | 0 | bool unused; |
2711 | 0 | jsonToHeap(r, unused, scratch); |
2712 | 0 | } else { |
2713 | 0 | if (r->kind != JsonnetJsonValue::STRING) { |
2714 | 0 | throw makeError( |
2715 | 0 | ast.location, |
2716 | 0 | "native extension returned an error that was not a string."); |
2717 | 0 | } |
2718 | 0 | std::string rs = r->string; |
2719 | 0 | throw makeError(ast.location, rs); |
2720 | 0 | } |
2721 | |
|
2722 | 319k | } else { |
2723 | | // Not all arguments forced yet. |
2724 | 319k | HeapThunk *th = f.thunks[f.elementId++]; |
2725 | 319k | if (!th->filled) { |
2726 | 319k | stack.newCall(ast.location, th, th->self, th->offset, th->upValues); |
2727 | 319k | ast_ = th->body; |
2728 | 319k | goto recurse; |
2729 | 319k | } |
2730 | 319k | } |
2731 | 526k | } break; |
2732 | | |
2733 | 1.12M | case FRAME_CALL: { |
2734 | 1.12M | if (auto *thunk = dynamic_cast<HeapThunk *>(f.context)) { |
2735 | | // If we called a thunk, cache result. |
2736 | 629k | thunk->fill(scratch); |
2737 | 629k | } else if (auto *closure = dynamic_cast<HeapClosure *>(f.context)) { |
2738 | 248k | if (f.elementId < f.thunks.size()) { |
2739 | | // If tailstrict, force thunks |
2740 | 146k | HeapThunk *th = f.thunks[f.elementId++]; |
2741 | 146k | if (!th->filled) { |
2742 | 146k | stack.newCall(f.location, th, th->self, th->offset, th->upValues); |
2743 | 146k | ast_ = th->body; |
2744 | 146k | goto recurse; |
2745 | 146k | } |
2746 | 146k | } else if (f.thunks.size() == 0) { |
2747 | | // Body has now been executed |
2748 | 53.8k | } else { |
2749 | | // Execute the body |
2750 | 53.8k | f.thunks.clear(); |
2751 | 53.8k | f.elementId = 0; |
2752 | 53.8k | ast_ = closure->body; |
2753 | 53.8k | goto recurse; |
2754 | 53.8k | } |
2755 | 248k | } |
2756 | | // Result of call is in scratch, just pop. |
2757 | 1.12M | } break; |
2758 | | |
2759 | 926k | case FRAME_ERROR: { |
2760 | 40 | const auto &ast = *static_cast<const Error *>(f.ast); |
2761 | 40 | UString msg; |
2762 | 40 | if (scratch.t == Value::STRING) { |
2763 | 40 | msg = static_cast<HeapString *>(scratch.v.h)->value; |
2764 | 40 | } else { |
2765 | 0 | msg = toString(ast.location); |
2766 | 0 | } |
2767 | 40 | throw makeError(ast.location, encode_utf8(msg)); |
2768 | 1.12M | } break; |
2769 | | |
2770 | 156k | case FRAME_IF: { |
2771 | 156k | const auto &ast = *static_cast<const Conditional *>(f.ast); |
2772 | 156k | if (scratch.t != Value::BOOLEAN) { |
2773 | 0 | throw makeError( |
2774 | 0 | ast.location, |
2775 | 0 | "condition must be boolean, got " + type_str(scratch) + "."); |
2776 | 0 | } |
2777 | 156k | ast_ = scratch.v.b ? ast.branchTrue : ast.branchFalse; |
2778 | 156k | stack.pop(); |
2779 | 156k | goto recurse; |
2780 | 156k | } break; |
2781 | | |
2782 | 3.90k | case FRAME_SUPER_INDEX: { |
2783 | 3.90k | const auto &ast = *static_cast<const SuperIndex *>(f.ast); |
2784 | 3.90k | HeapObject *self; |
2785 | 3.90k | unsigned offset; |
2786 | 3.90k | stack.getSelfBinding(self, offset); |
2787 | 3.90k | offset++; |
2788 | 3.90k | if (offset >= countLeaves(self)) { |
2789 | 0 | throw makeError(ast.location, |
2790 | 0 | "attempt to use super when there is no super class."); |
2791 | 0 | } |
2792 | 3.90k | if (scratch.t != Value::STRING) { |
2793 | 0 | throw makeError( |
2794 | 0 | ast.location, |
2795 | 0 | "super index must be string, got " + type_str(scratch) + "."); |
2796 | 0 | } |
2797 | | |
2798 | 3.90k | const UString &index_name = static_cast<HeapString *>(scratch.v.h)->value; |
2799 | 3.90k | auto *fid = alloc->makeIdentifier(index_name); |
2800 | 3.90k | stack.pop(); |
2801 | 3.90k | ast_ = objectIndex(ast.location, self, fid, offset); |
2802 | 3.90k | goto recurse; |
2803 | 3.90k | } break; |
2804 | | |
2805 | 7.10k | case FRAME_IN_SUPER_ELEMENT: { |
2806 | 7.10k | const auto &ast = *static_cast<const InSuper *>(f.ast); |
2807 | 7.10k | HeapObject *self; |
2808 | 7.10k | unsigned offset; |
2809 | 7.10k | stack.getSelfBinding(self, offset); |
2810 | 7.10k | offset++; |
2811 | 7.10k | if (scratch.t != Value::STRING) { |
2812 | 0 | throw makeError(ast.location, |
2813 | 0 | "left hand side of e in super must be string, got " + |
2814 | 0 | type_str(scratch) + "."); |
2815 | 0 | } |
2816 | 7.10k | if (offset >= countLeaves(self)) { |
2817 | | // There is no super object. |
2818 | 3.05k | scratch = makeBoolean(false); |
2819 | 4.05k | } else { |
2820 | 4.05k | const UString &element_name = static_cast<HeapString *>(scratch.v.h)->value; |
2821 | 4.05k | auto *fid = alloc->makeIdentifier(element_name); |
2822 | 4.05k | unsigned unused_found_at = 0; |
2823 | 4.05k | bool in = findObject(fid, self, offset, unused_found_at); |
2824 | 4.05k | scratch = makeBoolean(in); |
2825 | 4.05k | } |
2826 | 7.10k | } break; |
2827 | | |
2828 | 267k | case FRAME_INDEX_INDEX: { |
2829 | 267k | const auto &ast = *static_cast<const Index *>(f.ast); |
2830 | 267k | const Value &target = f.val; |
2831 | 267k | if (target.t == Value::ARRAY) { |
2832 | 394 | const auto *array = static_cast<HeapArray *>(target.v.h); |
2833 | 394 | if (scratch.t == Value::STRING) { |
2834 | 0 | const UString &str = static_cast<HeapString *>(scratch.v.h)->value; |
2835 | 0 | throw makeError( |
2836 | 0 | ast.location, |
2837 | 0 | "attempted index an array with string \"" |
2838 | 0 | + encode_utf8(jsonnet_string_escape(str, false)) + "\"."); |
2839 | 0 | } |
2840 | 394 | if (scratch.t != Value::NUMBER) { |
2841 | 0 | throw makeError( |
2842 | 0 | ast.location, |
2843 | 0 | "array index must be number, got " + type_str(scratch) + "."); |
2844 | 0 | } |
2845 | 394 | double index = ::floor(scratch.v.d); |
2846 | 394 | long sz = array->elements.size(); |
2847 | 394 | if (index < 0 || index >= sz) { |
2848 | 0 | std::stringstream ss; |
2849 | 0 | ss << "array bounds error: " << index << " not within [0, " << sz |
2850 | 0 | << ")"; |
2851 | 0 | throw makeError(ast.location, ss.str()); |
2852 | 0 | } |
2853 | 394 | if (scratch.v.d != index) { |
2854 | 0 | std::stringstream ss; |
2855 | 0 | ss << "array index was not integer: " << scratch.v.d; |
2856 | 0 | throw makeError(ast.location, ss.str()); |
2857 | 0 | } |
2858 | | // index < sz <= SIZE_T_MAX |
2859 | 394 | auto *thunk = array->elements[size_t(index)]; |
2860 | 394 | if (thunk->filled) { |
2861 | 18 | scratch = thunk->content; |
2862 | 376 | } else { |
2863 | 376 | stack.pop(); |
2864 | 376 | stack.newCall( |
2865 | 376 | ast.location, thunk, thunk->self, thunk->offset, thunk->upValues); |
2866 | 376 | ast_ = thunk->body; |
2867 | 376 | goto recurse; |
2868 | 376 | } |
2869 | 266k | } else if (target.t == Value::OBJECT) { |
2870 | 246k | auto *obj = static_cast<HeapObject *>(target.v.h); |
2871 | 246k | assert(obj != nullptr); |
2872 | 246k | if (scratch.t != Value::STRING) { |
2873 | 0 | throw makeError( |
2874 | 0 | ast.location, |
2875 | 0 | "object index must be string, got " + type_str(scratch) + "."); |
2876 | 0 | } |
2877 | 246k | const UString &index_name = static_cast<HeapString *>(scratch.v.h)->value; |
2878 | 246k | auto *fid = alloc->makeIdentifier(index_name); |
2879 | 246k | stack.pop(); |
2880 | 246k | ast_ = objectIndex(ast.location, obj, fid, 0); |
2881 | 246k | goto recurse; |
2882 | 246k | } else if (target.t == Value::STRING) { |
2883 | 20.3k | auto *obj = static_cast<HeapString *>(target.v.h); |
2884 | 20.3k | assert(obj != nullptr); |
2885 | 20.3k | if (scratch.t != Value::NUMBER) { |
2886 | 0 | throw makeError( |
2887 | 0 | ast.location, |
2888 | 0 | "string index must be a number, got " + type_str(scratch) + "."); |
2889 | 0 | } |
2890 | 20.3k | long sz = obj->value.length(); |
2891 | 20.3k | long i = (long)scratch.v.d; |
2892 | 20.3k | if (i < 0 || i >= sz) { |
2893 | 0 | std::stringstream ss; |
2894 | 0 | ss << "string bounds error: " << i << " not within [0, " << sz << ")"; |
2895 | 0 | throw makeError(ast.location, ss.str()); |
2896 | 0 | } |
2897 | 20.3k | char32_t ch[] = {obj->value[i], U'\0'}; |
2898 | 20.3k | scratch = makeString(ch); |
2899 | 20.3k | } else { |
2900 | 0 | std::cerr << "INTERNAL ERROR: not object / array / string." << std::endl; |
2901 | 0 | abort(); |
2902 | 0 | } |
2903 | 267k | } break; |
2904 | | |
2905 | 267k | case FRAME_INDEX_TARGET: { |
2906 | 267k | const auto &ast = *static_cast<const Index *>(f.ast); |
2907 | 267k | if (scratch.t != Value::ARRAY && scratch.t != Value::OBJECT && |
2908 | 267k | scratch.t != Value::STRING) { |
2909 | 0 | throw makeError(ast.location, |
2910 | 0 | "can only index objects, strings, and arrays, got " + |
2911 | 0 | type_str(scratch) + "."); |
2912 | 0 | } |
2913 | 267k | f.val = scratch; |
2914 | 267k | f.kind = FRAME_INDEX_INDEX; |
2915 | 267k | if (scratch.t == Value::OBJECT) { |
2916 | 246k | auto *self = static_cast<HeapObject *>(scratch.v.h); |
2917 | 246k | if (!stack.alreadyExecutingInvariants(self)) { |
2918 | 246k | stack.newFrame(FRAME_INVARIANTS, ast.location); |
2919 | 246k | Frame &f2 = stack.top(); |
2920 | 246k | f2.self = self; |
2921 | 246k | unsigned counter = 0; |
2922 | 246k | objectInvariants(self, self, counter, f2.thunks); |
2923 | 246k | if (f2.thunks.size() > 0) { |
2924 | 6 | auto *thunk = f2.thunks[0]; |
2925 | 6 | f2.elementId = 1; |
2926 | 6 | stack.newCall(ast.location, |
2927 | 6 | thunk, |
2928 | 6 | thunk->self, |
2929 | 6 | thunk->offset, |
2930 | 6 | thunk->upValues); |
2931 | 6 | ast_ = thunk->body; |
2932 | 6 | goto recurse; |
2933 | 6 | } |
2934 | 246k | } |
2935 | 246k | } |
2936 | 267k | ast_ = ast.index; |
2937 | 267k | goto recurse; |
2938 | 267k | } break; |
2939 | | |
2940 | 250k | case FRAME_INVARIANTS: { |
2941 | 250k | if (f.elementId >= f.thunks.size()) { |
2942 | 246k | if (stack.size() == initial_stack_size + 1) { |
2943 | | // Just pop, evaluate was invoked by runInvariants. |
2944 | 70 | break; |
2945 | 70 | } |
2946 | 246k | stack.pop(); |
2947 | 246k | Frame &f2 = stack.top(); |
2948 | 246k | const auto &ast = *static_cast<const Index *>(f2.ast); |
2949 | 246k | ast_ = ast.index; |
2950 | 246k | goto recurse; |
2951 | 246k | } |
2952 | 4.05k | auto *thunk = f.thunks[f.elementId++]; |
2953 | 4.05k | stack.newCall(f.location, thunk, thunk->self, thunk->offset, thunk->upValues); |
2954 | 4.05k | ast_ = thunk->body; |
2955 | 4.05k | goto recurse; |
2956 | 250k | } break; |
2957 | | |
2958 | 122k | case FRAME_LOCAL: { |
2959 | | // Result of execution is in scratch already. |
2960 | 122k | } break; |
2961 | | |
2962 | 73.9k | case FRAME_OBJECT: { |
2963 | 73.9k | const auto &ast = *static_cast<const DesugaredObject *>(f.ast); |
2964 | 73.9k | if (scratch.t != Value::NULL_TYPE) { |
2965 | 73.9k | if (scratch.t != Value::STRING) { |
2966 | 0 | throw makeError(ast.location, "field name was not a string."); |
2967 | 0 | } |
2968 | 73.9k | const auto &fname = static_cast<const HeapString *>(scratch.v.h)->value; |
2969 | 73.9k | const Identifier *fid = alloc->makeIdentifier(fname); |
2970 | 73.9k | if (f.objectFields.find(fid) != f.objectFields.end()) { |
2971 | 0 | std::string msg = |
2972 | 0 | "duplicate field name: \"" + encode_utf8(fname) + "\""; |
2973 | 0 | throw makeError(ast.location, msg); |
2974 | 0 | } |
2975 | 73.9k | f.objectFields[fid].hide = f.fit->hide; |
2976 | 73.9k | f.objectFields[fid].body = f.fit->body; |
2977 | 73.9k | } |
2978 | 73.9k | f.fit++; |
2979 | 73.9k | if (f.fit != ast.fields.end()) { |
2980 | 56.0k | ast_ = f.fit->name; |
2981 | 56.0k | goto recurse; |
2982 | 56.0k | } else { |
2983 | 17.8k | auto env = capture(ast.freeVariables); |
2984 | 17.8k | scratch = makeObject<HeapSimpleObject>(env, f.objectFields, ast.asserts); |
2985 | 17.8k | } |
2986 | 73.9k | } break; |
2987 | | |
2988 | 17.8k | case FRAME_OBJECT_COMP_ARRAY: { |
2989 | 0 | const auto &ast = *static_cast<const ObjectComprehensionSimple *>(f.ast); |
2990 | 0 | const Value &arr_v = scratch; |
2991 | 0 | if (scratch.t != Value::ARRAY) { |
2992 | 0 | throw makeError(ast.location, |
2993 | 0 | "object comprehension needs array, got " + type_str(arr_v)); |
2994 | 0 | } |
2995 | 0 | const auto *arr = static_cast<const HeapArray *>(arr_v.v.h); |
2996 | 0 | if (arr->elements.size() == 0) { |
2997 | | // Degenerate case. Just create the object now. |
2998 | 0 | scratch = makeObject<HeapComprehensionObject>( |
2999 | 0 | BindingFrame{}, ast.value, ast.id, BindingFrame{}); |
3000 | 0 | } else { |
3001 | 0 | f.kind = FRAME_OBJECT_COMP_ELEMENT; |
3002 | 0 | f.val = scratch; |
3003 | 0 | f.bindings[ast.id] = arr->elements[0]; |
3004 | 0 | f.elementId = 0; |
3005 | 0 | ast_ = ast.field; |
3006 | 0 | goto recurse; |
3007 | 0 | } |
3008 | 0 | } break; |
3009 | | |
3010 | 0 | case FRAME_OBJECT_COMP_ELEMENT: { |
3011 | 0 | const auto &ast = *static_cast<const ObjectComprehensionSimple *>(f.ast); |
3012 | 0 | const auto *arr = static_cast<const HeapArray *>(f.val.v.h); |
3013 | 0 | if (scratch.t != Value::NULL_TYPE) { |
3014 | 0 | if (scratch.t != Value::STRING) { |
3015 | 0 | std::stringstream ss; |
3016 | 0 | ss << "field must be string, got: " << type_str(scratch); |
3017 | 0 | throw makeError(ast.location, ss.str()); |
3018 | 0 | } |
3019 | 0 | const auto &fname = static_cast<const HeapString *>(scratch.v.h)->value; |
3020 | 0 | const Identifier *fid = alloc->makeIdentifier(fname); |
3021 | 0 | if (f.elements.find(fid) != f.elements.end()) { |
3022 | 0 | throw makeError(ast.location, |
3023 | 0 | "duplicate field name: \"" + encode_utf8(fname) + "\""); |
3024 | 0 | } |
3025 | 0 | f.elements[fid] = arr->elements[f.elementId]; |
3026 | 0 | } |
3027 | 0 | f.elementId++; |
3028 | |
|
3029 | 0 | if (f.elementId == arr->elements.size()) { |
3030 | 0 | auto env = capture(ast.freeVariables); |
3031 | 0 | scratch = |
3032 | 0 | makeObject<HeapComprehensionObject>(env, ast.value, ast.id, f.elements); |
3033 | 0 | } else { |
3034 | 0 | f.bindings[ast.id] = arr->elements[f.elementId]; |
3035 | 0 | ast_ = ast.field; |
3036 | 0 | goto recurse; |
3037 | 0 | } |
3038 | 0 | } break; |
3039 | | |
3040 | 33.8k | case FRAME_STRING_CONCAT: { |
3041 | 33.8k | const auto &ast = *static_cast<const Binary *>(f.ast); |
3042 | 33.8k | const Value &lhs = stack.top().val; |
3043 | 33.8k | UString output; |
3044 | 33.8k | if (lhs.t == Value::STRING) { |
3045 | 33.8k | output.append(static_cast<const HeapString *>(lhs.v.h)->value); |
3046 | 33.8k | } else { |
3047 | 0 | scratch = lhs; |
3048 | 0 | output.append(toString(ast.left->location)); |
3049 | 0 | } |
3050 | 33.8k | const Value &rhs = stack.top().val2; |
3051 | 33.8k | if (rhs.t == Value::STRING) { |
3052 | 28.8k | output.append(static_cast<const HeapString *>(rhs.v.h)->value); |
3053 | 28.8k | } else { |
3054 | 5.00k | scratch = rhs; |
3055 | 5.00k | output.append(toString(ast.right->location)); |
3056 | 5.00k | } |
3057 | 33.8k | scratch = makeString(output); |
3058 | 33.8k | } break; |
3059 | | |
3060 | 44.7k | case FRAME_UNARY: { |
3061 | 44.7k | const auto &ast = *static_cast<const Unary *>(f.ast); |
3062 | 44.7k | switch (scratch.t) { |
3063 | 40.0k | case Value::BOOLEAN: |
3064 | 40.0k | if (ast.op == UOP_NOT) { |
3065 | 40.0k | scratch = makeBoolean(!scratch.v.b); |
3066 | 40.0k | } else { |
3067 | 0 | throw makeError(ast.location, |
3068 | 0 | "unary operator " + uop_string(ast.op) + |
3069 | 0 | " does not operate on booleans."); |
3070 | 0 | } |
3071 | 40.0k | break; |
3072 | | |
3073 | 40.0k | case Value::NUMBER: |
3074 | 4.72k | switch (ast.op) { |
3075 | 143 | case UOP_PLUS: break; |
3076 | | |
3077 | 4.58k | case UOP_MINUS: scratch = makeNumber(-scratch.v.d); break; |
3078 | | |
3079 | 0 | case UOP_BITWISE_NOT: |
3080 | 0 | scratch = makeNumber(~(long)(scratch.v.d)); |
3081 | 0 | break; |
3082 | | |
3083 | 0 | default: |
3084 | 0 | throw makeError(ast.location, |
3085 | 0 | "unary operator " + uop_string(ast.op) + |
3086 | 0 | " does not operate on numbers."); |
3087 | 4.72k | } |
3088 | 4.72k | break; |
3089 | | |
3090 | 4.72k | default: |
3091 | 0 | throw makeError(ast.location, |
3092 | 0 | "unary operator " + uop_string(ast.op) + |
3093 | 0 | " does not operate on type " + type_str(scratch)); |
3094 | 44.7k | } |
3095 | 44.7k | } break; |
3096 | | |
3097 | 44.7k | case FRAME_BUILTIN_JOIN_STRINGS: { |
3098 | 0 | joinString(f.first, f.str, f.val, f.elementId, scratch); |
3099 | 0 | f.elementId++; |
3100 | 0 | auto *ast = joinStrings(); |
3101 | 0 | if (ast != nullptr) { |
3102 | 0 | ast_ = ast; |
3103 | 0 | goto recurse; |
3104 | 0 | } |
3105 | 0 | } break; |
3106 | | |
3107 | 0 | case FRAME_BUILTIN_JOIN_ARRAYS: { |
3108 | 0 | joinArray(f.first, f.thunks, f.val, f.elementId, scratch); |
3109 | 0 | f.elementId++; |
3110 | 0 | auto *ast = joinArrays(); |
3111 | 0 | if (ast != nullptr) { |
3112 | 0 | ast_ = ast; |
3113 | 0 | goto recurse; |
3114 | 0 | } |
3115 | 0 | } break; |
3116 | | |
3117 | 0 | case FRAME_BUILTIN_DECODE_UTF8: { |
3118 | 0 | auto *ast = decodeUTF8(); |
3119 | 0 | if (ast != nullptr) { |
3120 | 0 | ast_ = ast; |
3121 | 0 | goto recurse; |
3122 | 0 | } |
3123 | 0 | } break; |
3124 | | |
3125 | 0 | default: |
3126 | 0 | std::cerr << "INTERNAL ERROR: Unknown FrameKind: " << f.kind << std::endl; |
3127 | 0 | std::abort(); |
3128 | 3.38M | } |
3129 | | |
3130 | 1.46M | popframe:; |
3131 | | |
3132 | 1.46M | stack.pop(); |
3133 | | |
3134 | 1.75M | replaceframe:; |
3135 | 1.75M | } |
3136 | 4.27M | } |
3137 | | |
3138 | | /** Manifest the scratch value by evaluating any remaining fields, and then convert to JSON. |
3139 | | * |
3140 | | * This can trigger a garbage collection cycle. Be sure to stash any objects that aren't |
3141 | | * reachable via the stack or heap. |
3142 | | * |
3143 | | * \param multiline If true, will print objects and arrays in an indented fashion. |
3144 | | */ |
3145 | | UString manifestJson(const LocationRange &loc, bool multiline, const UString &indent) |
3146 | 2.65M | { |
3147 | | // Printing fields means evaluating and binding them, which can trigger |
3148 | | // garbage collection. |
3149 | | |
3150 | 2.65M | UStringStream ss; |
3151 | 2.65M | switch (scratch.t) { |
3152 | 296 | case Value::ARRAY: { |
3153 | 296 | HeapArray *arr = static_cast<HeapArray *>(scratch.v.h); |
3154 | 296 | if (arr->elements.size() == 0) { |
3155 | 0 | ss << U"[ ]"; |
3156 | 296 | } else { |
3157 | 296 | const char32_t *prefix = multiline ? U"[\n" : U"["; |
3158 | 296 | UString indent2 = multiline ? indent + U" " : indent; |
3159 | 2.64M | for (auto *thunk : arr->elements) { |
3160 | 2.64M | LocationRange tloc = thunk->body == nullptr ? loc : thunk->body->location; |
3161 | 2.64M | if (thunk->filled) { |
3162 | 0 | stack.newCall(loc, thunk, nullptr, 0, BindingFrame{}); |
3163 | | // Keep arr alive when scratch is overwritten |
3164 | 0 | stack.top().val = scratch; |
3165 | 0 | scratch = thunk->content; |
3166 | 2.64M | } else { |
3167 | 2.64M | stack.newCall(loc, thunk, thunk->self, thunk->offset, thunk->upValues); |
3168 | | // Keep arr alive when scratch is overwritten |
3169 | 2.64M | stack.top().val = scratch; |
3170 | 2.64M | evaluate(thunk->body, stack.size()); |
3171 | 2.64M | } |
3172 | 2.64M | auto element = manifestJson(tloc, multiline, indent2); |
3173 | | // Restore scratch |
3174 | 2.64M | scratch = stack.top().val; |
3175 | 2.64M | stack.pop(); |
3176 | 2.64M | ss << prefix << indent2 << element; |
3177 | 2.64M | prefix = multiline ? U",\n" : U", "; |
3178 | 2.64M | } |
3179 | 296 | ss << (multiline ? U"\n" : U"") << indent << U"]"; |
3180 | 296 | } |
3181 | 296 | } break; |
3182 | | |
3183 | 112 | case Value::BOOLEAN: ss << (scratch.v.b ? U"true" : U"false"); break; |
3184 | | |
3185 | 2.64M | case Value::NUMBER: ss << decode_utf8(jsonnet_unparse_number(scratch.v.d)); break; |
3186 | | |
3187 | 3 | case Value::FUNCTION: |
3188 | 3 | throw makeError(loc, "couldn't manifest function in JSON output."); |
3189 | | |
3190 | 3.13k | case Value::NULL_TYPE: ss << U"null"; break; |
3191 | | |
3192 | 5.51k | case Value::OBJECT: { |
3193 | 5.51k | auto *obj = static_cast<HeapObject *>(scratch.v.h); |
3194 | 5.51k | runInvariants(loc, obj); |
3195 | | // Using std::map has the useful side-effect of ordering the fields |
3196 | | // alphabetically. |
3197 | 5.51k | std::map<UString, const Identifier *> fields; |
3198 | 5.57k | for (const auto &f : objectFields(obj, true)) { |
3199 | 5.57k | fields[f->name] = f; |
3200 | 5.57k | } |
3201 | 5.51k | if (fields.size() == 0) { |
3202 | 210 | ss << U"{ }"; |
3203 | 5.30k | } else { |
3204 | 5.30k | UString indent2 = multiline ? indent + U" " : indent; |
3205 | 5.30k | const char32_t *prefix = multiline ? U"{\n" : U"{"; |
3206 | 5.54k | for (const auto &f : fields) { |
3207 | | // pushes FRAME_CALL |
3208 | 5.54k | const AST *body = objectIndex(loc, obj, f.second, 0); |
3209 | 5.54k | stack.top().val = scratch; |
3210 | 5.54k | evaluate(body, stack.size()); |
3211 | 5.54k | auto vstr = manifestJson(body->location, multiline, indent2); |
3212 | | // Reset scratch so that the object we're manifesting doesn't |
3213 | | // get GC'd. |
3214 | 5.54k | scratch = stack.top().val; |
3215 | 5.54k | stack.pop(); |
3216 | 5.54k | ss << prefix << indent2 << jsonnet_string_unparse(f.first, false) << U": " |
3217 | 5.54k | << vstr; |
3218 | 5.54k | prefix = multiline ? U",\n" : U", "; |
3219 | 5.54k | } |
3220 | 5.30k | ss << (multiline ? U"\n" : U"") << indent << U"}"; |
3221 | 5.30k | } |
3222 | 5.51k | } break; |
3223 | | |
3224 | 291 | case Value::STRING: { |
3225 | 291 | const UString &str = static_cast<HeapString *>(scratch.v.h)->value; |
3226 | 291 | ss << jsonnet_string_unparse(str, false); |
3227 | 291 | } break; |
3228 | 2.65M | } |
3229 | 2.65M | return ss.str(); |
3230 | 2.65M | } |
3231 | | |
3232 | | UString manifestString(const LocationRange &loc) |
3233 | 0 | { |
3234 | 0 | if (scratch.t != Value::STRING) { |
3235 | 0 | std::stringstream ss; |
3236 | 0 | ss << "expected string result, got: " << type_str(scratch.t); |
3237 | 0 | throw makeError(loc, ss.str()); |
3238 | 0 | } |
3239 | 0 | return static_cast<HeapString *>(scratch.v.h)->value; |
3240 | 0 | } |
3241 | | |
3242 | | StrMap manifestMulti(bool string) |
3243 | 0 | { |
3244 | 0 | StrMap r; |
3245 | 0 | LocationRange loc("During manifestation"); |
3246 | 0 | if (scratch.t != Value::OBJECT) { |
3247 | 0 | std::stringstream ss; |
3248 | 0 | ss << "multi mode: top-level object was a " << type_str(scratch.t) << ", " |
3249 | 0 | << "should be an object whose keys are filenames and values hold " |
3250 | 0 | << "the JSON for that file."; |
3251 | 0 | throw makeError(loc, ss.str()); |
3252 | 0 | } |
3253 | 0 | auto *obj = static_cast<HeapObject *>(scratch.v.h); |
3254 | 0 | runInvariants(loc, obj); |
3255 | 0 | std::map<UString, const Identifier *> fields; |
3256 | 0 | for (const auto &f : objectFields(obj, true)) { |
3257 | 0 | fields[f->name] = f; |
3258 | 0 | } |
3259 | 0 | for (const auto &f : fields) { |
3260 | | // pushes FRAME_CALL |
3261 | 0 | const AST *body = objectIndex(loc, obj, f.second, 0); |
3262 | 0 | stack.top().val = scratch; |
3263 | 0 | evaluate(body, stack.size()); |
3264 | 0 | auto vstr = |
3265 | 0 | string ? manifestString(body->location) : manifestJson(body->location, true, U""); |
3266 | | // Reset scratch so that the object we're manifesting doesn't |
3267 | | // get GC'd. |
3268 | 0 | scratch = stack.top().val; |
3269 | 0 | stack.pop(); |
3270 | 0 | r[encode_utf8(f.first)] = encode_utf8(vstr); |
3271 | 0 | } |
3272 | 0 | return r; |
3273 | 0 | } |
3274 | | |
3275 | | std::vector<std::string> manifestStream(bool string) |
3276 | 0 | { |
3277 | 0 | std::vector<std::string> r; |
3278 | 0 | LocationRange loc("During manifestation"); |
3279 | 0 | if (scratch.t != Value::ARRAY) { |
3280 | 0 | std::stringstream ss; |
3281 | 0 | ss << "stream mode: top-level object was a " << type_str(scratch.t) << ", " |
3282 | 0 | << "should be an array whose elements hold " |
3283 | 0 | << "the JSON for each document in the stream."; |
3284 | 0 | throw makeError(loc, ss.str()); |
3285 | 0 | } |
3286 | 0 | auto *arr = static_cast<HeapArray *>(scratch.v.h); |
3287 | 0 | for (auto *thunk : arr->elements) { |
3288 | 0 | LocationRange tloc = thunk->body == nullptr ? loc : thunk->body->location; |
3289 | 0 | if (thunk->filled) { |
3290 | 0 | stack.newCall(loc, thunk, nullptr, 0, BindingFrame{}); |
3291 | | // Keep arr alive when scratch is overwritten |
3292 | 0 | stack.top().val = scratch; |
3293 | 0 | scratch = thunk->content; |
3294 | 0 | } else { |
3295 | 0 | stack.newCall(loc, thunk, thunk->self, thunk->offset, thunk->upValues); |
3296 | | // Keep arr alive when scratch is overwritten |
3297 | 0 | stack.top().val = scratch; |
3298 | 0 | evaluate(thunk->body, stack.size()); |
3299 | 0 | } |
3300 | 0 | UString element = string ? manifestString(tloc) : manifestJson(tloc, true, U""); |
3301 | 0 | scratch = stack.top().val; |
3302 | 0 | stack.pop(); |
3303 | 0 | r.push_back(encode_utf8(element)); |
3304 | 0 | } |
3305 | 0 | return r; |
3306 | 0 | } |
3307 | | }; |
3308 | | |
3309 | | } // namespace |
3310 | | |
3311 | | std::string jsonnet_vm_execute(Allocator *alloc, const AST *ast, const ExtMap &ext_vars, |
3312 | | unsigned max_stack, double gc_min_objects, double gc_growth_trigger, |
3313 | | const VmNativeCallbackMap &natives, |
3314 | | JsonnetImportCallback *import_callback, void *ctx, |
3315 | | bool string_output) |
3316 | 214 | { |
3317 | 214 | Interpreter vm(alloc, |
3318 | 214 | ext_vars, |
3319 | 214 | max_stack, |
3320 | 214 | gc_min_objects, |
3321 | 214 | gc_growth_trigger, |
3322 | 214 | natives, |
3323 | 214 | import_callback, |
3324 | 214 | ctx); |
3325 | 214 | vm.evaluate(ast, 0); |
3326 | 214 | if (string_output) { |
3327 | 0 | return encode_utf8(vm.manifestString(LocationRange("During manifestation"))); |
3328 | 214 | } else { |
3329 | 214 | return encode_utf8(vm.manifestJson(LocationRange("During manifestation"), true, U"")); |
3330 | 214 | } |
3331 | 214 | } |
3332 | | |
3333 | | StrMap jsonnet_vm_execute_multi(Allocator *alloc, const AST *ast, const ExtMap &ext_vars, |
3334 | | unsigned max_stack, double gc_min_objects, double gc_growth_trigger, |
3335 | | const VmNativeCallbackMap &natives, |
3336 | | JsonnetImportCallback *import_callback, void *ctx, |
3337 | | bool string_output) |
3338 | 0 | { |
3339 | 0 | Interpreter vm(alloc, |
3340 | 0 | ext_vars, |
3341 | 0 | max_stack, |
3342 | 0 | gc_min_objects, |
3343 | 0 | gc_growth_trigger, |
3344 | 0 | natives, |
3345 | 0 | import_callback, |
3346 | 0 | ctx); |
3347 | 0 | vm.evaluate(ast, 0); |
3348 | 0 | return vm.manifestMulti(string_output); |
3349 | 0 | } |
3350 | | |
3351 | | std::vector<std::string> jsonnet_vm_execute_stream(Allocator *alloc, const AST *ast, |
3352 | | const ExtMap &ext_vars, unsigned max_stack, |
3353 | | double gc_min_objects, double gc_growth_trigger, |
3354 | | const VmNativeCallbackMap &natives, |
3355 | | JsonnetImportCallback *import_callback, |
3356 | | void *ctx, bool string_output) |
3357 | 0 | { |
3358 | 0 | Interpreter vm(alloc, |
3359 | 0 | ext_vars, |
3360 | 0 | max_stack, |
3361 | 0 | gc_min_objects, |
3362 | 0 | gc_growth_trigger, |
3363 | 0 | natives, |
3364 | 0 | import_callback, |
3365 | 0 | ctx); |
3366 | 0 | vm.evaluate(ast, 0); |
3367 | 0 | return vm.manifestStream(string_output); |
3368 | 0 | } |