一个游戏的Antlr语法

时间:2011-02-19 16:00:54

标签: antlr

我正在尝试从旧游戏预先处理一些对话框文件 - 吸血鬼化妆舞会:Bloodlines如果你很好奇 - 在某些数据文件的特定位置插入一些代码。

我想使用Antlr来转换对话框文件,但是我的语法很模糊,即使格式非常简单。

格式允许NPC和PC的对话作为一系列行:

 { TEXT } repeated (it varies, normally 13 but sometimes less)

特别是这些令牌中的一个(第5个,但在示例中为第1个)很重要,因为它定义了该行是属于NPC还是PC。我有'#'字符。但是,其他令牌可以具有相同的字符,并且我会收到一些我想要消除的有效文件的警告。

我的大问题ATM是一种语法模糊。为了解决令牌变量数量的问题,我决定使用'*'来表示我不关心的那些直到换行符。

所以我这样做了:

any* NL* 

期望在任何一组换行符之前匹配其余的标记。然而,Antlr说这个语法是暧昧的:

any NL* or any* NL is not.

编辑:删除旧语法,检查新语法和新问题。

编辑:我解决了歧义,感谢Kiers先生,我几乎可以肯定我的新语法会与输入相匹配,但是我现在遇到了一个新问题: “error(208):VampireDialog.g:99:1:永远不能匹配以下标记定义,因为先前的标记匹配相同的输入:NOT_SHARP” 如果我删除他正在抱怨的NL输入,那么抱怨的是NL Lexer规则......

正如Kiers先生告诉我在这里发布一个输入的例子它也是: npc行,请注意#

  

{1} {在哪里? }{ 去哪儿? } {#} {} {G.Cabbie_Line = 1} {} {} {} {} {} {} {}

pc line,注意缺少#

  

{2} {开车。 } {刚开车。 } {0} {} {npc.WorldMap(G.WorldMap_State)} {} {} {} {} {} {} {不在这里。 }

这是语法:

grammar VampireDialog;

options
{
output=AST;
ASTLabelType=CommonTree;
language=Java;
} 
tokens
{
REWRITE;
}

@parser::header {
import java.util.LinkedList;
import java.io.File;
}

@members {
    public static void main(String[] args) throws Exception {
        File vampireDir = new File(System.getProperty("user.home"), "Desktop/Vampire the Masquerade - Bloodlines/Vampire the Masquerade - Bloodlines/Vampire/dlg");
        List<File> files = new LinkedList<File>();
        getFiles(256, new File[]{vampireDir}, files, new LinkedList<File>());
        for (File f : files) {
            if (f.getName().endsWith(".dlg")) {
                VampireDialogLexer lex = new VampireDialogLexer(new ANTLRFileStream(f.getAbsolutePath(), "Windows-1252"));
                TokenRewriteStream tokens = new TokenRewriteStream(lex);
                VampireDialogParser parser = new VampireDialogParser(tokens);
                Tree t = (Tree) parser.dialog().getTree();
                //  System.out.println(t.toStringTree());
            }
        }
    }

    public static void getFiles(int levels, File[] search, List<File> files, List<File> directories) {
        for (File f : search) {
            if (!f.exists()) {
                throw new AssertionError("Search file array has non-existing files");
            }
        }
        getFilesAux(levels, search, files, directories);
    }

    private static void getFilesAux(int levels, File[] startFiles, List<File> files, List<File> directories) {
        List<File[]> subFilesList = new ArrayList<File[]>(50);
        for (File f : startFiles) {
            File[] subFiles = f.listFiles();
            if (subFiles == null) {
                files.add(f);
            } else {
                directories.add(f);
                subFilesList.add(subFiles);
            }
        }

        if (levels > 0) {
            for (File[] subFiles : subFilesList) {
                getFilesAux(levels - 1, subFiles, files, directories);
            }
        }
    }
}




/*------------------------------------------------------------------
 * PARSER RULES
 *------------------------------------------------------------------*/
dialog : (ANY ANY ANY  (npc_line | player_line) ANY* NL*)*;
npc_line :  npc_marker npc_conditional;
player_line : pc_marker conditional;
npc_conditional : '{' condiction '}'
            {   String cond = $condiction.tree.toStringTree(), partial = "npc.Reset()", full = "("+cond+") and npc.Reset()";
                boolean empty = cond.trim().isEmpty(); 
                boolean alreadyProcessed = cond.endsWith("npc.Reset()");}   
                ->   {empty}? '{' REWRITE[partial] '}'
                ->   {alreadyProcessed}? '{' REWRITE[cond] '}'
                ->   '{' REWRITE[full] '}';
conditional : '{' condiction '}'
            {   String cond = $condiction.tree.toStringTree(), full = "("+cond+") and npc.Count()";
                boolean empty = cond.trim().isEmpty(); 
                boolean alreadyProcessed = cond.endsWith("npc.Count()");}   
                ->   {empty}? '{' REWRITE[cond] '}'
                ->   {alreadyProcessed}? '{' REWRITE[cond] '}'
                ->   '{' REWRITE[full] '}';
condiction : TEXT*;
//in the parser ~('#') means: "match any token except the token that matches '#'" 
//and in lexer rules ~('#') means: "match any character except '#'"
pc_marker : '{' NOT_SHARP* '}';
npc_marker : '{' NOT_SHARP* '#' NOT_SHARP* '}';


/*------------------------------------------------------------------
 * LEXER RULES
 *------------------------------------------------------------------*/
ANY : '{' TEXT* '}';
TEXT : ~(NL|'}');
NOT_SHARP : ~(NL|'#'|'}');
NL : ( '\r' | '\n'| '\u000C');

1 个答案:

答案 0 :(得分:3)

我建议采用略有不同的方法。您可以使用名为syntactic predicate的内容。这看起来像(some_parser_or_lexer_rules_here)=> parser_or_lexer_rules。一个小例子:

line
  :  (A B)=> A B
  |          A C
  ;

规则line中发生的情况是这样的:首先执行前瞻以查看流中的下一个令牌是A还是B。如果是这种情况,则会匹配这些令牌,如果不匹配,则AC匹配。

如果在行结束前有#,如果匹配npc行,如果不匹配,您可以先在流中预先应用相同的内容,匹配pc行。

演示语法:

grammar VampireDialog;

parse
  :  LineBreak* line (LineBreak+ line)* LineBreak* EOF
  ;

line
  :  (any_except_line_breaks_and_hash+ Hash)=> conditionals {System.out.println("> npc :: " + $conditionals.text);}
  |                                            conditionals {System.out.println("> pc  :: " + $conditionals.text);}
  ;

conditionals  
  :  Space* conditional (Space* conditional)* Space*
  ;

conditional
  :  Open conditional_text Close
  ;

conditional_text
  :  (Hash | Space | Other)*
  ;

any_except_line_breaks_and_hash
  :  (Space | Open | Close | Other)
  ;

LineBreak
  :  '\r'? '\n'
  |  '\r'
  ;

Space
  :  ' ' | '\t'
  ;

Hash  : '#';
Open  : '{';
Close : '}';

// Fall through rule: if the lexer does not match anything 
// above this rule, this `Any` rule will match.
Other
  :  .
  ;

还有一个小班来测试语法:

import org.antlr.runtime.*;

public class Main {
    public static void main(String[] args) throws Exception {
        String source = 
                "{ 1 }{ Where to? }{ Where to? }{ # }{ }{ G.Cabbie_Line = 1 }{ }{ }{ }{ }{ }{ }{ }\n" + 
                "\n" +
                "{ 2 }{ Just drive. }{ Just drive. }{ 0 }{ }{ npc.WorldMap( G.WorldMap_State ) }{ }{ }{ }{ }{ }{ }{ Not here. }\n";
        ANTLRStringStream in = new ANTLRStringStream(source);
        VampireDialogLexer lexer = new VampireDialogLexer(in);
        CommonTokenStream tokens = new CommonTokenStream(lexer);
        VampireDialogParser parser = new VampireDialogParser(tokens);
        parser.parse();
    }
}

打印:

> npc :: { 1 }{ Where to? }{ Where to? }{ # }{ }{ G.Cabbie_Line = 1 }{ }{ }{ }{ }{ }{ }{ }
> pc  :: { 2 }{ Just drive. }{ Just drive. }{ 0 }{ }{ npc.WorldMap( G.WorldMap_State ) }{ }{ }{ }{ }{ }{ }{ Not here. }

如您所见,它也会跳过空行。

(请注意,句法或语义谓词不适用于ANTLRWorks,因此您需要在命令行上进行测试!)