开始尝试解析一下 tokens
对, 至少现在还是没有完成 parser 的工作, 为什么分类两篇主要是因为是分两次写的 orz
众所周知, 代码执行是通过 AST 来进行的, 所以这个步骤就是把之前的 tokens 转换成 AST, 所以, 先随便找一个网站大概看看现在的 AST 是什么样子的
嗯, 随便找的网站: https://astexplorer.net/ (估计很多人都很眼熟, 嗯...
如果是 js 的话, 被执行的代码应该是这样子的
它转换出来的 AST 嘛, 稍微有那么亿点点的复杂, 不过没关系, 目前只需要参考一些简单的格式来规避一些可能存在的错误, 其实完全自己设计 AST 也并不是不行...的感觉
嘛, 这个是转出来的部分结果
好吧, 确实是有那么亿点点的复杂, 嗯, 只是亿点点而已, 嘛...
之后就是要之前的 tokens 转成简易版的这个东西就行了, 所以还是要先大体规划一下 AST 的 types
PS: 扯一定题外话, 最开始是打算设计一个跨语言的脚本, 可以通过 import 动态加载不同语言的 lib, 比如万能的 js, 所以就存在了一些问题, 要么有一个支持各种语言的动态解释器, 嗯, 我知道这很蠢, 要么本地编译脚本, 统一输出目标平台的 code, 嗯..好吧, 听起来不是那么蠢了, 但也是很蠢, 脚本语言的目的就不见了喂, 就很蠢, 所以考虑下还是脚本 + lib 编译, 即 import 进来的东西默认为在当前平台可以执行, 嘛, 这不重要, 现在考虑这些还太早~
回到正题, 规划一下 types, 大概是这个样子
不够或者有问题可以再改, 参考了一些其它的代码, 感觉这些应该或许大概足够使用了 orz
PS: 之前有个小小的问题, 应该是 integer 的类型之前写成了 number, js 选手受害者 T T (计划是强类型的, 人总要有点梦想~
OK, 准备就绪, 开始抄算法啦, 呸, 是写算法, 呸呸呸, 写代码的事情, 怎么能叫抄呢~
这次的目标一共是解析两行代码, 目前为止看起来已经完成了一半~
(感觉好像没有什么需要补注释的地方(小声 orz
然后再去实现第二行代码的 AST 的解析 (感觉需要一个类型是 MemberExpression, 所以补了一个类型 orz
好啦! 这个写的很烂的 AST 解析器就这样完成啦~ (虽然一定有很多问题, 嘛...遇到问题的时候解决就好了~
最开始的两行代码已经变成了这个样子~
最后补一下抽出来的一些工具函数
嗯...有点后悔把字面量单独作为 token type 了, 不过影响不大 orz
最后附上这个源文件~ (这个就等于是备份了一下
const astTypes = [
'Identifier', // 变量名
'ImportDeclaration',
'StringLiteral',
'IntegerLiteral',
'CallExperssion',
'MemberExpression',
]
function isLiteral(type) {
return ['string', 'integer'].includes(type)
}
function isKeyword(token, value) {
return token.type === 'keyword' && token.value === value
}
function isOperator(token, value) {
return token.type === 'operator' && token.value === value
}
function parseLiteralType(type) {
return type.slice(0, 1).toUpperCase() + type.slice(1) + 'Literal'
}
function parseAST(input) {
let cur = 0
let ast = {
type: 'Program',
body: [],
}
function whileForImport() {
const node = {
type: 'ImportDeclaration',
}
let token = input[cur]
while(!isOperator(token, '\n') && cur < input.length) {
cur++
token = input[cur]
if (isLiteral(token.type) && !node.alias) {
const type = parseLiteralType(token.type)
node.source = { type, value: token.value }
} else if (token.type === 'name' && node.alias) {
node.alias = { type: 'Identifier', value: token.value }
} else if (isKeyword(token, 'as')) {
node.alias = true
}
}
cur++
return node
}
function whileForCallee() {
const node = {
type: 'CallExperssion',
}
let token = input[cur]
let parendeep = 0
while(!isOperator(token, '\n') && cur < input.length) {
if (token.type === 'name') {
node.property = { type: 'Identifier', value: token.value }
} else if (isOperator(token, '.') && node.property) {
const object = node.object
node.object = { ...node.property, type: 'MemberExpression' }
// 调用链的结构还是参考了一下 js 的 ast
if (object) {
node.object.object = object
}
node.property = null
} else if (token.type === 'paren') {
parendeep += ['('].includes(token.value) ? 1 : -1
} else if (parendeep && isLiteral(token.type)) {
node.arguments = node.arguments || []
const type = parseLiteralType(token.type)
node.arguments.push({ type, value: token.value })
}
cur++
token = input[cur]
}
cur++
return node
}
function walk() {
let token = input[cur]
if (isKeyword(token, 'import')) {
return whileForImport()
}
if (token.type === 'name') {
return whileForCallee()
}
cur++
}
while (cur < input.length) {
ast.body.push(walk());
}
return ast
}
module.exports = { parseAST }