Skip to content

Latest commit

 

History

History
170 lines (137 loc) · 4.16 KB

37. 实现解析插值表达式.md

File metadata and controls

170 lines (137 loc) · 4.16 KB

实现解析差值表达式

1. 测试样例

最终,我们将会让这个测试通过

test('simple interpolation', () => {
    const interpolationStr = '{{message}}'
    const ast = baseParse(interpolationStr)
    expect(ast.children[0]).toStrictEqual({
        type: 'interpolation',
        content: {
            type: 'simple_expression',
            content: 'message',
        },
    })
})
  • 接收一个字符串 {{message}}
  • 返回一个 ast

2. 实现

2.1 伪实现

我们首先可以将代码的功能分割为多个模块,先快速通过测试

// parse.ts

// 导出函数
export function baseParse(content: string) {
  const context = createContext(content)
  return createRoot(parseChildren(context))
}

// 创建上下文
function createContext(content: string) {
  return {
    source: content,
  }
}

// 创建 ast 根节点
function createRoot(children) {
  return {
    children,
  }
}

// 创建 children
function parseChildren(context: { source: string }): any {
  const nodes: any = []
  let node
  // 如果 context.source 以 {{ 开始
  if (context.source.startsWith('{{')) {
    node = parseInterpolation(context)
  }
  nodes.push(node)
  return [node]
}

// 解析插值表达式
function parseInterpolation(context: { source: string }) {
  return {
    type: 'interpolation',
    content: {
      type: 'simple_expression',
      content: 'message',
    },
  }
}
  • 将功能进行分层
  • 最终在 parseInterpolation 函数中进行解析插值

2.2 具体实现

目前,我们需要将 {{message}}中的 message 抽离出来,可以使用字符串的截取功能

// 将字符串截取为 message}}
const closeIndex = context.source.indexOf('}}', 2)
// 然后将字符串前面的 {{ 舍弃掉,我们将其称之为【推进】
context.source = context.source.slice(2)
// 获取到 {{}} 中间值的长度
const rawContentLength = closeIndex - 2
// 并将中间这个值获取出来
const content = context.source.slice(0, rawContentLength)
// 继续【推进】
context.source = context.source.slice(rawContentLength + 2)
  • 最终,我们可以通过 content 来获取到值

2.3 重构

1. 抽离字符串

在这一步,我们要将 {{ }} 抽离为具有语义化的字符串

const openDelimiter = '{{'
const closeDelimiter = '}}'
const closeIndex = context.source.indexOf(closeDelimiter, openDelimiter.length)
context.source = context.source.slice(openDelimiter.length)
const rawContentLength = closeIndex - closeDelimiter.length
const content = context.source.slice(0, rawContentLength)
context.source = context.source.slice(
    rawContentLength + closeDelimiter.length
)

2. 抽离推进逻辑

我们可以将推进的逻辑也抽离出来

 const openDelimiter = '{{'
 const closeDelimiter = '}}'
 const closeIndex = context.source.indexOf(
     closeDelimiter,
     openDelimiter.length
 )
+ advanceBy(context, openDelimiter.length)
- context.source = context.source.slice(openDelimiter.length)
 const rawContentLength = closeIndex - openDelimiter.length
 const content = context.source.slice(0, rawContentLength)
+ advanceBy(context, rawContentLength + closeDelimiter.length)
- context.source = context.source.slice(
-     rawContentLength + closeDelimiter.length
- )


+ function advanceBy(context, length: number) {
+   context.source = context.source.slice(length)
+ }

3. 抽离 AST Node 类型

// ast.ts
export const enum NodeType {
  INTERPOLATION,
  SIMPLE_EXPRESSION,
}

2.4 边缘情况

在这一块,我们需要修复一个边缘情况,在这里加入我们的插值表达式中存在空格,测试就会出现问题了,我们需要修复一下:

 const openDelimiter = '{{'
 const closeDelimiter = '}}'
 const closeIndex = context.source.indexOf(closeDelimiter, openDelimiter.length)
 context.source = context.source.slice(openDelimiter.length)
 const rawContentLength = closeIndex - closeDelimiter.length
-const content = context.source.slice(0, rawContentLength)
+const rawContent = context.source.slice(0, rawContentLength)
+const content = rawContent.trim()
 context.source = context.source.slice(
     rawContentLength + closeDelimiter.length
 )