3

我目前有一种使用 ANTLR 用 Ja​​va 实现的简单有效的语言。我想要做的是以与 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)
    ;
4

2 回答 2

2

但在这种情况之外, <% 总是表示代码块的开始。

在这种情况下,首先扫描文件以查找您的嵌入式代码,一旦有了这些,就使用专用解析器解析您的嵌入式代码(<%在标签之前和之后没有噪音%>)。

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! %>
于 2010-05-13T15:33:25.343 回答
1

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

至于怎么做,我对ANTLR一无所知,但它可能有类似flex的start conditions之类的东西。如果是这样,您可以让INITIAL开始条件除了查找之外什么都不做<%,这将切换到CODE定义所有实际标记的状态;然后 '%>' 会切换回来。在 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('%>'); %>,这取决于你,但很可能你会这样做

于 2010-05-09T18:03:28.317 回答