Agate aims to be embedded in other applications like games. It has a C API to interact with the runtime environment of the Agate virtual machine.
The whole Agate API is in the agate.h
header. You need to include it to have access to the features of Agate.
#include "agate.h"
Virtual Machine
Creation and Deletion
In order to use Agate in your application, the first step is to create an Agate virtual machine which is represented by a pointer to an AgateVM
structure in C. AgateVM
is an opaque type. The agateNewVM
function gives you a fresh new Agate virtual machine. The agateDeleteVM
function deletes the virtual machines and all the associated ressources.
AgateVM *vm = agateNewVM(NULL);
// do something with vm
agateDeleteVM(vm);
Basic Configuration
NULL
indicates to use a default configuration for the virtual machine. In many cases, that is not what you want. The AgateConfig
structure specifies the configuration of the virtual machine. In this section, we will see some basic configuration settings. Next sections will provide more advanced configuration options. The agateConfigInitialize
initializes a configuration structure.
AgateConfig config;
agateConfigInitialize(&config);
// set the configuration
AgateVM *vm = agateNewVM(&config);
// do something with vm
agateDeleteVM(vm);
Assert handling
Agate provides assertions in the language with the assert
keyword. The behaviour of a failed assertion is determined by assert_handling
in the configuration.
By default, the value of assert_handling
is AGATE_ASSERT_ABORT
. This means that the script aborts if the assertion fails. This is useful when developping the script. You can also choose that the assertion returns nil
instead of stopping the script, with the AGATE_ASSERT_NIL
value. In this case, the script may fail later. Or you can also choose that assertions are totally removed from the script with the AGATE_ASSERT_NONE
value. This should be the configuration in production, when the script has been well tested.
AgateConfig config;
agateConfigInitialize(&config);
config.assert_handling = AGATE_ASSERT_NONE;
Input/Output
By default, Agate does not print anything, you have to provide functions for this.
The print
function is responsible to print a string to the standard output. The simplest implementation of a print
function uses the fputs
standard function. This function is used by IO.print
and related functions.
void print_handler(AgateVM *vm, const char* text) {
fputs(text, stdout);
}
The write
function is responsible to write a single byte to the standard output. It can be used to output binary data to the standard output. The simplest implementation of a write
function uses the fputc
standard function. This function is used by the IO.write
function.
void write_handler(AgateVM *vm, uint8_t byte) {
fputc(byte, stdout);
}
The error
function is responsible to output errors. The function takes the following parameters: the kind of error, the unit name, the line of the error and the error message.
void error_handler(AgateVM *vm, AgateErrorKind kind, const char *unit_name, int line, const char *message) {
switch (kind) {
case AGATE_ERROR_COMPILE:
printf("%s:%d: error: %s\n", unit_name, line, message);
break;
case AGATE_ERROR_RUNTIME:
printf("error: %s\n", message);
break;
case AGATE_ERROR_STACKTRACE:
printf("%s:%d: in %s\n", unit_name, line, message);
break;
}
}
The input
function is responsible for taking the input from the user. The simplest implementation of an input
function uses the fgets
standard functions. This function is used by the IO.input
function.
void input_handler(AgateVM *vm, char *buffer, size_t size) {
fgets(buffer, size, stdin);
}
Finally, you can use these functions in the configuration.
AgateConfig config;
agateConfigInitialize(&config);
config.print = print_handler;
config.write = write_handler;
config.error = error_handler;
config.input = print_handler;
Conversions from string
Agate can be configured to use custom integer conversion from string in parse_int
and float conversion from string in parse_float
. These two functions are used during parsing and in the String.to_i
and String.to_f
functions.
If set to NULL
, a default implementation is provided using strtoll
for parse_int
and strtod
for parse_float
, taking care of locale management. If you use Agate in C++17 (or later), you can use std::from_chars
which may be faster than the default implementation.
// C++17 implementation of parse_int
bool parse_int(const char *text, ptrdiff_t size, int base, int64_t *result) {
int64_t value;
auto [ ptr, ec ] = std::from_chars(text, text + size, value, base);
if (ec == std::errc()) {
*result = value;
return ptr == text + size;
}
*result = std::numeric_limits<int64_t>::max();
return false;
}
// C++17 implementation of parse_float
bool parse_float(const char *text, ptrdiff_t size, double *result) {
double value;
auto [ ptr, ec ] = std::from_chars(text, text + size, value, std::chars_format::general);
if (ec == std::errc()) {
*result = value;
return ptr == text + size;
}
*result = std::numeric_limits<double>::quiet_NaN();
return false;
}
Finally, if you really want to override the defaut implementation, you must change the configuration.
AgateConfig config;
agateConfigInitialize(&config);
config.parse_int = parse_int;
config.parse_float = parse_float;
Executing a simple script
After the virtual machine is configured, you can execute a simple script in a string with agateCallString()
. The return value indicates if the script was executed without errors (AGATE_STATUS_OK
) or if the compilation of the script failed (AGATE_STATUS_COMPILE_ERROR
) or if a runtime error occured (AGATE_STATUS_RUNTIME_ERROR
).
AgateStatus status = agateCallString(vm, "game", "IO.println(\"Start!\")");