【问题标题】:Lemon Parser REPL柠檬解析器 REPL
【发布时间】:2019-02-20 07:42:58
【问题描述】:

我正在尝试基于LanguageKit 构建一个 Smalltalk REPL,它使用柠檬语法。目前解析器只支持解析完整的类定义,不支持解析方法语法之外的语句。

例如,这会被解析:

methodName [
    NSObject description.
    NSObject debugDescription.
]

但如果我尝试仅解析语句,它将失败:

NSObject description.
NSObject debugDescription.

以下不接受多个语句(例如Transcript show: 'hello'. Transcript show: 'world'.):

file ::= statement_list(M).
{
    [p setAST:M];
}

这里是最小语法:

%include {
#include <assert.h>
#import <Foundation/Foundation.h>
#import <LanguageKit/LanguageKit.h>
#import <LanguageKit/LKToken.h>
#import "SmalltalkParser.h"

}
%name SmalltalkParse
%token_prefix TOKEN_
%token_type {id}
%extra_argument {SmalltalkParser *p}
%left PLUS MINUS STAR SLASH EQ LT GT COMMA.
%left WORD.


file ::= method(M).
{
    [p setAST:M];
}
file ::= statement_list(M).
{
    [p setAST:M];
}
file ::= statement(M).
{
    [p setAST:M];
}
file ::= .



method(M) ::= signature(S) LSQBRACK statement_list(E) RSQBRACK.
{
    M = [LKInstanceMethod methodWithSignature:S locals:nil statements:E];
}

signature(S) ::= WORD(M).
{
    S = [LKMessageSend messageWithSelectorName:M];
}
signature(S) ::= keyword_signature(M).
{
    S = M;
}


statement_list(L) ::= statements(T).
{
    L = T;
}
statement_list(L) ::= statements(T) statement(S).
{
    [T addObject:S];
    L = T;
}

statements(L) ::= statements(T) statement(S) STOP.
{
    [T addObject:S];
    L = T;
}
statements(L) ::= .
{
    L = [NSMutableArray array];
}

statement(S) ::= expression(E).
{
    S = E;
}

%syntax_error 
{
    [NSException raise:@"ParserError" format:@"Parsing failed"];
}

message(M) ::= simple_message(S).
{
    M = S;
}

simple_message(M) ::= WORD(S).
{
    M = [LKMessageSend messageWithSelectorName:S];
}

expression(E) ::= simple_expression(S).
{
    E = S;
}

simple_expression(E) ::= WORD(T) simple_message(M).
{
    [M setTarget:T];
    E = M;
}

完整的语法可以在这里找到:smalltalk.y。我一直在阅读其他语法并搜索 stackoverflow,但看不到与此 gramma 的区别,也不明白为什么这不起作用。

【问题讨论】:

  • 我不知道如何解释“但它不会接受多个语句。”在您的问题中,后面是一条确实应该接受多个语句但不会出现在您声称的“完整”语法中的任何地方的规则。请关注实际问题;不要包含多个语法副本或关于可能修改的模糊指示。如果您使用minimal reproducible example 编辑您的问题,那么提供有用的建议会更加容易。
  • @rici 感谢您花时间看这个,非常感谢!我编辑了这个问题,希望这能解决问题。

标签: parsing grammar lemon


【解决方案1】:

您的语法存在解析冲突。如果您希望语法正常工作,则必须解决这些问题。

(该语法还有一个未定义的非终结符keyword_signature和一个未使用的非终结符message。为了让它在没有警告的情况下编译,我只是删除了它们。我认为它没有任何区别到下面的分析。)

部分冲突很简单:你不能同时拥有两者

file ::= statement_list .

和

file ::= statement .

事实上,我不清楚你为什么要这样做? statement 不是statement_list 的一个例子吗?

你不能两者兼得的原因是你有:

 statement_list ::= statements statement .

和

 statements ::= .

综合起来,这意味着以statement_list 开头,您可以识别单个statement。所以你的语法是模棱两可的;如果输入是单个语句,则可以直接将其解析为 file,也可以将其解析为 file ⇒ statement_list ⇒ statements statement ⇒ statement,并使用一组不同的操作。

你可能不在乎;实际上,您可能认为操作的顺序是相同的。你甚至可能是对的。但是解析器无法知道这一点,它也不会相信。它认为这两个解析必然是不同的。所以它会报告一个冲突。

简而言之,摆脱file ::= statement .。然后你就可以开始处理其他的解析冲突了。


更根本的问题也是基于statements可以推导出空序列。

我们看一下语法(去掉所有语义后简化):

statement_list ::= statements .
statement_list ::= statements statement .
statements     ::= statements statement STOP .
statements     ::= .

如果statement_list 不为空,则匹配的内容必须以空的statements 开头,后跟statement。而statement 必须以WORD 开头,因此statement_list 必须匹配以WORD 开头的输入。但在它可以移动WORD 以继续解析之前,它需要先插入空的statements。所以它需要使用上面引用的最后一条规则进行缩减,然后才能处理WORD。 (如果这段不完全清楚,请尝试重读,如果仍有疑问,请提出。理解这部分很重要。)

如果不是因为file 也可以是method,并且method 也以WORD 开头,那么这些都不是问题。但是,与statement_list 不同的是,它实际上以WORD 开头。它不是以空的statements 开头,所以如果解析器创建了一个空的statements 并且输入实际上是一个method,那么解析就会失败。

事实上,如果您使用的是file ::= statement 而不是file ::= statement_list,则不会发生这种特殊的冲突,因为statement 也不是以空的statements 开头的。这意味着当解析器在输入的开头看到WORD 时,它还不必决定是要看到statement 还是method。在这两种情况下,解析操作都是移动 WORD 并看看接下来会发生什么。

为了解决这个问题,我们可以观察到statement_list 必须至少包含一个statement,并且statement_list 中的所有statements(可能最后一个除外)都必须以@ 结束987654373@(即 .)。如果我们从这个想法开始,很容易产生一个不需要在开头有一个空列表的替代语法:

statement_list ::= statements .
statement_list ::= statements STOP .
statements ::= statement .
statements ::= statements STOP statement .

这与您的语法不同,因为它认为 statement_list 是由点分隔的 statements 的非空列表,可选地以点结尾,而您的语法认为 statement_list 可能为空列表以点结尾的statements 后跟一个statement。


由于我现在已经测试了语法,因此我添加了完整的可测试代码,以说明我们在请求 Minimal Complete Verifiable Example 时所要求的内容。 (我使用 C 和 flex 而不是 Objective C,但我认为这没有任何区别。)

文件解析器.y:

%include { #include <assert.h> }
file ::= method.
file ::= statement_list.
file ::= .
method ::= signature OBRAC statement_list CBRAC .
signature ::= WORD .
statement_list ::= statements STOP .
statement_list ::= statements .
statements ::= statements STOP statement .
statements ::= statement .
statement ::= expression .
expression ::= simple_expression .
simple_expression ::= WORD simple_message .
simple_message ::= WORD .
%extra_argument { int* status }
%syntax_error { *status = 1; }
%parse_failure { fprintf(stderr, "Parse failed.\n"); }
%parse_accept { fprintf(stderr, "Parse succeeded.\n"); }

文件 main.l:

%{
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include "parser.h"
void* ParseAlloc(void* (*allocProc)(size_t));
void* Parse(void*, int, int, int*);
void* ParseFree(void*, void(*freeProc)(void*));

void synerr(const char* token) {
  fprintf(stderr, "Syntax error handling '%s'\n", token);
}
%}
%option noinput nounput noyywrap nodefault
%x FLUSH
%%
    void* parser = ParseAlloc(malloc);
    int status = 0;
    #define SEND(typ, val) do {                        \
       if (Parse(parser, typ, val, &status), status) { \
         synerr(yytext); BEGIN(FLUSH);                 \
       }                                               \
    } while(0)
[[:space:]]+ ;
[[:alnum:]]+ { SEND(WORD, 0); }
"["          { SEND(OBRAC, 0); }
"]"          { SEND(CBRAC, 0); }
"."          { SEND(STOP, 0); }
.            { synerr(yytext); BEGIN(FLUSH); }
<FLUSH>.+    ;
<FLUSH>\n    { status = 0; BEGIN(INITIAL); }
<<EOF>>      { if (status == 0) {
                 Parse(parser, 0, 0, &status);
                 if (status) synerr("EOF");
               }
               ParseFree(parser, free );
               return 0;
             }
%%

int main(int argc, char** argv) {
   return yylex();
}

构建过程:

$ lemon parser.y
$ flex -o main.c main.l
$ gcc -std=c11 -Wall -Wno-unused-variable -o catlan -D_XOPEN_SOURCE=800 main.c parser.c

测试:

$ ./catlan <<< 'NSObject'
Parse failed.
Syntax error handling 'EOF'
$ ./catlan <<< 'NSObject description'
Parse succeeded.
$ ./catlan <<< 'NSObject description.'
Parse succeeded.
$ ./catlan <<< 'NSObject description. OtherObject'
Parse failed.
Syntax error handling 'EOF'
$ ./catlan <<< 'NSObject description. OtherObject otherDesc'
Parse succeeded.
$ ./catlan <<< 'NSObject description. OtherObject otherDesc.'
Parse succeeded.
$ ./catlan <<< 'NSObject description. OtherObject otherDesc extra words'
Syntax error handling 'extra'
Parse succeeded.
$ ./catlan <<< 'method [ NSObject desc]'
Parse succeeded.
$ ./catlan <<< 'method [ NSObject desc.]'
Parse succeeded.
$ ./catlan <<< 'method [ NSObject desc extra words]'
Syntax error handling 'extra'
Parse failed.
$ ./catlan <<< 'method [ NSObject desc. Second]'
Syntax error handling ']'
Parse failed.
$ ./catlan <<< 'method [ NSObject desc. Second desc]'
Parse succeeded.

【讨论】:

  • 谢谢。在您提到警告后,我发现我的 Xcode 构建设置没有显示lemon warnings。您建议的语法确实有效,并在方法中应用 “点分隔语句列表可选地以点终止” 规则,但在方法之外,用点终止是必需的。作品:test [ NSObject description ] 失败:NSObject description. NSObject description
  • @catlan: 你的语法现在没有警告了吗?您是否删除了 'file ::= statement .`?
  • @catlan:对我来说很好用。当然,我必须发明一个词法分析器,所以它可能与你的不完全相同。顺便说一句,第 55 行的测试是不必要的,并且可能很危险。您应该无条件地将L 分配为一个新的空数组。
  • 你是对的,你的语法很好。仍然对柠檬感到不舒服,现在与 cmets 打架,但需要把我的头包起来。
猜你喜欢
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
相关资源
最近更新 更多