From 6ec67846ffadb5bd1be13d5a7ea3abcf67f1c536 Mon Sep 17 00:00:00 2001 From: Elias Fleckenstein Date: Sat, 1 Jan 2022 17:43:11 +0100 Subject: [PATCH] Redesign function names --- Makefile | 2 +- README.md | 10 ++-- doc/std.md | 54 ++++++++++----------- doc/syntax.md | 6 +-- example/fibo.uwu | 22 ++++----- example/print_args.uwu | 2 +- example/test.uwu | 6 +-- src/load.c | 105 +++++++++++++++++++++++++++++++---------- src/parse.c | 2 +- std/bool.c | 16 +++---- std/int.c | 20 ++++---- std/nil.c | 4 +- std/ref.c | 6 +-- std/str.c | 2 +- 14 files changed, 152 insertions(+), 105 deletions(-) diff --git a/Makefile b/Makefile index 4eb1f9e..ea736fa 100644 --- a/Makefile +++ b/Makefile @@ -1,4 +1,4 @@ -all: uwu std api +all: uwu api uwu: src/*.c src/*.h gcc -g -I. src/*.c -o uwu -D_GNU_SOURCE -Wall -Wextra -ldl diff --git a/README.md b/README.md index c762b4a..bea6e21 100644 --- a/README.md +++ b/README.md @@ -3,11 +3,11 @@ UWU (**U**ltimate p**W**ogwamming lang**U**age) is a **functional**, **interpret ```uwu fibo - :bool:if(:int:smaller($0, 0), 0, - :bool:if(:int:equal($0, 0), 1, - :int:add( - fibo(:int:sub($0, 1)), - fibo(:int:sub($0, 2)) + bool.if(int.smaller($1, 0), 0, + bool.if(int.equal($1, 0), 1, + int.add( + fibo(int.sub($1, 1)), + fibo(int.sub($1, 2)) ))) ``` diff --git a/doc/std.md b/doc/std.md index 2e7720b..681abda 100644 --- a/doc/std.md +++ b/doc/std.md @@ -1,42 +1,42 @@ # Standard library -## The `:bool` module +## The `bool` module -- `:bool:if`: Requires exactly 3 arguments of arbitrary type. If $0 is a truthy value, evaluate and return $1. If $0 is a falsy value, evaluate and return $2. Values considered as falsy are: `:bool:false` and `:nil:nil`. Everything else is considered truthy. -- `:bool:and`: Accepts an arbitrary number of arguments of arbitrary type, but at least one. Returns `:bool:true` if all of the arguments are considered truthy. -- `:bool:or`: Accepts an arbitrary number of arguments of arbitrary type, but at least one. Returns `:bool:true` if at least one of the arguments is considered truthy. -- `:bool:equal`: Accepts an arbitrary number of arguments of arbitrary type, but at least 2. Returns `bool:true` if either all of the arguments are considered truthy or all of the arguments are considered falsy. -- `:bool:not`: Accepts exactly one argument of arbitrary type. Returns `:bool:true` if the $0 is considered falsy, returns `:bool:false` if $0 is considered truthy. -- `:bool:true`: The true constant -- `:bool:false`: The false constant -- `:bool:is`: Accepts an arbitrary number of arguments of arbitrary type, but at least one. Returns `:bool:true` if all arguments are booleans (`:nil:nil` is NOT considered a boolean). +- `bool.if`: Requires exactly 3 arguments of arbitrary type. If $0 is a truthy value, evaluate and return $1. If $0 is a falsy value, evaluate and return $2. Values considered as falsy are: `bool.false` and `nil.nil`. Everything else is considered truthy. +- `bool.and`: Accepts an arbitrary number of arguments of arbitrary type, but at least one. Returns `bool.true` if all of the arguments are considered truthy. +- `bool.or`: Accepts an arbitrary number of arguments of arbitrary type, but at least one. Returns `bool.true` if at least one of the arguments is considered truthy. +- `bool.equal`: Accepts an arbitrary number of arguments of arbitrary type, but at least 2. Returns `bool.true` if either all of the arguments are considered truthy or all of the arguments are considered falsy. +- `bool.not`: Accepts exactly one argument of arbitrary type. Returns `bool.true` if the $0 is considered falsy, returns `bool.false` if $0 is considered truthy. +- `bool.true`: The true constant +- `bool.false`: The false constant +- `bool.is`: Accepts an arbitrary number of arguments of arbitrary type, but at least one. Returns `bool.true` if all arguments are booleans (`nil.nil` is NOT considered a boolean). ## The `:int` module -- `:int:add`: Accepts an arbitrary number of integer arguments (or none at all) and returns the sum of all arguments, or `0` if none were given. -- `:int:sub`: Accepts exactly 2 integer arguments and returns their difference. -- `:int:mul`: Accepts an arbitrary number of integer arguments (or none at all) and returns the product of all arguments, or `1` if none were given. -- `:int:div`: Accepts exactly 2 integer arguments and returns their quotient (rounded towards 0). -- `:int:mod`: Accepts exactly 2 integer arguments and returns the reminder of their division. -- `:int:smaller`: Accepts exactly 2 integer arguments and returns `:bool:true` if $0 is smaller than $1, `:boool:false` else. -- `:int:greater`: Accepts exactly 2 integer arguments and returns `:bool:true` if $0 is greater than $1, `:bool:false` else. -- `:int:equal`: Accepts an arbitrary number of integer arguments, but at least 2. Returns `bool:true` if all the arguments are equal. -- `:int:is`: Accepts an arbitrary number of arguments of arbitrary type, but at least one. Returns `:bool:true` if all arguments are integers. +- `int.add`: Accepts an arbitrary number of integer arguments (or none at all) and returns the sum of all arguments, or `0` if none were given. +- `int.sub`: Accepts exactly 2 integer arguments and returns their difference. +- `int.mul`: Accepts an arbitrary number of integer arguments (or none at all) and returns the product of all arguments, or `1` if none were given. +- `int.div`: Accepts exactly 2 integer arguments and returns their quotient (rounded towards 0). +- `int.mod`: Accepts exactly 2 integer arguments and returns the reminder of their division. +- `int.smaller`: Accepts exactly 2 integer arguments and returns `bool.true` if $0 is smaller than $1, `bool.false` else. +- `int.greater`: Accepts exactly 2 integer arguments and returns `bool.true` if $0 is greater than $1, `bool.false` else. +- `int.equal`: Accepts an arbitrary number of integer arguments, but at least 2. Returns `bool.true` if all the arguments are equal. +- `int.is`: Accepts an arbitrary number of arguments of arbitrary type, but at least one. Returns `bool.true` if all arguments are integers. -## The `:nil` module +## The `nil` module -- `:nil:nil`: The nil constant -- `:nil:is`: Accepts an arbitrary number of arguments of arbitrary type, but at least one. Returns `:bool:true` if all arguments are strings. +- `nil.nil`: The nil constant +- `nil.is`: Accepts an arbitrary number of arguments of arbitrary type, but at least one. Returns `bool.true` if all arguments are strings. -## The `:ref` module +## The `ref` module -- `:ref:call`: Accepts a function reference as $0 and after that and arbitrary number of arguments of arbitrary type. Calls the function $0 with the arguments that follow and returns it's return value. -- `:ref:is`: Accepts an arbitrary number of arguments of arbitrary type, but at least one. Returns `:bool:true` if all arguments are function references. +- `ref.call`: Accepts a function reference as $0 and after that and arbitrary number of arguments of arbitrary type. Calls the function $0 with the arguments that follow and returns it's return value. +- `ref.is`: Accepts an arbitrary number of arguments of arbitrary type, but at least one. Returns `bool.true` if all arguments are function references. -## The `:str` module +## The `str` module -- `:str:cat`: Accepts an arbitrary number of arguments of arbitrary type and returns the concatenation of their string representations. -- `:str:is`: Accepts an arbitrary number of arguments of arbitrary type, but at least one. Returns `:bool:true` if all arguments are strings. +- `str.cat`: Accepts an arbitrary number of arguments of arbitrary type and returns the concatenation of their string representations. +- `str.is`: Accepts an arbitrary number of arguments of arbitrary type, but at least one. Returns `bool.true` if all arguments are strings. ### String representations diff --git a/doc/syntax.md b/doc/syntax.md index fcb30a0..f87b279 100644 --- a/doc/syntax.md +++ b/doc/syntax.md @@ -31,10 +31,10 @@ string literal - Function reference: `&function_name` - Function call: `function_name(args)` or just `function_name`, where args is a comma-separated list of expressions. -**Important:** When passing arguments to a function, these arguments are not passed directly as values, but rather as expressions. Each argument is evaulated as the called function accesses it. This means that you can have constructs that would cause infinite recursion in other languages - a good example for a usecase of that are conditions: `:bool:if(condition, if_true, else)` is just a regular function from the standard library. `if_true` is only evaluated if condition is truey, meaning that if condition is falsey and `if_true` is a function call, that function will not be called. +**Important:** When passing arguments to a function, these arguments are not passed directly as values, but rather as expressions. Each argument is evaulated as the called function accesses it. This means that you can have constructs that would cause infinite recursion in other languages - a good example for a usecase of that are conditions: `bool.if(condition, if_true, else)` is just a regular function from the standard library. `if_true` is only evaluated if condition is truey, meaning that if condition is falsey and `if_true` is a function call, that function will not be called. Function name syntax: - `function`: Call `function` in the current module. -- `path:to:module:function`: Call `function` in the module located at `path/to/module`. That path is relative to the path of the module calling the function. See module paths section under Invocation. -- `:std_module:function`: Call `function` in the module `std_module` from the standard library. +- `.path.to.module.function`: Call `function` in the module located at `path/to/module`. That path is relative to the path of the module calling the function. See module paths section under invocation. +- `std_module.function`: Call `function` in the module `std_module` from the standard library. The location of the standard library is the directory `std` in the path of the `uwu` binary. The standard library can be overwritten; if the environment variable UWU_MODULE_PATH is set, it is used as the standard library path. UWU_MODULE_PATH can also be a list of paths, separated by `:`. In that case, whenever a module from the standard library is requested, the paths is tried in order and the first module found is selected. diff --git a/example/fibo.uwu b/example/fibo.uwu index c3a4aa5..549e048 100644 --- a/example/fibo.uwu +++ b/example/fibo.uwu @@ -1,24 +1,18 @@ -if :bool:if($1, $2, $3) -smaller :int:smaller($1, $2) -equal :int:equal($1, $2) -add :int:add($1, $2) -sub :int:sub($1, $2) - newline " " fibo - if(smaller($1, 0), 0, - if(equal($1, 0), 1, - add( - fibo(sub($1, 1)), - fibo(sub($1, 2)) + bool.if(int.smaller($1, 0), 0, + bool.if(int.equal($1, 0), 1, + int.add( + fibo(int.sub($1, 1)), + fibo(int.sub($1, 2)) ))) print - if(smaller($1, 0), "", - :str:cat( - print(sub($1, 1)), + bool.if(int.smaller($1, 0), "", + str.cat( + print(int.sub($1, 1)), fibo($1), newline )) diff --git a/example/print_args.uwu b/example/print_args.uwu index 593b4be..6705d6c 100644 --- a/example/print_args.uwu +++ b/example/print_args.uwu @@ -1,4 +1,4 @@ -main :str:cat( +main str.cat( $1, $2, $3 diff --git a/example/test.uwu b/example/test.uwu index 081eb69..90ced3f 100644 --- a/example/test.uwu +++ b/example/test.uwu @@ -1,8 +1,8 @@ hello_world "hello world " -main :str:cat( +main str.cat( hello_world, - fibo:print(10), - :nil:nil + .fibo.print(10), + nil.nil ) diff --git a/src/load.c b/src/load.c index e5f6836..4a05915 100644 --- a/src/load.c +++ b/src/load.c @@ -35,31 +35,35 @@ typedef struct typedef struct { - char *path; // path without file extension - char *filename; // path with file extension - char *environment; // directory path + char *path; // path without file extension + char *filename; // path with file extension + char *environment; // directory path - UwUVMModuleType type; // native (.so) or plain (.uwu) + UwUVMModuleType type; // native (.so) or plain (.uwu) - FunctionLink *functions; // required functions - size_t num_functions; // number of required functions - size_t loaded_functions; // number of loaded functions (<= num_functions) + FunctionLink *functions; // required functions + size_t num_functions; // number of required functions + size_t loaded_functions; // number of loaded functions (<= num_functions) union { - AbstractSyntaxTree ast; // abstract syntax tree generated by parser (for plain modules) - void *lib; // dlopen() shared object handle (for native modules) + AbstractSyntaxTree ast; // abstract syntax tree generated by parser (for plain modules) + void *lib; // dlopen() shared object handle (for native modules) } handle; } Module; typedef struct { - Module **modules; // loaded modules - size_t num_modules; // count for modules + Module **modules; // loaded modules + size_t num_modules; // count for modules - char *std_path; // path to standard library + char **module_paths; // module search paths + size_t num_module_paths; // count for module_paths + char *module_paths_str; // module search paths, stringified - Program program; // the result program + char *std_path; // path to standard library + + Program program; // the result program } LoadState; // functions @@ -88,7 +92,7 @@ static inline char *get_filename(const char *module_path) } // module_path is a mallocated string -static Module *require_module(LoadState *state, char *module_path) +static Module *load_module(LoadState *state, char *module_path) { for (size_t i = 0; i < state->num_modules; i++) { Module *module = state->modules[i]; @@ -101,8 +105,10 @@ static Module *require_module(LoadState *state, char *module_path) char *filename = get_filename(module_path); - if (! filename) - error("module error: module %s not found\n", module_path); + if (! filename) { + free(module_path); + return NULL; + } size_t filename_len = strlen(filename); UwUVMModuleType type = (filename_len >= 3 && strcmp(filename + filename_len - 3, ".so") == 0) ? MODULE_NATIVE : MODULE_PLAIN; @@ -163,10 +169,10 @@ static UwUVMFunction *resolve_function(LoadState *state, Module *caller_module, size_t len = strlen(full_name); const char *fnname; - for (fnname = &full_name[len - 1]; *fnname != ':' && fnname > full_name; fnname--) + for (fnname = &full_name[len - 1]; *fnname != '.' && fnname > full_name; fnname--) ; - if (*fnname == ':') + if (*fnname == '.') fnname++; if (*fnname == '\0') @@ -177,12 +183,18 @@ static UwUVMFunction *resolve_function(LoadState *state, Module *caller_module, if (fnname == full_name) { callee_module = caller_module; } else { - const char *caller_path = caller_module->environment; + char **environments = state->module_paths; + size_t num_environments = state->num_module_paths; + char *environments_str = state->module_paths_str; + const char *callee_name = full_name; - if (*callee_name == ':') { - caller_path = state->std_path; + if (*callee_name == '.') { callee_name++; + + environments = &caller_module->environment; + num_environments = 1; + environments_str = caller_module->environment; } size_t path_len = fnname - callee_name; @@ -190,10 +202,15 @@ static UwUVMFunction *resolve_function(LoadState *state, Module *caller_module, for (size_t i = 0; i < path_len; i++) callee_path[i] = (i == path_len - 1) ? '\0' - : (callee_name[i] == ':') ? '/' + : (callee_name[i] == '.') ? '/' : callee_name[i]; - callee_module = require_module(state, asprintf_wrapper("%s/%s", caller_path, callee_path)); + for (size_t i = 0; i < num_environments; i++) + if ((callee_module = load_module(state, asprintf_wrapper("%s/%s", environments[i], callee_path)))) + break; + + if (! callee_module) + error("module error: no module %s in path %s\n", callee_path, environments_str); } return require_function(state, callee_module, fnname); @@ -301,7 +318,6 @@ Program load_program(const char *progname, const char *modname) LoadState state = { .modules = NULL, .num_modules = 0, - .std_path = asprintf_wrapper("%s/std", prog_dirname), .program = { .api_library = dlopen(api_path, RTLD_NOW | RTLD_GLOBAL), .main_function = NULL, @@ -312,10 +328,44 @@ Program load_program(const char *progname, const char *modname) }, }; + char *uwu_module_path = getenv("UWU_MODULE_PATH"); + + if (uwu_module_path) { + char *uwu_module_path_ptr = state.module_paths_str = uwu_module_path; + char *uwu_module_path_base_ptr = uwu_module_path_ptr; + size_t uwu_module_path_len = 1; + + state.num_module_paths = 0; + state.module_paths = NULL; + + for (;; uwu_module_path_ptr++, uwu_module_path_len++) { + if (*uwu_module_path_ptr == '\0' || *uwu_module_path_ptr == ':') { + state.module_paths = realloc(state.module_paths, sizeof(char **) * ++state.num_module_paths); + strncpy(state.module_paths[state.num_module_paths - 1] = malloc(uwu_module_path_len), uwu_module_path_base_ptr, uwu_module_path_len)[uwu_module_path_len - 1] = '\0'; + + uwu_module_path_len = 0; + uwu_module_path_base_ptr = uwu_module_path_ptr + 1; + } + + if (*uwu_module_path_ptr == '\0') + break; + } + } else { + state.module_paths_str = asprintf_wrapper("%s/std", prog_dirname); + state.num_module_paths = 1; + state.module_paths = malloc(sizeof(char **)); + state.module_paths[0] = state.module_paths_str; + } + free(prog_dirname); free(api_path); - state.program.main_function = require_function(&state, require_module(&state, strdup(modname)), "main"); + Module *main_module = load_module(&state, strdup(modname)); + + if (! main_module) + error("module error: requested module %s not found\n", modname); + + state.program.main_function = require_function(&state, main_module, "main"); while (true) { bool fully_loaded = true; @@ -337,7 +387,10 @@ Program load_program(const char *progname, const char *modname) break; } - free(state.std_path); + for (size_t i = 0; i < state.num_module_paths; i++) + free(state.module_paths[i]); + + free(state.module_paths); for (size_t i = 0; i < state.num_modules; i++) { Module *module = state.modules[i]; diff --git a/src/parse.c b/src/parse.c index 66a90a3..bb9bf81 100644 --- a/src/parse.c +++ b/src/parse.c @@ -224,7 +224,7 @@ static bool parse_function(ParseState *state, char c) printf("%s\n", __FUNCTION__); #endif - if (c == '\"' || c == '$' || c == ':' || c == ',' || c == '&' || c == '(' || c == ')' || isdigit(c)) + if (c == '\"' || c == '$' || c == '.' || c == ',' || c == '&' || c == '(' || c == ')' || isdigit(c)) return false; if (! isspace(c)) { diff --git a/std/bool.c b/std/bool.c index 8c8df7d..72a52d3 100644 --- a/std/bool.c +++ b/std/bool.c @@ -12,7 +12,7 @@ static inline bool get_bool_arg(UwUVMArgs *args, size_t i) UwUVMValue uwu_if(UwUVMArgs *args) { - uwuutil_require_exact(":bool:if", args, 3); + uwuutil_require_exact("bool.if", args, 3); return uwuvm_clone_value(get_bool_arg(args, 0) ? uwuvm_get_arg(args, 1) @@ -22,7 +22,7 @@ UwUVMValue uwu_if(UwUVMArgs *args) UwUVMValue uwu_and(UwUVMArgs *args) { - uwuutil_require_min(":bool:and", args, 1); + uwuutil_require_min("bool.and", args, 1); for (size_t i = 0; i < args->num; i++) if (! get_bool_arg(args, i)) @@ -33,7 +33,7 @@ UwUVMValue uwu_and(UwUVMArgs *args) UwUVMValue uwu_or(UwUVMArgs *args) { - uwuutil_require_min(":bool:or", args, 1); + uwuutil_require_min("bool.or", args, 1); for (size_t i = 0; i < args->num; i++) if (get_bool_arg(args, i)) @@ -44,7 +44,7 @@ UwUVMValue uwu_or(UwUVMArgs *args) UwUVMValue uwu_equal(UwUVMArgs *args) { - uwuutil_require_min(":bool:equal", args, 2); + uwuutil_require_min("bool.equal", args, 2); bool value = get_bool_arg(args, 0); @@ -57,23 +57,23 @@ UwUVMValue uwu_equal(UwUVMArgs *args) UwUVMValue uwu_not(UwUVMArgs *args) { - uwuutil_require_exact(":bool:not", args, 1); + uwuutil_require_exact("bool.not", args, 1); return uwubool_create(! get_bool_arg(args, 0)); } UwUVMValue uwu_true(UwUVMArgs *args) { - uwuutil_require_exact(":bool:true", args, 0); + uwuutil_require_exact("bool.true", args, 0); return uwubool_create(true); } UwUVMValue uwu_false(UwUVMArgs *args) { - uwuutil_require_exact(":bool:false", args, 0); + uwuutil_require_exact("bool.false", args, 0); return uwubool_create(false); } UwUVMValue uwu_is(UwUVMArgs *args) { - return uwuutil_is_type(":bool:is", args, &uwubool_type); + return uwuutil_is_type("bool.is", args, &uwubool_type); } diff --git a/std/int.c b/std/int.c index 615d3a5..625595d 100644 --- a/std/int.c +++ b/std/int.c @@ -82,46 +82,46 @@ static long reduce(const char *fnname, UwUVMArgs *args, ReduceOP op, long result UwUVMValue uwu_add(UwUVMArgs *args) { - return uwuint_create(reduce(":int:add", args, ROP_ADD, 0)); + return uwuint_create(reduce("int.add", args, ROP_ADD, 0)); } UwUVMValue uwu_sub(UwUVMArgs *args) { - return uwuint_create(binary(":int:sub", args, BOP_SUB)); + return uwuint_create(binary("int.sub", args, BOP_SUB)); } UwUVMValue uwu_mul(UwUVMArgs *args) { - return uwuint_create(reduce(":int:mul", args, ROP_MUL, 1)); + return uwuint_create(reduce("int.mul", args, ROP_MUL, 1)); } UwUVMValue uwu_div(UwUVMArgs *args) { - return uwuint_create(binary(":int:div", args, BOP_DIV)); + return uwuint_create(binary("int.div", args, BOP_DIV)); } UwUVMValue uwu_mod(UwUVMArgs *args) { - return uwuint_create(binary(":int:mod", args, BOP_MOD)); + return uwuint_create(binary("int.mod", args, BOP_MOD)); } UwUVMValue uwu_smaller(UwUVMArgs *args) { - return uwubool_create(binary(":int:smaller", args, BOP_SML) == 1); + return uwubool_create(binary("int.smaller", args, BOP_SML) == 1); } UwUVMValue uwu_greater(UwUVMArgs *args) { - return uwubool_create(binary(":int:greater", args, BOP_GRT) == 1); + return uwubool_create(binary("int.greater", args, BOP_GRT) == 1); } UwUVMValue uwu_equal(UwUVMArgs *args) { - uwuutil_require_min(":int:equal", args, 2); - return uwubool_create(reduce(":int:equal", args, ROP_EQU, 1) == 1); + uwuutil_require_min("int.equal", args, 2); + return uwubool_create(reduce("int.equal", args, ROP_EQU, 1) == 1); } UwUVMValue uwu_is(UwUVMArgs *args) { - return uwuutil_is_type(":int:is", args, &uwuint_type); + return uwuutil_is_type("int.is", args, &uwuint_type); } diff --git a/std/nil.c b/std/nil.c index 52a2872..cb27dbb 100644 --- a/std/nil.c +++ b/std/nil.c @@ -4,11 +4,11 @@ UwUVMValue uwu_nil(UwUVMArgs *args) { - uwuutil_require_exact(":nil:nil", args, 0); + uwuutil_require_exact("nil.nil", args, 0); return uwunil_create(); } UwUVMValue uwu_is(UwUVMArgs *args) { - return uwuutil_is_type(":nil:is", args, &uwunil_type); + return uwuutil_is_type("nil.is", args, &uwunil_type); } diff --git a/std/ref.c b/std/ref.c index ee18bfc..0147160 100644 --- a/std/ref.c +++ b/std/ref.c @@ -4,17 +4,17 @@ UwUVMValue uwu_call(UwUVMArgs *args) { - uwuutil_require_min(":ref:call", args, 1); + uwuutil_require_min("ref.call", args, 1); UwUVMValue value = uwuvm_get_arg(args, 0); if (value.type != &uwuref_type) - error(":ref:call requires a function reference as $1\n"); + error("ref.call requires a function reference as $1\n"); return uwuvm_call_function(value.data, args->num - 1, &args->unevaluated[1], args->super); } UwUVMValue uwu_is(UwUVMArgs *args) { - return uwuutil_is_type(":ref:is", args, &uwuref_type); + return uwuutil_is_type("ref.is", args, &uwuref_type); } diff --git a/std/str.c b/std/str.c index a0e4edd..29b94db 100644 --- a/std/str.c +++ b/std/str.c @@ -32,5 +32,5 @@ UwUVMValue uwu_cat(UwUVMArgs *args) UwUVMValue uwu_is(UwUVMArgs *args) { - return uwuutil_is_type(":str:is", args, &uwustr_type); + return uwuutil_is_type("str.is", args, &uwustr_type); } -- 2.44.0