2

需要为 可能的表单设计语言 实现 COS aka MUMPS的语法突出显示

new (new,set,kill)
set kill=new

其中:'new' 和 'set' 是命令,也是变量

grammar cos;

Command_KILL            :( ('k'|'K') | ( ('k'|'K')('i'|'I')('l'|'L')('l'|'L') ) ); 
Command_NEW             :( ('n'|'N') | ( ('n'|'N')('e'|'E')('w'|'W') ) ); 
Command_SET             :( ('s'|'S') | ( ('s'|'S')('e'|'E')('t'|'T') ) );


INT : [0-9]+;
ID : [a-zA-Z][a-zA-Z0-9]*;
Space: ' ';
Equal: '=';

newCommand
    :   Command_NEW Space ID
    ;
setCommand
    :   Command_SET Space ID Space*  Equal Space* INT
    ; 

我有一个问题,当 ID 像名称作为命令(NEW、SET 等)

4

1 回答 1

5

根据维基百科页面, MUMPS 没有保留字:

保留字:无。由于 MUMPS 通过上下文解释源代码,因此不需要保留字。您可以使用语言命令的名称作为变量。

Lexer 规则之类的Command_KILL功能与保留字完全一样:它们旨在确保在"kill"遇到输入时不会生成其他标记。所以令牌类型Command_KILL总是会在 上产生"kill",即使它是一个标识符。如果需要,您可以保留命令词法分析器规则,但您也必须将它们视为 ID,因为您只是不知道仅"kill"基于令牌所指的是什么。

在 ANTLR 中实现 MUMPS 意味着关注令牌使用和上下文而不是令牌类型。考虑这个语法:

grammar Example;


document    : (expr (EOL|EOF))+;
expr        : command=ID Space+ value (Space* COMMA Space* value)*  #CallExpr
            | command=ID Space+ name=ID Space* Equal Space* value   #SetExpr
            ;     

value       : ID | INT;

INT         : [0-9]+;
ID          : [a-zA-Z][a-zA-Z0-9]*;
Space       : ' ';
Equal       : '=';
EOL         : [\r\n]+;
COMMA       : ',';

解析器规则expr知道ID令牌何时是基于整行布局的命令。

  • 如果输入标记是ID ID,那么输入是 a CallExpr:第一个ID是命令名称,第二个ID是常规标识符。
  • 如果输入标记是ID ID Equal ID,那么输入是 a SetExpr:第一个ID是命令("set"或者类似的东西),第二个ID是目标标识符,第三个ID是源标识符。

这是一个 Java 测试应用程序,后跟一个类似于您的问题中提到的测试用例。

import java.util.List;

import org.antlr.v4.runtime.ANTLRInputStream;
import org.antlr.v4.runtime.CommonTokenStream;

public class ExampleTest {

    public static void main(String[] args) {

        ANTLRInputStream input = new ANTLRInputStream(
                "new new, set, kill\nset kill = new");

        ExampleLexer lexer = new ExampleLexer(input);

        ExampleParser parser = new ExampleParser(new CommonTokenStream(lexer));

        parser.addParseListener(new ExampleBaseListener() {
            @Override
            public void exitCallExpr(ExampleParser.CallExprContext ctx) {
                System.out.println("Call:");
                System.out.printf("\tcommand = %s%n", ctx.command.getText());
                List<ExampleParser.ValueContext> values = ctx.value();
                if (values != null) {
                    for (int i = 0, count = values.size(); i < count; ++i) {
                        ExampleParser.ValueContext value = values.get(i);
                        System.out.printf("\targ[%d]  = %s%n", i,
                                value.getText());
                    }
                }
            }

            @Override
            public void exitSetExpr(ExampleParser.SetExprContext ctx) {
                System.out.println("Set:");
                System.out.printf("\tcommand = %s%n", ctx.command.getText());
                System.out.printf("\tname    = %s%n", ctx.name.getText());
                System.out.printf("\tvalue   = %s%n", ctx.value().getText());
            }

        });

        parser.document();
    }
}

输入

new new, set, kill
set kill = new

输出

Call:
    command = new
    arg[0]  = new
    arg[1]  = set
    arg[2]  = kill
Set:
    command = set
    name    = kill
    value   = new

由调用代码决定命令在给定上下文中是否有效。由于 MUMPS 对命令和标识符的松散方法,解析器无法合理处理此问题。但这并不像听起来那么糟糕:您将知道哪些命令的功能类似于调用,哪些功能类似于集合,因此您将能够测试来自ListenerANTLR 生成的输入。例如,在上面的代码中,很容易测试“set”是否是传递给exitSetExpr.

一些 MUMPS 语法可能比这更难处理,但一般方法是相同的:让词法分析器像IDs 一样处理命令和标识符,并使用解析器规则来确定 anID是指命令还是基于整行的上下文。

于 2012-12-23T21:35:06.120 回答