词法分析
首先我们知道其实很多地方都会使用到词法分析,比如这里举一个典型的例子,浏览器的渲染过程。
我们会先去解析html,当遇到javascript的时候就会停止解析html,这里在解析html的时候会生成一颗dom树,因为浏览器是不认识html的内容的,需要把他转化成浏览器能够理解的 DOM 树。那这里js是可以操作DOM元素的,那js又是谁去执行的呢?
这里就要提到目前最强大的引擎v8引擎了。他会对js进行一个parse阶段形成一颗AST抽象树。这里我们先来说一下parse阶段,这个parse阶段的最主要过程就是词法分析和语法分析。
什么是词法分析
词法分析:他的主要工作就是是将一个长长的字符串识别出一个个的单词,这一个个单词就是 Token。这里有一个概念就叫做状态机,这个状态机是什么呢,我们先来举一个例子
状态迁移
比如 let a=function()
1.我们一开始会读到这个let,读到这个let的时候我们就大概是知道应该是赋值操作
2.那接下来又读到a这个变量,那这个时候这个状态就转移到这个变量这里,当然这只是其中一种操作
3.接下来我们读到这个=这里,状态就转移到后面的这个函数,那如果后面跟的是字符串,或者数字这些,状态就会转移到对应的,那这里就能明确是一个函数的赋值操作
总体来说就是他会用一个状态机来记录你这段代码做了什么。这里我们可以具体来看一下词法分析的过程
词法分析详解
(add 2 (subtract 4 2)) 2+(4-2) 那我们以解析2+(4-2)为例
1.首先定义了一个结果类,收集最终的解析结果;其中的 TokenType
就对应了图中的三种状态,简单的用枚举值来表示。
export enum TokenTypes {
Paren,
Name,
Number
}
2.再定义一下每一个token的类型以及值
export interface Token {
type: TokenTypes
value: string
}
3.那么我们通过定义一个指针current依次去取出code里面的值进行判断,那么这里的核心内容如下
a:一开始我们匹配到的是'('这个状态1,就将其类型以及value值作为键值对的方式放进定义的token数组里。使其指针加一,进行下一次判断。这里有一个需要注意的是,空格应该是不算在判断里面的,所以我们需要对空格进行一个处理。
// 通过正则来判断是否是空格
const WHITESPACE = /\s/
if (WHITESPACE.test(char)) {
current++
continue
}
if (char === "(") {
tokens.push({
type: TokenTypes.Paren,
value: char
})
current++
continue
}`
b.接下来匹配到的是一个标识符a,那就会由上一步的状态1迁移到状态2 那这里该如何判断呢?
我们可以使用正则来判断, const LETTERS = /[a-z]/i。这里需要注意的是他跟上一个不同的是,他不是判断一个就结束了,他需要判断到下一个字符不是字符的时候才结束,并将其放入tokens里面,所以这里我们需要在里面去循环到遇到下一个不是字符的时候,那在没遇到之前,就会一直保持在这个状态2,一直到后续发现下一个不是a到z的标识符,并继续接下来的状态转移判断。
// 通过正则来判断是否是字母a~z
const LETTERS = /[a-z]/i
if (LETTERS.test(char)) {
let value = ''
while (LETTERS.test(char) && current < code.length) {
value += char
char = code[current++]
}
tokens.push({
type: TokenTypes.Name,
value: value
})
}
c.那么针对以上,接下来的判断比如数字,右括号就跟上面类似,这就实现了一个简单的词法分析的过程 那么完整代码可以参考一下。
总结
所以总结一下:词法分析就是去读取每一个字符或者是一段字符,然后不断的进行状态转移,每一个状态转移都是一个函数,我们需要根据读取的是什么写出对应的状态函数,写完了之后将每一个转成token的形式,里面是键值对的形式放到数组里面,之后就是将词法分析拿到的tokens转成ast树,也就是我们接下来要说的语法分析
`
export enum TokenTypes {
Paren,
Name,
Number,
String
}
export interface Token {
type: TokenTypes
value: string
}
export function tokenizer(code: string) {
const tokens: Token[] = []
//指针
let current = 0
while (current < code.length) {
let char = code[current]
// 通过正则来判断是否是空格
const WHITESPACE = /\s/
if (WHITESPACE.test(char)) {
current++
continue
}
if (char === "(") {
tokens.push({
type: TokenTypes.Paren,
value: char
})
current++
continue
}
if (char === ")") {
tokens.push({
type: TokenTypes.Paren,
value: char
})
current++
continue
}
// 通过正则来判断是否是字母a~z
const LETTERS = /[a-z]/i
if (LETTERS.test(char)) {
let value = ''
while (LETTERS.test(char) && current < code.length) {
value += char
char = code[++current]
}
tokens.push({
type: TokenTypes.Name,
value: value
})
}
// 通过正则来判断是否是数字0~9
const NUMBERS = /[0-9]/
if (NUMBERS.test(char)) {
let value = ''
while (NUMBERS.test(char) && current < code.length) {
value += char
char = code[++current]
}
tokens.push({
type: TokenTypes.Number,
value: value
})
}
}
return tokens
}
`