词法分析

193 阅读4分钟

词法分析

首先我们知道其实很多地方都会使用到词法分析,比如这里举一个典型的例子,浏览器的渲染过程。

image.png 我们会先去解析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
}
`