Index

APIs

Introduction

Ufy is written in C, and because of this, it can be used in your C program, for example to add scripting abilities. This document describes how you would go about that, in the most common ways.

Approaches

Back to Top

Embedding the ufy interpreter in your application

To embed an ufy interpreter in your application, you must go through various steps. These steps are introduced to allow for various interjections by the using code. First of all, an instance of the interpreter must be created:

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.

Back to Top

Embedding a big main body and executing snippets after that

Back to Top

Caveats of embedding multiple interpreters in your application

Back to Top

Adding your library functionality as functions to ufy

Creating a library for ufy in C is done in a few steps. Those steps require some awareness beforehand. That is:

Creating a C file for your module

Let's say we're creating 'mod_foo.c'. This would be the skeleton of such a file:
#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: The next thing you might want to do, is to learn how to deal with instances and return values. For this, the ufy API provides a type: inst_t. Say we want to return the integer '3' from the function above. We would add the following line just before the 'return 0;' statement:
*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_

Back to Top

Using ufy source code inside C

Sometimes you want to add a source code function to a module which has been otherwise completely implemented natively. Sometimes you want to add a source code method to a class whose implementation has been done completely in C. Mind, you could implement those functionalities natively, however, given the nature of the beast, you would really like to do it in a few lines of ufy source code. Because it's simpler, more concise - it just 'fits' better. It can be done. Here's how:

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.

Back to Top


Author: Kees Jan Hermans
Date: 2006-07-06