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

Posted 妇男主任

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了用JS自制表格软件玩数据10. 为表格脚本设计一个语法解析器相关的知识,希望对你有一定的参考价值。

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

概述

在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; 用JS自制表格软件玩数据8. 设计单元格中的右键菜单

用JS自制表格软件玩数据8. 设计单元格中的右键菜单

用JS自制表格软件玩数据7. 设计常用的样式功能与单元格合并

用JS自制表格软件玩数据5. 渲染出整个Excel单元格

用JS自制表格软件玩数据7. 设计常用的样式功能与单元格合并

用JS自制表格软件玩数据5. 渲染出整个Excel单元格