我如何lex这个输入?

时间:2010-05-09 17:46:55

标签: antlr lexical-analysis

我目前使用ANTLR在Java中实现了一种简单易用的语言。我想做的是以纯文本形式嵌入,与PHP类似。

例如:

Lorem ipsum dolor sit amet
<% print('consectetur adipiscing elit'); %>
Phasellus volutpat dignissim sapien.

我预计生成的令牌流看起来像:

CDATA OPEN PRINT OPAREN APOS STRING APOS CPAREN SEMI CLOSE CDATA

我如何实现这一目标,还是有更好的方法?

<%块之外的内容没有限制。根据Michael Mrozek的回答,我假设<% print('%>'); %>之类的东西是可能的,但在这种情况之外,<%总是表示代码块的开始。


示例实施

我根据Michael Mrozek的回答提出了一个解决方案,使用ANTLR的门控语义谓词来模拟Flex的启动条件:

lexer grammar Lexer;

@members {
    boolean codeMode = false;
}

OPEN    : {!codeMode}?=> '<%' { codeMode = true; } ;
CLOSE   : {codeMode}?=> '%>' { codeMode = false;} ;
LPAREN  : {codeMode}?=> '(';
//etc.

CHAR    : {!codeMode}?=> ~('<%');


parser grammar Parser;

options {
    tokenVocab = Lexer;
    output = AST;
}

tokens {
    VERBATIM;
}

program :
    (code | verbatim)+
    ;   

code :
    OPEN statement+ CLOSE -> statement+
    ;

verbatim :
    CHAR -> ^(VERBATIM CHAR)
    ;

2 个答案:

答案 0 :(得分:2)

  

但在这种情况之外,&lt;%将始终指示代码块的开始。

在这种情况下,首先扫描文件以获取嵌入代码,然后一旦有了这些代码,使用专用解析器解析嵌入代码(在<%之前和%>标记之后没有噪声)。

ANTLR可以选择让词法分析器只解析输入文件的(小)部分而忽略其余部分。请注意,在这种情况下,您无法创建“组合语法”(解析器和词法分析器)。以下是如何创建这样的“部分词法分析器”:

// file EmbeddedCodeLexer.g
lexer grammar EmbeddedCodeLexer;

options{filter=true;} // <- enables the partial lexing!

EmbeddedCode
  :  '<%'                            // match an open tag
     (  String                       // ( match a string literal
     |  ~('%' | '\'')                //   OR match any char except `%` and `'`
     |  {input.LT(2) != '>'}?=> '%'  //   OR only match a `%` if `>` is not ahead of it
     )*                              // ) <- zero or more times
     '%>'                            // match a close tag
  ;

fragment
String
  :  '\'' ('\\' . | ~('\'' | '\\'))* '\''
  ;

如果您现在从中创建词法分析器:

java -cp antlr-3.2.jar org.antlr.Tool EmbeddedCodeLexer.g 

并创建一个小测试工具:

import org.antlr.runtime.*;

public class Main {
    public static void main(String[] args) throws Exception {
        String source = "Lorem ipsum dolor sit amet       \n"+
                "<%                                       \n"+
                "a = 2 > 1 && 10 % 3;                     \n"+
                "print('consectetur %> adipiscing elit'); \n"+
                "%>                                       \n"+
                "Phasellus volutpat dignissim sapien.     \n"+
                "foo <% more code! %> bar                 \n";
        ANTLRStringStream in = new ANTLRStringStream(source);
        EmbeddedCodeLexer lexer = new EmbeddedCodeLexer(in);
        CommonTokenStream tokens = new CommonTokenStream(lexer);
        for(Object o : tokens.getTokens()) {
            System.out.println("=======================================\n"+
                    "EmbeddedCode = "+((Token)o).getText());
        }
    }
}

全部编译:

javac -cp antlr-3.2.jar *.java

最后通过执行以下操作来运行Main类:

// *nix/MacOS
java -cp .:antlr-3.2.jar Main

// Windows
java -cp .;antlr-3.2.jar Main 

它将产生以下输出:

=======================================
EmbeddedCode = <%                                       
a = 2 > 1 && 10 % 3;                     
print('consectetur %> adipiscing elit'); 
%>
=======================================
EmbeddedCode = <% more code! %>

答案 1 :(得分:1)

实际概念看起来很好,尽管你不太可能有PRINT令牌;词法分析器可能会发出类似IDENTIFIER的东西,解析器将负责确定它是一个函数调用(例如通过查找IDENTIFIER OPAREN ... CPAREN)并做适当的事情。

至于怎么做,我对ANTLR一无所知,但它可能有像flex start conditions这样的东西。如果是这样,您可以让INITIAL启动条件不执行任何操作,只查找<%,这将切换到CODE状态,其中定义了所有实际令牌;那么'%&gt;'会转回去。在flex中它将是:

%s CODE

%%

<INITIAL>{
    "<%"      {BEGIN(CODE);}
    .         {}
}

 /* All these are implicitly in CODE because it was declared %s,
    but you could wrap it in <CODE>{} too
  */
"%>"          {BEGIN(INITIAL);}
"("           {return OPAREN;}
"'"           {return APOS;}
...

你需要注意在不是结束标记的上下文中匹配%>之类的东西,比如在字符串中;如果你想允许<% print('%>'); %>,你可以自己决定