柠檬解析器REPL

时间:2019-02-20 07:42:58

标签: parsing grammar lemon

我正在尝试基于LanguageKit构建Smalltalk REPL,该版本使用柠檬语法。当前,解析器仅支持解析完整的类定义,但不支持方法语法之外的语句。

例如,将对此进行解析:

{% extends 'base.html' %}

{% block title %}Welcome
{% endblock %}

{% block content %}
<h1>Testtt</h1>

<a href="{% url 'login' %}">login</a>
<a href="{% url 'signup' %}">signup</a>
<button-element title='test'></button-element>
<dropdown-element></dropdown-element>
<paper-button class="pink" >link</paper-button>
{% endblock %}

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

methodName [
    NSObject description.
    NSObject debugDescription.
]

以下内容将不接受多个语句(例如NSObject description. NSObject debugDescription. ):

Transcript show: 'hello'. Transcript show: 'world'.

以下是最小语法:

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

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

1 个答案:

答案 0 :(得分:1)

您的语法存在解析冲突。如果您希望语法能够正确运行,则必须解决这些问题。

(语法还有一个未定义的非终结符keyword_signature和一个未使用的非终结符message。要使其在没有警告的情况下进行编译,我只是删除了它们。我不认为它对下面的分析没有任何影响。)

部分冲突非常简单:你们不能同时拥有

file ::= statement_list .

file ::= statement .

实际上,我不清楚您为什么要这么做? statement不是statement_list的示例吗?

不能同时拥有这两个原因是因为拥有:

 statement_list ::= statements statement .

 statements ::= .

加在一起,这意味着从statement_list开始,您可以识别单个statement。所以你的语法是模棱两可的。如果输入是单个语句,则可以将其直接解析为file,也可以将其解析为filestatement_liststatements statementstatement,采取一系列不同的动作。

您可能不在乎;确实,您可能会认为操作顺序是相同的。您甚至可能对此是正确的。但是解析器无法知道,也不会相信。它认为这两个解析必然是不同的。因此它将报告冲突。

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


更根本的问题还基于statements可以得出空序列的事实。

让我们看一下语法(通过删除所有语义简化):

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

如果statement_list不为空,则匹配的内容必须以空的statements开头,后跟statementstatement反过来必须以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内的所有statement_list(可能是最后一个除外)一个)必须以STOP(即)终止。如果我们从这个想法开始,那么很容易产生一个替代语法,该语法在开始时不需要空列表:

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

这与您的语法不同之处在于,它认为statement_list是由点分隔的statement组成的非空列表(可选以点结尾),而您的语法则将statement_list视为一个可能是空的以点结尾的statement列表,后跟一个statement


由于我现在已经测试了语法,因此我添加了完整的可测试代码,以作为我们在请求Minimal Complete Verifiable Example时所要求的说明。 (我使用C和flex而不是目标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.