Yes; you can embrace Greenspun's Tenth Rule and develop a full blown dynamic language in C, and in the process, develop a relatively clean C run time that can be used from within C.
In this project I did just that, as have others before me.
In the C run time of this project, a generic number would be created from a C number like this:
val n = num(42);
because of the way val
is represented, it takes up only a machine word. A few bits of type tag are used to distinguish a number from a pointer, from a character, etc.
There is also this:
val n = num_fast(42);
which is much faster (a bit manipulation macro) because it doesn't do any special checks that the number 42 fits into the "fixnum" range; it's used for small integers.
A function that adds its argument to every element of a vector could be written (very inefficiently) like this:
val vector_add(val vec, val delta)
{
val iter;
for (iter = zero; lt(iter, length(vec)); iter = plus(iter, one)) {
val *pelem = vecref_l(vec, iter);
*pelem = plus(*pelem, delta);
}
return nil;
}
Since plus
is generic, this will work with fixnums, bignums and reals, as well as with characters, since it is possible to add integer displacements to characters via plus
.
Type mismatch errors will be caught by the lower level functions and turned into exceptions. For instance if vec
isn't something to which length
can be applied, length
will throw.
Functions with a _l
suffix return a location. Wherease vecref(v, i)
returns the value at offset i
in vector v
, vecref_l(v, i)
returns a pointer to the val
typed location in the vector which stores that value.
It's all C, just with the ISO C rules bent a little bit: you can't make a type like val
efficiently in strictly conforming C, but you can do it quite portably to architectures and compilers you care about supporting.
Our vector_add
isn't generic enough. It's possible to do better:
val sequence_add(val vec, val delta)
{
val iter;
for (iter = zero; lt(iter, length(vec)); iter = plus(iter, one)) {
val elem = ref(vec, iter);
refset(vec, iter, plus(elem, delta));
}
return nil;
}
By using the generic ref
and refset
, this now works with lists and strings also, not only vectors. We can do something like:
val str = string(L"abcd");
sequence_add(str, num(2));
The contents of str
will change to cdef
since a displacement of 2
is added to each character, in place.