语法分析器实现

  • 输入 词法分析所得 Tokens 数组
  • 输出 语法树
  • 术语
    • 终止符 Terminal // 形式语言必须完全由终止符组成!
    • 非终止符 Non-Terminal
    • LL 与 LR
    • 递归下降

递归下降的 LL 算法

  1. 从语法树顶部的根节点(注意不是首个 Token)开始,向前【匹配非终止符】
  2. 每个【匹配非终止符】的过程,都是调用一个函数的过程。若非终止符 X 中又需要匹配该非终止符,此时需再次调用自身以向下匹配该类型,即【递归下降】
  3. 当所有的 Token 都被解析后,返回匹配后的原始语法树
  4. 隐式构造语法树,适合手写 Parser

递归下降实例

  • 输入 (())
  • 语法
    • A → (B)
    • B → A
    • B → null

设计输入格式

  • TagOpen - div
  • Value - xxx
  • TagClose - div
  • Tag - TagOpen Value TagClose
  • Tags - Tag Tag…

实现

词法分析暴露出来的是一个 parse 的函数,输入的是 tokens 数组,然后调用自身关键 NT.html 方法处理整个 tokens 数组,通过 tag、tags 函数的互相调用 。在函数互相调用的过程中,一些 match('TagOpen') 开始符、match('TagClose') 终止符的函数回去不断的匹配。最终构造出一棵语法树。

  1. 定义 tokens 的 Mock 数据,这个数据在上一节的词法分析中生成。
let t = [
  { type: 'TagOpen', val: '<p>' },
    { type: 'Value', val: 'hello' },
  { type: 'TagClose', val: '</p>' },

  { type: 'TagOpen', val: '<div>' },

    { type: 'TagOpen', val: '<h2>' },
      { type: 'TagOpen', val: '<small>' },
        { type: 'Value', val: 'world' },
      { type: 'TagClose', val: '</small>' },
    { type: 'TagClose', val: '</h2>' },

    { type: 'TagOpen', val: '<span>' },
      { type: 'Value', val: '!' },
    { type: 'TagClose', val: '</span>' },

  { type: 'TagClose', val: '</div>' }
]
  1. 参考代码
let tokens = t;
let currIndex = 0;
let lookahead = tokens[currIndex];

function nextToken () {
  return tokens[++currIndex]
}

function match (terminalType) {
  if (lookahead && terminalType === lookahead.type) lookahead = nextToken()
  else throw Error('SyntaxError')
}

function tag(currNode){
  match('TagOpen')
  if (lookahead.type === 'TagOpen') {
    currNode = NT.tags(currNode)
  } else {
    currNode.val = lookahead.val
    match('Value')
  }
  match('TagClose')
  return currNode
}

function tags(currNode){
  while (lookahead) {
    let tagNode = { type: lookahead.val, val: null, children: [] }
    tagNode = NT.tag(tagNode)

    currNode.children.push(tagNode)
    if (lookahead && lookahead.type === 'TagClose') break
  }
  return currNode
}

function parse(){
  let dom = { type: 'html', val: null, children: [] }
  return NT.tags(dom)
}

let dom = parse();

在 tags 中初始化对 currNode 做处理,其实就是初始化 的 dom ,一个普通的对象,它的类型是 html ,没有任何的值,也没有 children。

{ type: 'html', val: null, children: [] }

lookahead ,是当前每次语法分析器,下标所处的 token 。如果有 token ,如果有我们调用 token 的匹配规则。初始化 tagNode

let tagNode = { type: lookahead.val, val: null, children: [] }

并且对 tagNode ,进行

  1. 输出
dom = {
  type: 'html',
  val: null,
  children: [
    {
      type: '<p>',
      val: 'hello',
      children: []
    },
    {
      type: '<div>',
      val: null,
      children: [
        {
          type: '<h2>',
          val: null,
          children: [
            {
              type: '<small>',
              val: 'world',
              children: []
            }
          ]
        },
        {
          type: '<span>',
          val: '!',
          children: []
        }
      ]
    }
  ]
}