Javascript正则解析出代码的函数体

Javascript正则解析代码的函数体

How to use regex to capture and extract “class/function” context in source code

在做软件工程研究时,我们通常会遇到处理代码片段的问题。

最近在研究上遇到一个问题:如何抽取Solidity代码中的contractfunction内容?

前期准备

  1. regex正则表达式基础
  2. node.js或其他运行js的平台(前端)

遇到的问题

  • 多层嵌套的花括号{{{{}}}}
  • 引号里的字符串,尤其是字符串还带有了{}()等本来需要匹配的特殊字符
  • 中文,或非ASCII码
  • 注释中的花括号,引号等影响

第一步

首先我们需要先处理掉代码中的注释部分,因为这部分如果不去除,后面在使用正则解析时会遇到非常多奇奇怪怪的字符。

去除注释方法如下:

function clearCode(sources) {
    // merge files
    let code = ''
    for (const i in sources) code += sources[i].content
    // remove commments
    code = code.replace(/(\/\*[\s\S]*?\*\/)|((?<!:)\/\/.*)/g, '')
    // remove import
    code = code.replace(/import.*;/g, '')
    // remove pragma
    code = code.replace(/pragma.*;/g, '')
    return code.trim()
}

这里实际使用的一行正则是:(\/\*[\s\S]*?\*\/)|((?<!:)\/\/.*)
注意下,这里排出了各种可能出现的://,因为代码中容易出现类似http://, ftp://这类字符串;
另外上述方法删除了Sol文件的前缀信息,包括pragma编译版本,import引入合约等。

第二步

第二步,我们需要使用base64来mask引号内容,也就是代码中的字符串,防止字符串里的特殊字符影响接下来的正则匹配。

// to mask string with '...', "..."
    hashStr(text, flag = true) {
        if (flag) {
            const reg = /("[^"]*")|('[^']*')/g
            text = text.replace(reg, s => `"${this.encode(s)}"`)
        } else {
            const reg = /"[^"]*"/g
            text = text.replace(reg, s => this.decode(s.slice(1).slice(0, -1)))
        }
        return text
    }
   encode(text) {
        return btoa(unescape(encodeURIComponent(text)))
   }
    decode(text) {
        return decodeURIComponent(escape(atob(text)))
    }

函数hashStr作用是遮掩(mask)掉代码文本中的所有字符串,思路是连带引号一起转换为base64码。

注意,base64前需要使用encode,这是因为你很可能遇到字符串中是UTF8编码内容,base64默认只支持latin(ASCII),反之解码base64之后,也需要decode出文本。这是一个常见的坑,需要注意⚠️。

hashStr关键的正则为:
hash时:("[^"]*")|('[^']*')
反向hash时:"[^"]*"
反向时仅匹配"是因为我在base64后,统一用双引号highlight出这些base64,为的是方便反向解码时容易匹配到。

第三步

最后,我们可以放心使用如下超长超长的regex进行capture啦,抓取contractfunction内容吧。
获取class/contract

    getContracts(text) {
        text = this.hashStr(text)
        const reg =
            /(^|\s)(contract|interface|library|abstractcontract)\s[^;{}]*{(?:[^{}]+|{(?:[^{}]+|{(?:[^{}]+|{(?:[^{}]+|{(?:[^{}]+|{(?:[^{}]+|{(?:[^{}]+|{(?:[^{}]+|{(?:[^{}]+|{[^{}]*})*})*})*})*})*})*})*})*})*}/g
        const res = text.match(reg) || []
        for (const i in res) res[i] = this.hashStr(res[i].trim(), false)
        return res
    }

获取functiion

getFunctions(text) {
        text = this.hashStr(text)
        const reg =
            /(^|\s)((function|event)\s|constructor\s*\(.*\))[^{};]*({(?:[^}{]+|{(?:[^}{]+|{(?:[^}{]+|{(?:[^}{]+|{(?:[^}{]+|{(?:[^}{]+|{(?:[^}{]+|{(?:[^}{]+|{[^}{]*})*})*})*})*})*})*})*})*}|;)/g
        const res = text.match(reg) || []
        for (const i in res) res[i] = this.hashStr(res[i].trim(), false)
        return res
    }

注意,regex本身是不支持多层嵌套的,这里勉强采用手动嵌套的方式来匹配嵌套的花括号。

getContracts支持10层嵌套,regex是:(^|\s)(contract|interface|library|abstractcontract)\s[^;{}]*{(?:[^{}]+|{(?:[^{}]+|{(?:[^{}]+|{(?:[^{}]+|{(?:[^{}]+|{(?:[^{}]+|{(?:[^{}]+|{(?:[^{}]+|{(?:[^{}]+|{[^{}]*})*})*})*})*})*})*})*})*})*}

getFunctions支持9层嵌套,regex是:(^|\s)((function|event)\s|constructor\s*\(.*\))[^{};]*({(?:[^}{]+|{(?:[^}{]+|{(?:[^}{]+|{(?:[^}{]+|{(?:[^}{]+|{(?:[^}{]+|{(?:[^}{]+|{(?:[^}{]+|{[^}{]*})*})*})*})*})*})*})*})*}|;)

如需超出预设层数,js会出现timeout,可以自行添加层数

补充

同时,为了方便获得函数名,类名,这里还给出一套方法:

nWord(str, n) {
        if (typeof n === 'number') {
            const m = str.match(new RegExp('^(?:\\w+\\W+){' + n + '}(\\w+)'))
            return m && m[1]
        } else if (n.length) {
            const arr = []
            for (const i of n) arr.push(this.nWord(str, i))
            return arr
        }
    },
    getContractName(contractCode) {
        const words = this.nWord(contractCode, [0, 1, 2])
        if (words[0] === 'abstract') return words[2]
        else return words[1]
    },
    getFunctionName(functionCode) {
        const words = this.nWord(functionCode, [0, 1])
        if (words[0] === 'constructor') return words[0]
        else return words[1]
    }

nWord函数可以获取第n个单词,getContractName获得合约/类名,getFunctionName获取方法名。

以上所有步骤结合,就可以获取方法,类的内容,名称。

结合echarts插件还可以构建函数树。

在这里插入图片描述

开发中,regex常用工具网站:

工具一:可以显示regex的流程规则图
https://www.debuggex.com/

工具二:在线调试regex和需要匹配的样本文件
https://regexr.com/

在这里插入图片描述
在这里插入图片描述

  • 0
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

devilyouwei

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值