- 文章
- 基于后缀算术表达式的代码解析
- 基于AST的算数表达式解析
- Vscode Java 环境配置
- 纯前端实现图片的模板匹配
- 测试用例管理工具Luckyframe安装
- Vscode远程开发,本地翻墙神器
- 记前端手写方法
- Node 2020年新增功能
- yum-404-error
- react16特性:fiber reconciler解密
- cmd终端设置代理
- 前端面试题收集
- git子模块
- 算法-排序
- linux安装python-pyenv环境
- 开发人员良心工具
- 斐波拉契数列js实现
- 数组ArrayFlatten
- Docker安装部署taiga项目
- 极光推送RN集成
- docker-pm2发布node服务
- git-pull获取指定文件
- git获取第一次commit提交记录
- ReactNative项目选型设计
- Docker-Mysql8.0安装及初始化配置
- DDA算法
- ubuntu搭建shadowsocks服务
- React-Native 接入百度统计SDK
- docker-使用yum安装
- 前端入门篇
- CodePush尝试
- Markdown数学公式
- Mongoose踩坑路
- linux系统nvm指定版本安装
- linux安装nginx
- Vscode-Threejs代码智能提示
- linux常用命令
- 说明
基于AST的算数表达式解析
一月 08, 2021前序
之前在另一篇文章中<<基于后缀算术表达式的代码解析
>>已经实现过简单的(1 + 3 - 5 / 54 * 2
)等算术的运算,但是代码逻辑实现满足不了更多场景,如
- 对数据的赋值:
a = b + c
- 逻辑判断:
a == b
ora != b
- 与或非:
a && c
ora || c
所以为了更好的解决一些场景,这里使用AST(抽象语法树)来解决这部分的问题
准备工作
- 表达式 转 AST
- AST 遍历
- 数据作用域
1、表达式转AST
首先可以参考AST在线转换这里,比如
a + 4
js 代码实现:
npm i acorn
import * as acorn from 'acorn';
const code = '7 / (4 + 3)';
const ast = acorn.parse(code);
转换的效果如下:
{
"type": "Program",
"start": 0,
"end": 5,
"body": [
{
"type": "ExpressionStatement",
"start": 0,
"end": 5,
"expression": {
"type": "BinaryExpression",
"start": 0,
"end": 5,
"left": {
"type": "Literal",
"start": 0,
"end": 1,
"value": 1,
"raw": "1"
},
"operator": "+",
"right": {
"type": "Literal",
"start": 4,
"end": 5,
"value": 4,
"raw": "4"
}
}
}
],
"sourceType": "module"
}
假如我们可以通过遍历
的手段拿到ast中这里的解析运算值
我们只需要根据type:为(BinaryExpression
)(每一个带type字段的对应节点都是一个astNode节点,都有对应的遍历函数)进行左右计算(operator
)操作, 如:
BinaryExpression(node) {
const { operator } = node;
const leftValue = node?.left?.value as any;
const rightValue = node?.right?.value as any;
if (operator === '+') {
node.value = leftValue + rightValue; // 关键:注意这里的操作计算值最后被定义赋值到AST node节点本身上了
}
}
Tips:
遍历AST时, 注意是从内到外的遍历,也就是从最child级遍历回到parent结束
如:7 / (4 + 3)
对应AST
{
"type": "Program",
"start": 0,
"end": 11,
"body": [
{
"type": "ExpressionStatement",
"start": 0,
"end": 11,
"expression": {
"type": "BinaryExpression",
"start": 0,
"end": 11,
"left": {
"type": "Literal",
"start": 0,
"end": 1,
"value": 7, // ③式
"raw": "7"
},
"operator": "/",
"right": {
"type": "BinaryExpression",
"start": 5,
"end": 10,
"left": {
"type": "Literal",
"start": 5,
"end": 6,
"value": 4, // ①式
"raw": "4"
},
"operator": "+",
"right": {
"type": "Literal",
"start": 9,
"end": 10,
"value": 3, // ②式
"raw": "3"
}
}
}
}
],
"sourceType": "module"
}
这里就是需要从child节点的 ①式 + ②式
计算完后再与 ③式
进行操作
2、AST 遍历
通常我们会在算术表达式里遇到以下的AST type节点:(当然你有更多的语法node节点需要支持,你可以再添加type遍历函数)
const walkNode = {
Identifier(node) {
// 如 a, b, c等字符等
},
Literal(node) {
// 如 1,2,3,4数字等
},
CallExpression(node) {
// 如 func(a, v)
},
AssignmentExpression(node) {
// 如 a = b + 1
},
BinaryExpression(node) {
// 如 a + b
// 如 a - b
// 如 a * b
// 如 a / b
// 如 a > b
// 如 a < b
// 如 a >= b
// 如 a <= b
// 如 a == b
// 如 a === b
// 如 a != b
// 如 a !== b
},
UnaryExpression(node) {
// 如 !a
},
LogicalExpression(node) {
// 如 a || b
},
ExpressionStatement(node) {
// 可看作AST遍历结束节点
},
}
那这个时候只需要根据node的value及operator等进行相应的运算即可, 这里需要安装一个模块包:
npm i acorn-walk
import * as walk from 'acorn-walk'
walk.simple(ast, walkNode);
这样通过acorn-walk
这个库可以轻松遍历ast的node节点,这个使用即可根据当前节点进行对应的操作.
3、数据作用域
一般我们解析对应的表达式都有变量的概念:比如a + b
, 不是仅仅的等于ab
, 而是根据当前a,b的可能是变量的概念,去进行相加(如 a = 3, b = 4)最后结果是7.
这也是对应以a,b,c...
这种字符为标识类型的节点进行计算时,需要注意的,而且我们来看下它的type类型:
// 如:a
{
"type": "Identifier",
"start": 0,
"end": 1,
"name": "a"
}
它的type为Identifier
, 是一种标记的概念,本身可能就是一个变量。则遍历的时候需要去查找下当前作用域下对应的值
const walkNode = {
....
Literal(node) {
node.value = dataScope[node?.name] || node?.name;
// dataScope 为自定义作用域
// Literal 需要节点赋值value属性保持在上一父节点拿到真实的值
},
...
}
因此有了作用域我们可以补全下我们的遍历函数:
let result;
const walkNode = {
Identifier(node) {
node.value = dataScope[node?.name] || node?.name;
},
Literal(node) {
node.value = dataScope[node?.value] || node?.value;
},
CallExpression(node) {
const { callee = {}, arguments: args = [] } = node;
const { name } = callee!;
const argsParams = args.map(d => d.value) || [];
node.value = InnerFuncs[name!]?.(...argsParams);
// InnerFuncs 自己内置定义的函数调用
// import { InnerFuncs } from './Functions';
/**
InnerFuncs = {
func1: () {
return data;
},
func2: () {
return data;
}
};
*/
},
AssignmentExpression(node) {
const { operator } = node;
// const leftValue = node?.left?.value as any;
const rightValue = node?.right?.value as any;
if (operator === '=') {
_lodashSet(dataScope, node.left.name, rightValue); // 需要引入import _lodashSet from 'lodash/set';
}
},
BinaryExpression(node) {
const { operator } = node;
const leftValue = node?.left?.value as any;
const rightValue = node?.right?.value as any;
if (operator === '+') {
node.value = leftValue + rightValue;
} else if (operator === '-') {
node.value = leftValue - rightValue;
} else if (operator === '*') {
node.value = leftValue * rightValue;
} else if (operator === '/') {
node.value = leftValue / rightValue;
} else if (operator === '>') {
node.value = leftValue > rightValue;
} else if (operator === '<') {
node.value = leftValue < rightValue;
} else if (operator === '>=') {
node.value = leftValue >= rightValue;
} else if (operator === '<=') {
node.value = leftValue >= rightValue;
} else if (operator === '==') {
node.value = leftValue == rightValue;
} else if (operator === '===') {
node.value = leftValue === rightValue;
} else if (operator === '!=') {
node.value = leftValue != rightValue;
} else if (operator === '!==') {
node.value = leftValue !== rightValue;
}
},
UnaryExpression(node) {
const { operator } = node;
if (operator === '!') {
node.value = !node?.argument?.value;
}
},
LogicalExpression(node) {
const { operator } = node;
const leftValue = node?.left?.value as any;
const rightValue = node?.right?.value as any;
if (operator === '||') {
node.value = leftValue || rightValue;
} else if (operator === '&&') {
node.value = leftValue && rightValue;
}
},
ExpressionStatement(node) {
result = node?.expression?.value;
}
}
总结
此种方式使用AST解析算术表达式,更加容易扩展语法及函数,带来更加方便的遍历操作。