跳到主要内容

parser.js

parser 功能为生成 AST 抽象语法树

Demo示例一

<h1 id="title"><span>hello</span>world</h1> 生成 AST 抽象语法树

分析

转换规则为:

  • jsxElement: <JSXIdentifier attribute*> child* </JSXIdentifier>
  • attribute: AttributeKey="AttributeStringValue"
  • child: jsxElement | JSXText

实现

const tokenizer = require("./tokenizer");
const tokenTypes = require("./tokenTypes");
const nodeTypes = require("./nodeTypes.md");

function parser(sourceCode) {
const tokens = tokenizer(sourceCode);
let current = 0;
function walk(parent) {
let token = tokens[current];
let nextToken = tokens[current + 1];
if (
token.type === tokenTypes.LeftParentheses &&
nextToken.type === tokenTypes.JSXIdentifier
) {
let node = {
type: nodeTypes.JSXElement,
openingElement: null,
closingElement: null,
children: [],
};
token = tokens[++current];
node.openingElement = {
type: nodeTypes.JSXOpeningElement,
name: {
type: nodeTypes.JSXIdentifier,
name: token.value,
},
attributes: [],
};
token = tokens[++current];
while (token.type === tokenTypes.AttributeKey) {
node.openingElement.attributes.push(walk());
token = tokens[current];
}
token = tokens[++current];
nextToken = tokens[current + 1];
while (
token.type != tokenTypes.LeftParentheses ||
(token.type === tokenTypes.LeftParentheses &&
nextToken.type !== tokenTypes.BackSlash)
) {
node.children.push(walk());
token = tokens[current];
nextToken = tokens[current + 1];
}
node.closingElement = walk(node);
return node;
} else if (token.type === tokenTypes.AttributeKey) {
let nextToken = tokens[++current];
let node = {
type: nodeTypes.JSXAttribute,
name: {
type: nodeTypes.JSXIdentifier,
name: token.value,
},
value: {
type: nodeTypes.Literal,
value: nextToken.value,
},
};
current++;
return node;
} else if (token.type === tokenTypes.JSXText) {
current++;
return {
type: nodeTypes.JSXText,
value: token.value,
};
} else if (
parent &&
token.type === tokenTypes.LeftParentheses &&
nextToken.type === tokenTypes.BackSlash
) {
current++;
current++;
token = tokens[current];
current++;
current++;
if (parent.openingElement.name.name !== token.value) {
throw new TypeError(
`开始标签${parent.openingElement.name.name}和结束标签${token.value}不匹配`
);
}
return {
type: nodeTypes.JSXClosingElement,
name: {
type: nodeTypes.JSXIdentifier,
name: token.value,
},
};
}
throw new TypeError("非法字符");
}
let ast = {
type: nodeTypes.Program,
body: [
{
type: nodeTypes.ExpressionStatement,
expression: walk(),
},
],
};
return ast;
}

module.exports = parser;

测试

const parser = require("./parser");
const traverse = require("./traverse");

const sourceCode = '<h1 id="title"><span>hello</span>world</h1>';
const ast = parser(sourceCode);
console.log(JSON.stringify(ast,null,2))