ExtendingFoment - leftmike/foment GitHub Wiki
Extending Foment
Objects
Objects are represented as void pointers: typedef void * FObject
. Objects can be direct,
meaning that the value of the object is directly stored in the pointer (eg. fixnums, characters,
and booleans). Or objects can be indirect, meaning that the pointer points at the value of the
object (eg. bignums, pairs, strings, etc.). Indirect objects are allocated on the garbage
collected heap.
All objects have a tag indicating their type; for direct objects, the tag is in the low 3 bits of the pointer. For indirect objects, the tag is part of the object header in the heap.
Direct objects are referred to as immediate, and indirect objects as objects.
All of the direct object tags are currently used (see FDirectTag
in
foment.hpp). Adding another
direct object type is possible, but it will take some work.
All objects start with a header (see FObjHdr
in
foment.hpp), located
immediately before the object pointer; see AsObjHdr
. This header contains the tag,
some flags, and the size of the object. Each object contains zero or more references to
other objects (slots) followed by zero or bytes of non-pointer data. Each slot contains
an object (FObject
). All slots must be at the beginning of an object so that the garbage
collector can find them.
Every object needs a tag in the FIndirectTag
enum (which is in
foment.hpp) and an entry
in the IndirectTypes[]
array (which is in
foment.cpp). Each entry
has the name of the type and a function to write objects of that type. The order of
tags and entries must match. Add new tags before FreeTag
.
Allocate new objects using MakeObject
which takes a tag, total size of the object in bytes,
the number of slots, and who is doing the allocation.
Primitives
Primitives are the functions written in C++ which are callable from Scheme. The macro Define
(look for it in foment.hpp) is
used to define a new primitive. It takes the Scheme name for the primitive, followed by the C++
name for the primitive, finally followed by the code for the primitive. The code takes an
argument count and an array of arguments as parameters; each argument is an object (FObject
).
The code must return an object.
In foment.hpp there are a bunch
of inline functions for doing argument count checking and argument type checking. See, for
example, OneArgCheck
and CharacterArgCheck
.
Besides defining a primitive using Define
, the primitive must also be added to the
BedrockLibrary
using DefinePrimitive
. There are a number of Setup
functions that do this
for each of the types; see, for example, SetupVectors
in
vectors.cpp.
Doing all of this will make the primitive available to Scheme code which imports the
(foment bedrock)
library. To make the primitive directly available to users and / or available
to library code which builds on it, export the primitive from the (foment base)
library (see
base.scm).
Standard Libraries
The standard libraries are the libraries that are built into the foment executable.
Add a new standard library as a Scheme source file; see, for example,
srfi-14.scm. In both the Windows
makefile and the Unix
makefile, add the new Scheme
source file to the debug/base.cpp
rule.
Example
Part of the implementation of SRFI 14: Character-set Library will be used as an example.
Add CharSetTag
to the FIndirectTag
enum in
foment.hpp. Add an entry
for the new tag to IndirectTypes[]
in
foment.cpp, making sure
to preserve the same order: {"char-set", WriteCharSet}
. Go back to
foment.hpp and add a
function prototype for WriteCharSet
.
Create a new file, charset.cpp, and add it to both the Windows makefile and the Unix makefile.
#include "foment.hpp"
#include "unicode.hpp"
void WriteCharSet(FWriteContext * wctx, FObject obj)
{
FCh s[16];
long_t sl = FixnumAsString((long_t) obj, s, 16);
wctx->WriteStringC("#<char-set: ");
wctx->WriteString(s, sl);
wctx->WriteCh('>');
}
At this point, foment will build and run, though nothing can be done with char-sets yet.
At the top of charset.cpp, define the C++ type for a char-set, a way to convert an object (remember that they are void pointers) into a pointer to the C++ type, and a type test for char-sets.
typedef struct
{
FCh Start;
FCh End; // Inclusive
} FCharRange;
typedef struct
{
FCharRange Ranges[1];
} FCharSet;
#define AsCharSet(obj) ((FCharSet *) (obj))
#define CharSetP(obj) (IndirectTag(obj) == CharSetTag)
Finally, add a function to make char-sets. Allocate the object, and then
initialize and return it. Note the 0 argument to MakeObject
: that means this
object has zero slots. Look at FHashNode
and MakeHashNode
in
hashtbl.cpp
as an example of an object type which contains both slots and bytes. There are
3 FObject
fields in FHashNode
and 3 is passed as the slot count to
MakeObject
.
static FObject MakeCharSet(ulong_t nr, FCharRange * r, const char * who)
{
FCharSet * cset = (FCharSet *) MakeObject(CharSetTag, sizeof(FCharRange) * nr, 0, who);
memcpy(cset->Ranges, r, sizeof(FCharRange) * nr);
return(cset);
}
Define a type predicate for char-sets as a primitive. And then update LookupTypeTags
in
foment.cpp to return the type
tag for char-sets.
Define("char-set?", CharSetPPrimitive)(long_t argc, FObject argv[])
{
OneArgCheck("char-set?", argc);
return(CharSetP(argv[0]) ? TrueObject : FalseObject);
}
Now define some more primitives which will allow Scheme code to manipulate char-sets. But first add an argument type checking function for char-sets.
inline void CharSetArgCheck(const char * who, FObject obj)
{
if (CharSetP(obj) == 0)
RaiseExceptionC(Assertion, who, "expected a character set", List(obj));
}
Finally, here is the primitive, char-set=
, which checks if zero or more char-sets are equal.
Define("char-set=", CharSetEqualPrimitive)(long_t argc, FObject argv[])
{
if (argc == 0)
return(TrueObject);
CharSetArgCheck("char-set=", argv[0]);
ulong_t nr = NumRanges(argv[0]);
for (long_t adx = 1; adx < argc; adx++)
{
CharSetArgCheck("char-set=", argv[adx]);
if (nr != NumRanges(argv[adx])
|| memcmp(AsCharSet(argv[0])->Ranges, AsCharSet(argv[adx])->Ranges,
nr * sizeof(FCharRange)) != 0)
return(FalseObject);
}
return(TrueObject);
}