Определение типа пустоты* (и других переменных) в C

#c #lisp #void-pointers

Вопрос:

Я пишу простой интерпретатор Lisp и определяю следующие struct s и enum s:

 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 относится к поддерживаемым типам атомов для моего Lisp.
data_t используется для хранения каждого атома. Он используется только в atom_t .
list_t используется для сбора atom_t данных. В нем есть head то, что указывает либо на
atom_t или a list_t (в случае вложенных списков)
atom_t является структурой для атома. Он состоит из атома (хранящегося в data )
, описания его типа ( type ) и a void* . Этот указатель на пустоту может указывать на другое atom_t или на a list_t .

Я разработал его таким образом, чтобы при написании Lisp было более понятно, когда список вложен и каков его родительский/дочерний список. Анализ s-exp всегда даст вам list_t*, потому что весь допустимый код Lisp начинается с открывающей скобки, знака начала списка.

Сейчас я нахожусь на стадии «оценки» Lisp, и eval функция работает следующим образом:

Если next (в a atom_t или a list_t ) указывает на an atom_t , eval этот атом и последующие (т. Е. (set x 10) ). Если next указывает на a list_t , оцените весь этот список, атом за атомом.(т. е. «(набор x (* 5 2))»)

Я разработал его, как описано выше, предполагая, что C предоставляет встроенную type() функцию или isinstance() функцию, которую я мог бы использовать в eval функции-насколько мне известно, это не так. Как бы я имитировал функцию Python isinstance() в C, чтобы я мог сравнивать типы указателей на пустоту? Я хотел бы придерживаться ANSI C, где это возможно, не позднее C99, чтобы максимизировать переносимость.

Ответ №1:

В C нет отражения — это означает, что вы не можете рассуждать о типах переменных языка во время выполнения так же, как в Python.

Что вы можете сделать, так это сохранить тип элемента внутри его структуры — например

 typedef enum {
    DATA,
    ATOM
} element_type;
 

и

 typedef union {
    element_type type;
    ...
} data_t;

typedef struct {
    element_type type;
    ...
} atom_t;
 

а затем проверьте это вот так:

 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;
    ...
}
 

Кроме того, у вас может быть одна структура, которая инкапсулирует как тип, так и void* :

 struct {
    element_type type;
    void* element; // points to data_t* or atom_t* depending on the type
}
 

Факт бонусного удовольствия — это распространенный шаблон в кодовых базах C, один из самых известных примеров которого можно найти в самом Python. Все объекты в CPython являются C PyObjects -закулисными:

 typedef struct _object {
    _PyObject_HEAD_EXTRA
    Py_ssize_t ob_refcnt;
    PyTypeObject *ob_type;
} PyObject;
 

и CPython смотрит на ob_type поле, чтобы узнать, какой тип является общим PyObject .

Ответ №2:

Вы просто должны это сделать:

 typedef enum {
    STRING,
    INTEGER,
    FLOAT,
    FUNCTION,
    VARIABLE,
    SYMBOL,
    LIST,  // Add this!
    NIL
} atom_e;
 

Затем data_t добавьте это:

 typedef union {
    char* string;
    int integer;
    float decimal;
    void *head; // add this, specific to LIST type
} data_t;
 

Таким образом, этот список-всего лишь еще atom_t один . Конечно, тогда этот тип не следует называть atom_t , но value_t или что-то в этом роде: он представляет любое значение: атомы, а также список.

atom_e Должно быть просто type_e : он перечисляет все типы, а не только атомы.

Затем ваша функция оценки должна выполнить

 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
}
 

Итак, есть ваш instanceof ; вы просто смотрите на код типа, который у вас уже есть.

Обратите внимание, что в реальном Lisp нет «переменной» и «символа» как отдельных типов. Переменная-это связь между символом и значением, а не вид объекта. Когда символ оценивается, он понимается как переменная: оценщик ищет в текущей среде привязку, которая названа этим символом.

(Среда Lisp может представлять собой набор объектов привязки , которые имеют внутренний тип VARIABLE , для выполнения косвенного обращения к переменным во время выполнения, но это расширение по сравнению с базовой семантикой Lisp.)