学习笔记
Posted FserSuN
tags:
篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了学习笔记相关的知识,希望对你有一定的参考价值。
第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>
JSON转换为XML总结
通过json转xml的例子也可以将相关方法应用到其它格式转换。通过这个例子总结以下转换方式:
- 转换都是发生在exit方法上,即一个文法符号访问完毕后处理。
- 处理过程是递归的进行,最终获取转换后的数据。在处理一个节点的时候将通过map存储。父节点通过map获取子节点的结果。最终通过根拿到最终结果。
在处理其它转换时应用此方式即可完成转换。
生成调用关系
这里调用关系是指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()。因此生成调用关系的思路如下:
- 编写Cymbol语言文法,并生成相应的XXListener。
- 在访问到函数声明时,记录当前函数名。
- 在访问到函数声明内部的调用语句时,记录调用关系。(当前函数名,调用语句)构成一对调用关系。
最后完成的文法与程序代码如下。
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-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的使用就需要根据作用域来判断了。
数字所标注的表示作用域。从任意一个结点到根结点形成一个作用域栈。为了寻找符号,首先从所引用符号所在的定义域内开始,一直到根结点去寻找要找的符号。
根据符号表的操作,最终的实现可以分为符号定义与查找两部分。对于定义,需要监听变
以上是关于学习笔记的主要内容,如果未能解决你的问题,请参考以下文章