html语义化标签例子,HTML 语义化标签(示例代码)

作为程序猿,每天都在写代码,但是有没有想过通过代码对写好的代码”动点手脚”呢?今天就与大家分享——如何通过用AST语法树改写Java代码。

先抛一个问题:如何将图一代码改写为图二?

voidsomeMethod(){

Stringrst=callAnotherMethod();

LogUtil.log(TAG,”这里是一条非常非常长,比唐僧还啰嗦的日志信息描述,但是我短一点还不方便进行错误日志分析,调用callSomeMethod返回的结果是:”+rst);

……

}

图一

voidsomeMethod(){

Stringrst=callAnotherMethod();

LogUtil.log(TAG,””+rst);

……

}

图二

此题需要把代码中和程序逻辑无关的字符串提取出来,替换为id。比如个推日志输出类,缩短日志描述信息后,输出的日志就随之变短,根据映射表可以恢复真实原始日志。

通过何种方案改写?

你可能会想通过万能的“正则表达式”匹配替换,但当代码较为复杂时(如下图所示),使用“正则表达法”则会将问题复杂化,难以确保所有代码的完美覆盖并匹配。若通过AST语法树,可以很好地解决此问题。

import staticLog.log;

log(“i am also the log”);

Stringaa=“i am variable string”;

log(“i am the part of log”+ aa +String.format(“current time is %d”,System.currentTimeMillis()));

什么是AST语法树?

AST(Abstract syntax tree)即为“抽象语法树”,简称语法树,指代码在计算机内存的一种树状数据结构,便于计算机理解和阅读。

9c6753a98e7e33fbd2e683c4b831b2a2.png

一般只有语言的编译器开发人员或者从事语言设计的人员才涉及到语法树的提取和处理,所以很多人会对这个概念比较陌生。

97ad35605286a88fb857d24b3889a0d8.png

上图即为语法树,左边树的节点对应右边相同颜色覆盖的代码块。

5d51430760e091a3400720b90fdc5716.png

众所周知,Java 编译流程(上图)中也有对AST语法树的提取处理,那是否可以在此环节操作语法树呢?由于编译链代码栈太深,鲜有对外的接口和文档,使得其可操作性不强。不过,如果采用迂回战术如下图所示,可以对其进行操作。

eb4326a7bbb8f603455f4a4b50c24d1c.png

个推log-rewrite项目改写日志,就是用AST语法树进行的,流程图如下图所示。

f6f47536f5e670eac29ee487f849210f.png

先把所有源码解析为AST语法树,遍历每一个编译单元与单元的类声明,在类声明里根据日志方法的签名找到所有的方法调用,然后遍历每个方法调用,将方法调用的第二个参数表达式放入递归方法,对字符串字面值进行改写。

对应的代码较为简短, 使用github的 Netflix-Skunkworks/rewrite开源库与kotlin语言,能读懂Java的你也一定能读明白。

valJavaSources:List//Java source file path list

OracleJdkParser().parse(JavaSources)

.forEach { unit ->

unit.refactor(Consumer { tx ->

unit.classes.forEach { clazz ->

clazz.findMethodCalls("demo.LogUtillog(String,String)").forEach{ mc ->

valargs = mc.args.args

valexpression = args[1]

logMapping.refactor(clazz, expression, tx)

}

}

valfix = tx.fix()

valnewFile = ...//dist Source File ...

newFile.writeText(fix.print())

})

}

funrefactor(clazz: Tr.ClassDecl,target: Expression,refactor: Refactor,originSb: StringBuilder):Unit{

when(target) {

is Tr.Literal -> {

refactor.changeLiteral(target) { t ->

valid = pushMapping(clazz, t)//pushLiteral to mapping and return id

originSb.append("$PREFIX$t$POSTFIX")

return@changeLiteral rewriteNormal(id)

}

}

}

is Tr.Binary -> {

refactor(clazz, target.left, refactor, originSb)

refactor(clazz, target.right, refactor, originSb)

}

}

}

如果想将日志恢复原样,可根据前缀、后缀定制正则表达式,逐行匹配替换。如下图所示。

valnormalPattern = Pattern.compile("()")

logFiles.forEach { file ->

file.bufferedReader().use { reader ->

File(distDir, file.name).bufferedWriter().use { writer ->

varline:String

while(true){

line = reader.readLine()

if(line ==null)break

valmatcher = normalPattern.matcher(line)

varnewLine:String= line +""

while(matcher.find()) {//normal recover

valtoken = matcher.group(1)

valprojectName = matcher.group(2)

valappVersion = matcher.group(3).toInt()

valtargetVersion = matcher.group(4).toInt()

valid = matcher.group(5).toLong()

valreplaceMent = findReplacement(projectName,appVersion, targetVersion, id)

newLine = newLine.replace(token, replaceMent)

}

writer.write(newLine)

writer.newLine()

}

}

}

AST有哪些应用场景?

1、    编译工具从ant到gradle的切换

the ant env SDK_VERSION=2.0.0.2

// #expand public static final Stringsdk_conf_version = "%SDK_VERSION%";

publicstaticfinalStringsdk_conf_version ="1.0.0.1";

4778d028baa01a994a33e1e33c1708eb.png

publicstaticfinalStringsdk_conf_version = “2.0.0.2";

//public static final String sdk_conf_version= "1.0.0.1";

此项目起步于ant主流时期,随着技术日渐成熟,gradle逐渐取代了ant的位置,演变成官方的编译打包方式。因为历史原因,若直接将上图类似预编译的代码切换到gradle较为棘手,通过AST语法树重写,再用gradle编译,就可以解决此问题。

try{

value =Boolean.parseBoolean(str);

}catch(Throwablee) {

// #debug

e.printStackTrace();

}

4778d028baa01a994a33e1e33c1708eb.png

try{

value =Boolean.parseBoolean(str);

}catch(Throwablee) {

}

voidm(){

relaseCall();

//#mdebug

Stringinfo="some debug infomation";

LogUtil.log(info);

//#enddebug

}

4778d028baa01a994a33e1e33c1708eb.png

voidm(){

relaseCall();

}

上图的#debug和#mdebug指令,也可以通过AST改写之后再进行编译。

2、   自动静态埋点

void onClick(View v){

doSomeThing()

}

void onClick(View v){

RUtil.recordClick(v);

doSomeThing();

}

代码中需要运营统计、数据分析等,需要通过代码埋点进行用户行为数据收集。传统的做法是手动在代码中添加埋点代码,但此过程较为繁琐,可能会对业务代码造成干扰,倘若通过改写AST语法树,在编译打包期添加这种类似的埋点代码,就可减少不必要的繁琐过程,使其更加高效。

最后附推荐操作AST类库链接&完整项目源码地址,希望可以帮助大家打开脑洞,设想更多的应用场景。

推荐操作AST类库链接

完整项目源码地址如下,欢迎fork&start

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值