标签:
第8章介绍了四个例子,讲述了Antlr了实际应用。下面的阅读笔记中,最终实现与书中并非完全一致。其中调用关系仅输出关系,而未转换为Dot语言。
CSV是逗号分隔值的缩写,其形式为。
Details,Month,Amount
Mid Bonus,June,"$2,000"
,January,"""zippo"""
Total Bonuses,"","$5,000"
接下来将从CSV文件中读取每行数据,然后将读取的数据存储于Map中。最终将读取的数据打印,输出的形式如下。
[{Details=Mid Bonus, Month=June, Amount="$2,000"},
{Details=, Month=January, Amount="""zippo"""},
{Details=Total Bonuses, Month="", Amount="$5,000"}]
首先写出CSV的文法。
grammar csv;
file : hdr row+;
hdr : row;
row : field (‘,‘ field)*‘\r‘?‘\n‘;
field
: TEXT # text
| STRING # string
| # empty
;
TEXT : ~[,\n\r"]+;
STRING : ‘"‘ (‘""‘ | ~‘"‘)* ‘"‘;
hdr定义了文件头,在示例文件中是Details,Month,Amount且只出现一次。而普通的行则可以出现多次。
为了进行更准确的分析,文法中使用了符号#,为每条规则增加标签,最终在所生成的代码中包含处理响应规则的函数。
最终实现的思路是,每当访问一行时创建一个列表用来存放每行中的数据项。当访问结束时,将头和数据行拼接成(title,value)对,然后放入map,最后将map放入List,List为Map的集合。最后完整的代码如下。
public class Loader extends csvBaseListener{
private List<Map<String,String>> rows = new ArrayList<Map<String, String>>();
private List<String> header;
private List<String> currentRowFieldValues;
private final String EMPTY = "";
public List<Map<String,String>> getRows(){
return rows;
}
@Override
public void exitEmpty(EmptyContext ctx) {
currentRowFieldValues.add(EMPTY);
}
// Hdr访问结束将数据存储于header中
@Override
public void exitHdr(HdrContext ctx) {
header = new ArrayList<String>();
header.addAll(currentRowFieldValues);
}
@Override
public void exitText(TextContext ctx) {
currentRowFieldValues.add(ctx.TEXT().getText());
}
// 为每个Row创建一个列表存储CSV文件中的数据
@Override
public void enterRow(RowContext ctx) {
currentRowFieldValues = new ArrayList<String>();
}
@Override
public void exitRow(RowContext ctx) {
// 如果当前行不是文件头,则将内数据转换为<标题,数据> 并存于Map中
if(ctx.getParent().getRuleIndex() == csvParser.RULE_hdr){
return;
}
Map<String,String> m = new LinkedHashMap<String, String>();
int i = 0;
for(String v : currentRowFieldValues){
m.put(header.get(i), v);
i++;
}
rows.add(m);
}
// 当访问完终结点则将结点文本添加到本行所关联的列表中
@Override
public void exitString(StringContext ctx) {
currentRowFieldValues.add(ctx.STRING().getText());
}
}
public class Main {
public static void main(String[] args) throws IOException {
File csvFile = new File("D:\\csv.csv");
InputStream fi = new FileInputStream(csvFile);
ANTLRInputStream inputStream = new ANTLRInputStream(fi);
csvLexer lexer = new csvLexer(inputStream);
CommonTokenStream tokenStream = new CommonTokenStream(lexer);
csvParser parser = new csvParser(tokenStream);
ParseTreeWalker walker = new ParseTreeWalker();
Loader loader = new Loader();
walker.walk(loader,parser.file());
List<Map<String,String>> rows = loader.getRows();
for(Map<String,String> map : rows){
System.out.println(map);
}
}
}
csv.csv数据为
Details,Month,Amount
Mid Bonus,June,"$2,000"
,January,"""zippo"""
Total Bonuses,"","$5,000"
test,,sun
程序打印结果
{Details=Mid Bonus, Month=June, Amount="$2,000"}
{Details=, Month=January, Amount="""zippo"""}
{Details=Total Bonuses, Month="", Amount="$5,000"}
{Details=test, Month=, Amount=sun}
将json转换为xml,就是将json中key,value对以xml标签的形式表示。例如{x:v}表示为<x>v</x>,而数组元素则以<element>数组值</element>的形式表示。嵌套再上面两种情况的基础上嵌套即可。
在程序中实现时,当某个json元素访问结束生成对应的xml即可。完整的演示代码如下。
public class XMLEmitter extends JSONBaseListener{
public ParseTreeProperty<String> xml = new ParseTreeProperty<String>();
String getXML(ParseTree ctx){
return xml.get(ctx);
}
void setXML(ParseTree ctx,String s){
xml.put(ctx, s);
}
@Override
public void exitAtom(AtomContext ctx) {
setXML(ctx, ctx.getText());
}
@Override
public void exitArrayValue(ArrayValueContext ctx) {
setXML(ctx,getXML(ctx.array()));
}
@Override
public void exitString(StringContext ctx) {
setXML(ctx,ctx.getText().replaceAll("\"", ""));
}
@Override
public void exitObjectValue(ObjectValueContext ctx) {
setXML(ctx,getXML(ctx.object()));
}
@Override
public void exitPair(PairContext ctx) {
String tag = ctx.STRING().getText().replace("\"", "");
ValueContext vctx = ctx.value();
String x = String.format("<%s>%s<%s>\n",tag,getXML(vctx),tag);
setXML(ctx,x);
}
@Override
public void exitAnObject(AnObjectContext ctx) {
StringBuilder buf = new StringBuilder();
buf.append("\n");
for(PairContext pctx : ctx.pair()){
buf.append(getXML(pctx));
}
setXML(ctx,buf.toString());
}
@Override
public void exitEmptyObject(EmptyObjectContext ctx) {
setXML(ctx,"");
}
@Override
public void exitArrayOfValues(ArrayOfValuesContext ctx) {
StringBuilder buf = new StringBuilder();
buf.append("\n");
for(ValueContext vctx : ctx.value()){
buf.append("<element>")
.append(getXML(vctx))
.append("<element>")
.append("\n");
}
setXML(ctx,buf.toString());
}
@Override
public void exitEmptyArray(EmptyArrayContext ctx) {
setXML(ctx,"");
}
@Override
public void exitJson(JsonContext ctx) {
setXML(ctx,getXML(ctx.getChild(0)));
}
}
public class Main {
public static void main(String[] args) throws IOException {
File csvFile = new File("D:\\csv.txt");
InputStream fi = new FileInputStream(csvFile);
ANTLRInputStream inputStream = new ANTLRInputStream(fi);
JSONLexer lexer = new JSONLexer(inputStream);
CommonTokenStream tokenStream = new CommonTokenStream(lexer);
JSONParser parser = new JSONParser(tokenStream);
ParseTreeWalker walker = new ParseTreeWalker();
XMLEmitter xml = new XMLEmitter();
ParseTree json = parser.json();
walker.walk(xml,json);
System.out.println(xml.xml.get(json));
}
}
输入:
{"a":[1.234,"abcd",{"key":"value"}],"key2":"value2"}
输出:
<a>
<element>1.234<element>
<element>abcd<element>
<element>
<key>value<key>
<element>
<a>
<key2>value2<key2>
这里调用关系是指Cymbol语言(书中介绍为C的一个子集)中的调用关系,例如下面的程序片段。
int main() { fact(); a(); }
float fact(int n) {
print(n);
if ( n==0 ) then return 1;
return n * fact(n-1);
}
从上面的程序可以看出,调用语句是写在函数声明的内部。例如int main(){}内部的fact(),a()。因此生成调用关系的思路如下:
最后完成的文法与程序代码如下。
grammar Cymbol;
file : (functionDecl | varDecal)+;
varDecal : type ID (‘=‘ expr)? ‘;‘;
functionDecl : type ID ‘(‘ formalParameters? ‘)‘ block;
formalParameters : formalParameter (‘,‘ formalParameter)*;
formalParameter : type ID;
type : ‘float‘ | ‘int‘ | ‘void‘;
block : ‘{‘ stat* ‘}‘;
stat : block
| varDecal
| ‘if‘ expr ‘then‘ stat (‘else‘ stat)?
| ‘return‘ expr? ‘;‘
| expr ‘=‘ expr ‘;‘
| expr ‘;‘
;
expr : ID ‘(‘ exprList? ‘)‘ # Call
| expr ‘[‘ expr ‘]‘ # Index
| ‘-‘expr # Negate
| ‘!‘expr # Not
| expr ‘*‘ expr # Mult
| expr (‘+‘|‘-‘) expr # AddSub
| expr ‘==‘ expr # Equal
| ID # var
| INT # Int
| ‘(‘ expr ‘)‘ # Parens
;
exprList : expr (‘,‘ expr)*;
ID : [a-zA-Z]+;
INT : ‘0‘ | [1-9][0-9]*;
WS : [ \t\n\r]+ -> skip;
程序代码。
public class FunctionListener extends CymbolBaseListener{
Graph graph = new Graph();
String currentFunctionName = null;
public void printGraph(){
MultiMap<String,String> mMap = graph.edges;
List<Pair<String, String>> pairs = mMap.getPairs();
System.out.println("=====================");
for(Pair<String, String> pair : pairs){
System.out.println(pair.a + "->" + pair.b);
}
}
@Override
public void enterFunctionDecl(FunctionDeclContext ctx) {
// 进入函数语法子树时,获取函数名。
currentFunctionName = ctx.ID().getText();
graph.nodes.add(currentFunctionName);
}
@Override
public void exitCall(CallContext ctx) {
// 当调用语句子树访问结束
String funcName = ctx.ID().getText();
graph.edge(currentFunctionName,funcName);
}
private static class Graph{
Set<String> nodes = new OrderedHashSet<String>();
MultiMap<String,String> edges =
new MultiMap<String,String>();
public void edge(String source,String target){
edges.map(source, target);
}
}
}
public class CallRelation {
public static void main(String[] args) throws IOException {
InputStream cymbol = new FileInputStream(new File("D:\\antlrInput\\cymbol.txt"));
ANTLRInputStream aInputStream = new ANTLRInputStream(cymbol);
CymbolLexer lexer = new CymbolLexer(aInputStream);
CommonTokenStream cts = new CommonTokenStream(lexer);
CymbolParser parser = new CymbolParser(cts);
ParseTreeWalker walker = new ParseTreeWalker();
FunctionListener collector = new FunctionListener();
walker.walk(collector, parser.file());
collector.printGraph();
}
}
输入文件:
int main()
{
a();
fact();
b();
}
float fact(int n)
{
print(n);
if ( n==0 ) then return 1;
return n * fact(n-1);
}
输出结果
main->a
main->fact
main->b
fact->print
fact->fact
输出结果中 a->b 的这种形式表示a调用b。
本节对Cymbol语言中使用的符号进行验证,验证包括下面几个方面。
1. 在作用域内所引用的变量有响应的定义。
2. 所引用的函数有响应的定义。
3. 变量不能被当作函数来使用。
4. 函数不能被当作变量来使用。
对于下面的例子,经检查后有些语句不满足1-4点中的某些要求。
int f(int x, float y) {
g(); // forward reference is ok
i = 3; // 没有i的声明(错误)
g = 4; // g不是变量 (错误)
return x + y;
}
void g() {
int x = 0;
float y;
y = 9;
f();
z(); // 函数z不存在(错误)
y(); // 函数y不存在(错误)
x = f; // f是函数,但赋给整型变量 (错误)
}
为了完成上述的验证任务,需要用到符号表。通过符号表来检查是否正确使用了所引用的符号。
符号表用来保存程序语言中所使用的符号,并根据符号所在的作用域将不同符号组织在一起。符号表的两种基本操作是定义符号与解析符号。定义符号意味着将符号添加到作用域中。解析符号是根据名称在作用域中查找对应的符号,以下面的程序为例。
1.int x;
2.int y;
3.void a()
{
int x;
x = 1; // x resolves to current scope, not x in global scope
y = 2; // y is not found in current scope, but resolves in global
4.{ int y = x; }
}
5.void b(int z)
6.{ }
在全局作用域中,包含了变量x,y以及函数a()与函数b().而3,4,6则处于函数作用域内。在上面的例子中,x变量有两个定义。这时对于x的使用就需要根据作用域来判断了。
数字所标注的表示作用域。从任意一个结点到根结点形成一个作用域栈。为了寻找符号,首先从所引用符号所在的定义域内开始,一直到根结点去寻找要找的符号。
根据符号表的操作,最终的实现可以分为符号定义与查找两部分。对于定义,需要监听变量与函数定义事件,并将符号对象插入到符号所在的作用域内。为了解析并检查符号的引用,需要监听表达式内的变量与函数名引用。对于每个引用需要验证是否有定义,并且进行了正确的引用。
在解析查找时,会遇到前向引用,即调用的函数,定义在引用位置之后。为了支持前向引用,需要遍历两次分析树。第一遍完成符号定义,第二遍完成符号的解析。
为了解析,需要保存对全局作用域的引用,指向当前作用域的指针,以及所创建的作用域。在代码中enterFile开始了解析工作,并创建全局作用域。而exitFile负责打印结果。当分析器找到函数定义时,程序需要创建函数符号对象,该对象即作为对象,也作为作用域并包含参数。为了找到的函数作用域“嵌入”全局作用域之中,我们将函数作为域的父作用域指定为全局作用域。
最后完整的演示代码如下,而Cymbol文法与前文一致。
public class Validation {
public static void main(String[] args) throws IOException {
// 建立作用域树,并添加符号
InputStream cymbol = new FileInputStream(new File("D:\\antlrInput\\cymbol.txt"));
ANTLRInputStream aInputStream = new ANTLRInputStream(cymbol);
CymbolLexer lexer = new CymbolLexer(aInputStream);
CommonTokenStream cts = new CommonTokenStream(lexer);
CymbolParser parser = new CymbolParser(cts);
parser.setBuildParseTree(true);
ParseTree tree = parser.file();
ParseTreeWalker walker = new ParseTreeWalker();
DefPhase def = new DefPhase();
walker.walk(def, tree);
// 检查
walker = new ParseTreeWalker();
RefPhase ref = new RefPhase(def.globals, def.scopes);
parser = new CymbolParser(cts);
walker.walk(ref, tree);
}
}
public class RefPhase extends CymbolBaseListener {
private GlobalScope globals;
private ParseTreeProperty<Scope> scopes;
private Scope currentScope;
public RefPhase(GlobalScope globals, ParseTreeProperty<Scope> scopes) {
this.globals = globals;
this.scopes = scopes;
}
@Override
public void enterFile(FileContext ctx) {
currentScope = globals;
}
@Override
public void exitFunctionDecl(FunctionDeclContext ctx) {
currentScope = currentScope.getEnclosingScope();
}
@Override
public void enterFunctionDecl(FunctionDeclContext ctx) {
currentScope = scopes.get(ctx);
System.out.println(currentScope);
}
@Override
public void enterBlock(BlockContext ctx) {
currentScope = scopes.get(ctx);
}
@Override
public void exitBlock(BlockContext ctx) {
currentScope = currentScope.getEnclosingScope();
}
@Override
public void exitVar(VarContext ctx) {
String name = ctx.ID().getSymbol().getText();
// 访问到符号后解析符号,resovle方法应该是从当前作用到根去寻找
Symbol var = currentScope.resolve(name);
if (var == null) {
System.err.println(ctx.ID().getSymbol() + "no such variable: " + name);
}
if (var instanceof FunctionSymbol) {
System.err.println(ctx.ID().getSymbol() + "is not a variable");
}
}
@Override
public void exitCall(CallContext ctx) {
// 得到调用名
String name = ctx.ID().getSymbol().getText();
Symbol var = currentScope.resolve(name);
if (var == null) {
System.err.println(ctx.ID().getSymbol() + "no such function: " + name);
}
if (var instanceof VariableSymbol) {
System.err.println(ctx.ID().getSymbol() + "is not a function");
}
}
}
public class DefPhase extends CymbolBaseListener {
public ParseTreeProperty<Scope> scopes = new ParseTreeProperty<Scope>();
public GlobalScope globals;
Scope currentScope;
@Override
public void enterFile(FileContext ctx) {
// 进入文件,创建全局作用域
globals = new GlobalScope();
currentScope = globals;
}
@Override
public void exitFile(FileContext ctx) {
System.out.println(globals);
}
@Override
public void enterFunctionDecl(FunctionDeclContext ctx) {
// 获取函数名
String name = ctx.ID().getText();
// ctx.type().start.getType();
String typeTokenType = ctx.type().getText();
FunctionSymbol function = new FunctionSymbol(name, typeTokenType, currentScope);
currentScope.define(function);
saveScope(ctx, function);
currentScope = function;
}
@Override
public void exitFunctionDecl(FunctionDeclContext ctx) {
System.out.println(ctx);
currentScope = currentScope.getEnclosingScope();
}
@Override
public void enterBlock(BlockContext ctx) {
Scope scope = new BlockScope("block", currentScope);
saveScope(ctx, new BlockScope("block", currentScope));
currentScope = scope;
}
@Override
public void exitBlock(BlockContext ctx) {
System.out.println(ctx);
currentScope = currentScope.getEnclosingScope();
}
public void saveScope(ParserRuleContext ctx, Scope s) {
scopes.put(ctx, s);
}
@Override
public void exitFormalParameter(FormalParameterContext ctx) {
defineVar(ctx.type(), ctx.ID().getSymbol());
}
@Override
public void exitVarDecal(VarDecalContext ctx) {
defineVar(ctx.type(), ctx.ID().getSymbol());
}
public void defineVar(TypeContext typeCtx, Token nameToken) {
String typeTokenType = typeCtx.getText();
VariableSymbol var = new VariableSymbol(nameToken.getText(), typeTokenType);
currentScope.define(var);
}
}
函数符号的定义。函数即是一个符,同时也作为作用域。
public class FunctionSymbol extends Symbol implements Scope{
private Map<String,Symbol> symbols = new HashMap<String, Symbol>();
private Scope enclosingScope;
public FunctionSymbol(String name, String type) {
super(name, type);
}
public FunctionSymbol(String name, String type,Scope enclosingScope) {
super(name, type);
this.enclosingScope = enclosingScope;
}
public String getScopeName() {
return name;
}
public Scope getEnclosingScope() {
return enclosingScope;
}
public void define(Symbol sym) {
symbols.put(sym.name, sym);
}
public Symbol resolve(String name) {
if(symbols.get(name) == null && enclosingScope != null)
return enclosingScope.resolve(name);
return null;
}
}
变量符号
public class VariableSymbol extends Symbol{
public VariableSymbol(String name,String type){
super(name, type);
}
}
符号定义
public class Symbol {
public String name; // 符号名称
public String type; // 符号类型
public Symbol(String name, String type) {
this.name = name;
this.type = type;
}
public Symbol(String name) {
this.name = name;
}
public String getName() {
return name;
}
@Override
public String toString() {
return "Symbol [name=" + name + ", type=" + type + "]";
}
}
作用域接口定义
public interface Scope {
// 名称
public String getScopeName();
//外部作用域
public Scope getEnclosingScope();
//定义作用域中的符号
public void define(Symbol sym);
//根据名称查找符号
public Symbol resolve(String name);
}
具体作用域的定义,全局作用域。
public class GlobalScope implements Scope{
Map<String,Symbol> symbols = new HashMap<String, Symbol>();
public String getScopeName() {
return "global";
}
public Scope getEnclosingScope() {
return null;
}
public void define(Symbol sym) {
symbols.put(sym.name, sym);
}
public Symbol resolve(String name) {
return symbols.get(name);
}
}
块作用域。
public class BlockScope implements Scope{
private Map<String,Symbol> symbols = new HashMap<String, Symbol>();
private Scope enclosingScope;
private String scopeName;
public BlockScope(String name,Scope enclosing){
this.scopeName = name;
this.enclosingScope = enclosing;
}
public String getScopeName() {
return scopeName;
}
public Scope getEnclosingScope() {
return enclosingScope;
}
public void define(Symbol sym) {
symbols.put(sym.name, sym);
}
public Symbol resolve(String name) {
if(symbols.get(name) == null && enclosingScope != null)
return enclosingScope.resolve(name);
return null;
}
}
传入输入。
int f(int x, float y)
{
g();
i = 3;
g = 4;
return x + y;
}
void g()
{
int x = 0;
float y;
y = 9;
f();
z();
y();
x = f;
}
生成输出。
[@14,35:35=‘i‘,<22>,4:1]no such variable: i
[@18,44:44=‘g‘,<22>,5:1]is not a variable
[@23,61:61=‘x‘,<22>,6:8]no such variable: x
[@25,65:65=‘y‘,<22>,6:12]no such variable: y
Symbol [name=g, type=void]
[@41,111:111=‘y‘,<22>,12:1]no such variable: y
[@49,129:129=‘z‘,<22>,14:1]no such function: z
[@53,136:136=‘y‘,<22>,15:1]no such function: y
[@57,144:144=‘x‘,<22>,16:1]no such variable: x
[@59,148:148=‘f‘,<22>,16:5]is not a variable
The Definitive Antlr 4 第8章学习笔记
标签:
原文地址:http://blog.csdn.net/revivedsun/article/details/51591917