-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
```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))
)))
```
# 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
- 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.
-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
))
-main :str:cat(
+main str.cat(
$1,
$2,
$3
hello_world "hello world
"
-main :str:cat(
+main str.cat(
hello_world,
- fibo:print(10),
- :nil:nil
+ .fibo.print(10),
+ nil.nil
)
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
}
// 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];
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;
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')
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;
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);
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,
},
};
+ 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;
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];
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)) {
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)
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))
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))
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);
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);
}
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);
}
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);
}
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);
}
UwUVMValue uwu_is(UwUVMArgs *args)
{
- return uwuutil_is_type(":str:is", args, &uwustr_type);
+ return uwuutil_is_type("str.is", args, &uwustr_type);
}