Table of Contents

1 Introduction

1.1 Command Line Interpreter

1.2 Hello World

1.3 Main Wafl Concepts

1.4 Introduction to the Types

2 Program Structure

2.1 Program Is an Expression

2.2 Comments

2.3 Tuples

2.4 Local Definitions

2.5 Function Definition

2.6 Named Expression Definition

2.7 No Variables

2.8 The Order of the Definitions

2.9 Conditional Expression if

2.10 Conditional Expression switch

2.11 Recursion

2.12 Libraries

3 Programming With Functions

3.1 Strict Type Checking

3.2 Automatic Type Inference

3.3 Polymorphism

3.4 Higher Order Functions

3.5 Partial Application

3.6 Lambda Functions

3.7 Lambda Closures

3.8 Operators as Functions

3.9 Dot Operator

3.10 Explicit Computation State

3.11 Cached Functions

4 Primitive Types

4.1 Literals

4.2 Operators

4.3 Conversion Functions

4.4 Integer Functions

4.5 Float Functions

4.6 String Functions

5 List Type

5.1 List Literals

5.2 Basic List Functions

5.3 Basic List Processing

5.4 Advanced List Processing

5.5 More List Functions

5.6 Functions map and mapIdx

5.7 Functions zip and cross

5.8 Functions foldr, foldl and fold

5.9 Selection Functions

5.10 Functions for Finding Elements

5.11 Functions forall and exists

5.12 Functions count and countRep

5.13 Functions sort and sortBy

5.14 Functions in and contains

5.15 Lazy Lists

6 Structured Types

6.1 Array Type

6.2 Map Type

6.3 Tuple Type

6.4 Record Type

7 Databases

7.1 Database Connection

7.2 Queries

7.3 Transactions

8 Elements of Wafl Library

8.1 Program Control

8.2 File Reading

8.3 File Writing

8.4 File Operations

8.5 Directory Operations

8.6 File Operations Error Handling

8.7 Regex Functions

8.8 Command Line

8.9 Web and HTTP Functions

8.10 Wafl to JSON

8.11 Wafl Program Evaluation

9 Parallelization

9.1 Wafl and Parallelization

9.2 Parallel Functions

10 Wafl Library Reference

10.1 Core Library

10.2 Binary Libraries

10.3 Wafl Libraries

11 Command Line Reference

11.1 Command Line Options

11.2 Configuration Files

11.3 ANSI Control Codes

12 Advanced

12.1 JIT

12.2 Wafl Binary Libraries

13 Issues

13.1 Linux Issues

14 More Content…

14.1 Soon…

 

 

Last update: 09.05.2026.

Wafl

Wafl

Tutorial / 12 - Advanced

Open printable version

12 Advanced

Some of the material has been collected from the Wafl Project source files. These files document some features that have not yet been prepared in regular tutorial form. Please forgive us for some technical inconsistencies in these files.

12.1 JIT

Wafl Evaluator provides a basic support for Just In Time compilation. Wafl JIT optimization is based on using the extern binary libraries. The interpreter first analyzes the code and selects the functions that can translate to C++. Then it generates the C++ source file and builds the binary library. The built library is dynamically loaded and linked. The linking process effectively replaces the usual Wafl evaluator nodes with dynamically loaded library nodes.

To use JIT optimization, the user/programmer needs to:

  • enable the JIT optimization and
  • set the configuration for JIT library module build process.

JIT Command Line Options

JIT optimization can be used it two different phases and with some significant differences in the algorithm and the capabilities: on abstract syntax tree (AST) and on abstract evaluation graph (AEG). It cannot be used in both ways at the same time. The default behavior is to have the JIT optimization on the AEG.

By default, the JIT optimization is disabled. To enable JIT, one of the JIT-related command line options must be specified.

  • -jit-ast - Enable JIT optimization on AST;
  • -jit-aeg - Enable JIT optimization on AEG;
  • -jit - Enable default JIT optimization (AEG in current version);
  • -jit-rebuild - Enable default JIT optimization and force a module rebuild.

JIT Build Configuration

The build process is based on external compilation tools and the Wafl Core library. To set up the build configuration, use Wafl’s ini configuration files. The ini configuration is obtained by reading and merging three configuration files, so that the contents of the second override the contents of the third, and the contents of the first override the contents of both the second and the third.

  • .wafl.ini in the current directory;
  • .wafl.ini in the user’s home directory; and
  • wafl.ini in the global program configuration directory (/etc for Linux or %PROGRAMDATA% for Windows).

Linux Options

For Linux, it is assumed that c++ tools are used (g++ or clang can be set). The JIT configuration options are located in the [jit-g++] section of the ini file. The default configuration is expected to work for Linux, so in most cases this section can be left blank.

All the paths are specified without quotes.

Linux Wafl Core Path

It is possible to have multiple versions of the Wafl Core libraries running at the same time. They are usually all located in the /usr/lib/wafl/dev/ directory, and are named like GNU_15.2.0_uxmkfl_x86_64_8_1_CPP20_Release. Each of these versions contains include and lib subdirectories, with the corresponding headers and libraries.

The latest Wafl installation sets a symbolic link /usr/lib/wafl/dev/default to the latest installed Wafl Core library. However, if an older version, or a version located elsewhere, must be used, then it must be configured in the ini files by setting the value of the wafl-core-path variable in the [jit-g++] section:

wafl-core-path = ~/waflcore/GNU_15.2.0_uxmkfl_x86_64_8_1_CPP20_Release

The corresponding include and lib subdirectories will be automatically used.

Linux Include Search Path

Include Search Path contains a list of directories where C++ header files are located. It must include all compiler libraries, OS system libraries and Wafl Core library. The include files search path is specified by using multiple lines with variables named like include-path-..., or by using a single variable include-path with directories separated by ;. For example:

include-path-1 = ~/dev/somelibs/include

These parameters rarely need to be set manually. If the C++ development tools are configured correctly, and the path to the Wafl Core library is set correctly (or the default version is used), then the JIT should not use any other include directory.

Linux Libraries Path

The same applies to Libraries Paths as to Include Paths. The only difference is that the variables are named lib-path-....

Linux Compiler Options

Compiler options are specified using multiple lines with variables named like compiler-options-..., or using a single variable compiler-options. The options are separated by spaces. There are no different configurations for debug and release builds. The same built JIT modules can be used by the Wafl interpreter for both debug and release mode.

For example:

compiler-options-1 = -std=c++20 -fpic -ldl -shared 
compiler-options-release = -O3 -DNDEBUG
#compiler-options-release = -save-temps

Please do not change compiler options unless you are absolutely sure you know what you are doing.

Linux Linker Options

The same applies to linker options as to compiler options. The only difference is that the variables are named link-options-....

Other Options

After the JIT build process is complete, the generated C++ source files and command scripts are deleted. If you need to keep the source files in the user’s TEMP directory, use the keep-source=1 configuration option.

Windows MSVC Options

For Windows operating system it is assumed the MSVC tools are used. JIT configuration options are located in [jit-msvc] section. If paths to the MSVC tools are added to the system paths, then the default configuration is expected to work, and this section can be left blank.

All the paths are specified without quotes.

Windows Tools Search Path

The easiest way to set all the paths is to specify the path to the script that sets all the paths, as a value of the variable msvc-config-bat. For example, for Visual Studio 2022 that should usually be:

msvc-config-bat = C:\Program Files\Microsoft Visual Studio\2022\Community\VC\Auxiliary\Build\vcvarsall.bat

This script will be run, to set all the other MSVC paths (tools, include and lib).

An alternative way to set the path to the compiler tools is to explicitly set the value of the tools-path variable. In that case, the include and lib directories will usually need to be set manually.

Windows Wafl Core Path

It is possible to have multiple versions of the Wafl Core libraries running at the same time. They are usually all located in the ...\Wafl\dev\ directory, and are named like MSVC_143_win64_AMD64_8_1_CPP20_Release. Each of these versions contains include and lib subdirectories, with the corresponding headers and libraries.

The latest Wafl installation sets a directory junction link ...\Wafl\dev\default to the latest installed Wafl Core library. However, if an older version, or a version located elsewhere, must be used, then it must be configured in the ini files by setting the value of the wafl-core-path variable in the [jit-msvc] section:

wafl-core-path = C:\WaflCoreLibs\GNU_15.2.0_uxmkfl_x86_64_8_1_CPP20_Release

The corresponding include and lib subdirectories will be automatically used.

Windows Include Search Path

Include Search Path contains a list of directories where C++ header files are located. It must include all compiler libraries, OS system libraries and Wafl Core library. The include files search path is specified by using multiple lines with variables named like include-path-..., or by using a single variable include-path with directories separated by ;. For example:

include-path-1 = C:\app\somelibs\include

These parameters rarely need to be set manually. If the msvc-config-bat is configured correctly, and the path to the Wafl Core library is set correctly (or the default version is used), then the JIT should not use any other include directory.

Windows Libraries Path

The same applies to Libraries Paths as to Include Paths. The only difference is that the variables are named lib-path-....

Note: Windows SDK libraries are required. If they are installed, the script configured in msvc-config-bat should add them to the paths automatically.

Windows Compiler Options

Compiler options are specified using multiple lines with variables named like compiler-options-..., or using a single variable compiler-options. The options are separated by spaces. There are no different configurations for debug and release builds. The same built JIT modules can be used by the Wafl interpreter for both debug and release mode.

Please do not change compiler options unless you are absolutely sure you know what you are doing.

Windows Linker Options

The same applies to linker options as to compiler options. The only difference is that the variables are named link-options-....

Other Options

After the JIT build process is complete, the generated C++ source files and command scripts are deleted. If you need to keep the source files in the user’s TEMP directory, use the keep-source=1 configuration option.

A Complete Window Example

Here is a complete example, for VS 2022, Windows 11 and appropriate Wafl Core:

[jit-msvc]
msvc-config-bat = C:\Program Files\Microsoft Visual Studio\2022\Community\VC\Auxiliary\Build\vcvarsall.bat
wafl-core-path = C:\Program Files\Wafl\dev\MSVC_143_win64_AMD64_8_1_CPP20_Release

12.2 Wafl Binary Libraries

Wafl Binary Libraries are function libraries developed in C++. The main support for implementation of Wafl Binary Libraries is the libExtern library.

This document describes the libExtern library and the Wafl Binary Library development process. libExtern is pure header library. It consists of two header files:

  • ExternLibInterface.h, which defines a function arguments marshalling interface, and
  • ExternLibBase.h, which defines the library catalog interface and the catalog object itself.

Each C++ source file implementing a binary library should include the first header, but only one source file should and must include the second.

The library loading is implemented in BinaryLibraryDefinition class and method ImportLibrary.

WaflCore

Based on the libExtern library, the WaflCore project is exported as waflcore library. It includes all elements of the Wafl project that are required to build binary libraries out of the main Wafl project.

The usage of the waflcore exported library is similar to the usage of the libExtern library. It consists of:

  • include/waflcore/libExtern.h is a header file, equivalent to ExternLibInterface.h. Ito should be included by each module that implements some parts of the binary library interface;
  • include/waflcore/libExternBase.h is a header file equivalent to ExternLibBase.h. It must be included by one and only one module, because it defines some interface objects;
  • lib/libWaflCore.lib or lib/libWaflCore.a (depending on the platform) is a static library that contains all required elements of the Wafl project.

Glossary

  • binary library - a Wafl library implemented in C++;
  • binary function - a function in a binary library visible from Wafl;
  • binary type - a class defined in a binary library, which may be communicated to Wafl.

1. Using Binary Defined Libraries

Binary defined libraries are used in a similar way like the common Wafl defined libraries. To explicitly declare a binary function, the extern prefix is used:

    myLib = library extern '<library file name>';

The library filename is usually specified without any extension. Each implementation will automatically add the appropriate extension (.dll for Windows and .so for Linux) when loading the library. Moreover, if the library is not found, the interpreter will try to add a prefix libw to the library name. Also, if prefix extern is not specified, the library manager will first try to load a binary library, and if not found, then it will try to load a regular Wafl library.

For example, the library defined in a file libwRegex.so can be declared as:

    regex = library 'Regex';

The library functions are accessed as usual, using :: domain operator:

    ... myLib::fn1 ...
    ... myLib::fn2 ...

To list the content of a binary library, the usual command line interface can be used. Because a large number of extern libraries may exist, the extern library content is listed only if a library name is used as a filter.

For example, to list the contents of a binary library libwRegex.so, one of the following commands can be used:

    clwafl ? Regex
    clwafl ? libwRegex
    clwafl ? libwRegex.so

For more detailed descriptions, use ??:

    clwafl ?? Regex

The import syntax is also available.

2. Defining Functions

Each exported binary function have to have a so called canonical form:

    void aFunction( EvCell* reserved, const EvCell* args )

The first argument reserved is pointer to the preallocated (non-constructed) space where a result will be stored. The second argument args points to an array of function arguments. All arguments and result are objects of EvCell type.

It is, of course, usual to define (or to already have) a function in a regular way, like:

    int fn( int, float, bool )

or like methods in some of the defined classes, like:

    int Type::fn( ... )

The corresponding canonical function can be defined explicitly or automatically. If the canonical function is expected to be built automatically, then some of the following sections are not important. However, if the canonical function is defined explicitly, then it must follow some rules. The rules are explained in the following sections.

3. Defining Canonical Functions

This section is not important if canonical functions are built automatically.

Each canonical function implementation has to do three jobs:

  • to read the arguments values from EvCell objects;
  • to invoke the regular version of the function (or to perform the equivalent processing) and
  • to convert the result to EvCell object.

The conversion of arguments and result is covered in the following sections. It is not too complex, but for the most of the cases it follows the same rules. This is why a wrapper function wrappedFn is provided (as well as a wrapper class FunctionWrapper), to make the wrapping of a regular function easier.

For example, the following expression defines a canonical version of library function for a given regular function fn:

    wrappedFn< decltype(fn), fn >

The wrapper function has the appropriate canonical type. It marshalls all the arguments to the regular function, invokes the function and finally marshalls the result back from a regular form to the EvCell form.

The wrapper function (and the wrapper class) works for functions with argument types supported by getArg template function and result type supported by setResult template function. For other types these template functions have to be specialized, first. See the following sections for details.

4. Arguments

This section is not important if canonical functions are built automatically.

Please consult the section on the supported types.

Each argument is read from the appropriate EvCell object and marshalled to the appropriate expected type. It can be done manually or using function template:

T getArg<T>( const EvCell* arg )

The template is defined for the common types as follows:

  • for any integral type T (including bool):
    • (T) arg->AsInteger()
  • for example, for int:
    • (int) arg->AsInteger()
  • for any floating point type T:
    • (T) arg->AsFloat()
  • for example, for double:
    • (double) arg->AsFloat()
  • for char* and const char*:
    • arg->AsString().c_str()
  • for std::string and std::string&:
    • arg->AsString().asBaseString()
  • for sml::String and sml::String&:
    • arg->AsString()
  • for T*, where T is a library defined class, which is exported from the library:
    • (T*) TheLibrary.ParentContext() .GetObjectFromExternalObjectCell( arg )
  • for T, where T is an internal library defined class, which is not exported from the library, the marshalling uses Wafl records.

To support another type, it is required to define the appropriate specialization of the template function getArg. For example, to support the type A, a specialization like this is required:

    template<>
    A getArg<A>( const EvCell* arg )
    {
        ... 
        return (A) ...;
    }

The new specializations will be used by the wrappers, too.

5. Result

This section is not important if canonical functions are built automatically.

Function result has to be converted to Wafl EvCell objects. It can be done manually or using function template:

    void setResult<T>( EvCell* reserved, T value )

The setResult is defined for the common types based on the CellFactory functions (and wrappers), which are available from the callers context. The supported types include integral types, bool, floating point types, string types (char*, std::string, sml::String) and object pointers.

For example, integer results are marshalled like this:

    TheLibrary.ParentContext()
    .CreateConstIntegerCellAt( reserved, (fInteger) value );

To support another type, it is required to define the appropriate specialization of the template function setResult. For example, to support a type A, a specialization like this is required:

    template<>
    void setResult<A>( EvCell* reserved, A value )
    {
        ... create a cell at reserved ...
        ... with given value of type A ...
    }

The new specializations will be used by the wrappers, too.

6. Function Types

This section is not important if canonical functions are built automatically.

When registering a function, its Wafl type is specified using a type string representation. For example, a function that maps an integer to a float has a type ( Int -> Float ). For more details on the types, see Wafl documents.

A function template is provided to automatically generate the type strings:

    const fString& waflFnTypeName<FN>()

It creates a function type string using another template function to get individual argument or result type name:

    const char* waflSimpleTypeName<T>()

Template function waflSimpleTypeName supports the common types (just like getArg and setResult) and requires further specializations for advanced cases.

7. Supported Arguments and Result Types

7.1 Primitive NonString Types

All primitive types are supported:

  • Wafl Int type size corresponds to build settings and ISA. For 64-bit processors it is signed 64 bit integer. However, any integer type is allowed, but take care of the conversions;
  • Wafl Float type is defined as C++ double type. However, any float type may be used, but take care of the conversions;
  • Wafl Bool type corresponds to C++ bool type.

NOTE: If the canonical versions of the functions and/or the function types are automatically created, then the following additional constraints hold:

  • Type fBool is used as a logical type, almost equivalent to the bool type. However, this type is defined as a 64-bit unsigned integer. If integers are used, please take care not to use 64-bit unsigned integers (including size_t and similar types) for arguments and results, because they may be miss-detected as a logical type.
    • Any other integer type will be correctly detected as integer type.
    • The fInteger type represents 64-bit signed integer.
    • Since Wafl uses only the signed 64-bit integers, this is not a limitation.

7.2 Using String type

Wafl String type is internally defined as fString, which is a synonym for sml::String. They are both defined in the specified header files. Of course, std::string is supported, also.

Because of the different handling of memory allocation in dynamic modules in some environments (in Windows, for example, each DLL may have its own memory heap), the String arguments and results have to be freed in the same module where they are created.

String as argument

So, the String arguments must not be freed in binary function. Moreover, they must not be copied. Please copy only the binary char* representation. This is why String& is better to be used than simple String.

TO DO! The solution is modified and the following sentence is false. Check and improve!

However, if there is a specific case when a String argument has to be destructed, then first the void FreeString() const method must be invoked on the string.

String as result

Using String as result is similar to the argument case. The string itself should be copied. This is why it is better to use char* as result than String.

7.3 Arrays

The following array types are supported:

  • Wafl type Array[Int] corresponds to C++ type fArrayInt;
  • Wafl type Array[Float] corresponds to C++ type fArrayFloat;
  • Wafl type Array[Bool] corresponds to C++ type fArrayBool;
  • Wafl type Array[String] corresponds to C++ type fArrayString.
  • Wafl type Array[Array[Int]] corresponds to C++ type fArrayInt2;
  • Wafl type Array[Array[Array[Int]]] corresponds to C++ type fArrayInt3;
  • …and similar for Float, Bool and String.

Types fArrayInt, fArrayFloat, fArrayBool and fArrayString are interfaces to Wafl arrays.

Arguments of array types must be specified as const pointers, like:

    size_t getArrayLen( const fArrayInt* arr )

Such arguments must not be deleted by library functions.

Array must be returned by pointer. It will be deleted by Wafl evaluator after usage.

    fArrayInt* getRandomArray( int range, int size )

See examples for more details.

  • Wafl type Array[...] corresponds to C++ type fArrayOfCells. It is not recognized automatically and has to be specified manually.

7.4 Records

Records are mapped to C++ classes defined in the library. Such classes are not exported from the library.

As C++ has no reflection features, it is not possible to implement complete marshalling of record/class types automatically. The developers have to provide explicitly the mapping of the types. The mapping is specified by implementing a specialization of the template RecordMarshalling.

To map a class MyClass to a Wafl record type, there are a few requirements to fulfill:

  1. the specialization of class template template<> class RecordMarshalling<MyClass> with four static methods has to be implemented
  2. MyClass WaflToCpp( const WAFL_ExternLib::Record* record ) for conversion of arguments of a Wafl record type to objects of the MyClass class.
    • This method must return a new local object of the type MyClass.
    • The object must be fully initialized.
    • The class MyClass should support a move construction.
    • The typical implementation has the following form:
    static MyClass WaflToCpp( const WAFL_ExternLib::Record* record )
    {
        MyClass obj {
            getArg_safe<int>( record->ElementByName( "intAttr1" )),
            getArg_safe<int>( record->ElementByName( "intAttr2" ))
        };
        return obj;
    }
  1. void CppToWafl( WAFL_ExternLib::Record* record, const MyClass& obj ) for conversion of function results of the MyClass type to a Wafl record type.
    • The method must create a record at the given address record and with the appropriate attribute names.
    • Each of the attributes must be initialized by creating a Wafl cell object at its location. Of course, the types of the objects must conform to the declared type of the attributes.
    • After the usage, the MyClass argument is destroyed.
    • The typical implementation has the following form:
    static void CppToWafl( WAFL_ExternLib::Record* record, const MyClass& obj )
    {
        TheLibrary.ParentContext().CreateDynamicRecordAt( record, getRecordAttributeNames() );
        TheLibrary.ParentContext().CreateConstIntegerCellAt( record->Elements(), obj.intAttr1 );
        TheLibrary.ParentContext().CreateConstIntegerCellAt( record->Elements() + 1, obj.intAttr2 );
    }

3a. In some cases, if the object can be moved to a record, then: * void CppToWafl( WAFL_ExternLib::Record* record, MyClass&& obj ) method should be implemented in a similar way; * void CppToWafl( WAFL_ExternLib::Record* record, const MyClass& obj ) can be implemented to use the other one, like this:

    static void CppToWafl( WAFL_ExternLib::Record* record, const MyClass& obj )
    {
        MyClass tmp = obj;
        CppToWafl( record, std::move(tmp) );
    }
  1. const char* WaflTypeName() returns a textual specification of the corresponding Wafl record type. The usual form is:
    static const char* WaflTypeName() {
        return "Record[ intAttr2: Int, intAttr1: Int ]";
    }
  1. const WAFL_ExternLib::RecordAttributes& getRecordAttributeNames() returns an attribute names handler object. The name specified should be ordered lexically. The usual form is:
    static const WAFL_ExternLib::RecordAttributes& getRecordAttributeNames() 
    {
        static WAFL_ExternLib::RecordAttributes names {
            "intAttr1",
            "intAttr2",
        };
        return names;
    }
  1. In implemented library functions, the MyClass arguments must be passed by value or by r-value reference MyClass&&. The l-value references are not allowed.
  2. In implemented library functions, the MyClass results must be passed by value.

If all the requirements are fulfilled, the functions using MyClass type as an argument or as a result are defined and registered in the same way as any other function.

See examples in testLibExtern project for an example.

7a. Arguments and Result Lifetime

Assume that the arguments will be deleted by caller. If the argument is to be returned, it must be copied.

Assume that the returned object will be deleted by caller after the usage. If an argument is to be returned, or some permanent object, then it must be copied.

8. Exported Types

Any class defined in the binary library may be exported as a binary type. The only requirement is that it has to be described by a corresponding descriptor object in the library object. The descriptor object is created using LibraryClassDescription template class in a form:

    LibraryClassDescription< aType > aTypeDesc( "aTypeName" );

When a function result is a binary type, the result is prepared using SetResult method in the following form:

    data.SetResult( new aType(...), aTypeDesc.GetData() );

Please note:

  1. A returned object must be created dynamically, using new operator. It will be deleted by Wafl evaluator using appropriate destructors and deallocators specified by the provided descriptor.
  2. The type descriptor is used to provide the information on the appropriate destructors and deallocators.
  3. No binary type object may be created or destroyed directly by Wafl evaluator. They may be created in binary library only.
  4. Arguments of binary types constructors should never be deleted by the library; only the caller should delete them. This is important, because if they are provided by Wafl evaluator, then only the evaluator may delete them.

The only required method of the binary type class is fString print() const method, which is used to create a display-ready string representation of the object.

9. Library Catalog

Each binary library must define a catalog of its content. The catalog informs the Wafl evaluator, which uses the library, on the function names, types and implementations.

The catalog has to be implemented by LibraryImplementation::InitLibrary method of a predefined class LibraryImplementation. The class and its singleton object are defined in ExternLibBase.h header file, and the initialization method is implemented by the library.

The InitLibrary method is invoked on an already created LibraryImplementation object and it should populate it with the functions data. The concept is simple and based on idea that each chunk of information is inserted into the catalog using insertion operator <<:

  1. The library name is registered by inserting a const char* :
    *this << "A Binary Library"
  1. The minimal caller version number is registered by inserting a VersionNumber object:
    *this << VersionNumber( 0, 6, 5 )
  1. Each of the exported classes (i.e. library defined Wafl object types) is registered by inserting the corresponding LibraryClassDescription<...> object:
    *this << aBinaryTypeDesc
  1. Each of the exported functions is registered by inserting the corresponding tLibFnData object. It can be exported in its canonical form:
    *this << tLibFnData {
        "maxInt",            //  exported fn. name
        canonicalMaxIntFn,   //  canonical fn. implementation
        "( Integer * Integer -> Integer )", //  fn. type
        "Returns a greater of two numbers." // fn. description
    }

It is easier to use macro LIB_FN_DATA, which creates the canonical version of the function on the fly, and prepares its registration data:

    *this << LIB_FN_DATA(
        "maxInt",            //  exported fn. name
        regularMaxIntFn,     //  regular fn. implementation
        "( Integer * Integer -> Integer )", //  fn. type, using Wafl notation
        "Returns a greater of two numbers." // fn. description
    }

There is also even more advanced macro LIB_FN_DATA_T. It does all the same like the previous one, but also automatically generates the function type name:

    *this << LIB_FN_DATA_T(
        "maxInt",            //  exported fn. name
        regularMaxIntFn,     //  regular fn. implementation
        "Returns a greater of two numbers." // fn. description
    }
  1. The methods of the registered classes can be registered in a similar way. To export a method, an appropriate canonical function has to be provided. When a method is registered, it is used in Wafl as a function, where an appropriate object is added as a first argument. So, a method registration is almost the same as in the case of the functions:
    *this << tLibFnData {
        "aMethod",           //  exported fn. name
        canonicalMethodFn,   //  canonical fn. implementation
        "( TypeName -> Integer )", //  fn. type
        "Maps object to integer"   // fn. description
    }

There are macros to automatically create canonical functions for methods. The first one is LIB_METHOD_DATA, which creates the canonical function for the method on the fly, and prepares its registration data:

    *this << LIB_METHOD_DATA(
        "aMethod",        //  exported fn. name
        TT::method,       //  regular fn. implementation
        "( TypeName -> Integer )", //  fn. type
        "Maps object to integer"   // fn. description
    }

There is also even more advanced macro LIB_METHOD_DATA_T. It does all the same like the previous one, but also automatically generates the function type name:

    *this << LIB_METHOD_DATA_T(
        "aMethod",        //  exported fn. name
        TT::method,       //  regular fn. implementation
        "Maps object to integer"   // fn. description
    }

Note: Static methods are registered as functions, not as methods.

Please see the examples for more details.

10 Headers

A binary library must include libExtern/ExternLibBase.h from Wafl project. This header file defines all the library elements described in this file.

11 Linking

A binary library must link libExtern library from Wafl project. This library already includes the other required Wafl libraries.

12 Examples

Here we present some simple examples. For more information please see the source for testLibExtern project and for libw* sub-projects.

1. A simple unary function

As a first example, we will export a sqrt function. The first option is to define a canonical function:

    void sqrtFnC( EvCell* reserved, const EvCell* args )
    {
        auto result = sqrt( getArg<double>( &args[ 0 ] ) );
        setResult( reserved, result );
    }

This function uses the predefined marshalling template functions getArg and setResult. It may be registered using:

    *this << tLibFnData {
        "sqrt",                 //  exported fn. name
        sqrtFnC,                //  canonical fn. implementation
        "( Float -> Float )",   //  fn. type
        "Returns a square root of the number."
    }

The easier option is to use generic canonical functions, defined by wrappers. However, because the sqrt is overloaded, we should be more explicit on the version, like in:

    inline double sqrtFn( double x ) { return sqrt( x ); }
    auto sqrtFnC = wrappedFn< decltype(sqrtFn), sqrtFn >;

It is easier to use some macros, which do some of the preparations “on the fly”. The basic macro is LIB_FN_DATA, which creates the canonical version and prepares it registration data:

    inline double sqrtFn( double x ) { return sqrt( x ); }
    ...
    *this << LIB_FN_DATA(
        "sqrt",
        sqrtFn,
        "( Float -> Float )",
        "Returns a square root of the number."
    }

Even more advanced is macro LIB_FN_DATA_T. It does all the same like the previous one, but also automatically generates the function type name:

    inline double sqrtFn( double x ) { return sqrt( x ); }
    ...
    *this << LIB_FN_DATA_T(
        "sqrt",
        sqrtFn,
        "Returns a square root of the number."
    }

2. An integer binary function

3. A binary String function

4. A binary type object constructor

This function creates and returns as a result a new binary type object:

    class aBinaryType {
    public:
        aBinaryType( int n ) : Attr_( n ) {...}
        ~aBinaryType() {...}
        int Attr_;
    };

    LibraryClassDescription<aBinaryType> aBinaryTypeDesc( "aBinaryType" );

    aBinaryType* createBinaryObject( int x )
    {
        return new aBinaryType( x );
    }

5. A binary type object as argument

The function uses an object of a binary type as an argument:

    int useBinaryObject( const aBinaryType* obj )
    {
        return obj->Attr_;
    }

6. Array as argument

The function computes the sum of integer array elements:

    fInteger getArraySum( const fArrayInt* arr )
    {
        fInteger sum = 0;
        for( auto i : *arr )
            sum += i;
        return sum;
    }

The life of array argument object is managed by Wafl evaluator. If a pointer to the object is is stored internally by a library, then the evaluator must be informed of that:

    arr->AddReference();

When such rerefence is deleted, teh evaluator must be informed of that, also:

    arr->Free();

7. Array as result

The function creates and returns an array of size random integers in the range [0,range-1]:

    fArrayInt* getRandomArray( int range, int size )
    {
        fArrayInt* arr = (fArrayInt*) TheLibrary.ParentContext().CreateUninitializedArrayCellPtr( size );
        auto set = TheLibrary.ParentContext().CreateConstIntegerCellAt;
        for( auto& i : *arr )
            set( arr->getElementAddr( i ), rand() % range );
        return arr;
    }

8. A library catalog

The library defined in previous examples may have the following catalog:

...
    void LibraryImplementation::InitLibrary()
    {
        *this
            << "A Binary Library"           //  Library name
            << aBinaryTypeDesc              //  A binary type

            //  Functions...
            << tLibFnData{ "sqrt1", sqrtFn,
                            "( Float -> Float )" }
            << tLibFnData{ "maxInt", maxIntFn,
                            "( Integer * Integer -> Integer )" }
            << tLibFnData{ "stringAdd", stringAddFn,
                            "( String * String -> String )" }
            << tLibFnData{ "newBin", createBinaryObject",
                            "( Integer -> aBinaryType )" }
            << tLibFnData{ "useBin", useBinaryObject,
                            "( aBinaryType -> Integer )" };
    }

Wafl Home            Downloads            Wafl Tutorial v.0.6.11 © 2021-2026 Saša Malkov            Open printable version