前言

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

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

Java解析实例
  1. STGroup group = new STGroupFile("demo.stg");//绝对路径
  2. ST st = group.getInstanceOf("decl");//获取模板实例
  3. st.add("type", "int");//填充占位符
  4. st.add("name", "x");
  5. st.add("value", 0);
  6. 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:
  1. 3+4*5

output:

  1. ; public class Calc extends Object { ...}
  2. .class public Calc
  3. .super java/lang/Object
  4.  
  5. ; public Calc() { super(); } // calls java.lang.Object()
  6. .method public ()V
  7. aload_0
  8. invokenonvirtual java/lang/Object/()V
  9. return
  10. .end method
  11.  
  12. ; main(): Expr.g will generate bytecode in this method
  13. .method public static main([Ljava/lang/String;)V
  14. .limit stack ; how much stack space do we need?
  15. .limit locals ; how many locals do we need?
  16. getstatic java/lang/System/out Ljava/io/PrintStream;
  17. ; code translated from input stream
  18. ; compute 3+4*5
  19.  
  20. ldc
  21. ldc
  22. ldc
  23. imul
  24. iadd
  25. ; print result on top of stack
  26. invokevirtual java/io/PrintStream/println(I)V
  27. return
  28. .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:
  1. int x;
  2. void foo(){
  3. int y;
  4. y = 1;
  5. g(34, y);
  6. x = h();
  7. }
output:
  1. int x;
  2. void foo(){
  3. int y;
  4. y = 1; write_to("y", y)
  5. g(34, y); call("g");
  6. x = eval_r("h", h()); write_to("x", x)
  7. }
用到的文件包括,见文后代码部分
(1)输出template、设置rewrite选项为true的文法CMinus.g
(2)测试文件Test.java
 
当然也可以采用IR之AST的树文法实现重写器。
 
 
代码
 
(生成器1)生成AST的文法Expr.g
  1. grammar Expr;
  2.  
  3. options{output=AST;ASTLabelType=CommonTree;}
  4.  
  5. @header{
  6. package template;
  7. import java.util.HashMap;
  8. }
  9.  
  10. @members {
  11. int numOps = 0;//operation count
  12. HashMap locals = new HashMap();//local variable name-count map
  13. int localVarNum = 1;//local variable count
  14. }
  15.  
  16. prog : stat+;
  17.  
  18. stat : expr NEWLINE -> expr
  19. | ID '=' expr NEWLINE
  20. {
  21. if(locals.get($ID.text)==null){
  22. locals.put($ID.text, new Integer(localVarNum++));
  23. }
  24. }
  25. -> ^('=' ID expr)
  26. | NEWLINE ->
  27. ;
  28.  
  29. expr : multExpr ('+'^|'-'^) multExpr {numOps++;}
  30. ;
  31.  
  32. multExpr: atom ('*'^ atom {numOps++;})*
  33. ;
  34.  
  35. atom : INT
  36. | ID
  37. | '('! expr ')'!;
  38.  
  39. ID : ('a'..'z'|'A'..'Z'|'_') ('a'..'z'|'A'..'Z'|'0'..'9'|'_')* ;
  40. INT : '0'..'9'+;
  41. WS : ( ' '| '\t'| '\r'| '\n') {$channel=HIDDEN;} ;
  42. NEWLINE : '\r'?'\n';
(生成器2)树文法Gen.g
  1. tree grammar Gen;
  2.  
  3. options {
  4. tokenVocab=Expr;
  5. ASTLabelType=CommonTree;
  6. output=template;
  7. }
  8.  
  9. @header {
  10. package template;
  11. import java.util.HashMap;
  12. }
  13.  
  14. @members {
  15. HashMap locals;
  16. }
  17.  
  18. prog[int numOps, HashMap locals]
  19. @init {this.locals= locals;}
  20. : (s+=stat)+ -> jasminFile(instructions={$s}, maxStackDepth={numOps+1+1}, maxLocals={locals.size()+1})
  21. ;
  22. stat : expr -> exprStat(v={$expr.st}, descr={$expr.text})
  23. | ^('=' ID expr) -> assign(id={$ID.text}, descr={$text}, varNum={locals.get($ID.text)}, v={$expr.st})
  24. ;
  25.  
  26. expr returns [int value]
  27. : ^('+' a=expr b=expr) -> add(a={$a.st}, b={$b.st})
  28. | ^('-' a=expr b=expr) -> sub(a={$a.st}, b={$b.st})
  29. | ^('*' a=expr b=expr) -> mult(a={$a.st}, b={$b.st})
  30. | INT -> int(v={$INT.text})
  31. | ID -> var(id={$ID.text}, varNum={locals.get($ID.text)})
  32. ;
(生成器3)模板组文件ByteCode.stg
  1. group ByteCode;
  2.  
  3. jasminFile(maxStackDepth, maxLocals, instructions) ::= <<
  4. ; public class Calc extends Object { ...}
  5. .class public Calc
  6. .super java/lang/Object
  7.  
  8. ; public Calc() { super(); } // calls java.lang.Object()
  9. .method public \()V
  10. aload_0
  11. invokenonvirtual java/lang/Object/\()V
  12. return
  13. .end method
  14.  
  15. ; main(): Expr.g will generate bytecode in this method
  16. .method public static main([Ljava/lang/String;)V
  17. .limit stack ; how much stack space do we need?
  18. .limit locals ; how many locals do we need?
  19. getstatic java/lang/System/out Ljava/io/PrintStream;
  20. ; code translated from input stream
  21.  
  22. ; print result on top of stack
  23. invokevirtual java/io/PrintStream/println(I)V
  24. return
  25. .end method
  26. >>
  27.  
  28. assign(varNum, v ,descr, id) ::= <<
  29. ;compute descr
  30. istore : id
  31. >>
  32.  
  33. exprStat(v, descr) ::= <<
  34. ; compute
  35. >>
  36.  
  37. add(a, b) ::= <<
  38. iadd
  39. >>
  40.  
  41. sub(a, b) ::= <<
  42. isub
  43. >>
  44.  
  45. mult(a, b) ::= <<
  46. imul
  47. >>
  48.  
  49. int(v) ::= "ldc "
  50.  
  51. var(id, varNum) ::= "iload ; "
(生成器4)测试文件ByteCodeGeneratorTest.java
  1. package template;
  2.  
  3. import java.io.File;
  4. import java.io.FileInputStream;
  5. import java.io.FileReader;
  6. import java.io.InputStream;
  7.  
  8. import org.antlr.runtime.ANTLRInputStream;
  9. import org.antlr.runtime.CommonTokenStream;
  10. import org.antlr.runtime.tree.CommonTree;
  11. import org.antlr.runtime.tree.CommonTreeNodeStream;
  12. import org.antlr.stringtemplate.StringTemplate;
  13. import org.antlr.stringtemplate.StringTemplateGroup;
  14.  
  15. public class ByteCodeGeneratorTest {
  16.  
  17. public static void main(String[] args) throws Exception {
  18. FileReader groupFileReader = new FileReader("D:/workspace/maven/antlrv3/grammar/template/generator/ByteCode.stg");
  19. StringTemplateGroup templateGroup = new StringTemplateGroup(groupFileReader);
  20. groupFileReader.close();
  21.  
  22. // [1]3+4*5
  23. // [2]a=3+4
  24. // a
  25. InputStream is = new FileInputStream(new File("D:/workspace/maven/antlrv3/language/template.test"));
  26. ANTLRInputStream inputStream = new ANTLRInputStream(is);
  27. ExprLexer lexer = new ExprLexer(inputStream);
  28. CommonTokenStream tokenStream = new CommonTokenStream(lexer);
  29. ExprParser parser = new ExprParser(tokenStream);
  30. ExprParser.prog_return r = parser.prog();
  31.  
  32. // tree walker to generate template's value
  33. CommonTree tree = (CommonTree) r.getTree();
  34. CommonTreeNodeStream treeNodeStream = new CommonTreeNodeStream(tree);
  35. treeNodeStream.setTokenStream(tokenStream);
  36.  
  37. Gen walker = new Gen(treeNodeStream);
  38. walker.setTemplateLib(templateGroup);
  39. Gen.prog_return r2 = walker.prog(parser.numOps, parser.locals);
  40.  
  41. StringTemplate output = (StringTemplate) r2.getTemplate();
  42. System.out.println(output.toString());
  43. }
  44.  
  45. }
 
(重写器1)输出template、设置rewrite选项为true的文法CMinus.g
  1. grammar CMinus;
  2. options{output=template;rewrite=true;}
  3.  
  4. program : declaration+;
  5.  
  6. declaration
  7. : variable
  8. | function
  9. ;
  10.  
  11. variable: type ID';';
  12.  
  13. function: type ID '(' (formalParameter (',' formalParameter)* )? ')' block;
  14.  
  15. formalParameter
  16. : type ID;
  17. type : 'int'|'void';
  18.  
  19. stat
  20. scope{boolean isAssign;}
  21. : expr ';'
  22. | block
  23. | ID '=' {$stat::isAssign=true;} expr ';'
  24. -> template(id={$ID.text}, assign={$text})
  25. " write_to(\"\", )"
  26. | ';'
  27. ;
  28.  
  29. block : '{' variable* stat* '}';
  30.  
  31. expr : ID
  32. | INT
  33. | ID '(' (expr (',' expr)* )? ')'
  34. -> {$stat::isAssign}? template(id={$ID.text}, e={$text})
  35. "eval_r(\"\", )"
  36. ->template(id={$ID.text}, e={$text})
  37. "; call(\"\")"
  38. | '(' expr ')'
  39. ;
  40.  
  41. ID : ('a'..'z'|'A'..'Z'|'_') ('a'..'z'|'A'..'Z'|'0'..'9'|'_')* ;
  42. INT : '0'..'9'+ ;
  43. COMMENT : '' {$channel=HIDDEN;} ;
  44. WS : ( ' '| '\t'| '\r'| '\n') {$channel=HIDDEN;};
(重写器2)测试文件Test.java
  1. package template.rewriter;
  2.  
  3. import java.io.File;
  4. import java.io.FileInputStream;
  5. import java.io.InputStream;
  6.  
  7. import org.antlr.runtime.ANTLRInputStream;
  8. import org.antlr.runtime.TokenRewriteStream;
  9.  
  10. public class Test {
  11.  
  12. public static void main(String[] args) throws Exception {
  13. InputStream is = new FileInputStream(new File("D:/workspace/maven/antlrv3/language/rewriter/CMinus.test"));
  14. ANTLRInputStream input = new ANTLRInputStream(is);
  15. CMinusLexer lexer = new CMinusLexer(input);
  16.  
  17. TokenRewriteStream tokens = new TokenRewriteStream(lexer);
  18. CMinusParser parser = new CMinusParser(tokens);
  19. parser.program();
  20.  
  21. System.out.println(tokens.toString());
  22. }
  23.  
  24. }
 
 

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. PHP慢脚本日志和Mysql的慢查询日志(转)

      1.PHP慢脚本日志 间歇性的502,是后端 PHP-FPM 不可用造成的,间歇性的502一般认为是由于 PHP-FPM 进程重启造成的. 在 PHP-FPM 的子进程数目超过的配置中的数量时候, ...

  2. $(":input").each()和$.each()的区别

    <body>    <form action="strutsAction" method="get">        <input ...

  3. ASP.NET MVC学习之路由篇(1)

    1.基本路由 RouteConfig.cs: 1 public class RouteConfig 2 { 3 public static void RegisterRoutes(RouteColle ...

  4. Apache—DBUtils

    简介 commons-dbutils 是 Apache 组织提供的一个开源 JDBC工具类库,它是对JDBC的简单封装,学习成本极低,并且使用dbutils能极大简化jdbc编码的工作量,同时也不会影 ...

  5. "琳琅满屋"调查问卷 心得体会及结果分析

    ·关于心得体会       当时小组提出这个校园二手交易市场的时候,就确定了对象范围,仅仅是面向在校大学生,而且在我们之前就已经有了很多成功的商品交易的例子可以让我们去借鉴,再加上我们或多或少的有过网 ...

  6. HighAvailability和LoadBalancer

    HighAvailability                         LoadBalancer 红帽RHCS                                lvs(三种工作 ...

  7. Android 插入图片到媒体库

    今天介绍一下在Android中怎么插入图片到媒体库,下面看代码: final String titleName = Function.md5(imageUri.toLowerCase()) + &qu ...

  8. Spring AOP中pointcut expression表达式解析 及匹配多个条件

    Spring中事务控制相关配置: <bean id="txManager" class="org.springframework.jdbc.datasource.D ...

  9. [转]Putty中文乱码解决方法

    from: http://www.putty.ws/putty-luanma putty使用的过程中,你是否遇到过putty出现中文乱码的情况呢?putty终端出现乱码,是情况,多数是由于系统或软件缺 ...

  10. switch… case 语句的用法(一)

    public class Test7 { public static void main(String[] args) { int i=5; switch(i) { case 1: System.ou ...