使用动态事实来模拟Prolog中的“堆栈”

时间:2014-03-24 01:12:46

标签: prolog

所以基本上我正在尝试使用Prolog来模拟asm代码。

在@mbratch的帮助下,我知道使用动态事实来模拟像

这样的指令很简单易行
add eax, 1
mov eax, 1

以这种方式:

:- dynamic(register/2).  % Fill in as needed

register(eax, 0).
....
add(Reg, Value) :-
(   retract(register(Reg, OldValue))
 ->   NewValue is OldValue + Value
),
assertz(register(Reg, NewValue)).

但问题是如何以类似的方式模拟堆栈......?

最初我写了一些非常类似FP的代码:

nth_member(1, [M|_], M).
nth_member(N, [_|T], M) :- N>1, N1 is N - 1, nth_member(N1, T, M).
.....
% push  ebp
ESP is ESP - 1, nth_member(ESP, STACK, EBP),
....

但问题是我不知道如何以动态的事实风格重写这段代码......

有人能给我一些帮助..?谢谢!

2 个答案:

答案 0 :(得分:2)

Prolog正典说,除非你有充分的理由,否则不要将动态事实用于州。换句话说,如果要对堆栈建模,请将其保留为变异的术语,并传递给递归谓词的下一步,该谓词将状态作为参数。例如(非常简化),

step(Current_stack, Final_stack) :-
    read_next_instruction(Instruction/*, whatever other arguments you need */),
    apply_instruction(Current_stack, Instruction, New_stack),
    step(New_stack, Final_stack).

第二个参数Final_stack,如果您希望在完成正在模拟的代码中的所有指令后获得最终堆栈。它可能是模拟开始时的自由变量,或者,如果要验证,则可能是预期的最终状态。

堆栈本身可以是一个列表(如果您只需要堆栈的顶部),也可以是更复杂的,可能是嵌套的术语。您很可能也希望以这种方式维护所有寄存器(如my other answer所示)。

还有另一种选择,使用适当的可变全局变量。根据您使用的Prolog实现,这将涉及不同的内置插件。对于SWI-Prolog,请查看here;对于GNU-Prolog,here。其他实现也可能具有相同的谓词。

这里的要点是使用assert和retract来维护状态经常更改会使您的程序非常难以理解,并且效率非常低。 “纯”Prolog解决方案是第一个建议;在某些情况下使用全局变量可能更有效。

PS:

作为如何使用堆栈的完整示例,请参阅有关基于堆栈的计算器(无耻的自我推销)的问题的答案: Postfix expression list evaluation

为了扩展“不使用动态谓词”,它们肯定有用。如果您正在实现关系数据库,那么这是一个很好的解决方案的良好示例。然后,您的表被实现为事实,每列有一个子句:

name_age('Bob', 20).
name_age('Jane', 23).
% etc

name_occupation('Bob', student).
name_occupation('Jane', teacher).
% etc

在这里,您可以使用断言向表中添加新行,或缩进以删除行。重点是,您可能会更频繁地查询您的数据库,您将更改它。您还可以从Prolog有效查找事实中获益,此外您还可以以更自然的方式编写查询。

答案 1 :(得分:2)

我想强调@Boris提出的观点:不要使用动态谓词。

到目前为止,最干净的解决方案是使用状态变量来携带模拟机器的当前状态。由于Prolog的单一赋值特性,您将始终拥有以下对:状态之前和状态之后。对于寄存器和存储器,状态最好表示为将寄存器名称(或存储器地址)映射到值的表。堆栈可以简单地保存为列表。例如:

main :-
    Stack0 = [],
    Regs0 = [eax-0, ebx-0, ecx-0, edx-0],
    Code = [movi(3,eax), add(eax,7), push(eax), pop(ecx)],
    sim_code(Code, Regs0, RegsN, Stack0, StackN),
    write(RegsN), nl, write(StackN), nl.

% simulate a sequence of instructions
sim_code([], Regs, Regs, Stack, Stack).
sim_code([Instr|Instrs], Regs0, RegsN, Stack0, StackN) :-
    sim_instr(Instr, Regs0, Regs1, Stack0, Stack1),
    sim_code(Instrs, Regs1, RegsN, Stack1, StackN).

% simulate one instruction
sim_instr(movi(Value,Reg), Regs0, RegsN, Stack, Stack) :-
    update(Regs0, Reg, _Old, Value, RegsN).
sim_instr(add(Reg,Value), Regs0, RegsN, Stack, Stack) :-
    update(Regs0, Reg, Old, New, RegsN),
    New is Old+Value.
sim_instr(push(Reg), Regs, Regs, Stack, [Val|Stack]) :-
    lookup(Regs, Reg, Val).
sim_instr(pop(Reg), Regs0, RegsN, [Val|Stack], Stack) :-
    update(Regs0, Reg, _Old, Val, RegsN).
%sim_instr(etc, ...).

% simple key-value table (replace with more efficient library predicates)
lookup([K-V|KVs], Key, Val) :-
    ( Key==K -> Val=V ; lookup(KVs, Key, Val) ).

update([K-V|KVs], Key, Old, New, KVs1) :-
    ( Key==K ->
        Old = V, KVs1 = [K-New|KVs]
    ;
        KVs1 = [K-V|KVs2],
        update(KVs, Key, Old, New, KVs2)
    ).

实际上,您应该使用高效的散列或基于树的版本替换我的简单表实现(lookup / 3,update / 5)。这些都不是标准化的,但您通常可以在Prolog系统附带的库中找到一个。