【vue设计与实现】编译器 - 构造AST

实际上,不同用途的编译器之间可能会存在非常大的差异。它们唯一的共同点是,都会将代码转换成目标代码。
为Vue.js的模板构造AST是一件很简单的事。HTML是一种标记语言,它的格式非常固定。标签元素之间天然嵌套,形成父子关系。因此,一棵用于描述HTML的AST将拥有与HTML标签非常相似的树型结构。举例来说,假设有如下模板:

<div><p>Vue</p><p>Template</p></div>

我们可以将这段模板对应的AST设计为:

const ast = {
	// AST的逻辑根节点
	type: 'Root',
	children: [
		{
			type: 'Element',
			tag: 'div',
			children: [
				//div 节点的第一个子节点 p
				{
					type: 'Element',
					tag: 'p',
					children: [
						{
							type: 'Text',
							tag: 'Vue',
						}
					]
				},
				//div 节点的第二个子节点 p
				{
					type: 'Element',
					tag: 'p',
					children: [
						{
							type: 'Text',
							tag: 'Template',
						}
					]
				},
			]
		}
	]
}

可看到,AST在结构上与模板是“同构”的,它们都具有树型结构,

了解了AST的结构,接下来的任务是,使用程序根据模板解析后生成的Token构造出这样一棵AST。
首先,使用 tokenize 函数将本节开头给出的模板进行标记化。解析这段模板得到的 tokens如下所示:

const tokens= tokenize( `<div><p>Vue</p><p>Template</p></div>`)

执行上面这段代码,我们将得到如下 tokens:

const tokens = [
	{type: "tag",name: "div"},
	{type: "tag",name: "p"},
	{type: "text",name: "Vue"},
	{type: "tagEnd",name: "p"},
	{type: "tag",name: "p"},
	{type: "text",name: "Template"},
	{type: "tagEnd",name: "p"},
	{type: "tagEnd",name: "div"},
]

根据Token列表构建AST的过程,其实就是对 Token列表进行扫描的过程。从第一个Token开始,顺序地扫描整个 Token列表,直到列表中的所有 Token处理完毕。在这个过程中,我们要维护一个栈elementstack,这个栈将用于维护元素间的父子关系。每遇到一个开始标签节点,就构造一个Element类型的AST节点,并将其压人栈中。类似地,每当遇到一个结束标签节点,我们就将当前栈顶的节点弹出。这样,栈顶的节点将始终充当父节点的角色。扫描过程中到的所有节点,都会作为当前栈顶节点的子节点,并添加到栈顶节点的 children 属性下。

扫描Token列表并构建AST的具体实现如下:

//parse函数接收模板作为参数
function parse(str){
	// 首先对模板进行标记化,得到 tokens
	const tokens = tokenize(str)
	//创建Root根节点
	const root = {
		type: 'Root',
		children: []
	}
	//创建 elementStack 栈,起初只有Root 根节点
	const elementStack = [root]

	// 开启一个 while循环扫描 tokens,直到所有 Token 都被扫描完毕为止
	while(tokens.length){
		// 获取当前栈顶节点作为父节点 parent
		const parent = elementStack[elementStack.length - 1]
		//当前扫描的 Token
		const t = tokens[0]
		switch(t.type){
			case 'tag':
				//如果当前 Token 是开始标签,则创建 ELement 类型的 AST 节点
				const elementNode = {
					type: 'Element',
					tag: t.name,
					children: []
				}
				// 将其添加到父级节点的 children 中
				parent.children.push(elementNode)
				// 将当前节点压入栈
				elementStack.push(elementNode)
				break
			case 'text':
				// 如果当前 Token 是文本,则创建 Text 类型的 AST 节点
				const textNode = {
					type: 'Text',
					content: t.content
				}
				// 将其添加到父节点的 children 中
				parent.children.push(textNode)
				break
			case 'tagEnd':
				// 遇到结束标签,将栈顶节点弹出
				elementStack.pop()
				break
		}
		// 消费已经扫描过的 token
		tokens.shift()
	}
	//最后返回AST
	return root
}

这里只是展示思路,其实还有很多问题没有处理,后面会完善。

  • 1
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 1
    评论
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值