oxibts
oxibts

Reputation: 51

Determining type of a void* (and other variables) in C

I am writing a simple Lisp interpreter and have the following structs and enums 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_ts. 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

Answers (2)

Kaz
Kaz

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

Daniel Kleinstein
Daniel Kleinstein

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

Related Questions