Traverse Context Free Grammar

时间:2014-10-24 06:39:17

标签: prolog dcg postfix-notation

我面临着遍历prolog环境中使用的CFG的问题,使其以后序方式遍历。以下是使用的语法 -

list_ast(Ls, AST) :- phrase(expression(AST), Ls).

expression(E)       --> term(T), expression_r(T, E).

expression_r(E0, E) --> [+], term(T), expression_r(E0+T, E).
expression_r(E0, E) --> [-], term(T), expression_r(E0-T, E).
expression_r(E, E)  --> [].

term(T)       --> power(P), term_r(P, T).
term_r(T0, T) --> [*], power(P), term_r(T0*P, T).
term_r(T0, T) --> [/], power(P), term_r(T0/P, T).
term_r(T, T)  --> [].

power(P)          --> factor(F), power_r(F, P).
power_r(P0, P0^P) --> [^], factor(P1), power_r(P1, P).
power_r(P, P)     --> [].

factor(N) --> [N], { number(N) }.
factor(E) --> ['('], expression(E), [')'].

在输出生成后执行此语法时 -

?- list_ast([2,+,4,*,3], X).
X = 2+4*3 .

如何更改语法以便可以在POST-ORDER中遍历,以接受诸如的表达式 - ? - list_ast([2,4,3,*,+],X)。

2 个答案:

答案 0 :(得分:2)

我可能应该让你自己解决这个问题,但我记得在这些事情上苦苦挣扎,所以我认为这对其他人有帮助。

编辑请参阅Wouter关于我的解决方案的评论:它不适用于减法和除法等非交换操作。

首先,我想从postfix翻译成中缀,因为这对我来说似乎更有趣。此外,我可以让Prolog评估它,我不必明确地构建一个堆栈来进行评估。我认为这是Prolog的一个奇迹,你可以操纵像这样的算术表达式而不会折叠成值。

事实上,由于我们只是想从右到左进行解析,我只是将其翻转到波兰表示法列表并使用序列本身作为堆栈进行解析。

postfix_to_infix(Postfix, Infix) :-
    reverse(Postfix, Prefix),
    prefix_to_infix(Prefix, Infix).

从前缀转换为中缀并不是那么糟糕,诀窍是线程消耗列表,所以我们需要另一个参数。请注意,这种线程正是DCG所做的,所以每当我注意到自己做了很多这样的事情时,我认为“哎呀,我可以用DCG来做到这一点。”另一方面,在两个辅助谓词中,只有一个明确地执行此线程,因此它可能没那么多。我想是为读者练习。

我们正在使用大学=..来建立一个Prolog术语。评估结束后。

prefix_to_infix(Seq, Expr) :-
    prefix_to_infix(Seq, Expr, []).

% base case: peel off a number
prefix_to_infix([Val|Xs], Val, Xs) :- number(Val).

% inductive case
prefix_to_infix([Op|Rest], Expr, Remainder) :-
    atom(Op),
    % threading Rest -> Rem1 -> Remainder
    prefix_to_infix(Rest, Left, Rem1),
    prefix_to_infix(Rem1, Right, Remainder),
    Expr =.. [Op, Left, Right].

让我们在评估中看到它几次:

?- postfix_to_infix([2,4,3,7,*,+,*],Term), Res is Term.
Term = (7*3+4)*2,
Res = 50 ;
false.

让我们通过以保留意义的方式移动运算符和文字来置换列表,只是为了确保解析没有做任何完全愚蠢的事情。

?- postfix_to_infix([2,3,7,*,4,+,*],Term), Res is Term.
Term = (4+7*3)*2,
Res = 50 ;
false.

?- postfix_to_infix([3,7,*,4,+,2,*],Term), Res is Term.
Term = 2* (4+7*3),
Res = 50 ;
false.

现在让我们确保当我们有太多或太少时它会正常失败。

?- postfix_to_infix([3,7,*,4,+,2,*,3],Term), Res is Term.
false.

?- postfix_to_infix([3,7,*,4,+,2,*,+],Term), Res is Term.
false.

?- postfix_to_infix([7,*], Term), Res is Term.
false.

看起来对我很有用。

希望这有帮助!

答案 1 :(得分:2)

我的实施方式与丹尼尔的不同之处在于:

  • 适用于-//等非交换运算符。我相信后缀表示法与波兰表示法完全相反。例如,波兰表示法中的- 1 2对应于后缀表示法中的1 2 -,而不是2 1 -
  • 适用于一元运营商。不幸的是,在Prolog中不会出现arity> 2的运算符,但实现也能够处理这些运算符。

代码

RPN代表反向波兰表示法,也称为后缀表示法。

%! rpn(+Notation:list(atomic), -Outcome:number) is det.

rpn(Notation, Outcome):-
  rpn(Notation, [], Outcome).

rpn([], [Outcome], Outcome):-
  number(Outcome).
% Push operands onto the stack.
rpn([Operand|Notation], Stack, Outcome):-
  number(Operand), !,
  rpn(Notation, [Operand|Stack], Outcome).
% Evaluate n-ary operators w.r.t. the top n operands on the stack.
rpn([Op|Notation], Stack, Outcome):-
  % Notice that there can be multiple operators with the same name.
  current_op(_, OpType, Op),
  op_type_arity(OpType, OpArity),

  % Select the appropriate operands.
  length(OperandsRev, OpArity),
  append(OperandsRev, NewStack, Stack),

  % Apply the operator to its operands.
  reverse(OperandsRev, Operands),
  Expression =.. [Op|Operands],
  Result is Expression,

  rpn(Notation, [Result|NewStack], Outcome).

op_type_arity(fx,  1).
op_type_arity(fy,  1).
op_type_arity(xf,  1).
op_type_arity(xfx, 2).
op_type_arity(xfy, 2).
op_type_arity(yf,  1).
op_type_arity(yfx, 2).

使用示例

?- rpn([5,1,2,+,4,*,+,3,-], X).
X = 14.

后记

我特别喜欢丹尼尔使用is/2评估结果,因此他的主要任务是转换为中缀符号。我的实现也使用当前的运算符声明(即op/3),而不是使用is/2代替current_op/3

由于Prolog定义了多个具有相同名称的运算符,因此我的方法可能会给出不明确的结果:

?- rpn([1,2,+,-], X).
X = -1 ;
X = -3 ;
false.

另一个例子:丹尼尔的算法中出现以下情况:

?- rpn([3,7,*,4,+,2,*,+], X).
X = 29 ;
X = 50 ;
false.

这种模棱两可的情况可能不允许在官方'后缀表示法(虽然我喜欢它)。仅通过获取给定运算符名称的最大arity就可以轻松限制它。