#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.)