记录一下如何实现一门编程语言~(2)

204 阅读3分钟

开始尝试解析一下 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 }