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 zip

5.7 Functions foldr, foldl and fold

5.8 Functions filter, find and filterMap

5.9 Functions forall and exists

5.10 Functions count and countRep

5.11 Functions sort and sortBy

5.12 Functions in and contains

5.13 Lazy Lists

6 Structured Types

6.1 Array Type

6.2 Map Type

6.3 Tuple Type

6.4 Record Type

7 Elements of Wafl Library

7.1 Program Control

7.2 File Reading

7.3 File Writing

7.4 File Operations

7.5 Directory Operations

7.6 Regex Functions

7.7 Command Line

7.8 Web and HTTP Functions

7.9 Wafl to JSON

7.10 Wafl Program Evaluation

8 Parallelization

8.1 Wafl and Parallelization

8.2 Parallel Functions

9 Core Library Reference

9.1 Main Library

9.2 Command Line Library

9.3 Web Library

9.4 XML Library

9.5 Drawing Library (SDL)

9.6 Timer Library

9.7 Edlib Library

10 Command Line Reference

10.1 Command Line Options

10.2 Configuration Files

10.3 ANSI Control Codes

11 Advanced

11.1 JIT

11.2 Wafl Binary Libraries

12 More Content…

12.1 Soon…

 

 

Last update: 29.01.2025.

Wafl

Wafl

Tutorial / 11 - Advanced

Open printable version

11 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.

11.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.

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. The default behavior is to have the JIT optimization on the AEG.

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

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

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. The default behavior is to have the JIT optimization on the AEG.

To enable JIT one of the JIT command line options has to be specified. By default the JIT optimization is disabled.

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

Configuration

The library building process is based on the extern libraries and Wafl Core libraries. To have all the tools available, it is required to have the development environment set properly, or to provide the wafl.ini configuration file in the working directory. Configuration file uses the traditional ini file format, with sections, names and values. The line comments begin with # or //.

Linux Options

For Linux operating system it is assumed that g++ tools are used. JIT configuration options are located in [jit-g++] section. It is usual to have all the general options already set in the command shell environment, so for Linux it is usually enough to se just the Wafl-specific options, like include path and some compiler and linker options.

All the paths are specified without quotes.

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, but the environment is usually already prepared in general. 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/WaflLocal/bld/debug/_install/waflcore/GNU_12.4.0_uxmkfl_x86_64_8_1_CPP20_Debug/include
Linux Compiler Options

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

For example:

compiler-options-1 = -std=c++20 -fpic -ldl -shared 
compiler-options-release = -O3 -DNDEBUG
Linux Linker Options

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

Link options should include all the libraries required to build the JIT module, including the Wafl Core library.

For example:

link-options-1 = ~/dev/WaflLocal/bld/debug/_install/waflcore/GNU_12.4.0_uxmkfl_x86_64_8_1_CPP20_Debug/lib/libWaflCore.a;
Linux Keep Source Files

When JIT build process is completed, the generated C++ source files and command scripts are deleted. To keep the sources in user’s TEMP directory, use configuration option keep-source=1.

A Complete Linux Example

Here is a complete example, for Ubuntu Linux 24.10, g++ and appropriate Wafl Core:

[jit-g++]
include-path-1 = /home/smalkov/dev/WaflLocal/bld/debug/_install/waflcore/GNU_12.4.0_uxmkfl_x86_64_8_1_CPP20_Debug/include

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

link-options-1 = ~/dev/WaflLocal/bld/debug/_install/waflcore/GNU_12.4.0_uxmkfl_x86_64_8_1_CPP20_Debug/lib/libWaflCore.a;

Windows MSVC Options

For Windows operating system it is assumed the MSVC tools are used. JIT configuration options are located in [jit-msvc] section. The options include tools path, include path, library path, compiler options and linker options.

All the paths are specified without quotes.

Windows Tools Search Path

Tools Search Path is a list of directories where compiler and linker are located. It is specified as a value of tools-path parameter. It is usually a single directory, but it can be a list of directories separated by ;. For example:

tools-path = C:\Program Files\Microsoft Visual Studio\2022\Community\VC\Tools\MSVC\14.42.34433\bin\Hostx64\x64
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:\Program Files\Microsoft Visual Studio\2022\Community\VC\Tools\MSVC\14.42.34433\include
include-path-2 = C:\Program Files (x86)\Windows Kits\10\include\10.0.22621.0\ucrt
include-path-3 = E:\Wafl\bld\vs17_release\_install\waflcore\MSVC_143_win64_AMD64_8_1_CPP20_Release\include
Windows Library Search Path

Library Search Path contains a list of directories where C++ libraries are located. It must include all the libraries used to build the JIT extern library, except the ones that are specified with their full absolute path in linker options. The library files search path is specified by using multiple lines with variables named like lib-path-..., or by using a single variable lib-path with directories separated by ;. For example:

lib-path-1 = C:\Program Files\Microsoft Visual Studio\2022\Community\VC\Tools\MSVC\14.42.34433\lib\x64
lib-path-2 = C:\Program Files (x86)\Windows Kits\10\lib\10.0.22621.0\ucrt\x64
lib-path-3 = C:\Program Files (x86)\Windows Kits\10\lib\10.0.22621.0\um\x64
lib-path-4 = E:\Wafl\bld\vs17_release\_install\waflcore\MSVC_143_win64_AMD64_8_1_CPP20_Release\lib
Windows Compiler Options

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

For example:

compiler-options-1 = /nologo /std:c++20 /EHsc /Gd /MP
compiler-options-2 = /Fo: "%TEMP%"
compiler-options-3 = /D _WINDLL /D _ITERATOR_DEBUG_LEVEL=0 /D WIN32 /D _WINDOWS /D _WIN32_WINNT=0x0A00
# compiler-options-debug = /MTd /Od /Ob0
compiler-options-release = /MT /O2
Windows Linker Options

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

Link options should include all the libraries required to build the JIT module.

For example:

link-options-1 = /NOLOGO /DLL /DYNAMICBASE /MACHINE:X64 /LTCG /NOIMPLIB /NOEXP
link-options-2 = libWaflCore.lib 
link-options-3 = kernel32.lib user32.lib gdi32.lib winspool.lib shell32.lib ole32.lib oleaut32.lib uuid.lib comdlg32.lib advapi32.lib
Windows Keep Source Files

When JIT build process is completed, the generated C++ source files and command scripts are deleted. To keep the sources in user’s TEMP directory, use configuration option keep-source=1.

A Complete Window Example

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

[jit-msvc]
tools-path = C:\Program Files\Microsoft Visual Studio\2022\Community\VC\Tools\MSVC\14.42.34433\bin\Hostx64\x64

include-path-1 = C:\Program Files\Microsoft Visual Studio\2022\Community\VC\Tools\MSVC\14.42.34433\include
include-path-2 = C:\Program Files (x86)\Windows Kits\10\include\10.0.22621.0\ucrt
include-path-3 = E:\Wafl\bld\vs17_release\_install\waflcore\MSVC_143_win64_AMD64_8_1_CPP20_Release\include

lib-path-1 = C:\Program Files\Microsoft Visual Studio\2022\Community\VC\Tools\MSVC\14.42.34433\lib\x64
lib-path-2 = C:\Program Files (x86)\Windows Kits\10\lib\10.0.22621.0\ucrt\x64
lib-path-3 = C:\Program Files (x86)\Windows Kits\10\lib\10.0.22621.0\um\x64
lib-path-4 = E:\Wafl\bld\vs17_release\_install\waflcore\MSVC_143_win64_AMD64_8_1_CPP20_Release\lib

compiler-options-1 = /nologo /std:c++20 /EHsc /Gd /MP
compiler-options-2 = /Fo: "%TEMP%"
compiler-options-3 = /D _WINDLL /D _ITERATOR_DEBUG_LEVEL=0 /D WIN32 /D _WINDOWS /D _WIN32_WINNT=0x0A00
# compiler-options-debug = /MTd /Od /Ob0
compiler-options-release = /MT /O2

link-options-1 = /NOLOGO /DLL /DYNAMICBASE /MACHINE:X64 /LTCG /NOIMPLIB /NOEXP
link-options-2 = libWaflCore.lib 
link-options-3 = kernel32.lib user32.lib gdi32.lib winspool.lib shell32.lib ole32.lib oleaut32.lib uuid.lib comdlg32.lib advapi32.lib

11.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.

Notes

Before version 0.6.5 another marshalling mechanism was used. It was based on a transfer buffer, which was used from both sides. Argument were filled-in by Wafl interpreter an used by a library, and result was filled-in by a library and used by interpreter.

For more details please see the version of this file from version 0.6.4.

TODO: marshalling of other types... like List, Wafl functions,...

TODO: in the future, some other compiled programming languages will be supported...

TODO: ClassPrinter returns fString. Which destructor and deallocator are called?

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. The main difference is that library is references using a special declaration:

    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.

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

    regex = library extern '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 -listlib:Regex
    clwafl -listlib:libwRegex
    clwafl -listlib:libwRegex.so

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 fArrayInt;
  • Wafl type Array[Int] corresponds to C++ type fArrayInt.

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, 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, MyClass&& obj )
    {
        TheLibrary.ParentContext().CreateDynamicRecordAt( record, getRecordAttributeNames() );
        TheLibrary.ParentContext().CreateConstIntegerCellAt( record->Elements(), obj.intAttr1 );
        TheLibrary.ParentContext().CreateConstIntegerCellAt( record->Elements() + 1, obj.intAttr2 );
    }
  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 (libCommon, and libMemory).

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.8 © 2021-2025 Saša Malkov            Open printable version