码迷,mamicode.com
首页 > 其他好文 > 详细

The Definitive Antlr 4 第8章学习笔记

时间:2016-06-06 01:20:29      阅读:246      评论:0      收藏:0      [点我收藏+]

标签:

第8章介绍了四个例子,讲述了Antlr了实际应用。下面的阅读笔记中,最终实现与书中并非完全一致。其中调用关系仅输出关系,而未转换为Dot语言。

加载CSV数据

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转换为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()。因此生成调用关系的思路如下:

  1. 编写Cymbol语言文法,并生成响应的XXListener。
  2. 在访问到函数声明时,记录当前函数名。
  3. 在访问到函数声明内部的调用语句时,记录调用关系。(当前函数名,调用语句)构成一对调用关系。

最后完成的文法与程序代码如下。

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

(0)
(0)
   
举报
评论 一句话评论(0
登录后才能评论!
© 2014 mamicode.com 版权所有  联系我们:gaon5@hotmail.com
迷上了代码!