-
-
Notifications
You must be signed in to change notification settings - Fork 3
/
ast.js
90 lines (71 loc) 路 2.48 KB
/
ast.js
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
/**
* parser/ast.js
*/
function Node(parent, type, text = null) {
this.type = type;
this.parent = parent;
this.children = [];
if (text !== null) this.text = text;
}
/**
* Build an AST from an array of lines according to a set of block definitions
*/
module.exports = function(blocks, lines) {
// Initialize the AST with the root node
const ast = new Node(null, 'document');
let currentNode = ast;
let nbConsEmptyLines = 0;
lines.forEach(function(line, index) {
// Update the count of consecutive empty lines
nbConsEmptyLines = line.length ? 0 : nbConsEmptyLines + 1;
// Generate the context object
const context = {
line, index,
previousLine: index > 0 ? lines[index - 1] : '',
nextLine: index < lines.length - 1 ? lines[index + 1] : '',
nextNextLine: index < lines.length - 2 ? lines[index + 2] : '',
emptyLines: nbConsEmptyLines,
current: currentNode,
parent: currentNode.parent || {},
};
// Check recursively for the possible start of a new child node
let childNodeType;
while ((childNodeType = blocks[currentNode.type].containedBlocks.find(type => blocks[type].start(context))) !== undefined) {
// Create a new child node and add it to the current node
const childNode = new Node(currentNode, childNodeType);
currentNode.children.push(childNode);
// Move to the new child node
currentNode = childNode;
context.current = currentNode;
context.parent = currentNode.parent;
}
// Insert the line into the current node if it's not empty
if (line.length) currentNode.children.push(new Node(currentNode, 'text', line));
// Check recursively for the end of the current node
while (blocks[currentNode.type].end(context)) {
currentNode = currentNode.parent;
context.current = currentNode;
context.parent = currentNode.parent || {};
}
});
return removeUnwrappedTextNodes(removeEmptyNodes(ast));
}
/**
* Cleaners
* =============================================================================
*/
/**
* Remove textual nodes that are direct descendants of the document
*/
function removeUnwrappedTextNodes(ast) {
ast.children = ast.children.filter(childNode => childNode.type != 'text');
return ast;
}
/**
* Remove non-textual nodes with no children
*/
function removeEmptyNodes(node) {
node.children.forEach(childNode => removeEmptyNodes(childNode));
node.children = node.children.filter(childNode => childNode.type == 'text' || childNode.children.length)
return node;
}