Skip to content

Defining New Functions

Vinícius Garcia edited this page May 2, 2017 · 36 revisions

The framework supports a feature to facilitate the declaration of new built-in functions. All of the current built-in functions were declared that way, so if you want some examples you can take a look at builtin-features/functions.h file.

Declaring a new function

To declare a new function you first need a normal C++ function like this one:

packToken fibonacci(TokenMap scope) {
  int N = scope["N"].asInt();
  if (N == 0) return 0;
  if (N == 1) return 1;
 
  scope["N"] = N-1;
  int result = fibonacci(scope).asInt();
  scope["N"] = N-2;
  return result + fibonacci(scope).asInt();
}

Note that the function must return a packToken and receive a TokenMap as argument. The TokenMap will contain all the function arguments, and the TokenMap parent will be the most local scope from where the function is being called.

To create a Function Token you need to use the CppFunction class so that you can store it as a packToken:

packToken my_func = CppFunction(&fibonacci, {"N"}, "");
packToken my_named_func = CppFunction(&fibonacci, {"N"}, "fib_name");

The only difference between a named function and an empty name function is how it shows when you call packToken::str() or try to print them:

std::cout << my_func << std::endl; // [Function]
std::cout << my_named_func << std::endl; // [Function: fibonacci]

To make a function available for the final user you must add it to an accessible map, for instance the default global scope is the most usual place to put them:

struct MyStartupClass {
  MyStartupClass() {
    // This constructor will be executed once before main()
    TokenMap& global = TokenMap::default_global();
    global["fibonacci"] = CppFunction(&fibonacci, {"N"});
  }
// Make sure to write an instance name here:
} MyStartupInstance;

Built-in Function Features

All built-in functions will receive a TokenMap as argument called scope, all the values they will have access to will be stored as key-valued items on this map. A function receives 4 types of arguments:

  1. Named Positional Arguments:

    For each name passed to the CppFunction constructor as argument names, there will be a key-valued item available on the function's scope with that name as would be expected. If the caller of the function does not provide all the required values, those with no value will be set to None.

  2. Extra Positional Arguments:

    If the caller passes more arguments than required by the named positional arguments of the function, the excess will be piled up on a TokenList, and will be made available by the name of "args".

    For example if the fibonacci function described above, was called like this:

    calculator::calculate("fibonacci(10, 1, 2, 3)");

    Its "args" argument would contain the list: [1, 2, 3]

    This feature is useful when declaring functions that accept multiple arguments, like a print() or a sum() function.

  3. Key-word arguments:

    Key-word arguments are different from positional argument in 2 aspects:

    1. The order they are passed is not important

    2. They must be passed as a pair of a string key, representing the argument name, followed by the argument value.

      For example we could call the fibonacci like this:

      calculator::calculate("fibonacci('N': 10)");

    This is useful for functions with many optional arguments, since the caller will often only want to use some of these optional arguments. In that situation he can pass the optional arguments he want by name, and just ignore the ones he does not want.

    Another important aspect of this feature is that it works as an extra documentation. Passing arguments by name may help an unaware reader to better understand what the function is expected to do with that argument.

  4. Extra key-word Arguments:

    If the user passes key-word arguments whose name is not included on the named arguments of the function, they will be stored on a special TokenMap available through the name of "kwargs".

    This is useful in some cases, when the function cannot know in advance the names of the arguments it will receive. It may be hard to imagine such a function but in such cases it is a relief to have this feature to help you.

    As a simple example of such a situation we have the built-in function for constructing maps:

    calculator::calculate("my_map = map('atrb1': 1, 'atrb2': 2)");

    If in a function like fibonacci we may also pass extra key-word arguments like this:

    calculator::calculate("fibonacci('N':10, 'a':1, 'b':2, 'c':3)");

    Although the fibonacci function will make no use of these extra arguments, they would be available inside the kwargs TokenMap.

The this Reference

Besides all the forms of passing arguments to a function there is one more argument that will be always available, its the this reference. The this reference is a reference to the container that contains the function being called.

Note that this must not always be a TokenMap. For a function stored in a TokenList this would be that list, also in functions such as the 'str'.len(), the reference this is set to the respective object, in this case the string 'str'. This kind of function (Type-specific functions) are explained on the next section.

Type-specific functions

Defining type-specific functions is much like defining normal functions, except for 2 important differences:

  1. The function should be stored in the default typeMap of the desired type instead of the default global scope as normal functions would be:

    struct Startup {
      Startup() {
        typeMap_t& typeMap = calculator::type_attribute_map();
        typeMap[STR]["len"] = CppFunction(&str_len, {});
        typeMap[LIST]["len"] = CppFunction(&list_len, {});
        typeMap[MAP]["len"] = CppFunction(&map_size, {});
      }
    } Startup;
  2. Inside the function declaration, this will point to the an element of the specific type instead of a TokenMap:

    packToken str_len(TokenMap scope) {
      // "this" points to an STR token:
      return scope["this"].asString().size();
    }

Ok, and how do I use it?

To use specific type functions, we have added a built-in operation using the operator .:

int strLen = calculator::calculate("'my_str'.len()").asInt();
int listLen = calculator::calculate("list(1,2,3).len()").asInt();

If you want to access them in your language using a different syntax read the Defining New Operations page.