游戏的Antlr语法
我试图预处理一些旧游戏中的对话文件——吸血鬼化妆舞会:血统,如果你好奇的话——在一些数据文件的特定位置插入一些代码 我想使用Antlr来转换对话框文件,但是我的语法是不明确的,尽管格式非常简单 该格式允许NPC和PC以一系列行的形式进行对话:游戏的Antlr语法,antlr,Antlr,我试图预处理一些旧游戏中的对话文件——吸血鬼化妆舞会:血统,如果你好奇的话——在一些数据文件的特定位置插入一些代码 我想使用Antlr来转换对话框文件,但是我的语法是不明确的,尽管格式非常简单 该格式允许NPC和PC以一系列行的形式进行对话: { TEXT } repeated (it varies, normally 13 but sometimes less) 其中一个标记尤其重要(示例中为第5个,但为第1个),因为它定义了该行是属于NPC还是属于PC。I上有“#”字符。但是,其他令牌可
{ TEXT } repeated (it varies, normally 13 but sometimes less)
其中一个标记尤其重要(示例中为第5个,但为第1个),因为它定义了该行是属于NPC还是属于PC。I上有“#”字符。但是,其他令牌可以具有相同的字符,并且我在一些有效文件上收到警告,我希望消除这些警告
我最大的问题是语法歧义。为了解决令牌数量可变的问题,我决定使用“*”对我在换行之前不关心的令牌进行全局化
所以我这样做了:
any* NL*
期望这与任何换行符集之前的其余标记相匹配。然而,Antlr说语法是模棱两可的,而:
any NL* or any* NL is not.
编辑:删除旧语法,检查新语法和新问题
编辑:我解决了模棱两可的问题,多亏了基尔斯先生,我几乎可以肯定我的新语法将与输入相匹配,但我现在有一个新问题:
“错误(208):错误对话框。g:99:1:无法匹配以下令牌定义,因为先前的令牌匹配相同的输入:NOT_SHARP”
如果我删除了他抱怨的NL输入,那么抱怨的是NL Lexer规则
正如Kiers先生告诉我在这里发布一个输入示例一样:
npc行,注意#
{1}{Where to?}{Where to?}{{{{}{{{G.Cabbie_Line=1}{{}{}{{}{}{}{}}
pc行,注意没有#
{2}{Just drive.}{Just drive.}{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');
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();
}
}
语法对话框;
选择权
{
输出=AST;
ASTLabelType=CommonTree;
语言=Java;
}
代币
{
重写;
}
@解析器::头{
导入java.util.LinkedList;
导入java.io.File;
}
@成员{
公共静态void main(字符串[]args)引发异常{
File vampairdir=new文件(System.getProperty(“user.home”),“桌面/吸血鬼化妆舞会-血统/吸血鬼化妆舞会-血统/吸血鬼/dlg”);
列表文件=新建LinkedList();
getFiles(256,新文件[]{vampairdir},文件,新LinkedList());
用于(文件f:文件){
if(f.getName().endsWith(“.dlg”)){
吸血鬼对话lexer lex=新的吸血鬼对话lexer(新的ANTLRFileStream(f.getAbsolutePath(),“Windows-1252”);
TokenRewriteStream令牌=新的TokenRewriteStream(lex);
吸血鬼对话框解析器=新的吸血鬼对话框解析器(令牌);
Tree t=(Tree)parser.dialog().getTree();
//System.out.println(t.toStringTree());
}
}
}
公共静态void getFiles(int级别、文件[]搜索、列表文件、列表目录){
用于(文件f:搜索){
如果(!f.exists()){
抛出新的断言错误(“搜索文件数组有不存在的文件”);
}
}
GetFileSux(级别、搜索、文件、目录);
}
私有静态void getFilesAux(int级别、文件[]开始文件、列表文件、列表目录){
列表子文件列表=新的ArrayList(50);
对于(文件f:startFiles){
File[]子文件=f.listFiles();
如果(子文件==null){
添加(f);
}否则{
目录。添加(f);
子文件列表。添加(子文件);
}
}
如果(级别>0){
对于(文件[]子文件:子文件列表){
GetFileSax(级别-1,子文件,文件,目录);
}
}
}
}
/*------------------------------------------------------------------
*解析器规则
*------------------------------------------------------------------*/
对话:(任意任意任意(npc|线|玩家|线)任意*NL*)*;
npc_线:npc_标记npc_条件;
播放器线:pc线;
npc_条件:'{'条件'}'
{String cond=$condiction.tree.toStringTree()、partial=“npc.Reset()”、full=“(“+cond+”)和npc.Reset()”;
布尔空=cond.trim().isEmpty();
布尔值alreadyProcessed=cond.endsWith(“npc.Reset()”);}
->{空}?“{“重写[部分]”}
->{alreadyProcessed}?“{“重写[cond]”
->“{”重写[完整]”};
条件:“{”条件“}”
{String cond=$condiction.tree.toStringTree(),full=“(“+cond+”)和npc.Count()”;
布尔空=cond.trim().isEmpty();
布尔值alreadyProcessed=cond.endsWith(“npc.Count()”);}
->{空}?“{“重写[cond]”
->{alreadyProcessed}?“{“重写[cond]”
->“{”重写[完整]”};
条件:文本*;
//在解析器中,(“#”)的意思是:“匹配除匹配“#”的标记之外的任何标记”
//在lexer规则中,(“#”)的意思是:“匹配除“#”以外的任何字符”
pc_标记:“{”不_SHARP*“}”;
npc_标记:“{”NOT_SHARP*“#”NOT_SHARP*“}”;
/*------------------------------------------------------------------
*LEXER规则
*------------------------------------------------------------------*/
任何:“{”文本*“}”;
正文:~(NL |'});
不尖锐:~(NL |'#'|'});
NL:(“\r”|“\n”|“\u000C”);
我提出了一种稍微不同的方法。你可以用一种叫a的东西。这看起来像(这里有些语法分析器或词法分析器规则)=>语法分析器或词法分析器规则。一个小例子:
line
: (A B)=> A B
| A C
;
规则行
中发生的情况是:首先执行一个前瞻,以查看流中的下一个令牌是否是a
和B
。如果是这种情况,则这些令牌
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. }