解析、匹配和关键字

Parsing, matching and keywords

我正在使用 Java15 语法,有几个问题是关于 Rascal 的解析器如何工作以及为什么有些东西不工作。给出具体语法:

module tests::Concrete

start syntax CompilationUnit =
    compilationUnit: TypeDec* LAYOUTLIST
    ;

syntax TypeDec =
    ClassDec
    ;

syntax ClassDec =
    \class: ClassDecHead ClassBody
    ;

syntax ClassDecHead =
    "class" Id
    ;

syntax ClassBody =
    "{" ClassBodyDec* "}"
    ;

syntax ClassBodyDec =
    ClassMemberDec
    ;

syntax ClassMemberDec =
    MethodDec
    ;

syntax MethodDec =
    \method: MethodDecHead
    ;

syntax MethodDecHead =
    ResultType Id
    ;

syntax ResultType =
    \void: "void"
    ;

syntax Id =
    \id: [A-Z_a-z] !<< ID \ IDKeywords !>> [0-9A-Z_a-z]
    ;

keyword Keyword =
    "void"
    ;

keyword IDKeywords =
    "null"
    | Keyword
    ;

lexical LAYOUT =
    [\t-\n \a0C-\a0D \ ]
    ;

lexical ID =
    [A-Z_a-z] [0-9A-Z_a-z]*
    ;

layout LAYOUTLIST  =
    LAYOUT* !>> [\t-\n \a0C-\a0D \ ] !>> (  [/]  [*]  ) !>> (  [/]  [/]  ) !>> "/*" !>> "//"
    ;

AST 定义:

module tests::Abstract

data Declaration =
    \compilationUnit(list[Declaration] body)
   | \package(ID name)
   | \import(ID name)
   | \class(ID name, list[Declaration] body)
   | \method(Type ret, ID name)
             ;

data Type =
    \void()
    ;

data ID =
    \id(str id)
    ;

和加载文件的驱动程序:

module tests::Load

import Prelude;
import tests::Concrete;
import tests::Abstract;

public Declaration load(loc l) = implode(#Declaration, parse(#CompilationUnit, l));

我发现实际有效和无效的一些奇怪之处。如果我参加这个项目:

class A {

}

这按预期解析为:compilationUnit([ class(id("A"),[]) ]) 但事实证明,为 class 内部的方法解析和构建 AST 节点有点棘手。给定程序:

class A {
    void f
}

这会产生 "Cannot find a constructor for Declaration" 错误。如果我将语法修改为:

syntax MethodDecHead =
    ResultType
    ;

要成为的 AST:

| \method(Type ret)

我能够得到我期望的树:compilationUnit([class(id("A"),[method(void())])])

我对这里发生的事情、如何处理关键字以及导致这种行为的原因感到很困惑。

除此之外,如果我不将 LAYOUTLIST 添加到 start syntax 生产的末尾,我每次尝试读取文件时都会得到 ParseError

我不是 implode 方面的专家,所以我暂时不说了,但是 LAYOUTLIST 的问题是由于 parse 的调用方式。

每个由start Something =定义的start非终结符产生两种类型,即: * 非终结符本身 Something 和 * 一个名为 start[Something] 的非终端包装器。

包装器 automatically/implicitly 定义如下:

syntax start[Something] = LAYOUTLIST before Something top LAYOUTLIST after;

所以,如果你想在你的程序前后有空格和注释,你可以像这样调用解析:

parse(#start[Something], yourLocation)

如果您不想保留注释或空白以备后用,那么您可以像这样投影出顶层树:

Something mySomething = parse(#start[Something], myLocation).top;

ClassDec 的产生式规则与 AST 节点 class 不兼容。 将其更改为:

syntax ClassDec = \class: "class" Id "{" ClassBodyDec* "}" ; 使其与 AST 节点更加规则和同构 class(ID name, list[Declaration])

但是:名称应该始终对应,所以我建议在语法中将 ID 更改为 Id。此外,您的 AST 节点需要 Declarations,但在语法中您有 ClassBodyDecs.

implode 的一般规则是:

  • 非终结符对应ADT类型
  • 生产标签对应ADT构造函数
  • 关键字、运算符、布局等被跳过。
  • 未标记的词汇产生式映射到原语(str、int、real)。
  • 如果需要,带标签的词法可以映射到构造函数:lexical Id = id: [a-z]+,可以映射到 data Id = id(str x)
  • 如果你不标记上下文无关的产品,implode "looks over them":所以如果我有 syntax A = B; syntax B = cons: "bla",那么我可以使用 ADT:data A = cons()

(这些规则记录在 Parsetree.rsc、https://github.com/cwi-swat/rascal/blob/master/src/org/rascalmpl/library/ParseTree.rsc