AST解web控制流平坦化

  • 此代码可以解决大部分 while if else 控制流平坦化
  • 原理:
    • 先将 if 语句转为 switch 语句,再将 switch 分支合并,最后删除已合并的分支(具体看代码)
  • 实现效果图
  • 首先安装依赖:
cmd 复制代码
npm install @babel/parser
npm install @babel/generator
npm install @babel/traverse        
npm install @babel/types

代码:

js 复制代码
/*
* 控制流平坦化 if语句转 switch* */
function del_code(name, consequent) {
    // 删除合并分支后多余的 赋值和break代码
    let assignment_bool, break_bool;
    for (let i = consequent.length - 1; i >= 0; i--) {
        if (consequent[i].type === "BreakStatement") {
            if (break_bool) {
                consequent.splice(i, 1);
            } else {
                break_bool = true;
            }
        } else if (consequent[i].type === "ExpressionStatement" && consequent[i].expression.type === "AssignmentExpression" && consequent[i].expression.left.name === name) {
            if (assignment_bool) {
                consequent.splice(i, 1);
            } else {
                assignment_bool = true;
            }
        }
    }
}

function merge_branch(name, key, cases_dict) {
    // 用于递归合并 switch 分支
    let {consequent} = cases_dict[key];
    let value = -1;
    for (let i in consequent) {
        if (consequent[i].type === "ExpressionStatement" && consequent[i].expression.type === "AssignmentExpression" && consequent[i].expression.left.name === name && consequent[i].expression.right.type === "NumericLiteral") {
            value = consequent[i].expression.right.value;
            break;
        }
    }

    if (value !== -1 && cases_dict.hasOwnProperty(value)) {
        del_cases_dict[value] = 1;
        return consequent.concat(merge_branch(name, value, cases_dict));    // 继续下一分支的合并
    }
    return consequent;
}

const fs = require('fs');
const {parse} = require("@babel/parser");
const traverse = require("@babel/traverse").default;
const generator = require("@babel/generator").default;
const types = require("@babel/types");

const js_code = fs.readFileSync("./test.js", 'utf8');
const ast_code = parse(js_code);

switch_cases_dict = {};
break_node = types.breakStatement();

traverse(ast_code, {
    'IfStatement': {
        enter(path) {
            var name = path.node.test.left.name;
            if (path.node.test.operator === "===") {  // 如果判断符号是 ===
                if (switch_cases_dict[name] === undefined) {
                    switch_cases_dict[name] = [];
                }
                path.node.consequent.body.push(break_node);
                switch_cases_dict[name].push(types.switchCase(path.node.test.right, path.node.consequent.body));

                if (path.node.alternate.type === 'BlockStatement') {
                    path.node.alternate.body.push(break_node);
                    let num = path.node.test.right.value + 1;
                    switch_cases_dict[name].push(types.switchCase(
                        types.numericLiteral(num),
                        path.node.alternate.body,
                    ));
                }
            }
        },
        exit(path) {
            var name = path.node.test.left.name;
            if (path.parentPath.parentPath.type === "WhileStatement" && switch_cases_dict[name].length !== 0) {
                console.log(name, "if 已替换 switch");
                path.replaceWith(types.switchStatement(
                    discriminant = types.identifier(name),
                    cases = switch_cases_dict[name]
                ));
            }
        }
    },
    'SwitchStatement': {
        enter(path) {
            del_cases_dict = {}; // 待删除的 分支语句
            let cases_dict = {};
            let cases_list = path.node.cases;
            let {name} = path.node.discriminant;
            if (switch_cases_dict.hasOwnProperty(name)) {
                console.log(name, "switch 分支合并");
                for (let i in cases_list) {
                    cases_dict[cases_list[i].test.value] = cases_list[i]
                }

                for (let key in cases_dict) {   // 合并分支并删除多余代码
                    cases_dict[key].consequent = merge_branch(name, key, cases_dict);
                    del_code(name, cases_dict[key].consequent);
                }
                for (let key in del_cases_dict) {
                    delete cases_dict[key]; // 删除多余分支
                }
                path.node.cases = Object.values(cases_dict);
            }
        },
    }
})

// console.log(generator(ast_code).code)
fs.writeFileSync("./demo.js", generator(ast_code).code, 'utf8')

注:以上js解出来的代码在某些分支会出现多次 return 语句,当然,并不影响运行。我暂时没找到出现这种问题的原因在哪里,如果您找到请务必和我说下,另您也可以在 del_code 函数中删除多的 return 语句

相关推荐
半个落月1 小时前
LangSmith 全链路观测(下):用 Dataset 与 LLM Judge 量化评估 RA
javascript·langchain
半个落月2 小时前
LangSmith 全链路观测(上):用 LangGraph 与 Milvus 构建可追踪的 RAG 客服
javascript·langchain
半个落月3 小时前
LangChain.js Agent 中间件实战:生命周期、动态工具与文件权限
javascript·langchain
志尊宝4 小时前
Vue3 零基础每日笔记(082):文件上传实战——el-upload 校验、自定义上传、头像预览
前端·javascript·vue.js·笔记·#javascript·#vue.js
雪雪爱冲浪6 小时前
2026 年网络爬虫领域的Puppeteer vs. Playwright
爬虫·网络协议·测试工具·接口隔离原则
bmxy小明同学10 小时前
2026-10-11-幻觉治理
服务器·前端·javascript
m0_5873830010 小时前
无人自助拼豆系统落地开发实战指南 核心逻辑实现与部署经验分享
前端·javascript·vue.js·人工智能·经验分享·需求分析
zhaiyy10 小时前
HTML Popover API 实践
前端·javascript·html
OpsEye11 小时前
企业 AI 管理新需求:不仅要接入模型,还要看懂每一次调用
javascript·ai编程
牧艺11 小时前
SPA 也能像原生 App:View Transitions API 从原理到可上线实战
前端·javascript·css