/
fullgen.js
147 lines (124 loc) · 4.02 KB
/
fullgen.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
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
var xjst = require('../../xjst'),
utils = xjst.utils;
//
// ### function Merger ()
// Merge common tree parts and identify them
//
function Merger() {
this.cache = {};
this.idCounter = 1;
}
//
// ### function merge (obj)
// #### @obj {Object} part of AST
// Searches cache for obj and for all it's successors
// If found - returns cached object instead of original
// If not - pushes object to cache and returns it
//
Merger.prototype.merge = function merge(obj) {
var self = this,
hash,
size;
if (obj['switch']) {
hash = ['switch ', JSON.stringify(obj['switch']), '{'];
size = 0;
obj.cases.forEach(function (c){
c[1] = self.merge(c[1]);
size += c[1].size;
hash.push(c[1].hash, ' ');
});
obj['default'] = self.merge(obj['default']);
size += obj['default'].size;
hash.push(obj['default'].hash, '}');
hash = utils.sha1(hash.join(''));
} else {
var json = JSON.stringify(obj.stmt);
hash = obj.hash || utils.sha1(json);
// XXX Strange Euristics
size = json.length / 3200;
}
obj.size = size;
if(this.cache[hash] !== undefined) {
obj.id = this.cache[hash].id;
} else {
this.cache[obj.hash = hash] = obj;
obj.id = this.idCounter++;
}
if (obj.tag) obj.id = obj.tag;
return this.cache[hash];
};
//
// ### function engine (templates, options, values)
// #### @templates {Array} AST
// #### @options {Object} Compiler options
// #### @values {Object} Hash map of values of each each predicate
// Returns optimized tree (via fullgen algorithm)
//
module.exports = function engine(templates, options, values) {
var merger = new Merger(),
merge = merger.merge.bind(merger),
unique = new utils.Identifier();
function addNode(node, state) {
if (node.state) {
node.state.merge(state);
} else {
node.state = state.clone();
}
if (options.merge && node['switch']) node.longId = node.state.hash();
return node;
}
function traverse(i, j, state) {
var template = templates[i];
// If we stepped out of templates - we're in unexpected place
// throw exception
if (!template) {
return addNode(
merge({
tag: 'unexpected',
fn: true,
stmt: ['throw', ['new', 'Error']]
}),
state
);
}
// If we stepped out of predicates - add template's body to tree
var subMatch = template[0][j];
if (!subMatch) return addNode(merge({ stmt: template[1] }), state);
// Skip unreachable templates
// template (p1 === c1 && p2 === c2)
var known = template[0].slice(j + 1).some(function(s) {
var predicate = utils.stringify(s[1]),
predicateConst = s[2];
return state.has(predicate) &&
!state.has(predicate, utils.stringify(predicateConst));
});
if (known) return traverse(i + 1, 0, state);
var predicate = utils.stringify(subMatch[1]),
predicateConst = subMatch[2];
// If we already know value of this predicate
if (state.has(predicate)) {
// And if current template's predicate value equals to known
if (state.has(predicate, utils.stringify(predicateConst))) {
// Skip this predicate and go further
return traverse(i, j + 1, state);
} else {
// Skip whole template as it's unreachable
return traverse(i + 1, 0, state);
}
} else {
// Create switch for current predicate and all known values of it
var result = {};
result['switch'] = subMatch[1];
result.cases = values[subMatch[0]].map(function(v) {
return [v, traverse(i, j,
xjst.state.create(state, predicate,
utils.stringify(v)))];
});
result['default'] = traverse(i, j, xjst.state.create(state,
predicate,
-unique.generate()));
return addNode(merge(result), state);
}
}
return traverse(0, 0, xjst.state.create({ values: values }));
};