Index: trunk/extensions/VisualEditor/tests/parser/parserTests.js |
— | — | @@ -57,7 +57,7 @@ |
58 | 58 | |
59 | 59 | _import(pj('parser', 'mediawiki.tokenizer.peg.js'), ['PegTokenizer']); |
60 | 60 | _import(pj('parser', 'mediawiki.parser.environment.js'), ['MWParserEnvironment']); |
61 | | -_import(pj('parser', 'mediawiki.TokenTransformer.js'), ['TokenTransformer']); |
| 61 | +_import(pj('parser', 'mediawiki.TokenTransformDispatcher.js'), ['TokenTransformDispatcher']); |
62 | 62 | _import(pj('parser', 'ext.cite.taghook.ref.js'), ['MWRefTagHook']); |
63 | 63 | |
64 | 64 | _import(pj('parser', 'mediawiki.HTML5TreeBuilder.node.js'), ['FauxHTML5']); |
— | — | @@ -181,9 +181,9 @@ |
182 | 182 | |
183 | 183 | var pt = this; |
184 | 184 | |
185 | | - // Set up the TokenTransformer with a callback for the remaining |
| 185 | + // Set up the TokenTransformDispatcher with a callback for the remaining |
186 | 186 | // processing. |
187 | | - this.tokenTransformer = new TokenTransformer ( function ( tokens ) { |
| 187 | + this.tokenDispatcher = new TokenTransformDispatcher ( function ( tokens ) { |
188 | 188 | |
189 | 189 | //console.log("TOKENS: " + JSON.stringify(tokens, null, 2)); |
190 | 190 | |
— | — | @@ -205,7 +205,7 @@ |
206 | 206 | |
207 | 207 | // Add token transformations.. |
208 | 208 | var qt = new QuoteTransformer(); |
209 | | - qt.register(this.tokenTransformer); |
| 209 | + qt.register(this.tokenDispatcher); |
210 | 210 | |
211 | 211 | // Test statistics |
212 | 212 | this.passedTests = 0; |
— | — | @@ -434,10 +434,10 @@ |
435 | 435 | //Slightly better token output debugging: |
436 | 436 | //console.log( util.inspect( tokens, false, null ).yellow); |
437 | 437 | |
438 | | - // Transform tokens using the TokenTransformer. When done, the |
439 | | - // TokenTransformer calls buildTree() and checkResult() with the |
| 438 | + // Transform tokens using the TokenTransformDispatcher. When done, the |
| 439 | + // TokenTransformDispatcher calls buildTree() and checkResult() with the |
440 | 440 | // transformed tokens. |
441 | | - this.tokenTransformer.transformTokens( res.tokens ); |
| 441 | + this.tokenDispatcher.transformTokens( res.tokens ); |
442 | 442 | } |
443 | 443 | }; |
444 | 444 | |
Index: trunk/extensions/VisualEditor/modules/parser/mediawiki.TokenTransformer.js |
— | — | @@ -1,266 +0,0 @@ |
2 | | -/* Generic token transformation dispatcher with support for asynchronous token |
3 | | - * expansion. Individual transformations register for the token types they are |
4 | | - * interested in and are called on each matching token. |
5 | | - * |
6 | | - * A transformer might return null, a single token, or an array of tokens. |
7 | | - * - Null removes the token and stops further processing for this token. |
8 | | - * - A single token is further processed using the remaining transformations |
9 | | - * registered for this token, and finally placed in the output token list. |
10 | | - * - A list of tokens stops the processing for this token. Instead, processing |
11 | | - * restarts with the first returned token. |
12 | | - * |
13 | | - * Additionally, transformers performing asynchronous actions on a token can |
14 | | - * create a new TokenAccumulator using .newAccumulator(). This creates a new |
15 | | - * accumulator for each asynchronous result, with the asynchronously processed |
16 | | - * token last in its internal accumulator. This setup avoids the need to apply |
17 | | - * operational-transform-like index transformations when parallel expansions |
18 | | - * insert tokens in front of other ongoing expansion tasks. |
19 | | - * */ |
20 | | - |
21 | | -function TokenTransformer( callback ) { |
22 | | - this.cb = callback; // Called with transformed token list when done |
23 | | - this.transformers = { |
24 | | - tag: {}, // for TAG, ENDTAG, SELFCLOSINGTAG, keyed on name |
25 | | - text: [], |
26 | | - newline: [], |
27 | | - comment: [], |
28 | | - end: [], // eof |
29 | | - martian: [] // none of the above |
30 | | - }; |
31 | | - this.reset(); |
32 | | - return this; |
33 | | -} |
34 | | - |
35 | | -TokenTransformer.prototype.reset = function () { |
36 | | - this.accum = new TokenAccumulator(null); |
37 | | - this.firstaccum = this.accum; |
38 | | - this.outstanding = 1; // Number of outstanding processing steps |
39 | | - // (e.g., async template fetches/expansions) |
40 | | -}; |
41 | | - |
42 | | -TokenTransformer.prototype.appendListener = function ( listener, type, name ) { |
43 | | - if ( type === 'tag' ) { |
44 | | - if ( $.isArray(this.transformers.tag.name) ) { |
45 | | - this.transformers.tag[name].push(listener); |
46 | | - } else { |
47 | | - this.transformers.tag[name] = [listener]; |
48 | | - } |
49 | | - } else { |
50 | | - this.transformers[type].push(listener); |
51 | | - } |
52 | | -}; |
53 | | - |
54 | | -TokenTransformer.prototype.prependListener = function ( listener, type, name ) { |
55 | | - if ( type === 'tag' ) { |
56 | | - if ( $.isArray(this.transformers.tag.name) ) { |
57 | | - this.transformers.tag[name].unshift(listener); |
58 | | - } else { |
59 | | - this.transformers.tag[name] = [listener]; |
60 | | - } |
61 | | - } else { |
62 | | - this.transformers[type].unshift(listener); |
63 | | - } |
64 | | -}; |
65 | | - |
66 | | -TokenTransformer.prototype.removeListener = function ( listener, type, name ) { |
67 | | - var i = -1; |
68 | | - var ts; |
69 | | - if ( type === 'tag' ) { |
70 | | - if ( $.isArray(this.transformers.tag.name) ) { |
71 | | - ts = this.transformers.tag[name]; |
72 | | - i = ts.indexOf(listener); |
73 | | - } |
74 | | - } else { |
75 | | - ts = this.transformers[type]; |
76 | | - i = ts.indexOf(listener); |
77 | | - } |
78 | | - if ( i >= 0 ) { |
79 | | - ts.splice(i, 1); |
80 | | - } |
81 | | -}; |
82 | | - |
83 | | -/* Constructor for information context relevant to token transformers |
84 | | - * |
85 | | - * @param token The token to precess |
86 | | - * @param accum {TokenAccumulator} The active TokenAccumulator. |
87 | | - * @param processor {TokenTransformer} The TokenTransformer object. |
88 | | - * @param lastToken Last returned token or {undefined}. |
89 | | - * @returns {TokenContext}. |
90 | | - */ |
91 | | -function TokenContext ( token, accum, transformer, lastToken ) { |
92 | | - this.token = token; |
93 | | - this.accum = accum; |
94 | | - this.transformer = transformer; |
95 | | - this.lastToken = lastToken; |
96 | | - return this; |
97 | | -} |
98 | | - |
99 | | -/* Call all transformers on a tag. |
100 | | - * |
101 | | - * @param {TokenContext} The current token and its context. |
102 | | - * @returns {TokenContext} Context with updated token and/or accum. |
103 | | - */ |
104 | | -TokenTransformer.prototype._transformTagToken = function ( tokenCTX ) { |
105 | | - var ts = this.transformers.tag[tokenCTX.token.name]; |
106 | | - if ( ts ) { |
107 | | - for (var i = 0, l = ts.length; i < l; i++ ) { |
108 | | - // Transform token with side effects |
109 | | - tokenCTX = ts[i]( tokenCTX ); |
110 | | - if ( tokenCTX.token === null || $.isArray(tokenCTX.token) ) { |
111 | | - break; |
112 | | - } |
113 | | - |
114 | | - } |
115 | | - } |
116 | | - return tokenCTX; |
117 | | -}; |
118 | | - |
119 | | -/* Call all transformers on other token types. |
120 | | - * |
121 | | - * @param tokenCTX {TokenContext} The current token and its context. |
122 | | - * @param ts List of token transformers for this token type. |
123 | | - * @returns {TokenContext} Context with updated token and/or accum. |
124 | | - */ |
125 | | -TokenTransformer.prototype._transformToken = function ( tokenCTX, ts ) { |
126 | | - if ( ts ) { |
127 | | - for (var i = 0, l = ts.length; i < l; i++ ) { |
128 | | - tokenCTX = ts[i]( tokenCTX ); |
129 | | - if ( tokenCTX.token === null || $.isArray(tokenCTX.token) ) { |
130 | | - break; |
131 | | - } |
132 | | - } |
133 | | - } |
134 | | - return tokenCTX; |
135 | | -}; |
136 | | - |
137 | | -/* Transform and expand tokens. |
138 | | - * |
139 | | - * Normally called with undefined accum. Asynchronous expansions will call |
140 | | - * this with their known accum, which allows expanded tokens to be spliced in |
141 | | - * at the appropriate location in the token list, which is always at the tail |
142 | | - * end of the current accumulator. |
143 | | - * |
144 | | - * @param tokens {List of tokens} Tokens to process. |
145 | | - * @param accum {TokenAccumulator} object. Undefined for first call, set to |
146 | | - * accumulator with expanded token at tail for asynchronous |
147 | | - * expansions. |
148 | | - * @returns nothing: Calls back registered callback if there are no more |
149 | | - * outstanding asynchronous expansions. |
150 | | - * */ |
151 | | -TokenTransformer.prototype.transformTokens = function ( tokens, accum, delta ) { |
152 | | - if ( accum === undefined ) { |
153 | | - this.reset(); |
154 | | - accum = this.accum; |
155 | | - } |
156 | | - |
157 | | - //console.log('transformTokens: ' + JSON.stringify(tokens) + JSON.stringify(accum.accum) ); |
158 | | - |
159 | | - var tokenCTX = new TokenContext(undefined, accum, this, undefined); |
160 | | - var origLen = tokens.length; |
161 | | - for ( var i = 0; i < tokens.length; i++ ) { |
162 | | - tokenCTX.lastToken = tokenCTX.token; // XXX: Fix for re-entrant case! |
163 | | - tokenCTX.token = tokens[i]; |
164 | | - tokenCTX.pos = i; |
165 | | - tokenCTX.accum = accum; |
166 | | - var ts; |
167 | | - switch(tokenCTX.token.type) { |
168 | | - case 'TAG': |
169 | | - case 'ENDTAG': |
170 | | - case 'SELFCLOSINGTAG': |
171 | | - tokenCTX = this._transformTagToken( tokenCTX ); |
172 | | - break; |
173 | | - case 'TEXT': |
174 | | - tokenCTX = this._transformToken( tokenCTX, this.transformers.text ); |
175 | | - break; |
176 | | - case 'COMMENT': |
177 | | - tokenCTX = this._transformToken( tokenCTX, this.transformers.comment); |
178 | | - break; |
179 | | - case 'NEWLINE': |
180 | | - tokenCTX = this._transformToken( tokenCTX, this.transformers.newline ); |
181 | | - break; |
182 | | - case 'END': |
183 | | - tokenCTX = this._transformToken( tokenCTX, this.transformers.end ); |
184 | | - break; |
185 | | - default: |
186 | | - tokenCTX = this._transformToken( tokenCTX, this.transformers.martian ); |
187 | | - break; |
188 | | - } |
189 | | - if( $.isArray(tokenCTX.token) ) { |
190 | | - // Splice in the returned tokens (while replacing the original |
191 | | - // token), and process them next. |
192 | | - [].splice.apply(tokens, [i, 1].concat(tokenCTX.token)); |
193 | | - //l += tokenCTX.token.length - 1; |
194 | | - i--; // continue at first inserted token |
195 | | - } else if (tokenCTX.token) { |
196 | | - // push to accumulator |
197 | | - accum.push(tokenCTX.token); |
198 | | - } |
199 | | - // Update current accum, in case a new one was spliced in by a |
200 | | - // transformation starting asynch work. |
201 | | - accum = tokenCTX.accum; |
202 | | - } |
203 | | - |
204 | | - if ( delta === undefined ) { |
205 | | - delta = 1; |
206 | | - } |
207 | | - |
208 | | - this.finish( delta ); |
209 | | -}; |
210 | | - |
211 | | -TokenTransformer.prototype.finish = function ( delta ) { |
212 | | - this.outstanding -= delta; |
213 | | - if ( this.outstanding === 0 ) { |
214 | | - // Join the token accumulators back into a single token list |
215 | | - var a = this.firstaccum; |
216 | | - var tokens = a.accum; |
217 | | - while ( a.next !== null ) { |
218 | | - a = a.next; |
219 | | - tokens = tokens.concat(a.accum); |
220 | | - } |
221 | | - //console.log('TOKENS: ' + JSON.stringify(tokens, null, 2)); |
222 | | - // Call our callback with the flattened token list |
223 | | - this.cb(tokens); |
224 | | - } |
225 | | -}; |
226 | | - |
227 | | -/* Start a new accumulator for asynchronous work. */ |
228 | | -TokenTransformer.prototype.newAccumulator = function ( accum, count ) { |
229 | | - if ( count !== undefined ) { |
230 | | - this.outstanding += count; |
231 | | - } else { |
232 | | - this.outstanding++; |
233 | | - } |
234 | | - if ( accum === undefined ) { |
235 | | - accum = this.accum; |
236 | | - } |
237 | | - return accum.insertAccumulator( ); |
238 | | -}; |
239 | | - |
240 | | -// Token accumulators in a linked list. Using a linked list simplifies async |
241 | | -// callbacks for template expansions. |
242 | | -function TokenAccumulator ( next, tokens ) { |
243 | | - this.next = next; |
244 | | - if ( tokens ) { |
245 | | - this.accum = tokens; |
246 | | - } else { |
247 | | - this.accum = []; |
248 | | - } |
249 | | - return this; |
250 | | -} |
251 | | - |
252 | | -TokenAccumulator.prototype.push = function ( token ) { |
253 | | - return this.accum.push(token); |
254 | | -}; |
255 | | - |
256 | | -TokenAccumulator.prototype.pop = function ( ) { |
257 | | - return this.accum.pop(); |
258 | | -}; |
259 | | - |
260 | | -TokenAccumulator.prototype.insertAccumulator = function ( ) { |
261 | | - this.next = new TokenAccumulator(this.next); |
262 | | - return this.next; |
263 | | -}; |
264 | | - |
265 | | -if (typeof module == "object") { |
266 | | - module.exports.TokenTransformer = TokenTransformer; |
267 | | -} |
Index: trunk/extensions/VisualEditor/modules/parser/mediawiki.TokenTransformDispatcher.js |
— | — | @@ -0,0 +1,267 @@ |
| 2 | +/* Generic token transformation dispatcher with support for asynchronous token |
| 3 | + * expansion. Individual transformations register for the token types they are |
| 4 | + * interested in and are called on each matching token. |
| 5 | + * |
| 6 | + * A transformer might return null, a single token, or an array of tokens. |
| 7 | + * - Null removes the token and stops further processing for this token. |
| 8 | + * - A single token is further processed using the remaining transformations |
| 9 | + * registered for this token, and finally placed in the output token list. |
| 10 | + * - A list of tokens stops the processing for this token. Instead, processing |
| 11 | + * restarts with the first returned token. |
| 12 | + * |
| 13 | + * Additionally, transformers performing asynchronous actions on a token can |
| 14 | + * create a new TokenAccumulator using .newAccumulator(). This creates a new |
| 15 | + * accumulator for each asynchronous result, with the asynchronously processed |
| 16 | + * token last in its internal accumulator. This setup avoids the need to apply |
| 17 | + * operational-transform-like index transformations when parallel expansions |
| 18 | + * insert tokens in front of other ongoing expansion tasks. |
| 19 | + * */ |
| 20 | + |
| 21 | +function TokenTransformDispatcher( callback ) { |
| 22 | + this.cb = callback; // Called with transformed token list when done |
| 23 | + this.transformers = { |
| 24 | + tag: {}, // for TAG, ENDTAG, SELFCLOSINGTAG, keyed on name |
| 25 | + text: [], |
| 26 | + newline: [], |
| 27 | + comment: [], |
| 28 | + end: [], // eof |
| 29 | + martian: [] // none of the above |
| 30 | + // XXX: Add an any registration that always matches? |
| 31 | + }; |
| 32 | + this.reset(); |
| 33 | + return this; |
| 34 | +} |
| 35 | + |
| 36 | +TokenTransformDispatcher.prototype.reset = function () { |
| 37 | + this.accum = new TokenAccumulator(null); |
| 38 | + this.firstaccum = this.accum; |
| 39 | + this.outstanding = 1; // Number of outstanding processing steps |
| 40 | + // (e.g., async template fetches/expansions) |
| 41 | +}; |
| 42 | + |
| 43 | +TokenTransformDispatcher.prototype.appendListener = function ( listener, type, name ) { |
| 44 | + if ( type === 'tag' ) { |
| 45 | + if ( $.isArray(this.transformers.tag.name) ) { |
| 46 | + this.transformers.tag[name].push(listener); |
| 47 | + } else { |
| 48 | + this.transformers.tag[name] = [listener]; |
| 49 | + } |
| 50 | + } else { |
| 51 | + this.transformers[type].push(listener); |
| 52 | + } |
| 53 | +}; |
| 54 | + |
| 55 | +TokenTransformDispatcher.prototype.prependListener = function ( listener, type, name ) { |
| 56 | + if ( type === 'tag' ) { |
| 57 | + if ( $.isArray(this.transformers.tag.name) ) { |
| 58 | + this.transformers.tag[name].unshift(listener); |
| 59 | + } else { |
| 60 | + this.transformers.tag[name] = [listener]; |
| 61 | + } |
| 62 | + } else { |
| 63 | + this.transformers[type].unshift(listener); |
| 64 | + } |
| 65 | +}; |
| 66 | + |
| 67 | +TokenTransformDispatcher.prototype.removeListener = function ( listener, type, name ) { |
| 68 | + var i = -1; |
| 69 | + var ts; |
| 70 | + if ( type === 'tag' ) { |
| 71 | + if ( $.isArray(this.transformers.tag.name) ) { |
| 72 | + ts = this.transformers.tag[name]; |
| 73 | + i = ts.indexOf(listener); |
| 74 | + } |
| 75 | + } else { |
| 76 | + ts = this.transformers[type]; |
| 77 | + i = ts.indexOf(listener); |
| 78 | + } |
| 79 | + if ( i >= 0 ) { |
| 80 | + ts.splice(i, 1); |
| 81 | + } |
| 82 | +}; |
| 83 | + |
| 84 | +/* Constructor for information context relevant to token transformers |
| 85 | + * |
| 86 | + * @param token The token to precess |
| 87 | + * @param accum {TokenAccumulator} The active TokenAccumulator. |
| 88 | + * @param processor {TokenTransformDispatcher} The TokenTransformDispatcher object. |
| 89 | + * @param lastToken Last returned token or {undefined}. |
| 90 | + * @returns {TokenContext}. |
| 91 | + */ |
| 92 | +function TokenContext ( token, accum, dispatcher, lastToken ) { |
| 93 | + this.token = token; |
| 94 | + this.accum = accum; |
| 95 | + this.dispatcher = dispatcher; |
| 96 | + this.lastToken = lastToken; |
| 97 | + return this; |
| 98 | +} |
| 99 | + |
| 100 | +/* Call all transformers on a tag. |
| 101 | + * |
| 102 | + * @param {TokenContext} The current token and its context. |
| 103 | + * @returns {TokenContext} Context with updated token and/or accum. |
| 104 | + */ |
| 105 | +TokenTransformDispatcher.prototype._transformTagToken = function ( tokenCTX ) { |
| 106 | + var ts = this.transformers.tag[tokenCTX.token.name]; |
| 107 | + if ( ts ) { |
| 108 | + for (var i = 0, l = ts.length; i < l; i++ ) { |
| 109 | + // Transform token with side effects |
| 110 | + tokenCTX = ts[i]( tokenCTX ); |
| 111 | + if ( tokenCTX.token === null || $.isArray(tokenCTX.token) ) { |
| 112 | + break; |
| 113 | + } |
| 114 | + |
| 115 | + } |
| 116 | + } |
| 117 | + return tokenCTX; |
| 118 | +}; |
| 119 | + |
| 120 | +/* Call all transformers on other token types. |
| 121 | + * |
| 122 | + * @param tokenCTX {TokenContext} The current token and its context. |
| 123 | + * @param ts List of token transformers for this token type. |
| 124 | + * @returns {TokenContext} Context with updated token and/or accum. |
| 125 | + */ |
| 126 | +TokenTransformDispatcher.prototype._transformToken = function ( tokenCTX, ts ) { |
| 127 | + if ( ts ) { |
| 128 | + for (var i = 0, l = ts.length; i < l; i++ ) { |
| 129 | + tokenCTX = ts[i]( tokenCTX ); |
| 130 | + if ( tokenCTX.token === null || $.isArray(tokenCTX.token) ) { |
| 131 | + break; |
| 132 | + } |
| 133 | + } |
| 134 | + } |
| 135 | + return tokenCTX; |
| 136 | +}; |
| 137 | + |
| 138 | +/* Transform and expand tokens. |
| 139 | + * |
| 140 | + * Normally called with undefined accum. Asynchronous expansions will call |
| 141 | + * this with their known accum, which allows expanded tokens to be spliced in |
| 142 | + * at the appropriate location in the token list, which is always at the tail |
| 143 | + * end of the current accumulator. |
| 144 | + * |
| 145 | + * @param tokens {List of tokens} Tokens to process. |
| 146 | + * @param accum {TokenAccumulator} object. Undefined for first call, set to |
| 147 | + * accumulator with expanded token at tail for asynchronous |
| 148 | + * expansions. |
| 149 | + * @returns nothing: Calls back registered callback if there are no more |
| 150 | + * outstanding asynchronous expansions. |
| 151 | + * */ |
| 152 | +TokenTransformDispatcher.prototype.transformTokens = function ( tokens, accum, delta ) { |
| 153 | + if ( accum === undefined ) { |
| 154 | + this.reset(); |
| 155 | + accum = this.accum; |
| 156 | + } |
| 157 | + |
| 158 | + //console.log('transformTokens: ' + JSON.stringify(tokens) + JSON.stringify(accum.accum) ); |
| 159 | + |
| 160 | + var tokenCTX = new TokenContext(undefined, accum, this, undefined); |
| 161 | + var origLen = tokens.length; |
| 162 | + for ( var i = 0; i < tokens.length; i++ ) { |
| 163 | + tokenCTX.lastToken = tokenCTX.token; // XXX: Fix for re-entrant case! |
| 164 | + tokenCTX.token = tokens[i]; |
| 165 | + tokenCTX.pos = i; |
| 166 | + tokenCTX.accum = accum; |
| 167 | + var ts; |
| 168 | + switch(tokenCTX.token.type) { |
| 169 | + case 'TAG': |
| 170 | + case 'ENDTAG': |
| 171 | + case 'SELFCLOSINGTAG': |
| 172 | + tokenCTX = this._transformTagToken( tokenCTX ); |
| 173 | + break; |
| 174 | + case 'TEXT': |
| 175 | + tokenCTX = this._transformToken( tokenCTX, this.transformers.text ); |
| 176 | + break; |
| 177 | + case 'COMMENT': |
| 178 | + tokenCTX = this._transformToken( tokenCTX, this.transformers.comment); |
| 179 | + break; |
| 180 | + case 'NEWLINE': |
| 181 | + tokenCTX = this._transformToken( tokenCTX, this.transformers.newline ); |
| 182 | + break; |
| 183 | + case 'END': |
| 184 | + tokenCTX = this._transformToken( tokenCTX, this.transformers.end ); |
| 185 | + break; |
| 186 | + default: |
| 187 | + tokenCTX = this._transformToken( tokenCTX, this.transformers.martian ); |
| 188 | + break; |
| 189 | + } |
| 190 | + if( $.isArray(tokenCTX.token) ) { |
| 191 | + // Splice in the returned tokens (while replacing the original |
| 192 | + // token), and process them next. |
| 193 | + [].splice.apply(tokens, [i, 1].concat(tokenCTX.token)); |
| 194 | + //l += tokenCTX.token.length - 1; |
| 195 | + i--; // continue at first inserted token |
| 196 | + } else if (tokenCTX.token) { |
| 197 | + // push to accumulator |
| 198 | + accum.push(tokenCTX.token); |
| 199 | + } |
| 200 | + // Update current accum, in case a new one was spliced in by a |
| 201 | + // transformation starting asynch work. |
| 202 | + accum = tokenCTX.accum; |
| 203 | + } |
| 204 | + |
| 205 | + if ( delta === undefined ) { |
| 206 | + delta = 1; |
| 207 | + } |
| 208 | + |
| 209 | + this.finish( delta ); |
| 210 | +}; |
| 211 | + |
| 212 | +TokenTransformDispatcher.prototype.finish = function ( delta ) { |
| 213 | + this.outstanding -= delta; |
| 214 | + if ( this.outstanding === 0 ) { |
| 215 | + // Join the token accumulators back into a single token list |
| 216 | + var a = this.firstaccum; |
| 217 | + var tokens = a.accum; |
| 218 | + while ( a.next !== null ) { |
| 219 | + a = a.next; |
| 220 | + tokens = tokens.concat(a.accum); |
| 221 | + } |
| 222 | + //console.log('TOKENS: ' + JSON.stringify(tokens, null, 2)); |
| 223 | + // Call our callback with the flattened token list |
| 224 | + this.cb(tokens); |
| 225 | + } |
| 226 | +}; |
| 227 | + |
| 228 | +/* Start a new accumulator for asynchronous work. */ |
| 229 | +TokenTransformDispatcher.prototype.newAccumulator = function ( accum, count ) { |
| 230 | + if ( count !== undefined ) { |
| 231 | + this.outstanding += count; |
| 232 | + } else { |
| 233 | + this.outstanding++; |
| 234 | + } |
| 235 | + if ( accum === undefined ) { |
| 236 | + accum = this.accum; |
| 237 | + } |
| 238 | + return accum.insertAccumulator( ); |
| 239 | +}; |
| 240 | + |
| 241 | +// Token accumulators in a linked list. Using a linked list simplifies async |
| 242 | +// callbacks for template expansions. |
| 243 | +function TokenAccumulator ( next, tokens ) { |
| 244 | + this.next = next; |
| 245 | + if ( tokens ) { |
| 246 | + this.accum = tokens; |
| 247 | + } else { |
| 248 | + this.accum = []; |
| 249 | + } |
| 250 | + return this; |
| 251 | +} |
| 252 | + |
| 253 | +TokenAccumulator.prototype.push = function ( token ) { |
| 254 | + return this.accum.push(token); |
| 255 | +}; |
| 256 | + |
| 257 | +TokenAccumulator.prototype.pop = function ( ) { |
| 258 | + return this.accum.pop(); |
| 259 | +}; |
| 260 | + |
| 261 | +TokenAccumulator.prototype.insertAccumulator = function ( ) { |
| 262 | + this.next = new TokenAccumulator(this.next); |
| 263 | + return this.next; |
| 264 | +}; |
| 265 | + |
| 266 | +if (typeof module == "object") { |
| 267 | + module.exports.TokenTransformDispatcher = TokenTransformDispatcher; |
| 268 | +} |
Property changes on: trunk/extensions/VisualEditor/modules/parser/mediawiki.TokenTransformDispatcher.js |
___________________________________________________________________ |
Added: svn:eol-style |
1 | 269 | + native |
Index: trunk/extensions/VisualEditor/modules/parser/ext.core.QuoteTransformer.js |
— | — | @@ -9,19 +9,20 @@ |
10 | 10 | */ |
11 | 11 | |
12 | 12 | function QuoteTransformer ( ) { |
| 13 | + // Bold and italic tokens are collected in these lists, and then processed |
| 14 | + // in onNewLine. |
13 | 15 | this.italics = []; |
14 | 16 | this.bolds = []; |
15 | | - this.inserted = 0; |
16 | 17 | } |
17 | 18 | |
18 | 19 | // Register this transformer with the TokenTransformer |
19 | | -QuoteTransformer.prototype.register = function ( tokenTransformer ) { |
| 20 | +QuoteTransformer.prototype.register = function ( dispatcher ) { |
20 | 21 | // Register for NEWLINE and QUOTE tag tokens |
21 | 22 | var self = this; |
22 | | - tokenTransformer.appendListener( function (ctx) { |
| 23 | + dispatcher.appendListener( function (ctx) { |
23 | 24 | return self.onNewLine(ctx); |
24 | 25 | }, 'newline' ); |
25 | | - tokenTransformer.appendListener( function (ctx) { |
| 26 | + dispatcher.appendListener( function (ctx) { |
26 | 27 | return self.onQuote(ctx); |
27 | 28 | }, 'tag', 'QUOTE' ); |
28 | 29 | }; |
— | — | @@ -49,11 +50,11 @@ |
50 | 51 | // Start a new accumulator, so we can later go back using the |
51 | 52 | // reference to this accumulator and append our tags at the end of |
52 | 53 | // it. |
53 | | - accum = tokenCTX.transformer.newAccumulator(accum); |
| 54 | + accum = tokenCTX.dispatcher.newAccumulator(accum); |
54 | 55 | this.italics.push(ctx); |
55 | 56 | break; |
56 | 57 | case 3: |
57 | | - accum = tokenCTX.transformer.newAccumulator(accum); |
| 58 | + accum = tokenCTX.dispatcher.newAccumulator(accum); |
58 | 59 | this.bolds.push(ctx); |
59 | 60 | break; |
60 | 61 | case 4: |
— | — | @@ -62,7 +63,7 @@ |
63 | 64 | } else { |
64 | 65 | out = {type: 'TEXT', value: "'"}; |
65 | 66 | } |
66 | | - accum = tokenCTX.transformer.newAccumulator(accum); |
| 67 | + accum = tokenCTX.dispatcher.newAccumulator(accum); |
67 | 68 | this.bolds.push(ctx); |
68 | 69 | break; |
69 | 70 | case 5: |
— | — | @@ -71,7 +72,7 @@ |
72 | 73 | // by the HTML 5 tree builder. This does not always result in the |
73 | 74 | // prettiest result, but at least it is always correct and very |
74 | 75 | // convenient. |
75 | | - accum = tokenCTX.transformer.newAccumulator(accum, 2); |
| 76 | + accum = tokenCTX.dispatcher.newAccumulator(accum, 2); |
76 | 77 | this.italics.push(ctx); |
77 | 78 | ctx2 = this.ctx(tokenCTX); |
78 | 79 | ctx2.token = {attribs: ctx.token.attribs}; |
— | — | @@ -84,7 +85,7 @@ |
85 | 86 | } else { |
86 | 87 | out = {type: 'TEXT', value: newvalue}; |
87 | 88 | } |
88 | | - accum = tokenCTX.transformer.newAccumulator(accum, 2); |
| 89 | + accum = tokenCTX.dispatcher.newAccumulator(accum, 2); |
89 | 90 | this.italics.push(ctx); |
90 | 91 | ctx2 = this.ctx(tokenCTX); |
91 | 92 | ctx2.token = {attribs: ctx.token.attribs}; |
— | — | @@ -152,8 +153,8 @@ |
153 | 154 | } |
154 | 155 | } |
155 | 156 | |
156 | | - this.quotesToTags(this.italics, 'i', tokenCTX.transformer); |
157 | | - this.quotesToTags(this.bolds, 'b', tokenCTX.transformer); |
| 157 | + this.quotesToTags(this.italics, 'i', tokenCTX.dispatcher); |
| 158 | + this.quotesToTags(this.bolds, 'b', tokenCTX.dispatcher); |
158 | 159 | |
159 | 160 | this.bolds = []; |
160 | 161 | this.italics = []; |
— | — | @@ -183,7 +184,7 @@ |
184 | 185 | }; |
185 | 186 | |
186 | 187 | // Convert italics/bolds into tags |
187 | | -QuoteTransformer.prototype.quotesToTags = function ( contexts, name, transformer ) { |
| 188 | +QuoteTransformer.prototype.quotesToTags = function ( contexts, name, dispatcher ) { |
188 | 189 | var toggle = true, |
189 | 190 | t, |
190 | 191 | out = []; |
— | — | @@ -194,7 +195,7 @@ |
195 | 196 | // Slip in a text token from bold to italic rebalancing. Don't |
196 | 197 | // count this callback towards completion. |
197 | 198 | var realToken = t.pop(); |
198 | | - transformer.transformTokens( t, contexts[j].accum, 0 ); |
| 199 | + dispatcher.transformTokens( t, contexts[j].accum, 0 ); |
199 | 200 | t = realToken; |
200 | 201 | } |
201 | 202 | |
— | — | @@ -208,18 +209,18 @@ |
209 | 210 | toggle = !toggle; |
210 | 211 | // Re-add and process the new token with the original accumulator, but |
211 | 212 | // don't yet count this callback towards callback completion. |
212 | | - transformer.transformTokens( [t], contexts[j].accum, 0 ); |
| 213 | + dispatcher.transformTokens( [t], contexts[j].accum, 0 ); |
213 | 214 | } |
214 | 215 | var l = contexts.length; |
215 | 216 | if (!toggle) { |
216 | 217 | // Add end tag, but don't count it towards completion. |
217 | | - transformer.transformTokens( [{type: 'ENDTAG', name: name}], |
| 218 | + dispatcher.transformTokens( [{type: 'ENDTAG', name: name}], |
218 | 219 | contexts[contexts.length - 1].accum, 0 ); |
219 | 220 | } |
220 | 221 | // Now finally count the number of contexts towards completion, which |
221 | | - // causes the transformer to call its own callback if no more asynch |
| 222 | + // causes the dispatcher to call its own callback if no more asynch |
222 | 223 | // callbacks are outstanding. |
223 | | - transformer.finish( contexts.length ); |
| 224 | + dispatcher.finish( contexts.length ); |
224 | 225 | }; |
225 | 226 | |
226 | 227 | if (typeof module == "object") { |