Gauche:APIUpdate

Gauche:APIUpdate

This page summarizes planned changes of Gauche C API in release 0.9 and later.

Introduction

As of developing Gauche 0.8.x we're reviewing Gauche C API so that it will be consistent and flexible to allow future extension. We aim at fixing API&ABI on release 0.9; before that we don't avoid radical, incompatible changes, and we alerady did a few. Sorry for the inconvenience, but we believe it's worth the pain to have stable and consistent API in later versions.

To ease the transition, those incompatible changes will not be visible unless you #define specific preprocessor symbols in 0.8.x. Upon release of 0.9 we will reverse the condition, i.e. the new API will be visible by default and you have to #define specific preprocessor symbols to get the backward compatibility. And eventually (sometime in 0.9.x) we'll drop the backward compatibility APIs.

We encourage the extension developers to adapt to those changes before 0.9 (by enabling new APIs with preprocessor symbols), so that it will keep working fine after 0.9 and later. It is also good if you find serious flaws in the new API before 0.9 so that we can fix them.

Those preprocessor symbols must be defined before you #include gauche.h. It is a bit tricky for *.stub files, since "#include <gauche.h>" line is inserted automatically before any code you write in *.stub files. The trick is to give -DSYMBOL option to genstub, or --cppflags="-DSYMBOL" to gauche-package; for example, to enable all the new APIs introduced in 0.8.10, change your makefile to call genstub as "genstub -DGAUCHE_API_0_8_10" or call "gauche-package compile" as "gauche-package compile --cppflags=-DGAUCHE_API_0_8_10".

Eval and apply

Changes are introduced in 0.8.8. The new API becomes visible if you define either one of GAUCHE_API_0_8_8, or GAUCHE_API_0_9.

Old API

ScmObj Scm_Eval(ScmObj form, ScmObj env)
ScmObj Scm_EvalCString(const char *sform, ScmObj env)
ScmObj Scm_Apply(ScmObj proc, ScmObj args)

The problem of original API is that (1) it's not easy to catch errors generated during those functions, (2) there's no clean way to retrieve multiple values if the given expression results more than one, and (3) evaluating select-module special form changes the current module, but there's no easy way for the caller to know if it is changed.

New API

int Scm_Eval(ScmObj form, ScmObj env, ScmEvalPacket *packet);
int Scm_EvalCString(const char *form, ScmObj env, ScmEvalPacket *packet);
int Scm_Apply(ScmObj proc, ScmObj args, ScmEvalPacket *packet);

Those functions now returns the number of result values (>= 0), or -1 for an error. The actual results, as well as the thrown condition for an error, is stored in ScmEvalPacket:

typedef struct ScmEvalPacketRec {
    ScmObj results[SCM_VM_MAX_VALUES];
    int    numResults;
    ScmObj exception;
    ScmModule *module;          /* 'Current module' after evaluation */
} ScmEvalPacket;

The non-error-capturing version are also provided as the following names ('Rec' stands for recursive, since they enter VM recursively).

ScmObj Scm_EvalRec(ScmObj form, ScmObj env)
ScmObj Scm_EvalCStringRec(const char *sform, ScmObj env)
ScmObj Scm_ApplyRec(ScmObj proc, ScmObj args)

These behave exactly in the same way as the old API's Scm_Eval etc.

How to change your extension code

The easiest way is to change Scm_Eval to Scm_EvalRec etc. It's just fine if your call to Scm_Eval is done during VM is active (e.g. calling it in the body of subr), since the error raised in Scm_EvalRec etc. are handled by the running VM.

If you're calling Scm_Eval at the application's toplevel, where there's no VM running, consider using the new Scm_Eval so that you can handle errors easily.

String ports

Changes are introduced in 0.8.10. The new API becomes visible if you define either one of GAUCHE_API_0_8_10 or GAUCHE_API_0_9.

Old API

ScmObj Scm_GetOutputString(ScmPort *port);
ScmObj Scm_GetOutputStringUnsafe(ScmPort *port);
ScmObj Scm_GetRemainingInputString(ScmPort *port);

New API

ScmObj Scm_GetOutputString(ScmPort *port, int flags);
ScmObj Scm_GetOutputStringUnsafe(ScmPort *port, int flags);
ScmObj Scm_GetRemainingInputString(ScmPort *port, int flags);

New "flags" argument are added for extensibility.

How to change your extension code

Just pass 0 to the flags argument and they behave the same way as old API.

Hashtables

Changes are introduced in 0.8.10. The new API becomes visible if you define either one of GAUCHE_API_0_8_10 or GAUCHE_API_0_9.

Old API

ScmHashEntry *Scm_HashTableGet(ScmHashTable *hash, ScmObj key)
ScmHashEntry *Scm_HashTableAdd(ScmHashTable *hash, ScmObj key, ScmObj value)
ScmHashEntry *Scm_HashTablePut(ScmHashTable *hash, ScmObj key, ScmObj value)
ScmHashEntry *Scm_HashTableDelete(ScmHashTable *ht, ScmObj key)

void          Scm_HashIterInit(ScmHashTable *table, ScmHashIter *iter)
ScmHashEntry *Scm_HashIterNext(ScmHashIter *iter)

ScmHashEntry *Scm_HashTableGetRaw(ScmHashTable *hash, void *key)
ScmHashEntry *Scm_HashTableAddRaw(ScmHashTable *hash, void *key, void *value)
ScmHashEntry *Scm_HashTablePutRaw(ScmHashTable *hash, void *key, void *value)
ScmHashEntry *Scm_HashTableDeleteRaw(ScmHashTable *ht, void *key)

Originally we planned ScmHashTable to keep only ScmObjs as keys and values; later we found we wanted to keep non-ScmObjs as well, and added a bunch of "*Raw" APIs. It was somewhat awkward and confusing to use the same structure (ScmHashTable) to use different purposes (Raw hashtables and ScmObj hashtables).

New API

We splitted "raw" hashtables and ScmObj hashtables into layers, the latter being built on top of the former. The "raw" hashtable is just a C struct, not ScmObj, and we call it ScmHashCore. ScmHashTable embeds ScmHashCore, and restricted to keep ScmObjs as keys and values.

There's another motivation for the change: As of 0.8.10 we have another build-in data structure that associates keys to values--- ScmTreeMap, which uses a balanced binary tree. And we thought it's better to have consistent APIs for those two.

void Scm_HashCoreInitSimple(ScmHashCore *core, ScmHashType type, unsigned int initSize, void *data)
void Scm_HashCoreCopy(ScmHashCore *dst, const ScmHashCore *src)

ScmDictEntry *Scm_HashCoreSearch(ScmHashCore *core, intptr_t key, ScmDictOp op)

void Scm_HashIterInit(ScmHashIter *iter, ScmHashCore *core)
ScmDictEntry *Scm_HashIterNext(ScmHashIter *iter)
ScmDictEntry *Scm_HashIterCurrent(ScmHashIter *iter)

ScmObj Scm_HashTableRef(ScmHashTable *ht, ScmObj key, ScmObj fallback)
ScmObj Scm_HashTableSet(ScmHashTable *ht, ScmObj key, ScmObj value, int flags)
ScmObj Scm_HashTableDelete(ScmHashTable *ht, ScmObj key)

ScmDictEntry is a common structure for key-value pair, and used in both hashtables and treemaps:

typedef struct ScmDictEntryRec {
    const intptr_t key;
    intptr_t  value;
} ScmDictEntry;

See the doc/gauche-dev.texi for the detailed description of those new APIs.

How to change your extension code

We keep Scm_HashTableGet, ScmHashTableAdd, and ScmHashTablePut for a while, since they don't conflict with the new APIs. (however, we need to cast ScmDictEntry* to ScmHashEntry*, which isn't possible on rare platforms where sizeof(intptr_t) != sizeof(void*). On those platforms you have to move to the new API immediately).

The new Scm_HashTable{Ref|Set} functions will cover the most cases of the original Scm_HashTable{Get|Add|Put}, so you can gradually rewrite the latter by the former.

Some existing code changes the value field of the ScmHashEntry obtained by the original APIs. To port them to the new API you have to fall back to ScmHashCore API, which is a little bit verbose.

/* Old */
ScmObj new_value();
ScmHashEntry *e = Scm_HashTableAdd(ht, key, SCM_FALSE)
if (SCM_FALSEP(e->value)) {  /* if this is a new entry */
  e->value = new_value();
}

/* New */
ScmObj new_value();
ScmDictEntry *e = Scm_HashCoreSearch(SCM_HASH_TABLE_CORE(ht), 
                                     (intptr_t)key, SCM_DICT_CREATE)
if (!e->value) {   /* e->value == 0 for a new entry */
  SCM_DICT_SET_VALUE(e, new_value());
}

We hope this case is not very common; if it turns out otherwise, we might add convenience APIs for this pattern.

We dropped support of the original Scm_HashTableDelete, which returned ScmHashEntry*, assuming few code was using the its return value. The new one returns the original entry value or SCM_UNBOUND if the entry didn't exist.

We also dropped *Raw APIs entirely, assuming they weren't used outside Gauche's internal code.

The iterator API conflicts the old one. You have to rewrite as follows:

/* Old */
ScmHashIter iter;
ScmHashEntry *e;

Scm_HashIterInit(ht, &iter);

while ((e = Scm_HashIterNext(&iter)) != NULL) {
  do_something(e->key, e->value);
}

/* New */
ScmHashIter iter;
ScmDictEntry *e;

Scm_HashIterInit(&iter, SCM_HASH_TABLE_CORE(ht));

while ((e = Scm_HashIterNext(&iter)) != NULL) {
  do_something(SCM_DICT_KEY(e), SCM_DICT_VALUE(e));
}

Load and Require

Changes are introduced in 0.8.10. The new API becomes visible if you define either one of GAUCHE_API_0_8_10 or GAUCHE_API_0_9.

Old API

void   Scm_LoadFromPort(ScmPort *port, int flags);
int    Scm_Load(ScmPort *port, int flags);
ScmObj Scm_Require(ScmObj feature);

They have a similar problem as Scm_Eval etc., i.e. it is not easy for applications to catch errors.

New API

int Scm_LoadFromPort(ScmPort *port, int flags, ScmLoadPacket *result);
int Scm_Load(ScmPort *port, int flags, ScmLoadPacket *result);
int Scm_Require(ScmObj feature, int flags, ScmLoadPacket *result);

How to change your extension code

To make the new API behaves as the old API, pass SCM_LOAD_PROPAGATE_ERROR for flags and NULL for result.

The old Scm_Require always returned SCM_TRUE, so I assume nobody is using the result value.


Last modified : 2012/02/02 12:26:19 UTC