实现解析插值表达式

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
 )