Skip to content

Commit ba86f35

Browse files
committed
✨feat: 添加15.6_代码生成
1 parent 124d0cc commit ba86f35

File tree

1 file changed

+383
-0
lines changed

1 file changed

+383
-0
lines changed
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,383 @@
1+
const State = {
2+
initial: 1,
3+
tagOpen: 2,
4+
tagName: 3,
5+
text: 4,
6+
tagEnd: 5,
7+
tagEndName: 6
8+
}
9+
10+
function isAlpha (char) {
11+
return char >= 'a' && char <= 'z' || char >= 'A' && char <= 'Z'
12+
}
13+
14+
function tokenize (str) {
15+
let currentState = State.initial
16+
const chars = []
17+
const tokens = []
18+
while (str) {
19+
const char = str[0]
20+
switch (currentState) {
21+
case State.initial:
22+
if (char === '<') {
23+
currentState = State.tagOpen
24+
str = str.slice(1)
25+
} else if (isAlpha(char)) {
26+
currentState = State.text
27+
chars.push(char)
28+
str = str.slice(1)
29+
}
30+
break
31+
case State.tagOpen:
32+
if (isAlpha(char)) {
33+
currentState = State.tagName
34+
chars.push(char)
35+
str = str.slice(1)
36+
} else if (char === '/') {
37+
currentState = State.tagEnd
38+
str = str.slice(1)
39+
}
40+
break
41+
case State.tagName:
42+
if (isAlpha(char)) {
43+
chars.push(char)
44+
str = str.slice(1)
45+
} else if (char === '>') {
46+
currentState = State.initial
47+
tokens.push({
48+
type: 'tag',
49+
name: chars.join('')
50+
})
51+
chars.length = 0
52+
str = str.slice(1)
53+
}
54+
break
55+
case State.text:
56+
if (isAlpha(char)) {
57+
chars.push(char)
58+
str = str.slice(1)
59+
} else if (char === '<') {
60+
currentState = State.tagOpen
61+
tokens.push({
62+
type: 'text',
63+
content: chars.join('')
64+
})
65+
chars.length = 0
66+
str = str.slice(1)
67+
}
68+
break
69+
case State.tagEnd:
70+
if (isAlpha(char)) {
71+
currentState = State.tagEndName
72+
chars.push(char)
73+
str = str.slice(1)
74+
}
75+
break
76+
case State.tagEndName:
77+
if (isAlpha(char)) {
78+
chars.push(char)
79+
str = str.slice(1)
80+
} else if (char === '>') {
81+
currentState = State.initial
82+
tokens.push({
83+
type: 'tagEnd',
84+
name: chars.join('')
85+
})
86+
chars.length = 0
87+
str = str.slice(1)
88+
}
89+
break
90+
}
91+
}
92+
93+
return tokens
94+
}
95+
96+
function parse (str) {
97+
const tokens = tokenize(str)
98+
99+
const root = {
100+
type: 'Root',
101+
children: []
102+
}
103+
const elementStack = [root]
104+
105+
while (tokens.length) {
106+
const parent = elementStack[elementStack.length - 1]
107+
const t = tokens[0]
108+
switch (t.type) {
109+
case 'tag':
110+
const elementNode = {
111+
type: 'Element',
112+
tag: t.name,
113+
children: []
114+
}
115+
parent.children.push(elementNode)
116+
elementStack.push(elementNode)
117+
break
118+
case 'text':
119+
const textNode = {
120+
type: 'Text',
121+
content: t.content
122+
}
123+
parent.children.push(textNode)
124+
break
125+
case 'tagEnd':
126+
elementStack.pop()
127+
break
128+
}
129+
tokens.shift()
130+
}
131+
132+
return root
133+
}
134+
135+
function traverseNode (ast, context) {
136+
context.currentNode = ast
137+
138+
const exitFns = []
139+
const transforms = context.nodeTransforms
140+
for (let i = 0;i < transforms.length;i++) {
141+
const onExit = transforms[i](context.currentNode, context)
142+
if (onExit) {
143+
exitFns.push(onExit)
144+
}
145+
if (!context.currentNode) return
146+
}
147+
148+
const children = context.currentNode.children
149+
if (children) {
150+
for (let i = 0;i < children.length;i++) {
151+
context.parent = context.currentNode
152+
context.childIndex = i
153+
traverseNode(children[i], context)
154+
}
155+
}
156+
157+
let i = exitFns.length
158+
while (i--) {
159+
exitFns[i]()
160+
}
161+
}
162+
163+
164+
function transform (ast) {
165+
const context = {
166+
currentNode: null,
167+
parent: null,
168+
replaceNode (node) {
169+
context.currentNode = node
170+
context.parent.children[context.childIndex] = node
171+
},
172+
removeNode () {
173+
if (context.parent) {
174+
context.parent.children.splice(context.childIndex, 1)
175+
context.currentNode = null
176+
}
177+
},
178+
nodeTransforms: [
179+
transformRoot,
180+
transformElement,
181+
transformText
182+
]
183+
}
184+
// 调用 traverseNode 完成转换
185+
traverseNode(ast, context)
186+
}
187+
188+
189+
190+
191+
192+
// =============================== AST 工具函数 ===============================
193+
194+
function createStringLiteral (value) {
195+
return {
196+
type: 'StringLiteral',
197+
value
198+
}
199+
}
200+
201+
function createIdentifier (name) {
202+
return {
203+
type: 'Identifier',
204+
name
205+
}
206+
}
207+
208+
function createArrayExpression (elements) {
209+
return {
210+
type: 'ArrayExpression',
211+
elements
212+
}
213+
}
214+
215+
function createCallExpression (callee, arguments) {
216+
return {
217+
type: 'CallExpression',
218+
callee: createIdentifier(callee),
219+
arguments
220+
}
221+
}
222+
223+
// =============================== AST 工具函数 ===============================
224+
225+
function transformText (node) {
226+
if (node.type !== 'Text') {
227+
return
228+
}
229+
230+
node.jsNode = createStringLiteral(node.content)
231+
}
232+
233+
234+
function transformElement (node) {
235+
236+
return () => {
237+
if (node.type !== 'Element') {
238+
return
239+
}
240+
241+
const callExp = createCallExpression('h', [
242+
createStringLiteral(node.tag)
243+
])
244+
node.children.length === 1
245+
? callExp.arguments.push(node.children[0].jsNode)
246+
: callExp.arguments.push(
247+
createArrayExpression(node.children.map(c => c.jsNode))
248+
)
249+
250+
node.jsNode = callExp
251+
}
252+
}
253+
254+
function transformRoot (node) {
255+
return () => {
256+
if (node.type !== 'Root') {
257+
return
258+
}
259+
260+
const vnodeJSAST = node.children[0].jsNode
261+
262+
node.jsNode = {
263+
type: 'FunctionDecl',
264+
id: { type: 'Identifier', name: 'render' },
265+
params: [],
266+
body: [
267+
{
268+
type: 'ReturnStatement',
269+
return: vnodeJSAST
270+
}
271+
]
272+
}
273+
}
274+
}
275+
276+
const ast = parse(`<div><p>Vue</p><p>Template</p></div>`)
277+
transform(ast)
278+
279+
console.log(ast)
280+
281+
console.log(generate(ast.jsNode))
282+
283+
// ============================ code generate ============================
284+
285+
function generate (node) {
286+
const context = {
287+
code: '',
288+
push (code) {
289+
context.code += code
290+
},
291+
currentIndent: 0,
292+
newline () {
293+
context.code += '\n' + ` `.repeat(context.currentIndent)
294+
},
295+
indent () {
296+
context.currentIndent++
297+
context.newline()
298+
},
299+
deIndent () {
300+
context.currentIndent--
301+
context.newline()
302+
}
303+
}
304+
305+
genNode(node, context)
306+
307+
return context.code
308+
}
309+
310+
function genNode (node, context) {
311+
switch (node.type) {
312+
case 'FunctionDecl':
313+
genFunctionDecl(node, context)
314+
break
315+
case 'ReturnStatement':
316+
genReturnStatement(node, context)
317+
break
318+
case 'CallExpression':
319+
genCallExpression(node, context)
320+
break
321+
case 'StringLiteral':
322+
genStringLiteral(node, context)
323+
break
324+
case 'ArrayExpression':
325+
genArrayExpression(node, context)
326+
break
327+
}
328+
}
329+
330+
function genFunctionDecl (node, context) {
331+
const { push, indent, deIndent } = context
332+
333+
push(`function ${ node.id.name } `)
334+
push(`(`)
335+
genNodeList(node.params, context)
336+
push(`) `)
337+
push(`{`)
338+
indent()
339+
340+
node.body.forEach(n => genNode(n, context))
341+
342+
deIndent()
343+
push(`}`)
344+
}
345+
346+
function genNodeList (nodes, context) {
347+
const { push } = context
348+
for (let i = 0;i < nodes.length;i++) {
349+
const node = nodes[i]
350+
genNode(node, context)
351+
if (i < nodes.length - 1) {
352+
push(', ')
353+
}
354+
}
355+
}
356+
357+
function genReturnStatement (node, context) {
358+
const { push } = context
359+
360+
push(`return `)
361+
genNode(node.return, context)
362+
}
363+
364+
function genCallExpression (node, context) {
365+
const { push } = context
366+
const { callee, arguments: args } = node
367+
push(`${ callee.name }(`)
368+
genNodeList(args, context)
369+
push(`)`)
370+
}
371+
372+
function genStringLiteral (node, context) {
373+
const { push } = context
374+
375+
push(`'${ node.value }'`)
376+
}
377+
378+
function genArrayExpression (node, context) {
379+
const { push } = context
380+
push('[')
381+
genNodeList(node.elements, context)
382+
push(']')
383+
}

0 commit comments

Comments
 (0)