我正在尝试从旧游戏预先处理一些对话框文件 - 吸血鬼化妆舞会: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');
答案 0 :(得分:3)
我建议采用略有不同的方法。您可以使用名为syntactic predicate的内容。这看起来像(some_parser_or_lexer_rules_here)=> parser_or_lexer_rules
。一个小例子:
line
: (A B)=> A B
| A C
;
规则line
中发生的情况是这样的:首先执行前瞻以查看流中的下一个令牌是A
还是B
。如果是这种情况,则会匹配这些令牌,如果不匹配,则A
和C
匹配。
如果在行结束前有#
,如果匹配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,因此您需要在命令行上进行测试!)