在JavaCC中,如何降低正则表达式的优先级?

时间:2018-07-26 07:00:24

标签: compiler-construction javacc

让我用一个具体的例子来阐明我的问题:

首先,我有一个令牌:

<T_SELECT:"SELECT">

第二,我有一个正则表达式标记:

<T_TABLE_NAME:~[","]>

第三,我有一部作品:

<T_SELECT> <T_TABLE_NAME> (","<T_TABLE_NAME>)*

最后,我想解析如下字符串:

SELECT TABLE1

我的问题是: 而不是先消耗SELECT然后再消耗TABLE1,JavaCC最终将SELECT TABLE1当作<T_TABLE_NAME>

很明显,我期望它消耗SELECT,因为我定义了一个名为<T_SELECT: "SELECT">的令牌

我该怎么做才能告诉JavaCC使用我定义的确切字符串而不是正则表达式?

2 个答案:

答案 0 :(得分:1)

这几乎肯定不是您要执行的操作,因为它将把selection变成select ion

~[","]将匹配逗号以外的任何字符,包括空格和换行符。这也不对,因为表名不是以(或包含)空格字符开头。

您需要匹配并忽略空格,并将名称限制为合法名称字符。

答案 1 :(得分:1)

我假设您唯一的词汇规则就是这些

TOKEN { <T_SELECT:"SELECT"> }
TOKEN { <T_TABLE_NAME:~[","]> }
TOKEN { <T_COMMA: "," > }

第三个规则是隐式的,因为语法中包含字符串“,”。我在这里明确指出。现在,如果要解析的字符序列是

 "SELECT TABLE1" , 

序列将按以下顺序分类

  • 首先找到一个T_SELECT。
  • 然后是7个T_TABLE_NAME令牌。
  • 然后是EOF令牌。

即:

T_SELECT("SELECT")
T_TABLE_NAME(" ")
T_TABLE_NAME("T")
T_TABLE_NAME("A")
...
T_TABLE_NAME("1")
EOF

如果您的语法由一个规则组成

void select() : {} {
    <T_SELECT>
    <T_TABLE_NAME>
    (<T_COMMA> <T_TABLE_NAME>)* 
}   ,

然后只有前两个标记会被匹配。

如果您的语法由一个规则组成

void select() : {} {
    <T_SELECT>
    <T_TABLE_NAME>
    (<T_COMMA> <T_TABLE_NAME>)* 
    <EOF>   // I added an <EOF>
}   ,

然后,示例输入出现语法错误,解析器将引发异常。对于第三个令牌,解析器期望的是T_COMMAEOF,但是它得到的是T_TABLE_NAME


我将做更多的假设。

  • 首先,我假设表名可以包含0个或更多字符,而不是OP中的1个字符。
  • 第二,我假设表名不能包含换行符或换行符。否则,除非点击文件末尾,否则将无法终止SELECT查询。
  • 第三,我假设换行符或文件结尾会终止每个SELECT查询。
  • 第四,我假设SELECT关键字仅在表名之前使用。

这些可能是不正确的假设,但我需要做一些 假设才能理解这个问题。


作为第一个更改,我将允许表名称为任意长度。并且我将禁止在表名和逗号中使用换行符。我将制作一个新令牌来处理任何换行符。词汇规则现在是

TOKEN { <T_SELECT:"SELECT"> }
TOKEN { <T_TABLE_NAME: (~[",","\n","\r"])* > }
TOKEN { <T_COMMA: "," > }
TOKEN { <T_NEWLINE: ("\n" | "\n\r" | "\r") > }

现在会发生什么?再次假设输入字符串为

"SELECT TABLE1"    ,

存在与规则匹配的长度为6的前缀

<T_SELECT:"SELECT">

并且有几个(实际上是14个)匹配规则的前缀

<T_TABLE_NAME: (~[",","\n","\r"])* >

由于长度6的前缀都被这两个规则匹配,因此总共有14个前缀可以被至少一个规则匹配。

在这14个前缀中,词法分析器将始终选择最长的前缀。这是最大的咀嚼规则。因此令牌的顺序将是

T_TABLE_NAME("SELECT TABLE1") EOF

无法关闭最大规则。您无法赢得这场比赛。你必须作弊。


作弊的方法是参加比赛。将T_TABLE_NAME规则移到另一个词法状态。

您要在lexer看到SELECT关键字之后切换状态。

这样编写词法分析器规则:

TOKEN : { <T_SELECT: "SELECT"> : S_TABLE_NAME } 

<S_TABLE_NAME> TOKEN :
    { <T_TABLE_NAME:(~[",","\n","\r"])*> : S_TABLE_NAME 
    | <T_COMMA: "," } : S_TABLE_NAME
    | <T_NEWLINE: ("\n" | "\n\r" | "\r") : DEFAULT }

现在您的作品将成为

void Select() : {} 
{ <T_SELECT>
  <T_TABLE_NAME>
  ( <T_COMMA> <T_TABLE_NAME>)*
  ( <T_NEWLINE> )? 
  <EOF>
}

如果输入为

 "SELECT ABC, DEF\n"    ,

它将按照以下方式分类

State          Token Produced
------------------------------
DEFAULT        T_SELECT("SELECT")
S_TABLE_NAME   T_TABLE_NAME( " ABC" )
S_TABLE_NAME   T_COMMA( "," )
S_TABLE_NAME   T_TABLE_NAME( " DEF" )
S_TABLE_NAME   T_NEWLINE( "\n" )
DEFAULT        EOF.

它将解析无误。