先说明一下之前的几个问题
由于之前的 AST 是直接参考完成版的格式, 在阅读了一些代码之后, 中途先转换为 tree 然后在转换为可执行结构会好一些, 不过目前来看影响不大, 至少现在生成的 ast tree 是可以执行的 (大概
好, 那就在之前过程错误但结果看起来正确但基础上, 继续朝向这样一个大目标前进吧!
嗯...为什么又突然停下来, 是因为还是有一些要确认的问题 (啊哈哈..哈哈..哈...
其实在有了 AST 之后, 剩下的只是以合适的方式去遍历并且处理 AST 就可以了, 这个倒不是很困难的事情, 倒不如说生成 AST 才是一件更复杂的事情, 其实废话了这么多, 主要还不是因为编程语言可以编译(complier)和解释(runtime)两种方式嘛, 虽然有些语言可以将部分 runtime 的内存管理编译到执行文件里面, 不过脚本嘛, 还是要有解释器才行的...
所以, 问题是什么呢? 哈哈, 怎么说呢, 纯个人感觉, 处理 runtime 是一件比处理 AST 更复杂的事情, 所以, 打算先走编译的方式成功执行这段代码快乐一下再说~~
对, 就是和 babel 经常干的事情一样, 把不知道什么奇怪的代码转换成 js
所以, 可以开始开工了~
注意: 这次的小目标只是把 字符串 -> js
对, 只是这样一个小目标
没有任何 runtime 的事情, 是一个纯粹的静态编译!
纯静态编译!
好吧那就先简单的实现一下
首先需要改造一下之前的 io.js, 因为 node 并不支持 export 这种写法 (至少我的版本还不支持
然后看一下 main.sx, 没什么变化, 只是单纯的回忆一下
之后放出一下执行函数~, 其实也只是, 整合了之前实现的一些功能, 并且增加了文件读取的入口~
index.js
啊? 那个 complier 是个什么东西? 之前好像没有提到过?
嗯...只是一个勉强能用的垃圾实现而已, 对编译行为可以有一个初体验~
写的很烂, 但是就这两行代码而言, 足够用了 (真的是人生头一回写这种代码 orz
最后, 自然就是执行结果啦!
哦耶, 我完事了~ 短点就短点吧 orz
如果解释执行的话应该是一个什么思路
哎, 这个很麻烦的 = =
不过趁现在还只需要处理两行代码, 嘛...
解释执行的话, 首先需要面临的一个问题就是, 这些代码是在什么平台下执行的, 不同平台下要怎么做到一些兼容的问题, 这里说的平台是一个很笼统的概念, 大概知道有这么个东西应该就行了 (大概
解释执行一定会用到实现解释器这个语言的一些特性, 但问题就在于如何合理的去使用这样一些特性, 当然对于我这个小目标而言, 还是能更多的使用语言特性就使用喽~
好, 下面开始! 你以为是去改造 complier 这个函数嘛? 不并不是 (其实我刚开始也是这么想的
(主要是 complier 的这个实现丢掉了很多信息, 所以应该需要一种能获得 runtime 信息的遍历方法
其实也就是这个~
虽然很简单, 反正目前来看足够用了 orz (只解析这两行代码的话, 真的足够用了
然后就是去遍历这个 AST 了
PS: 和编译相比, 解释执行的时候意外的补了很多注释, 感觉有点难理解, 但可能也不是那么难理解?? (应该是递归的锅 orz
在我的思路和脑补中, 解释器大概就是这么个东西了, 很可能猜测的不是很对, 实现方式什么的会有问题, 但总归感觉是这么一个思路
嘛...不要在意一些细节 orz
稍微封装一下这些方法, 看起来更好看一些
哦耶, 大功告成~
嗯...主要是编译, 和解释执行, 这两种实现方式实际上是有完全本质上的不同, 希望看到文章的小伙伴阔以稍稍领悟一下这个思路的差异~
什么? 完结撒花~
怎么可能! 这才只执行了两行代码而已, 还有更多更多更多的坑在前方的道路上等着呢 orz
最后, 附上代码~
complier.js
function complier(node) {
switch (node.type) {
case 'Program':
return node.body.map(complier).join(';')
case 'ImportDeclaration':
return `const ${node.alias.value} = require('${
// 这里偷懒一下, 直接把路径写死了 orz
'..' + node.source.value.slice(2)
})`
case 'CallExperssion':
return `${complier(node.object)}${node.property.value}(${
node.arguments.map(arg => arg.value).join(',')
})`
case 'MemberExpression':
return `${node.value}.`
}
}
module.exports = { complier }
traverser.js
const fs = require('fs')
const path = require('path')
function executeAST(ast, execPath) {
const vars = {}
traverser(ast, {
ImportDeclaration(node, parent) {
const p = path.resolve(execPath, node.source.value)
const js = fs.readFileSync(p, { encoding: 'utf-8' })
// 使用 js 的语法特性, 在内存中申请变量空间
vars[node.alias.value] = eval(js)
},
CallExperssion(node, parent) {
// obj 找不到的话不就是最常见的空引用异常了么 = =
const obj = vars[node.object.value]
const params = node.arguments.map(arg => arg.value)
// 链式调用的问题先不管 orz
obj[node.property.value](...params)
},
IntegerLiteral(node, parent) {
// 数字类型的转换, 先放这里吧
node.value = parseInt(node.value)
},
StringLiteral(node, parent) {
// 字符串去掉引号, 也先放这里吧
node.value = node.value.slice(1, -1)
}
})
}
function traverser(ast, visitor) {
function traverseArray(array, parent) {
array.forEach(child => {
traverseNode(child, parent)
})
}
function traverseNode(node, parent) {
const method = visitor[node.type]
switch (node.type) {
case 'Program':
traverseArray(node.body, node)
break
// 单词打错了, 要死了 orz
case 'CallExperssion':
traverseArray(node.arguments, node)
break
case 'ImportDeclaration':
traverseNode(node.source, node)
break
}
// visitor 按道理应该支持 前序遍历 和 后序遍历两种
// 还是那句话, 先这样吧 orz (只有后序
if (method) {
method(node, parent)
}
}
traverseNode(ast, null)
}
module.exports = { traverser, executeAST }
index.js
const fs = require('fs')
const { getTokens } = require('./utils/tokens')
const { parseAST } = require('./utils/ast')
const { executeAST } = require('./utils/traverser')
const p = process.argv[2]
const input = fs.readFileSync(p, { encoding: 'utf-8' })
const path = require('path')
function execute(code) {
const tokens = getTokens(code)
const ast = parseAST(tokens)
const execPath = path.resolve(p).replace(p, '')
executeAST(ast, execPath)
}
execute(input)