用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种类型
- KEYWORD 关键词
- VARIABLE 变量
- STRING 字符串
- OPERATOR 操作符
- BRACKET 分段符
- INTEGER 整数
- FLOAT 浮点数
- BOOLEAN 布尔值
- OBJECT 对象
- FUNCTION 函数
- 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自制表格软件玩数据7. 设计常用的样式功能与单元格合并