【用JS自制表格软件玩数据】10. 为表格脚本设计一个语法解析器

news2025/1/11 14:14:49

设计脚本语言的语法解析器

  • 概述
    • 脚本源码语法预览
    • 运行效果如下图
  • 设计计算符号的优先级
  • 定义一些关键词
    • 生成一份关键词的map方便引用
  • 枚举关键词的类型
  • 错误异常的捕获
  • 字符匹配
  • 代码的字符转化成迭代器
  • 关键词标记器
  • 词法分析器
  • 设计一个队列处理器
  • 源代码字符串迭代器
  • 代码的块级运行环境
    • 脚本源码
    • 运行效果如下图
  • 上代码
  • 最后封装成简单的脚本解析器

概述

在EXCEL表格中,有个标配的VBA脚本语言,近年,也有人开始将 Python 也植入里面。
在该自制的脚本语言中,目标是办公自动化。语法特性是尽量贴近 其他开发语言的使用习惯。但更倾向于简单易用为主。不排除后面进行简化。
本章为脚本解析器,为下一章的虚拟机指令集与CPU,内存等虚拟硬件做铺垫。

脚本源码语法预览

var a = 1+6*(2-1)/3; // 一条普通的对 变量a 进行赋值,计算结果为整数
var b = 1+a*2/1/3; // 对 变量b赋值的公式中,包含了变量a,计算结果为整数
var c = 1+(2-1)/3; // 一条普通的对 变量c 进行赋值,计算结果为浮点数

运行效果如下图

在这里插入图片描述

设计计算符号的优先级

下列的运算符,越底部,优先级越高。

window.calculatelevel = [
  [",", ";"], // 多个计算	按优先级计算,然后从右向左
  ["=", "+=", "-=", "*=", "/=", "%=", "&=", "|=", "^=", "<", "<=", ">", ">=", ">>="], // 混合赋值运算符	从右向左
  ["?", ":"], // 条件运算符	从右向左
  ["||"], // 短路或(逻辑“或”)	从左向右
  ["&&"], // 短路与(逻辑“与”)	从左向右
  ["|"], // 按位“或”	从左向右
  ["^"], // 按位“异或”	从左向右
  ["&"], // 按位“与”	从左向右
  ["==", "!=", "===", "!=="], // 相等、不相等、全等,不全等	从左向右
  ["<", "<=", ">", ">="], // 小于、小于或等于、大于、大于或等于、是否为特定类的实例	从左向右
  ["<<", ">>", ">>>"], // 左位移、右位移、无符号右移	从左向右
  ["+", "-"], // 相加、相减、字符串串联	从左向右
  ["*", "/", "%"], // 相乘、相除、求余数	从左向右
  ["++", "--", "~", "!"], // 	一元运算符、返回数据类型、对象创建、未定义的值	从右向左
  ["[", "]", "(", ")", "{", "}", ".", "\"", "'"] // 字段访问、数组索引、函数调用和表达式分组	从左向右
];

定义一些关键词

因为脚本计划借鉴JS的语法结构,所以直接把它的关键词都直接复用,等后面看哪些用不上的再去除。

window.jskeyword = [
  "abstract",
  "arguments",
  "boolean",
  "break",
  "case",
  "continue",
  "char",
  "catch",
  "class*",
  "delete",
  "default",
  "double",
  "debugger",
  "extends*",
  "for",
  "import*",
  "let",
  "package",
  "short",
  "this",
  "try",
  "while",
  "else",
  "FALSE",
  "function",
  "in",
  "long",
  "private",
  "static",
  "throw",
  "typeof",
  "with",
  "enum*",
  "final",
  "goto",
  "instanceof",
  "native",
  "protected",
  "super*",
  "throws",
  "var",
  "yield",
  "eval",
  "finally",
  "if",
  "int",
  "new",
  "public",
  "switch",
  "transient",
  "void",
  "byte",
  "const",
  "do",
  "export*",
  "float",
  "implements",
  "interface",
  "null",
  "return",
  "synchronized",
  "TRUE",
  "volatile",
  "Array",
  "Infinity",
  "Math",
  "prototype",
  "Date",
  "isFinite",
  "NaN",
  "String",
  "eval",
  "isNaN",
  "name",
  "toString",
  "function",
  "isPrototypeOf",
  "Number",
  "undefined",
  "hasOwnProperty",
  "length",
  "Object",
  "valueOf",
  "alert",
  "assign",
  "clearTimeout",
  "constructor",
  "document",
  "encodeURI",
  "focus",
  "innerWidth",
  "mimeTypes",
  "hidden",
  "open",
  "packages",
  "parseInt",
  "propertyIsEnum",
  "scroll",
  "setTimeout",
  "textarea",
  "all",
  "blur",
  "clientInformation",
  "crypto",
  "element",
  "encodeURIComponent",
  "form",
  "layer",
  "navigate",
  "history",
  "opener",
  "pageXOffset",
  "password",
  "radio",
  "secure",
  "status",
  "top",
  "anchor",
  "button",
  "close",
  "decodeURI",
  "elements",
  "escape",
  "forms",
  "layers",
  "navigator",
  "image",
  "option",
  "pageYOffset",
  "pkcs11",
  "reset",
  "select",
  "submit",
  "unescape",
  "anchors",
  "checkbox",
  "closed",
  "decodeURIComponent",
  "embed",
  "event",
  "frame",
  "link",
  "frames",
  "images",
  "outerHeight",
  "parent",
  "plugin",
  "screenX",
  "self",
  "taint",
  "untaint",
  "area",
  "clearInterval",
  "confirm",
  "defaultStatus",
  "embeds",
  "fileUpload",
  "innerHeight",
  "location",
  "frameRate",
  "offscreenBuffering",
  "outerWidth",
  "parseFloat",
  "prompt",
  "screenY",
  "setInterval",
  "text",
  "window"
];

生成一份关键词的map方便引用

const Keywords = new Set(window.jskeyword) // window.jskeyword

枚举关键词的类型


class Enum {
    constructor(type, value) {
        this.type = type;
        this.value = value;
    }
}

var TokenType = {
    KEYWORD: new Enum("KEYWORD", 1), // 关键词
    VARIABLE: new Enum("VARIABLE", 2), // 值
    STRING: new Enum("STRING", 3), // 字符串
    OPERATOR: new Enum("OPERATOR", 4), // 操作符
    BRACKET: new Enum("BRACKET", 5), // 各类括号以及闭包符号,分段
    INTEGER: new Enum("INTEGER", 6), // 整数
    FLOAT: new Enum("FLOAT", 7), // 浮点数
    BOOLEAN: new Enum("BOOLEAN", 8), // 布尔数(只有两种): true , false
    OBJECT: new Enum("OBJECT", 9), // 类,对象
    FUNCTION: new Enum("FUNCTION", 10), // 函数
    ARRAY: new Enum("ARRAY", 11) // 数组
}

总共有11种类型

  1. KEYWORD 关键词
  2. VARIABLE 变量
  3. STRING 字符串
  4. OPERATOR 操作符
  5. BRACKET 分段符
  6. INTEGER 整数
  7. FLOAT 浮点数
  8. BOOLEAN 布尔值
  9. OBJECT 对象
  10. FUNCTION 函数
  11. ARRAY 数组

错误异常的捕获

脚本的异常捕获,它继承自Error 类。这样比较省事儿


// 异常处理
class ScriptException extends Error {
    constructor(msg) {
        super(msg)
    }

    // 词汇异常
    static fromChar(c) {
        return new ScriptException(`unexpected char ${c}`);
    }
    
    // 语法异常
    static fromToken(token) {
        return new ScriptException(`Syntax Error, unexpected token ${token.getValue()}`)
    }
}

字符匹配

// 字符匹配
class AlphabetMatch {
    static ptnLetter = /^[a-zA-Z]$/
    static ptnNumber = /^[0-9]$/
    static ptnLiteral = /^[_a-zA-Z0-9]$/
    static operator = /^[+-\\*/><=!&|?:^%]$/

    static isLetter(c) { //判断是否字母
        return AlphabetMatch.ptnLetter.test(c)
    }

    static isNumber(c) { //判断是否数字
        return AlphabetMatch.ptnNumber.test(c)
    }

    static isLiteral(c) { // 判断是否字符
        return AlphabetMatch.ptnLiteral.test(c)
    }

    static isOperator(c) { // 判断是否操作符
        return AlphabetMatch.operator.test(c)
    }
}

代码的字符转化成迭代器

function* arrayToGenerator(array) {
    for (let i = 0; i < array.length; i++) {
        yield array[i];
    }
} // 构建数组迭代器

关键词标记器

class Token { // 标记器
    constructor(type, value) {
        this._type = type;
        this._value = value;
        this._level = null;
    }

    getType() {
        return this._type;
    } // 获取类型

    getValue() {
        return this._value;
    } // 获取值

    getLevel() {
        return this._level;
    } // 获取优先级

    isVariable() { // 判断是否变量
        return this._type == TokenType.VARIABLE;
    }

    isFunction() { // 判断是否函数
        return this._type == TokenType.FUNCTION;
    }

    isValue() {
        return this.isScalar() || this.isVariable();
    } // 判断是否数值

    isType() {
        return (
            this._value === "bool" ||
            this._value === "int" ||
            this._value === "float" ||
            this._value === "void" ||
            this._value === "string"
        );
    } // 返回格式

    isScalar() {
        return (
            this._type == TokenType.INTEGER ||
            this._type == TokenType.FLOAT ||
            this._type == TokenType.STRING ||
            this._type == TokenType.BOOLEAN
        );
    }

    toString() {
        return `type ${this._type.type}, value ${this._value}`;
    } // 对格式与值进行序列化

    static makeVarOrKeyword(it) { // 返回关键词
        let s = ""; // 初始化一个临时的字符串

        while (it.hasNext()) { // 判断迭代器是否有下一个字符
            const c = it.peek();

            if (AlphabetMatch.isLiteral(c)) { // 判断字符串
                s += c; // 如果是字符串,就对字符进行拼接
            } else {
                break; // 否则跳出 while 循环
            }

            it.next(); // 迭代器一直往前推进
        }

        if (Keywords.has(s)) { // 是否关键词
            return new Token(TokenType.KEYWORD, s);
        }

        if (s == "true" || s == "false") { // 是否布尔
            return new Token(TokenType.BOOLEAN, s);
        }

        return new Token(TokenType.VARIABLE, s); // 返回变量
    }

    static makeString(it) { // 返回字符串
        let s = ""; // 初始化一个临时的字符串

        let state = 0; // 初始化状态

        while (it.hasNext()) { // 判断迭代器是否有下一个字符
            let c = it.next();

            switch (state) {
                case 0:
                    if (c == '"') { // 检测出是前双引号
                        state = 1;
                    } else {
                        state = 2;
                    }
                    s += c;
                    break;
                case 1:
                    if (c == '"') { // 检测出是后双引号
                        return new Token(TokenType.STRING, s + c);
                    } else {
                        s += c; // 否则就链接字符
                    }
                    break;
                case 2:
                    if (c == "'") { // 判断单引号
                        return new Token(TokenType.STRING, s + c);
                    } else {
                        s += c;
                    }
                    break;
            }
        }
        throw new ScriptException("Unexpected error"); // 抛出异常
    }

    static makeOp(it) { // 返回操作符
        let state = 0; // 设置状态
        while (it.hasNext()) {
            let lookahead = it.next();
            switch (state) {
                case 0:
                    switch (lookahead) {
                        case "+": state = 1; break;
                        case "-": state = 2; break;
                        case "*": state = 3; break;
                        case "/": state = 4; break;
                        case ">": state = 5; break;
                        case "<": state = 6; break;
                        case "=": state = 7; break;
                        case "!": state = 8; break;
                        case "&": state = 9; break;
                        case "|": state = 10; break;
                        case "^": state = 11; break;
                        case "%": state = 12; break;
                        case ".": state = 13; break;
                        case "?": return new Token(TokenType.OPERATOR, "?");
                        case ":": return new Token(TokenType.OPERATOR, ":");
                        case ",": return new Token(TokenType.OPERATOR, ",");
                        case ";": return new Token(TokenType.OPERATOR, ";");
                    }
                    break;
                case 1: {
                    if (lookahead == "+") { return new Token(TokenType.OPERATOR, "++"); }
                    else if (lookahead == "=") { return new Token(TokenType.OPERATOR, "+="); }
                    else { it.putBack(); return new Token(TokenType.OPERATOR, "+"); }
                }
                case 2: {
                    if (lookahead == "-") { return new Token(TokenType.OPERATOR, "--"); }
                    else if (lookahead == "=") { return new Token(TokenType.OPERATOR, "-="); }
                    else { it.putBack(); return new Token(TokenType.OPERATOR, "-"); }
                }
                case 3: {
                    if (lookahead == "=") { return new Token(TokenType.OPERATOR, "*="); }
                    else { it.putBack(); return new Token(TokenType.OPERATOR, "*"); }
                }
                case 4: {
                    if (lookahead == "=") { return new Token(TokenType.OPERATOR, "/="); }
                    else { it.putBack(); return new Token(TokenType.OPERATOR, "/"); }
                }
                case 5: {
                    if ((lookahead == ">") && (">" == it.peek())) { it.next(); return new Token(TokenType.OPERATOR, ">>>"); }
                    else if ((lookahead == ">") && ("=" == it.peek())) { it.next(); return new Token(TokenType.OPERATOR, ">>="); }
                    else if (lookahead == "=") { return new Token(TokenType.OPERATOR, ">="); }
                    else if (lookahead == ">") { return new Token(TokenType.OPERATOR, ">>"); }
                    else { it.putBack(); return new Token(TokenType.OPERATOR, ">"); }
                }
                case 6: {
                    if (lookahead == "=") { return new Token(TokenType.OPERATOR, "<="); }
                    else if (lookahead == "<") { return new Token(TokenType.OPERATOR, "<<"); }
                    else { it.putBack(); return new Token(TokenType.OPERATOR, "<"); }
                }
                case 7: {
                    if ((lookahead == "=") && ("=" == it.peek())) { it.next(); return new Token(TokenType.OPERATOR, "==="); }
                    else if (lookahead == "=") { return new Token(TokenType.OPERATOR, "=="); }
                    else if (lookahead == ">") { return new Token(TokenType.OPERATOR, "=>"); }
                    else { it.putBack(); return new Token(TokenType.OPERATOR, "="); }
                }
                case 8: {
                    if ((lookahead == "=") && ("=" == it.peek())) { it.next(); return new Token(TokenType.OPERATOR, "!=="); }
                    else if (lookahead == "=") { return new Token(TokenType.OPERATOR, "!="); }
                    else { it.putBack(); return new Token(TokenType.OPERATOR, "!"); }
                }
                case 9: {
                    if (lookahead == "&") { return new Token(TokenType.OPERATOR, "&&"); }
                    else if (lookahead == "=") { return new Token(TokenType.OPERATOR, "&="); }
                    else { it.putBack(); return new Token(TokenType.OPERATOR, "&"); }
                }
                case 10: {
                    if (lookahead == "|") { return new Token(TokenType.OPERATOR, "||"); }
                    else if (lookahead == "=") { return new Token(TokenType.OPERATOR, "|="); }
                    else { it.putBack(); return new Token(TokenType.OPERATOR, "|"); }
                }
                case 11: {
                    if (lookahead == "^") { return new Token(TokenType.OPERATOR, "^^"); }
                    else if (lookahead == "=") { return new Token(TokenType.OPERATOR, "^="); }
                    else { it.putBack(); return new Token(TokenType.OPERATOR, "^"); }
                }
                case 12: {
                    if (lookahead == "=") { return new Token(TokenType.OPERATOR, "%="); }
                    else { it.putBack(); return new Token(TokenType.OPERATOR, "%"); }
                }
                case 13: {
                    if ((lookahead == ".") && ("." == it.peek())) { it.next(); return new Token(TokenType.OPERATOR, "..."); }
                    else { return new Token(TokenType.OPERATOR, "."); }
                }
            }
        } // 这里结束 while

        throw new ScriptException("Unexpected error"); // 抛出异常
    }

    static makeNumber(it) { // 返回数字
        let state = 0;
        let s = "";

        while (it.hasNext()) {
            let lookahead = it.peek();

            switch (state) {
                case 0:
                    if (lookahead == "0") {
                        state = 1;
                    } else if (AlphabetMatch.isNumber(lookahead)) {
                        state = 2;
                    } else if (lookahead == "+" || lookahead == "-") {
                        state = 3;
                    } else if (lookahead == ".") {
                        state = 5;
                    }
                    break;
                case 1:
                    if (lookahead == "0") {
                        state = 1;
                    } else if (lookahead == ".") {
                        state = 4;
                    } else if (AlphabetMatch.isNumber(lookahead)) {
                        state = 2;
                    } else {
                        return new Token(TokenType.INTEGER, s);
                    }
                    break;
                case 2:
                    if (AlphabetMatch.isNumber(lookahead)) {
                        state = 2;
                    } else if (lookahead == ".") {
                        state = 4;
                    } else {
                        return new Token(TokenType.INTEGER, s);
                    }
                    break;
                case 3:
                    if (AlphabetMatch.isNumber(lookahead)) {
                        state = 2;
                    } else if (lookahead == ".") {
                        state = 5;
                    } else {
                        throw ScriptException.fromChar(lookahead);
                    }
                    break;
                case 4:
                    if (lookahead == ".") {
                        throw ScriptException.fromChar(lookahead);
                    } else if (AlphabetMatch.isNumber(lookahead)) {
                        state = 4;
                    } else {
                        return new Token(TokenType.FLOAT, s);
                    }
                    break;
                case 5:
                    if (AlphabetMatch.isNumber(lookahead)) {
                        state = 4;
                    } else {
                        throw ScriptException.fromChar(lookahead);
                    }
                    break;
            }
            s += lookahead;
            it.next();
        } // 这里结束 while
        throw new ScriptException("Unexpected error"); // 抛出异常
    }
}

词法分析器

class Lexer { // 词法分析器
    analyse(source) { // 这里是源代码的入口
        const tokens = [];
        const it = new characterIterator(source, "\0");

        while (it.hasNext()) {
            let c = it.next();
            // 如果遇到终结符 \0 ,就停止
            if (c == "\0") {
                break;
            }

            // 获取当前的指针指向
            let lookahead = it.peek();

            // 跳过空格,换行符
            if (c == " " || c == "\n" || c == "\r") {
                continue;
            }

            // 提取注释的程序
            if (c == "/") { //如果前一个字符是 /
                if (lookahead == "/") { // 如果当前字符也是 /
                    // 一直循环,直至遇到 \n 换行符,最后跳出循环
                    while (it.hasNext() && (c = it.next()) != "\n");
                    continue;
                } else if (lookahead == "*") { // 如果当前字符是 *
                    let valid = false; // 设置注释符的开关设置为不可用的状态
                    while (it.hasNext()) { // 开始一直读取字符
                        const p = it.next(); // 先读取一个字符
                        if (p == "*" && it.peek() == "/") { //如果读取的字符和当前指向的字符为 */ ,那就意味着注释的终结
                            valid = true; // 将开关置为 true 可用状态
                            it.next(); // 由于当前指向的字符为 / , 所以,直接 next 跳过这个字符
                            break; // 跳出循环
                        }
                    }

                    if (!valid) { // 注释没有终结符,抛出异常
                        throw new ScriptException("comment not matched");
                    }
                    continue;
                }
            }

            if (c == "{" || c == "}" || c == "(" || c == ")" || c == "[" || c == "]") { // 如果遇到了括号
                tokens.push(new Token(TokenType.BRACKET, c)); // 将括号压入队列
                continue;
            }

            if (c == '"' || c == "'") {// 如果遇到了单引号或者双引号
                it.putBack(); // 将c退回流中
                tokens.push(Token.makeString(it)); // 将it数据流,放到makeString中提取字符串
                continue;
            }

            if (AlphabetMatch.isLetter(c)) {// 判断是否字符串
                it.putBack(); // 将c退回流中
                tokens.push(Token.makeVarOrKeyword(it));
                continue;
            }

            if (AlphabetMatch.isNumber(c)) {// 判断是否数字
                it.putBack(); // 将c退回流中
                tokens.push(Token.makeNumber(it));
                continue;
            }

            // + -
            if ((c == "+" || c == "-") && AlphabetMatch.isNumber(lookahead)) {
                // 跳过:a+1, 1+1
                // +5, 3*-5
                const lastToken = tokens[tokens.length - 1] || null;

                if (lastToken == null || !lastToken.isValue()) {
                    it.putBack();
                    tokens.push(Token.makeNumber(it));
                    continue;
                }
            }

            if (AlphabetMatch.isOperator(c)) { // 判断是否操作符
                it.putBack();
                tokens.push(Token.makeOp(it));
                continue;
            }

            throw ScriptException.fromChar(c);
        }
        return tokens;
    }

    static fromFile(src) { // 从远程文件中加载源代码
        // 此处代码未完善
    }
}

设计一个队列处理器

该队列处理器为了方便后面的迭代器的操作


/**
 * 队列模块
 * 该队列为一个数组,可以设置长度,可以从头部压入,尾部弹出;也可以从尾部压入,头部弹出。
 */
class Queue {
  /**
   * @param {number} items_count 设置队列的长度
   */
  constructor(items_count) {
    this.items_count = items_count || 2000; // 队列尺寸
    this.items = []; // 队列实体
  }

  /**
   * @property {Function} enqueue_to_head 头部入队
   * @param {*} input 元素输入
   * @returns {boolean|Object} 如果储存的长度溢出,则将最先进入的元素从尾部弹出,否则,返回false
   */
  enqueue_to_head(input) {
    this.items.unshift(input);
    if (this.items_count < this.items.length) {
      // 若数据入队,则将旧数据挤出
      return this.dequeue_from_tail();
    } else {
      return false;
    }
  }

  /**
   * @property {Function} dequeue_from_head 头部出队
   * @returns {boolean|Object} 头部出队,如果没有内容,则返回false
   */
  dequeue_from_head() {
    var result = this.items.shift();
    return typeof result != "undefined" ? result : false;
  }

  /**
   * @property {Function} enqueue_to_tail 尾部入队
   * @param {input} 元素输入
   * @returns {boolean|Object} 如果储存的长度溢出,则将最先进入的元素从头部弹出,否则,返回false
   */
  enqueue_to_tail(input) {
    this.items.push(input);
    if (this.items_count < this.items.length) {
      return this.dequeue_from_head();
    } else {
      return false;
    }
  }

  /**
   * @property {Function} dequeue_from_tail 尾部出队
   * @returns {boolean|Object} 尾部出队,如果没有内容,则返回false
   */
  dequeue_from_tail() {
    var result = this.items.pop();
    return typeof result != "undefined" ? result : false;
  }

  /**
   * @property {Function} isEmpty 队列是否空
   * @returns {boolean} 如果为空,返回True
   */
  isEmpty() {
    return this.items.length == 0;
  }

  /**
   * @property {Function} clearQueue 清空队列
   * @returns {void}
   */
  clearQueue() {
    this.items = [];
  }

  /**
   * @property {Function} showQueue 打印队列
   * @returns {void}
   */
  showQueue() {
    console.log("show Queue:");
    for (let index = 0; index < this.items.length; index++) {
      console.log(index + ":", this.items[index]);
    }
  }
}

源代码字符串迭代器

/**
 * @class
 * @param {Iterable} it 迭代的数组
 * @param {String} endToken 终结符
 * @method peek 获取即将读取的值
 * @method putBack 回退
 */
class characterIterator {
    constructor(it, endToken = null) {
        this.it = it;

        // 需要putback的元素
        this.stackPutBacks = new Queue();

        // 基于时间窗口的缓存
        this.queueCache = new Queue(10);

        // this.stackPutBacks.items = it;

        this.endToken = endToken;
    }


    peek() {
        if (!this.stackPutBacks.isEmpty()) { // 如果不空
            let tail = this.stackPutBacks.items.length - 1;
            var tmp = this.stackPutBacks.items[tail];
            return tmp;
        }

        const val = this.next()
        this.putBack()
        return val
    }

    putBack() {
        if (!this.queueCache.isEmpty()) { // 判断缓存是不空了
            var val = this.queueCache.dequeue_from_tail() // 缓存尾部出队
            this.stackPutBacks.enqueue_to_tail(val); // 尾部入队
        }
    }

    hasNext() {
        // 该语句会将任意类型转为bool类型
        return this.endToken || !!this.peek();
    }

    next() {
        let val = null;

        if (!this.stackPutBacks.isEmpty()) { // 判断是否空了
            val = this.stackPutBacks.dequeue_from_tail() // 尾部出队

        } else {
            val = this.it.next().value
            if (val === undefined) {
                const tmp = this.endToken
                this.endToken = null
                val = tmp
            }
        }

        this.queueCache.enqueue_to_tail(val) // 尾部入队
        return val;
    }
}

代码的块级运行环境

我们的脚本要能运行一下变量的计算,因此就要设计一个块级的运行环境,设计变量的作用域

脚本源码

var a = 1+6*(2-1)/3; // 1621-*3/+
var b = 1+a*2/1/3; // 162*1/3/+
var c = 1+(2-1)/3; // 121-3/+

运行效果如下图

在这里插入图片描述

上代码


class Block { // 代码块的运行环境
    constructor(){
        this.mem = { // 抽象语法树
            "parent" : null
        };
        this.mempoint = this.mem // 语法树指针

        this.level = {}; // 符号等级树
        this.levelStep = 0; // 当前符号等级

        //建立运算符级别表
        for (let index = 0; index < window.calculatelevel.length; index++) {
            for (let _index = 0; _index < window.calculatelevel[index].length; _index++) {
                this.level[window.calculatelevel[index][_index]] = index;
            }
        }

    }

    /**
     * @property {Function} getLevel 获取关键词的级别
     * @param {Object} operator 输入关键词
     * @returns {Number} 返回级别编号,数字越大,级别越高
     */
    getLevel(operator) {
        return this.level[operator.getValue()] + this.levelStep*window.calculatelevel.length;
    }

    /**
     * @property {Function} changeLevel 切换级别
     * @param {Object} bracket 终结符
     */
    changeLevel(bracket){
        if (bracket.getValue() == "(") {
            ++this.levelStep;
        } else if (bracket.getValue() == ")"){
            --this.levelStep;
        }
    }

    /**
     * @property {Function} calculate 计算
     * @param {Object} s 计算符号
     * @param {Object} b 值
     * @param {Object} a 值
     * @returns {Object}
     */
    calculate(s,b,a){
        var aa,bb;

        if (a.getType() == TokenType.INTEGER) {
            aa = parseInt(a.getValue())
        }else{
            aa = parseFloat(a.getValue())
        }

        if (b.getType() == TokenType.INTEGER) {
            bb = parseInt(b.getValue())
        }else{
            bb = parseFloat(b.getValue())
        }

		var calculatemap = {
			"+" : function(a,b){return a+b;},
			"-" : function(a,b){return a-b;},
			"*" : function(a,b){return a*b;},
			"/" : function(a,b){return a/b;}
		}
        var t = calculatemap[s.getValue()](aa,bb);
		if(t%1 === 0){
            return new Token(TokenType.INTEGER, ""+t);
        }else{
            return new Token(TokenType.FLOAT, ""+t);
        }
	}

    /**
     * @property {Function} ReversePoland 逆波兰式
     * @param {String} it 迭代器
     * @returns {Object} 返回结果
     */
    ReversePoland(it){
        var sop = [];
		var L = [];

        while (it.hasNext()) {
            var step = it.next();
            // 如果遇到终结符,换行符,分号,则退出循环
            if ((step.getValue() == "\0")||(step.getValue() == "\n")||(step.getValue() == "\r")||(step.getValue() == ";")) {
                while (sop.length != 0) {
                    L.push(sop.pop());
                }
                break;
            }
            switch (step.getType()) {
                case TokenType.VARIABLE: // 如果是变量
                    // 则直接追加到 L中
                    if (!this.mempoint.hasOwnProperty(step.getValue())) { throw new Error("变量未定义"); } // 如果没有找到已经定义的变量

                    if ((this.mempoint[step.getValue()].getType() == TokenType.INTEGER)||
                        (this.mempoint[step.getValue()].getType() == TokenType.FLOAT)) {
                        
                        L.push(this.mempoint[step.getValue()]);
                    } else { throw new Error("变量非数字"); }
                    break;
                case TokenType.BRACKET: // 如果是分界符
                    this.changeLevel(step); // 切换级别
                    break;
                case TokenType.INTEGER: // 整数
                case TokenType.FLOAT: // 浮点数
                    L.push(step);
                    if((L[L.length-1].getType() == TokenType.OPERATOR)&&
                       (L[L.length-1].getLevel() >= sop[sop.length-1].getLevel())
                        ){
                        L.push(sop.pop());
                    }
                    break;
                case TokenType.STRING: // 字符串
                case TokenType.BOOLEAN: // 布尔
                    return step;
                case TokenType.OPERATOR: // 如果是运算符
                    step._level = this.getLevel(step) // 获取优先级
                    if (sop.length == 0) { // 如果栈为空
                        sop.push(step);
                    } else if(step.getLevel() > sop[sop.length-1].getLevel()){
                        sop.push(step);// 如果当前符号级别比栈顶级别高
                    } else {
                        // 如果当前符号级别比栈顶级别 等于或低于
                        while ((step.getLevel() <= sop[sop.length-1].getLevel())&&(sop.length != 0)) {
                            L.push(sop.pop());
                        }
                        sop.push(step);
                    }
                    break;
                default:
                    throw new Error("赋值失败");
            }
        }
        for (let index = 0; index < L.length; index++) {
            switch (L[index].getValue()) {
                case "+":
                case "-":
                case "*":
                case "/":
                    sop.push(this.calculate(L[index],sop.pop(),sop.pop()))
                    break;
                default:
                    sop.push(L[index]);
            }
        }
        return sop.pop();
    }

    setvariable(it){ // 设置变量
        while (it.hasNext()) {
            var step = it.next();
            if ((step.getValue() == "\0")||(step.getValue() == "\n")||(step.getValue() == "\r")||(step.getValue() == ";")) { break; }

            if (this.mempoint.hasOwnProperty(step.getValue())) { throw new Error("重复定义变量"); }
            if ((step.getType() == TokenType.VARIABLE)&&(it.next().getValue() == "=")) {
                this.mempoint[step.getValue()] = this.ReversePoland(it); // 在此处设置变量
                break;
            }
        }
    }
}

最后封装成简单的脚本解析器

class SimpleParser extends Block{
    constructor(){
        super();
        var that = this;
        this._dokeyword = {
            "var" : function (it) {
                that.setvariable(it)
            }
        };
    }

    parse(tokens) {
        const it = new characterIterator(tokens);
        while (it.hasNext()) {
            var step = it.next();
            if (step.getValue() == "\0") { break; }
            switch (step.getType()){
                case TokenType.KEYWORD:
                    this._dokeyword[step.getValue()](it);
                    break;
                case TokenType.VARIABLE:
                    // if(!this.mempoint.hasOwnProperty(step.getValue())) { throw new Error("未定义的变量或函数"); } // 判断变量是否重复定义
                    // if(this.mempoint[step.getValue()].isFunction()){
                    //     this.mempoint = this.mempoint[step.getValue()];
                    // } // 如果是函数
                    break;
            }
        }
        console.log(this.mem);
    }
}

暂时这个解析器还比较粗糙,接下来要设计虚拟机来执行这些脚本,所以暂时做到这个阶段,
下一篇会开始介绍虚拟机的开发。包括虚拟机的机器指令集,内存作用域。当打好这些基础做好之后,再继续回过头来完善这个脚本解析器。

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/156955.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

【HTML基础篇003】前端基础之CSS选择器大全

✨一、CSS的基本介绍 CSS&#xff08;Cascading Style Sheet&#xff0c;层叠样式表)定义如何显示HTML元素。 当浏览器读到一个样式表&#xff0c;它就会按照这个样式表来对文档进行格式化&#xff08;渲染&#xff09;。 ✨二、CSS的几种引入方式 &#x1f338;2.1、方法一&am…

连接查询之内连接(等值连接、非等值连接和自连接)

1、等值连接&#xff1a;表连接条件是等值关系&#xff0c;我们称为等值连接。 需求&#xff1a;查询每个员工所在部门名称&#xff0c;显示员工名和部门名&#xff1a; 查看员工表的ename和deptno字段信息&#xff1a; 查看部门表中的deptno和dname字段信息 SQL92语法&#x…

2023年最值得关注的机器人趋势TOP10

新兴的机器人技术趋势和预测满足了对工业自动化、数字化和可持续性的需求。仓库中的材料处理由自动移动机器人&#xff08;AMR&#xff09;和自动引导车辆&#xff08;AGV&#xff09;实现自动化。相关机构对8949家全球初创企业和2023年机器人趋势规模的样本进行了研究&#xf…

Linux的安装(云服务器专讲)

一、Linux环境的安装有一下几种方式&#xff1a;双系统或则将自己的笔记本搞成Linux系统——严重不推荐&#xff0c;这样安装成本高、并且容易把自己电脑弄坏。虚拟机推荐wmware player这是免费的&#xff0c;并且推荐是打在了centos7.x版本的&#xff0c;这个的好处就是不需要…

微信接入 ChatGPT(学习笔记,不作教程)

微信接入 ChatGPT前置条件接入前提去Linux虚拟机&#xff08;必须有go环境&#xff09;安装前先配置下ssh密钥生成新的ssh密钥检查将 SSH 密钥添加到 ssh-agent将 SSH 密钥添加到您在 GitHub 上的帐户上去github上将密钥复制在里面然后点击添加ssh密钥安转部署最后直接go run m…

【JavaSE】Java序列化详解

【JavaSE】Java序列化详解 文章目录【JavaSE】Java序列化详解一&#xff1a;什么是序列化和反序列化?二&#xff1a;序列化协议对应于 TCP/IP 4 层模型的哪一层&#xff1f;三&#xff1a;常见序列化协议有哪些&#xff1f;四&#xff1a;JDK 自带的序列化方式1&#xff1a;序…

广告业务系统 之 数据桥梁 —— “日志中心-曝光数据流转结算”

文章目录广告业务系统 之 数据桥梁 —— “日志中心-曝光数据流转结算”曝光数据流转结算管道式架构助力高可用管道式架构模式图流式链路中特殊的缓存设计一、二级缓存Nosql 数据型缓存组件s2s 监测上报广告业务系统 之 数据桥梁 —— “日志中心-曝光数据流转结算” 曝光数据…

SpringCloud微服务项目实战 - 5.自媒体文章审核

愤怒归根结底是为了达成目的的一种工具和手段&#xff0c;大声呵斥乃至拍桌子&#xff0c;目的都是通过震慑对方&#xff0c;进而使其听自己的话&#xff0c;因为他们也找不到更好的办法。 系列文章目录 项目搭建App登录及网关App文章自媒体平台&#xff08;博主后台&#xff…

68.多尺度目标锚框的代码实现

在之前&#xff0c;我们以输入图像的每个像素为中心&#xff0c;生成了多个锚框。 基本而言&#xff0c;这些锚框代表了图像不同区域的样本。 然而&#xff0c;如果为每个像素都生成的锚框&#xff0c;我们最终可能会得到太多需要计算的锚框。 想象一个 561728 的输入图像&…

UG/NX 二次开发(C#)自动出2D零件图(标准件配置Bata版)

一、前言 昨天分享了自动出2D零件图的思路&#xff08;UG/NX 二次开发(C#)自动出2D零件图思路&#xff09;&#xff0c;今天整理了Q群里各位大佬的意见&#xff0c;结合当前实际项目情况&#xff0c;做了一个可配置的半自动出图版本&#xff0c;暂且称之为标准件配置Bata版。 虽…

steam/csgo搬砖项目详解

steam搬砖项目简单来说&#xff0c;就是在steam平台购买游戏装备到网易BUFF平台出售&#xff0c;赚取汇率差和装备差价。 我今天也就给大家讲解一下steam项目的原理&#xff0c;还有存在什么样的风险。 做steam搬砖需要什么准备&#xff1a; 1.手机 2.电脑 3.美服steam账号 4.…

【hcip】多点双向重发布实验

目录 1.拓扑图 2.要求 3.主要配置 4.测试 1.拓扑图 2.要求 达到所有目标最优&#xff0c;互有备份 3.主要配置 左边区域配置rip&#xff0c;右边区域配置ospf&#xff0c;以r2为例 [r2]rip [r2-rip-1]version 2 [r2-rip-1]network 12.0.0.0 [r2-rip-1]network 2.0.…

堆排序详细说明及实现-python

先了解什么是堆&#xff1a; 堆的定义 n个元素的序列 [ k1&#xff0c;k2&#xff0c;...&#xff0c;kn ]&#xff0c;满足以下的性质时称之为堆&#xff1a; ki≥k2i 且 ki≥k2i1 (1≤i≤ ) 或 ki≤k2i 且 ki≤k2i1 (1≤i≤ ) 如果采用顺序方式即用一维数组存储这个序列&…

《回眸2022·圆满收官||展望2023·砥砺奋发》

系列文章目录 文章目录系列文章目录寄言和CSDN相遇大学生活从小白到千粉博主回眸2022|圆满收官展望2023|砥砺奋发致每一个追梦人寄言 岁月不距&#xff0c;时节如流&#xff01;站在岁末的门槛前&#xff0c;回望2022这一年&#xff0c;不知你是否已经完美的书写完2022的答卷&…

计算机工作过程(超详细)

文章目录一、计算机组成框图二、思维导图三、部件剖析&#xff08;1&#xff09;存储器&#xff08;2&#xff09;运算器&#xff08;3&#xff09;控制器四、案例剖析&#xff08;重点&#xff09;&#xff08;1&#xff09;a2&#xff08;2&#xff09;a*b&#xff08;3&…

vscode配置Markdown snippet 的快捷键

Snippet 是&#xff1f; 不同文件的模板语法/代码片段&#xff0c;可以设置好后在不同类型文件快速插入&#xff0c;提高效率。 例如&#xff1a;可以在Markdown使用快捷键生成自己想要的模板内容&#xff0c;自定义输入时tab的跳转位置 具体设置 官方文档 生成snippet的在…

Apache Spark 机器学习 数据源 2

数据源 数据源作为机器学习的数据输入&#xff0c;以供给Spark进行机器学习&#xff0c;Spark技术框架除了支持Parquet、CSV、JSON以及JDBC这些常用的数据源&#xff0c;还提供一些特殊数据源的支持&#xff0c;例如&#xff0c;图像或者LIBSVM。 Parquet数据源 该数据源是a…

2023什么蓝牙耳机值得入手?值得入手的半入耳蓝牙耳机推荐

毫无疑问&#xff0c;近年来蓝牙耳机的外出使用频率越来越高&#xff0c;这是由于其外出携带的便捷性以及配置越来越高端、先进&#xff0c;而半入耳式蓝牙耳机又凭借更舒适的佩戴体验以及便携性受到用户的喜爱。但&#xff0c;面对形形色色的蓝牙耳机&#xff0c;不同价位、不…

Shell中的 test 命令

Shell中的 test 命令用于检查某个条件是否成立&#xff0c;它可以进行数值、字符和文件三个方面的测试。数值测试参数说明-eq等于则为真-ne不等于则为真-gt大于则为真-ge大于等于则为真-lt小于则为真-le小于等于则为真实例num1100num2100if test $[num1] -eq $[num2]thenecho 两…

完全背包理论基础

目录 一.理论基础 二.遍历顺序问题 2.1 01背包 2.2完全背包 3.相关题型 3.1零钱兑换 3.1.数组总和IV 一.理论基础 题目描述&#xff1a; 有N件物品和一个最多能背重量为W的背包。第i件物品的重量是weight[i]&#xff0c;得到的价值是value[i] 。每件物品都有无限个&…