Creating your own C library#
In this tutorial we will create a small 2D vector library, to show some of the functions available.
Installation of the source#
First things first you need to clone the latest stable release of Nest by executing:
git clone -b stable https://github.com/TheSilvered/nest.git
cd nest
Then you can compile it from source as specified in the installation documentation.
Here is a little explanation of what all the folders contain:
nest
├── build -- various files to compile Nest and the installers
├── docs -- the documentation
├── examples -- some examples of Nest code
├── include -- the header files for the core interpreter
├── libs -- the standard libraries
├── plugins -- the plugins for SublimeText and VSCode
├── src -- the source of the core interpreter
├── tests -- the tests for the libraries and the interpreter
└── tools -- tools for developing Nest
From here navigate to tools/
where you will find mkclib.nest
. This is a
utility that will generate the necessary files to create your own C library.
mkclib.nest
#
Running mkclib.nest
without any arguments will display this:
$ nest mkclib.nest
Usage: nest mkclib.nest [new <name>|update] <path>
This means that there are two modes to use this utility: the first (new
)
creates a new library whereas update
will update the headers and the shared
objects.
As of now, we are only interested in new
, so let's go ahead and create a new
library called vec_lib
and put it in ~/vec_lib/
.
$ nest mkclib.nest new vec_lib ~/
Copied headers
Updated Windows x86 Debug
Updated Windows x86 Release
Updated Windows x64 Debug
Updated Windows x64 Release
Updated Linux x64/x86 Debug
Updated Linux x86 Release
Updated Linux x64 Release
Created 'vec_lib.sln'
Created 'makefile'
Created 'vec_lib.nest'
Created 'vec_lib.vcxproj'
Created 'vec_lib.vcxproj.filters'
Created 'dllmain.cpp'
Created 'framework.h'
Created 'vec_lib.h'
Created 'vec_lib.cpp'
'vec_lib' created succesfully!
You may also see a message such as this:
'../build/windows/projects/nest/x64/Release/libnest.lib' could not be found
'../build/windows/projects/nest/x64/Release/libnest.dll' could not be found
To add the file(s) build Windows x64 Release and update the library
But not to worry! This depends on what configuration you have compiled, the library will still be created, just keep in mind that if you want to compile it for that configuration as well, you need to compile Nest for it first.
Now that the library has been created you can navigate to ~/vec_lib/vec_lib
to access the source files. dllmain.cpp
and framework.h
are used on Windows
to generate the DLL and vec_lib.vcxproj
and vec_lib.vcxproj.filters
are
project files used by Visual Studio.
Furthermore, if you navigate to ~/vec_lib/nest_source
you will find all the
necessary headers.
Analyzing the generated files#
The other two files inside ~/vec_lib/vec_lib
are vec_lib.h
and vec_lib.cpp
that are the ones that you will want to modify.
vec_lib.h
contains the following:
#ifndef VEC_LIB_H
#define VEC_LIB_H
#include "nest.h"
#ifdef __cplusplus
extern "C" {
#endif // !__cplusplus
NstEXP bool NstC lib_init();
NstEXP Nst_DeclrList *NstC get_func_ptrs();
// NstEXP void NstC free_lib();
Nst_FUNC_SIGN(add);
#ifdef __cplusplus
}
#endif // !__cplusplus
#endif // !VEC_LIB_H
Here, excluding boilerplate code, you will see two three function declarations, the third of which is commented out, and another line to which we'll get to in a minute. These functions are the means that the main interpreter uses to communicate with the library.
lib_init
is the function that is called when the library is first loaded and should initialize the libraryget_func_ptrs
will give to the interpreter all the function and object declarations that the user of the library can accessfree_lib
is optional, as you can see here, and is called when the main interpreter is closing and the library is unloaded, here there should be the cleanup of the things initialized withlib_init
Now we can get to the last line: Nst_FUNC_SIGN(add)
. This is also a function
declaration that would appear like this without the macro:
Nst_Obj *NstC add(usize arg_num, Nst_Obj **args);
Though using the macro is preferred since other macros rely on the correct naming of the arguments.
This kind of function is what can be called by the user of the library, that is why it uses the Nest Function Signature.
Now let's take a look at vec_lib.cpp
:
#include "vec_lib.h"
#define FUNC_COUNT 1 // define here the number of functions inside the library
static Nst_ObjDeclr func_list_[FUNC_COUNT];
static Nst_DeclrList obj_list_ = { func_list_, FUNC_COUNT };
static bool lib_init_ = false;
bool lib_init()
{
usize idx = 0;
func_list_[idx++] = NST_MAKE_FUNCDECLR(add, 2);
#if __LINE__ - FUNC_COUNT != 14 // keep this number as the line after the first
// func_list_[idx++] = ...
#error
#endif
lib_init_ = !Nst_error_occurred();
return lib_init_;
}
Nst_DeclrList *get_func_ptrs()
{
return lib_init_ ? &obj_list_ : nullptr;
}
Nst_FUNC_SIGN(add)
{
i64 a, b;
Nst_DEF_EXTRACT("ii", &a, &b);
return Nst_int_new(a + b);
}
First, let's look at the implementation of lib_init
. The first two lines are
there to make it easier to set the values of func_list_
and to catch any
errors, then line 14 declares the function add
as a variable to be exported
as a function that accepts two arguments.
Now let's address the elephant in the room: #if __LINE__ - FUNC_COUNT != 14
.
This is quite odd but is there to prevent FUNC_COUNT
from being incorrect and
causing a write-out-of-bounds operation because the func_list_
array is too
small. The program will not compile, though, when you add any new statements
above this line but I reckon is more desirable to fail to compile than to crash
at run-time.
Moving our attention at the implementation of get_func_ptrs
; it will probably
never be modified, as a matter of fact all the standard library uses that format.
This said, let's take a look at the implementation of add
. Adding two numbers
is not a hard task: firstly the values passed from Nest are extracted, as they
are in the args
array, and then a new Int
object is created from the sum
of the values of the two arguments. I suggest reading the documentation of
Nst_DEF_EXTRACT
and of the
other functions and macros declared in lib_import.h
as they were originally
thought for external libraries.
Creating the Vec2
type#
The first step to creating this library is to make a new Type
object that will
be assigned to the instances of the new Vec2
objects. To create it let's first
make a global static variable called t_Vec2
of type Nst_TypeObj *
to contain
the new Type
object. Then we can replace the declaration of add
with this
object declaration:
func_list_[idx++] = Nst_MAKE_NAMED_OBJDECLR(t_Vec2, "Vec2");
This creates a new variable when the module is imported with the name Vec2
and
the value of t_Vec2
. But t_Vec2
has not been yet initialized, that is why
we add this line after before the declaration and change line 18:
t_Vec2 = Nst_type_new("Vec2");
The last thing to do is implement free_lib
since we initialized a new object
in lib_init
.
void free_lib()
{
Nst_dec_ref(t_Vec2);
}
Without forgetting to export it in vec_lib.h
.
To test that everything is working, compile it from Visual Studio or with make
and open vec_lib.nest
and replace the contents with
|#| '__C__:vec_lib.cnest' = vec_lib
>>> (vec_lib.Vec2 '\n' ><)
changing vec_lib.cnest
depending on what version of Nest you have installed,
since I have the 64-bit release on Windows I will put x64/Release/vec_lib.dll
.
Now, if you run it you should see Vec2
being printed on the console.
Note
When importing a C library directly you need to add __C__
in front of the
path otherwise Nest will attempt to load it as a .nest
file.
Creating the Vec2
object#
Now we can move on to create the actual Vec2
object. The first thing to do is
creating the struct that will represent it, so in vec_lib.h
add this before
the function declarations:
typedef struct _Vec2Obj
{
Nst_OBJ_HEAD;
f64 x, y;
}
Vec2Obj;
Here, Nst_OBJ_HEAD
adds all the fields to the struct to be able to manage it
as an object and x
and y
are just additional fields.
Then let's declare a new function to create an instance of this object:
Nst_Obj *vec2_new(f64 x, f64 y);
and implement it in vec_lib.cpp
Nst_Obj *vec2_new(f64 x, f64 y)
{
Vec2Obj *vec2 = Nst_obj_alloc(Vec2Obj, t_Vec2, nullptr);
if (vec2 == nullptr)
return nullptr;
vec2->x = x;
vec2->y = y;
return OBJ(vec2);
}
Now that we have the Vec2
object ready, we can start working onto implementing
the functions that the user will be able to call from Nest.
Implementing vec2
and vec2_to_str
#
Now that we can create Vec2
objects in C, we should let the user do it too! To
do this we need a wrapper function for vec2_new
that is callable by Nest
simply called vec2
by adding NST_FUNC_SIGN(vec2);
to vec_lib.h
and
the implementation to vec_lib.cpp
:
Nst_FUNC_SIGN(vec2)
{
f64 x, y;
Nst_DEF_EXTRACT("NN", &x, &y);
return vec2_new(x, y);
}
Now let's make it available to the user by adding this line to lib_init
:
func_list_[idx++] = Nst_MAKE_FUNCDECLR(vec2, 2);
and updating FUNC_COUNT
. Note that the second argument, 2
is the number of
arguments that the function takes.
Now we can try to test it and you should be able to create the vectors but if
you try to print them you will probably be greeted with something like
<Vec2 object at 0x000001B2A47C36F0>
. That is not ideal, and since there is no
way to see its contents let's create another function just for that called
vec2_to_str
. Here I will just show you the implementation and you can add
everything else just like you did for vec2
.
Nst_FUNC_SIGN(vec2_to_str)
{
Vec2Obj *vec2;
Nst_DEF_EXTRACT("#", t_Vec2, &vec2);
return Nst_sprintf("<Vec2 x=%lg y=%lg>", vec2->x, vec2->y);
}
Implementing other functions#
Now that the basic functions are in place, you can start implementing various
functions to get back the x
and y
values, to normalize, to get the length or
the cross product etc... all following the general process showed above.
To see my implementation of the library you can check it in the repository.
Nest wrapper#
After you have implemented all the functions you want to include, you should
create a wrapper to the library to let the user import a .nest
file instead of
the .cnest
one.
Note
In Windows, even though the library is compiled as a .dll
you should
change the extension to .cnest
to allow the .nest
wrapper to be
cross-platform.