Написание интерпретатора Scheme с использованием FPC: распределение и указатели
Будучи историком, написание интерпретатора Scheme в FPC уже на первом этапе оказывается для меня серьезной задачей.:) Я слежу за блогом Питера Мишо, который показал, как это сделать на C (есть также перевод на Ada, который может быть полезен для Pascal).
Рассмотрим эти две функции в C из работы Мишо (v 0.1):
object *alloc_object(void) {
object *obj;
obj = malloc(sizeof(object));
if (obj == NULL) {
fprintf(stderr, "out of memory\n");
exit(1);
}
return obj;
}
object *make_fixnum(long value) {
object *obj;
obj = alloc_object();
obj->type = FIXNUM;
obj->data.fixnum.value = value;
return obj;
}
Насколько я понимаю (просто базовые знания по чтению на С), конструктор make_fixnum
возвращает указатель на структуру (помеченные данные типа fixnum); для построенного объекта должна быть выделена память (спасибо @David Heffernan за его точку зрения вчера).
Это мой перевод на FPC, который компилируется без ошибок:
program scheme;
type
TTag = (ScmFixnum);
PScmObject = ^TScmObject;
TScmObject = record
case ScmObjectTag: TTag of
ScmFixnum: (ScmObjectFixnum: integer);
end;
var Test: PScmObject = nil;
procedure AllocateObject(x: PScmObject);
begin
new(x);
end;
function MakeFixnum(x: integer): PScmObject;
var
fixnum: PScmObject = nil;
begin
AllocateObject(fixnum);
fixnum^.ScmObjectTag := ScmFixnum;
fixnum^.ScmObjectFixnum := x;
MakeFixnum := fixnum;
end;
begin
Test := MakeFixnum(1);
writeln(Test^.ScmObjectTag);
writeln(Test^.ScmObjectFixnum);
end.
Тем не мение...:
$ ./test
Runtime error 216 at $080480DD
$080480DD
$08048117
$08063873
Я подозреваю, что есть серьезный недостаток в том, как я использую и ссылаюсь на указатели.
Большое спасибо всем, кто помогает мне понять, как работает этот указатель и память (также приветствуются ссылки на часто задаваемые вопросы, статьи и т. Д.).
1 ответ
Ваша функция AllocateObject неверна. Он создает новый объект в переменной x, но не передает созданный объект вызывающей функции, поскольку он вызывается по значению. Если вы измените соглашение о вызовах, оно работает:
procedure AllocateObject(out x: PScmObject);
begin
new(x);
end;
Вы можете увидеть, если вы посмотрите на переменную fixnum в отладчике, она останется нулевой.
Не связанный с вашим вопросом, я не думаю, что это хорошая идея использовать записи в переводчике. Скоро это превращается в кошмар управления памятью (по крайней мере, это произошло в интерпретаторе, который я написал, когда он приблизился к 20 kloc, и мне пришлось заменить записи следующим образом:)
Вместо вашей записи
PScmObject = ^TScmObject;
TScmObject = record
case ScmObjectTag: TTag of
ScmFixnum: (ScmObjectFixnum: integer);
end;
Вы можете использовать классы, такие как:
TScmObject = class()
function Tag: TTag; virtual; abstract;
function Fixnum: integer; virtual; abstract;
end;
TScmObjectFixNum = class(TScmObject)
function Tag: TTag; override;
function Fixnum: integer; override;
private
value: integer;
end;
function TScmObjectFixNum.Tag: TTag;
begin
result := ScmFixnum;
end;
function TScmObjectFixNum.Fixnum: integer;
begin
result := value;
end;
Тогда вы создаете это легко с
var x: TScmObject;
x := TScmObjectFixNum.create() ;
if x.tag = scmfixnum (* or x is TScmObjectFixNum *) then
... x.scmfixnum ...
x.free
Если в реализации вашей схемы нет циклических ссылок, вы даже можете использовать интерфейсы. Тогда это ссылка подсчитывается и автоматически освобождается:
IScmObject = interface
function Tag: TTag;
function Fixnum: integer;
end;
TScmObject = class(TInterfacedObject, IScmObject)
function Tag: TTag; virtual; abstract;
function Fixnum: integer; virtual; abstract;
end;
TScmObjectFixNum = class(TScmObject)
function Tag: TTag; override;
function Fixnum: integer; override;
private
value: integer;
end;
var x: IScmObject;
x := TScmObjectFixNum.create() ;
if x.tag = scmfixnum (* or x is TScmObjectFixNum *) then
... x.scmfixnum ...
//x.free no longer necessary (or allowed)!