Getting started
Next we'll describe how to create a minimal C module and use it from an Alore program. Then we'll proceed to introduce a few useful functions and concepts. Finally, some miscellaneous important information regarding the API is presented.
Hello module
This C file defines a trivial module that contains a single function Greeting. Greeting accepts no arguments and returns the string "hello, world" when called.
#include <alore/alore.h> AValue Greeting(AThread *t, AValue *frame) { return AMakeStr(t, "hello, world"); } A_MODULE(hello, "hello") A_SUB("Greeting", 0, 0, Greeting) A_END_MODULE()
Next we explain each line of code in the above example.
The first line includes the Alore C API header file:
#include <alore/alore.h>
You must include this file in order to use the Alore C API. It defines all the required functions, types and macros.
All functions that can be called from Alore code have this signature:
AValue Greeting(AThread *t, AValue *frame)
They accept two arguments, of type AThread * and AValue *, and return a value of type AValue. These types and arguments will be discussed later in more detail. At this point, is suffices to say that AValue represents a reference to a single Alore object. The C-level name of the function (in this example, Greeting) is internal to the module and not visible to Alore code, but for clarity it makes sense to keep the internal and public names similar.
The function constructs a single Str object with value "hello, world" and returns it to the caller:
return AMakeStr(t, "hello, world");
Customarily the end of a C module file contains a module description that is introduced with the A_MODULE macro. The arguments to this macro specify the name of the module, in this case hello. The name of the module must be included twice, once without quotes and then quoted. We'll see later the reason for this.
A_MODULE(hello, "hello")
The module description describes the function, class and variable definitions included in the module. The hello module only defines a single function, named "Greeting" (hello::Greeting):
A_SUB("Greeting", 0, 0, Greeting)
The last argument to the macro is a pointer to the corresponding C function that is called to execute the function. The two 0 parameters will be described later in detail, but the first one specifies the number of arguments the function takes (in this case, no arguments).
Finally, the module description must end with the A_END_MODULE() macro. Note that the empty parentheses must be included.
A_END_MODULE()
Compiling and using hello
To use the hello module, you have to compile and link the source code. The details of these tasks depend on your operating system and C compiler.
In Linux, first compile the C file, here assumed to be named hello_module.c, to an object file hello_module.o:
gcc -pthread -fPIC -c hello_module.c
Alore header files must have been installed to a directory that is in the include path of the compiler, or the path must be passed to the C compiler using the -I option.
Then, the linker is used to build a shared library hello.so from the object file:
gcc -pthread -shared -o hello.so hello_module.o
Multiple .o files can be used in this step. Any additional required C libraries can be added using the -l and -L linker options.
The last command generated the hello.so file which contains the linked C module, ready to be used. Now save the following Alore program as testhello.alo in the directory that also contains the hello.so file:
import hello sub Main() WriteLn(Greeting()) end
Finally, run the program normally:
$ alore testhello.alo hello, world
If you see that message, you have successfully compiled and run an Alore C module! These Linux instructions also apply to FreeBSD.
Compiling in Windows
For Windows, you first need to install the MinGW development tools, available at www.mingw.org. Then the module can be compiled in the MinGW shell:
gcc -c hello_module.c gcc -shared -o hello.dll hello_module.o
Note that unlike in Linux, the name of the compiled module file is hello.dll.
Compiling in Mac OS X
Use these commands to compile hello in Mac OS X using gcc:
gcc -c hello_module.c gcc -bundle -undefined dynamic_lookup -o hello.so hello_module.o
You need to have Apple developer tools installed. They are included in the Xcode development environment installation.
Compiling in Solaris
Use these commands to compile hello in Solaris using Sun's cc (select the correct variant of the first command based on your processor architecture):
cc -KPIC -c hello_module.c # Intel cc -xcode=pic32 -c hello_module.c # Sparc cc -G -o hello.so hello_module.o
Accessing function arguments
As briefly mentioned above, you can define the number of arguments a function accepts using a parameter for the A_SUB macro. Let's define a function that increments an integer (this version is a bit limited, but soon you will learn how to implement this properly):
#include <alore/alore.h> AValue Inc(AThread *t, AValue *frame) { int i = AGetInt(t, frame[0]); return AMakeInt(t, i + 1); } A_MODULE(hello, "hello") A_SUB("Inc", 1, 0, Inc) A_END_MODULE()
Three lines in this example require further discussion. First, the A_SUB macro defines a function Inc that takes a single argument, as specified by the second macro argument:
A_SUB("Inc", 1, 0, Inc)
This argument can be accessed in the function implementation as frame[0]. If the function would accept two arguments, the second argument would be frame[1], etc. We use the API function AGetInt to convert the argument to a C int, or raise an exception if it is not possible (we'll describe exceptions later):
int i = AGetInt(t, frame[0]);
Finally, we increment the integer by one, and convert it back to AValue using AMakeInt:
return AMakeInt(t, i + 1);
This example illustrates a typical approach to using the Alore C API: First we convert Alore data to C types (in this case, using AGetInt), then perform some processing with the data using C types, and finally we convert the data back to Alore objects. Performing operations using C types such as int and char is generally much faster than performing the same operations using Alore objects or AValues. But there are also valid reasons for performing the operations using only Alore objects without the conversion steps, and an example is given in the next section.
Storing temporary values
As functions get even slightly more complex than the previous example, it is necessary to generate intermediate AValue values. An important property of the AValue type is that you cannot generally store values of type AValue in local or global C variables — AValues must usually be accessed using pointers to specific locations, since otherwise the garbage collector cannot manage them.
Fortunately it is rather easy to allocate new AValue locations for holding temporary values. The third argument of the A_SUB macro defines the number of additional temporary AValue locations to allocate in the block pointed to by the frame argument. These temporary locations are allocated just after the function arguments.
Let's return to the previous example, the incrementer. Since it converts Alore integers to C int variables, it can only process integers large enough to fit in a C int variable. Alore integers, however, have an unlimited precision, and we would like our Inc function to support them correctly. This can be accomplished by performing the addition using an Alore C API function AAdd which performs the Alore + operation:
AValue Inc(AThread *t, AValue *frame) { frame[1] = AMakeInt(t, 1); return AAdd(t, frame[0], frame[1]); }
We first construct an Int object 1. This is necessary, since AAdd takes two AValue arguments. This object is stored in the temporary location frame[1], and finally the AAdd function is called to add the first function argument and the just created object 1.
We still have to allocate space for the temporary value frame[1]. Otherwise, assigning to frame[1] might corrupt the Alore stack and cause crashes or other problems. We change the third argument of A_SUB to 1 to reflect the fact that we only need a single temporary location:
A_MODULE(hello, "hello") A_SUB("Inc", 1, 1, Inc) A_END_MODULE()
You might wonder if the temporary location is really necessary, since it might seem better to implement the function using a single expression:
return AAdd(t, frame[0], AMakeInt(t, 1)); /* Error */
This is, however, not permitted, but the reason is somewhat non-trivial, and thus it is described only later in section Coexisting with the garbage collector.
More C API functions
This example demonstrates how to create an array object. It creates an array object containing integers 1, 2, ..., n:
AValue Sequence(AThread *t, AValue *frame) { int i; int n = AGetInt(t, frame[0]); frame[1] = AMakeArray(t, n); for (i = 0; i < n; i++) { frame[2] = AMakeInt(t, i + 1); ASetArrayItem(t, frame[1], i, frame[2]); } return frame[1]; } A_MODULE(example, "example") A_SUB("Sequence", 1, 2, Sequence) A_END_MODULE()
Omitting the return value
To signal that a function does not return a meaningful value, you can return ANil, which corresponds to the Alore nil value:
AValue DoNothing(AThread *t, AValue *frame) { return ANil; }
Important C types, constants and functions
These important types are defined in the Alore C API:
- AValue
- This type represents a single Alore object reference. In addition, a few special values (AError, ADefault) are supported. Note that these special values should not be used in all contexts.
- AThread *
- This type represents the state of the current thread. This is an opaque type, and you should not access its internals directly. It can only be passed around and used as an argument to API functions. Each thread has a separate AThread * value.
- ABool
- This is a C-level boolean type, and it is equivalent to int. The
constants TRUE (1) and FALSE (0) are also defined.
Note: Alore booleans are represented as AValues ATrue and AFalse. Be careful not to mix these types.
- AInt64
- 64-bit signed integer type.
- AIntU64
- 64-bit unsigned integer type.
- AWideChar
- 16-bit unsigned integer type that can be used to represent a 16-bit Unicode character.
- Asize_t
- Equivalent to the C size_t type.
- Assize_t
- The signed variant of Asize_t.
Here are descriptions of some AValue constants defined in the API:
- AError
- This value is only used as a return value for functions that return
AValue. It represent the case where an exception was raised in the
function and it was propagated to the caller. Some exceptions
can also be raised as direct exceptions, and in that case the
AError return value is not used. This will be described later in section
Exceptions.
See also: Use AIsError (described below) to check if a value is equal to AError.
- ADefault
- This value is used to represent a missing value for an optional function
argument. Any other value means that the caller provided a value.
See also: Use AIsDefault (described below) to check if a value is equal to ADefault.
- ANil
- This value references the nil object. It also represents a missing return
value (similar to return without a return value expression in Alore
code).
See also: Use AIsNil to check if a value is nil.
- ATrue
- This value references the True object.
See also: Use AIsTrue to check if a value is True.
- AFalse
- This value references the False object.
See also: Use AIsFalse to check if a value is False.
- AZero
- This value represents the number zero. It can be used instead of AMakeInt(t, 0).
These functions are often useful:
- ABool AIsError(AValue v)
- Return a boolean indicating whether v is equal to AError.
- ABool AIsDefault(AValue v)
- Return a boolean indicating whether v is equal to ADefault.
- ABool AIsNil(AValue v)
- Return a boolean indicating whether the argument refers to the nil object.
Note: You should not compare AValues using the C == operator. You should use functions such as AIsError above or AIsEq (described later) to compare Alore values.
Important conventions
All API calls may be implemented as macros, and therefore you should never take pointers to them. Any macros may also evaluate their arguments more than once. Therefore no side effects are allowed in arguments, and code like this is invalid:
return AMakeInt(t, Counter++); /* Error */
It should be replaced with something like this:
int x = Counter++; return AMakeInt(t, x);
The following naming conventions are used in the Alore C API:
- Types, functions and macros that behave more or less like functions are named using CamelCase, and they all start with 'A'.
- Constants and macros that can be used outside functions are named like this: A_CONSTANT_NAME.
- Constant values that can only be used within functions are named like functions. Examples: ANil, AError and ATrue. Even though these might be implemented as C global variables, you must not try to create pointers to them.
- Macros that are used within functions but that do not behave like functions are named like functions, but have a _M suffix. Examples: AGetData_M and ASetData_M.