前言

真正意义上的程序员都很懒,懒的连多余的一行代码也不写。
如果能将底层满手油污的活儿都可以交给别人去做,自己就扮演个智囊团成员的角色,生活会比想象中的还要惬意。
严格的按照指令执行长时间不知疲倦的计算是计算机所擅长的事情,那么给它一个代码模板,留些运行时它可以获取的值的占位符,再揉进一些固定套路的指令,程序员的生活也可以很美好。
尽管如此,但工具永远不是软件工程中的银弹(看看人月神话吧),适可而止,让富有攻击性的洞察力指引前进的方向,而在实际工作中持适当的保守性态度。
 
内容
1 StringTemplate基本概念
2 为什么要将代码/文本生成逻辑和所生成的代码/文本内容区分开来
3 重写器(rewriter)和生成器(generator)
 
 
1 StringTemplate基本概念
StringTemplate是一个模板引擎,模板的含义是带有占位符(hole/placeholder)的字符串/文本,属性是传递给模板的参数,用于填充占位符;在模板中可以定义模板表达式,模板表示式可以理解为属性的函数。
 
StringTemplate将模板划分为文本块和属性表达式,属性表达式的默认形式为,主要在不改变属性表达式界定符(即<>)的情况下,文本块中包含'<'、'>'字符时均需转义为'\<'、'\>'(这一点在文中字节码生成器的示例中没有指出,使得Java字节码中函数无法在模板中定义)。
 
API概述
详细的内容参见官方文档:StringTemplate4官方在线文档
模板文件后缀名为st,模板组文件为stg
模板组示例(模板的使用与此类似)
//demo.stg
decl(type, name, value) ::= " ;"
init(v) ::= " = "

decl(type, name, value)、init(v)为模板名和模板参数,为条件表达式(conditions),其它<...>为占位符。

Java解析实例
STGroup group = new STGroupFile("demo.stg");//绝对路径
ST st = group.getInstanceOf("decl");//获取模板实例
st.add("type", "int");//填充占位符
st.add("name", "x");
st.add("value", 0);
String result = st.render();//模板输出
2 为什么要将代码/文本生成逻辑和所生成的代码/文本内容区分开来
Parr指出翻译器的最终步骤是从解析产生的内部数据结构中生成结构化文本,翻译器中提交结构化文本的元件称为提交器(emitter)。
根据提交器所提交的文本与输入文本之间的关系,存在两类翻译器:重写器(rewriter)和生成器(generator)。
翻译器中元件抽象:(1)模型:解析输入生成的内部数据结构;(2)视图:提交器输入的结构化文本;(3)控制器:输入解析器和中间表示IR的遍历器。
将提交器与代码生成逻辑和生成中相应的计算显示的区分开来,遵循松耦合的良好软件工程实践:可以在不影响代码生成逻辑和相应计算的前提下,替换提交器提交的文本,而模板引擎的引入极大的便利化这一文本替换操作过程。
 
3 重写器(rewriter)和生成器(generator)
重写器、生成器的概念
重写器生成的输出与输入很类似,这种翻译器可以直接在输入上直接做修改,典型的应用包括读入源代码文件,做小幅度的修改、插入stub代码用于调试、代码覆盖度和性能度量,Evernote的"悦读"也可以算是重写器。
生成器生成的输入与输入存在很大的不同,典型的应用包括生成一段信息的内容摘要、生成源代码信息的报告、从Java代码中生成接口、从Java类中提取公共接口等。
自然的,重写器也可以理解为生成器。
 
在动作中引用模板
 语法  说明
 %foo(a={}, b={},...)  模板构建语法:创建模板foo,设置其属性a、b...
 %({<<nameExpr>>}(a={}...))  间接模板构造引用,nameExpr是计算出的模板名称,其他部分与模板构造语法相同
 %x.y=<<z>>  将模板x的属性y赋值为值z
 %{<<expr>>}.y=<<z>>  将StringTemplate类型表达式expr的属性赋值为表达式z的值
 %{<<stringExpr>>}  从String类型stringExpr创建匿名模板
 
 
生成器案例
说明:生成Java字节码
工具:jasmin(Jasmin Home Page)
jasmin是最初设计用于"Java虚拟机"一书(该书由Jon Meyer,Troy Downing撰写),是SourceForge上的开源项目。
jasmin是JVM的汇编器,其接受的文件形式是Java字节码的ASCII形式(后缀名j),这种文件是采用JVM指令集以类似于汇编语言风格编写的。jasmin将这类文件转换为Java字节码文件,可直接在运行时环境中运行。
JVM指令集请参考JVM规范。
sample:
input:
3+4*5

output:

; public class Calc extends Object { ...}
.class public Calc
.super java/lang/Object

; public Calc() { super(); } // calls java.lang.Object()
.method public ()V
   aload_0
   invokenonvirtual java/lang/Object/()V
   return
.end method

; main(): Expr.g will generate bytecode in this method
.method public static main([Ljava/lang/String;)V
   .limit stack  ; how much stack space do we need?
   .limit locals  ; how many locals do we need?
   getstatic java/lang/System/out Ljava/io/PrintStream;
   ; code translated from input stream
   ; compute 3+4*5

   ldc
   ldc
   ldc
   imul
   iadd
   ; print result on top of stack
   invokevirtual java/io/PrintStream/println(I)V
   return
.end method
将输出拷贝至文件generator.j中,执行jasmin转换命令java -jar jasmin.jar generator.j
生成Calc.class,直接运行java Calc.class,输出23。
 
用到的文件包括,见文后代码部分
(1)生成AST的文法Expr.g
(2)树文法Gen.g,根据模板内容输出
(3)模板组文件ByteCode.stg(其中jasminFile模板采用该书源码文件,文中未给出)
(4)测试文件ByteCodeGeneratorTest.java
 
重写器案例
说明:跟踪C-语言中过程、函数调用和变量赋值
过程指没有返回值的函数,翻译后以call()指明;函数调用用eval_r()指明;变量赋值用write_to()指明。
sample:
input:
int x;
void foo(){
int y;
y = 1;
g(34, y);
x  = h();
}
output:
int x;
void foo(){
int y;
y = 1; write_to("y", y)
g(34, y); call("g");
x  = eval_r("h", h()); write_to("x", x)
}
用到的文件包括,见文后代码部分
(1)输出template、设置rewrite选项为true的文法CMinus.g
(2)测试文件Test.java
 
当然也可以采用IR之AST的树文法实现重写器。
 
 
代码
 
(生成器1)生成AST的文法Expr.g
grammar Expr;

options{output=AST;ASTLabelType=CommonTree;}

@header{
package template;
import java.util.HashMap;
}

@members {
int numOps = 0;//operation count
HashMap locals = new HashMap();//local variable name-count map
int localVarNum = 1;//local variable count
}

prog : stat+;

stat : expr NEWLINE -> expr
| ID '=' expr NEWLINE
{
if(locals.get($ID.text)==null){
locals.put($ID.text, new Integer(localVarNum++));
}
}
-> ^('=' ID expr)
| NEWLINE ->
;

expr : multExpr ('+'^|'-'^) multExpr {numOps++;}
;

multExpr: atom ('*'^ atom {numOps++;})*
;

atom : INT
| ID
| '('! expr ')'!;

ID  : ('a'..'z'|'A'..'Z'|'_') ('a'..'z'|'A'..'Z'|'0'..'9'|'_')* ;
INT : '0'..'9'+;
WS  :   ( ' '| '\t'| '\r'| '\n') {$channel=HIDDEN;} ;
NEWLINE : '\r'?'\n';
(生成器2)树文法Gen.g
tree grammar Gen;

options {
tokenVocab=Expr;
ASTLabelType=CommonTree;
output=template;
}

@header {
package template;
import java.util.HashMap;
}

@members {
HashMap locals;
}

prog[int numOps, HashMap locals]
@init {this.locals= locals;}
: (s+=stat)+ -> jasminFile(instructions={$s}, maxStackDepth={numOps+1+1}, maxLocals={locals.size()+1})
;
stat : expr -> exprStat(v={$expr.st}, descr={$expr.text})
| ^('=' ID expr) -> assign(id={$ID.text}, descr={$text}, varNum={locals.get($ID.text)}, v={$expr.st})
;

expr returns [int value]
: ^('+' a=expr b=expr) -> add(a={$a.st}, b={$b.st})
| ^('-' a=expr b=expr) -> sub(a={$a.st}, b={$b.st})
| ^('*' a=expr b=expr) -> mult(a={$a.st}, b={$b.st})
| INT -> int(v={$INT.text})
| ID -> var(id={$ID.text}, varNum={locals.get($ID.text)})
;
(生成器3)模板组文件ByteCode.stg
group ByteCode;

jasminFile(maxStackDepth, maxLocals, instructions) ::= <<
; public class Calc extends Object { ...}
.class public Calc
.super java/lang/Object

; public Calc() { super(); } // calls java.lang.Object()
.method public \()V
   aload_0
   invokenonvirtual java/lang/Object/\()V
   return
.end method

; main(): Expr.g will generate bytecode in this method
.method public static main([Ljava/lang/String;)V
   .limit stack ; how much stack space do we need?
   .limit locals ; how many locals do we need?
   getstatic java/lang/System/out Ljava/io/PrintStream;
   ; code translated from input stream

   ; print result on top of stack
   invokevirtual java/io/PrintStream/println(I)V
   return
.end method
>>

assign(varNum, v ,descr, id) ::= <<
;compute descr
istore : id
>>

exprStat(v, descr) ::= <<
; compute
>>

add(a, b) ::= <<
iadd
>>

sub(a, b) ::= <<
isub
>>

mult(a, b) ::= <<
imul
>>

int(v) ::= "ldc "

var(id, varNum) ::= "iload ; "
(生成器4)测试文件ByteCodeGeneratorTest.java
package template;

import java.io.File;
import java.io.FileInputStream;
import java.io.FileReader;
import java.io.InputStream;

import org.antlr.runtime.ANTLRInputStream;
import org.antlr.runtime.CommonTokenStream;
import org.antlr.runtime.tree.CommonTree;
import org.antlr.runtime.tree.CommonTreeNodeStream;
import org.antlr.stringtemplate.StringTemplate;
import org.antlr.stringtemplate.StringTemplateGroup;

public class ByteCodeGeneratorTest {

public static void main(String[] args) throws Exception {
FileReader groupFileReader = new FileReader("D:/workspace/maven/antlrv3/grammar/template/generator/ByteCode.stg");
StringTemplateGroup templateGroup = new StringTemplateGroup(groupFileReader);
groupFileReader.close();

// [1]3+4*5
// [2]a=3+4
// a
InputStream is = new FileInputStream(new File("D:/workspace/maven/antlrv3/language/template.test"));
ANTLRInputStream inputStream = new ANTLRInputStream(is);
ExprLexer lexer = new ExprLexer(inputStream);
CommonTokenStream tokenStream = new CommonTokenStream(lexer);
ExprParser parser = new ExprParser(tokenStream);
ExprParser.prog_return r = parser.prog();

// tree walker to generate template's value
CommonTree tree = (CommonTree) r.getTree();
CommonTreeNodeStream treeNodeStream = new CommonTreeNodeStream(tree);
treeNodeStream.setTokenStream(tokenStream);

Gen walker = new Gen(treeNodeStream);
walker.setTemplateLib(templateGroup);
Gen.prog_return r2 = walker.prog(parser.numOps, parser.locals);

StringTemplate output = (StringTemplate) r2.getTemplate();
System.out.println(output.toString());
}

}
 
(重写器1)输出template、设置rewrite选项为true的文法CMinus.g
grammar CMinus;
options{output=template;rewrite=true;}

program : declaration+;

declaration
: variable
| function
;

variable: type ID';';

function: type ID '(' (formalParameter (',' formalParameter)* )? ')' block;

formalParameter
: type ID;
type : 'int'|'void';

stat
scope{boolean isAssign;}
: expr ';'
| block
| ID '=' {$stat::isAssign=true;} expr ';'
-> template(id={$ID.text}, assign={$text})
" write_to(\"\", )"
| ';'
;

block : '{' variable* stat* '}';

expr : ID
| INT
| ID '(' (expr (',' expr)* )? ')'
-> {$stat::isAssign}? template(id={$ID.text}, e={$text})
"eval_r(\"\", )"
->template(id={$ID.text}, e={$text})
"; call(\"\")"
| '(' expr ')'
;

ID  : ('a'..'z'|'A'..'Z'|'_') ('a'..'z'|'A'..'Z'|'0'..'9'|'_')* ;
INT : '0'..'9'+ ;
COMMENT :   '' {$channel=HIDDEN;} ;
WS  :   ( ' '| '\t'| '\r'| '\n') {$channel=HIDDEN;};
(重写器2)测试文件Test.java
package template.rewriter;

import java.io.File;
import java.io.FileInputStream;
import java.io.InputStream;

import org.antlr.runtime.ANTLRInputStream;
import org.antlr.runtime.TokenRewriteStream;

public class Test {

public static void main(String[] args) throws Exception {
InputStream is = new FileInputStream(new File("D:/workspace/maven/antlrv3/language/rewriter/CMinus.test"));
ANTLRInputStream input = new ANTLRInputStream(is);
CMinusLexer lexer = new CMinusLexer(input);

TokenRewriteStream tokens = new TokenRewriteStream(lexer);
CMinusParser parser = new CMinusParser(tokens);
parser.program();

System.out.println(tokens.toString());
}

}
 
 

ANTLR3完全参考指南读书笔记[07]的更多相关文章

  1. ANTLR3完全参考指南读书笔记[01]

    引用 Terence Parr. The Definitive ANTLR Reference, Building Domain Specific Languages(antlr3 version). ...

  2. ANTLR3完全参考指南读书笔记[06]

    前言 这段时间在公司忙的跟狗似的,但忙的是没多少技术含量的活儿. 终于将AST IR和tree grammar过了一遍,计划明天写完这部分的读书笔记.   内容 1 内部表示AST构建 2 树文法   ...

  3. ANTLR3完全参考指南读书笔记[02]

    前言 程序语言是什么? 用wiki上的描述,程序语言是一种人工设计的语言,用于通过指令与机器交互:程序语言是编程程序的标记,而程序是一种计算或算法的描述.详细介绍和背景信息参考: Programmin ...

  4. ANTLR3完全参考指南读书笔记[08]

    前言 不要让用户被那些“专业术语”吓住! 用心设计的提示和反馈信息是软件设计者的“职业良心”.   内容 1 存在哪些错误? 2 美化错误提示 3 错误恢复策略   1 存在哪些错误? 在DSL语言开 ...

  5. ANTLR3完全参考指南读书笔记[05]

    前言 仅生成给出true/false的识别器是没有多大用处的,自然的就有在识别过程中遇到某一结构时执行一段代码.存储该结构中信息的想法. ANTLR提供了在文法中嵌入属性和动作超级混合“文法”,可以生 ...

  6. ANTLR3完全参考指南读书笔记[04]

    前言 学习框架或第三方库的方法是什么 (1)少量的浏览manual或tutoral,只关注程序所需的特征,再完善其详细内容和特征的认识? (2)花大量的时间研究详细内容,再考虑程序实现? 这是个先有鸡 ...

  7. ANTLR3完全参考指南读书笔记[03]

    前言 文中第4章内容有点多,有点枯燥,但不坚持一下,之前所做的工作就白做了. 再次确认一下总体目标: protege4编辑器中Class Definition中语法解析和错误提示: Java虚拟机规范 ...

  8. 机器学习实战 - 读书笔记(07) - 利用AdaBoost元算法提高分类性能

    前言 最近在看Peter Harrington写的"机器学习实战",这是我的学习笔记,这次是第7章 - 利用AdaBoost元算法提高分类性能. 核心思想 在使用某个特定的算法是, ...

  9. HTTP权威指南读书笔记

    HTTP权威指南笔记 读书有两种境界,第一种境界是将书读薄,另一种是读厚.本篇文章就是HTTP权威指南的读书笔记,算是读书的第一重境界,将厚书读薄.文章对HTTP的一些关键概念做了比较详细的概述,通读 ...

随机推荐

  1. AngularJS recursive(递归)

    工作中我们经常要遍历多层数据,如果数据是已知层级的话,用 ng-repeat 就搞定了,要是数据深度是无限的呢,或者我们要实现一个无限层级的 tree 的时候,该怎么办? 答案是使用 ng-inclu ...

  2. 数据结构-Stack和Queue

    实现: #include "c2_list.h" template <typename object> class Stack{ public: bool isEmpt ...

  3. 腾讯微博数据抓取(java实现)

    不多说,同样贴出相关代码 参数实体: package token.def; import java.io.Serializable; import java.util.Properties; publ ...

  4. 踏着前人的脚印学Hadoop——结构、重点

    HDFS作为一个分布式文件系统,是所有这些项目的基础.分析好HDFS,有利于了解其他系统.由于Hadoop的HDFS和MapReduce是同一个项目,我们就把他们放在一块,进行分析. 如果把整个had ...

  5. C++学习之类的构造函数、析构函数

    在C++的类中,都会有一个或多个构造函数.一个析构函数.一个赋值运算操作符.即使我们自己定义的类中,没有显示定义它们,编译器也会声明一个默认构造函数.一个析构函数和一个赋值运算操作符.例如: //声明 ...

  6. jQuery 常用动画

    <!DOCTYPE html> <html> <head> <meta charset="UTF-8"> <title> ...

  7. <转>用thinkPHP实现验证码的功能

    许多系统的登录都有验证码,而如果使用thinkPHP框架搭建网站的话,验证码的生成和验证就比较容易了 1.生成验证码 thinkPHP有对应生成验证码的方法 要使用验证码,需要导入扩展类库中的ORG. ...

  8. 2013年7月底至8月初51Aspx源码发布详情

    兼职人员信息管理系统源码  2013-8-2 [VS2008]2013.8.2更新内容:修改了一级菜单不能修改的bug.功能介绍:兼职人员信息管理:添加,修改,删除,查询,支持数据导出Excel,按多 ...

  9. HashMap简单理解

    1. hashmap基于哈希表的map接口实现,此实现提供所有可选的映射操作,并允许使用 null 值和 null 键.(除了非同步和允许使用 null 之外,HashMap 类与 Hashtable ...

  10. Linksys WRT120N路由器备份文件解析

    Perusing the release notes for the latest Linksys WRT120N firmware, one of the more interesting comm ...