parseargs.c
1 #include <kuroko/vm.h>
2 #include <kuroko/util.h>
3 
12 static int matchType(const char * _method_name, KrkClass * type, KrkValue arg) {
13  if (arg != KWARGS_VAL(0) && !krk_isInstanceOf(arg, type)) {
14  krk_runtimeError(vm.exceptions->typeError, "%s() expects %s, not '%T'",
15  _method_name, type ? type->name->chars : "unknown type", arg);
16  return 0;
17  }
18  return 1;
19 }
20 
40  const char * _method_name,
41  int argc, const KrkValue argv[], int hasKw,
42  const char * fmt, const char ** names, va_list args) {
43  int iarg = 0;
44  int oarg = 0;
45  int required = 1;
46  int acceptextrakws = 0;
48  const char * maybeColon = strchr(fmt, ':');
49  if (maybeColon) {
50  _method_name = maybeColon + 1;
51  }
52 
53  if (*fmt == '.') {
60  argv++;
61  argc--;
62  fmt++;
63  }
64 
65  /* Required args */
66  while (*fmt) {
67  if (*fmt == ':') break;
68  if (*fmt == '|') {
74  if (!required) {
75  krk_runtimeError(vm.exceptions->typeError, "format string has multiple |s");
76  return 1;
77  }
78  required = 0;
79  fmt++;
80  continue;
81  }
82  if (*fmt == '*') {
92  int * out_c = va_arg(args, int *);
93  const KrkValue ** out_v = va_arg(args, const KrkValue **);
94  *out_c = argc - iarg;
95  *out_v = &argv[iarg];
96  iarg = argc;
97  required = 0;
98  fmt++;
99  continue;
100  }
101  if (*fmt == '$') {
109  if (required) {
110  krk_runtimeError(vm.exceptions->typeError, "$ must be after | or * in format string");
111  return 1;
112  }
113  if (iarg < argc) break;
114  fmt++;
115  continue;
116  }
117  if (*fmt == '~') {
125  acceptextrakws = 1;
126  fmt++;
127  continue;
128  }
129 
130  int wasPositional = 0;
131  KrkValue arg = KWARGS_VAL(0);
132  krk_push(OBJECT_VAL(krk_copyString(names[oarg],strlen(names[oarg]))));
133 
134  if (iarg < argc) {
135  /* Positional arguments are pretty straightforward. */
136  arg = argv[iarg];
137  iarg++;
138  wasPositional = 1;
139  } else if ((required && !hasKw) || (hasKw && !krk_tableGet_fast(AS_DICT(argv[argc]), AS_STRING(krk_peek(0)), &arg) && required)) {
140  /* If keyword argument lookup failed and this is not an optional argument, raise an exception. */
141  krk_runtimeError(vm.exceptions->typeError, "%s() missing required positional argument: '%S'",
142  _method_name, AS_STRING(krk_peek(0)));
143  goto _error;
144  }
145 
146  if (hasKw && krk_tableDelete(AS_DICT(argv[argc]), krk_peek(0)) && wasPositional) {
147  /* We remove all arguments from kwargs. If we got this argument from a positional argument,
148  * and it was found during deletion, we raise a multiple-defs exception. */
149  krk_runtimeError(vm.exceptions->typeError, "%s() got multiple values for argument '%S'",
150  _method_name, AS_STRING(krk_peek(0)));
151  goto _error;
152  }
153 
154  char argtype = *fmt++;
155 
156  if (*fmt == '?') {
157  /* "is present", useful for things where relying on a default isn't useful but you
158  * still want to have all the type checking and automatic parsing. */
159  fmt++;
160  int * out = va_arg(args, int*);
161  *out = arg != KWARGS_VAL(0);
162  }
163 
164  if (*fmt == '!') {
165  /* "of type", thrown an exception if the argument was present but was not
166  * an instance of a given class. Originally just for @c O and @c V but
167  * now available anywhere, though likely not useful for other types.
168  * Maybe if you want @c p to only be a bool this could be useful? */
169  fmt++;
170  KrkClass * type = va_arg(args, KrkClass*);
171  if (!matchType(_method_name, type, arg)) goto _error;
172  }
173 
174  switch (argtype) {
184  case 'O': {
185  KrkObj ** out = va_arg(args, KrkObj**);
186  if (arg != KWARGS_VAL(0)) {
187  if (IS_NONE(arg)) {
188  *out = NULL;
189  } else if (!IS_OBJECT(arg)) {
190  TYPE_ERROR(heap object,arg);
191  goto _error;
192  } else {
193  *out = AS_OBJECT(arg);
194  }
195  }
196  break;
197  }
198 
208  case 'V': {
209  KrkValue * out = va_arg(args, KrkValue*);
210  if (arg != KWARGS_VAL(0)) {
211  *out = arg;
212  }
213  break;
214  }
215 
223  case 'z': {
224  char ** out = va_arg(args, char **);
225  size_t * size = NULL;
226  if (*fmt == '#') {
227  fmt++;
228  size = va_arg(args, size_t*);
229  }
230  if (arg != KWARGS_VAL(0)) {
231  if (arg == NONE_VAL()) {
232  *out = NULL;
233  if (size) *size = 0;
234  } else if (IS_STRING(arg)) {
235  *out = AS_CSTRING(arg);
236  if (size) *size = AS_STRING(arg)->length;
237  } else {
238  TYPE_ERROR(str or None,arg);
239  goto _error;
240  }
241  }
242  break;
243  }
244 
248  case 's': {
249  char ** out = va_arg(args, char **);
250  size_t * size = NULL;
251  if (*fmt == '#') {
252  fmt++;
253  size = va_arg(args, size_t*);
254  }
255  if (arg != KWARGS_VAL(0)) {
256  if (IS_STRING(arg)) {
257  *out = AS_CSTRING(arg);
258  if (size) *size = AS_STRING(arg)->length;
259  } else {
260  TYPE_ERROR(str,arg);
261  goto _error;
262  }
263  }
264  break;
265  }
266 
276 #define NUMERIC(c,type) case c: { type * out = va_arg(args, type*); if (arg != KWARGS_VAL(0)) { if (!krk_long_to_int(arg, sizeof(type), out)) goto _error; } break; }
277  NUMERIC('b',unsigned char)
278  NUMERIC('h',short)
279  NUMERIC('H',unsigned short)
280  NUMERIC('i',int)
281  NUMERIC('I',unsigned int)
282  NUMERIC('l',long)
283  NUMERIC('k',unsigned long)
284  NUMERIC('L',long long)
285  NUMERIC('K',unsigned long long)
286  NUMERIC('n',ssize_t)
287  NUMERIC('N',size_t)
288 
289 
293  case 'C': {
294  int * out = va_arg(args, int*);
295  if (arg != KWARGS_VAL(0)) {
296  if (!IS_STRING(arg) || AS_STRING(arg)->codesLength != 1) {
297  TYPE_ERROR(str of length 1,arg);
298  goto _error;
299  }
300  *out = krk_unicodeCodepoint(AS_STRING(arg),0);
301  }
302  break;
303  }
304 
305 #ifndef KRK_NO_FLOAT
309  case 'f': {
310  float * out = va_arg(args, float*);
311  if (arg != KWARGS_VAL(0)) {
312  if (!IS_FLOATING(arg)) {
313  KrkClass * type = krk_getType(arg);
314  krk_push(arg);
315  if (!krk_bindMethod(type, S("__float__"))) {
316  krk_pop();
317  TYPE_ERROR(float,arg);
318  goto _error;
319  }
320  arg = krk_callStack(0);
321  }
322  *out = AS_FLOATING(arg);
323  }
324  break;
325  }
326 
330  case 'd': {
331  double * out = va_arg(args, double*);
332  if (arg != KWARGS_VAL(0)) {
333  if (!IS_FLOATING(arg)) {
334  KrkClass * type = krk_getType(arg);
335  krk_push(arg);
336  if (!krk_bindMethod(type, S("__float__"))) {
337  krk_pop();
338  TYPE_ERROR(float,arg);
339  goto _error;
340  }
341  arg = krk_callStack(0);
342  }
343  *out = AS_FLOATING(arg);
344  }
345  break;
346  }
347 #else
348  case 'f':
349  case 'd':
350  krk_runtimeError(vm.exceptions->typeError, "no float support");
351  goto _error;
352 #endif
353 
360  case 'p': {
361  int * out = va_arg(args, int*);
362  if (arg != KWARGS_VAL(0)) {
363  *out = !krk_isFalsey(arg);
364  if (krk_currentThread.flags & KRK_THREAD_HAS_EXCEPTION) goto _error;
365  }
366  break;
367  }
368 
369  default: {
370  krk_runtimeError(vm.exceptions->typeError, "unrecognized directive '%c' in format string", argtype);
371  goto _error;
372  }
373  }
374 
375  krk_pop();
376  oarg++;
377  }
378 
379  if (iarg < argc) {
384  krk_runtimeError(vm.exceptions->argumentError, "%s() takes %s %d argument%s (%d given)",
385  _method_name, required ? "exactly" : "at most", oarg, oarg == 1 ? "" : "s", argc);
386  return 0;
387  }
388 
389  if (!acceptextrakws && hasKw && AS_DICT(argv[argc])->count) {
396  for (size_t i = 0; i < AS_DICT(argv[argc])->capacity; ++i) {
397  KrkTableEntry * entry = &AS_DICT(argv[argc])->entries[i];
398  if (IS_STRING(entry->key)) {
399  krk_runtimeError(vm.exceptions->typeError, "%s() got an unexpected keyword argument '%S'",
400  _method_name, AS_STRING(entry->key));
401  return 0;
402  }
403  }
404  }
405 
406  return 1;
407 
408 _error:
409  krk_pop(); /* name of argument with error */
410  return 0;
411 }
412 
417  const char * _method_name,
418  int argc, const KrkValue argv[], int hasKw,
419  const char * format, const char ** names, ...) {
420  va_list args;
421  va_start(args, names);
422  int result = krk_parseVArgs(_method_name,argc,argv,hasKw,format,names,args);
423  va_end(args);
424  return result;
425 }
426 
KrkValue krk_runtimeError(KrkClass *type, const char *fmt,...)
Produce and raise an exception with a formatted message.
Definition: exceptions.c:445
Type object.
Definition: object.h:189
KrkString * name
Name of the class.
Definition: object.h:193
int krk_bindMethod(KrkClass *_class, KrkString *name)
Perform method binding on the stack.
Definition: vm.c:1693
The most basic object type.
Definition: object.h:41
uint32_t krk_unicodeCodepoint(KrkString *string, size_t index)
Obtain the codepoint at a given index in a string.
Definition: object.c:161
KrkString * krk_copyString(const char *chars, size_t length)
Obtain a string object representation of the given C string.
Definition: object.c:221
char * chars
UTF8 canonical data.
Definition: object.h:97
One (key,value) pair in a table.
Definition: table.h:20
int krk_tableDelete(KrkTable *table, KrkValue key)
Remove a key from a hash table.
Definition: table.c:208
int krk_tableGet_fast(KrkTable *table, struct KrkString *str, KrkValue *value)
Obtain the value associated with a string key in a table.
Definition: table.c:189
int flags
Definition: vm.h:174
Stack reference or primative value.
int krk_isInstanceOf(KrkValue obj, const KrkClass *type)
Determine if a class is an instance or subclass of a given type.
Definition: vm.c:317
KrkClass * krk_getType(KrkValue value)
Get the class representing a value.
Definition: vm.c:275
int krk_isFalsey(KrkValue value)
Determine the truth of a value.
Definition: vm.c:852
Utilities for creating native bindings.
int krk_parseArgs_impl(const char *_method_name, int argc, const KrkValue argv[], int hasKw, const char *format, const char **names,...)
Variable argument version of krk_parseVArgs.
Definition: parseargs.c:416
int krk_parseVArgs(const char *_method_name, int argc, const KrkValue argv[], int hasKw, const char *fmt, const char **names, va_list args)
Validate and parse arguments to a function similar to how managed function arguments are handled.
Definition: parseargs.c:39
Core API for the bytecode virtual machine.
KrkValue krk_callStack(int argCount)
Call a callable on the stack with argCount arguments.
Definition: vm.c:763
#define vm
Convenience macro for namespacing.
Definition: vm.h:267
KrkValue krk_pop(void)
Pop the top of the stack.
Definition: vm.c:170
threadLocal KrkThreadState krk_currentThread
Thread-local VM state.
void krk_push(KrkValue value)
Push a stack value.
Definition: vm.c:157
KrkValue krk_peek(int distance)
Peek down from the top of the stack.
Definition: vm.c:178