сборка мусора — Как безопасно преобразовать древовидные структуры данных между C ++ / Ocaml?

У меня есть устаревшая структура данных, написанная на C ++, и новый инструмент в OCaml, который, как ожидается, будет работать с этими устаревшими данными. Поэтому мне нужно импортировать / переводить данные с первого на второе. Данные представлены в виде дерева и обычно обрабатываются посетителями.

В качестве простого примера рассмотрим этот минимальный DSL:

#include <memory>

using namespace std;

class intnode;
class addnode;

struct visitor {
virtual void visit(const intnode& n) = 0;
virtual void visit(const addnode& n) = 0;
};

struct node {
virtual void accept(visitor& v) = 0;
};

struct intnode : public node {
int x;

virtual void accept(visitor& v) { v.visit(*this); }
};

struct addnode : public node {
shared_ptr<node> l;
shared_ptr<node> r;

virtual void accept(visitor& v) { v.visit(*this); }
};

Его представление в OCaml выглядит так:

type node = Int of int
| Plus of node * node

let make_int x = Int x
let make_plus l r = Plus(l,r)

Вопрос в том, как безопасно и эффективно преобразовать дерево C ++ в его представление OCaml?

Пока у меня есть два подхода:

Подход 1

Написать посетителю, который вызывает конструкторы OCaml и выдает valueнапример, что-то вроде этого:

value translate(shared_ptr<node> n);

struct translator : public visitor {
value retval;

virtual visit(const intnode& n) {
retval = call(make_int, Val_int(x->value));
}

virtual visit(const addnode& n) {
value l = translate(n.l);
value r = translate(n.r);
retval =  call(make_add, l, r);
}
};

value translate(shared_ptr<node> n)
{
translator t;
t.visit(*n);
}

Просто предположим, что call делает все необходимые леса для обратного вызова в OCaml и вызова правильного конструктора.

Проблема с этим подходом — сборщик мусора от OCaml. Если GC работает, в то время как сторона C ++ имеет некоторые value Если это стек, то это значение (которое является указателем на кучу OCaml) может быть недействительным. Поэтому мне нужен какой-то способ сообщить OCaml о том, что значения все еще нужны. Обычно это делается с помощью макросов CAML *, но как мне это сделать в таком случае? Могу ли я использовать эти макросы внутри visit методы?

Подход 2

Второй подход более сложный. Когда нет способа безопасно хранить промежуточные ссылки, я мог бы изменить ситуацию и вставить указатели C ++ в кучу OCaml:

type cppnode (* C++ pointer *)

type functions = {
transl_plus : cppnode -> cppnode -> node;
transl_int : int -> node;
}

external dispatch : functions -> cppnode -> node = "dispatch_transl"
let rec translate n = dispatch {transl_plus; transl_int = make_int} n

and transl_plus a b = make_plus (translate a) (translate b)

Идея заключается в том, что функция «dispatch» обернет все подузлы в структуры CustomVal и передаст их OCaml без сохранения каких-либо промежуточных значений. Соответствующий посетитель будет реализовывать только сопоставление с образцом. Это должно четко работать с GC, но имеет недостаток: он немного менее эффективен (из-за обтекания указателя) и потенциально менее читабелен (из-за различия между диспетчеризацией и реконструкцией).

Есть ли способ получить безопасность подхода 2 с элегантностью подхода 1?

5

Решение

Я не вижу проблем в построении значений OCaml на стеке C даже в рекурсивном случае. В вашем примере вы используете элемент структуры для хранения значения кучи OCaml. Это также возможно, однако, вам нужно использовать caml_register_global_root или же caml_register_generational_root и выпустить их с caml_remove_global_root или же caml_remove_generational_global_root, Фактически, вы можете даже создать умный указатель, который будет содержать значения OCaml.

Учитывая все вышесказанное, я до сих пор не вижу никаких причин (по крайней мере для упрощенного примера, который вы продемонстрировали), почему вы должны идти к ученикам для этого, вот как я бы это решил:

struct translator : public visitor {

virtual value visit(const intnode& n) {
CAMLparam0();
CAMLlocal1(x);
x = call(make_int, Val_int(n->value);
CAMLreturn(x);
}

virtual value visit(const addnode& n) {
CAMLparam0();
CAMLlocal(l,r,x);
l = visit(*n.l);
r = visit(*n.r);
x = call(make_add, l, r);
CAMLreturn(x);
}
};

Это, конечно, предполагает, что у вас есть посетитель, который может возвращать значения произвольных типов. Если у вас его нет и вы не хотите его реализовывать, то вы определенно можете наращивать свое значение постепенно:

value translate(shared_ptr<node> n);

class builder : public visitor {
value result;
public:
builder() {
result = Val_unit; // or any better default
caml_register_generational_global_root(&result);
}

virtual ~builder() {
caml_remove_generational_global_root(&result);
}

virtual void visit(const intnode& n) {
CAMLparam0();
CAMLlocal1(x);
x = call(make_int, Val_int(n->value);
caml_modify_generational_global_root(&result, x);
CAMLreturn0;
}

virtual void visit(const addnode& n) {
CAMLparam0();
CAMLlocal(l,r,x);
l = translate(n.l);
r = translate(n.r);
x = call(make_add, l, r);
caml_modify_generational_global_root(&result,x)
CAMLreturn0;
}
};

value translate(share_ptr<node> node) {
CAMLparam0();
CAMLlocal1(x);
builder b;
b.visit(*node);
x = b.result;
CAMLreturn(x);
}

Вы также можете посмотреть на Berke Durak’s зубр проект, который создает синтаксический анализ деревьев на месте с использованием C.

2

Другие решения

Лично я написал бы дампер в C ++ и парсер этого дампа в OCaml.
Если вас не пугает более сложный маршрут, возможно, вы можете взглянуть на этот инструмент:
https://github.com/Antique-team/clangml

0

По вопросам рекламы [email protected]