char* path; ufy_t* ufy = ufy_new(NULL); ufy_config(ufy, path);
'path' may be NULL, or may be a string, referring to a location on the filesystem. If it is NULL, however, it will try to autoconfigure from known locations. If ufy_config isn't called at all, that means no configuration is prepended when the actual script arrives.
ufy_set_script(ufy_t* ufy, char* path, char* source); ufy_set_script_path(ufy_t* ufy, char* path);
Through these functions, we set the interpreter up with a script. In the first variety, 'path' may be NULL, but source may not be. The path is just auxillary here. In the second variety, 'path' may obviously not be NULL; it points to the place of the script on the filesystem.
mainthrd_t* mainthread = mainthread_new(NULL, ufy);
Here we create a new main-thread. A main-thread is the top of a tree of threads that run through the static ufy context. The only thing left to do now is running it:
inst_t* instance = NULL; thrd_run(mainthread, &instance);
When this function returns, the script has finished running. The 'instance' contains the resulting value of the script.
#include "ufy.h" static int mod_foo_decl (ctx_t* ctx, char* name) { return 0; } static int mod_foo_var (thrd_t* thread, char* name) { return 0; } void ufy_module_init (native_t* module, char* name, char* use) { module->load_declarations = mod_foo_decl; module->load_variables = mod_foo_var; }
The gist of it is this: you fill a pointed-to structure with functors that will, on various occasions, be called to provide the static and dynamic contexts related to the execution of an ufy script. The 'declarations' are called when the module is loaded, the 'variables' are called when the main thread starts running through the script. Now we have to fill one, or both, of those functions with our module-specific stuff. Like so:
static int mod_foo_fnc_bar (thrd_t* thread, lst_t* params, inst_t** result) { return 0; } static int mod_foo_decl (ctx_t* ctx, char* name) { ufy_add_function(ctx, "bar", mod_foo_fnc_bar); return 0; }
Again, this is a skeleton. This time for the native 'bar' function. Now, let's make this function 'bar' actually do something. Let's say it's a function that accepts either a single 'int' as a parameter, or a single 'string', and just do some reporting:
static int mod_foo_fnc_bar (thrd_t* thread, lst_t* params, inst_t** result) { switch (check_params_alt(params, 2, 1, INSTYPE_STRING, 1, INSTYPE_INT)) { case 0: fprintf(stderr, "You've passed a string !\n'%s'\n", PARAM_STRING(params,0)); break; case 1: fprintf(stderr, "You've passed an int !\n'%d'\n", PARAM_INT(params,0)); break; default: return thrd_pusherr(thread, ERR_PARAM, "bar: Need string or int"); } return 0; }The function implementation above contains four elements which are part of the ufy API, and which need further explanation:
*result = inst_new_int(3);The following instance instantiators are at your disposal:
inst_t* inst_new_void(); inst_t* inst_new_byte(unsigned char c); inst_t* inst_new_int(int i); inst_t* inst_new_float(double d); inst_t* inst_new_string(char* str); /* must be a malloced pointer */ inst_t* inst_
int mod_foo_decl (ctx_t* ctx, char* name) { ctx_add_function( ctx, ufy_new_sfunction( "join", "function join(string s, list l) {" " var result = string(pop(l));" " foreach (; elt; l) {" " result += s + string(elt);" " } " "}" ) ); }
The C code above is the implementation of a module; it's declaration callback, to be more precise. Inside it, we pass a string to the ufy_new_sfunction function, which parses it, and wraps it inside an ufy fnc_t* type.
There are a few conditions to doing this: you should always provide the complete function declaration. You should always use a 'function' declaration, even though you're actually creating a method to a class. If you think the string may have faulty syntax, you should check the return value of the ufy_new_sfunction function - it will return NULL if there are any errors. You may use the private modifier prefix in the declaration.