ANTLR是否可以创建包含嵌套语法的语法?

5

ANTLR:是否可以编写包含自己的词法分析器的嵌套语法?

例如,我的语言中可以使用嵌入式SQL语言:

var Query = [select * from table];
with Query do something ....;

使用ANTLR可以实现这个吗?
2个回答

10

能否在嵌入语法中使用自己的词法分析器来构建语法?

如果你是想要在单个语法中定义两种不同的语言(使用独立的词法分析器),那么答案是否定的,这是不可能的。

但是,如果问题是关于是否可以将两种语言解析为单个AST,则答案是:是的,这是可能的。

您只需要:

  • 在各自的语法中定义这两种语言;
  • 在主语法中创建一个词法规则,捕获嵌入式语言的整个输入;
  • 使用重写规则调用自定义方法解析外部AST,并使用 { ... } 将其插入到主AST中(参见主语法(MyLanguage.g)中的 expr 规则)。

MyLanguage.g

grammar MyLanguage;

options {
  output=AST;
  ASTLabelType=CommonTree;
}

tokens {
  ROOT;
}

@members {
  private CommonTree parseSQL(String sqlSrc) {
    try {
      MiniSQLLexer lexer = new MiniSQLLexer(new ANTLRStringStream(sqlSrc));
      MiniSQLParser parser = new MiniSQLParser(new CommonTokenStream(lexer));
      return (CommonTree)parser.parse().getTree();
    } catch(Exception e) {
      return new CommonTree(new CommonToken(-1, e.getMessage()));
    }
  }
}

parse
  :  assignment+ EOF -> ^(ROOT assignment+)
  ;

assignment
  :  Var Id '=' expr ';' -> ^('=' Id expr)
  ;

expr
  :  Num
  |  SQL -> {parseSQL($SQL.text)}
  ;

Var   : 'var';
Id    : ('a'..'z' | 'A'..'Z')+;
Num   : '0'..'9'+;
SQL   : '[' ~']'* ']';
Space : ' ' {skip();};

MiniSQL.g

grammar MiniSQL;

options {
  output=AST;
  ASTLabelType=CommonTree;
}

parse
  :  '[' statement ']' EOF -> statement
  ;

statement
  :  select
  ;

select
  :  Select '*' From ID -> ^(Select '*' From ID)
  ;

Select : 'select';
From   : 'from';
ID     : ('a'..'z' | 'A'..'Z')+;
Space  : ' ' {skip();};

Main.java

import org.antlr.runtime.*;
import org.antlr.runtime.tree.*;
import org.antlr.stringtemplate.*;

public class Main {
  public static void main(String[] args) throws Exception {
    String src = "var Query = [select * from table]; var x = 42;";
    MyLanguageLexer lexer = new MyLanguageLexer(new ANTLRStringStream(src));
    MyLanguageParser parser = new MyLanguageParser(new CommonTokenStream(lexer));
    CommonTree tree = (CommonTree)parser.parse().getTree();
    DOTTreeGenerator gen = new DOTTreeGenerator();
    StringTemplate st = gen.toDOT(tree);
    System.out.println(st);
  }
}

运行演示

java -cp antlr-3.3.jar org.antlr.Tool MiniSQL.g 
java -cp antlr-3.3.jar org.antlr.Tool MyLanguage.g 
javac -cp antlr-3.3.jar *.java
java -cp .:antlr-3.3.jar Main

给定以下输入:

var Query = [select * from table]; var x = 42;

Main类的输出对应于以下AST:

enter image description here

如果要允许在SQL中使用字符串字面量(其中可能包含])和注释(其中可能包含']),则可以在主语法中使用以下SQL规则:

SQL
  :  '[' ( ~(']' | '\'' | '-')
         | '-' ~'-' 
         | COMMENT 
         | STR
         )* 
     ']'
  ;

fragment STR 
  :  '\'' (~('\'' | '\r' | '\n') | '\'\'')+ '\'' 
  |  '\'\''
  ;

fragment COMMENT
  :  '--' ~('\r' | '\n')*
  ;

它将正确解析以下输入并生成单个标记:

[
  select a,b,c 
  from table 
  where a='A''B]C' 
  and b='' -- some ] comment ] here'
]

请注意,尝试为整个 SQL 方言(甚至是大片段)创建语法并非易事!您可能需要搜索现有的 SQL 解析器,或查看 ANTLR 维基以获取示例语法。


对于antlr4,答案是否相同?或者antlr4是否会在语法方面简化它? - Andrei
1
@Andrei,如果使用ANTLR4,您可以使用词法模式使这种事情更容易:https://github.com/antlr/antlr4/blob/master/doc/lexer-rules.md#lexical-modes - Bart Kiers
是否可以为特定模式(比如我在XML中嵌入了Java)包含词法分析器标记,以便我的Java标记不会干扰XML标记? - Andrei
我不知道你所说的“干扰”是什么意思。令牌名称是唯一的,因此永远不会相互干扰。 - Bart Kiers
我的意思是Java标记只需在语法的“Java模式”部分中进行“声明”。如果您在XMLLexer开头声明“IF:'if'”标记,它会破坏解析器。所以我想知道是否只能手动将JavaLexer中的标记从JavaLexer复制/粘贴到XMLLexer的“Java模式”部分,或者是否有更好的方法(在模式中进行某种“包含”)。 - Andrei
不,词法分析器模式中没有“include”功能。 - Bart Kiers

2

似乎AntLR没有内置解析嵌套语法的能力。在您的示例中,包括语义谓词和编辑生成的解析器和词法分析器等一些技巧。这里似乎一切都不好...我认为在这种情况下更正确的答案应该是“不行。AntLR没有内置实现此功能的能力,但有时可以使用一些技巧...” - Astronavigator
@Astronavigator:很抱歉,我误解了你问题的第一行,认为是“是否可以使用AntLR解析混合语言”(这确实是可能的),而不是“是否可以在同一语法中描述混合语言”(这在v3中至少没有内置)。正如你所注意到的,你需要定义一些起始和结束点,让你的解析器知道何时在语言之间切换。 “岛屿”语法只是另一个常规语法,它使用选定的输入流片段进行调用。 - Seki

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接