实现解析插值表达式
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
)
