exceptions.c
Go to the documentation of this file.
1 
5 #include <string.h>
6 #include <kuroko/vm.h>
7 #include <kuroko/value.h>
8 #include <kuroko/memory.h>
9 #include <kuroko/util.h>
10 
11 #include "private.h"
12 #include "opcode_enum.h"
13 
19 #define ADD_EXCEPTION_CLASS(obj,name,baseClass) KrkClass * name; do { \
20  ADD_BASE_CLASS(obj,#name,baseClass); \
21  krk_finalizeClass(obj); \
22  name = obj; \
23  (void)name; \
24 } while (0)
25 
26 #define IS_BaseException(o) (likely(krk_isInstanceOf(o,vm.exceptions->baseException)))
27 #define AS_BaseException(o) (AS_INSTANCE(o))
28 #define IS_KeyError(o) (likely(krk_isInstanceOf(o,vm.exceptions->keyError)))
29 #define AS_KeyError(o) (AS_INSTANCE(o))
30 #define IS_SyntaxError(o) (likely(krk_isInstanceOf(o,vm.exceptions->syntaxError)))
31 #define AS_SyntaxError(o) (AS_INSTANCE(o))
32 #define CURRENT_CTYPE KrkInstance*
33 #define CURRENT_NAME self
34 
42 KRK_Method(BaseException,__init__) {
43  if (argc > 1) {
44  krk_attachNamedValue(&self->fields, "arg", argv[1]);
45  }
46  krk_attachNamedValue(&self->fields, "__cause__", NONE_VAL());
47  krk_attachNamedValue(&self->fields, "__context__", NONE_VAL());
48  return NONE_VAL();
49 }
50 
58 KRK_Method(BaseException,__repr__) {
59  KrkValue arg;
60  struct StringBuilder sb = {0};
61 
62  pushStringBuilderStr(&sb, self->_class->name->chars, self->_class->name->length);
63  pushStringBuilder(&sb, '(');
64 
65  if (krk_tableGet(&self->fields, OBJECT_VAL(S("arg")), &arg)) {
66  /* repr it */
67  krk_push(arg);
68  KrkValue repred = krk_callDirect(krk_getType(arg)->_reprer, 1);
69  pushStringBuilderStr(&sb, AS_CSTRING(repred), AS_STRING(repred)->length);
70  }
71 
72  pushStringBuilder(&sb, ')');
73 
74  return finishStringBuilder(&sb);
75 }
76 
85 KRK_Method(BaseException,__str__) {
86  KrkValue arg;
87  if (!krk_tableGet(&self->fields, OBJECT_VAL(S("arg")), &arg) || IS_NONE(arg)) {
88  return OBJECT_VAL(S(""));
89  } else if (!IS_STRING(arg)) {
90  KrkClass * type = krk_getType(arg);
91  if (type->_tostr) {
92  krk_push(arg);
93  return krk_callDirect(krk_getType(arg)->_tostr, 1);
94  }
95  return OBJECT_VAL(S(""));
96  } else {
97  return arg;
98  }
99 }
100 
101 KRK_Method(KeyError,__str__) {
102  if (!IS_INSTANCE(argv[0])) return NONE_VAL(); /* uh oh */
103  KrkValue arg;
104  if (krk_tableGet(&self->fields, OBJECT_VAL(S("arg")), &arg)) {
105  KrkClass * type = krk_getType(arg);
106  if (type->_reprer) {
107  krk_push(arg);
108  return krk_callDirect(krk_getType(arg)->_reprer, 1);
109  }
110  }
111  return FUNC_NAME(BaseException,__str__)(argc,argv,hasKw);
112 }
113 
125 KRK_Method(SyntaxError,__str__) {
126  /* .arg */
127  KrkValue file, line, lineno, colno, arg, func, width;
128  if (!krk_tableGet(&self->fields, OBJECT_VAL(S("file")), &file) || !IS_STRING(file)) goto _badSyntaxError;
129  if (!krk_tableGet(&self->fields, OBJECT_VAL(S("line")), &line) || !IS_STRING(line)) goto _badSyntaxError;
130  if (!krk_tableGet(&self->fields, OBJECT_VAL(S("lineno")), &lineno) || !IS_INTEGER(lineno)) goto _badSyntaxError;
131  if (!krk_tableGet(&self->fields, OBJECT_VAL(S("colno")), &colno) || !IS_INTEGER(colno)) goto _badSyntaxError;
132  if (!krk_tableGet(&self->fields, OBJECT_VAL(S("arg")), &arg) || !IS_STRING(arg)) goto _badSyntaxError;
133  if (!krk_tableGet(&self->fields, OBJECT_VAL(S("func")), &func)) goto _badSyntaxError;
134  if (!krk_tableGet(&self->fields, OBJECT_VAL(S("width")), &width) || !IS_INTEGER(width)) goto _badSyntaxError;
135 
136  if (AS_INTEGER(colno) <= 0) colno = INTEGER_VAL(1);
137 
138  int definitelyNotFullWidth = !(AS_STRING(line)->obj.flags & KRK_OBJ_FLAGS_STRING_MASK);
139 
140  krk_push(OBJECT_VAL(S("^")));
141  if (definitelyNotFullWidth && AS_INTEGER(width) > 1) {
142  for (krk_integer_type i = 1; i < AS_INTEGER(width); ++i) {
143  krk_push(OBJECT_VAL(S("^")));
144  krk_addObjects();
145  }
146  }
147 
148  krk_push(OBJECT_VAL(S(" File \"{}\", line {}{}\n {}\n {}{}\n{}: {}")));
149  unsigned int column = AS_INTEGER(colno);
150  char * tmp = malloc(column);
151  memset(tmp,' ',column);
152  tmp[column-1] = '\0';
153  krk_push(OBJECT_VAL(krk_takeString(tmp,column-1)));
154  krk_push(OBJECT_VAL(self->_class->name));
155  if (IS_STRING(func)) {
156  krk_push(OBJECT_VAL(S(" in ")));
157  krk_push(func);
158  krk_addObjects();
159  } else {
160  krk_push(OBJECT_VAL(S("")));
161  }
162  KrkValue formattedString = krk_string_format(9,
163  (KrkValue[]){krk_peek(3), file, lineno, krk_peek(0), line, krk_peek(2), krk_peek(4), krk_peek(1), arg}, 0);
164  krk_pop(); /* instr */
165  krk_pop(); /* class */
166  krk_pop(); /* spaces */
167  krk_pop(); /* format string */
168  krk_pop(); /* carets */
169 
170  return formattedString;
171 
172 _badSyntaxError:
173  return OBJECT_VAL(S("SyntaxError: invalid syntax"));
174 }
175 
176 
183 _noexport
185  /* Add exception classes */
186  ADD_EXCEPTION_CLASS(vm.exceptions->baseException, BaseException, vm.baseClasses->objectClass);
187  BIND_METHOD(BaseException,__init__);
188  BIND_METHOD(BaseException,__repr__);
189  BIND_METHOD(BaseException,__str__);
190  krk_finalizeClass(BaseException);
191 
192  /* KeyboardInterrupt is currently the only thing that directly inherits from BaseException. */
193  ADD_EXCEPTION_CLASS(vm.exceptions->keyboardInterrupt, KeyboardInterrupt, BaseException);
194 
195  /* Everything else subclasses Exception */
196  ADD_EXCEPTION_CLASS(vm.exceptions->Exception, Exception, BaseException);
197 
198  /* TypeError has a subclass ArgumentError, which is what we raise on arity mismatches */
199  ADD_EXCEPTION_CLASS(vm.exceptions->typeError, TypeError, Exception);
200  ADD_EXCEPTION_CLASS(vm.exceptions->argumentError, ArgumentError, TypeError);
201 
202  /* KeyError gets its own string conversion so it can repr msg */
203  ADD_EXCEPTION_CLASS(vm.exceptions->keyError, KeyError, Exception);
204  BIND_METHOD(KeyError,__str__);
205  krk_finalizeClass(KeyError);
206 
207  /* There is nothing special about these. */
208  ADD_EXCEPTION_CLASS(vm.exceptions->indexError, IndexError, Exception);
209  ADD_EXCEPTION_CLASS(vm.exceptions->attributeError, AttributeError, Exception);
210  ADD_EXCEPTION_CLASS(vm.exceptions->nameError, NameError, Exception);
211  ADD_EXCEPTION_CLASS(vm.exceptions->importError, ImportError, Exception);
212  ADD_EXCEPTION_CLASS(vm.exceptions->ioError, IOError, Exception);
213  ADD_EXCEPTION_CLASS(vm.exceptions->valueError, ValueError, Exception);
214  ADD_EXCEPTION_CLASS(vm.exceptions->zeroDivisionError, ZeroDivisionError, Exception);
215  ADD_EXCEPTION_CLASS(vm.exceptions->notImplementedError, NotImplementedError, Exception);
216  ADD_EXCEPTION_CLASS(vm.exceptions->assertionError, AssertionError, Exception);
217  ADD_EXCEPTION_CLASS(vm.exceptions->OSError, OSError, Exception);
218  ADD_EXCEPTION_CLASS(vm.exceptions->SystemError, SystemError, Exception);
219 
220  /* SyntaxError also gets a special __str__ method... but also the whole exception
221  * printer has special logic for it - TODO fix that */
222  ADD_EXCEPTION_CLASS(vm.exceptions->syntaxError, SyntaxError, vm.exceptions->Exception);
223  BIND_METHOD(SyntaxError,__str__);
224  krk_finalizeClass(SyntaxError);
225 }
226 
227 static void dumpInnerException(KrkValue exception, int depth) {
228  if (depth > 10) {
229  fprintf(stderr, "Too many inner exceptions encountered.\n");
230  return;
231  }
232 
233  krk_push(exception);
234  if (IS_INSTANCE(exception)) {
235 
236  KrkValue inner;
237 
238  /* Print cause or context */
239  if (krk_tableGet(&AS_INSTANCE(exception)->fields, OBJECT_VAL(S("__cause__")), &inner) && !IS_NONE(inner)) {
240  dumpInnerException(inner, depth + 1);
241  fprintf(stderr, "\nThe above exception was the direct cause of the following exception:\n\n");
242  } else if (krk_tableGet(&AS_INSTANCE(exception)->fields, OBJECT_VAL(S("__context__")), &inner) && !IS_NONE(inner)) {
243  dumpInnerException(inner, depth + 1);
244  fprintf(stderr, "\nDuring handling of the above exception, another exception occurred:\n\n");
245  }
246 
247  KrkValue tracebackEntries;
248  if (krk_tableGet(&AS_INSTANCE(exception)->fields, OBJECT_VAL(S("traceback")), &tracebackEntries)
249  && IS_list(tracebackEntries) && AS_LIST(tracebackEntries)->count > 0) {
250 
251  /* This exception has a traceback we can print. */
252  fprintf(stderr, "Traceback (most recent call last):\n");
253  for (size_t i = 0; i < AS_LIST(tracebackEntries)->count; ++i) {
254 
255  /* Quietly skip invalid entries as we don't want to bother printing explanatory text for them */
256  if (!IS_TUPLE(AS_LIST(tracebackEntries)->values[i])) continue;
257  KrkTuple * entry = AS_TUPLE(AS_LIST(tracebackEntries)->values[i]);
258  if (entry->values.count != 2) continue;
259  if (!IS_CLOSURE(entry->values.values[0])) continue;
260  if (!IS_INTEGER(entry->values.values[1])) continue;
261 
262  /* Get the function and instruction index from this traceback entry */
263  KrkClosure * closure = AS_CLOSURE(entry->values.values[0]);
264  KrkCodeObject * function = closure->function;
265  size_t instruction = AS_INTEGER(entry->values.values[1]);
266 
267  /* Calculate the line number */
268  int lineNo = (int)krk_lineNumber(&function->chunk, instruction);
269 
270  /* Print the simple stuff that we already know */
271  fprintf(stderr, " File \"%s\", line %d, in %s\n",
272  (function->chunk.filename ? function->chunk.filename->chars : "?"),
273  lineNo,
274  (function->name ? function->name->chars : "(unnamed)"));
275 
276 #ifndef KRK_NO_SOURCE_IN_TRACEBACK
277  /* Try to open the file */
278  if (function->chunk.filename) {
279  FILE * f = fopen(function->chunk.filename->chars, "r");
280  if (f) {
281  int line = 1;
282  do {
283  int c = fgetc(f);
284  if (c < -1) break;
285  if (c == '\n') {
286  line++;
287  continue;
288  }
289  if (line == lineNo) {
290  fprintf(stderr," ");
291  unsigned short j = 1;
292  while (c == ' ' || c == '\t') {
293  c = fgetc(f);
294  j++;
295  }
296  do {
297  fputc(c, stderr);
298  c = fgetc(f);
299  } while (!feof(f) && c > 0 && c != '\n');
300  fprintf(stderr, "\n");
301 #ifndef KRK_DISABLE_DEBUG
302  uint8_t start, midStart, midEnd, end;
303  if (krk_debug_expressionUnderline(function, &start, &midStart, &midEnd, &end, instruction)) {
304  fprintf(stderr," ");
305  for (; j < start; ++j) fprintf(stderr," ");
306  for (; j < midStart; ++j) fprintf(stderr,"~");
307  for (; j < midEnd; ++j) fprintf(stderr, "^");
308  for (; j < end; ++j) fprintf(stderr,"~");
309  fprintf(stderr,"\n");
310  }
311 #endif
312  break;
313  }
314  } while (!feof(f));
315  fclose(f);
316  }
317  }
318 #endif
319  }
320  }
321  }
322 
323  /* Is this a SyntaxError? Handle those specially. */
324  if (krk_isInstanceOf(exception, vm.exceptions->syntaxError)) {
325  KrkValue result = krk_callDirect(krk_getType(exception)->_tostr, 1);
326  fprintf(stderr, "%s\n", AS_CSTRING(result));
327  return;
328  }
329 
330  /* Clear the exception state while printing the exception. */
331  int hadException = krk_currentThread.flags & KRK_THREAD_HAS_EXCEPTION;
332  krk_currentThread.flags &= ~(KRK_THREAD_HAS_EXCEPTION);
333 
334  /* Prepare to print exception name with prefixed module, if it's not __builtins__. */
335  KrkClass * type = krk_getType(exception);
336  KrkValue module = NONE_VAL();
337  krk_tableGet(&type->methods, OBJECT_VAL(S("__module__")), &module);
338  if (!(IS_NONE(module) || (IS_STRING(module) && AS_STRING(module) == S("builtins")))) {
339  fprintf(stderr, "%s.", AS_CSTRING(module));
340  }
341 
342  /* Print type name */
343  fprintf(stderr, "%s", krk_typeName(exception));
344 
345  /* Stringify it. */
346  KrkValue result = krk_callDirect(krk_getType(exception)->_tostr, 1);
347  if (!IS_STRING(result) || AS_STRING(result)->length == 0) {
348  fprintf(stderr, "\n");
349  } else {
350  fprintf(stderr, ": ");
351  fwrite(AS_CSTRING(result), AS_STRING(result)->length, 1, stderr);
352  fprintf(stderr, "\n");
353  }
354 
355  /* Turn the exception flag back on */
356  krk_currentThread.flags |= hadException;
357 }
358 
366 void krk_dumpTraceback(void) {
368  dumpInnerException(krk_currentThread.currentException, 0);
369  }
370 }
371 
375 static void attachTraceback(void) {
376  if (IS_INSTANCE(krk_currentThread.currentException)) {
377  KrkInstance * theException = AS_INSTANCE(krk_currentThread.currentException);
378  KrkValue tracebackList;
379  if (krk_tableGet(&theException->fields, OBJECT_VAL(S("traceback")), &tracebackList)) {
380  krk_push(tracebackList);
381  } else {
382  krk_push(NONE_VAL());
383  }
384  tracebackList = krk_list_of(0,NULL,0);
385  krk_push(tracebackList);
386 
387  /* Build the traceback object */
389 
390  /* Go up until we get to the exit frame */
391  size_t frameOffset = 0;
393  size_t stackOffset = krk_currentThread.stackTop - krk_currentThread.stack - 1;
394  while (stackOffset > 0 && !IS_HANDLER_TYPE(krk_currentThread.stack[stackOffset], OP_PUSH_TRY)) stackOffset--;
395  frameOffset = krk_currentThread.frameCount - 1;
396  while (frameOffset > 0 && krk_currentThread.frames[frameOffset].slots > stackOffset) frameOffset--;
397  }
398 
399  for (size_t i = frameOffset; i < krk_currentThread.frameCount; i++) {
400  KrkCallFrame * frame = &krk_currentThread.frames[i];
401  KrkTuple * tbEntry = krk_newTuple(2);
402  krk_push(OBJECT_VAL(tbEntry));
403  tbEntry->values.values[tbEntry->values.count++] = OBJECT_VAL(frame->closure);
404  tbEntry->values.values[tbEntry->values.count++] = INTEGER_VAL(frame->ip - frame->closure->function->chunk.code - 1);
405  krk_writeValueArray(AS_LIST(tracebackList), OBJECT_VAL(tbEntry));
406  krk_pop();
407  }
408  }
409 
410  if (IS_list(krk_peek(1))) {
411  KrkValueArray * existingTraceback = AS_LIST(krk_peek(1));
412  for (size_t i = 0; i < existingTraceback->count; ++i) {
413  krk_writeValueArray(AS_LIST(tracebackList), existingTraceback->values[i]);
414  }
415  }
416 
417  krk_attachNamedValue(&theException->fields, "traceback", tracebackList);
418  krk_pop();
419  krk_pop();
420  } /* else: probably a legacy 'raise str', just don't bother. */
421 }
422 
423 void krk_attachInnerException(KrkValue innerException) {
424  if (IS_INSTANCE(krk_currentThread.currentException)) {
425  KrkInstance * theException = AS_INSTANCE(krk_currentThread.currentException);
426  if (krk_valuesSame(krk_currentThread.currentException,innerException)) {
427  /* re-raised? */
428  return;
429  } else {
430  krk_attachNamedValue(&theException->fields, "__context__", innerException);
431  }
432  }
433 }
434 
436  if (IS_CLASS(base)) {
437  krk_push(base);
438  base = krk_callStack(0);
439  if (krk_currentThread.flags & KRK_THREAD_HAS_EXCEPTION) return;
440  }
442  if (IS_CLASS(cause)) {
443  krk_push(cause);
444  cause = krk_callStack(0);
445  if (krk_currentThread.flags & KRK_THREAD_HAS_EXCEPTION) return;
446  }
447  if (IS_INSTANCE(krk_currentThread.currentException) && !IS_NONE(cause)) {
449  "__cause__", cause);
450  }
451  attachTraceback();
452  krk_currentThread.flags |= KRK_THREAD_HAS_EXCEPTION;
453 }
454 
460 KrkValue krk_runtimeError(KrkClass * type, const char * fmt, ...) {
461  KrkValue msg = KWARGS_VAL(0);
462  struct StringBuilder sb = {0};
463 
464  va_list args;
465  va_start(args, fmt);
466 
467  if (!strcmp(fmt,"%V")) {
468  msg = va_arg(args, KrkValue);
469  } else if (!krk_pushStringBuilderFormatV(&sb, fmt, args)) {
470  return NONE_VAL();
471  }
472 
473  va_end(args);
474  krk_currentThread.flags |= KRK_THREAD_HAS_EXCEPTION;
475 
476  /* Allocate an exception object of the requested type. */
477  KrkInstance * exceptionObject = krk_newInstance(type);
478  krk_push(OBJECT_VAL(exceptionObject));
479  krk_attachNamedValue(&exceptionObject->fields, "arg", krk_valuesSame(msg,KWARGS_VAL(0)) ? finishStringBuilder(&sb) : msg);
480  krk_attachNamedValue(&exceptionObject->fields, "__cause__", NONE_VAL());
481  krk_attachNamedValue(&exceptionObject->fields, "__context__", NONE_VAL());
482  krk_pop();
483 
484  /* Set the current exception to be picked up by handleException */
485  krk_currentThread.currentException = OBJECT_VAL(exceptionObject);
486  attachTraceback();
487  return NONE_VAL();
488 }
_noexport void _createAndBind_exceptions(void)
Bind native methods and classes for exceptions.
Definition: exceptions.c:184
void krk_attachInnerException(KrkValue innerException)
Attach an inner exception to the current exception object.
Definition: exceptions.c:423
#define ADD_EXCEPTION_CLASS(obj, name, baseClass)
Definition: exceptions.c:19
void krk_raiseException(KrkValue base, KrkValue cause)
Raise an exception value.
Definition: exceptions.c:435
KrkValue krk_runtimeError(KrkClass *type, const char *fmt,...)
Produce and raise an exception with a formatted message.
Definition: exceptions.c:460
void krk_dumpTraceback(void)
If there is an active exception, print a traceback to stderr.
Definition: exceptions.c:366
Functions for dealing with garbage collection and memory allocation.
Internal header.
Represents a managed call state in a VM thread.
Definition: vm.h:44
size_t slots
Definition: vm.h:47
KrkClosure * closure
Definition: vm.h:45
uint8_t * ip
Definition: vm.h:46
size_t krk_lineNumber(KrkChunk *chunk, size_t offset)
Obtain the line number for a byte offset into a bytecode chunk.
Definition: chunk.c:75
Type object.
Definition: object.h:215
KrkObj * _tostr
__str__ Called to produce a string from an instance
Definition: object.h:230
KrkObj * _reprer
__repr__ Called to create a reproducible string representation of an instance
Definition: object.h:229
KrkTable methods
General attributes table.
Definition: object.h:218
void krk_finalizeClass(KrkClass *_class)
Finalize a class by collecting pointers to core methods.
Definition: vm.c:189
Function object.
Definition: object.h:195
KrkCodeObject * function
The codeobject containing the bytecode run when this function is called.
Definition: object.h:197
Code object.
Definition: object.h:163
KrkChunk chunk
Bytecode data.
Definition: object.h:170
int krk_debug_expressionUnderline(const KrkCodeObject *codeobject, uint8_t *start, uint8_t *midStart, uint8_t *endStart, uint8_t *end, size_t instruction)
Extract expression mapping from chunk.
Definition: debug.c:726
An object of a class.
Definition: object.h:281
KrkInstance * krk_newInstance(KrkClass *_class)
Create a new instance of the given class.
Definition: object.c:343
KrkTable fields
Attributes table.
Definition: object.h:284
KrkValue krk_list_of(int argc, const KrkValue argv[], int hasKw)
Create a list object.
Definition: obj_list.c:30
KrkString * krk_takeString(char *chars, size_t length)
Yield ownership of a C string to the GC and obtain a string object.
Definition: object.c:208
int krk_tableGet(KrkTable *table, KrkValue key, KrkValue *value)
Obtain the value associated with a key in a table.
Definition: table.c:211
void krk_attachNamedValue(KrkTable *table, const char name[], KrkValue obj)
Attach a value to an attribute table.
Definition: vm.c:794
KrkValue currentException
Definition: vm.h:164
size_t frameCount
Definition: vm.h:156
KrkValue * stack
Definition: vm.h:158
KrkValue * stackTop
Definition: vm.h:159
KrkCallFrame * frames
Definition: vm.h:155
int flags
Definition: vm.h:165
Immutable sequence of arbitrary values.
Definition: object.h:323
KrkValueArray values
Stores the length, capacity, and actual values of the tuple.
Definition: object.h:325
KrkTuple * krk_newTuple(size_t length)
Create a new tuple.
Definition: object.c:357
Flexible vector of stack references.
Definition: value.h:75
KrkValue * values
Definition: value.h:78
void krk_writeValueArray(KrkValueArray *array, KrkValue value)
Add a value to a value array.
Definition: value.c:17
size_t count
Definition: value.h:77
Stack reference or primative value.
const char * krk_typeName(KrkValue value)
Get the name of the type of a value.
Definition: vm.c:984
int krk_isInstanceOf(KrkValue obj, const KrkClass *type)
Determine if a class is an instance or subclass of a given type.
Definition: vm.c:282
int krk_valuesEqual(KrkValue a, KrkValue b)
Compare two values for equality.
Definition: value.c:106
KrkClass * krk_getType(KrkValue value)
Get the class representing a value.
Definition: vm.c:240
static int krk_valuesSame(KrkValue a, KrkValue b)
Compare two values by identity.
Definition: value.h:141
Inline flexible string array.
Definition: util.h:162
Utilities for creating native bindings.
Definitions for primitive stack references.
Core API for the bytecode virtual machine.
krk_threadLocal KrkThreadState krk_currentThread
Thread-local VM state.
KrkValue krk_callStack(int argCount)
Call a callable on the stack with argCount arguments.
Definition: vm.c:732
#define vm
Convenience macro for namespacing.
Definition: vm.h:257
KrkValue krk_pop(void)
Pop the top of the stack.
Definition: vm.c:131
void krk_addObjects(void)
Concatenate two strings.
Definition: obj_str.c:941
void krk_push(KrkValue value)
Push a stack value.
Definition: vm.c:118
KrkValue krk_callDirect(KrkObj *callable, int argCount)
Call a closure or native function with argCount arguments.
Definition: vm.c:740
KrkValue krk_peek(int distance)
Peek down from the top of the stack.
Definition: vm.c:139