Paul-Henri Van der Steichel pvdrstei@vub.ac.be
v1.2, 09 november 2001
I will try to explain how to add a new native to the borgcore code.
The code is written in Ansi-C.
You need to update the cborgcore/Natives.h file as well as one of the cborgcore/Nat*.c files.
In Natives.h you need to add/copy 1 line (like : NATIVE(ADD,"+") THUNK(AD1) THUNK(AD2)\ -> for a + in Pico) and place it under the appropriate subsection (ARITHMETIC, RELATIONAL, TRANSCENDENTAL OPERATORS, IDENTITY, CONVERSION, STRINGS, TABLES, BOOLEAN, ROUTER, AGENTS, OBJECTS, EVALUATOR, FILES, METALEVEL, READER, INTERACTION, DEBUGGING, MEMORY NATIVES, TIME & DATE, NUMBERS, HOOKS, AUXILLIARY).
Then search the right .c file. These always begin with Nat + the subsection where your Native belongs to + .c . So for Arithmetic it would be : NatArithmetic.c . In that file you create the body of your native.
In Natives.h you need to add one line. It will be of the form (for a simple + operator) :
NATIVE(ADD,"+") THUNK(AD1) THUNK(AD2)\
Choose a name. This will be the name of the first thunk. (In this case ADD)
Choose a symbol that you will use in borg. (In this case +)
And you specify how much thunks you need. (In this case 2) (I will explain later why I need 2 extra thunks ).
Be sure not to forget the '\' at the end of the line.
Be sure you add the line in the appropriate subsection (well structured in the Natives.h file).
Every different subsection of natives has a different .c file. So first look for the right file. It always begins with Nat + the name of the subsection (first character is a capital) + .c .
Every thunk has first a little explanation of what is found on the 2 stacks (the EXP and the CNT stack, respectively the expression stack and the continuation stack). And there you add the implementation of your Native.
You can try and implement the body of your native in 1 thunk. Just programming in C what you want your native would do and pushing that result onto the expression stack. This is a bad way because you need to keep every thunk as small as possible. Borg will check every n-times it has evaluated a thunk if the user did something (pressed a key) and needs to do an action (abort the current evaluation for example). So if you make a thunk very large, borg will respond very slow to an user action. Another reason is that we use stacks. These stacks are limited in size. In the beginning of every thunk we can allocate an amount of memory. If we need more memory in the middle of a thunk we have a big problem, because the garbage collector can corrupt data on these stacks. So every time a garbage collection _can_come in between, we need to splits into thunks.
But if you are a good Borg programmer (or you want to be one), then you try to implement your native in a stack-based programming style. What you do is expand your EXP- and your CNT-stack and then compute the needed results.
Here follows an example code for a faculty.
_NIL_TYPE_ FAC(_NIL_TYPE_)
{
UNARY(__FA1, FAC_str);
}A faculty only takes 1 argument as a parameter. That's why we call unary. This will check if there is only one argument and put it on the EXP stack as well as putting 'FA1' on the CNT stack. There is unary (only 1 argument), binary (2 arguments) or unabinary (can take one as well as two arguments). 'FAC_str' will be used for error handling.
_NIL_TYPE_ FA1(_NIL_TYPE_)
{
_EXP_TYPE_ val;
_SGN_TYPE_ sgn;
_mem_claim_();
_stk_peek_EXP_(val);
if (_ag_is_NBR_(val))
{
sgn = _ag_get_NBR_(val);
if (sgn <= 0) _error_str_(_NEG_ERROR_, FAC_str);
if (sgn > 1)
{
_stk_push_EXP_(_ag_make_NBR_(-sgn));
_stk_poke_CNT_(__FA2);
_stk_push_CNT_(__FA1); return;
}
_stk_zap__CNT_();
return;
}
_error_str_(_ATC_ERROR_, FAC_str);
}FA1 will peek1 the (first) argument from the stack and will see that it is a number. This number must be positif and bigger then one. If so it will push this number substracted with one on the EXP-stack, FA2 will be poked on the CNT-stack ('FA1' was still on top and will be overwritten by 'FA2') and FA1 will be pushed on the CNT-stack. So FA1 will be always on top of the stack until the value on top of the EXP stack is 1. Then we ZAP FA1 from this CNT stack. Now n-1 times 'FA2' is found on te CNT stack. Here you see FA1 will expand the stack, so you need a thunk to reduce the stack as well (The reason why we need 2 extra thunks).
_NIL_TYPE_ FA2(_NIL_TYPE_)
{
_EXP_TYPE_ val, val2;
_SGN_TYPE_ sgn, sgn2;
_stk_pop__EXP_(val);
_stk_peek_EXP_(val2);
sgn = _ag_get_NBR_(val);
sgn2 = _ag_get_NBR_(val2);
_stk_poke_EXP_(_ag_make_NBR_(sgn*sgn2));
_stk_zap__CNT_();
}FA2 will the compute the result. So Fa2 will get the 2 top values from the stack and multiply them. This result will be put on top of the stack. One FA2 will be zapped from the CNT-stack. So in the end we will find on top of the EXP-stack the result of the faculty.
When you only have one or two arguments for you native, you can use the function call to UNARY and BINARY. But else, you have to take care of your arguments by yourself. And how do you do that? For zero arguments, it's easy. On the expression stack, you will find an table with no elelments in it. You just pop or peek it and make a check if it's number of elements realy is zero.
Example:
_NIL_TYPE_ SSC(_NIL_TYPE_)
{
_EXP_TYPE_ arg;
_UNS_TYPE_ siz;
_SGN_TYPE_ sock;
_stk_peek_EXP_(arg);
siz = _ag_get_TAB_SIZ_(arg);
if (siz == 0)
{
... and so on ...When you have more than two, it's not realy so much more dificult. The thing is you have to evaluate the elements before you go on. You do that by pushing __EAR on the continuation stack, and this will evaluate the arguments and return a table with them. Then you can work further by making a call to all the elements in the table.
Example:
/*---------------------------------*/
/* SVE */
/* exp-stk: [... ... ... ... ... ARG] -> [... ... ... ... ... EXP] */
/* cnt-stk: [... ... ... ... ... SVE] -> [... ... ... ... SVX EXP] */
/*---------------------------------*/
_NIL_TYPE_ SVE(_NIL_TYPE_)
{
_stk_poke_CNT_(__SV1);
_stk_push_CNT_(__EAR);
}
/*---------------------------------*/
/* SV1 */
/* exp-stk: [... ... ... ... ... ARG] -> [... ... ... FNM DCT RLK] */
/* cnt-stk: [... ... ... ... ... SV1] -> [... ... ... ... SV2 SEr] */
/*---------------------------------*/
_NIL_TYPE_ SV1(_NIL_TYPE_)
{
_EXP_TYPE_ txt, exp, arg, relink;
_stk_peek_EXP_(arg);
exp=_ag_get_TAB_EXP_(arg,1);
relink=_ag_get_TAB_EXP_(arg,2);
if (!_ag_is_DCT_(relink)) goto ERR;
txt=_ag_get_TAB_EXP_(arg,3);
... and so on ...I would also suggest tot take a look at the other natives to see what you need to do.
Sometimes it happends that you do not find an apropriate native definition file to put your new native in. For example, not so long ago, Borg did not support any socket operation. Natives had to be added to make it possible, but there was not really a file were they could belong in. So a new Nat*.c file was created, the NatSockets.c file.
To make your own, it is not difficult. You make the file, with the needed includes and everything (for that, just look in the other files, it is obvious how to do it from that) and add it in de cborgcore directory (don't forget to also add it to the CVS). Then you edit the makefile and put an new filename to the OBJECTS list, like NatSockets.o. Then, compile and hope it works ;o)
Notabene: When you add new natives, it is always a good thing, before you commit your changes to the cvs, to check that everything else is still working (= that you did not mess up the entire program). This can be done by loading (dubbleclicking on it in the open file window) the file CoreTest.cborg, that you find in the directory examples/CoreTest. If it runs without giving warnings, it should mean you didn't mess up everything ;o)
Werner Van Belle
Karsten Verelst
Dirk Van Deun
Cedric Vanrykel