Reputation: 51
I am writing a simple Lisp interpreter and have the following struct
s and enum
s defined:
typedef enum {
STRING,
INTEGER,
FLOAT,
FUNCTION,
VARIABLE,
SYMBOL,
NIL
} atom_e;
typedef union {
char* string;
int integer;
float decimal;
} data_t;
typedef struct {
data_t data;
atom_e type;
void* next;
} atom_t;
typedef struct {
void* head;
} list_t;
atom_e
refers to the supported atom types for my Lisp.
data_t
is used to store each atom. It is only ever used in atom_t
.
list_t
is used to collect atom_t
s. It has head
which points to either an
atom_t
or a list_t
(in the case of nested lists)
atom_t
is the struct for an atom. It consists of an atom (stored in data
)
, a description of its type (type
), and a void*
. This void pointer may point to another atom_t
or a list_t
.
I designed it this way in order to make more clear when writing the Lisp when a list is nested and what its parent/child list is. Parsing a s-exp will always give you a list_t* because all valid Lisp code begins with an opening bracket, the sign of a list starting.
I'm at the "eval" stage of the Lisp now, and the eval
function works as follows:
If next
(in an atom_t
or a list_t
) points to an atom_t
, eval
that atom and the ones that follow (ie. (set x 10)
).
If next
points to a list_t
, eval that entire list, atom by atom.(ie. "(set x (* 5 2))")
I designed it as described above assuming that C provides a built-in type()
or isinstance()
function that I could use in the eval
function - to the best of my knowledge, it doesn't. How would I mimic the function of Python's isinstance()
in C, so that I can compare types of void pointers? I'd like to stick to ANSI C where possible, at the latest C99, in order to maximise portability.
Upvotes: 1
Views: 125
Reputation: 58558
You simply have to do this:
typedef enum {
STRING,
INTEGER,
FLOAT,
FUNCTION,
VARIABLE,
SYMBOL,
LIST, // Add this!
NIL
} atom_e;
Then in data_t
add this:
typedef union {
char* string;
int integer;
float decimal;
void *head; // add this, specific to LIST type
} data_t;
Thereby, the list is just another atom_t
. Of course, then this type should not be called atom_t
but value_t
or something like that: it represents any value: atoms as well as list.
The atom_e
should just be type_e
: it enumerates all types, not only atoms.
Your eval function then has to do
switch (value->type) {
case STRING:
case INTEGER:
case FLOAT:
case FUNCTION:
case NIL:
return value; // these are self-evaluating
case VARIABLE:
// ?
case SYMBOL:
// ?
case LIST:
// implement compound form evaluation here
}
So there is your instanceof
; you just look at the type code you already have.
Note that real Lisp doesn't have "variable" and "symbol" as distinct types. A variable is an association between a symbol and a value, not a kind of object. When a symbol is evaluated, it is understood to be a variable: the evaluator searches the current environment for a binding which is named by that symbol.
(A Lisp environment could be a collection of binding objects that are internally of type VARIABLE
, for doing run-time indirection on variables, but that's an extension over basic Lisp semantics.)
Upvotes: 1
Reputation: 5502
There is no reflection in C - meaning you can't reason with the language types of variables at runtime the same way you can in Python.
What you can do is store the type of element inside its struct - e.g.
typedef enum {
DATA,
ATOM
} element_type;
and
typedef union {
element_type type;
...
} data_t;
typedef struct {
element_type type;
...
} atom_t;
and then check it like this:
void* element;
element_type type = *((element_type*)element);
if (type == DATA) {
data_t* data = (data_t*)element;
....
} else if (type == ATOM) {
atom_t* atom = (atom_t*)element;
...
}
Alternatively, you can have a single struct that encapsulates both a type and a void*
:
struct {
element_type type;
void* element; // points to data_t* or atom_t* depending on the type
}
Bonus fun fact - this is a common pattern in C codebases, with one of the most famous examples found in Python itself. All objects in CPython are C PyObjects
behind the scenes:
typedef struct _object {
_PyObject_HEAD_EXTRA
Py_ssize_t ob_refcnt;
PyTypeObject *ob_type;
} PyObject;
and CPython looks at the ob_type
field to know which type a generic PyObject
is.
Upvotes: 1