| 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
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253 |
6x
6x
6x
6x
6x
66x
38x
70x
14x
38x
38x
38x
38x
38x
38x
38x
221x
221x
228x
228x
220x
220x
183x
220x
228x
228x
228x
228x
219x
9x
9x
9x
| import TransformationContext from './transformationContext';
import Transformation from './transformation';
import ScopeTraverser from './scopetraverser';
import Node from '../parser/nodes/node';
import ASTTool from './asttool';
import t from '../parser/nodes';
/**
* Takes an AST and transforms it according to a series of transformations
*
* This is a generic class, reference VSLTransfomer for a VSL-specific
* implementation
*
* ### Overview
* This takes a series of "passes" which are applied to the AST, each "pass"
* would return a new AST node which would be replaced (if specified as so).
* This is now excluded and further matching AST nodes would be applied
*
* ### Usage
*
* A `Transformer` can be subclassed (see {@link VSLTransformer}) with a no-args
* providing this class with the applicable transformers. This class can also
* be directly constructed with the applicable constructors with no difference.
* A subclass of `Transformer` should not offer any significant interface not
* specified by `Transformer` as internal structure might rapidly change.
*
* Note: If you're in the REPL and want to just load everything. Just run:
*
* var VSLParser = require('./lib/vsl/parser/vslparser.js');
* var p = new VSLParser(); var ast = p.feed("1 + 1");
* var VSLTransformer = require('./lib/vsl/transform/vsltransformer');
* var t = new VSLTransformer();
*
* Additionally, it is reccomended to use `Transformer#queue` to initially load
* the AST.
*
* ### Details
* In practice, each AST Node is treated like a `Node**`, when a node is swapped
* the other transformers will mutate it if applicable. This prevents infinite
* recursion at the end, the node will be requeued for processing, it's children
* will be in turn verified for applicable transformations. This means a node should not rely on transformation order for any reason,
* transformations may be parallelized and therefore should also not have any
* side-effects and be thread safe, concurrency may or may not be implemented in
* any specific way.
*
* ### Scoping
* Handles scoping. This resolves identifiers to an extent. To locate an item
* a search will take a non-constant time complexity but declarations can be
* used in terms of indexing. If an \\(O(1 + \lg{n})\\) does not resolve our item then
* we will check if:
*
*
* $$\exists n\in S :n \geq (V : n)$$
*
* Where `S` is the dynamic processed scope \\( (S\_n)\_{n\in V} \\) and `V` represents
* the relative position in a pre-generated scope. In this case we'd specify
* a resolution to the value. If:
*
* $$\exists t \subseteq C : \forall j \in t : t \in S_n$$
*
* Where `C` is a candidate, then we will throw an error due to a cyclic
* dependence on type resolution index.
*
* ---
*
* Each scope is defined by a `t.CodeBlock` node and if a `get Node#identifierPath()`
* call does not return nil; the returned {@link Identifier} is then taken as the
* identifier to declare a variable as.
*/
export default class Transformer extends ScopeTraverser {
/**
* Creates a new Transformer with the given passes and context. Reference
* {@link TransformationContext} for more information on the context itself.
* Passes can again use their AST tool to access context information.
*
* @param {Transformation[]} passes - The given passes to setup
* @param {TransformationContext} [context=TransformationContext()] - The
* context of the given transformation see this constructor's
* description for more info.
*/
constructor(passes: Transformation[], context: TransformationContext = new TransformationContext()) {
super(true);
/**
* Contains a list of all the transformations the transformer will use.
*
* @type {Transformation[]}
*/
this.passes = passes;
/** @private */
this.time = null;
/**
* DO NOT directly modify. Use `queue` or such to modify. Exposed
* primarially for performance and GC reasons.
*
* @type {Node[]}
*/
this.nodeQueue = [];
/**
* The context of the the transformer. Transformations can set or use
* this to get global context info etc.
*
* @type {TransformationContext}
*/
this.context = context;
}
/**
* Queues an AST to be parsed, calls `transform(ast:)` and automatically
* handles transformation distribution. Therefore, this is the reccomended
* way to automatically setup and queue the inital AST nodes.
*
* It is reccomended to buffer expressions with `queue(ast:)` and use the transform
* status to determine whether a fork of Transformers should be utilized or
* to simply continuing to pipe further AST statements into queue. This operation
* may or may not be syncronous.
*
* Avoid directly calling from a {@link Transformation}
*
* Return value can be extracted by specifying an `.oncompletion` handler.
*
* @param {any} ast - An AST as outputted by a `Parser`
*/
queue(ast: any) {
super.queue(ast);
}
/** @override */
receivedNode(parent: Node | Node[], name: string) {
// Add to queue
this.appendNodeQueue(parent, name);
}
/** @override */
finishedNode(parent: Node | Node[], name: string) {
if (parent[name] instanceof t.CodeBlock) {
this.scope.pop()
}
}
/**
* Adds item to the end node queue (reccomended).
*/
appendNodeQueue(parent: Node | Node[], name: string | number) {
parent[name].queueQualifier = this.nodeQueue.length;
this.nodeQueue.push([ parent[name], parent, name ]);
this.didUpdateQueue()
}
/**
* Handles the queue items
* @private
*/
didUpdateQueue() {
var value, node, parent, item;
while ((value = this.nodeQueue.shift()) !== (void 0)) {
Iif (value === null) continue;
[node, parent, item] = value;
this.transform(node, parent, item);
}
}
/**
* Queues a given node for transformation.
*
* This accepts a specific transformation which needs to be executed, and
* also required the parent node and the current node's name. It is
* reccomended to use ASTTool's interface
*
* This is useful if your node has children which _must_ be processed before
* the current one is.
*
* @param {Node|Node[]} node - The node(s) to be processed and queued.
* @param {Node|Node[]} parent - The parent node of the given ast
* @param {any} name - The reference to the child relative to the parent.
* @param {Transformation} transformation - The desired transformation to run
*/
queueThen(node: Node, parent: parent, name: any, transformation: Transformation) {
this.transform_once(node, parent, name, transformation);
}
/**
* Transform the AST accordi
ng to the setup transformer. This is recursively
* called and should never be called from within a transformer
*
* @param {Node} ast - An AST as outputted by a `Parser`
* @param {Node|Node[]} parent - The parent node of the given ast
* @param {any} name - The reference to the child relative to the parent.
* @param {Transformation[]} [passes=this.passes] - Do not specify. Only for internal use
* @return A transformed AST with the passes applied
*
* @example
* var AST = new VSLParser().feed(new VSLTokenizer().tokenize(input));
* var final = new VSLTransformer(VSLTransformer.default).transform(AST);
*/
transform(ast: any, parent: parent, name: any, passes: Transformation[] = this.passes) {
let t = process.hrtime();
for (let i = 0; i < passes.length; i++) {
let result = this.transform_once(
ast,
parent,
name,
passes[i]
);
if (result === false) {
// Requeue with remaining transformations. Excluding current
let queuedTransforms = passes.slice(i + 1);
if (queuedTransforms.length > 0)
this.transform(parent[name], parent, name, queuedTransforms);
break;
}
}
}
/**
* Transforms with single transformer.
*
* @return {bool} if node was mutated and should be requeued.
* @private
*/
transform_once(ast: Node, parent: Node | Node[], name: any, pass: Transformation) {
// Create the tool for modification
let astTool = new ASTTool(parent, name, this);
// Setup the transformation
let transformation = new pass();
let type = transformation.type;
// Ensure ast is of the correct type
// otherwise stop processing the node
if (!(type === null || ast instanceof type))
return false;
// Call transformation
transformation.modify(ast, astTool);
// Get new node
let result = parent[name];
return ast === result;
}
}
|