diff --git a/.gitignore b/.gitignore index 1a38e12..005a38b 100644 --- a/.gitignore +++ b/.gitignore @@ -1,6 +1,10 @@ /node_modules/ /src/*.js.map /dist/*.js.map -/examples/*/*.js.map +/dist/examples/*/*.js.map /.cache/ -/src/*/*.js.map +/dist/src/*.js.map +/dist/vm/*.js.map +/dist/test/*.js.map +/dist/src/*/*.js.map +/dist/vm/*/*.js.map \ No newline at end of file diff --git a/README.md b/README.md index 277477c..b4e1fab 100644 --- a/README.md +++ b/README.md @@ -10,11 +10,13 @@ Integer ::= [0-9]+ Number ::= Integer Ignored String ::= '"' '"' Ignored | '"' StringCharacter '"' Ignored Variable ::= "$" Name Ignored // 变量 -Assignment ::= Variable Ignored '=' Ignored ( String | Number ) Ignored +Assignment ::= Variable Ignored '=' Ignored ( String | Number | Variable) Ignored Print ::= "print" "(" Ignored Variable Ignored ")" Ignored Statement ::= Print | Assignment SourceCode ::= Statement+ -Comment ::= Ignored "#" SourceCharacter Ignored // 注释 +Comment ::= Ignored "#" SourceCharacter // 注释 +Expression ::= Variable Ignored Operator Ignored Variable +Operator ::= "+" | "-" | "*" | "/" ``` @@ -34,3 +36,6 @@ npm run test ## Contributors - [karminski](https://github.com/karminski) - [liulinboyi](https://github.com/liulinboyi) + + +# 最后把代码转成JavaScript的AST然后使用[javascript的解释器canjs](https://github.com/jrainlau/canjs)执行代码. diff --git a/examples/pineapple/hello-world-1.pineapple b/demo/hello-world-1.pineapple similarity index 100% rename from examples/pineapple/hello-world-1.pineapple rename to demo/hello-world-1.pineapple diff --git a/demo/hello-world-2.pineapple b/demo/hello-world-2.pineapple new file mode 100644 index 0000000..d31317b --- /dev/null +++ b/demo/hello-world-2.pineapple @@ -0,0 +1,5 @@ +#$a = 0 +$b = 1 +print($b) +$e = "啊哈哈哈哈" +#$c = $b \ No newline at end of file diff --git a/examples/pineapple/hello-world.pineapple b/demo/hello-world.pineapple similarity index 100% rename from examples/pineapple/hello-world.pineapple rename to demo/hello-world.pineapple diff --git a/dist/examples/pineapple/main.js b/dist/examples/pineapple/main.js new file mode 100644 index 0000000..72a5049 --- /dev/null +++ b/dist/examples/pineapple/main.js @@ -0,0 +1,21 @@ +"use strict"; +var __importDefault = (this && this.__importDefault) || function (mod) { + return (mod && mod.__esModule) ? mod : { "default": mod }; +}; +Object.defineProperty(exports, "__esModule", { value: true }); +const backend_1 = require("../../src/backend"); +const fs_1 = __importDefault(require("fs")); +const args = process.argv.slice(2); +if (args[0]) { + let code = ''; + try { + code = fs_1.default.readFileSync(args[0], { encoding: 'utf-8' }); + } + catch (error) { + console.log(`Error reading file: ${args[0]}`); + } + if (code.length > 0) { + backend_1.Execute(code); + } +} +//# sourceMappingURL=main.js.map \ No newline at end of file diff --git a/src/backend.js b/dist/src/backend.js similarity index 73% rename from src/backend.js rename to dist/src/backend.js index 5440b3d..6c9ba31 100644 --- a/src/backend.js +++ b/dist/src/backend.js @@ -1,10 +1,14 @@ "use strict"; +var __importDefault = (this && this.__importDefault) || function (mod) { + return (mod && mod.__esModule) ? mod : { "default": mod }; +}; Object.defineProperty(exports, "__esModule", { value: true }); exports.Execute = void 0; const parser_1 = require("./parser"); const Assignment_1 = require("./parser/Assignment"); const Print_1 = require("./parser/Print"); const Comment_1 = require("./parser/Comment"); +const index_js_1 = __importDefault(require("../vm/index.js")); let GlobalVariables = { Variables: {} }; @@ -18,10 +22,17 @@ function Execute(code) { let g = NewGlobalVariables(); // parse ast = parser_1.parse(code); + for (let item in ast.body) { + if (ast.body[item].type === "COMMENT") { // 如果是注释,删除 + ast.body.splice(item, 1); + } + } console.log(JSON.stringify(ast, null, 4), '\r\rAST'); console.log("--------------------------------------------"); // resolve - resolveAST(g, ast); + const vm = new index_js_1.default(ast); + vm.run(); + // resolveAST(g, ast) } exports.Execute = Execute; function resolveAST(g, ast) { @@ -55,15 +66,19 @@ function resolveAssignment(g, assignment) { if (varName == "") { throw new Error("resolveAssignment(): variable name can NOT be empty."); } - if (assignment.String !== null && assignment.String !== undefined) { - g.Variables[varName] = assignment.String; - } - else if (assignment.Number !== null && assignment.Number !== undefined) { - g.Variables[varName] = assignment.Number; + if (assignment.Literal) { + g.Variables[varName] = assignment.Literal.value; } else { throw new Error("Ivalie value."); } + // if (assignment.String !== null && assignment.String !== undefined) { + // g.Variables[varName] = assignment.String + // } else if (assignment.Number !== null && assignment.Number !== undefined) { + // g.Variables[varName] = assignment.Number + // } else { + // throw new Error("Ivalie value."); + // } return null; } function resolvePrint(g, print) { diff --git a/src/definition.js b/dist/src/definition.js similarity index 51% rename from src/definition.js rename to dist/src/definition.js index ad2a0c8..5d11404 100644 --- a/src/definition.js +++ b/dist/src/definition.js @@ -1,3 +1,8 @@ "use strict"; Object.defineProperty(exports, "__esModule", { value: true }); +// export interface Variable { +// LineNum?: number, +// Name?: string, +// [x: string]: any +// } //# sourceMappingURL=definition.js.map \ No newline at end of file diff --git a/src/lexer.js b/dist/src/lexer.js similarity index 100% rename from src/lexer.js rename to dist/src/lexer.js diff --git a/src/lexer1.js b/dist/src/lexer1.js similarity index 94% rename from src/lexer1.js rename to dist/src/lexer1.js index 328855d..32362aa 100644 --- a/src/lexer1.js +++ b/dist/src/lexer1.js @@ -66,6 +66,39 @@ class Lexer { this.nextTokenType = nextTokenType; this.hasCache = false; } + /** + * LookAhead (向前看) 一个 Token, 告诉我们下一个 Token 是什么 + * @returns + */ + LookAhead() { + // lexer.nextToken already setted + if (this.hasCache) { + return { tokenType: this.nextTokenType, lineNum: this.lineNum, token: this.nextToken }; + // return this.nextTokenType + } + // set it + // 当前行 + let { lineNum, tokenType, token } = this.GetNextToken(); + // * + // 下一行 + this.hasCache = true; + this.lineNum = lineNum; + this.nextTokenType = tokenType; + this.nextToken = token; + return { tokenType, lineNum, token }; + } + LookAheadAndSkip(expectedType) { + // get next token + // 查看看下一个Token信息 + let { lineNum, tokenType, token } = this.GetNextToken(); + // not is expected type, reverse cursor + if (tokenType != expectedType) { + this.hasCache = true; + this.lineNum = lineNum; + this.nextTokenType = tokenType; + this.nextToken = token; + } + } /** * 断言下一个 Token 是什么 */ @@ -94,8 +127,22 @@ class Lexer { } return this.MatchToken(); } + checkCode(c) { + // 确保源代码,不包含非法字符,对应着SourceCharacter的EBNF + if (!/\u0009|\u000A|\u000D|[\u0020-\uFFFF]/.test(this.sourceCode[0])) { + throw new Error('The source code contains characters that cannot be parsed.'); + } + } + // 直接跳过几个字符,返回被跳过的字符 + next(skip) { + this.checkCode(this.sourceCode[0]); + const code = this.sourceCode[0]; + this.skipSourceCode(skip); + return code; + } // 匹配Token并跳过匹配的Token MatchToken() { + this.checkCode(this.sourceCode[0]); // 只做检查,不吃字符 // console.log(this.sourceCode[0], '当前Token') // check ignored if (this.isIgnored()) { @@ -227,39 +274,6 @@ class Lexer { GetLineNum() { return this.lineNum; } - /** - * LookAhead (向前看) 一个 Token, 告诉我们下一个 Token 是什么 - * @returns - */ - LookAhead() { - // lexer.nextToken already setted - if (this.hasCache) { - return { tokenType: this.nextTokenType, lineNum: this.lineNum, token: this.nextToken }; - // return this.nextTokenType - } - // set it - // 当前行 - let { lineNum, tokenType, token } = this.GetNextToken(); - // * - // 下一行 - this.hasCache = true; - this.lineNum = lineNum; - this.nextTokenType = tokenType; - this.nextToken = token; - return { tokenType, lineNum, token }; - } - LookAheadAndSkip(expectedType) { - // get next token - // 查看看下一个Token信息 - let { lineNum, tokenType, token } = this.GetNextToken(); - // not is expected type, reverse cursor - if (tokenType != expectedType) { - this.hasCache = true; - this.lineNum = lineNum; - this.nextTokenType = tokenType; - this.nextToken = token; - } - } // return content before token scanBeforeToken(token) { // 以单个双引号,划分数组 diff --git a/src/parser.js b/dist/src/parser.js similarity index 82% rename from src/parser.js rename to dist/src/parser.js index f334b81..7f96b81 100644 --- a/src/parser.js +++ b/dist/src/parser.js @@ -1,17 +1,28 @@ "use strict"; Object.defineProperty(exports, "__esModule", { value: true }); -exports.parse = exports.parseString = exports.parseNumber = exports.parseVariable = exports.SourceCharacter = exports.COMMENT = exports.STRING = exports.NUMBER = exports.INTERGER = exports.TOKEN_IGNORED = exports.TOKEN_PRINT = exports.TOKEN_NAME = exports.TOKEN_DUOQUOTE = exports.TOKEN_QUOTE = exports.TOKEN_EQUAL = exports.TOKEN_RIGHT_PAREN = exports.TOKEN_LEFT_PAREN = exports.TOKEN_VAR_PREFIX = exports.TOKEN_EOF = void 0; +exports.parse = exports.parseString = exports.parseNumber = exports.parseVariable = exports.Program = exports.SourceCharacter = exports.COMMENT = exports.STRING = exports.NUMBER = exports.INTERGER = exports.TOKEN_IGNORED = exports.TOKEN_PRINT = exports.TOKEN_NAME = exports.TOKEN_DUOQUOTE = exports.TOKEN_QUOTE = exports.TOKEN_EQUAL = exports.TOKEN_RIGHT_PAREN = exports.TOKEN_LEFT_PAREN = exports.TOKEN_VAR_PREFIX = exports.TOKEN_EOF = void 0; const lexer1_1 = require("./lexer1"); const Comment_1 = require("./parser/Comment"); const Print_1 = require("./parser/Print"); const Assignment_1 = require("./parser/Assignment"); exports.TOKEN_EOF = lexer1_1.Tokens.TOKEN_EOF, exports.TOKEN_VAR_PREFIX = lexer1_1.Tokens.TOKEN_VAR_PREFIX, exports.TOKEN_LEFT_PAREN = lexer1_1.Tokens.TOKEN_LEFT_PAREN, exports.TOKEN_RIGHT_PAREN = lexer1_1.Tokens.TOKEN_RIGHT_PAREN, exports.TOKEN_EQUAL = lexer1_1.Tokens.TOKEN_EQUAL, exports.TOKEN_QUOTE = lexer1_1.Tokens.TOKEN_QUOTE, exports.TOKEN_DUOQUOTE = lexer1_1.Tokens.TOKEN_DUOQUOTE, exports.TOKEN_NAME = lexer1_1.Tokens.TOKEN_NAME, exports.TOKEN_PRINT = lexer1_1.Tokens.TOKEN_PRINT, exports.TOKEN_IGNORED = lexer1_1.Tokens.TOKEN_IGNORED, exports.INTERGER = lexer1_1.Tokens.INTERGER, exports.NUMBER = lexer1_1.Tokens.NUMBER, exports.STRING = lexer1_1.Tokens.STRING, exports.COMMENT = lexer1_1.Tokens.COMMENT, exports.SourceCharacter = lexer1_1.Tokens.SourceCharacter; +class Program { + constructor(type, body, LineNum) { + this.type = 'Program'; + this.body = body; + this.LineNum = LineNum; + } +} +exports.Program = Program; // SourceCode ::= Statement+ function parseSourceCode(lexer) { + let program = new Program(); let sourceCode = {}; - sourceCode.LineNum = lexer.GetLineNum(); - sourceCode.Statements = parseStatements(lexer); - return sourceCode; + // sourceCode.LineNum = lexer.GetLineNum() + // sourceCode.Statements = parseStatements(lexer) + program.LineNum = lexer.GetLineNum(); + program.body = parseStatements(lexer); + return program; } // Statement ::= Print | Assignment function parseStatements(lexer) { @@ -28,7 +39,7 @@ function parseStatement(lexer) { // 向前看一个token并跳过 lexer.LookAheadAndSkip(exports.TOKEN_IGNORED); // skip if source code start with ignored token let look = lexer.LookAhead().tokenType; - // console.log(look, 'look') + console.log(look, 'look'); switch (look) { case exports.TOKEN_PRINT: return Print_1.parsePrint(lexer); @@ -45,7 +56,7 @@ function isSourceCodeEnd(token) { } // Variable ::= "$" Name Ignored function parseVariable(lexer) { - let variable = {}; + let variable = { Name: '' }; variable.LineNum = lexer.GetLineNum(); lexer.NextTokenIs(exports.TOKEN_VAR_PREFIX); variable.Name = parseName(lexer); @@ -68,12 +79,12 @@ function parseNumber(lexer) { if (tokenType === exports.NUMBER) { while (lexer.isNumber(lexer.sourceCode[0])) { // console.log(lexer.sourceCode[0]) - str = lexer.sourceCode[0]; - lexer.skipSourceCode(1); + str = lexer.next(1); } if (!lexer.isIgnored()) { throw new Error('Uncaught SyntaxError: Invalid or unexpected token'); } + lexer.NextTokenIs(exports.NUMBER); lexer.LookAheadAndSkip(exports.TOKEN_IGNORED); } return +str; diff --git a/dist/src/parser/Assignment.js b/dist/src/parser/Assignment.js new file mode 100644 index 0000000..40737bf --- /dev/null +++ b/dist/src/parser/Assignment.js @@ -0,0 +1,67 @@ +"use strict"; +Object.defineProperty(exports, "__esModule", { value: true }); +exports.parseAssignment = exports.Assignment = exports.Literal = void 0; +const parser_1 = require("../parser"); +class Literal { + constructor(value, type = 'Literal') { + this.type = type; + this.value = value; + } +} +exports.Literal = Literal; +class Assignment { + constructor(LineNum, Variable, String, num, type, Literal, kind) { + this.LineNum = LineNum; + this.Variable = Variable; + this.String = String; + this.Number = num; + this.type = type; + this.Literal = Literal; + this.kind = 'let'; + } +} +exports.Assignment = Assignment; +// Assignment ::= Variable Ignored "=" Ignored String Ignored +function parseAssignment(lexer) { + let assignment = new Assignment(); + assignment.LineNum = lexer.GetLineNum(); + assignment.declarations = []; + let VariableDeclarator = { type: "VariableDeclarator" }; + VariableDeclarator.id = { type: "Identifier" }; + VariableDeclarator.id.name = parser_1.parseVariable(lexer).Name; + // assignment.Variable = parseVariable(lexer) // 标识符 + lexer.LookAheadAndSkip(parser_1.TOKEN_IGNORED); // 空格 + lexer.NextTokenIs(parser_1.TOKEN_EQUAL); // = + lexer.LookAheadAndSkip(parser_1.TOKEN_IGNORED); // 空格 + console.log(lexer.LookAhead().tokenType, 'lexer.LookAhead().tokenType'); + // 如果后面仍是$ + if (lexer.LookAhead().tokenType === parser_1.TOKEN_VAR_PREFIX) { + const Variable = parser_1.parseVariable(lexer); // 标识符 + console.log(Variable, 'Variable'); + assignment.Variable = Variable; + return assignment; + } + else { + if (lexer.isNumber(lexer.sourceCode[0])) { + // console.log('parseNumber start') + const literial = new Literal(parser_1.parseNumber(lexer)); + VariableDeclarator.init = literial; + // assignment.Literal = literial + // assignment.type = tokenNameMap[NUMBER] + assignment.type = "VariableDeclaration"; + // console.log('parseNumber end') + } + else { + const literial = new Literal(parser_1.parseString(lexer)); + // assignment.Literal = literial + VariableDeclarator.init = literial; + // assignment.type = tokenNameMap[STRING] + assignment.type = "VariableDeclaration"; + } + lexer.LookAheadAndSkip(parser_1.TOKEN_IGNORED); + assignment.declarations.push(VariableDeclarator); // 一行只允许声明和初始化一个变量 + return assignment; + } +} +exports.parseAssignment = parseAssignment; +//# sourceMappingURL=Assignment.js.map \ No newline at end of file diff --git a/src/parser/Comment.js b/dist/src/parser/Comment.js similarity index 76% rename from src/parser/Comment.js rename to dist/src/parser/Comment.js index b8bad40..65478fe 100644 --- a/src/parser/Comment.js +++ b/dist/src/parser/Comment.js @@ -1,12 +1,13 @@ "use strict"; Object.defineProperty(exports, "__esModule", { value: true }); exports.paseComment = exports.Comment = void 0; +const lexer1_1 = require("../lexer1"); const parser_1 = require("../parser"); class Comment { - constructor(LineNum, Content, Type) { + constructor(LineNum, Content, type) { this.LineNum = LineNum; this.Content = Content; - this.Type = Type; + this.type = type; } } exports.Comment = Comment; @@ -14,18 +15,16 @@ function paseComment(lexer) { let comment = new Comment(); console.log("paseComment start"); comment.LineNum = lexer.GetLineNum(); - lexer.LookAheadAndSkip(parser_1.TOKEN_IGNORED); // 空格 lexer.NextTokenIs(parser_1.COMMENT); - lexer.LookAheadAndSkip(parser_1.TOKEN_IGNORED); // 空格 console.log(lexer.isNewLine(lexer.sourceCode[0]), 'isNewLine'); let content = ""; // 如果换行或者源码为空则停止解析注释 while (!lexer.isNewLine(lexer.sourceCode[0]) && !lexer.isEmpty()) { - content += lexer.sourceCode[0]; - lexer.skipSourceCode(1); + content += lexer.next(1); } lexer.LookAheadAndSkip(parser_1.TOKEN_IGNORED); comment.Content = content; + comment.type = lexer1_1.tokenNameMap[parser_1.COMMENT]; return comment; } exports.paseComment = paseComment; diff --git a/dist/src/parser/Print.js b/dist/src/parser/Print.js new file mode 100644 index 0000000..696e5c7 --- /dev/null +++ b/dist/src/parser/Print.js @@ -0,0 +1,57 @@ +"use strict"; +Object.defineProperty(exports, "__esModule", { value: true }); +exports.parsePrint = exports.Print = void 0; +const lexer1_1 = require("../lexer1"); +const parser_1 = require("../parser"); +class Print { + constructor(LineNum, Variable, type) { + this.LineNum = LineNum; + this.Variable = Variable; + this.type = type; + } +} +exports.Print = Print; +// Print ::= "print" "(" Ignored Variable Ignored ")" Ignored +function parsePrint(lexer) { + let print = new Print(); + print.LineNum = lexer.GetLineNum(); + lexer.NextTokenIs(parser_1.TOKEN_PRINT); + lexer.NextTokenIs(parser_1.TOKEN_LEFT_PAREN); + lexer.LookAheadAndSkip(parser_1.TOKEN_IGNORED); + print.Variable = parser_1.parseVariable(lexer); + print.type = lexer1_1.tokenNameMap[parser_1.TOKEN_PRINT]; + lexer.LookAheadAndSkip(parser_1.TOKEN_IGNORED); + lexer.NextTokenIs(parser_1.TOKEN_RIGHT_PAREN); + lexer.LookAheadAndSkip(parser_1.TOKEN_IGNORED); + const p = { + "type": "ExpressionStatement", + "expression": { + "type": "CallExpression", + "callee": { + "type": "MemberExpression", + "object": { + "type": "Identifier", + "name": "console" + }, + "property": { + "type": "Identifier", + "name": "log" + }, + "computed": false, + "optional": false + }, + "arguments": [ + { + "type": "Identifier", + "name": "a" + } + ], + "optional": false + } + }; + // print 只打印,不计算 + p.expression.arguments[0].name = print.Variable.Name; + return p; +} +exports.parsePrint = parsePrint; +//# sourceMappingURL=Print.js.map \ No newline at end of file diff --git a/dist/test/test.js b/dist/test/test.js new file mode 100644 index 0000000..bd4a314 --- /dev/null +++ b/dist/test/test.js @@ -0,0 +1,10 @@ +"use strict"; +const fs = require('fs'); +const path = require('path'); +const Execute = require('../dist/src/backend.js').Execute; +code = fs.readFileSync(path.resolve(__dirname, '../demo/hello-world-2.pineapple'), { encoding: 'utf-8' }); +console.log(code, 'code'); +if (code.length > 0) { + Execute(code); +} +//# sourceMappingURL=test.js.map \ No newline at end of file diff --git a/dist/vm/es_versions/es5.js b/dist/vm/es_versions/es5.js new file mode 100644 index 0000000..4f2ade6 --- /dev/null +++ b/dist/vm/es_versions/es5.js @@ -0,0 +1,509 @@ +"use strict"; +/** + * @author Jrainlau + * @desc 节点处理器,处理AST当中的节点 + */ +// 关键字判断工具 +// 判断JS语句当中的return,break,continue关键字。 +const Signal = require('../signal'); +const { MemberValue } = require('../value'); +// 节点处理器 +const NodeHandler = { + Program(nodeIterator) { + for (const node of nodeIterator.node.body) { + nodeIterator.traverse(node); + } + }, + // 这里做作用域处理 + // 变量定义节点处理器 + VariableDeclaration(nodeIterator) { + const kind = nodeIterator.node.kind; + // nodeIterator.node.declarations 是一个数组,里面存放着VariableDeclarator + for (const declaration of nodeIterator.node.declarations) { + const { name /* Identifier name */ } = declaration.id; // 标识符Identifier + // declaration.init 可能是Literal或者CallExpression等类型,需要再次进行处理 + const value = declaration.init ? nodeIterator.traverse(declaration.init) : undefined; + // 问题来了,拿到了变量的名称和值,然后把它保存到哪里去呢? + /* + 处理完变量声明节点以后,理应把这个变量保存起来。 + 按照JS语言特性,这个变量应该存放在一个作用域当中。 + 在JS解析器的实现过程中,这个作用域可以被定义为一个scope对象。 + */ + // 在作用域当中定义变量 + // 若为块级作用域且关键字为var,则需要做全局污染 + if (nodeIterator.scope.type === 'block' && kind === 'var') { + nodeIterator.scope.parentScope.declare(name, value, kind); + } + else { + nodeIterator.scope.declare(name, value, kind); + } + } + }, + // 标识符节点处理器 + Identifier(nodeIterator) { + if (nodeIterator.node.name === 'undefined') { + return undefined; + } + return nodeIterator.scope.get(nodeIterator.node.name).value; + }, + // 字符节点处理器 + Literal(nodeIterator) { + return nodeIterator.node.value; + }, + ExpressionStatement(nodeIterator) { + return nodeIterator.traverse(nodeIterator.node.expression); + }, + // 表达式调用节点处理器 + // 用于处理表达式调用节点的处理器,如处理func(),console.log()等。 + CallExpression(nodeIterator) { + // 遍历callee获取函数体 + const func = nodeIterator.traverse(nodeIterator.node.callee); + // 获取参数 + const args = nodeIterator.node.arguments.map(arg => nodeIterator.traverse(arg)); + let value; + if (nodeIterator.node.callee.type === 'MemberExpression') { + value = nodeIterator.traverse(nodeIterator.node.callee.object); + } + // 返回函数运行结果 + return func.apply(value, args); + }, + // 表达式节点处理器 + // 表达式节点指的是person.say,console.log这种函数表达式。 + MemberExpression(nodeIterator) { + // 获取对象,如console + const obj = nodeIterator.traverse(nodeIterator.node.object); + // 获取对象的方法,如log + const name = nodeIterator.node.property.name; + // 返回表达式,如console.log + return obj[name]; + }, + ObjectExpression(nodeIterator) { + const obj = {}; + for (const prop of nodeIterator.node.properties) { + let key; + if (prop.key.type === 'Literal') { + key = `${prop.key.value}`; + } + else if (prop.key.type === 'Identifier') { + key = prop.key.name; + } + else { + throw new Error(`canjs: [ObjectExpression] Unsupported property key type "${prop.key.type}"`); + } + obj[key] = nodeIterator.traverse(prop.value); + } + return obj; + }, + ArrayExpression(nodeIterator) { + return nodeIterator.node.elements.map(ele => nodeIterator.traverse(ele)); + }, + // 块级声明节点处理器 + // 非常常用的处理器,专门用于处理块级声明节点,如函数、循环、try...catch...当中的情景。 + BlockStatement(nodeIterator) { + // 先定义一个块级作用域 + let scope = nodeIterator.createScope('block'); + // 处理块级节点内的每一个节点 + for (const node of nodeIterator.node.body) { + if (node.type === 'FunctionDeclaration') { + nodeIterator.traverse(node, { scope }); + } + else if (node.type === 'VariableDeclaration' && node.kind === 'var') { + /* + var a = 1,d = 3; 这种定义变量,declarations数组会有两个元素 + */ + for (const declaration of node.declarations) { + /* + var a = 1,d = 3,e; e的变量声明,没有赋值,那么AST中init就是null + */ + if (declaration.init) { + scope.declare(declaration.id.name, nodeIterator.traverse(declaration.init, { scope }), node.kind); + } + else { + scope.declare(declaration.id.name, undefined, node.kind); + } + } + } + } + // 提取关键字(return, break, continue) + for (const node of nodeIterator.node.body) { + if (node.type === 'FunctionDeclaration') { + continue; + } + const signal = nodeIterator.traverse(node, { scope }); + if (Signal.isSignal(signal)) { + return signal; + } + } + /* + 两个for...of循环。 + 第一个用于处理块级内语句, + 第二个专门用于识别关键字, + 如循环体内部的break,continue或者函数体内部的return。 + */ + }, + // 函数定义节点处理器 + // 向作用域当中声明一个和函数名相同的变量,值为所定义的函数 + FunctionDeclaration(nodeIterator) { + const fn = NodeHandler.FunctionExpression(nodeIterator); + nodeIterator.scope.varDeclare(nodeIterator.node.id.name, fn); + return fn; + }, + // 函数表达式节点处理器 + FunctionExpression(nodeIterator) { + const node = nodeIterator.node; + /** + * 1、定义函数需要先为其定义一个函数作用域,且允许继承父级作用域 + * 2、注册`this`, `arguments`和形参到作用域的变量空间 + * 3、检查return关键字 + * 4、定义函数名和长度 + */ + const fn = function () { + // 创建函数作用域 + const scope = nodeIterator.createScope('function'); + scope.constDeclare('this', this); + scope.constDeclare('arguments', arguments); + node.params.forEach((param, index) => { + const name = param.name; + scope.varDeclare(name, arguments[index]); + }); + const signal = nodeIterator.traverse(node.body, { scope }); + if (Signal.isReturn(signal)) { + return signal.value; + } + }; + Object.defineProperties(fn, { + name: { value: node.id ? node.id.name : '' }, + length: { value: node.params.length } + }); + return fn; + }, + // this表达式处理器 + // 直接使用JS语言自身的特性,把this关键字从作用域中取出即可。 + ThisExpression(nodeIterator) { + const value = nodeIterator.scope.get('this'); + return value ? value.value : null; + }, + // 沿用JS的语言特性,获取函数和参数之后,通过bind关键字生成一个构造函数,并返回。 + NewExpression(nodeIterator) { + const func = nodeIterator.traverse(nodeIterator.node.callee); + const args = nodeIterator.node.arguments.map(arg => nodeIterator.traverse(arg)); + return new (func.bind(null, ...args)); + }, + UpdateExpression(nodeIterator) { + const { operator, prefix } = nodeIterator.node; + const { name } = nodeIterator.node.argument; + let val = nodeIterator.scope.get(name).value; + operator === "++" ? nodeIterator.scope.set(name, val + 1) : nodeIterator.scope.set(name, val - 1); + if (operator === "++" && prefix) { + return ++val; + } + else if (operator === "++" && !prefix) { + return val++; + } + else if (operator === "--" && prefix) { + return --val; + } + else { + return val--; + } + }, + AssignmentExpressionOperatortraverseMap: { + '=': (value, v) => value instanceof MemberValue ? value.obj[value.prop] = v : value.value = v, + '+=': (value, v) => value instanceof MemberValue ? value.obj[value.prop] += v : value.value += v, + '-=': (value, v) => value instanceof MemberValue ? value.obj[value.prop] -= v : value.value -= v, + '*=': (value, v) => value instanceof MemberValue ? value.obj[value.prop] *= v : value.value *= v, + '/=': (value, v) => value instanceof MemberValue ? value.obj[value.prop] /= v : value.value /= v, + '%=': (value, v) => value instanceof MemberValue ? value.obj[value.prop] %= v : value.value %= v, + '**=': () => { throw new Error('canjs: es5 doen\'t supports operator "**='); }, + '<<=': (value, v) => value instanceof MemberValue ? value.obj[value.prop] <<= v : value.value <<= v, + '>>=': (value, v) => value instanceof MemberValue ? value.obj[value.prop] >>= v : value.value >>= v, + '>>>=': (value, v) => value instanceof MemberValue ? value.obj[value.prop] >>>= v : value.value >>>= v, + '|=': (value, v) => value instanceof MemberValue ? value.obj[value.prop] |= v : value.value |= v, + '^=': (value, v) => value instanceof MemberValue ? value.obj[value.prop] ^= v : value.value ^= v, + '&=': (value, v) => value instanceof MemberValue ? value.obj[value.prop] &= v : value.value &= v + }, + AssignmentExpression(nodeIterator) { + const node = nodeIterator.node; + const value = getIdentifierOrMemberExpressionValue(node.left, nodeIterator); + return NodeHandler.AssignmentExpressionOperatortraverseMap[node.operator](value, nodeIterator.traverse(node.right)); + }, + UnaryExpressionOperatortraverseMap: { + '-': (nodeIterator) => -nodeIterator.traverse(nodeIterator.node.argument), + '+': (nodeIterator) => +nodeIterator.traverse(nodeIterator.node.argument), + '!': (nodeIterator) => !nodeIterator.traverse(nodeIterator.node.argument), + '~': (nodeIterator) => ~nodeIterator.traverse(nodeIterator.node.argument), + 'typeof': (nodeIterator) => { + if (nodeIterator.node.argument.type === 'Identifier') { + try { + const value = nodeIterator.scope.get(nodeIterator.node.argument.name); + return value ? typeof value.value : 'undefined'; + } + catch (err) { + if (err.message === `${nodeIterator.node.argument.name} is not defined`) { + return 'undefined'; + } + else { + throw err; + } + } + } + else { + return typeof nodeIterator.traverse(nodeIterator.node.argument); + } + }, + 'void': (nodeIterator) => void nodeIterator.traverse(nodeIterator.node.argument), + 'delete': (nodeIterator) => { + const argument = nodeIterator.node.argument; + if (argument.type === 'MemberExpression') { + const obj = nodeIterator.traverse(argument.object); + const name = getPropertyName(argument, nodeIterator); + return delete obj[name]; + } + else if (argument.type === 'Identifier') { + return false; + } + else if (argument.type === 'Literal') { + return true; + } + } + }, + UnaryExpression(nodeIterator) { + return NodeHandler.UnaryExpressionOperatortraverseMap[nodeIterator.node.operator](nodeIterator); + }, + BinaryExpressionOperatortraverseMap: { + '==': (a, b) => a == b, + '!=': (a, b) => a != b, + '===': (a, b) => a === b, + '!==': (a, b) => a !== b, + '<': (a, b) => a < b, + '<=': (a, b) => a <= b, + '>': (a, b) => a > b, + '>=': (a, b) => a >= b, + '<<': (a, b) => a << b, + '>>': (a, b) => a >> b, + '>>>': (a, b) => a >>> b, + '+': (a, b) => a + b, + '-': (a, b) => a - b, + '*': (a, b) => a * b, + '/': (a, b) => a / b, + '%': (a, b) => a % b, + '**': (a, b) => { throw new Error('canjs: es5 doesn\'t supports operator "**"'); }, + '|': (a, b) => a | b, + '^': (a, b) => a ^ b, + '&': (a, b) => a & b, + 'in': (a, b) => a in b, + 'instanceof': (a, b) => a instanceof b + }, + BinaryExpression(nodeIterator) { + const a = nodeIterator.traverse(nodeIterator.node.left); + const b = nodeIterator.traverse(nodeIterator.node.right); + return NodeHandler.BinaryExpressionOperatortraverseMap[nodeIterator.node.operator](a, b); + }, + LogicalExpressionOperatortraverseMap: { + '||': (a, b) => a || b, + '&&': (a, b) => a && b + }, + LogicalExpression(nodeIterator) { + const a = nodeIterator.traverse(nodeIterator.node.left); + if (a) { + if (nodeIterator.node.operator == '||') { + return true; + } + } + else if (nodeIterator.node.operator == '&&') { + return false; + } + const b = nodeIterator.traverse(nodeIterator.node.right); + return NodeHandler.LogicalExpressionOperatortraverseMap[nodeIterator.node.operator](a, b); + }, + // For循环节点处理器 + /* + For循环的三个参数对应着节点的init,test,update属性, + 对着三个属性分别调用节点处理器处理, + 并放回JS原生的for循环当中即可。 + */ + ForStatement(nodeIterator) { + const node = nodeIterator.node; + let scope = nodeIterator.scope; + if (node.init && node.init.type === 'VariableDeclaration' && node.init.kind !== 'var') { + scope = nodeIterator.createScope('block'); + } + for (node.init && nodeIterator.traverse(node.init, { scope }); node.test ? nodeIterator.traverse(node.test, { scope }) : true; node.update && nodeIterator.traverse(node.update, { scope })) { + const signal = nodeIterator.traverse(node.body, { scope }); + if (Signal.isBreak(signal)) { + break; + } + else if (Signal.isContinue(signal)) { + continue; + } + else if (Signal.isReturn(signal)) { + return signal; + } + } + }, + ForInStatement(nodeIterator) { + const { left, right, body } = nodeIterator.node; + let scope = nodeIterator.scope; + let value; + if (left.type === 'VariableDeclaration') { + const id = left.declarations[0].id; + value = scope.declare(id.name, undefined, left.kind); + } + else if (left.type === 'Identifier') { + value = scope.get(left.name, true); + } + else { + throw new Error(`canjs: [ForInStatement] Unsupported left type "${left.type}"`); + } + for (const key in nodeIterator.traverse(right)) { + value.value = key; + const signal = nodeIterator.traverse(body, { scope }); + if (Signal.isBreak(signal)) { + break; + } + else if (Signal.isContinue(signal)) { + continue; + } + else if (Signal.isReturn(signal)) { + return signal; + } + } + }, + WhileStatement(nodeIterator) { + while (nodeIterator.traverse(nodeIterator.node.test)) { + const signal = nodeIterator.traverse(nodeIterator.node.body); + if (Signal.isBreak(signal)) { + break; + } + else if (Signal.isContinue(signal)) { + continue; + } + else if (Signal.isReturn(signal)) { + return signal; + } + } + }, + DoWhileStatement(nodeIterator) { + do { + const signal = nodeIterator.traverse(nodeIterator.node.body); + if (Signal.isBreak(signal)) { + break; + } + else if (Signal.isContinue(signal)) { + continue; + } + else if (Signal.isReturn(signal)) { + return signal; + } + } while (nodeIterator.traverse(nodeIterator.node.test)); + }, + ReturnStatement(nodeIterator) { + let value; + if (nodeIterator.node.argument) { + value = nodeIterator.traverse(nodeIterator.node.argument); + } + return Signal.Return(value); + }, + BreakStatement(nodeIterator) { + let label; + if (nodeIterator.node.label) { + label = nodeIterator.node.label.name; + } + return Signal.Break(label); + }, + ContinueStatement(nodeIterator) { + let label; + if (nodeIterator.node.label) { + label = nodeIterator.node.label.name; + } + return Signal.Continue(label); + }, + // If声明节点处理器 + IfStatement(nodeIterator) { + if (nodeIterator.traverse(nodeIterator.node.test)) { + return nodeIterator.traverse(nodeIterator.node.consequent); + } + else if (nodeIterator.node.alternate) { + return nodeIterator.traverse(nodeIterator.node.alternate); + } + }, + SwitchStatement(nodeIterator) { + const discriminant = nodeIterator.traverse(nodeIterator.node.discriminant); + for (const theCase of nodeIterator.node.cases) { + if (!theCase.test || discriminant === nodeIterator.traverse(theCase.test)) { + const signal = nodeIterator.traverse(theCase); + if (Signal.isBreak(signal)) { + break; + } + else if (Signal.isContinue(signal)) { + continue; + } + else if (Signal.isReturn(signal)) { + return signal; + } + } + } + }, + SwitchCase(nodeIterator) { + for (const node of nodeIterator.node.consequent) { + const signal = nodeIterator.traverse(node); + if (Signal.isSignal(signal)) { + return signal; + } + } + }, + ConditionalExpression(nodeIterator) { + return nodeIterator.traverse(nodeIterator.node.test) + ? nodeIterator.traverse(nodeIterator.node.consequent) + : nodeIterator.traverse(nodeIterator.node.alternate); + }, + ThrowStatement(nodeIterator) { + throw nodeIterator.traverse(nodeIterator.node.argument); + }, + TryStatement(nodeIterator) { + const { block, handler, finalizer } = nodeIterator.node; + try { + return nodeIterator.traverse(block); + } + catch (err) { + if (handler) { + const param = handler.param; + const scope = nodeIterator.createScope('block'); + scope.letDeclare(param.name, err); + return nodeIterator.traverse(handler, { scope }); + } + throw err; + } + finally { + if (finalizer) { + return nodeIterator.traverse(finalizer); + } + } + }, + CatchClause(nodeIterator) { + return nodeIterator.traverse(nodeIterator.node.body); + } +}; +function getPropertyName(node, nodeIterator) { + if (node.computed) { + return nodeIterator.traverse(node.property); + } + else { + return node.property.name; + } +} +function getIdentifierOrMemberExpressionValue(node, nodeIterator) { + if (node.type === 'Identifier') { + return nodeIterator.scope.get(node.name); + } + else if (node.type === 'MemberExpression') { + const obj = nodeIterator.traverse(node.object); + const name = getPropertyName(node, nodeIterator); + return new MemberValue(obj, name); + } + else { + throw new Error(`canjs: Not support to get value of node type "${node.type}"`); + } +} +module.exports = NodeHandler; +//# sourceMappingURL=es5.js.map \ No newline at end of file diff --git a/dist/vm/es_versions/index.js b/dist/vm/es_versions/index.js new file mode 100644 index 0000000..0fd8a28 --- /dev/null +++ b/dist/vm/es_versions/index.js @@ -0,0 +1,6 @@ +"use strict"; +const es5 = require('./es5'); +module.exports = { + ...es5 +}; +//# sourceMappingURL=index.js.map \ No newline at end of file diff --git a/dist/vm/index.js b/dist/vm/index.js new file mode 100644 index 0000000..89b5ba8 --- /dev/null +++ b/dist/vm/index.js @@ -0,0 +1,38 @@ +"use strict"; +/** + * @author Jrainlau + * @desc Canjs类 + * + * @class + * + * 传入字符串形式的es5代码,可选的新增全局变量 + * 运行`.run()`方法即可输出运行结果 + * + * eg: new Canjs('console.log("Hello World!")').run() + */ +// const {Parser} = require('acorn') +const NodeIterator = require('./iterator'); +const Scope = require('./scope'); +class Canjs { + constructor(ast, code = '', extraDeclaration = {}) { + this.code = code; + this.extraDeclaration = extraDeclaration; + // this.ast = Parser.parse(code) + this.ast = ast; + this.nodeIterator = null; + this.init(); + } + init() { + const globalScope = new Scope('function'); + // 根据入参定义标准库之外的全局变量 + Object.keys(this.extraDeclaration).forEach((key) => { + globalScope.addDeclaration(key, this.extraDeclaration[key]); + }); + this.nodeIterator = new NodeIterator(null, globalScope); + } + run() { + return this.nodeIterator.traverse(this.ast); + } +} +module.exports = Canjs; +//# sourceMappingURL=index.js.map \ No newline at end of file diff --git a/dist/vm/iterator.js b/dist/vm/iterator.js new file mode 100644 index 0000000..ed19142 --- /dev/null +++ b/dist/vm/iterator.js @@ -0,0 +1,60 @@ +"use strict"; +/** + * @author Jrainlau + * @desc 节点遍历器,递归遍历AST内的每一个节点并调用对应的方法进行解析 + * + * @class + * + * 针对AST节点进行解析,根据节点类型调用“节点处理器”(nodeHandler)对应的方法。 + * 在进行解析的时候,会传入节点和节点对应的作用域。 + * + * 另外也提供了创建作用域的方法(createScope),可用于创建函数作用域或者块级作用域。 + */ +// 节点处理器 +const nodeHandler = require('./es_versions'); +const Scope = require('./scope'); +// 节点遍历器 +class NodeIterator { + constructor(node, scope) { + this.node = node; + this.scope = scope; + this.nodeHandler = nodeHandler; + } + traverse(node, options = {}) { + // 作用域 + // 作用域的处理,可以说是整个JS解释器最难的部分 + /* + // eg: + const a = 1 + { + const b = 2 + console.log(a) + } + console.log(b) + */ + /* + 运行结果必然是能够打印出a的值, + 然后报错:Uncaught ReferenceError: b is not defined + */ + /* + 块级作用域或者函数作用域可以读取其父级作用域当中的变量, + 反之则不行,所以对于作用域我们不能简单地定义一个空对象, + 而是要专门进行处理。 + */ + const scope = options.scope || this.scope; + const nodeIterator = new NodeIterator(node, scope); + // 根据节点类型找到节点处理器当中对应的函数 + const _eval = this.nodeHandler[node.type]; + // 若找不到则报错 + if (!_eval) { + throw new Error(`canjs: Unknown node type "${node.type}".`); + } + // 运行处理函数 + return _eval(nodeIterator); + } + createScope(blockType = 'block') { + return new Scope(blockType, this.scope); + } +} +module.exports = NodeIterator; +//# sourceMappingURL=iterator.js.map \ No newline at end of file diff --git a/dist/vm/scope.js b/dist/vm/scope.js new file mode 100644 index 0000000..057ec26 --- /dev/null +++ b/dist/vm/scope.js @@ -0,0 +1,113 @@ +"use strict"; +/** + * @author Jrainlau + * @desc 管理作用域 + * + * @class + * + * 每次对节点的处理,都要考虑其作用域的问题。Scope实例会定义该作用域为函数作用域(function)或者块级作用域(block)。 + * 每次新建Scope实例,都会为当前节点创建一个全新的“作用域变量空间”(declaration),任何在此作用域内定义的变量都会存放在这个空间当中 + * 此外,新建Scope实例也会保存其父级作用域。 + */ +// standardMap对象提供es5所支持的内置对象/方法 JS标准库。 +/* +JS标准库就是JS这门语言本身所带有的一系列方法和属性, +如常用的setTimeout,console.log等等。 +为了让解析器也能够执行这些方法,所以我们需要为其注入标准库: +*/ +const standardMap = require('./standard'); +const { SimpleValue } = require('./value'); +// 作用域 +class Scope { + constructor(type, parentScope) { + // 作用域类型,区分函数作用域function和块级作用域block + this.type = type; + // 父级作用域 + this.parentScope = parentScope; + // 全局作用域 + this.globalDeclaration = standardMap; + // 当前作用域的变量空间 + this.declaration = Object.create(null); // 每次都新建一个全新的作用域 + } + addDeclaration(name, value) { + this.globalDeclaration[name] = new SimpleValue(value); + } + /* + get/set方法用于获取/设置当前作用域中对应name的变量值 + 符合JS语法规则,优先从当前作用域去找,若找不到则到父级作用域去找,然后到全局作用域找。 + 如果都没有,就报错 + */ + get(name) { + if (this.declaration[name]) { + return this.declaration[name]; + } + else if (this.parentScope) { + return this.parentScope.get(name); + } + else if (this.globalDeclaration[name]) { + return this.globalDeclaration[name]; + } + throw new ReferenceError(`${name} is not defined`); + } + set(name, value) { + if (this.declaration[name]) { + this.declaration[name].set(value); + } + else if (this.parentScope) { + this.parentScope.set(name, value); + } + else if (this.globalDeclaration[name]) { + return this.globalDeclaration.set(name, value); + } + else { + throw new ReferenceError(`${name} is not defined`); + } + } + /** + 根据变量的kind调用不同的变量定义方法 + */ + declare(name, value, kind = 'var') { + if (kind === 'var') { + return this.varDeclare(name, value); + } + else if (kind === 'let') { + return this.letDeclare(name, value); + } + else if (kind === 'const') { + return this.constDeclare(name, value); + } + else { + throw new Error(`canjs: Invalid Variable Declaration Kind of "${kind}"`); + } + } + varDeclare(name, value) { + let scope = this; + // example + // ? + // 如果遇到块级作用域且关键字为var,则需要把这个变量也定义到父级作用域当中,这也就是我们常说的“全局变量污染”。 + // 若当前作用域存在非函数类型的父级作用域时,就把变量定义到父级作用域 + while (scope.parentScope && scope.type !== 'function') { + scope = scope.parentScope; + } + scope.declaration[name] = new SimpleValue(value, 'var'); + return scope.declaration[name]; + } + letDeclare(name, value) { + // 不允许重复定义 + if (this.declaration[name]) { + throw new SyntaxError(`Identifier ${name} has already been declared`); + } + this.declaration[name] = new SimpleValue(value, 'let'); + return this.declaration[name]; + } + constDeclare(name, value) { + // 不允许重复定义 + if (this.declaration[name]) { + throw new SyntaxError(`Identifier ${name} has already been declared`); + } + this.declaration[name] = new SimpleValue(value, 'const'); + return this.declaration[name]; + } +} +module.exports = Scope; +//# sourceMappingURL=scope.js.map \ No newline at end of file diff --git a/dist/vm/signal.js b/dist/vm/signal.js new file mode 100644 index 0000000..8cccfb2 --- /dev/null +++ b/dist/vm/signal.js @@ -0,0 +1,36 @@ +"use strict"; +/** + * @author: Jrainlau + * @desc: 判断、记录关键标记语句(return, break, continue) + * + * @class + */ +class Signal { + constructor(type, value) { + this.type = type; + this.value = value; + } + static Return(value) { + return new Signal('return', value); + } + static Break(label = null) { + return new Signal('break', label); + } + static Continue(label) { + return new Signal('continue', label); + } + static isReturn(signal) { + return signal instanceof Signal && signal.type === 'return'; + } + static isContinue(signal) { + return signal instanceof Signal && signal.type === 'continue'; + } + static isBreak(signal) { + return signal instanceof Signal && signal.type === 'break'; + } + static isSignal(signal) { + return signal instanceof Signal; + } +} +module.exports = Signal; +//# sourceMappingURL=signal.js.map \ No newline at end of file diff --git a/dist/vm/standard.js b/dist/vm/standard.js new file mode 100644 index 0000000..5418772 --- /dev/null +++ b/dist/vm/standard.js @@ -0,0 +1,72 @@ +"use strict"; +/** + * @author: JrainLau + * @desc: es5标准库,提供es5所支持的内置对象/方法 + */ +const { SimpleValue } = require('./value'); +let windowObj = null; +let globalObj = null; +try { + windowObj = window; +} +catch (e) { } +try { + globalObj = global; +} +catch (e) { } +// 提供es5所支持的内置对象/方法 +const standardMap = { + // Function properties + isFinite: new SimpleValue(isFinite), + isNaN: new SimpleValue(isNaN), + parseFloat: new SimpleValue(parseFloat), + parseInt: new SimpleValue(parseInt), + decodeURI: new SimpleValue(decodeURI), + decodeURIComponent: new SimpleValue(decodeURIComponent), + encodeURI: new SimpleValue(encodeURI), + encodeURIComponent: new SimpleValue(encodeURIComponent), + // Fundamental objects + Object: new SimpleValue(Object), + Function: new SimpleValue(Function), + Boolean: new SimpleValue(Boolean), + Symbol: new SimpleValue(Symbol), + Error: new SimpleValue(Error), + EvalError: new SimpleValue(EvalError), + RangeError: new SimpleValue(RangeError), + ReferenceError: new SimpleValue(ReferenceError), + SyntaxError: new SimpleValue(SyntaxError), + TypeError: new SimpleValue(TypeError), + URIError: new SimpleValue(URIError), + // Numbers and dates + Number: new SimpleValue(Number), + Math: new SimpleValue(Math), + Date: new SimpleValue(Date), + // Text processing + String: new SimpleValue(String), + RegExp: new SimpleValue(RegExp), + // Indexed collections + Array: new SimpleValue(Array), + Int8Array: new SimpleValue(Int8Array), + Uint8Array: new SimpleValue(Uint8Array), + Uint8ClampedArray: new SimpleValue(Uint8ClampedArray), + Int16Array: new SimpleValue(Int16Array), + Uint16Array: new SimpleValue(Uint16Array), + Int32Array: new SimpleValue(Int32Array), + Uint32Array: new SimpleValue(Uint32Array), + Float32Array: new SimpleValue(Float32Array), + Float64Array: new SimpleValue(Float64Array), + // Structured data + ArrayBuffer: new SimpleValue(ArrayBuffer), + DataView: new SimpleValue(DataView), + JSON: new SimpleValue(JSON), + // // Other + window: new SimpleValue(windowObj), + global: new SimpleValue(globalObj), + console: new SimpleValue(console), + setTimeout: new SimpleValue(setTimeout), + clearTimeout: new SimpleValue(clearTimeout), + setInterval: new SimpleValue(setInterval), + clearInterval: new SimpleValue(clearInterval) +}; +module.exports = standardMap; +//# sourceMappingURL=standard.js.map \ No newline at end of file diff --git a/dist/vm/value.js b/dist/vm/value.js new file mode 100644 index 0000000..68e820a --- /dev/null +++ b/dist/vm/value.js @@ -0,0 +1,56 @@ +"use strict"; +/** + * @author Jrainlau + * @desc 和变量保存相关 + */ +/** + * 创建一个普通变量值 + * + * @class + * @param any value 值 + * @param string kind 变量定义符(var, let, const) + * @method set 设置值 + * @method get 获取值 + */ +class SimpleValue { + constructor(value, kind = '') { + this.value = value; + this.kind = kind; + } + set(value) { + // 禁止重新对const类型变量赋值 + if (this.kind === 'const') { + throw new TypeError('Assignment to constant variable'); + } + else { + this.value = value; + } + } + get() { + return this.value; + } +} +/** + * 创建一个类变量 + * + * @class + * @param any obj 类 + * @param prop any 属性 + * @method set 设置类的属性的值 + * @method get 获取类的属性的值 + */ +class MemberValue { + constructor(obj, prop) { + this.obj = obj; + this.prop = prop; + } + set(value) { + this.obj[this.prop] = value; + } + get() { + return this.obj[this.prop]; + } +} +module.exports.SimpleValue = SimpleValue; +module.exports.MemberValue = MemberValue; +//# sourceMappingURL=value.js.map \ No newline at end of file diff --git a/examples/pineapple/main.js b/examples/pineapple/main.js deleted file mode 100644 index c9237e1..0000000 --- a/examples/pineapple/main.js +++ /dev/null @@ -1,32 +0,0 @@ -"use strict"; -var __importDefault = (this && this.__importDefault) || function (mod) { - return (mod && mod.__esModule) ? mod : { "default": mod }; -}; -Object.defineProperty(exports, "__esModule", { value: true }); -const backend_1 = require("../../src/backend"); -const fs_1 = __importDefault(require("fs")); -// const args = process.argv.slice(2) -// if (args[0]) { -// let code = '' -// try { -// code = fs.readFileSync(args[0], { encoding: 'utf-8' }) -// } catch (error) { -// console.log(`Error reading file: ${args[0]}`) -// } -// if (code.length > 0) { -// Execute(code) -// } -// } -const path_1 = __importDefault(require("path")); -let code = ''; -try { - code = fs_1.default.readFileSync(path_1.default.resolve(__dirname, './hello-world1.pineapple'), { encoding: 'utf-8' }); - console.log(code, 'code'); -} -catch (error) { - console.log(`${error}`); -} -if (code.length > 0) { - backend_1.Execute(code); -} -//# sourceMappingURL=main.js.map \ No newline at end of file diff --git a/examples/pineapple/main.ts b/examples/pineapple/main.ts index 2b5ae75..946248c 100644 --- a/examples/pineapple/main.ts +++ b/examples/pineapple/main.ts @@ -1,31 +1,16 @@ import { Execute } from '../../src/backend' import fs from "fs"; -// const args = process.argv.slice(2) -// if (args[0]) { -// let code = '' +const args = process.argv.slice(2) +if (args[0]) { + let code = '' -// try { -// code = fs.readFileSync(args[0], { encoding: 'utf-8' }) -// } catch (error) { -// console.log(`Error reading file: ${args[0]}`) -// } + try { + code = fs.readFileSync(args[0], { encoding: 'utf-8' }) + } catch (error) { + console.log(`Error reading file: ${args[0]}`) + } -// if (code.length > 0) { -// Execute(code) -// } -// } - - -import path from 'path' -let code = '' - -try { - code = fs.readFileSync(path.resolve(__dirname,'./hello-world1.pineapple'), { encoding: 'utf-8' }) - console.log(code, 'code') -} catch (error) { - console.log(`${error}`) -} - -if (code.length > 0) { - Execute(code) + if (code.length > 0) { + Execute(code) + } } diff --git a/package.json b/package.json index 7346023..dc6f0a9 100644 --- a/package.json +++ b/package.json @@ -5,6 +5,7 @@ "typescript": "^4.1.3" }, "scripts": { - "test": "ts-node ./examples/pineapple/main.ts ./examples/pineapple/hello-world.pineapple" + "test": "ts-node ./examples/pineapple/main.ts ./examples/pineapple/hello-world.pineapple", + "build": "rm -rf ./dist/ && tsc" } } \ No newline at end of file diff --git a/src/backend.ts b/src/backend.ts index cb079d6..9fb2376 100644 --- a/src/backend.ts +++ b/src/backend.ts @@ -3,6 +3,7 @@ import { GlobalVariables } from './definition' import { Assignment } from "./parser/Assignment" import { Print } from "./parser/Print" import { Comment } from "./parser/Comment" +import Canjs from '../vm/index.js' let GlobalVariables: GlobalVariables = { Variables: {} @@ -17,18 +18,26 @@ function NewGlobalVariables() { export function Execute(code: string) { - var ast = {} + var ast: any = {} let g = NewGlobalVariables() // parse ast = parse(code) + for (let item in ast.body) { + if (ast.body[item].type === "COMMENT") { // 如果是注释,删除 + ast.body.splice(item, 1) + } + } + console.log(JSON.stringify(ast, null, 4), '\r\rAST') console.log("--------------------------------------------") // resolve - resolveAST(g, ast) + const vm = new Canjs(ast); + vm.run() + // resolveAST(g, ast) } function resolveAST(g: any, ast: any) { @@ -61,13 +70,18 @@ function resolveAssignment(g: any, assignment: any) { if (varName == "") { throw new Error("resolveAssignment(): variable name can NOT be empty.") } - if (assignment.String !== null && assignment.String !== undefined) { - g.Variables[varName] = assignment.String - } else if (assignment.Number !== null && assignment.Number !== undefined) { - g.Variables[varName] = assignment.Number + if (assignment.Literal) { + g.Variables[varName] = assignment.Literal.value } else { throw new Error("Ivalie value."); } + // if (assignment.String !== null && assignment.String !== undefined) { + // g.Variables[varName] = assignment.String + // } else if (assignment.Number !== null && assignment.Number !== undefined) { + // g.Variables[varName] = assignment.Number + // } else { + // throw new Error("Ivalie value."); + // } return null } diff --git a/src/definition.ts b/src/definition.ts index acccd88..71ccc2a 100644 --- a/src/definition.ts +++ b/src/definition.ts @@ -1,3 +1,6 @@ +import { Assignment } from "./parser/Assignment"; +import { Print } from "./parser/Print"; + export interface GlobalVariables { Variables: { [index: string]: string @@ -14,7 +17,10 @@ export interface TokenNameMap { [index: number]: any } -export interface Variable { - LineNum?: number, - Name?: string, -} +export type Variable = Print | Assignment | Comment | undefined + +// export interface Variable { +// LineNum?: number, +// Name?: string, +// [x: string]: any +// } diff --git a/src/lexer1.ts b/src/lexer1.ts index d17f200..f70ef5b 100644 --- a/src/lexer1.ts +++ b/src/lexer1.ts @@ -8,11 +8,11 @@ Integer ::= [0-9]+ Number ::= Integer Ignored String ::= '"' '"' Ignored | '"' StringCharacter '"' Ignored Variable ::= "$" Name Ignored // 变量 -Assignment ::= Variable Ignored '=' Ignored ( String | Number ) Ignored +Assignment ::= Variable Ignored '=' Ignored ( String | Number | Variable) Ignored Print ::= "print" "(" Ignored Variable Ignored ")" Ignored Statement ::= Print | Assignment SourceCode ::= Statement+ -Comment ::= Ignored "#" SourceCharacter Ignored // 注释 +Comment ::= Ignored "#" SourceCharacter // 注释 */ @@ -96,7 +96,40 @@ export class Lexer { this.nextTokenType = nextTokenType; this.hasCache = false; } + /** + * LookAhead (向前看) 一个 Token, 告诉我们下一个 Token 是什么 + * @returns + */ + LookAhead(): { lineNum: number, tokenType: number, token: string } { + // lexer.nextToken already setted + if (this.hasCache) { + return { tokenType: this.nextTokenType, lineNum: this.lineNum, token: this.nextToken } + // return this.nextTokenType + } + // set it + // 当前行 + let { lineNum, tokenType, token } = this.GetNextToken() + // * + // 下一行 + this.hasCache = true + this.lineNum = lineNum + this.nextTokenType = tokenType + this.nextToken = token + return { tokenType, lineNum, token } + } + LookAheadAndSkip(expectedType: number) { + // get next token + // 查看看下一个Token信息 + let { lineNum, tokenType, token } = this.GetNextToken() + // not is expected type, reverse cursor + if (tokenType != expectedType) { + this.hasCache = true + this.lineNum = lineNum + this.nextTokenType = tokenType + this.nextToken = token + } + } /** * 断言下一个 Token 是什么 */ @@ -128,8 +161,22 @@ export class Lexer { } return this.MatchToken() } + checkCode(c: string) { + // 确保源代码,不包含非法字符,对应着SourceCharacter的EBNF + if (!/\u0009|\u000A|\u000D|[\u0020-\uFFFF]/.test(this.sourceCode[0])) { + throw new Error('The source code contains characters that cannot be parsed.') + } + } + // 直接跳过几个字符,返回被跳过的字符 + next(skip: number) { + this.checkCode(this.sourceCode[0]) + const code = this.sourceCode[0] + this.skipSourceCode(skip) + return code + } // 匹配Token并跳过匹配的Token MatchToken(): { lineNum: number, tokenType: number, token: string } { + this.checkCode(this.sourceCode[0]) // 只做检查,不吃字符 // console.log(this.sourceCode[0], '当前Token') // check ignored if (this.isIgnored()) { @@ -257,40 +304,6 @@ export class Lexer { GetLineNum(): number { return this.lineNum } - /** - * LookAhead (向前看) 一个 Token, 告诉我们下一个 Token 是什么 - * @returns - */ - LookAhead(): { lineNum: number, tokenType: number, token: string } { - // lexer.nextToken already setted - if (this.hasCache) { - return { tokenType: this.nextTokenType, lineNum: this.lineNum, token: this.nextToken } - // return this.nextTokenType - } - // set it - // 当前行 - let { lineNum, tokenType, token } = this.GetNextToken() - // * - // 下一行 - this.hasCache = true - this.lineNum = lineNum - this.nextTokenType = tokenType - this.nextToken = token - return { tokenType, lineNum, token } - } - - LookAheadAndSkip(expectedType: number) { - // get next token - // 查看看下一个Token信息 - let { lineNum, tokenType, token } = this.GetNextToken() - // not is expected type, reverse cursor - if (tokenType != expectedType) { - this.hasCache = true - this.lineNum = lineNum - this.nextTokenType = tokenType - this.nextToken = token - } - } // return content before token scanBeforeToken(token: string): string { // 以单个双引号,划分数组 diff --git a/src/parser.ts b/src/parser.ts index 47c9bad..c196369 100644 --- a/src/parser.ts +++ b/src/parser.ts @@ -1,8 +1,8 @@ import { Lexer, NewLexer, tokenNameMap, Tokens } from "./lexer1" import { Variable } from './definition' import { paseComment } from './parser/Comment' -import { parsePrint } from "./parser/Print" -import { parseAssignment } from "./parser/Assignment" +import { parsePrint, Print } from "./parser/Print" +import { Assignment, parseAssignment } from "./parser/Assignment" export const { TOKEN_EOF, // end-of-file TOKEN_VAR_PREFIX, // $ @@ -21,13 +21,31 @@ export const { TOKEN_EOF, // end-of-file SourceCharacter, // 所有代码字符串 } = Tokens +export class Program { + constructor(type?: string, body?: Array, LineNum?: number) { + this.type = 'Program' + this.body = body + this.LineNum = LineNum + } +} + +export interface Program { + type?: string, + body?: Array, + LineNum?: number +} + // SourceCode ::= Statement+ function parseSourceCode(lexer: Lexer) { + let program: Program = new Program() + let sourceCode: { LineNum?: number, Statements?: Array } = {} - sourceCode.LineNum = lexer.GetLineNum() - sourceCode.Statements = parseStatements(lexer) - return sourceCode + // sourceCode.LineNum = lexer.GetLineNum() + // sourceCode.Statements = parseStatements(lexer) + program.LineNum = lexer.GetLineNum() + program.body = parseStatements(lexer) + return program } // Statement ::= Print | Assignment @@ -36,7 +54,7 @@ function parseStatements(lexer: Lexer) { // 先调用LookAhead一次,将GetNextToken的结果缓存 while (!isSourceCodeEnd(lexer.LookAhead().tokenType)) { - let statement = {} + let statement: Print | Assignment | Comment | undefined = {} statement = parseStatement(lexer) statements.push(statement) } @@ -47,7 +65,7 @@ function parseStatement(lexer: Lexer) { // 向前看一个token并跳过 lexer.LookAheadAndSkip(TOKEN_IGNORED) // skip if source code start with ignored token let look = lexer.LookAhead().tokenType - // console.log(look, 'look') + console.log(look, 'look') switch (look) { case TOKEN_PRINT: return parsePrint(lexer) @@ -68,8 +86,8 @@ function isSourceCodeEnd(token: number): boolean { export function parseVariable(lexer: Lexer) { let variable: { LineNum?: number - Name?: string - } = {} + Name: string + } = { Name: '' } variable.LineNum = lexer.GetLineNum() lexer.NextTokenIs(TOKEN_VAR_PREFIX) @@ -96,12 +114,12 @@ export function parseNumber(lexer: Lexer) { while (lexer.isNumber(lexer.sourceCode[0])) { // console.log(lexer.sourceCode[0]) - str = lexer.sourceCode[0]; - lexer.skipSourceCode(1); + str = lexer.next(1) } if (!lexer.isIgnored()) { throw new Error('Uncaught SyntaxError: Invalid or unexpected token') } + lexer.NextTokenIs(NUMBER) lexer.LookAheadAndSkip(TOKEN_IGNORED) } diff --git a/src/parser/Assignment.js b/src/parser/Assignment.js deleted file mode 100644 index 4fa7d5d..0000000 --- a/src/parser/Assignment.js +++ /dev/null @@ -1,38 +0,0 @@ -"use strict"; -Object.defineProperty(exports, "__esModule", { value: true }); -exports.parseAssignment = exports.Assignment = void 0; -const lexer1_1 = require("../lexer1"); -const parser_1 = require("../parser"); -class Assignment { - constructor(LineNum, Variable, String, num, type) { - this.LineNum = LineNum; - this.Variable = Variable; - this.String = String; - this.Number = num; - this.Type = type; - } -} -exports.Assignment = Assignment; -// Assignment ::= Variable Ignored "=" Ignored String Ignored -function parseAssignment(lexer) { - let assignment = new Assignment(); - assignment.LineNum = lexer.GetLineNum(); - assignment.Variable = parser_1.parseVariable(lexer); - lexer.LookAheadAndSkip(parser_1.TOKEN_IGNORED); // 空格 - lexer.NextTokenIs(parser_1.TOKEN_EQUAL); // = - lexer.LookAheadAndSkip(parser_1.TOKEN_IGNORED); // 空格 - if (lexer.isNumber(lexer.sourceCode[0])) { - // console.log('parseNumber start') - assignment.Number = parser_1.parseNumber(lexer); - assignment.Type = lexer1_1.tokenNameMap[parser_1.NUMBER]; - // console.log('parseNumber end') - } - else { - assignment.String = parser_1.parseString(lexer); - assignment.Type = lexer1_1.tokenNameMap[parser_1.STRING]; - } - lexer.LookAheadAndSkip(parser_1.TOKEN_IGNORED); - return assignment; -} -exports.parseAssignment = parseAssignment; -//# sourceMappingURL=Assignment.js.map \ No newline at end of file diff --git a/src/parser/Assignment.ts b/src/parser/Assignment.ts index 2039acb..a2a71df 100644 --- a/src/parser/Assignment.ts +++ b/src/parser/Assignment.ts @@ -1,47 +1,83 @@ import { Variable } from "../definition"; import { Lexer, tokenNameMap, Tokens } from "../lexer1"; -import { NUMBER, parseNumber, parseString, parseVariable, STRING, TOKEN_EQUAL, TOKEN_IGNORED } from "../parser"; +import { NUMBER, parseNumber, parseString, parseVariable, STRING, TOKEN_EQUAL, TOKEN_IGNORED, TOKEN_VAR_PREFIX } from "../parser"; export interface Assignment { LineNum?: number, Variable?: Variable, String?: string, Number?: number, - Type?: string + type?: string, + Literal?: Literal, + kind?: string, +} + +export interface Literal { + type?: string, + value: string | number +} + +export class Literal { + constructor(value: string | number, type = 'Literal') { + this.type = type + this.value = value + } } export class Assignment { - constructor(LineNum?: number, Variable?: Variable, String?: string, num?: number, type?: string) { + constructor(LineNum?: number, Variable?: Variable, String?: string, num?: number, type?: string, Literal?: Literal, kind?: string) { this.LineNum = LineNum this.Variable = Variable this.String = String this.Number = num - this.Type = type + this.type = type + this.Literal = Literal + this.kind = 'let' } } // Assignment ::= Variable Ignored "=" Ignored String Ignored export function parseAssignment(lexer: Lexer) { - let assignment = new Assignment() + let assignment: any = new Assignment() assignment.LineNum = lexer.GetLineNum() - assignment.Variable = parseVariable(lexer) + assignment.declarations = [] + let VariableDeclarator: any = { type: "VariableDeclarator" } + VariableDeclarator.id = { type: "Identifier" } + VariableDeclarator.id.name = parseVariable(lexer).Name + // assignment.Variable = parseVariable(lexer) // 标识符 lexer.LookAheadAndSkip(TOKEN_IGNORED) // 空格 lexer.NextTokenIs(TOKEN_EQUAL) // = lexer.LookAheadAndSkip(TOKEN_IGNORED) // 空格 - if (lexer.isNumber(lexer.sourceCode[0])) { - // console.log('parseNumber start') - assignment.Number = parseNumber(lexer) - assignment.Type = tokenNameMap[NUMBER] - // console.log('parseNumber end') + console.log(lexer.LookAhead().tokenType, 'lexer.LookAhead().tokenType') + // 如果后面仍是$ + if (lexer.LookAhead().tokenType === TOKEN_VAR_PREFIX) { + const Variable = parseVariable(lexer) // 标识符 + console.log(Variable, 'Variable') + assignment.Variable = Variable + return assignment } else { - assignment.String = parseString(lexer) - assignment.Type = tokenNameMap[STRING] - } + if (lexer.isNumber(lexer.sourceCode[0])) { + // console.log('parseNumber start') + const literial = new Literal(parseNumber(lexer)) + VariableDeclarator.init = literial + // assignment.Literal = literial + // assignment.type = tokenNameMap[NUMBER] + assignment.type = "VariableDeclaration" + // console.log('parseNumber end') + } else { + const literial = new Literal(parseString(lexer)) + // assignment.Literal = literial + VariableDeclarator.init = literial + // assignment.type = tokenNameMap[STRING] + assignment.type = "VariableDeclaration" + } - lexer.LookAheadAndSkip(TOKEN_IGNORED) - return assignment + lexer.LookAheadAndSkip(TOKEN_IGNORED) + assignment.declarations.push(VariableDeclarator) // 一行只允许声明和初始化一个变量 + return assignment + } } \ No newline at end of file diff --git a/src/parser/Comment.ts b/src/parser/Comment.ts index 3867c20..be23d0e 100644 --- a/src/parser/Comment.ts +++ b/src/parser/Comment.ts @@ -1,17 +1,17 @@ -import { Lexer, Tokens } from "../lexer1" +import { Lexer, tokenNameMap, Tokens } from "../lexer1" import { COMMENT, SourceCharacter, TOKEN_IGNORED } from "../parser" export interface Comment { LineNum?: number, - Type?: string, + type?: string, Content?: string } export class Comment { - constructor(LineNum?: number, Content?: string, Type?: string) { + constructor(LineNum?: number, Content?: string, type?: string) { this.LineNum = LineNum this.Content = Content - this.Type = Type + this.type = type } } @@ -20,19 +20,17 @@ export function paseComment(lexer: Lexer) { console.log("paseComment start") comment.LineNum = lexer.GetLineNum() - lexer.LookAheadAndSkip(TOKEN_IGNORED) // 空格 lexer.NextTokenIs(COMMENT) - lexer.LookAheadAndSkip(TOKEN_IGNORED) // 空格 console.log(lexer.isNewLine(lexer.sourceCode[0]), 'isNewLine') let content = "" // 如果换行或者源码为空则停止解析注释 while (!lexer.isNewLine(lexer.sourceCode[0]) && !lexer.isEmpty()) { - content += lexer.sourceCode[0] - lexer.skipSourceCode(1) + content += lexer.next(1) } lexer.LookAheadAndSkip(TOKEN_IGNORED) comment.Content = content + comment.type = tokenNameMap[COMMENT] return comment } \ No newline at end of file diff --git a/src/parser/Print.js b/src/parser/Print.js deleted file mode 100644 index ad747ba..0000000 --- a/src/parser/Print.js +++ /dev/null @@ -1,29 +0,0 @@ -"use strict"; -Object.defineProperty(exports, "__esModule", { value: true }); -exports.parsePrint = exports.Print = void 0; -const lexer1_1 = require("../lexer1"); -const parser_1 = require("../parser"); -class Print { - constructor(LineNum, Variable, Type) { - this.LineNum = LineNum; - this.Variable = Variable; - this.Type = Type; - } -} -exports.Print = Print; -// Print ::= "print" "(" Ignored Variable Ignored ")" Ignored -function parsePrint(lexer) { - let print = new Print(); - print.LineNum = lexer.GetLineNum(); - lexer.NextTokenIs(parser_1.TOKEN_PRINT); - lexer.NextTokenIs(parser_1.TOKEN_LEFT_PAREN); - lexer.LookAheadAndSkip(parser_1.TOKEN_IGNORED); - print.Variable = parser_1.parseVariable(lexer); - print.Type = lexer1_1.tokenNameMap[parser_1.TOKEN_PRINT]; - lexer.LookAheadAndSkip(parser_1.TOKEN_IGNORED); - lexer.NextTokenIs(parser_1.TOKEN_RIGHT_PAREN); - lexer.LookAheadAndSkip(parser_1.TOKEN_IGNORED); - return print; -} -exports.parsePrint = parsePrint; -//# sourceMappingURL=Print.js.map \ No newline at end of file diff --git a/src/parser/Print.ts b/src/parser/Print.ts index ab441f9..c6b9ad8 100644 --- a/src/parser/Print.ts +++ b/src/parser/Print.ts @@ -4,15 +4,21 @@ import { parseVariable, TOKEN_IGNORED, TOKEN_LEFT_PAREN, TOKEN_PRINT, TOKEN_RIGH export interface Print { LineNum?: number, - Variable?: Variable, - Type?: string, + Variable?: { + LineNum?: number + Name: string + }, + type?: string, } export class Print { - constructor(LineNum?: number, Variable?: Variable, Type?: string) { + constructor(LineNum?: number, Variable?: { + LineNum?: number + Name: string + }, type?: string) { this.LineNum = LineNum this.Variable = Variable - this.Type = Type + this.type = type } } @@ -25,10 +31,38 @@ export function parsePrint(lexer: Lexer) { lexer.NextTokenIs(TOKEN_LEFT_PAREN) lexer.LookAheadAndSkip(TOKEN_IGNORED) print.Variable = parseVariable(lexer) - print.Type = tokenNameMap[TOKEN_PRINT] + print.type = tokenNameMap[TOKEN_PRINT] lexer.LookAheadAndSkip(TOKEN_IGNORED) lexer.NextTokenIs(TOKEN_RIGHT_PAREN) lexer.LookAheadAndSkip(TOKEN_IGNORED) - return print + const p = { + "type": "ExpressionStatement", + "expression": { + "type": "CallExpression", + "callee": { + "type": "MemberExpression", + "object": { + "type": "Identifier", + "name": "console" + }, + "property": { + "type": "Identifier", + "name": "log" + }, + "computed": false, + "optional": false + }, + "arguments": [ + { + "type": "Identifier", + "name": "a" + } + ], + "optional": false + } + } + // print 只打印,不计算 + p.expression.arguments[0].name = print.Variable.Name + return p } \ No newline at end of file diff --git a/test/test.js b/test/test.js new file mode 100644 index 0000000..2b794a9 --- /dev/null +++ b/test/test.js @@ -0,0 +1,9 @@ +const fs = require('fs') +const path = require('path') +const Execute = require('../dist/src/backend.js').Execute + +code = fs.readFileSync(path.resolve(__dirname, '../demo/hello-world-2.pineapple'), {encoding: 'utf-8'}) +console.log(code, 'code') +if (code.length > 0) { + Execute(code) +} diff --git a/tsconfig.json b/tsconfig.json index b2da2df..8c230fb 100644 --- a/tsconfig.json +++ b/tsconfig.json @@ -7,14 +7,14 @@ "target": "ESNEXT", /* Specify ECMAScript target version: 'ES3' (default), 'ES5', 'ES2015', 'ES2016', 'ES2017', 'ES2018', 'ES2019', 'ES2020', or 'ESNEXT'. */ "module": "commonjs", /* Specify module code generation: 'none', 'commonjs', 'amd', 'system', 'umd', 'es2015', 'es2020', or 'ESNext'. */ "lib": ["ESNext", "DOM"], /* Specify library files to be included in the compilation. */ - // "allowJs": true, /* Allow javascript files to be compiled. */ + "allowJs": true, /* Allow javascript files to be compiled. */ // "checkJs": true, /* Report errors in .js files. */ // "jsx": "preserve", /* Specify JSX code generation: 'preserve', 'react-native', or 'react'. */ // "declaration": true, /* Generates corresponding '.d.ts' file. */ // "declarationMap": true, /* Generates a sourcemap for each corresponding '.d.ts' file. */ "sourceMap": true, /* Generates corresponding '.map' file. */ // "outFile": "./", /* Concatenate and emit output to single file. */ - // "outDir": "./", /* Redirect output structure to the directory. */ + "outDir": "./dist", /* Redirect output structure to the directory. */ // "rootDir": "./", /* Specify the root directory of input files. Use to control the output directory structure with --outDir. */ // "composite": true, /* Enable project compilation */ // "tsBuildInfoFile": "./", /* Specify file to store incremental compilation information */ @@ -66,5 +66,8 @@ /* Advanced Options */ "skipLibCheck": true, /* Skip type checking of declaration files. */ "forceConsistentCasingInFileNames": true /* Disallow inconsistently-cased references to the same file. */ - } + }, + "exclude": [ + "vm" + ], } diff --git a/vm/es_versions/es5.js b/vm/es_versions/es5.js new file mode 100644 index 0000000..5d94701 --- /dev/null +++ b/vm/es_versions/es5.js @@ -0,0 +1,509 @@ +/** + * @author Jrainlau + * @desc 节点处理器,处理AST当中的节点 + */ + +// 关键字判断工具 +// 判断JS语句当中的return,break,continue关键字。 +const Signal = require('../signal') +const { MemberValue } = require('../value') +// 节点处理器 +const NodeHandler = { + Program (nodeIterator) { + for (const node of nodeIterator.node.body) { + nodeIterator.traverse(node) + } + }, + // 这里做作用域处理 + // 变量定义节点处理器 + VariableDeclaration (nodeIterator) { + const kind = nodeIterator.node.kind + // nodeIterator.node.declarations 是一个数组,里面存放着VariableDeclarator + for (const declaration of nodeIterator.node.declarations) { + const { name/* Identifier name */ } = declaration.id // 标识符Identifier + // declaration.init 可能是Literal或者CallExpression等类型,需要再次进行处理 + const value = declaration.init ? nodeIterator.traverse(declaration.init) : undefined + // 问题来了,拿到了变量的名称和值,然后把它保存到哪里去呢? + /* + 处理完变量声明节点以后,理应把这个变量保存起来。 + 按照JS语言特性,这个变量应该存放在一个作用域当中。 + 在JS解析器的实现过程中,这个作用域可以被定义为一个scope对象。 + */ + // 在作用域当中定义变量 + // 若为块级作用域且关键字为var,则需要做全局污染 + if (nodeIterator.scope.type === 'block' && kind === 'var') { + nodeIterator.scope.parentScope.declare(name, value, kind) + } else { + nodeIterator.scope.declare(name, value, kind) + } + } + }, + // 标识符节点处理器 + Identifier (nodeIterator) { + if (nodeIterator.node.name === 'undefined') { + return undefined + } + return nodeIterator.scope.get(nodeIterator.node.name).value + }, + // 字符节点处理器 + Literal (nodeIterator) { + return nodeIterator.node.value + }, + + ExpressionStatement (nodeIterator) { + return nodeIterator.traverse(nodeIterator.node.expression) + }, + // 表达式调用节点处理器 + // 用于处理表达式调用节点的处理器,如处理func(),console.log()等。 + CallExpression (nodeIterator) { + // 遍历callee获取函数体 + const func = nodeIterator.traverse(nodeIterator.node.callee) + // 获取参数 + const args = nodeIterator.node.arguments.map(arg => nodeIterator.traverse(arg)) + + let value + if (nodeIterator.node.callee.type === 'MemberExpression') { + value = nodeIterator.traverse(nodeIterator.node.callee.object) + } + // 返回函数运行结果 + return func.apply(value, args) + }, + // 表达式节点处理器 + // 表达式节点指的是person.say,console.log这种函数表达式。 + MemberExpression (nodeIterator) { + // 获取对象,如console + const obj = nodeIterator.traverse(nodeIterator.node.object) + // 获取对象的方法,如log + const name = nodeIterator.node.property.name + // 返回表达式,如console.log + return obj[name] + }, + ObjectExpression (nodeIterator) { + const obj = {} + for (const prop of nodeIterator.node.properties) { + let key + if (prop.key.type === 'Literal') { + key = `${prop.key.value}` + } else if (prop.key.type === 'Identifier') { + key = prop.key.name + } else { + throw new Error(`canjs: [ObjectExpression] Unsupported property key type "${prop.key.type}"`) + } + obj[key] = nodeIterator.traverse(prop.value) + } + return obj + }, + ArrayExpression (nodeIterator) { + return nodeIterator.node.elements.map(ele => nodeIterator.traverse(ele)) + }, + // 块级声明节点处理器 + // 非常常用的处理器,专门用于处理块级声明节点,如函数、循环、try...catch...当中的情景。 + BlockStatement (nodeIterator) { + // 先定义一个块级作用域 + let scope = nodeIterator.createScope('block') + + // 处理块级节点内的每一个节点 + for (const node of nodeIterator.node.body) { + if (node.type === 'FunctionDeclaration') { + nodeIterator.traverse(node, { scope }) + } else if (node.type === 'VariableDeclaration' && node.kind === 'var') { + /* + var a = 1,d = 3; 这种定义变量,declarations数组会有两个元素 + */ + for (const declaration of node.declarations) { + /* + var a = 1,d = 3,e; e的变量声明,没有赋值,那么AST中init就是null + */ + if (declaration.init) { + scope.declare(declaration.id.name, nodeIterator.traverse(declaration.init, { scope }), node.kind) + } else { + scope.declare(declaration.id.name, undefined, node.kind) + } + } + } + } + + // 提取关键字(return, break, continue) + for (const node of nodeIterator.node.body) { + if (node.type === 'FunctionDeclaration') { + continue + } + const signal = nodeIterator.traverse(node, { scope }) + if (Signal.isSignal(signal)) { + return signal + } + } + /* + 两个for...of循环。 + 第一个用于处理块级内语句, + 第二个专门用于识别关键字, + 如循环体内部的break,continue或者函数体内部的return。 + */ + }, + // 函数定义节点处理器 + // 向作用域当中声明一个和函数名相同的变量,值为所定义的函数 + FunctionDeclaration (nodeIterator) { + const fn = NodeHandler.FunctionExpression(nodeIterator) + nodeIterator.scope.varDeclare(nodeIterator.node.id.name, fn) + return fn + }, + // 函数表达式节点处理器 + FunctionExpression (nodeIterator) { + const node = nodeIterator.node + /** + * 1、定义函数需要先为其定义一个函数作用域,且允许继承父级作用域 + * 2、注册`this`, `arguments`和形参到作用域的变量空间 + * 3、检查return关键字 + * 4、定义函数名和长度 + */ + const fn = function () { + // 创建函数作用域 + const scope = nodeIterator.createScope('function') + scope.constDeclare('this', this) + scope.constDeclare('arguments', arguments) + + node.params.forEach((param, index) => { + const name = param.name + scope.varDeclare(name, arguments[index]) + }) + + const signal = nodeIterator.traverse(node.body, { scope }) + if (Signal.isReturn(signal)) { + return signal.value + } + } + + Object.defineProperties(fn, { + name: { value: node.id ? node.id.name : '' }, + length: { value: node.params.length } + }) + + return fn + }, + // this表达式处理器 + // 直接使用JS语言自身的特性,把this关键字从作用域中取出即可。 + ThisExpression (nodeIterator) { + const value = nodeIterator.scope.get('this') + return value ? value.value : null + }, + // 沿用JS的语言特性,获取函数和参数之后,通过bind关键字生成一个构造函数,并返回。 + NewExpression (nodeIterator) { + const func = nodeIterator.traverse(nodeIterator.node.callee) + const args = nodeIterator.node.arguments.map(arg => nodeIterator.traverse(arg)) + return new (func.bind(null, ...args)) + }, + + UpdateExpression (nodeIterator) { + const { operator, prefix } = nodeIterator.node + const { name } = nodeIterator.node.argument + let val = nodeIterator.scope.get(name).value + + operator === "++" ? nodeIterator.scope.set(name, val + 1) : nodeIterator.scope.set(name, val - 1) + + if (operator === "++" && prefix) { + return ++val + } else if (operator === "++" && !prefix) { + return val++ + } else if (operator === "--" && prefix) { + return --val + } else { + return val-- + } + }, + AssignmentExpressionOperatortraverseMap: { + '=': (value, v) => value instanceof MemberValue ? value.obj[value.prop] = v : value.value = v, + '+=': (value, v) => value instanceof MemberValue ? value.obj[value.prop] += v : value.value += v, + '-=': (value, v) => value instanceof MemberValue ? value.obj[value.prop] -= v : value.value -= v, + '*=': (value, v) => value instanceof MemberValue ? value.obj[value.prop] *= v : value.value *= v, + '/=': (value, v) => value instanceof MemberValue ? value.obj[value.prop] /= v : value.value /= v, + '%=': (value, v) => value instanceof MemberValue ? value.obj[value.prop] %= v : value.value %= v, + '**=': () => { throw new Error('canjs: es5 doen\'t supports operator "**=') }, + '<<=': (value, v) => value instanceof MemberValue ? value.obj[value.prop] <<= v : value.value <<= v, + '>>=': (value, v) => value instanceof MemberValue ? value.obj[value.prop] >>= v : value.value >>= v, + '>>>=': (value, v) => value instanceof MemberValue ? value.obj[value.prop] >>>= v : value.value >>>= v, + '|=': (value, v) => value instanceof MemberValue ? value.obj[value.prop] |= v : value.value |= v, + '^=': (value, v) => value instanceof MemberValue ? value.obj[value.prop] ^= v : value.value ^= v, + '&=': (value, v) => value instanceof MemberValue ? value.obj[value.prop] &= v : value.value &= v + }, + AssignmentExpression (nodeIterator) { + const node = nodeIterator.node + const value = getIdentifierOrMemberExpressionValue(node.left, nodeIterator) + return NodeHandler.AssignmentExpressionOperatortraverseMap[node.operator](value, nodeIterator.traverse(node.right)) + }, + UnaryExpressionOperatortraverseMap: { + '-': (nodeIterator) => -nodeIterator.traverse(nodeIterator.node.argument), + '+': (nodeIterator) => +nodeIterator.traverse(nodeIterator.node.argument), + '!': (nodeIterator) => !nodeIterator.traverse(nodeIterator.node.argument), + '~': (nodeIterator) => ~nodeIterator.traverse(nodeIterator.node.argument), + 'typeof': (nodeIterator) => { + if (nodeIterator.node.argument.type === 'Identifier') { + try { + const value = nodeIterator.scope.get(nodeIterator.node.argument.name) + return value ? typeof value.value : 'undefined' + } catch (err) { + if (err.message === `${nodeIterator.node.argument.name} is not defined`) { + return 'undefined' + } else { + throw err + } + } + } else { + return typeof nodeIterator.traverse(nodeIterator.node.argument) + } + }, + 'void': (nodeIterator) => void nodeIterator.traverse(nodeIterator.node.argument), + 'delete': (nodeIterator) => { + const argument = nodeIterator.node.argument + if (argument.type === 'MemberExpression') { + const obj = nodeIterator.traverse(argument.object) + const name = getPropertyName(argument, nodeIterator) + return delete obj[name] + } else if (argument.type === 'Identifier') { + return false + } else if (argument.type === 'Literal') { + return true + } + } + }, + UnaryExpression (nodeIterator) { + return NodeHandler.UnaryExpressionOperatortraverseMap[nodeIterator.node.operator](nodeIterator) + }, + BinaryExpressionOperatortraverseMap: { + '==': (a, b) => a == b, + '!=': (a, b) => a != b, + '===': (a, b) => a === b, + '!==': (a, b) => a !== b, + '<': (a, b) => a < b, + '<=': (a, b) => a <= b, + '>': (a, b) => a > b, + '>=': (a, b) => a >= b, + '<<': (a, b) => a << b, + '>>': (a, b) => a >> b, + '>>>': (a, b) => a >>> b, + '+': (a, b) => a + b, + '-': (a, b) => a - b, + '*': (a, b) => a * b, + '/': (a, b) => a / b, + '%': (a, b) => a % b, + '**': (a, b) => { throw new Error('canjs: es5 doesn\'t supports operator "**"') }, + '|': (a, b) => a | b, + '^': (a, b) => a ^ b, + '&': (a, b) => a & b, + 'in': (a, b) => a in b, + 'instanceof': (a, b) => a instanceof b + }, + BinaryExpression (nodeIterator) { + const a = nodeIterator.traverse(nodeIterator.node.left) + const b = nodeIterator.traverse(nodeIterator.node.right) + return NodeHandler.BinaryExpressionOperatortraverseMap[nodeIterator.node.operator](a, b) + }, + LogicalExpressionOperatortraverseMap: { + '||': (a, b) => a || b, + '&&': (a, b) => a && b + }, + LogicalExpression (nodeIterator) { + const a = nodeIterator.traverse(nodeIterator.node.left) + if (a) { + if (nodeIterator.node.operator == '||') { + return true; + } + } + else if (nodeIterator.node.operator == '&&') { + return false; + } + + const b = nodeIterator.traverse(nodeIterator.node.right) + return NodeHandler.LogicalExpressionOperatortraverseMap[nodeIterator.node.operator](a, b) + }, + + // For循环节点处理器 + /* + For循环的三个参数对应着节点的init,test,update属性, + 对着三个属性分别调用节点处理器处理, + 并放回JS原生的for循环当中即可。 + */ + ForStatement (nodeIterator) { + const node = nodeIterator.node + let scope = nodeIterator.scope + if (node.init && node.init.type === 'VariableDeclaration' && node.init.kind !== 'var') { + scope = nodeIterator.createScope('block') + } + + for ( + node.init && nodeIterator.traverse(node.init, { scope }); + node.test ? nodeIterator.traverse(node.test, { scope }) : true; + node.update && nodeIterator.traverse(node.update, { scope }) + ) { + const signal = nodeIterator.traverse(node.body, { scope }) + + if (Signal.isBreak(signal)) { + break + } else if (Signal.isContinue(signal)) { + continue + } else if (Signal.isReturn(signal)) { + return signal + } + } + }, + ForInStatement (nodeIterator) { + const { left, right, body } = nodeIterator.node + let scope = nodeIterator.scope + + let value + if (left.type === 'VariableDeclaration') { + const id = left.declarations[0].id + value = scope.declare(id.name, undefined, left.kind) + } else if (left.type === 'Identifier') { + value = scope.get(left.name, true) + } else { + throw new Error(`canjs: [ForInStatement] Unsupported left type "${left.type}"`) + } + + for (const key in nodeIterator.traverse(right)) { + value.value = key + const signal = nodeIterator.traverse(body, { scope }) + + if (Signal.isBreak(signal)) { + break + } else if (Signal.isContinue(signal)) { + continue + } else if (Signal.isReturn(signal)) { + return signal + } + } + }, + WhileStatement (nodeIterator) { + while (nodeIterator.traverse(nodeIterator.node.test)) { + const signal = nodeIterator.traverse(nodeIterator.node.body) + + if (Signal.isBreak(signal)) { + break + } else if (Signal.isContinue(signal)) { + continue + } else if (Signal.isReturn(signal)) { + return signal + } + } + }, + DoWhileStatement (nodeIterator) { + do { + const signal = nodeIterator.traverse(nodeIterator.node.body) + + if (Signal.isBreak(signal)) { + break + } else if (Signal.isContinue(signal)) { + continue + } else if (Signal.isReturn(signal)) { + return signal + } + } while (nodeIterator.traverse(nodeIterator.node.test)) + }, + + ReturnStatement (nodeIterator) { + let value + if (nodeIterator.node.argument) { + value = nodeIterator.traverse(nodeIterator.node.argument) + } + return Signal.Return(value) + }, + BreakStatement (nodeIterator) { + let label + if (nodeIterator.node.label) { + label = nodeIterator.node.label.name + } + return Signal.Break(label) + }, + ContinueStatement (nodeIterator) { + let label + if (nodeIterator.node.label) { + label = nodeIterator.node.label.name + } + return Signal.Continue(label) + }, + + // If声明节点处理器 + IfStatement (nodeIterator) { + if (nodeIterator.traverse(nodeIterator.node.test)) { + return nodeIterator.traverse(nodeIterator.node.consequent) + } else if (nodeIterator.node.alternate) { + return nodeIterator.traverse(nodeIterator.node.alternate) + } + }, + SwitchStatement (nodeIterator) { + const discriminant = nodeIterator.traverse(nodeIterator.node.discriminant) + + for (const theCase of nodeIterator.node.cases) { + if (!theCase.test || discriminant === nodeIterator.traverse(theCase.test)) { + const signal = nodeIterator.traverse(theCase) + + if (Signal.isBreak(signal)) { + break + } else if (Signal.isContinue(signal)) { + continue + } else if (Signal.isReturn(signal)) { + return signal + } + } + } + }, + SwitchCase (nodeIterator) { + for (const node of nodeIterator.node.consequent) { + const signal = nodeIterator.traverse(node) + if (Signal.isSignal(signal)) { + return signal + } + } + }, + ConditionalExpression (nodeIterator) { + return nodeIterator.traverse(nodeIterator.node.test) + ? nodeIterator.traverse(nodeIterator.node.consequent) + : nodeIterator.traverse(nodeIterator.node.alternate) + }, + + ThrowStatement(nodeIterator) { + throw nodeIterator.traverse(nodeIterator.node.argument) + }, + TryStatement(nodeIterator) { + const { block, handler, finalizer } = nodeIterator.node + try { + return nodeIterator.traverse(block) + } catch (err) { + if (handler) { + const param = handler.param + const scope = nodeIterator.createScope('block') + scope.letDeclare(param.name, err) + return nodeIterator.traverse(handler, { scope }) + } + throw err + } finally { + if (finalizer) { + return nodeIterator.traverse(finalizer) + } + } + }, + CatchClause(nodeIterator) { + return nodeIterator.traverse(nodeIterator.node.body); + } +} + +function getPropertyName (node, nodeIterator) { + if (node.computed) { + return nodeIterator.traverse(node.property) + } else { + return node.property.name + } +} + +function getIdentifierOrMemberExpressionValue(node, nodeIterator) { + if (node.type === 'Identifier') { + return nodeIterator.scope.get(node.name) + } else if (node.type === 'MemberExpression') { + const obj = nodeIterator.traverse(node.object) + const name = getPropertyName(node, nodeIterator) + return new MemberValue(obj, name) + } else { + throw new Error(`canjs: Not support to get value of node type "${node.type}"`) + } +} + +module.exports = NodeHandler diff --git a/vm/es_versions/index.js b/vm/es_versions/index.js new file mode 100644 index 0000000..52c476c --- /dev/null +++ b/vm/es_versions/index.js @@ -0,0 +1,5 @@ +const es5 = require('./es5') + +module.exports = { + ...es5 +} diff --git a/vm/index.js b/vm/index.js new file mode 100644 index 0000000..3cd5dd1 --- /dev/null +++ b/vm/index.js @@ -0,0 +1,41 @@ +/** + * @author Jrainlau + * @desc Canjs类 + * + * @class + * + * 传入字符串形式的es5代码,可选的新增全局变量 + * 运行`.run()`方法即可输出运行结果 + * + * eg: new Canjs('console.log("Hello World!")').run() + */ + +// const {Parser} = require('acorn') +const NodeIterator = require('./iterator') +const Scope = require('./scope') + +class Canjs { + constructor(ast, code = '', extraDeclaration = {}) { + this.code = code + this.extraDeclaration = extraDeclaration + // this.ast = Parser.parse(code) + this.ast = ast + this.nodeIterator = null + this.init() + } + + init() { // 定义全局作用域,该作用域类型为函数作用域 + const globalScope = new Scope('function') + // 根据入参定义标准库之外的全局变量 + Object.keys(this.extraDeclaration).forEach((key) => { + globalScope.addDeclaration(key, this.extraDeclaration[key]) + }) + this.nodeIterator = new NodeIterator(null, globalScope) + } + + run() { + return this.nodeIterator.traverse(this.ast) + } +} + +module.exports = Canjs diff --git a/vm/iterator.js b/vm/iterator.js new file mode 100644 index 0000000..43e989f --- /dev/null +++ b/vm/iterator.js @@ -0,0 +1,61 @@ +/** + * @author Jrainlau + * @desc 节点遍历器,递归遍历AST内的每一个节点并调用对应的方法进行解析 + * + * @class + * + * 针对AST节点进行解析,根据节点类型调用“节点处理器”(nodeHandler)对应的方法。 + * 在进行解析的时候,会传入节点和节点对应的作用域。 + * + * 另外也提供了创建作用域的方法(createScope),可用于创建函数作用域或者块级作用域。 + */ +// 节点处理器 +const nodeHandler = require('./es_versions') +const Scope = require('./scope') +// 节点遍历器 +class NodeIterator { + constructor (node, scope) { + this.node = node + this.scope = scope + this.nodeHandler = nodeHandler + } + + traverse (node, options = {}) { + // 作用域 + // 作用域的处理,可以说是整个JS解释器最难的部分 + /* + // eg: + const a = 1 + { + const b = 2 + console.log(a) + } + console.log(b) + */ + /* + 运行结果必然是能够打印出a的值, + 然后报错:Uncaught ReferenceError: b is not defined + */ + /* + 块级作用域或者函数作用域可以读取其父级作用域当中的变量, + 反之则不行,所以对于作用域我们不能简单地定义一个空对象, + 而是要专门进行处理。 + */ + const scope = options.scope || this.scope + const nodeIterator = new NodeIterator(node, scope) + // 根据节点类型找到节点处理器当中对应的函数 + const _eval = this.nodeHandler[node.type] + // 若找不到则报错 + if (!_eval) { + throw new Error(`canjs: Unknown node type "${node.type}".`) + } + // 运行处理函数 + return _eval(nodeIterator) + } + + createScope (blockType = 'block') { + return new Scope(blockType, this.scope) + } +} + +module.exports = NodeIterator diff --git a/vm/scope.js b/vm/scope.js new file mode 100644 index 0000000..733a0f4 --- /dev/null +++ b/vm/scope.js @@ -0,0 +1,111 @@ +/** + * @author Jrainlau + * @desc 管理作用域 + * + * @class + * + * 每次对节点的处理,都要考虑其作用域的问题。Scope实例会定义该作用域为函数作用域(function)或者块级作用域(block)。 + * 每次新建Scope实例,都会为当前节点创建一个全新的“作用域变量空间”(declaration),任何在此作用域内定义的变量都会存放在这个空间当中 + * 此外,新建Scope实例也会保存其父级作用域。 + */ +// standardMap对象提供es5所支持的内置对象/方法 JS标准库。 +/* +JS标准库就是JS这门语言本身所带有的一系列方法和属性, +如常用的setTimeout,console.log等等。 +为了让解析器也能够执行这些方法,所以我们需要为其注入标准库: +*/ +const standardMap = require('./standard') +const { SimpleValue } = require('./value') +// 作用域 +class Scope { + constructor (type, parentScope) { + // 作用域类型,区分函数作用域function和块级作用域block + this.type = type + // 父级作用域 + this.parentScope = parentScope + // 全局作用域 + this.globalDeclaration = standardMap + // 当前作用域的变量空间 + this.declaration = Object.create(null) // 每次都新建一个全新的作用域 + } + + addDeclaration (name, value) { + this.globalDeclaration[name] = new SimpleValue(value) + } + + /* + get/set方法用于获取/设置当前作用域中对应name的变量值 + 符合JS语法规则,优先从当前作用域去找,若找不到则到父级作用域去找,然后到全局作用域找。 + 如果都没有,就报错 + */ + get (name) { + if (this.declaration[name]) { + return this.declaration[name] + } else if (this.parentScope) { + return this.parentScope.get(name) + } else if (this.globalDeclaration[name]) { + return this.globalDeclaration[name] + } + throw new ReferenceError(`${name} is not defined`) + } + + set (name, value) { + if (this.declaration[name]) { + this.declaration[name].set(value) + } else if (this.parentScope) { + this.parentScope.set(name, value) + } else if (this.globalDeclaration[name]) { + return this.globalDeclaration.set(name, value) + } else { + throw new ReferenceError(`${name} is not defined`) + } + } + + /** + 根据变量的kind调用不同的变量定义方法 + */ + declare (name, value, kind = 'var') { + if (kind === 'var') { + return this.varDeclare(name, value) + } else if (kind === 'let') { + return this.letDeclare(name, value) + } else if (kind === 'const') { + return this.constDeclare(name, value) + } else { + throw new Error(`canjs: Invalid Variable Declaration Kind of "${kind}"`) + } + } + + varDeclare (name, value) { + let scope = this + // example + // ? + // 如果遇到块级作用域且关键字为var,则需要把这个变量也定义到父级作用域当中,这也就是我们常说的“全局变量污染”。 + // 若当前作用域存在非函数类型的父级作用域时,就把变量定义到父级作用域 + while (scope.parentScope && scope.type !== 'function') { + scope = scope.parentScope + } + scope.declaration[name] = new SimpleValue(value, 'var') + return scope.declaration[name] + } + + letDeclare (name, value) { + // 不允许重复定义 + if (this.declaration[name]) { + throw new SyntaxError(`Identifier ${name} has already been declared`) + } + this.declaration[name] = new SimpleValue(value, 'let') + return this.declaration[name] + } + + constDeclare (name, value) { + // 不允许重复定义 + if (this.declaration[name]) { + throw new SyntaxError(`Identifier ${name} has already been declared`) + } + this.declaration[name] = new SimpleValue(value, 'const') + return this.declaration[name] + } +} + +module.exports = Scope diff --git a/vm/signal.js b/vm/signal.js new file mode 100644 index 0000000..b0bee3e --- /dev/null +++ b/vm/signal.js @@ -0,0 +1,43 @@ +/** + * @author: Jrainlau + * @desc: 判断、记录关键标记语句(return, break, continue) + * + * @class + */ + +class Signal { + constructor (type, value) { + this.type = type + this.value = value + } + + static Return (value) { + return new Signal('return', value) + } + + static Break (label = null) { + return new Signal('break', label) + } + + static Continue (label) { + return new Signal('continue', label) + } + + static isReturn(signal) { + return signal instanceof Signal && signal.type === 'return' + } + + static isContinue(signal) { + return signal instanceof Signal && signal.type === 'continue' + } + + static isBreak(signal) { + return signal instanceof Signal && signal.type === 'break' + } + + static isSignal (signal) { + return signal instanceof Signal + } +} + +module.exports = Signal diff --git a/vm/standard.js b/vm/standard.js new file mode 100644 index 0000000..797ba7c --- /dev/null +++ b/vm/standard.js @@ -0,0 +1,79 @@ +/** + * @author: JrainLau + * @desc: es5标准库,提供es5所支持的内置对象/方法 + */ + +const { SimpleValue } = require('./value') + +let windowObj = null +let globalObj = null + +try { + windowObj = window +} catch (e) {} + +try { + globalObj = global +} catch (e) {} +// 提供es5所支持的内置对象/方法 +const standardMap = { + // Function properties + isFinite: new SimpleValue(isFinite), + isNaN: new SimpleValue(isNaN), + parseFloat: new SimpleValue(parseFloat), + parseInt: new SimpleValue(parseInt), + decodeURI: new SimpleValue(decodeURI), + decodeURIComponent: new SimpleValue(decodeURIComponent), + encodeURI: new SimpleValue(encodeURI), + encodeURIComponent: new SimpleValue(encodeURIComponent), + + // Fundamental objects + Object: new SimpleValue(Object), + Function: new SimpleValue(Function), + Boolean: new SimpleValue(Boolean), + Symbol: new SimpleValue(Symbol), + Error: new SimpleValue(Error), + EvalError: new SimpleValue(EvalError), + RangeError: new SimpleValue(RangeError), + ReferenceError: new SimpleValue(ReferenceError), + SyntaxError: new SimpleValue(SyntaxError), + TypeError: new SimpleValue(TypeError), + URIError: new SimpleValue(URIError), + + // Numbers and dates + Number: new SimpleValue(Number), + Math: new SimpleValue(Math), + Date: new SimpleValue(Date), + + // Text processing + String: new SimpleValue(String), + RegExp: new SimpleValue(RegExp), + + // Indexed collections + Array: new SimpleValue(Array), + Int8Array: new SimpleValue(Int8Array), + Uint8Array: new SimpleValue(Uint8Array), + Uint8ClampedArray: new SimpleValue(Uint8ClampedArray), + Int16Array: new SimpleValue(Int16Array), + Uint16Array: new SimpleValue(Uint16Array), + Int32Array: new SimpleValue(Int32Array), + Uint32Array: new SimpleValue(Uint32Array), + Float32Array: new SimpleValue(Float32Array), + Float64Array: new SimpleValue(Float64Array), + + // Structured data + ArrayBuffer: new SimpleValue(ArrayBuffer), + DataView: new SimpleValue(DataView), + JSON: new SimpleValue(JSON), + + // // Other + window: new SimpleValue(windowObj), + global: new SimpleValue(globalObj), + console: new SimpleValue(console), + setTimeout: new SimpleValue(setTimeout), + clearTimeout: new SimpleValue(clearTimeout), + setInterval: new SimpleValue(setInterval), + clearInterval: new SimpleValue(clearInterval) +} + +module.exports = standardMap diff --git a/vm/value.js b/vm/value.js new file mode 100644 index 0000000..e17fb25 --- /dev/null +++ b/vm/value.js @@ -0,0 +1,60 @@ +/** + * @author Jrainlau + * @desc 和变量保存相关 + */ + +/** + * 创建一个普通变量值 + * + * @class + * @param any value 值 + * @param string kind 变量定义符(var, let, const) + * @method set 设置值 + * @method get 获取值 + */ +class SimpleValue { + constructor (value, kind = '') { + this.value = value + this.kind = kind + } + + set (value) { + // 禁止重新对const类型变量赋值 + if (this.kind === 'const') { + throw new TypeError('Assignment to constant variable') + } else { + this.value = value + } + } + + get () { + return this.value + } +} + +/** + * 创建一个类变量 + * + * @class + * @param any obj 类 + * @param prop any 属性 + * @method set 设置类的属性的值 + * @method get 获取类的属性的值 + */ +class MemberValue { + constructor(obj, prop) { + this.obj = obj + this.prop = prop + } + + set (value) { + this.obj[this.prop] = value + } + + get () { + return this.obj[this.prop] + } +} + +module.exports.SimpleValue = SimpleValue +module.exports.MemberValue = MemberValue