我正在为Wolfram语言编写解析器。该语言具有"命名字符"的概念,由\[
和]
分隔的名称指定。例如:\[Pi]
。
假设我想为标识符指定正则表达式。标识符可以包含命名字符。我看到了两种方法:一种是使用预处理器将所有命名字符转换为它们的unicode表示,另外两种方法是将源代码形式中所有可能的命名字符枚举为正则表达式的一部分。
第二种方法似乎不可行,因为有很多命名字符。我希望在我的正则表达式中使用unicode字符范围。
所以我想预处理我的令牌流。换句话说,在我看来,词法分析器需要检查命名字符语法是否正确,然后查找名称并将其转换为unicode。
但是如果语法不正确或名称不存在,我需要告诉用户它。如何将此错误传播给用户,然后让antlr4从错误中恢复并恢复?也许我可以选择" pipe"词法分析器/解析器? (我是antlr的新手。)
编辑:
在Wolfram语言中,我可以将此字符串作为标识符:\[Pi]Squared
。括号之间的部分称为"命名字符"。有一组有限的命名字符,每个字符对应一个unicode代码点。我试图弄清楚如何标记这样的标识符。
我可以为我的令牌设置一个规则(简化为命名字符和ASCII字符的组合):
NAME : ('\\[' [a-z]+ ']'|[a-zA-Z])+ ;
但是我想检查命名字符是否确实存在(以及其他属性,例如它是否是一个字母,但后一部分超出了问题的范围),所以这个正则表达式不起作用
我考虑制作一个允许的命名字符列表,只是制作一个列举所有字符的长正则表达式,但这看起来很难看。
对此有什么好处?
编辑结束
答案 0 :(得分:1)
一种常见的方法是编写词法分析器/解析器以允许语法正确的输入并将语义问题推迟到生成的解析树的分析。在这种情况下,词法分析器可以天真地接受命名字符:
NChar : NCBeg .? RBrack ;
fragment NCBeg : '\\[' ;
fragment LBrack: '[' ;
fragment RBrack: ']' ;
<强>更新强>
在解析器中,允许NChar作为离散终端节点存在于解析树中:
idents : ident+ ;
ident : NChar // named character string
| ID // simple character string?
| Literal // something quoted?
| ....
;
这使得对解析树的分析变得相当容易:每个ident
上下文将只包含一个非空值,用于可离散识别的alt;并将所有排序问题的分析分离到idents
上下文。
<强> UPDATE2 强>
对于输入\[Pi]Squared
,最容易分析的解析树表单是idents
个节点,其中包含两个排序良好的子节点\[Pi]
和Squared
。
最佳做法不是将两个孩子打包成相同的令牌 - 只需稍后手动将令牌文本分成两部分以检查它是否包含有效的命名字符以及特定的部件序列是否允许。
没有正则表达式允许对命名字符进行确凿的验证。这需要一份清单。但是,收紧NChar的词法分析器定义可以获得相当于正则表达式的结果:
NChar : NCBeg [A-Z][A-Za-z]+ RBrack ;
如果担心命名字符后面可能有空格,请考虑使用语义警告而不是语法错误来更好地处理这种情况。而不是跳过词法分析器中的空格,而是将空白放在隐藏的通道上。然后,在每个idents
上下文的验证分析中,检查隐藏的通道以插入空格并根据需要发出警告。
<强> ---- 强>
然后,解析树访问者可以根据需要检查,验证和警告未知或拼写错误的命名字符。
要在解析器中进行验证,如果需要,请使用谓词规则来区分已知和未知的命名字符:
@members {
ArrayList<String> keyList = .... // list of named chars
public boolean inList(String id) {
return keyList.contains(id) ;
}
}
nChar : known
| unknown
;
known : NChar { inList($NChar.getText()) }? ;
unknown : NChar { error("Unknown " + $NChar.getText()); } ;
inList
函数可以实现距离度量来检测拼写错误,但直接在解析树中更正文本有点复杂。在访问者操作期间实现为解析树装饰时更容易做到。
最后,将named characters划分为可用的地图(unicode和ascii)可能值得处理两种表示形式以及转换和拼写错误。