实现一个计算器

function 计算(表达式) {
    let tokens = 分词(表达式);
    console.log(tokens)
    let node = 解析(tokens);
    console.log(node)
    let value = 执行(node)
    return value
}

// #region 词法解析

function 分词(表达式) {
    let arr = [];
    let i = 0,
        len = 表达式.length;
    while (i < len) {
        if (是否是数字(表达式[i])) {
            let 数字 = 扫描数字(表达式, i);
            arr.push({ 类型: '数字', 内容: 数字, 位置: i });
            i += 数字.length;
        } else if (是否是空白(表达式[i])) {
            i++;
        } else if (表达式[i] == '.') {
            let 上一个节点 = arr[arr.length-1] || {};
            if (上一个节点.类型 == '数字') {
                arr.pop();
                let 小数部分 = 扫描数字(表达式, i + 1);
                arr.push({ 类型: '小数', 内容: 上一个节点.内容 + '.' + 小数部分, 位置: i });
                i += 小数部分.length + 1;
            } else if (上一个节点.类型 == '小数') {
                throw '非法字符.,位置:' + i
            } else {
                let 小数部分 = 扫描数字(表达式, i + 1);
                if (小数部分 == '') {
                    throw '非法字符.,位置:' + i
                }
                arr.push({ 类型: '小数', 内容: '.' + 小数部分, 位置: i });
                i += 小数部分.length + 1;
            }
        } else {
            arr.push({ 类型: '运算符', 内容: 表达式[i], 位置: i });
            i++;
        }
    }
    return arr;
}

function 扫描数字(表达式, 开始扫描位置) {
    let 数字 = '',
        i = 开始扫描位置;
    while (是否是数字(表达式[i])) {
        数字 += 表达式[i];
        i++;
    }
    return 数字
}

function 是否是数字(字符) {
    if (字符 >= '0' && 字符 <= '9') {
        return true
    }
    return false
}

function 是否是空白(字符) {
    if (字符 == ' ' || 字符 == '\n') {
        return true
    }
    return false
}

// #endregion

// #region 语法解析

function 解析(标记列表) {
    return 解析表达式(标记列表, 0, 0).节点
}

function 解析表达式(标记列表, 开始解析位置, 优先级) {
    let { 节点, 下一个位置 } = 解析节点(标记列表, 开始解析位置);
    return 组合节点(标记列表, 下一个位置, 节点, 优先级)
}

function 解析节点(标记列表, 开始解析位置) {
    let token = 标记列表[开始解析位置];
    if (token.类型 == '数字' || token.类型 == '小数') {
        return { 节点: { 类型: '数字', 内容: token.内容, 位置: token.位置 }, 下一个位置: 开始解析位置 + 1 }
    } else {
        if (token.内容 == '*' || token.内容 == '/' || token.内容 == ')') {
            throw '非法的符号:' + token.内容 + ' 位置:' + token.位置
        } else if (token.内容 == '+' || token.内容 == '-') {
            let { 节点, 下一个位置 } = 解析节点(标记列表, 开始解析位置 + 1);
            return { 节点: { 类型: token.内容, 右值: 节点, 位置: token.位置 }, 下一个位置: 下一个位置 }
        } else if (token.内容 == '(') {
            let { 节点, 下一个位置 } = 解析表达式(标记列表, 开始解析位置 + 1, 0);
            if (标记列表[下一个位置].内容 != ')') {
                throw '缺少),位置:' + token.位置
            }
            return { 节点: 节点, 下一个位置: 下一个位置 + 1 }
        }
    }
}

function 组合节点(标记列表, 开始解析位置, 左值, 优先级) {
    let token;
    while (token = 标记列表[开始解析位置]) {
        if (token.内容 == "+" || token.内容 == "-" || token.内容 == "*" || token.内容 == "/") {
            if (!标记列表[开始解析位置 + 1]) {
                throw '缺少右值,位置:' + token.位置
            }
            let 下一个优先级 = 获取优先级(token.内容);
            if (下一个优先级 > 优先级) {
                let { 节点, 下一个位置 } = 解析表达式(标记列表, 开始解析位置 + 1, 下一个优先级);
                左值 = { 类型: token.内容, 左值: 左值, 右值: 节点, 位置: 左值.位置 };
                开始解析位置 = 下一个位置;
                优先级 = 0;
            } else {
                break;
            }
        } else if (token.内容 == ')') {
            break;
        } else {
            throw '非法的符号:' + token.内容 + ' 位置:' + token.位置
        }
    }
    return { 节点: 左值, 下一个位置: 开始解析位置 };
}

function 获取优先级(运算符) {
    if (运算符 == "+" || 运算符 == "-") {
        return 1
    } else if (运算符 == "*" || 运算符 == "/") {
        return 2
    }
}

// #endregion

// #region 执行

function 执行(节点) {
    switch (节点.类型) {
        case '数字':
            return parseFloat(节点.内容)
        case '+':
            return (节点.左值 ? 执行(节点.左值) : 0) + 执行(节点.右值);
        case '-':
            return (节点.左值 ? 执行(节点.左值) : 0) - 执行(节点.右值);
        case '*':
            return 执行(节点.左值) * 执行(节点.右值);
        case '/':
            return 执行(节点.左值) / 执行(节点.右值);
    }
}

// #endregion

打赏
微信扫一扫支付
这篇文章对你有用?
微信logo 微信扫一扫,打赏 1 元表示支持吧~

发表评论

电子邮件地址不会被公开。