我想知道 C 或 C++ 中的 Prolog 实现会是什么样子。我主要对将其构建为 C 或 C++ 库感兴趣,尽管解释器应用程序也可以。我有兴趣阅读它的内部结构,即查询执行,即找到解决方案和相关的数据类型。如果您向我推荐任何有关主题的读物或任何直接的建议/建议,我会很高兴。读数可能适用于其他 OOP 语言或一般 OOP。大多数令人筋疲力尽的材料将解决这个问题。
4 回答
如果您想了解在 C/C++ 中如何将用 C 实现的 Prolog 系统用作库,请查看SWI-Prolog。它提供了一个完全双向的接口,包括 Unix/Mac/Window 的非确定性——等等。想想约束。
另一方面,您也在询问其实际实施。有两种方法可以解决这个问题。您可以从最底层开始,逐步提高自己的水平。或者你可以从 Prolog 开始,从在 Prolog 中实现 Prolog 的元解释器开始。从这里你可以慢慢挖掘到血块。
传统的方法是首先从最底层的问题开始,研究各种抽象机器。最常被引用的一种是 WAM(沃伦抽象机),然后是 您不应该错过的 WAM 的替代品。做好准备,从这到一个有效的ISO 实施将需要很长的路要走。有许多问题在文献中只是粗略地处理,例如垃圾收集和约束。然而,它们是稳健实施所必需的。
另一种方法是先学习 Prolog,然后详细研究元解释器。通过这种方式,您可能会学会从完全不同的角度看待 Prolog。而且您可能还会获得其他方式无法获得的见解。你可以从经典的三子句元解释器开始,它重用了 Prolog 的大部分功能。然后,根据您的兴趣,您可以开始具体化其中的一部分。好消息是您(就代码大小而言)几乎只为您想要挖掘的部分付费并重用该语言的其他部分。
至少在过去,这种方法导致了各种新的实现技术,例如约束、Erlang、二进制 Prolog 都首先作为“简单”元解释器存在。只有这样,在理解了语言问题之后,才完成了实际的实现。
还有一点支持先从 Prolog 开始:如果你在它中间停止努力,会发生什么?使用自下而上的方法,您最终会得到一组已失效的代码。对于第二种方法,您已经学习了 Prolog。
前段时间,我用 C++(真的,我的第一个 C++ 程序)编写了一个 Prolog 解释器,并采用了不同的方法,而不是(现在几乎无处不在的)WAM。我们的语言和编译器构建课程的老师谈到了一个 ABC 算法,我实现了它(我看到了“Prolog 实现 ABC 算法”,这里是我找到的 PDF,但我还不知道):这里是核心求解器
//--------------------------
// evaluation core of query
// use algorithm ABC
//
int IntlogExec::query(const Clause *q)
{
unsigned nc = 0;
ProofStack::Env *pcn, *pfn;
stkpos cn, fn;
#define PCN (pcn = ps->get(cn))
#define PFN (pfn = ps->get(fn))
UnifyStack us(vs, ts);
if (!q)
goto C;
fn = ps->push(STKNULL);
PFN->vspos = vs->reserve(q->get_nvars());
pfn->trail = ts->curr_dim();
pfn->dbpos = 0;
pfn->call = 0;
// current query is empty?
A: if (!q->get_body()) {
// search untried calls
A1: //fn = ps->curr_dim() - 1;
fn = cn;
ProofStack::Env *e = ps->get(fn);
while (e->father != STKNULL) {
if (tr && e->call && tr->exit(fn, e->call))
return -1;
if (e->call && !e->call->is_last())
break;
e = ps->get(fn = e->father);
}
if (e->father == STKNULL)
return 1;
// set call to next untried brother
cn = ps->push(PFN->father);
PCN->call = pfn->call->next();
pcn->vspos = pfn->vspos;
fn = pfn->father;
} else {
cn = ps->push(fn);
PCN->call = q->get_body();
}
A2: PFN;
pcn->dbpos = 0;
cc = pcn->call;
if (nc++ == ncycle)
{
nc = 0;
sighandler();
}
// trace the call
if (tr && tr->call(cn, cc))
return -1;
switch (cc->get_type()) {
case CallData::BUILTIN: {
BuiltIn *btp = cc->get_builtin();
pcn->trail = ts->curr_dim();
pcn->vspos = pfn->vspos;
// if evaluate OK
if (btp->eval(cc->args(), this, 0)) {
// if (tr && tr->exit(cn, cc))
// return -1;
// if (btp->retry || pcn->call->last())
// goto A1;
// pcn->call = pcn->call->next();
// goto A2;
goto A1;
}
PCN;
if (tr && tr->fail(cn, pcn->call))
return -1;
unbind(pcn->trail);
}
goto C1;
case CallData::CUT: {
stkpos gf = PFN->father;
if ( gf != STKNULL &&
pfn->call->is_last() &&
pfn->call == pcn->call->next()) {
// tail recursion optimization
ProofStack::Env *pgf = ps->get(gf);
pgf->vspos = pfn->vspos;
ASSERT(!pcn->call->is_last());
slist_iter s(tmpt);
ElemTmp *t;
while ((t = (ElemTmp*)s.next()) != 0 && t->spos > fn)
t->spos = fn;
CallData *cproc = pcn->call;
cn = ps->pop(cn - fn) - 1;
PCN->call = cproc->next();
fn = pcn->father;
goto A2;
}
pcn->trail = ts->curr_dim();
pcn->vspos = pfn->vspos;
}
goto A1;
case CallData::DISJUNCT: // replace with catenated try
pcn->vspos = pfn->vspos;
pcn->trail = ts->curr_dim();
cn = ps->push(fn);
PCN->call = cc->next(); // left side
goto A2;
case CallData::DBPRED:
// initialize DB search
pcn->dbpos = db->StartProc(cc->get_dbe());
// DB matching & unification
B: if (pcn->dbpos && (q = pcn->dbpos->get()) != 0) {
unsigned nvars = q->get_nvars();
pcn->vspos = vs->reserve(nvars);
pcn->trail = ts->curr_dim();
/*
if (!unify( pfn->vspos, cc->args(),
pcn->vspos, q->h_args(), q->h_arity()))
*/
if (q->h_arity() > 0) {
TermArgs pa1 = cc->args(),
pa2 = q->h_args();
us.clear();
for (int i = q->h_arity() - 1; i > 0; i--) {
UnifyStack::termPair *tp = us.push();
tp->t1 = pa1.getarg(i);
tp->i1 = pfn->vspos;
tp->t2 = pa2.getarg(i);
tp->i2 = pcn->vspos;
}
us.check_overflow();
if (!us.work( pa1.getarg(0), pfn->vspos,
pa2.getarg(0), pcn->vspos))
{
// undo changes
unbind(pcn->trail);
vs->pop(nvars);
// try next match
pcn->dbpos = pcn->dbpos->succ(db);
goto B;
}
}
fn = cn;
goto A;
}
break;
default:
ASSERT(0);
}
if (tr && PCN->call && tr->fail(cn, cc))
return -1;
// backtracking
C1: query_fail(ps->curr_dim() - cn);
// resume top query
C: cn = ps->curr_dim() - 1;
unbind(PCN->trail);
C2: if ((fn = pcn->father) == STKNULL)
return 0;
if ((cc = pcn->call) == 0)
goto C1;
switch (cc->get_type()) {
case CallData::CUT: { // change satisfaction path up to father
stkpos fvp = PFN->vspos;
query_fail(cn - fn + 1);
if ((cn = ps->curr_dim() - 1) != STKNULL) {
unbind(PCN->trail);
vs->pop(vs->curr_dim() - fvp);
goto C2;
}
return 0;
}
case CallData::BUILTIN: { // check builtins retry
BuiltIn *btp = cc->get_builtin();
if (btp->args & BuiltIn::retry) {
if (tr && tr->redo(cn, cc))
return -1;
// could be resatisfied
pcn->trail = ts->curr_dim();
pcn->vspos = PFN->vspos;
// if evaluate OK
if (btp->eval(cc->args(), this, 1))
goto A1;
}
// failed
goto C1;
}
case CallData::DISJUNCT: // evaluate right side
if (tr && tr->redo(cn, cc))
return -1;
pcn->call = cc->get_orelse();
goto A2;
case CallData::DBPRED: // a DB query node to retry
if (tr) { // display REDOs (TBD)
if (pcn->dbpos && pcn->dbpos->succ(db) && tr->redo(cn, cc))
return -1;
}
vs->pop(vs->curr_dim() - pcn->vspos);
pcn->dbpos = pcn->dbpos->succ(db);
PFN;
goto B;
default:
ASSERT(0);
}
return -1;
}
现在我对那个代码不是很自豪:我最终(通过相当痛苦的调试)而不是 ABC 到 A-A1-A2 B C1-C-C2。
编辑:我将完整的解释器源代码放在 github 中。
您可以从检查这个问题的答案开始。
您还可以检查各种开源 prolog 实现的来源(gnu prolog、swi-prolog、yap prolog 等)(尽管如果您只想要一个“幼稚”的实现或一些类似 prolog 的功能,例如回溯,这可能太复杂了)。
最后,您应该检查序言 ISO。
话虽如此,如果您对结合 C 和 prolog 感兴趣,可以使用一些接口;我不认为实施(有效的)序言是一项微不足道的任务,特别是如果我们考虑到(令人惊讶地)有许多公司/组织致力于它。
You might also be interested in looking at Mike Spivey's An Introduction to Logic Programming through Prolog. Both the full text of the book as well as an implementation of a simplified Prolog are available at the previous link (Note: the implementation itself is written in a minimal Pascal dialect, but for compilation this is translated into C. According to the author, this minimal Pascal dialect is more or less the "intersection of Pascal and C", anyway---whatever that means, so while not strictly satisfying the criteria, it should be quite useful for learning about Prolog).
I also noticed Alan Mycroft's Logic Programming and Functional Nets, following this link you will find a Prolog interpreter in C++, but I don't know much about it.