Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
757 changes: 735 additions & 22 deletions final/eval.js

Large diffs are not rendered by default.

147 changes: 147 additions & 0 deletions final/scope.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,147 @@
const standardMap = require("./standard"); // 注入标准库
class Scope {
constructor(variables, parent, type) {
// 解释器运行起来的时候,就需要定义一个全局作用域 global
this.type = type; // global || function || block
this.parent = parent;
this.variables = variables;
this.globalScope = standardMap;
}

declare(kind, name, initValue, initialized) {
// kind:var / const / let 不同情况定义的位置也不相同
switch (kind) {
case "var": {
if (this.type === "global" && this.find(name)) {
// 查找全局上下文中是否有该变量,如果有则不能更改上下文的值
return;
} else if (this.parent.type === "function") {
if (initialized === false && this.find(name)) return; // 如果是函数执行上下文,则只跳过声明提升
}
if (this.type === "function" || this.type === "global") {
this.variables[name] = {
kind: kind,
value: initValue,
initialized: true,
};
} else {
this.parent.declare(kind, name, initValue);
}
return;
}
case "const":
case "let": {
if (this.type === "global" || this.parent.type === "function") {
if (this.find(name))
throw new SyntaxError(
`Identifier '${name}' has already been declared`
);
}
if (this.variables[name] && this.variables[name].initialized === true) {
throw new SyntaxError(
`Identifier '${name}' has already been declared`
);
} else {
this.variables[name] = {
kind: kind,
value: initValue,
initialized: initialized,
};
}
return;
}
default: {
this.variables[name] = {
kind: kind,
value: initValue,
initialized: initialized,
};
}
}
return;
}

get(name) {
if (name === null) {
return { value: null };
} else {
if (name === "undefined") {
return { value: undefined };
} else {
if (this.variables && this.variables[name]) {
if (this.parent === undefined) return { value: this.variables[name] };
if (
this.variables[name].kind === "let" ||
this.variables[name].kind === "const"
) {
if (this.variables[name].initialized === false) {
throw new SyntaxError(
`Cannot access '${name}' before initialization`
);
}
}
return this.variables[name];
} else {
if (this.parent !== undefined) {
return this.parent.get(name);
} else {
if (this.globalScope[name]) {
return { value: this.globalScope[name] };
} else if (name === "this") return null;
else if (name === "newTarget") return { value: undefined };
else throw new ReferenceError(`${name} is not defined`);
}
}
}
}
}

set(name, value, property) {
if (property) {
if (this.variables[name]) {
if (name === "module") {
return (this.variables[name][property] = value);
} else {
return (this.variables[name].value[property] = value);
}
} else {
if (this.type !== "global" || name === "module") {
return this.parent.set(name, value, property);
} else {
this.variables[name] = {
kind: "var",
value: {},
};
return (this.variables[name].value[property] = value);
}
}
} else {
if (this.variables[name]) {
if (this.variables[name].kind === "const") {
throw new TypeError("Assignment to constant variable");
} else {
return (this.variables[name].value = value);
}
} else {
if (this.type !== "global") {
return this.parent.set(name, value);
} else {
this.variables[name] = {
kind: "var",
value: value,
initialized: true,
};
}
}
}
}

find(name) {
if (this.parent === undefined || this.type === "function") {
if (this.variables && this.variables[name]) return true;
else return false;
} else return this.parent.find(name);
}
}

module.exports = Scope;
73 changes: 73 additions & 0 deletions final/standard.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,73 @@
let windowObj = null
let globalObj = null

try {
windowObj = window
} catch (e) { }

try {
globalObj = global
} catch (e) { }

const StandardMap = {
// Function properties
isFinite: isFinite,
isNaN: isNaN,
parseFloat: parseFloat,
parseInt: parseInt,
decodeURI: decodeURI,
decodeURIComponent: decodeURIComponent,
encodeURI: encodeURI,
encodeURIComponent: encodeURIComponent,

// Fundamental objects
Object: Object,
Function: Function,
Boolean: Boolean,
Symbol: Symbol,
Error: Error,
EvalError: EvalError,
RangeError: RangeError,
ReferenceError: ReferenceError,
SyntaxError: SyntaxError,
TypeError: TypeError,
URIError: URIError,

// Numbers and dates
Number: Number,
Math: Math,
Date: Date,

// Text processing
String: String,
RegExp: RegExp,

// Indexed collections
Array: Array,
Int8Array: Int8Array,
Uint8Array: Uint8Array,
Uint8ClampedArray: Uint8ClampedArray,
Int16Array: Int16Array,
Uint16Array: Uint16Array,
Int32Array: Int32Array,
Uint32Array: Uint32Array,
Float32Array: Float32Array,
Float64Array: Float64Array,

// Structured data
ArrayBuffer: ArrayBuffer,
DataView: DataView,
JSON: JSON,

// // Other
window: windowObj,
global: globalObj,
console: console,
setTimeout: setTimeout,
clearTimeout: clearTimeout,
setInterval: setInterval,
clearInterval: clearInterval,
Promise: Promise
}

module.exports = StandardMap
17 changes: 8 additions & 9 deletions final/test/stack-track.test.js
Original file line number Diff line number Diff line change
Expand Up @@ -4,18 +4,17 @@ const { customEval, Scope } = require('../eval');
test("not defined", t => {
const scope = new Scope();

try {
customEval(
`function get(){
t.throws(
function () {
customEval(
`function get(){
var a = 123;
console.log(b);
}

get();`,
scope
);
t.fail("it should throw an error");
} catch (err) {
// ignore
}
scope
);
}
)
});
42 changes: 30 additions & 12 deletions homework/1/rename.js
Original file line number Diff line number Diff line change
Expand Up @@ -3,23 +3,41 @@ const astring = require('astring');
const traverse = require('../../common/traverse');

function transform(root, originName, targetName) {
// 遍历所有节点
return traverse((node, ctx, next) => {
// 遍历所有节点
return traverse((node, ctx, next) => {

// TODO: 作业代码写在这里
if (node.type === 'xxx') {
}
// TODO: 作业代码写在这里

// 继续往下遍历
return next(node, ctx)
})(root);
const nodeTypeArray = [
'FunctionDeclaration',
'VariableDeclarator',
'MemberExpression',
'BinaryExpression'
]
const keyTypeArray = [
'property',
'label',
'key'
]
if (nodeTypeArray.includes(node.type)) {
for (const key in node) {
if (keyTypeArray.includes(key)) continue
if (node[key].type === 'Identifier' && node[key].name === originName) {
node[key].name = targetName;
}
}
}

// 继续往下遍历
return next(node, ctx)
})(root);
}

function rename(code, originName, targetName) {
const ast = acorn.parse(code, {
ecmaVersion: 5,
})
return astring.generate(transform(ast, originName, targetName))
const ast = acorn.parse(code, {
ecmaVersion: 5,
})
return astring.generate(transform(ast, originName, targetName))
}

module.exports = rename
Loading