将复杂嵌套调用表达式的JavaScriptAST转换为一系列调用表达式的算法



这里有一个看似相对复杂的"CallExpression";在JavaScript:中

getM()
.getZ(getF().g)
.a.b.c.getX(1, 2, 3)
[getP(1, 2).x.y.getQ(getY(getN(1)))]
.r.getS()

我想用类似acornjs的东西将其解析为一系列";简单的";调用,其中序列中的每个调用都可以以函数调用结束,但结果必须存储在一个临时变量中。任何自变量也必须放入前面的临时变量中。所以上面的";表达式";大概是:

const tmpM = getM()
const tmpF = getF()
const tmpZ = tmpM.getZ(tmpF)
const tmpX = tmpZ.a.b.c.getX(1, 2, 3)
const tmpP = getP(1, 2)
const tmpN = getN(1)
const tmpY = getY(tmpN)
const tmpQ = tmpP.x.y.getQ(tmpY)
const tmpC = tmpX[tmpQ]
const tmpS = tmpC.r.getS()

假设第一个表达式的acornjs JSON AST是这样的:

{
"type": "Program",
"start": 0,
"end": 100,
"body": [
{
"type": "ExpressionStatement",
"start": 1,
"end": 99,
"expression": {
"type": "CallExpression",
"start": 1,
"end": 99,
"callee": {
"type": "MemberExpression",
"start": 1,
"end": 97,
"object": {
"type": "MemberExpression",
"start": 1,
"end": 92,
"object": {
"type": "MemberExpression",
"start": 1,
"end": 87,
"object": {
"type": "CallExpression",
"start": 1,
"end": 48,
"callee": {
"type": "MemberExpression",
"start": 1,
"end": 39,
"object": {
"type": "MemberExpression",
"start": 1,
"end": 34,
"object": {
"type": "MemberExpression",
"start": 1,
"end": 32,
"object": {
"type": "MemberExpression",
"start": 1,
"end": 30,
"object": {
"type": "CallExpression",
"start": 1,
"end": 25,
"callee": {
"type": "MemberExpression",
"start": 1,
"end": 15,
"object": {
"type": "CallExpression",
"start": 1,
"end": 7,
"callee": {
"type": "Identifier",
"start": 1,
"end": 5,
"name": "getM"
},
"arguments": [],
"optional": false
},
"property": {
"type": "Identifier",
"start": 11,
"end": 15,
"name": "getZ"
},
"computed": false,
"optional": false
},
"arguments": [
{
"type": "MemberExpression",
"start": 16,
"end": 24,
"object": {
"type": "CallExpression",
"start": 16,
"end": 22,
"callee": {
"type": "Identifier",
"start": 16,
"end": 20,
"name": "getF"
},
"arguments": [],
"optional": false
},
"property": {
"type": "Identifier",
"start": 23,
"end": 24,
"name": "g"
},
"computed": false,
"optional": false
}
],
"optional": false
},
"property": {
"type": "Identifier",
"start": 29,
"end": 30,
"name": "a"
},
"computed": false,
"optional": false
},
"property": {
"type": "Identifier",
"start": 31,
"end": 32,
"name": "b"
},
"computed": false,
"optional": false
},
"property": {
"type": "Identifier",
"start": 33,
"end": 34,
"name": "c"
},
"computed": false,
"optional": false
},
"property": {
"type": "Identifier",
"start": 35,
"end": 39,
"name": "getX"
},
"computed": false,
"optional": false
},
"arguments": [
{
"type": "Literal",
"start": 40,
"end": 41,
"value": 1,
"raw": "1"
},
{
"type": "Literal",
"start": 43,
"end": 44,
"value": 2,
"raw": "2"
},
{
"type": "Literal",
"start": 46,
"end": 47,
"value": 3,
"raw": "3"
}
],
"optional": false
},
"property": {
"type": "CallExpression",
"start": 52,
"end": 86,
"callee": {
"type": "MemberExpression",
"start": 52,
"end": 71,
"object": {
"type": "MemberExpression",
"start": 52,
"end": 66,
"object": {
"type": "MemberExpression",
"start": 52,
"end": 64,
"object": {
"type": "CallExpression",
"start": 52,
"end": 62,
"callee": {
"type": "Identifier",
"start": 52,
"end": 56,
"name": "getP"
},
"arguments": [
{
"type": "Literal",
"start": 57,
"end": 58,
"value": 1,
"raw": "1"
},
{
"type": "Literal",
"start": 60,
"end": 61,
"value": 2,
"raw": "2"
}
],
"optional": false
},
"property": {
"type": "Identifier",
"start": 63,
"end": 64,
"name": "x"
},
"computed": false,
"optional": false
},
"property": {
"type": "Identifier",
"start": 65,
"end": 66,
"name": "y"
},
"computed": false,
"optional": false
},
"property": {
"type": "Identifier",
"start": 67,
"end": 71,
"name": "getQ"
},
"computed": false,
"optional": false
},
"arguments": [
{
"type": "CallExpression",
"start": 72,
"end": 85,
"callee": {
"type": "Identifier",
"start": 72,
"end": 76,
"name": "getY"
},
"arguments": [
{
"type": "CallExpression",
"start": 77,
"end": 84,
"callee": {
"type": "Identifier",
"start": 77,
"end": 81,
"name": "getN"
},
"arguments": [
{
"type": "Literal",
"start": 82,
"end": 83,
"value": 1,
"raw": "1"
}
],
"optional": false
}
],
"optional": false
}
],
"optional": false
},
"computed": true,
"optional": false
},
"property": {
"type": "Identifier",
"start": 91,
"end": 92,
"name": "r"
},
"computed": false,
"optional": false
},
"property": {
"type": "Identifier",
"start": 93,
"end": 97,
"name": "getS"
},
"computed": false,
"optional": false
},
"arguments": [],
"optional": false
}
}
],
"sourceType": "module"
}

你将如何动态/编程地编写一个算法来接受任何类似的表达式并将其转换为相应的";扁平的";上面显示的表达式,以及所有的临时变量?

我迷失在接近开始的时候,因为我不知道该如何走下去;叶子;论点,把它们放在一开始,然后从那里开始。对我来说,首先很难看到正在发生的总体结构。

const acorn = require('acorn')
const fs = require('fs')
const input = fs.readFileSync('./tmp/parse.in.js', 'utf-8')
const jst = acorn.parse(input, {
ecmaVersion: 2021,
sourceType: 'module'
})
fs.writeFileSync('tmp/parse.out.js.json', JSON.stringify(jst, null, 2))
const flattenedText = flattenJST(jst.body[0].expression)
fs.writeFileSync('tmp/parse.flat.json', flattenedText)
function flattenJST(jst) {
const state = {
text: [],
tmps: 0
}
if (jst.type === 'CallExpression') {
compileCallExpression(jst, state)
}
return state.text.join('n')
// return state.out.map(serialize).join('n')
}
function serialize(node) {
switch (node.type) {
case 'VariableDeclaration':
break
}
}
function compileCallExpression(jst, state) {
const variable = `tmp${state.tmps++}`
let path
if (jst.callee.type === 'MemberExpression') {
path = compileMemberExpression(jst.callee, state)
} else if (jst.callee.type === 'Identifier') {
path = state.prefix ? `${state.prefix}.${jst.callee.name}` : jst.callee.name
}
state.text.push(`const ${variable} = ${path}()`)
return variable
}
function compileMemberExpression(jst, state) {
if (jst.object.type === 'MemberExpression') {
const back = compileMemberExpression(jst.object, state)
return `${back}.${jst.property.name}`
} else if (jst.object.type === 'CallExpression') {
return compileCallExpression(jst.object, state)
}
}

到目前为止的输出:

const tmp3 = getM()
const tmp2 = tmp3()
const tmp1 = tmp2.b.c.getX()
const tmp0 = tmp1.r.getS()

试错:

const acorn = require('acorn')
const fs = require('fs')
const input = fs.readFileSync('./tmp/parse.in.js', 'utf-8')
const jst = acorn.parse(input, {
ecmaVersion: 2021,
sourceType: 'module'
})
fs.writeFileSync('tmp/parse.out.js.json', JSON.stringify(jst, null, 2))
const flattenedText = flattenJST(jst.body[0].expression)
fs.writeFileSync('tmp/parse.flat.json', flattenedText)
function flattenJST(jst) {
const state = {
text: [],
tmps: 0
}
if (jst.type === 'CallExpression') {
compileCallExpression(jst, state)
} else if (jst.type === 'MemberExpression') {
state.text.push(`const tmp${state.tmps++} = ${compileMemberExpression(jst, state)}`)
}
return state.text.join('n')
// return state.out.map(serialize).join('n')
}
function serialize(node) {
switch (node.type) {
case 'VariableDeclaration':
break
}
}
function compileCallExpression(jst, state) {
const args = []
jst.arguments.forEach(arg => {
switch (arg.type) {
case 'CallExpression':
args.push(compileCallExpression(arg, state))
break
case 'MemberExpression':
args.push(compileMemberExpression(arg, state))
break
}
})
const variable = `tmp${state.tmps++}`
let path
if (jst.callee.type === 'MemberExpression') {
path = compileMemberExpression(jst.callee, state)
} else if (jst.callee.type === 'Identifier') {
path = state.prefix ? `${state.prefix}.${jst.callee.name}` : jst.callee.name
} else {
throw JSON.stringify(jst)
}
state.text.push(`const ${variable} = ${path}(${args.join(', ')})`)
return variable
}
function compileMemberExpression(jst, state) {
if (jst.object.type === 'MemberExpression') {
const back = compileMemberExpression(jst.object, state)
switch (jst.property.type) {
case 'Identifier': return joinProperty(`${back}`, `${jst.property.name}`, jst.computed)
case 'MemberExpression':
return joinProperty(back, compileMemberExpression(jst.property, state), jst.computed)
break
case 'CallExpression':
return joinProperty(back, compileCallExpression(jst.property, state), jst.computed)
break
}
} else if (jst.object.type === 'CallExpression') {
const back = compileCallExpression(jst.object, state)
switch (jst.property.type) {
case 'Identifier': return joinProperty(`${back}`, `${jst.property.name}`, jst.computed)
case 'MemberExpression':
return joinProperty(`${back}`, compileMemberExpression(jst.property, state), jst.computed)
break
case 'CallExpression':
return joinProperty(`${back}`, compileCallExpression(jst.property, state), jst.computed)
break
}
} else if (jst.object.type === 'Identifier') {
const back = jst.object.name
switch (jst.property.type) {
case 'Identifier': return joinProperty(`${back}`, `${jst.property.name}`, jst.computed)
case 'MemberExpression':
return compileMemberExpression(jst.property, state)
break
case 'CallExpression':
return compileCallExpression(jst.property, state)
break
}
} else {
throw JSON.stringify(jst)
}
}
function joinProperty(a, b, computed) {
if (computed) {
return `${a}[${b}]`
} else {
return `${a}.${b}`
}
}

输出:

const tmp2 = getF()
const tmp4 = getM()
const tmp3 = tmp4.getZ(tmp2.g)
const tmp1 = tmp3.a.b.c.getX()
const tmp5 = getN()
const tmp6 = getY(tmp5)
const tmp8 = getP()
const tmp7 = tmp8.x.y.getQ(tmp6)
const tmp0 = tmp1[tmp7].r.getS()

如果有人能想出一个更优雅的解决方案或解释它是如何工作的,请这样做,我会把它标记为正确答案。

最新更新