第二章 - 抽象语法树(AST)概述

第二章 - 抽象语法树(AST)概述

关于抽象语法树及其使用方法的介绍

如果想编写能生成代码或分析代码的程序,首先需要理解什么是抽象语法树。AST这个术语听起来可能很吓人,但其实它和HTML的DOM树一样简单。下面我们就用DOM树作为例子,说明树形结构的基本概念。

HTML DOM示例

假设我们有一个基础网页,包含标题和两个带子标题的内容区块。其HTML代码可能如下:

css 复制代码
<body>
  <main>
    <h1>重要标题</h1>
    <div>
      <h2>子标题1</h2>
      <p>附加信息</p>
    </div>
    <div>
      <h2>子标题2</h2>
      <p>其他信息</p>
    </div>
  </main>
</body>

对应的树形结构表示如下:

css 复制代码
body
|-> main
    |-> h1
    |-> div
        |-> h2
        |-> p
    |-> div
        |-> h2
        |-> p

这表示body节点有一个子节点main。main包含三个子节点:h1和两个div。每个div又包含h2和p子节点。这种树结构是源代码的元数据表示,即使省略具体文本内容,也能完整还原代码结构。

HTML的AST有一些规则限制,比如"p节点不能是另一个p节点的子节点"。这类规则定义了节点间的父子关系限制。在其他编程语言中,这类规则会更加重要。

TypeScript/JavaScript等语言也有类似的树形结构,通过AST可以重新生成原始代码。

JavaScript示例

来看一个TypeScript相关的例子,分析下面这个简单的if语句:

erlang 复制代码
if (6 === 7) {
  console.log("wow")
}

对应的AST结构如下:

rust 复制代码
IfStatement
|-> BinaryExpression
    |-> NumericLiteral
    |-> EqualsEqualsEqualsToken
    |-> NumericLiteral
|-> Block
    |-> ExpressionStatement
        |-> CallExpression
            |-> PropertyAccessExpression
                |-> Identifier
                |-> Identifier
            |-> StringLiteral

这个结构看起来复杂,让我们逐步解析:

最顶层的IfStatement包含两个子节点:

  • BinaryExpression表示条件判断部分
  • Block表示花括号{}内的代码块

BinaryExpression包含三个部分:

  • 左右两边的数字字面量6和7(NumericLiteral)
  • 中间的严格相等运算符===(EqualsEqualsEqualsToken)

Block节点包含一个ExpressionStatement,即console.log("wow")。这个表达式语句又包含:

  • CallExpression表示函数调用

    • PropertyAccessExpression表示属性访问console.log

      • 两个Identifier分别是console和log
    • StringLiteral是字符串参数"wow"

通过这个例子,我们完整解析了if语句的AST结构。接下来介绍TypeScript中常见的AST节点类型。

TypeScript AST节点类型

下面介绍TypeScript代码中最常用的节点类型。这不是完整列表,但掌握这些核心类型就能理解大部分AST结构。

注意:本节内容偏技术性,后续章节不会这么枯燥。但这些基础知识很重要。

字面量(Literals)

字面量是最直观的节点类型,表示源代码中的具体值。例如:

javascript 复制代码
const age = 102  // NumericLiteral
const name = "Jason"  // StringLiteral
const regex = /hello/  // RegularExpressionLiteral

其他字面量示例:

csharp 复制代码
const a = 1n  // BigIntLiteral
const b = true  // BooleanLiteral
const c = null  // NullLiteral
const d = {}  // ObjectLiteralExpression
const e = []  // ArrayLiteralExpression

表达式(Expressions)

表达式是能产生值或引起副作用的代码片段。常见类型包括:

scss 复制代码
hello()  // CallExpression
process.env  // PropertyAccessExpression
1 + 2  // BinaryExpression
() => {}  // ArrowFunctionExpression
await hello()  // AwaitExpression
this  // ThisExpression

语句(Statements)

语句表示执行动作的代码单元。与表达式的区别在于:大多数关键字都属于语句,如if、for循环等。所有表达式都可以作为语句,但反之不成立。

示例:

scss 复制代码
const a = 1  // VariableStatement
if(5>9){}  // IfStatement
for(let i in [1,2,3]){}  // ForInStatement
return  // ReturnStatement

声明(Declarations)

声明用于引入新类型或值:

javascript 复制代码
import a from "./a"  // ImportDeclaration
class Human {}  // ClassDeclaration
function d() {}  // FunctionDeclaration
export {}  // ExportDeclaration

注意:const a = 1整体是VariableStatement,但其中的a=1部分是VariableDeclaration。

标识符(Identifiers)

标识符是其他节点的名称:

csharp 复制代码
function hello() {}  // hello是标识符
class Animal {}  // Animal是标识符
const myVar = 7  // myVar是标识符

令牌(Tokens)

令牌表示特殊符号:

arduino 复制代码
=  // EqualsToken
===  // EqualsEqualsEqualsToken
...  // DotDotDotToken
++  // PlusPlusToken

子句(Clauses)

子句用于提升代码可读性:

scala 复制代码
try {} catch {}  // catch是子句
switch中的case/default  // 也是子句
class A extends B {}  // extends B是HeritageClause

关键字(Keywords)

关键字是语言保留的特殊单词:

vbnet 复制代码
async
await
const
class
interface

类型引用(References)

TypeScript特有,用于类型标注:

java 复制代码
type Age = {}
function myFunc(age: Age) {}  // :Age是TypeReference

总结

本章概述了TypeScript抽象语法树的核心概念。我们学习了AST的各种节点类型,为后续的代码分析和转换打下了基础。

相关推荐
修炼的dance15 分钟前
录屏关键帧间隔多少合适?六档实测,2秒到10秒小了75.6%
前端
Lank_M21 分钟前
关键帧从1秒拉到10秒,录屏小87.3%,摄像头小10.8%
前端
补码缩写23 分钟前
用 ffprobe 拆录屏,八成字节在 29 个关键帧上
前端
百思不得小李25 分钟前
一个复杂的动态表单是如何实现的
前端·设计模式
陈随易39 分钟前
Cmder 停更 17 个月,2.0 支持 Windows Terminal
前端·后端·程序员
风骏时光牛马1 小时前
前沿视界
前端
kyriewen1 小时前
我扫了 814 个依赖,97 个还敢要 Node 10
前端·javascript·程序员
陈随易1 小时前
3000 万人在用的翻译插件,重写一遍就是一门生意
前端·后端·程序员
IT_陈寒1 小时前
Vue的这个响应式陷阱,差点让我加班到天亮
前端·人工智能·后端
Flynt3 小时前
别写拖拽行为,只声明"能拖到哪":UIST 最佳论文的拖拽方案我实测了
前端·svg·preact