嗯数据类型是很重要一件事嗯
好吧, 接下来的过程能参考(copy)的代码就比较少了, 因为完全是凭自己喜好定义的一些东西, 所以只能依赖自己强大的脑补能力了 orz
作为一个强类型的脚本语言 (怎么感觉怪怪的, 嘛, 这不重要), 数据类型是一个及其重要的东西, 所以, 反正我就凭自己喜好这么定义了
一共四种基本数据结构, 对应四种字面量的声明方式, 所以这次的小目标是要解析这么 8 行代码, 和上次比足足翻了 4 倍呢, 哇哦哦哦~(手动斜眼
定义语法是一件很轻松加愉快的事情, 如果只需要定义语法那么说不定只需要两天我就可以完成这个大目标了~
修改了一下之前定义的结构, 并且新增了一些关键字, 写代码就是这么反反复复
然后重新解析 tokens, 仿佛又回到了最初的起点~ 啊不是, 其实就是回到了最开始的那个步骤了 T T
经过大幅度修改之后, getTokens 大概变成了这个样子
顺便写了两个为了便利化操作的小函数
好吧, 逐渐开始变得有一丢丢复杂了呢 orz
tokens 的结果也开始复杂了起来, 不过没关系, 可以每一代码的 tokens 都人为的确认一下 (如果有单元测试就没有这个问题了 (大雾
这次关注点在于类型推断是否正确, 以及新的关键字能否正常匹配, 还有之前的 import 的结构是否正常 (再次想念单元测试
之后要进入这次的正题了, 处理之前遗留的问题, 分两步转换 AST, 首先是将 tokens 映射到 AST, 然后再将这个半成品 AST 转换成比较容易执行的结构, 其实可以理解为一次预处理的感觉
这是一个不那么容易理解而且一次截屏放不下的递归, 太难了 T T
顺便补一下相关的工具函数, 还是递归 = =
其实吧, 这里可能就有点不那么好理解了, 但递归这个东西, 只能看每个人的灵性了, 因为循环都能转成递归写法, 但总有一些递归没办法转成循环 (对, 我也很讨厌递归 = =
首先肯定是需要一个简易的 simpleAST 的遍历器, 尝试了一下, 使用后序遍历会好一些 = =
然后就是一边遍历一边将语法树转换成更容易被执行的样子 orz
差不多这个样子, 有亿点点的复杂好像
语法树得出这个结果, 看上去好像没有什么大的问题 orz
对, 姑且就认为完全没有问题好了~
PS: 最开始说的, 这次只处理基本数据类型相关的代码, 函数调用包括链式调用相关的代码先忽略, 能用就行~
总之先把代码跑起来~ orz
最后还是, 附上源码~
constants.js
// 字面量类型
// PS 想了下还是从 token 里面抽离出来了, 说不定会有用 orz
const literalTypes = [
'string',
'integer',
'float',
'boolean',
]
// 字面量特殊值感觉也需要单独拉出来 orz
const specialValues = [
'true',
'false',
]
// PS: 之前的 name 感觉太不太好, 换个单词叫 refrence
const tokenTypes = [
'keyword',
'refrence',
'operator',
'paren',
]
const keywords = [
'import',
'as',
'let',
]
module.exports = {
literalTypes,
specialValues,
tokenTypes,
keywords,
}
tokens.js
const constants = require('./constants')
const { keywords, literalTypes } = constants
const parens = ['(', ')']
const operator = ['.', ':', '=']
function getTokens(input) {
const tokens = []
let cur = 0
let lastMatch = ''
function EOF() {
return cur >= input.length
}
function whileEndingChar(char) {
const ending = char
let str = char
while(str === ending ||
char !== ending && cur < input.length
) {
cur++
char = input[cur]
str += char
}
cur++
return str
}
function whileMatchRegexp(regexp) {
let str = ''
char = input[cur]
while (char.match(regexp) && cur < input.length) {
str += char
cur++
char = input[cur]
}
return str
}
function matchChars(chars) {
let offset = 0
while(chars[offset] === input[cur + offset] && offset < chars.length) {
offset++
}
const match = offset === chars.length
if (match) {
cur += chars.length
lastMatch = chars
}
return match
}
function matchMutipleChars(chars) {
for (const str of chars) {
if (matchChars(str)) {
return true
}
}
return false
}
while(cur < input.length) {
let char = input[cur]
// 处理换行操作
if (char === '\n') {
tokens.push({ type: 'operator', value: char })
cur++
continue
}
// 处理空格或者制表符什么的, 忽略不计
if (char.match(/\s/)) {
cur++
continue
}
// 匹配注释
// PS: 其实并不是很清楚, 为什么注释大多数使用 // 而不是 #
if (matchChars('//')) {
// 注释内容...应该没必要存储吧, 大概 orz
const str = whileEndingChar('\n')
continue
}
// 匹配字符串, 写的有点烂
if (char.match(/\'|\"/)) {
const str = whileEndingChar(char)
// 感觉这个时候应该就可以把 string 的引号去掉了 (大概
tokens.push({ type: 'string', value: str.slice(1, -1) })
continue
}
// 匹配数字
if (char.match(/-|\d/)) {
const str = whileMatchRegexp(/\d|\./)
const type = str.includes('\.') ? 'float' : 'integer'
tokens.push({ type, value: str })
continue
}
// 匹配布尔值字面量 (应该不算关键字
if (matchMutipleChars(['true', 'false'])) {
tokens.push({ type: 'boolean', value: lastMatch })
continue
}
// 匹配关键字 (性能可能有点差...嘛...
// 字面量类型声明也先算关键字吧...
if (matchMutipleChars(keywords.concat(literalTypes))) {
tokens.push({ type: 'keyword', value: lastMatch })
continue
}
// 匹配简单括号~
if (parens.includes(char)) {
tokens.push({ type: 'paren', value: char })
cur++
continue
}
// 匹配简单操作符~
if (operator.includes(char)) {
// 先这样实现吧~
tokens.push({ type: 'operator', value: char })
cur++
continue
}
// 匹配一部分变量引用 (想了下变量名还是不要以下划线开头了 orz
if (char.match(/[a-zA-Z]/)) {
const str = whileMatchRegexp(/_|[a-zA-Z]|[0-9]/)
tokens.push({ type: 'refrence', value: str })
continue
}
// console.error('not match')
cur++
}
return tokens
}
module.exports = { getTokens }
ast.js
const astTypes = [
// 引用及作用域相关
'Identifier',
'OperatorIdentifier',
'TypeIdentifier',
'ParenIdentifier',
// 关键字定义相关
'ImportDeclaration',
'VariableDeclaration',
// 字面量相关
'StringLiteral',
'IntegerLiteral',
'FloatLiteral',
'BooleanLiteral',
// 表达式相关
'CallExpression',
'MemberExpression',
'ExpressionStatement'
]
const constants = require('./constants')
const { literalTypes } = constants
function parseLiteralType(type) {
return type.slice(0, 1).toUpperCase() + type.slice(1) + 'Literal'
}
function reverseLiteralType(type) {
return type.slice(0, 1).toLowerCase() + type.slice(1, -('Literal'.length))
}
function parseSimpleAST(tokens) {
let nodes = []
let cur = 0
let lastKeyword = ''
function EOF() {
return cur >= tokens.length
}
function whileNextToken(type, value) {
const values = []
function matchToken(token) {
return token.type === type && token.value === value
}
cur++
while (!matchToken(tokens[cur]) && !EOF()) {
values.push(walk())
}
return values.filter(Boolean)
}
function walk() {
let token = tokens[cur]
// 关键字相关代码
if (token.type === 'keyword') {
lastKeyword = token.value
if (token.value === 'import') {
const params = whileNextToken('operator', '\n')
return { type: 'ImportDeclaration', params }
}
if (token.value === 'let') {
const params = whileNextToken('operator', '\n')
return { type: 'VariableDeclaration', params }
}
if (literalTypes.includes(token.value)) {
cur++
return { type: 'TypeIdentifier', value: token.value }
}
// 其余关键字先忽略 = =
cur++
return
}
// 操作符相关代码
if (token.type === 'operator') {
cur++
// 忽略无意义的换行
if (token.value === '\n') {
// 换行时初始化关键字, 感觉并不是很好的做法 orz
lastKeyword = ''
return
}
// 先不考虑操作符二义性的问题 orz
return { type: 'OperatorIdentifier', value: token.value }
}
if (literalTypes.includes(token.type)) {
cur++
const type = parseLiteralType(token.type)
return { type, value: token.value }
}
if (token.type === 'refrence') {
if (['let', 'as'].includes(lastKeyword)) {
cur++
return { type: 'Identifier', value: token.value }
}
// 暂时先以行为单位进行拆分, 太难了 T T
let params = [{ type: 'Identifier', value: token.value }]
params = params.concat(whileNextToken('operator', '\n'))
return { type: 'ExpressionStatement', params }
}
if (token.type === 'paren') {
cur++
// 这样处理估计不太行 = =
return { type: 'ParenIdentifier', value: token.value }
}
console.error('token not match:', token)
cur++
}
while (cur < tokens.length) {
nodes.push(walk());
}
return nodes.filter(Boolean)
}
function parseAST(simpleAST) {
return simpleTraverser(simpleAST, {
Program(node) {
node.body = node.params
delete node.params
},
ImportDeclaration(node, parent) {
node.source = node.params[0]
node.property = node.params[1]
delete node.params
},
VariableDeclaration(node, parent) {
const valueIdx = node.params.findIndex(p =>
p.type === 'OperatorIdentifier' && p.value === '='
)
const typeIdx = node.params.findIndex(p =>
p.type === 'OperatorIdentifier' && p.value === ':'
)
node.property = node.params[0]
node.value = node.params[valueIdx + 1]
if (typeIdx !== -1) {
node.datatype = node.params[typeIdx + 1]
} else {
// 自动的类型推导 orz
const value = reverseLiteralType(node.value.type)
node.datatype = { type: 'TypeIdentifier', value }
}
delete node.params
},
ExpressionStatement(node, parent) {
const parenStart = node.params.findIndex(p =>
p.type === 'ParenIdentifier' && p.value === '('
)
const parenEnd = node.params.findIndex(p =>
p.type === 'ParenIdentifier' && p.value === ')'
)
if (parenStart > -1 && parenEnd > -1) {
node.callee = { type: 'CallExpression' }
node.callee.arguments = node.params.slice(parenStart + 1, parenEnd)
node.callee.property = node.params[0]
delete node.params
return
}
// 函数调用也是很麻烦的事情, 先不管, 这次主要是数据类型 orz
const objIdx = node.params.findIndex(p =>
p.type === 'OperatorIdentifier' && p.value === '.'
)
const calleeIdx = node.params.findIndex(p =>
p.type === 'ExpressionStatement' && p.callee
)
if (objIdx > -1 && calleeIdx > -1) {
node.object = { type: 'MemberExpression', value: node.params[objIdx -1 ]}
node.callee = node.params[calleeIdx].callee
delete node.params
return
}
},
})
}
function simpleTraverser(simpleAST, visitor) {
const root = {
type: 'Program',
params: simpleAST,
}
function traverseArray(array, parent) {
array.forEach(child => {
traverseNode(child, parent)
})
}
function traverseNode(node, parent) {
if (node.params) {
traverseArray(node.params, node)
}
if (node.value) {
traverseNode(node.value, node)
}
const method = visitor[node.type]
if (method) {
method(node, parent)
}
}
traverseNode(root, null)
return root
}
module.exports = { parseAST, parseSimpleAST }