1 module wren.core;
2 import wren.math;
3 import wren.primitive;
4 import wren.value;
5 import wren.vm;
6 
7 // Throwing exception support
8 import dplug.core : mallocNew;
9 
10 // The core module source that is interpreted whenever core is initialized.
11 private static const(char)[] coreModuleSource = import("wren_core.wren");
12 
13 /++ Boolean primitives +/
14 @WrenPrimitive("Bool", "!") 
15 bool bool_not(WrenVM* vm, Value* args) @nogc
16 {
17     return RETURN_BOOL(args, !AS_BOOL(args[0]));
18 }
19 
20 @WrenPrimitive("Bool", "toString")
21 bool bool_toString(WrenVM* vm, Value* args) @nogc
22 {
23     if (AS_BOOL(args[0]))
24     {
25         return RETURN_VAL(args, CONST_STRING(vm, "true"));
26     }
27     else
28     {
29         return RETURN_VAL(args, CONST_STRING(vm, "false"));
30     }
31 }
32 
33 /++ Class primitives +/
34 @WrenPrimitive("Class", "name")
35 bool class_name(WrenVM* vm, Value* args) @nogc
36 {
37     return RETURN_OBJ(args, AS_CLASS(args[0]).name);
38 }
39 
40 @WrenPrimitive("Class", "supertype")
41 bool class_supertype(WrenVM* vm, Value* args) @nogc
42 {
43     ObjClass* classObj = AS_CLASS(args[0]);
44     
45     // Object has no superclass.
46     if (classObj.superclass == null) return RETURN_NULL(args);
47 
48     return RETURN_OBJ(args, classObj.superclass);        
49 }
50 
51 @WrenPrimitive("Class", "toString")
52 bool class_toString(WrenVM* vm, Value* args) @nogc
53 {
54     return RETURN_OBJ(args, AS_CLASS(args[0]).name);
55 }
56 
57 @WrenPrimitive("Class", "attributes")
58 bool class_attributes(WrenVM* vm, Value* args) @nogc
59 {
60     return RETURN_VAL(args, AS_CLASS(args[0]).attributes);
61 }
62 
63 /++ Fiber primitives +/
64 @WrenPrimitive("Fiber", "new(_)", MethodType.METHOD_PRIMITIVE, true)
65 bool fiber_new(WrenVM* vm, Value* args) @nogc
66 {
67     if (!validateFn(vm, args[1], "Argument")) return false;
68 
69     ObjClosure* closure = AS_CLOSURE(args[1]);
70     if (closure.fn.arity > 1)
71     {
72         return RETURN_ERROR(vm, "Function cannot take more than one parameter.");
73     }
74 
75     return RETURN_OBJ(args, wrenNewFiber(vm, closure));
76 }
77 
78 @WrenPrimitive("Fiber", "abort(_)", MethodType.METHOD_PRIMITIVE, true)
79 bool fiber_abort(WrenVM* vm, Value* args) @nogc
80 {
81     vm.fiber.error = args[1];
82 
83     // If the error is explicitly null, it's not really an abort.
84     return IS_NULL(args[1]);
85 }
86 
87 @WrenPrimitive("Fiber", "current", MethodType.METHOD_PRIMITIVE, true)
88 bool fiber_current(WrenVM* vm, Value* args) @nogc
89 {
90     return RETURN_OBJ(args, vm.fiber);
91 }
92 
93 @WrenPrimitive("Fiber", "suspend()", MethodType.METHOD_PRIMITIVE, true)
94 bool fiber_suspend(WrenVM* vm, Value* args) @nogc
95 {
96     return RETURN_VAL(args, AS_FIBER(args[0]).error);
97 }
98 
99 @WrenPrimitive("Fiber", "yield()", MethodType.METHOD_PRIMITIVE, true)
100 bool fiber_yield(WrenVM* vm, Value* args) @nogc
101 {
102     ObjFiber* current = vm.fiber;
103     vm.fiber = current.caller;
104 
105     // Unhook this fiber from the one that called it.
106     current.caller = null;
107     current.state = FiberState.FIBER_OTHER;
108 
109     if (vm.fiber != null)
110     {
111         // Make the caller's run method return null.
112         vm.fiber.stackTop[-1] = NULL_VAL;
113     }
114 
115     return false;
116 }
117 
118 @WrenPrimitive("Fiber", "yield(_)", MethodType.METHOD_PRIMITIVE, true)
119 bool fiber_yield1(WrenVM* vm, Value* args) @nogc
120 {
121     ObjFiber* current = vm.fiber;
122     vm.fiber = current.caller;
123 
124     // Unhook this fiber from the one that called it.
125     current.caller = null;
126     current.state = FiberState.FIBER_OTHER;
127 
128     if (vm.fiber != null)
129     {
130         // Make the caller's run method return the argument passed to yield.
131         vm.fiber.stackTop[-1] = args[1];
132 
133         // When the yielding fiber resumes, we'll store the result of the yield
134         // call in its stack. Since Fiber.yield(value) has two arguments (the Fiber
135         // class and the value) and we only need one slot for the result, discard
136         // the other slot now.
137         current.stackTop--;
138     }
139 
140     return false;
141 }
142 
143 // Transfer execution to [fiber] coming from the current fiber whose stack has
144 // [args].
145 //
146 // [isCall] is true if [fiber] is being called and not transferred.
147 //
148 // [hasValue] is true if a value in [args] is being passed to the new fiber.
149 // Otherwise, `null` is implicitly being passed.
150 static bool runFiber(WrenVM* vm, ObjFiber* fiber, Value* args, bool isCall,
151                      bool hasValue, const(char)* verb) @nogc
152 {
153     if (wrenHasError(fiber))
154     {
155        return RETURN_ERROR(vm, "Cannot $ an aborted fiber.", verb);
156     }
157 
158     if (isCall)
159     {
160         // You can't call a called fiber, but you can transfer directly to it,
161         // which is why this check is gated on `isCall`. This way, after resuming a
162         // suspended fiber, it will run and then return to the fiber that called it
163         // and so on.
164         if (fiber.caller != null) return RETURN_ERROR(vm, "Fiber has already been called.");
165 
166         if (fiber.state == FiberState.FIBER_ROOT) return RETURN_ERROR(vm, "Cannot call root fiber.");
167         
168         // Remember who ran it.
169         fiber.caller = vm.fiber;
170     }
171 
172     if (fiber.numFrames == 0)
173     {
174         return RETURN_ERROR(vm, "Cannot $ a finished fiber.", verb);
175     }
176 
177     // When the calling fiber resumes, we'll store the result of the call in its
178     // stack. If the call has two arguments (the fiber and the value), we only
179     // need one slot for the result, so discard the other slot now.
180     if (hasValue) vm.fiber.stackTop--;
181 
182     if (fiber.numFrames == 1 &&
183         fiber.frames[0].ip == fiber.frames[0].closure.fn.code.data)
184     {
185         // The fiber is being started for the first time. If its function takes a
186         // parameter, bind an argument to it.
187         if (fiber.frames[0].closure.fn.arity == 1)
188         {
189             fiber.stackTop[0] = hasValue ? args[1] : NULL_VAL;
190             fiber.stackTop++;
191         }
192     }
193     else
194     {
195         // The fiber is being resumed, make yield() or transfer() return the result.
196         fiber.stackTop[-1] = hasValue ? args[1] : NULL_VAL;
197     }
198 
199     vm.fiber = fiber;
200     return false;
201 }
202 
203 @WrenPrimitive("Fiber", "call()")
204 bool fiber_call(WrenVM* vm, Value* args) @nogc
205 {
206     return runFiber(vm, AS_FIBER(args[0]), args, true, false, "call");
207 }
208 
209 @WrenPrimitive("Fiber", "call(_)")
210 bool fiber_call1(WrenVM* vm, Value* args) @nogc
211 {
212     return runFiber(vm, AS_FIBER(args[0]), args, true, true, "call");
213 }
214 
215 @WrenPrimitive("Fiber", "error")
216 bool fiber_error(WrenVM* vm, Value* args) @nogc
217 {
218     return RETURN_VAL(args, AS_FIBER(args[0]).error);
219 }
220 
221 @WrenPrimitive("Fiber", "isDone")
222 bool fiber_isDone(WrenVM* vm, Value* args) @nogc
223 {
224     ObjFiber* runFiber = AS_FIBER(args[0]);
225     return RETURN_BOOL(args, runFiber.numFrames == 0 || wrenHasError(runFiber));
226 }
227 
228 @WrenPrimitive("Fiber", "transfer()")
229 bool fiber_transfer(WrenVM* vm, Value* args) @nogc
230 {
231     return runFiber(vm, AS_FIBER(args[0]), args, false, false, "transfer to");
232 }
233 
234 @WrenPrimitive("Fiber", "transfer(_)")
235 bool fiber_transfer1(WrenVM* vm, Value* args) @nogc
236 {
237     return runFiber(vm, AS_FIBER(args[0]), args, false, true, "transfer to");
238 }
239 
240 @WrenPrimitive("Fiber", "transferError(_)")
241 bool fiber_transferError(WrenVM* vm, Value* args) @nogc
242 {
243     runFiber(vm, AS_FIBER(args[0]), args, false, true, "transfer to");
244     vm.fiber.error = args[1];
245     return false;
246 }
247 
248 @WrenPrimitive("Fiber", "try()")
249 bool fiber_try(WrenVM* vm, Value* args) @nogc
250 {
251     runFiber(vm, AS_FIBER(args[0]), args, true, false, "try");
252 
253     // If we're switching to a valid fiber to try, remember that we're trying it.
254     if (!wrenHasError(vm.fiber)) vm.fiber.state = FiberState.FIBER_TRY;
255     return false;
256 }
257 
258 @WrenPrimitive("Fiber", "try(_)")
259 bool fiber_try1(WrenVM* vm, Value* args) @nogc
260 {
261     runFiber(vm, AS_FIBER(args[0]), args, true, true, "try");
262 
263     // If we're switching to a valid fiber to try, remember that we're trying it.
264     if (!wrenHasError(vm.fiber)) vm.fiber.state = FiberState.FIBER_TRY;
265     return false;
266 }
267 
268 /++ Fn primitives +/
269 
270 @WrenPrimitive("Fn", "new(_)", MethodType.METHOD_PRIMITIVE, true)
271 bool fn_new(WrenVM* vm, Value* args) @nogc
272 {
273     if (!validateFn(vm, args[1], "Argument")) return false;
274 
275     // The block argument is already a function, so just return it.
276     return RETURN_VAL(args, args[1]);
277 }
278 
279 @WrenPrimitive("Fn", "arity")
280 bool fn_arity(WrenVM* vm, Value* args) @nogc
281 {
282     return RETURN_NUM(args, AS_CLOSURE(args[0]).fn.arity);
283 }
284 
285 static void call_fn(WrenVM* vm, Value* args, int numArgs) @nogc
286 {
287     // +1 to include the function itself.
288     wrenCallFunction(vm, vm.fiber, AS_CLOSURE(args[0]), numArgs + 1);
289 }
290 
291 @WrenPrimitive("Fn", "call()", MethodType.METHOD_FUNCTION_CALL)
292 bool fn_call0(WrenVM* vm, Value* args) @nogc
293 {
294     call_fn(vm, args, 0);
295     return false;
296 }
297 
298 // This mixin is a mess, but we need to generate the primitives
299 // to allow a user to call a function with up to 16 arguments.
300 // This is the cleanest that I could make it, but it's still definitely a mess.
301 mixin(() {
302     import std.format : format;
303     import std.range : repeat, join;
304 
305     string ret = ""; 
306 
307     // Build up our argument array here
308     foreach(i; 1 .. 17) {
309         string args = "_" ~ ",_".repeat(i - 1).join;
310         // God have mercy on my soul for this format string
311         ret ~= format!q{
312             @WrenPrimitive("Fn", "call(%1$s)", MethodType.METHOD_FUNCTION_CALL)
313             bool fn_call%2$d(WrenVM* vm, Value* args) @nogc
314             {
315                 call_fn(vm, args, %2$d);
316                 return false;
317             }
318         }(args, i);
319     }
320 
321     return ret;
322 }());
323 
324 @WrenPrimitive("Fn", "toString")
325 bool fn_toString(WrenVM* vm, Value* args) @nogc
326 {
327     return RETURN_VAL(args, CONST_STRING(vm, "<fn>"));
328 }
329 
330 /++ List primitives +/
331 @WrenPrimitive("List", "filled(_,_)", MethodType.METHOD_PRIMITIVE, true)
332 bool list_filled(WrenVM* vm, Value* args) @nogc
333 {
334     if (!validateInt(vm, args[1], "Size")) return false;
335     if (AS_NUM(args[1]) < 0) return RETURN_ERROR(vm, "Size cannot be negative.");
336 
337     uint size = cast(uint)AS_NUM(args[1]);
338     ObjList* list = wrenNewList(vm, size);
339 
340     for (uint i = 0; i < size; i++)
341     {
342         list.elements.data[i] = args[2];
343     }
344 
345     return RETURN_OBJ(args, list);
346 }
347 
348 @WrenPrimitive("List", "new()", MethodType.METHOD_PRIMITIVE, true)
349 bool list_new(WrenVM* vm, Value* args) @nogc
350 {
351     return RETURN_OBJ(args, wrenNewList(vm, 0));
352 }
353 
354 @WrenPrimitive("List", "[_]")
355 bool list_subscript(WrenVM* vm, Value* args) @nogc
356 {
357     ObjList* list = AS_LIST(args[0]);
358 
359     if (IS_NUM(args[1]))
360     {
361         uint index = validateIndex(vm, args[1], list.elements.count,
362                                     "Subscript");
363         if (index == uint.max) return false;
364 
365         return RETURN_VAL(args, list.elements.data[index]);
366     }
367 
368     if (!IS_RANGE(args[1]))
369     {
370         return RETURN_ERROR(vm, "Subscript must be a number or a range.");
371     }
372 
373     int step;
374     uint count = list.elements.count;
375     uint start = calculateRange(vm, AS_RANGE(args[1]), &count, &step);
376     if (start == uint.max) return false;
377 
378     ObjList* result = wrenNewList(vm, count);
379     for (uint i = 0; i < count; i++)
380     {
381         result.elements.data[i] = list.elements.data[start + i * step];
382     }
383 
384     return RETURN_OBJ(args, result);
385 }
386 
387 @WrenPrimitive("List", "[_]=(_)")
388 bool list_subscriptSetter(WrenVM* vm, Value* args) @nogc
389 {
390     ObjList* list = AS_LIST(args[0]);
391     uint index = validateIndex(vm, args[1], list.elements.count,
392                                     "Subscript");
393     if (index == uint.max) return false;
394 
395     list.elements.data[index] = args[2];
396     return RETURN_VAL(args, args[2]);
397 }
398 
399 @WrenPrimitive("List", "add(_)")
400 bool list_add(WrenVM* vm, Value* args) @nogc
401 {
402     wrenValueBufferWrite(vm, &AS_LIST(args[0]).elements, args[1]);
403     return RETURN_VAL(args, args[1]);
404 }
405 
406 // Adds an element to the list and then returns the list itself. This is called
407 // by the compiler when compiling list literals instead of using add() to
408 // minimize stack churn.
409 @WrenPrimitive("List", "addCore_(_)")
410 bool list_addCore(WrenVM* vm, Value* args) @nogc
411 {
412     wrenValueBufferWrite(vm, &AS_LIST(args[0]).elements, args[1]);
413 
414     // Return the list.
415     return RETURN_VAL(args, args[0]);
416 }
417 
418 @WrenPrimitive("List", "clear()")
419 bool list_clear(WrenVM* vm, Value* args) @nogc
420 {
421     wrenValueBufferClear(vm, &AS_LIST(args[0]).elements);
422     return RETURN_NULL(args);
423 }
424 
425 @WrenPrimitive("List", "count")
426 bool list_count(WrenVM* vm, Value* args) @nogc
427 {
428     return RETURN_NUM(args, AS_LIST(args[0]).elements.count);
429 }
430 
431 @WrenPrimitive("List", "insert(_,_)")
432 bool list_insert(WrenVM* vm, Value* args) @nogc
433 {
434     ObjList* list = AS_LIST(args[0]);
435 
436     // count + 1 here so you can "insert" at the very end.
437     uint index = validateIndex(vm, args[1], list.elements.count + 1, "Index");
438     if (index == uint.max) return false;
439 
440     wrenListInsert(vm, list, args[2], index);
441     return RETURN_VAL(args, args[2]);
442 }
443 
444 @WrenPrimitive("List", "iterate(_)")
445 bool list_iterate(WrenVM* vm, Value* args) @nogc
446 {
447     ObjList* list = AS_LIST(args[0]);
448 
449     // If we're starting the iteration, return the first index.
450     if (IS_NULL(args[1]))
451     {
452         if (list.elements.count == 0) return RETURN_FALSE(args);
453         return RETURN_NUM(args, 0);
454     }
455 
456     if (!validateInt(vm, args[1], "Iterator")) return false;
457 
458     // Stop if we're out of bounds.
459     double index = AS_NUM(args[1]);
460     if (index < 0 || index >= list.elements.count - 1) return RETURN_FALSE(args);
461 
462     // Otherwise, move to the next index.
463     return RETURN_NUM(args, index + 1);
464 }
465 
466 @WrenPrimitive("List", "iteratorValue(_)")
467 bool list_iteratorValue(WrenVM* vm, Value* args) @nogc
468 {
469     ObjList* list = AS_LIST(args[0]);
470     uint index = validateIndex(vm, args[1], list.elements.count, "Iterator");
471     if (index == uint.max) return false;
472 
473     return RETURN_VAL(args, list.elements.data[index]);
474 }
475 
476 @WrenPrimitive("List", "removeAt(_)")
477 bool list_removeAt(WrenVM* vm, Value* args) @nogc
478 {
479     ObjList* list = AS_LIST(args[0]);
480     uint index = validateIndex(vm, args[1], list.elements.count, "Index");
481     if (index == uint.max) return false;
482 
483     return RETURN_VAL(args, wrenListRemoveAt(vm, list, index));
484 }
485 
486 @WrenPrimitive("List", "remove(_)")
487 bool list_removeValue(WrenVM* vm, Value* args) @nogc
488 {
489     ObjList* list = AS_LIST(args[0]);
490     int index = wrenListIndexOf(vm, list, args[1]);
491     if (index == -1) return RETURN_NULL(args);
492     return RETURN_VAL(args, wrenListRemoveAt(vm, list, index));
493 }
494 
495 @WrenPrimitive("List", "indexOf(_)")
496 bool list_indexOf(WrenVM* vm, Value* args) @nogc
497 {
498     ObjList* list = AS_LIST(args[0]);
499     return RETURN_NUM(args, wrenListIndexOf(vm, list, args[1]));
500 }
501 
502 @WrenPrimitive("List", "swap(_,_)")
503 bool list_swap(WrenVM* vm, Value* args) @nogc
504 {
505     ObjList* list = AS_LIST(args[0]);
506     uint indexA = validateIndex(vm, args[1], list.elements.count, "Index 0");
507     if (indexA == uint.max) return false;
508     uint indexB = validateIndex(vm, args[2], list.elements.count, "Index 1");
509     if (indexB == uint.max) return false;
510 
511     Value a = list.elements.data[indexA];
512     list.elements.data[indexA] = list.elements.data[indexB];
513     list.elements.data[indexB] = a;
514 
515     return RETURN_NULL(args);
516 }
517 /++ Null primitives +/
518 @WrenPrimitive("Null", "!")
519 bool null_not(WrenVM* vm, Value* args) @nogc
520 {
521     return RETURN_VAL(args, TRUE_VAL);
522 }
523 
524 @WrenPrimitive("Null", "toString")
525 bool null_toString(WrenVM* vm, Value* args) @nogc
526 {
527     return RETURN_VAL(args, CONST_STRING(vm, "null"));
528 }
529 
530 /++ Num primitives +/
531 
532 // Porting over macros is always a joy.
533 private string DEF_NUM_CONSTANT(string name, string val)
534 {
535     import std.format : format;
536 
537     return format!q{
538         @WrenPrimitive("Num", "%1$s", MethodType.METHOD_PRIMITIVE, true)
539         bool num_%1$s(WrenVM* vm, Value* args) @nogc
540         {
541             return RETURN_NUM(args, %2$s);
542         }
543     }(name, val);
544 }
545 
546 mixin(DEF_NUM_CONSTANT("infinity", "double.infinity"));
547 mixin(DEF_NUM_CONSTANT("nan", "WREN_DOUBLE_NAN"));
548 mixin(DEF_NUM_CONSTANT("pi", "3.14159265358979323846264338327950288"));
549 mixin(DEF_NUM_CONSTANT("tau", "6.28318530717958647692528676655900577"));
550 
551 mixin(DEF_NUM_CONSTANT("largest", "double.max"));
552 mixin(DEF_NUM_CONSTANT("smallest", "double.min_normal"));
553 
554 mixin(DEF_NUM_CONSTANT("maxSafeInteger", "9007199254740991.0"));
555 mixin(DEF_NUM_CONSTANT("minSafeInteger", "-9007199254740991.0"));
556 
557 private string DEF_NUM_INFIX(string name, string op, string type)
558 {
559     import std.format : format;
560 
561     return format!q{
562         @WrenPrimitive("Num", "%2$s(_)")
563         bool num_%1$s(WrenVM* vm, Value* args) @nogc
564         {
565             if (!validateNum(vm, args[1], "Right operand")) return false;
566             return RETURN_%3$s(args, AS_NUM(args[0]) %2$s AS_NUM(args[1]));
567         }
568     }(name, op, type);
569 }
570 
571 mixin(DEF_NUM_INFIX("minus",    "-",  "NUM"));
572 mixin(DEF_NUM_INFIX("plus",     "+",  "NUM"));
573 mixin(DEF_NUM_INFIX("multiply", "*",  "NUM"));
574 mixin(DEF_NUM_INFIX("divide",   "/",  "NUM"));
575 mixin(DEF_NUM_INFIX("lt",       "<",  "BOOL"));
576 mixin(DEF_NUM_INFIX("gt",       ">",  "BOOL"));
577 mixin(DEF_NUM_INFIX("lte",      "<=", "BOOL"));
578 mixin(DEF_NUM_INFIX("gte",      ">=", "BOOL"));
579 
580 private string DEF_NUM_BITWISE(string name, string op)
581 {
582     import std.format : format;
583 
584     return format!q{
585         @WrenPrimitive("Num", "%2$s(_)")
586         bool num_bitwise%1$s(WrenVM* vm, Value* args) @nogc
587         {
588             if (!validateNum(vm, args[1], "Right operand")) return false;
589             uint left = cast(uint)AS_NUM(args[0]);
590             uint right = cast(uint)AS_NUM(args[1]);
591             return RETURN_NUM(args, left %2$s right);
592         }
593     }(name, op);
594 }
595 
596 mixin(DEF_NUM_BITWISE("And",        "&"));
597 mixin(DEF_NUM_BITWISE("Or",         "|"));
598 mixin(DEF_NUM_BITWISE("Xor",        "^"));
599 mixin(DEF_NUM_BITWISE("LeftShift",  "<<"));
600 mixin(DEF_NUM_BITWISE("RightShift", ">>"));
601 
602 private string DEF_NUM_FN(string name, string fn)
603 {
604     import std.format : format;
605 
606     return format!q{
607         @WrenPrimitive("Num", "%1$s")
608         bool num_%1$s(WrenVM* vm, Value* args) @nogc
609         {
610             import core.stdc.math : %2$s;
611             return RETURN_NUM(args, %2$s(AS_NUM(args[0])));
612         }
613     }(name, fn);
614 }
615 
616 mixin(DEF_NUM_FN("abs", "fabs"));
617 mixin(DEF_NUM_FN("acos", "acos"));
618 mixin(DEF_NUM_FN("asin", "asin"));
619 mixin(DEF_NUM_FN("atan", "atan"));
620 mixin(DEF_NUM_FN("cbrt", "cbrt"));
621 mixin(DEF_NUM_FN("ceil", "ceil"));
622 mixin(DEF_NUM_FN("cos", "cos"));
623 mixin(DEF_NUM_FN("floor", "floor"));
624 mixin(DEF_NUM_FN("round", "round"));
625 mixin(DEF_NUM_FN("sin", "sin"));
626 mixin(DEF_NUM_FN("sqrt", "sqrt"));
627 mixin(DEF_NUM_FN("tan", "tan"));
628 mixin(DEF_NUM_FN("log", "log"));
629 mixin(DEF_NUM_FN("log2", "log2"));
630 mixin(DEF_NUM_FN("exp", "exp"));
631 
632 @WrenPrimitive("Num", "-")
633 bool num_negate(WrenVM* vm, Value* args) @nogc
634 {
635     return RETURN_NUM(args, -AS_NUM(args[0]));
636 }
637 
638 @WrenPrimitive("Num", "%(_)")
639 bool num_mod(WrenVM* vm, Value* args) @nogc
640 {
641     import core.stdc.math : fmod;
642     if (!validateNum(vm, args[1], "Right operand")) return false;
643     return RETURN_NUM(args, fmod(AS_NUM(args[0]), AS_NUM(args[1])));
644 }
645 
646 @WrenPrimitive("Num", "==(_)")
647 bool num_eqeq(WrenVM* vm, Value* args) @nogc
648 {
649     if (!IS_NUM(args[1])) return RETURN_FALSE(args);
650     return RETURN_BOOL(args, AS_NUM(args[0]) == AS_NUM(args[1]));
651 }
652 
653 @WrenPrimitive("Num", "!=(_)")
654 bool num_bangeq(WrenVM* vm, Value* args) @nogc
655 {
656     if (!IS_NUM(args[1])) return RETURN_TRUE(args);
657     return RETURN_BOOL(args, AS_NUM(args[0]) != AS_NUM(args[1]));
658 }
659 
660 @WrenPrimitive("Num", "~")
661 bool num_bitwiseNot(WrenVM* vm, Value* args) @nogc
662 {
663     // Bitwise operators always work on 32-bit unsigned ints.
664     return RETURN_NUM(args, ~cast(uint)(AS_NUM(args[0])));
665 }
666 
667 @WrenPrimitive("Num", "..(_)")
668 bool num_dotDot(WrenVM* vm, Value* args) @nogc
669 {
670     if (!validateNum(vm, args[1], "Right hand side of range")) return false;
671 
672     double from = AS_NUM(args[0]);
673     double to = AS_NUM(args[1]);
674     return RETURN_VAL(args, wrenNewRange(vm, from, to, true));
675 }
676 
677 @WrenPrimitive("Num", "...(_)")
678 bool num_dotDotDot(WrenVM* vm, Value* args) @nogc
679 {
680     if (!validateNum(vm, args[1], "Right hand side of range")) return false;
681 
682     double from = AS_NUM(args[0]);
683     double to = AS_NUM(args[1]);
684     return RETURN_VAL(args, wrenNewRange(vm, from, to, false));
685 }
686 
687 @WrenPrimitive("Num", "atan2(_)")
688 bool num_atan2(WrenVM* vm, Value* args) @nogc
689 {
690     import core.stdc.math : atan2;
691     if (!validateNum(vm, args[1], "x value")) return false;
692 
693     return RETURN_NUM(args, atan2(AS_NUM(args[0]), AS_NUM(args[1])));
694 }
695 
696 @WrenPrimitive("Num", "min(_)")
697 bool num_min(WrenVM* vm, Value* args) @nogc
698 {
699     if (!validateNum(vm, args[1], "Other value")) return false;
700 
701     double value = AS_NUM(args[0]);
702     double other = AS_NUM(args[1]);
703     return RETURN_NUM(args, value <= other ? value : other);
704 }
705 
706 @WrenPrimitive("Num", "max(_)")
707 bool num_max(WrenVM* vm, Value* args) @nogc
708 {
709     if (!validateNum(vm, args[1], "Other value")) return false;
710 
711     double value = AS_NUM(args[0]);
712     double other = AS_NUM(args[1]);
713     return RETURN_NUM(args, value > other ? value : other);
714 }
715 
716 @WrenPrimitive("Num", "clamp(_,_)")
717 bool num_clamp(WrenVM* vm, Value* args) @nogc
718 {
719     if (!validateNum(vm, args[1], "Min value")) return false;
720     if (!validateNum(vm, args[2], "Max value")) return false;
721 
722     double value = AS_NUM(args[0]);
723     double min = AS_NUM(args[1]);
724     double max = AS_NUM(args[2]);
725     double result = (value < min) ? min : ((value > max) ? max : value);
726     return RETURN_NUM(args, result);
727 }
728 
729 @WrenPrimitive("Num", "pow(_)")
730 bool num_pow(WrenVM* vm, Value* args) @nogc
731 {
732     import core.stdc.math : pow;
733     if (!validateNum(vm, args[1], "Power value")) return false;
734 
735     return RETURN_NUM(args, pow(AS_NUM(args[0]), AS_NUM(args[1])));
736 }
737 
738 @WrenPrimitive("Num", "fraction")
739 bool num_fraction(WrenVM* vm, Value* args) @nogc
740 {
741     import core.stdc.math : modf;
742 
743     double unused;
744     return RETURN_NUM(args, modf(AS_NUM(args[0]), &unused));
745 }
746 
747 @WrenPrimitive("Num", "isInfinity")
748 bool num_isInfinity(WrenVM* vm, Value* args) @nogc
749 {
750     import core.stdc.math : isinf;
751     return RETURN_BOOL(args, isinf(AS_NUM(args[0])) == 1);
752 }
753 
754 @WrenPrimitive("Num", "isNan")
755 bool num_isNan(WrenVM* vm, Value* args) @nogc
756 {
757     import core.stdc.math : isnan;
758     return RETURN_BOOL(args, isnan(AS_NUM(args[0])) == 1);
759 }
760 
761 @WrenPrimitive("Num", "sign")
762 bool num_sign(WrenVM* vm, Value* args) @nogc
763 {
764     double value = AS_NUM(args[0]);
765     if (value > 0) 
766     {
767         return RETURN_NUM(args, 1);
768     }
769     else if (value < 0)
770     {
771         return RETURN_NUM(args, -1);
772     }
773     else
774     {
775         return RETURN_NUM(args, 0);
776     }
777 }
778 
779 @WrenPrimitive("Num", "toString")
780 bool num_toString(WrenVM* vm, Value* args) @nogc
781 {
782     return RETURN_VAL(args, wrenNumToString(vm, AS_NUM(args[0])));   
783 }
784 
785 @WrenPrimitive("Num", "truncate")
786 bool num_truncate(WrenVM* vm, Value* args) @nogc
787 {
788     import core.stdc.math : modf;
789     double integer;
790     modf(AS_NUM(args[0]), &integer);
791     return RETURN_NUM(args, integer);
792 }
793 
794 /++ Object primitives +/
795 @WrenPrimitive("Object metaclass", "same(_,_)")
796 bool object_same(WrenVM* vm, Value* args) @nogc
797 {
798     return RETURN_BOOL(args, wrenValuesEqual(args[1], args[2]));
799 }
800 
801 @WrenPrimitive("Object", "!")
802 bool object_not(WrenVM* vm, Value* args) @nogc
803 {
804     return RETURN_VAL(args, FALSE_VAL);
805 }
806 
807 @WrenPrimitive("Object", "==(_)")
808 bool object_eqeq(WrenVM* vm, Value* args) @nogc
809 {
810     return RETURN_BOOL(args, wrenValuesEqual(args[0], args[1]));
811 }
812 
813 @WrenPrimitive("Object", "!=(_)")
814 bool object_bangeq(WrenVM* vm, Value* args) @nogc
815 {
816     return RETURN_BOOL(args, !wrenValuesEqual(args[0], args[1]));
817 }
818 
819 @WrenPrimitive("Object", "is(_)")
820 bool object_is(WrenVM* vm, Value* args) @nogc
821 {
822     if (!IS_CLASS(args[1]))
823     {
824         return RETURN_ERROR(vm, "Right operand must be a class.");
825     }
826 
827     ObjClass *classObj = wrenGetClass(vm, args[0]);
828     ObjClass *baseClassObj = AS_CLASS(args[1]);
829 
830     // Walk the superclass chain looking for the class.
831     do
832     {
833         if (baseClassObj == classObj) {
834             return RETURN_BOOL(args, true);
835         }
836 
837         classObj = classObj.superclass;
838     }
839     while (classObj != null);
840 
841     return RETURN_BOOL(args, false);
842 }
843 
844 @WrenPrimitive("Object", "toString")
845 bool object_toString(WrenVM* vm, Value* args) @nogc
846 {
847     if (!IS_OBJ(args[0]))
848         throw mallocNew!Error("Received `this` which is not an object for Object.toString");
849 
850     Obj* obj = AS_OBJ(args[0]);
851     Value name = OBJ_VAL(obj.classObj.name);
852     return RETURN_VAL(args, wrenStringFormat(vm, "instance of @", name));
853 }
854 
855 @WrenPrimitive("Object", "type")
856 bool object_type(WrenVM* vm, Value* args) @nogc
857 {
858     return RETURN_OBJ(args, wrenGetClass(vm, args[0]));
859 }
860 
861 /++ Range primitives +/
862 
863 @WrenPrimitive("Range", "from")
864 bool range_from(WrenVM* vm, Value* args) @nogc
865 {
866     return RETURN_NUM(args, AS_RANGE(args[0]).from);
867 }
868 
869 @WrenPrimitive("Range", "to")
870 bool range_to(WrenVM* vm, Value* args) @nogc
871 {
872     return RETURN_NUM(args, AS_RANGE(args[0]).to);
873 }
874 
875 @WrenPrimitive("Range", "min")
876 bool range_min(WrenVM* vm, Value* args) @nogc
877 {
878     import core.stdc.math : fmin;
879     ObjRange* range = AS_RANGE(args[0]);
880     return RETURN_NUM(args, fmin(range.from, range.to));
881 }
882 
883 @WrenPrimitive("Range", "max")
884 bool range_max(WrenVM* vm, Value* args) @nogc
885 {
886     import core.stdc.math : fmax;
887     ObjRange* range = AS_RANGE(args[0]);
888     return RETURN_NUM(args, fmax(range.from, range.to));
889 }
890 
891 @WrenPrimitive("Range", "isInclusive")
892 bool range_isInclusive(WrenVM* vm, Value* args) @nogc
893 {
894     return RETURN_BOOL(args, AS_RANGE(args[0]).isInclusive);
895 }
896 
897 @WrenPrimitive("Range", "iterate(_)")
898 bool range_iterate(WrenVM* vm, Value* args) @nogc
899 {
900     ObjRange* range = AS_RANGE(args[0]);
901 
902     // Special case: empty range.
903     if (range.from == range.to && !range.isInclusive) return RETURN_FALSE(args);
904 
905     // Start the iteration.
906     if (IS_NULL(args[1])) return RETURN_NUM(args, range.from);
907 
908     if (!validateNum(vm, args[1], "Iterator")) return false;
909 
910     double iterator = AS_NUM(args[1]);
911 
912     // Iterate towards [to] from [from].
913     if (range.from < range.to)
914     {
915         iterator++;
916         if (iterator > range.to) return RETURN_FALSE(args);
917     }
918     else
919     {
920         iterator--;
921         if (iterator < range.to) return RETURN_FALSE(args);
922     }
923 
924     if (!range.isInclusive && iterator == range.to) return RETURN_FALSE(args);
925 
926     return RETURN_NUM(args, iterator);
927 }
928 
929 @WrenPrimitive("Range", "iteratorValue(_)")
930 bool range_iteratorValue(WrenVM* vm, Value* args) @nogc
931 {
932     // Assume the iterator is a number so that is the value of the range.
933     return RETURN_VAL(args, args[1]);
934 }
935 
936 @WrenPrimitive("Range", "toString")
937 bool range_toString(WrenVM* vm, Value* args) @nogc
938 {
939     ObjRange* range = AS_RANGE(args[0]);
940 
941     Value from = wrenNumToString(vm, range.from);
942     wrenPushRoot(vm, AS_OBJ(from));
943 
944     Value to = wrenNumToString(vm, range.to);
945     wrenPushRoot(vm, AS_OBJ(to));
946 
947     Value result = wrenStringFormat(vm, "@$@", from,
948                                     range.isInclusive ? "..".ptr : "...".ptr, to);
949 
950     wrenPopRoot(vm);
951     wrenPopRoot(vm);
952     return RETURN_VAL(args, result);
953 }
954 
955 /++ String primitives +/
956 
957 @WrenPrimitive("String", "fromCodePoint(_)", MethodType.METHOD_PRIMITIVE, true)
958 bool string_fromCodePoint(WrenVM* vm, Value* args) @nogc
959 {
960     if (!validateInt(vm, args[1], "Code point")) return false;
961 
962     int codePoint = cast(int)AS_NUM(args[1]);
963     if (codePoint < 0)
964     {
965         return RETURN_ERROR(vm, "Code point cannot be negative.");
966     }
967     else if (codePoint > 0x10ffff)
968     {
969         return RETURN_ERROR(vm, "Code point cannot be greater than 0x10ffff.");
970     }
971 
972     return RETURN_VAL(args, wrenStringFromCodePoint(vm, codePoint));
973 }
974 
975 @WrenPrimitive("String", "fromByte(_)", MethodType.METHOD_PRIMITIVE, true)
976 bool string_fromByte(WrenVM* vm, Value* args) @nogc
977 {
978     if (!validateInt(vm, args[1], "Byte")) return false;
979     int byte_ = cast(int) AS_NUM(args[1]);
980     if (byte_ < 0)
981     {
982         return RETURN_ERROR(vm, "Byte cannot be negative.");
983     }
984     else if (byte_ > 0xff)
985     {
986         return RETURN_ERROR(vm, "Byte cannot be greater than 0xff.");
987     }
988     return RETURN_VAL(args, wrenStringFromByte(vm, cast(ubyte)byte_));
989 }
990 
991 @WrenPrimitive("String", "byteAt(_)")
992 bool string_byteAt(WrenVM* vm, Value* args) @nogc
993 {
994     ObjString* string_ = AS_STRING(args[0]);
995 
996     uint index = validateIndex(vm, args[1], string_.length, "Index");
997     if (index == uint.max) return false;
998 
999     return RETURN_NUM(args, cast(ubyte)string_.value[index]);
1000 }
1001 
1002 @WrenPrimitive("String", "byteCount")
1003 bool string_byteCount(WrenVM* vm, Value* args) @nogc
1004 {
1005     return RETURN_NUM(args, AS_STRING(args[0]).length);
1006 }
1007 
1008 @WrenPrimitive("String", "codePointAt(_)")
1009 bool string_codePointAt(WrenVM* vm, Value* args) @nogc
1010 {
1011     import wren.utils : wrenUtf8Decode;
1012     ObjString* string_ = AS_STRING(args[0]);
1013 
1014     uint index = validateIndex(vm, args[1], string_.length, "Index");
1015     if (index == uint.max) return false;
1016 
1017     // If we are in the middle of a UTF-8 sequence, indicate that.
1018     const(ubyte)* bytes = cast(ubyte*)string_.value.ptr;
1019     if ((bytes[index] & 0xc0) == 0x80) return RETURN_NUM(args, -1);
1020 
1021     // Decode the UTF-8 sequence.
1022     return RETURN_NUM(args, wrenUtf8Decode(cast(ubyte*)string_.value + index,
1023                                 string_.length - index));
1024 }
1025 
1026 @WrenPrimitive("String", "contains(_)")
1027 bool string_contains(WrenVM* vm, Value* args) @nogc
1028 {
1029     if (!validateString(vm, args[1], "Argument")) return false;
1030 
1031     ObjString* string_ = AS_STRING(args[0]);
1032     ObjString* search = AS_STRING(args[1]);
1033 
1034     return RETURN_BOOL(args, wrenStringFind(string_, search, 0) != uint.max);
1035 }
1036 
1037 @WrenPrimitive("String", "endsWith(_)")
1038 bool string_endsWith(WrenVM* vm, Value* args) @nogc
1039 {
1040     import core.stdc.string : memcmp;
1041     if (!validateString(vm, args[1], "Argument")) return false;
1042 
1043     ObjString* string_ = AS_STRING(args[0]);
1044     ObjString* search = AS_STRING(args[1]);
1045 
1046     // Edge case: If the search string is longer then return false right away.
1047     if (search.length > string_.length) return RETURN_FALSE(args);
1048 
1049     return RETURN_BOOL(args, memcmp(string_.value.ptr + string_.length - search.length,
1050                         search.value.ptr, search.length) == 0);
1051 }
1052 
1053 @WrenPrimitive("String", "indexOf(_)")
1054 bool string_indexOf1(WrenVM* vm, Value* args) @nogc
1055 {
1056     if (!validateString(vm, args[1], "Argument")) return false;
1057 
1058     ObjString* string_ = AS_STRING(args[0]);
1059     ObjString* search = AS_STRING(args[1]);
1060 
1061     uint index = wrenStringFind(string_, search, 0);
1062     return RETURN_NUM(args, index == uint.max ? -1 : cast(int)index);
1063 }
1064 
1065 @WrenPrimitive("String", "indexOf(_,_)")
1066 bool string_indexOf2(WrenVM* vm, Value* args) @nogc
1067 {
1068     if (!validateString(vm, args[1], "Argument")) return false;
1069 
1070     ObjString* string_ = AS_STRING(args[0]);
1071     ObjString* search = AS_STRING(args[1]);
1072     uint start = validateIndex(vm, args[2], string_.length, "Start");
1073     if (start == uint.max) return false;
1074     
1075     uint index = wrenStringFind(string_, search, start);
1076     return RETURN_NUM(args, index == uint.max ? -1 : cast(int)index);   
1077 }
1078 
1079 @WrenPrimitive("String", "iterate(_)")
1080 bool string_iterate(WrenVM* vm, Value* args) @nogc
1081 {
1082     ObjString* string_ = AS_STRING(args[0]);
1083 
1084     // If we're starting the iteration, return the first index.
1085     if (IS_NULL(args[1]))
1086     {
1087         if (string_.length == 0) return RETURN_FALSE(args);
1088         return RETURN_NUM(args, 0);
1089     }
1090 
1091     if (!validateInt(vm, args[1], "Iterator")) return false;
1092 
1093     if (AS_NUM(args[1]) < 0) return RETURN_FALSE(args);
1094     uint index = cast(uint)AS_NUM(args[1]);
1095 
1096     // Advance to the beginning of the next UTF-8 sequence.
1097     do
1098     {
1099         index++;
1100         if (index >= string_.length) return RETURN_FALSE(args);
1101     } while ((string_.value[index] & 0xc0) == 0x80);
1102 
1103     return RETURN_NUM(args, index);   
1104 }
1105 
1106 @WrenPrimitive("String", "iterateByte(_)")
1107 bool string_iterateByte(WrenVM* vm, Value* args) @nogc
1108 {
1109     ObjString* string_ = AS_STRING(args[0]);
1110 
1111     // If we're starting the iteration, return the first index.
1112     if (IS_NULL(args[1]))
1113     {
1114         if (string_.length == 0) return RETURN_FALSE(args);
1115         return RETURN_NUM(args, 0);
1116     }
1117 
1118     if (!validateInt(vm, args[1], "Iterator")) return false;
1119 
1120     if (AS_NUM(args[1]) < 0) return RETURN_FALSE(args);
1121     uint index = cast(uint)AS_NUM(args[1]);
1122 
1123     // Advance to the next byte.
1124     index++;
1125     if (index >= string_.length) return RETURN_FALSE(args);
1126 
1127     return RETURN_NUM(args, index);   
1128 }
1129 
1130 @WrenPrimitive("String", "iteratorValue(_)")
1131 bool string_iteratorValue(WrenVM* vm, Value* args) @nogc
1132 {
1133     ObjString* string_ = AS_STRING(args[0]);
1134     uint index = validateIndex(vm, args[1], string_.length, "Iterator");
1135     if (index == uint.max) return false;
1136 
1137     return RETURN_VAL(args, wrenStringCodePointAt(vm, string_, index));
1138 }
1139 
1140 @WrenPrimitive("String", "+(_)")
1141 bool string_plus(WrenVM* vm, Value* args) @nogc
1142 {
1143     if (!validateString(vm, args[1], "Right operand")) return false;
1144     return RETURN_VAL(args, wrenStringFormat(vm, "@@", args[0], args[1]));
1145 }
1146 
1147 @WrenPrimitive("String", "[_]")
1148 bool string_subscript(WrenVM* vm, Value* args) @nogc
1149 {
1150     ObjString* string_ = AS_STRING(args[0]);
1151 
1152     if (IS_NUM(args[1]))
1153     {
1154         int index = validateIndex(vm, args[1], string_.length, "Subscript");
1155         if (index == -1) return false;
1156 
1157         return RETURN_VAL(args, wrenStringCodePointAt(vm, string_, index));
1158     }
1159 
1160     if (!IS_RANGE(args[1]))
1161     {
1162         return RETURN_ERROR(vm, "Subscript must be a number or a range.");
1163     }
1164 
1165     int step;
1166     uint count = string_.length;
1167     int start = calculateRange(vm, AS_RANGE(args[1]), &count, &step);
1168     if (start == -1) return false;
1169 
1170     return RETURN_VAL(args, wrenNewStringFromRange(vm, string_, start, count, step));
1171 }
1172 
1173 @WrenPrimitive("String", "toString")
1174 bool string_toString(WrenVM* vm, Value* args) @nogc
1175 {
1176     return RETURN_VAL(args, args[0]);
1177 }
1178 
1179 @WrenPrimitive("System", "clock")
1180 bool system_clock(WrenVM* vm, Value* args) @nogc
1181 {
1182     version (Posix) {
1183         import core.sys.posix.stdc.time : clock, CLOCKS_PER_SEC;
1184         return RETURN_NUM(args, cast(double)clock / CLOCKS_PER_SEC);
1185     } else {
1186         import core.time : convClockFreq, MonoTime;
1187         double t = convClockFreq(MonoTime.currTime.ticks, MonoTime.ticksPerSecond, 1_000_000) * 0.000001;
1188         return RETURN_NUM(args, t);
1189     }
1190 }
1191 
1192 @WrenPrimitive("System", "gc()")
1193 bool system_gc(WrenVM* vm, Value* args) @nogc
1194 {
1195     wrenCollectGarbage(vm);
1196     return RETURN_NULL(args);
1197 }
1198 
1199 @WrenPrimitive("System", "writeString_(_)")
1200 bool system_writeString(WrenVM* vm, Value* args) @nogc
1201 {
1202     if (vm.config.writeFn != null)
1203     {
1204         vm.config.writeFn(vm, AS_CSTRING(args[1]));
1205     }
1206 
1207     return RETURN_VAL(args, args[1]);
1208 }
1209 
1210 // Creates either the Object or Class class in the core module with [name].
1211 static ObjClass* defineClass(WrenVM* vm, ObjModule* module_, const(char)* name) @nogc
1212 {
1213   ObjString* nameString = AS_STRING(wrenNewString(vm, name));
1214   wrenPushRoot(vm, cast(Obj*)nameString);
1215 
1216   ObjClass* classObj = wrenNewSingleClass(vm, 0, nameString);
1217 
1218   wrenDefineVariable(vm, module_, name, nameString.length, OBJ_VAL(classObj), null);
1219 
1220   wrenPopRoot(vm);
1221   return classObj;
1222 }
1223 
1224 private void registerPrimitives(string className)(WrenVM* vm, ObjClass* classObj) {
1225     static foreach(_mem; __traits(allMembers, mixin(__MODULE__)))
1226     {{
1227         import std.traits : getUDAs, hasUDA;
1228         alias member = __traits(getMember, mixin(__MODULE__), _mem);
1229         static if (hasUDA!(member, WrenPrimitive)) {
1230             enum primDef = getUDAs!(member, WrenPrimitive)[0];
1231             static if (primDef.className == className) {
1232                 PRIMITIVE!(primDef.primitiveName, member, primDef.methodType, primDef.registerToSuperClass)(vm, classObj);
1233             }
1234         }
1235     }}
1236 }
1237 
1238 void wrenInitializeCore(WrenVM* vm) @nogc
1239 {
1240     ObjModule* coreModule = wrenNewModule(vm, null);
1241     wrenPushRoot(vm, cast(Obj*)coreModule);
1242     
1243     // The core module's key is null in the module map.
1244     wrenMapSet(vm, vm.modules, NULL_VAL, OBJ_VAL(coreModule));
1245     wrenPopRoot(vm); // coreModule.
1246 
1247     // Define the root Object class. This has to be done a little specially
1248     // because it has no superclass.
1249     vm.objectClass = defineClass(vm, coreModule, "Object");
1250     registerPrimitives!("Object")(vm, vm.objectClass);
1251 
1252     // Now we can define Class, which is a subclass of Object.
1253     vm.classClass = defineClass(vm, coreModule, "Class");
1254     wrenBindSuperclass(vm, vm.classClass, vm.objectClass);
1255     // TODO: define primitives
1256 
1257     // Finally, we can define Object's metaclass which is a subclass of Class.
1258     ObjClass* objectMetaclass = defineClass(vm, coreModule, "Object metaclass");
1259 
1260     // Wire up the metaclass relationships now that all three classes are built.
1261     vm.objectClass.obj.classObj = objectMetaclass;
1262     objectMetaclass.obj.classObj = vm.classClass;
1263     vm.classClass.obj.classObj = vm.classClass;
1264 
1265     // Do this after wiring up the metaclasses so objectMetaclass doesn't get
1266     // collected.
1267     wrenBindSuperclass(vm, objectMetaclass, vm.classClass);
1268     registerPrimitives!("Object metaclass")(vm, objectMetaclass);
1269 
1270     // The core class diagram ends up looking like this, where single lines point
1271     // to a class's superclass, and double lines point to its metaclass:
1272     //
1273     //        .------------------------------------. .====.
1274     //        |                  .---------------. | #    #
1275     //        v                  |               v | v    #
1276     //   .---------.   .-------------------.   .-------.  #
1277     //   | Object  |==>| Object metaclass  |==>| Class |=="
1278     //   '---------'   '-------------------'   '-------'
1279     //        ^                                 ^ ^ ^ ^
1280     //        |                  .--------------' # | #
1281     //        |                  |                # | #
1282     //   .---------.   .-------------------.      # | # -.
1283     //   |  Base   |==>|  Base metaclass   |======" | #  |
1284     //   '---------'   '-------------------'        | #  |
1285     //        ^                                     | #  |
1286     //        |                  .------------------' #  | Example classes
1287     //        |                  |                    #  |
1288     //   .---------.   .-------------------.          #  |
1289     //   | Derived |==>| Derived metaclass |=========="  |
1290     //   '---------'   '-------------------'            -'
1291 
1292     // The rest of the classes can now be defined normally.
1293     wrenInterpret(vm, null, coreModuleSource.ptr);
1294 
1295     vm.boolClass = AS_CLASS(wrenFindVariable(vm, coreModule, "Bool"));
1296     registerPrimitives!("Bool")(vm, vm.boolClass);
1297 
1298     vm.fiberClass = AS_CLASS(wrenFindVariable(vm, coreModule, "Fiber"));
1299     registerPrimitives!("Fiber")(vm, vm.fiberClass);
1300 
1301     vm.fnClass = AS_CLASS(wrenFindVariable(vm, coreModule, "Fn"));
1302     registerPrimitives!("Fn")(vm, vm.fnClass);
1303 
1304     vm.nullClass = AS_CLASS(wrenFindVariable(vm, coreModule, "Null"));
1305     registerPrimitives!("Null")(vm, vm.nullClass);
1306 
1307     vm.numClass = AS_CLASS(wrenFindVariable(vm, coreModule, "Num"));
1308     registerPrimitives!("Num")(vm, vm.numClass);
1309 
1310     vm.stringClass = AS_CLASS(wrenFindVariable(vm, coreModule, "String"));
1311     registerPrimitives!("String")(vm, vm.stringClass);
1312 
1313     vm.listClass = AS_CLASS(wrenFindVariable(vm, coreModule, "List"));
1314     registerPrimitives!("List")(vm, vm.listClass);
1315 
1316     vm.mapClass = AS_CLASS(wrenFindVariable(vm, coreModule, "Map"));
1317     registerPrimitives!("Map")(vm, vm.mapClass);
1318 
1319     vm.rangeClass = AS_CLASS(wrenFindVariable(vm, coreModule, "Range"));
1320     registerPrimitives!("Range")(vm, vm.rangeClass);
1321 
1322     ObjClass* systemClass = AS_CLASS(wrenFindVariable(vm, coreModule, "System"));
1323     registerPrimitives!("System")(vm, systemClass.obj.classObj);
1324 
1325 
1326     // While bootstrapping the core types and running the core module, a number
1327     // of string objects have been created, many of which were instantiated
1328     // before stringClass was stored in the VM. Some of them *must* be created
1329     // first -- the ObjClass for string itself has a reference to the ObjString
1330     // for its name.
1331     //
1332     // These all currently have a NULL classObj pointer, so go back and assign
1333     // them now that the string class is known.
1334     for (Obj* obj = vm.first; obj != null; obj = obj.next)
1335     {
1336         if (obj.type == ObjType.OBJ_STRING) obj.classObj = vm.stringClass;
1337     }
1338 }