Acorn是什么?
Acorn 是一个用 JavaScript 编写的解析器,专门用于将源代码解析为抽象语法树(Abstract Syntax Tree,AST)。
它是一个轻量级、高性能的解析器,被广泛应用于许多 JavaScript 工具和框架中。
Acorn的整体工作流程
- 输入源代码(Input Source Code):接收 JavaScript 源代码作为输入。
- 词法分析(Lexical Analysis):Acorn 使用有限状态机将源代码拆分成一个个标记(tokens),包括关键字、标识符、运算符等。它会跳过空白字符,并处理字符串和注释等特殊情况。
- 语法分析(Syntax Parsing):使用递归下降解析器,Acorn 根据 JavaScript 语言规范定义的文法规则对标记序列进行语法分析。它逐个检查每个标记,并根据上下文判断其属于哪种语句或表达式类型。
- AST 构建与生成:通过解析器中内部数据结构来构建抽象语法树(AST)。AST 是以 JSON 形式表示源码结构和元素之间关系的数据结构。在这一步骤中,Acorn 将每个标记转换成相应的 AST 节点,并按照其在源代码中出现的顺序组织起来。
- 可选阶段:除了基本的词法和语义分析之外,在需要更复杂转换或静态分析时,可能还包含其他额外操作。例如,可以在这个阶段执行自定义的访问者模式,对 AST 进行进一步处理或生成新的代码。
Acorn - Parser Class 核心类
Parser类是 Acorn 解析器的核心部分
核心代码示例:
https://github.com/acornjs/acorn/tree/master/acorn/src 源码仓库
export class Parser {
constructor(options, input, startPos) {
//...构造函数
}
parse() {
// ...执行解析操作
}
// 通过读取当前变量作用域(currentVarScope)以及相关标记位(flags)
// 判断当前上下文所处的环境状态。
get inFunction() { return (this.currentVarScope().flags & SCOPE_FUNCTION) > 0 }
get inGenerator() { return (this.currentVarScope().flags & SCOPE_GENERATOR) > 0 }
get inAsync() { return (this.currentVarScope().flags & SCOPE_ASYNC) > 0 }
get allowSuper() { return (this.currentThisScope().flags & SCOPE_SUPER) > 0 }
get allowDirectSuper() { return (this.currentThisScope().flags & SCOPE_DIRECT_SUPER) > 0 }
get treatFunctionsAsVar() { return this.treatFunctionsAsVarInScope(this.currentScope()) }
get inNonArrowFunction() { return (this.currentThisScope().flags & SCOPE_FUNCTION) > 0 }
static extend(...plugins) {
// ...允许扩展 Parser 类
}
// 解析入口
// 在实例化 Parser 对象后调用其 parse 方法并返回结果
static parse(input, options) {
return new this(options, input).parse()
}
// 在指定位置 pos 解析表达式
static parseExpressionAt(input, pos, options) {
// 创建一个新的 Parser 实例对象
let parser = new this(options, input, pos)
// 调用 nextToken 来获取下一个 token
parser.nextToken()
// 调用 parseExpression 进行表达式解析
return parser.parseExpression()
}
// 分词入口 词法分析器
static tokenizer(input, options) {
return new this(options, input)
}
}
分词 Tokenization(上述流程词法分析阶段)
分词(Tokenization)是Acorn编译的一个关键步骤,核心目标是将源代码字符串转换为一个个具有语义信息的标记(tokens)。而tokens,也就是上述流程中的词法分析的运行结果。
状态机
对每个字符意义进行状态机的判断,状态机即是用来描述在分词过程中,如何根据当前字符和之前的上下文转换到下一个状态。
状态机在分词阶段做了什么?
- 读取字符:从输入源码中逐个读取字符。
- 状态转移和匹配规则:
标识符(Identifier):
如果当前字符是字母或下划线,则进入标识符状态,并持续读取后续的字母、数字或下划线以构成完整的标识符。如果遇到其他类型的字符,结束标识符并生成相应的 Token。
数字(Numeric):
如果当前字符是数字,则进入数字状态,并持续读取后续的数字和小数点以构成完整的数值常量。同时记录是否为科学计数法形式。遇到其他类型的字符,结束数字并生成相应 Token。
字符串(String):
如果当前字符是引号(单引号或双引号),则进入字符串状态,并持续读取直到遇到匹配引号为止。期间还需要处理转义序列等特殊情况。
注释(Comment):
如果当前字符是斜杠 / ,则进行注释检测。如果接下来连续两个斜杠 // 或者一个斜杠加上星号 /*则进入注释处理过程,直至遇到对应结束符 */ 为止。
运算符和标点符号(Operator and Punctuation):
根据当前字符的特定运算符或标点符号,直接生成相应 Token,并结束状态。 其他情况:如果遇到无法匹配上述规则的字符,则报告错误并结束状态。
对分词结果tokens进行分析(上述流程语法分析阶段)
依据 JavaScript 语言规范中定义的文法规则,检查每个标记并确定其属于哪种语句或表达式类型。在这一过程中,Parser利用上下文信息来判断正确的结构,并生成相应节点。
Acorn 在语法分析阶段使用了递归下降解析器(Recursive Descent Parser)的技术,它是一种自顶向下、从左到右递归地进行语法分析的方法。用于将输入代码序列转换为抽象语法树(Abstract Syntax Tree,AST)
1. 初始化:
设置初始状态和其他必要变量。
2. 标记流读取:
逐个读取词法分析器生成的标记,并记录当前位置信息。
3. 语法规则匹配与处理:
根据 JavaScript 语言的文法规范,在遇到不同类型的标记时应用相应的文法规则。
例如:
表达式解析:处理各种表达式,如算术表达式、赋值表达式、函数调用等。
声明解析:处理变量声明、函数声明、类声明等。
控制流解析:处理条件语句、循环结构等控制流程相关内容。
4. AST 构建与节点生成:
根据匹配到的语法规则,在相应位置上创建对应类型的 AST 节点,并填充相关信息。
每个节点通常包含一个或多个子节点来表示嵌套关系和操作符优先级等细节。
5. 错误检测与报告:
如果遇到无效或意外输入,则进行错误检测并报告相应的语法错误。这些错误可能包括缺失分号、未定义变量等。
6. AST 返回:
当整个标记流处理完成时,将生成的 AST 返回给调用者。