Prolog DCG:编写编程语言词法分析器

时间:2015-12-14 23:05:28

标签: prolog lexical-analysis dcg

我正在尝试将我的词法分析器和解析器分开,基于 Prolog和自然语言分析这本书的模糊建议,它实际上没有详细介绍lexing /标化。所以我试了一下,看到几个小问题向我表明我有一些明显缺失的东西。

我所有的小标记解析器似乎都正常工作;目前这是我的代码片段:

:- use_module(library(dcg/basics)).

operator('(')  --> "(".      operator(')')  --> ")".
operator('[')  --> "[".      operator(']')  --> "]".
% ... etc.

keyword(array)    --> "array".
keyword(break)    --> "break".
% ... etc.

它有点重复,但似乎有效。然后我有一些我并不完全喜欢的东西,欢迎提出建议,但似乎确实有效:

id(id(Id)) -->
    [C],
    {
        char_type(C, alpha)
    },
    idRest(Rest),
    {
        atom_chars(Id, [C|Rest])
    }.
idRest([C|Rest]) -->
    [C],
    {
        char_type(C, alpha) ; char_type(C, digit) ; C = '_'
    },
    idRest(Rest).
idRest([]) --> [].

int(int(Int)) --> integer(Int).

string(str(String)) -->
    "\"",
    stringContent(Codes),
    "\"",
    {
        string_chars(String, Codes)
    }.
stringContent([C|Chars]) -->
    stringChar(C), stringContent(Chars).
stringContent([]) --> [].

stringChar(0'\n) --> "\\n".
stringChar(0'\t) --> "\\t".
stringChar(0'\") --> "\\\"".
stringChar(0'\") --> "\\\\".
stringChar(C) --> [C].

我的tokenizer的主要规则是:

token(X) --> whites, (keyword(X) ; operator(X) ; id(X) ; int(X) ; string(X)).

它并不完美;我会看到int被解析为in,id(t),因为keyword(X)位于id(X)之前。所以我猜这是第一个问题。

我遇到的更大问题是我没有看到如何将评论正确地整合到这种情况中。我尝试过以下方法:

skipAhead --> [].
skipAhead --> (comment ; whites), skipAhead.

comment --> "/*", anything, "*/".
anything --> [].
anything --> [_], anything.

token(X) --> skipAhead, (keyword(X) ; operator(X) ; id(X) ; int(X) ; string(X)).

这似乎不起作用;返回的解析(我得到许多解析)似乎没有删除注释。我很紧张,我的评论规则不必要地效率低下,可能会导致很多不必要的回溯。我也很担心来自dcg / basics的whites//0是确定性的;然而,这个方程式的部分似乎是有效的,它只是将它与看似不合适的评论跳过整合在一起。

作为最后一点,我不知道如何使用此处的行/列信息将传播解析错误处理回用户。感觉就像我必须跟踪和穿过某种当前的行/列信息并将其写入令牌,然后如果我想做类似于llvm的操作,可能会尝试重建该行。那是公平的还是那里有“推荐做法”?

可以找到整个代码in this haste

2 个答案:

答案 0 :(得分:5)

它目前看起来仍然有点奇怪(unreadableNamesLikeInJavaAnyone?),但它的核心是非常可靠的,所以我对代码的某些方面和问题只有一些评论:

  1. 将lexing与解析分开非常有意义。它也是一个完全可以接受的解决方案,可以存储行和列信息以及每个令牌,并保留l_c_t(Line,Column,Token)Token-lc(Line,Column)形式的令牌(例如),供解析器处理。
  2. 评论总是令人讨厌,或者我应该说,经常不诚实? DCG中有用的模式通常用于最长匹配,在某些情况下您已经使用了这种模式,但尚未用于anything//0。因此,重新排序这两条规则可能会帮助您跳过所有要评论的内容。
  3. 关于决定论:可以提交匹配的第一个解析,但只执行一次,并抵制混淆声明性语法的诱惑。
  4. 在DCG中,使用|代替;非常优雅。
  5. tokenize//1?来吧!这只是tokens//1。它在各方面都很有意义。

答案 1 :(得分:2)

我有这个代码来支持错误报告,这本身必须小心处理,在代码周围散布有意义的消息和“跳过规则”。但是没有现成的替代方案:DCG是一个不错的计算引擎,但是它无法与专门的解析引擎竞争,它们能够自动发出错误消息,利用了它的理论属性。目标语法...

:- dynamic text_length/1.

parse_conf_cs(Cs, AST)   :-
    length(Cs, TL),
    retractall(text_length(_)),
    assert(text_length(TL)),
    phrase(cfg(AST), Cs).
....
%%  tag(?T, -X, -Y)// is det.
%
%   Start/Stop tokens for XML like entries.
%   Maybe this should restrict somewhat the allowed text.
%
tag(T, X, Y) -->
    pos(X), unquoted(T), pos(Y).
....

%%  pos(-C, +P, -P) is det.
%
%   capture offset from end of stream
%
pos(C, P, P) :- text_length(L), length(P, Q), C is L - Q.

tag // 3只是一个示例用法,在这个解析器中我正在构建一个可编辑的AST,所以我存储的位置能够在编辑器中正确地将每个嵌套部分归属...

修改

id // 1的一个小增强:SWI-Prolog有专门的code_type / 2:

1 ?- code_type(0'a, csymf).
true.

2 ?- code_type(0'1, csymf).
false.

所以(掩盖文字转换)

id([C|Cs]) --> [C], {code_type(C, csymf)}, id_rest(Cs).

id_rest([C|Cs]) --> [C], {code_type(C, csym)}, id_rest(Cs).
id_rest([]) --> [].

取决于你对推广小片段的态度,以及实际的语法细节,id_rest // 1可以以可重复使用的方式编写,并且可以确定性

id([C|Cs]) --> [C], {code_type(C, csymf)}, codes(csym, Cs).

% greedy and deterministic
codes(Kind, [C|Cs]) --> [C], {code_type(C, Kind)}, !, codes(Kind, Cs).
codes(Kind, []), [C] --> [C], {\+code_type(C, Kind)}, !.
codes(_, []) --> [].

这个更严格的id // 1定义还允许使用关键字前缀删除一些歧义wrt标识符:recoding keyword // 1 like

keyword(K) --> id(id(K)), {memberchk(K, [
    array,
    break,
...
]}.

将正确识别

?- phrase(tokenize(Ts), `if1*2`).
Ts = [id(if1), *, int(2)] ;

你的字符串// 1(OT:与库有什么不幸的冲突(dcg / basics):string // 1)是实现简单的“错误恢复策略”的一个简单的候选者:

stringChar(0'\") --> "\\\\".
stringChar(0'") --> pos(X), "\n", {format('unclosed string at ~d~n', [X])}.

这是“报告错误插入缺失令牌”的示例,因此解析可以继续......