r109584 MediaWiki - Code Review archive

Repository:MediaWiki
Revision:r109583‎ | r109584 | r109585 >
Date:23:43, 19 January 2012
Author:gwicke
Status:deferred
Tags:
Comment:
Fix async template expansion, so we can now render simple pages with templates
directly to WikiDom from enwiki using a commandline like this:

echo '{{User:GWicke/Test}}' | node parse.js

Wohoo!

Complex pages with templates won't render properly yet, as noinclude /
includeonly and parser functions are not yet implemented. As a result, the
parser will run out of memory or hit the currently low expansion depth limit
as it tries to expand documentation for all templates.
Modified paths:
  • /trunk/extensions/VisualEditor/modules/parser/ext.core.TemplateHandler.js (modified) (history)
  • /trunk/extensions/VisualEditor/modules/parser/mediawiki.TokenTransformManager.js (modified) (history)
  • /trunk/extensions/VisualEditor/modules/parser/mediawiki.parser.environment.js (modified) (history)
  • /trunk/extensions/VisualEditor/modules/parser/parse.js (modified) (history)

Diff [purge]

Index: trunk/extensions/VisualEditor/modules/parser/mediawiki.TokenTransformManager.js
@@ -314,7 +314,7 @@
315315 this._construct();
316316 this._reset( args, env );
317317 // FIXME: pass actual title?
318 - this.loopCheck = new LoopCheck( null );
 318+ this.loopAndDepthCheck = new LoopAndDepthCheck( null );
319319 }
320320
321321 // Inherit from TokenTransformManager, and thus also from EventEmitter.
@@ -336,14 +336,12 @@
337337
338338 // now set up a few things on the child AsyncTokenTransformManager.
339339 var child = pipe.last;
340 - // We assume that the title was already checked against this.loopCheck
 340+ // We assume that the title was already checked against this.loopAndDepthCheck
341341 // before!
342 - child.loopCheck = new LoopCheck (
343 - this.env.normalizeTitle( this.env.tokensToString ( title ) ),
344 - this.loopCheck
 342+ child.loopAndDepthCheck = new LoopAndDepthCheck (
 343+ this.loopAndDepthCheck,
 344+ this.env.normalizeTitle( this.env.tokensToString ( title ) )
345345 );
346 - // Same for depth!
347 - child.depth = this.depth + 1;
348346 return pipe;
349347 };
350348
@@ -357,7 +355,10 @@
358356 * first stage of the pipeline, and 'last' pointing to the last stage.
359357 */
360358 AsyncTokenTransformManager.prototype.getAttributePipeline = function ( inputType, args ) {
361 - return this.childFactories.attributes( inputType, args );
 359+ var pipe = this.childFactories.attributes( inputType, args );
 360+ var child = pipe.last;
 361+ child.loopAndDepthCheck = new LoopAndDepthCheck ( this.loopAndDepthCheck, '' );
 362+ return pipe;
362363 };
363364
364365 /**
@@ -373,8 +374,6 @@
374375 this.tailAccumulator = undefined;
375376 // eventize: bend to event emitter callback
376377 this.tokenCB = this._returnTokens.bind( this );
377 - this.accum = new TokenAccumulator(null);
378 - this.firstaccum = this.accum;
379378 this.prevToken = undefined;
380379 //console.log( 'AsyncTokenTransformManager args ' + JSON.stringify( args ) );
381380 if ( ! args ) {
@@ -412,13 +411,24 @@
413412 AsyncTokenTransformManager.prototype.onChunk = function ( tokens ) {
414413 // Set top-level callback to next transform phase
415414 var res = this.transformTokens ( tokens, this.tokenCB );
416 - this.tailAccumulator = res.async;
417 - //console.log('AsyncTokenTransformManager onChunk ', tokens);
418 - //this.phase2TailCB( tokens, true );
 415+
 416+ if ( ! this.tailAccumulator ) {
 417+ this.emit( 'chunk', res.tokens );
 418+ } else {
 419+ this.tailAccumulator.push( res.tokens );
 420+ }
 421+
419422 if ( res.async ) {
 423+ this.tailAccumulator = res.async;
420424 this.tokenCB = res.async.getParentCB ( 'sibling' );
421425 }
422 - this.emit( 'chunk', res.tokens );
 426+ this.env.dp('AsyncTokenTransformManager onChunk ' + res.async);
 427+ //this.phase2TailCB( tokens, true );
 428+
 429+ // The next processed chunk should call back as a sibling to last
 430+ // accumulator, if any.
 431+ if ( res.async ) {
 432+ }
423433 };
424434
425435 /**
@@ -519,7 +529,8 @@
520530 */
521531 AsyncTokenTransformManager.prototype._returnTokens = function ( tokens, notYetDone ) {
522532 //tokens = this._transformPhase2( this.frame, tokens, this.parentCB );
523 - //console.log('AsyncTokenTransformManager._returnTokens, after _transformPhase2.');
 533+ this.env.dp('AsyncTokenTransformManager._returnTokens, emitting chunk: ' +
 534+ JSON.stringify( tokens ) );
524535
525536 this.emit( 'chunk', tokens );
526537
@@ -542,9 +553,11 @@
543554 */
544555 AsyncTokenTransformManager.prototype.onEndEvent = function () {
545556 if ( this.tailAccumulator ) {
 557+ this.env.dp( 'AsyncTokenTransformManager.onEndEvent: calling siblingDone' );
546558 this.tailAccumulator.siblingDone();
547559 } else {
548560 // nothing was asynchronous, so we'll have to emit end here.
 561+ this.env.dp( 'AsyncTokenTransformManager.onEndEvent: synchronous done' );
549562 this.emit('end');
550563 this._reset();
551564 }
@@ -650,6 +663,8 @@
651664 }
652665 }
653666 }
 667+ this.env.dp( 'SyncTokenTransformManager.onChunk: emitting ' +
 668+ JSON.stringify( localAccum ) );
654669 this.emit( 'chunk', localAccum );
655670 };
656671
@@ -685,7 +700,7 @@
686701 this.callback = callback;
687702 this.outstanding = 0;
688703 this.kvs = [];
689 - this.pipe = manager.getAttributePipeline( manager.args );
 704+ //this.pipe = manager.getAttributePipeline( manager.args );
690705 }
691706
692707 AttributeTransformManager.prototype.process = function ( attributes ) {
@@ -834,8 +849,9 @@
835850 //console.log( 'TokenAccumulator._returnTokens' );
836851 if ( reference === 'child' ) {
837852 tokens = tokens.concat( this.accum );
838 - //console.log('TokenAccumulator._returnTokens: ' +
839 - // JSON.stringify( tokens, null, 2 )
 853+ //console.log('TokenAccumulator._returnTokens child: ' +
 854+ // JSON.stringify( tokens, null, 2 ) +
 855+ // ' outstanding: ' + this.outstanding
840856 // );
841857 this.accum = [];
842858 // XXX: Use some marker to avoid re-transforming token chunks several
@@ -862,7 +878,7 @@
863879 // tokens = res.tokens.concat( this.accum );
864880 // this.accum = [];
865881 //}
866 - this.parentCB( tokens, false );
 882+ this.parentCB( tokens, this.outstanding !== 0 );
867883 return null;
868884 } else {
869885 // sibling
@@ -870,14 +886,22 @@
871887 tokens = this.accum.concat( tokens );
872888 // A sibling will transform tokens, so we don't have to do this
873889 // again.
874 - this.parentCB( res.tokens, false );
 890+ //console.log( 'TokenAccumulator._returnTokens: sibling done and parentCB ' +
 891+ // JSON.stringify( tokens ) );
 892+ this.parentCB( tokens, false );
875893 return null;
876894 } else if ( this.outstanding === 1 && notYetDone ) {
 895+ //console.log( 'TokenAccumulator._returnTokens: sibling done and parentCB but notYetDone ' +
 896+ // JSON.stringify( tokens ) );
877897 // Sibling is not yet done, but child is. Return own parentCB to
878898 // allow the sibling to go direct, and call back parent with
879899 // tokens. The internal accumulator is empty at this stage, as its
880900 // tokens are passed to the parent when the child is done.
881901 return this.parentCB( tokens, true);
 902+ } else {
 903+ this.accum = this.accum.concat( tokens );
 904+ //console.log( 'TokenAccumulator._returnTokens: sibling done, but not overall ' +
 905+ // JSON.stringify( tokens ) );
882906 }
883907
884908
@@ -913,10 +937,12 @@
914938 * @class
915939 * @constructor
916940 */
917 -function LoopCheck ( title, parent ) {
 941+function LoopAndDepthCheck ( parent, title ) {
918942 if ( parent ) {
 943+ this.depth = parent.depth + 1;
919944 this.parent = parent;
920945 } else {
 946+ this.depth = 0;
921947 this.parent = null;
922948 }
923949 this.title = title;
@@ -928,13 +954,19 @@
929955 * @method
930956 * @param {String} Title to check.
931957 */
932 -LoopCheck.prototype.check = function ( title ) {
 958+LoopAndDepthCheck.prototype.check = function ( title ) {
 959+ // XXX: set limit really low for testing!
 960+ if ( this.depth > 6 ) {
 961+ // too deep
 962+ //console.log( 'Loopcheck: ' + JSON.stringify( this, null, 2 ) );
 963+ return 'Template expansion depth limit exceeded at ';
 964+ }
933965 var elem = this;
934966 do {
935967 //console.log( 'loop check: ' + title + ' vs ' + elem.title );
936968 if ( elem.title === title ) {
937969 // Loop detected
938 - return true;
 970+ return 'Template expansion loop detected at ';
939971 }
940972 elem = elem.parent;
941973 } while ( elem );
Index: trunk/extensions/VisualEditor/modules/parser/parse.js
@@ -11,7 +11,8 @@
1212 DOMConverter = require('./mediawiki.DOMConverter.js').DOMConverter,
1313 optimist = require('optimist');
1414
15 - var parser = new ParserPipeline( new ParserEnv({ fetchTemplates: true }) );
 15+ var env = new ParserEnv( { fetchTemplates: true } ),
 16+ parser = new ParserPipeline( env );
1617
1718
1819 process.stdin.resume();
@@ -33,6 +34,12 @@
3435 process.stdout.write( output );
3536 // add a trailing newline for shell user's benefit
3637 process.stdout.write( "\n" );
 38+
 39+ if ( env.debug ) {
 40+ // Also print out the html
 41+ process.stderr.write( document.body.innerHTML );
 42+ process.stderr.write( "\n" );
 43+ }
3744 process.exit(0);
3845 });
3946 // Kick off the pipeline by feeding the input into the parser pipeline
Index: trunk/extensions/VisualEditor/modules/parser/ext.core.TemplateHandler.js
@@ -22,7 +22,6 @@
2323 }
2424
2525 TemplateHandler.prototype.reset = function ( token ) {
26 - this.resultTokens = [];
2726 return {token: token};
2827 };
2928
@@ -56,8 +55,6 @@
5756 //console.log('onTemplate! ' + JSON.stringify( token, null, 2 ) +
5857 // ' args: ' + JSON.stringify( this.manager.args ));
5958
60 - this.parentCB = cb;
61 -
6259 // check for 'subst:'
6360 // check for variable magic names
6461 // check for msg, msgnw, raw magics
@@ -67,10 +64,12 @@
6865 var templateTokenTransformData = {
6966 args: {},
7067 manager: this.manager,
71 - outstanding: 1, // Avoid premature finish
7268 cb: cb,
7369 origToken: token,
74 - isAsync: false
 70+ resultTokens: [],
 71+ attribsAsync: true,
 72+ overallAsync: false,
 73+ expandDone: false
7574 },
7675 transformCB,
7776 i = 0,
@@ -89,17 +88,22 @@
9089 ).process( attributes );
9190
9291 // Unblock finish
93 - templateTokenTransformData.outstanding--;
94 - if ( templateTokenTransformData.outstanding === 0 ) {
95 - //console.log( 'direct call');
 92+ if ( ! templateTokenTransformData.attribsAsync ) {
 93+ // Attributes were transformed synchronously
 94+ this.manager.env.dp( 'sync attribs for ' + JSON.stringify( token ));
9695 // All attributes are fully expanded synchronously (no IO was needed)
9796 return this._expandTemplate ( templateTokenTransformData );
9897 } else {
99 - templateTokenTransformData.isAsync = true;
 98+ // Async attribute expansion is going on
 99+ this.manager.env.dp( 'async return for ' + JSON.stringify( token ));
 100+ templateTokenTransformData.overallAsync = true;
100101 return { async: true };
101102 }
102103 };
103104
 105+/**
 106+ * Create positional (number) keys for arguments without explicit keys
 107+ */
104108 TemplateHandler.prototype._nameArgs = function ( orderedArgs ) {
105109 var n = 1,
106110 out = [];
@@ -115,37 +119,49 @@
116120 return out;
117121 };
118122
 123+/**
 124+ * Callback for argument (including target) expansion in AttributeTransformManager
 125+ */
119126 TemplateHandler.prototype._returnAttributes = function ( templateTokenTransformData,
120127 attributes )
121128 {
122 - //console.log( 'TemplateHandler._returnAttributes: ' + JSON.stringify(attributes) );
 129+ this.manager.env.dp( 'TemplateHandler._returnAttributes: ' + JSON.stringify(attributes) );
123130 // Remove the target from the attributes
 131+ templateTokenTransformData.attribsAsync = false;
124132 templateTokenTransformData.target = attributes[0][1];
125133 attributes.shift();
126134 templateTokenTransformData.expandedArgs = attributes;
127 - if ( templateTokenTransformData.isAsync ) {
 135+ if ( templateTokenTransformData.overallAsync ) {
128136 this._expandTemplate ( templateTokenTransformData );
129137 }
130138 };
131139
132140 /**
133141 * Fetch, tokenize and token-transform a template after all arguments and the
134 - * target were expanded in frame.
 142+ * target were expanded.
135143 */
136144 TemplateHandler.prototype._expandTemplate = function ( templateTokenTransformData ) {
137145 //console.log('TemplateHandler.expandTemplate: ' +
138146 // JSON.stringify( templateTokenTransformData, null, 2 ) );
 147+
 148+ if ( ! templateTokenTransformData.target ) {
 149+ this.manager.env.dp( 'No target! ' +
 150+ JSON.stringify( templateTokenTransformData, null, 2 ) );
 151+ console.trace();
 152+ }
 153+
139154 // First, check the target for loops
140155 var target = this.manager.env.normalizeTitle(
141156 this.manager.env.tokensToString( templateTokenTransformData.target )
142157 );
143 - if( this.manager.loopCheck.check( target ) ) {
 158+ var checkRes = this.manager.loopAndDepthCheck.check( target );
 159+ if( checkRes ) {
144160 // Loop detected, abort!
145161 return {
146162 tokens: [
147163 {
148164 type: 'TEXT',
149 - value: 'Template loop detected: '
 165+ value: checkRes
150166 },
151167 {
152168 type: 'TAG',
@@ -172,6 +188,8 @@
173189 //console.log( 'expanded args: ' +
174190 // JSON.stringify( this.manager.env.KVtoHash(
175191 // templateTokenTransformData.expandedArgs ) ) );
 192+ //console.log( 'templateTokenTransformData: ' +
 193+ // JSON.stringify( templateTokenTransformData , null ,2 ) );
176194
177195 var inputPipeline = this.manager.newChildPipeline(
178196 this.manager.inputType || 'text/wiki',
@@ -179,9 +197,10 @@
180198 templateTokenTransformData.target
181199 );
182200
183 - // Hook up the inputPipeline output events to call back our parentCB.
184 - inputPipeline.addListener( 'chunk', this._onChunk.bind ( this ) );
185 - inputPipeline.addListener( 'end', this._onEnd.bind ( this ) );
 201+ // Hook up the inputPipeline output events to call back the parent
 202+ // callback.
 203+ inputPipeline.addListener( 'chunk', this._onChunk.bind ( this, templateTokenTransformData ) );
 204+ inputPipeline.addListener( 'end', this._onEnd.bind ( this, templateTokenTransformData ) );
186205
187206
188207 // Resolve a possibly relative link
@@ -189,9 +208,11 @@
190209 target,
191210 'Template'
192211 );
193 - this._fetchTemplateAndTitle( templateName,
194 - this._processTemplateAndTitle.bind( this, inputPipeline )
195 - );
 212+ this._fetchTemplateAndTitle(
 213+ templateName,
 214+ this._processTemplateAndTitle.bind( this, inputPipeline ),
 215+ templateTokenTransformData
 216+ );
196217
197218 // Set up a pipeline:
198219 // fetch template source -> tokenizer
@@ -213,30 +234,41 @@
214235 // fetch from DB or interwiki
215236 // infinte loop check
216237
217 - if ( this.isAsync ) {
 238+ if ( templateTokenTransformData.overallAsync ||
 239+ ! templateTokenTransformData.expandDone ) {
 240+ templateTokenTransformData.overallAsync = true;
 241+ this.manager.env.dp( 'Async return from _expandTemplate for ' +
 242+ JSON.stringify ( templateTokenTransformData.target ) );
218243 return { async: true };
219244 } else {
220 - return this.result;
 245+ this.manager.env.dp( 'Sync return from _expandTemplate for ' +
 246+ JSON.stringify( templateTokenTransformData.target ) + ' : ' +
 247+ JSON.stringify( templateTokenTransformData.result )
 248+ );
 249+ return templateTokenTransformData.result;
221250 }
222251 };
223252
224253
225254 /**
226 - * Convert AsyncTokenTransformManager output chunks to parent callbacks
 255+ * Handle chunk emitted from the input pipeline after feeding it a template
227256 */
228 -TemplateHandler.prototype._onChunk = function( chunk ) {
 257+TemplateHandler.prototype._onChunk = function( data, chunk ) {
229258 // We encapsulate the output by default, so collect tokens here.
230 - this.resultTokens = this.resultTokens.concat( chunk );
 259+ this.manager.env.dp( 'TemplateHandler._onChunk' + JSON.stringify( chunk ) );
 260+ data.resultTokens = data.resultTokens.concat( chunk );
231261 };
232262
233263 /**
234 - * Handle the end event emitted by the parser pipeline by calling our parentCB
235 - * with notYetDone set to false.
 264+ * Handle the end event emitted by the parser pipeline after fully processing
 265+ * the template source.
236266 */
237 -TemplateHandler.prototype._onEnd = function( token ) {
 267+TemplateHandler.prototype._onEnd = function( data, token ) {
238268 // Encapsulate the template in a single token, which contains all the
239269 // information needed for the editor.
240 - var res = this.resultTokens;
 270+ this.manager.env.dp( 'TemplateHandler._onEnd' + JSON.stringify( data.resultTokens ) );
 271+ data.expandDone = true;
 272+ var res = data.resultTokens;
241273 // Remove 'end' token from end
242274 if ( res.length && res[res.length - 1].type === 'END' ) {
243275 res.pop();
@@ -257,11 +289,14 @@
258290 */
259291 //console.log( 'TemplateHandler._onEnd: ' + JSON.stringify( res, null, 2 ) );
260292
261 - if ( this.isAsync ) {
262 - this.parentCB( res, false );
 293+ if ( data.overallAsync ) {
 294+ this.manager.env.dp( 'TemplateHandler._onEnd: calling back with res:' +
 295+ JSON.stringify( res ) );
 296+ data.cb( res, false );
263297 } else {
264 - this.result = { tokens: res };
265 - this.reset();
 298+ this.manager.env.dp( 'TemplateHandler._onEnd: synchronous return!' );
 299+ data.result = { tokens: res };
 300+ //data.reset();
266301 }
267302 };
268303
@@ -272,7 +307,7 @@
273308 */
274309 TemplateHandler.prototype._processTemplateAndTitle = function( pipeline, src, title ) {
275310 // Feed the pipeline. XXX: Support different formats.
276 - //console.log( 'TemplateHandler._processTemplateAndTitle: ' + src );
 311+ this.manager.env.dp( 'TemplateHandler._processTemplateAndTitle: ' + src );
277312 pipeline.process ( src );
278313 };
279314
@@ -281,7 +316,7 @@
282317 /**
283318 * Fetch a template
284319 */
285 -TemplateHandler.prototype._fetchTemplateAndTitle = function( title, callback ) {
 320+TemplateHandler.prototype._fetchTemplateAndTitle = function( title, callback, data ) {
286321 // @fixme normalize name?
287322 var self = this;
288323 if (title in this.manager.env.pageCache) {
@@ -292,8 +327,8 @@
293328 } else {
294329 // whee fun hack!
295330
296 - this.isAsync = true;
297 - console.log( 'trying to fetch ' + title );
 331+ data.overallAsync = true;
 332+ this.manager.env.dp( 'trying to fetch ' + title );
298333 //console.log(this.manager.env.pageCache);
299334 var url = this.manager.env.wgScriptPath + '/api' +
300335 this.manager.env.wgScriptExtension +
@@ -301,46 +336,48 @@
302337
303338 request({
304339 method: 'GET',
305 - //followRedirect: false,
 340+ followRedirect: true,
306341 url: url,
307342 headers: {
308343 'User-Agent': 'Mozilla/5.0 (X11; Linux x86_64; rv:9.0.1) Gecko/20100101 Firefox/9.0.1 Iceweasel/9.0.1'
309344 }
310345 },
311346 function (error, response, body) {
312 - console.log( 'response for ' + title + ': ' + body );
 347+ //console.log( 'response for ' + title + ' :' + body + ':' );
313348 if(error) {
314 - console.log(error);
315 - callback('Page/template fetch failure for title ' + title);
 349+ self.manager.env.dp(error);
 350+ callback('Page/template fetch failure for title ' + title, title);
316351 return ;
317352 }
318353
319354 if(response.statusCode == 200) {
320 - try{
 355+ var src = '';
 356+ try {
321357 //console.log( 'body: ' + body );
322 - data = JSON.parse(body);
323 - var src = null;
 358+ var data = JSON.parse( body );
 359+ } catch(e) {
 360+ console.log( "Error: while parsing result. Error was: " );
 361+ console.log( e );
 362+ console.log( "Response that didn't parse was:");
 363+ console.log( "------------------------------------------\n" + body );
 364+ console.log( "------------------------------------------" );
 365+ }
 366+ try {
324367 $.each(data.query.pages, function(i, page) {
325368 if (page.revisions && page.revisions.length) {
326369 src = page.revisions[0]['*'];
327370 title = page.title;
328371 }
329372 });
330 - console.log( 'Page ' + title + ': got ' + src );
331 - self.manager.env.pageCache[title] = src;
332 - callback(src, title);
333 - } catch(e) {
334 - console.log("Error: while parsing result. Error was: ");
335 - console.log(e);
336 - console.log("Response that didn't parse was:\n" + body);
337 -
338 - data = {
339 - error: '',
340 - errorWfMsg: 'chat-err-communicating-with-mediawiki',
341 - errorMsgParams: []
342 - };
 373+ } catch ( e ) {
 374+ console.log( 'Did not find page revisions in the returned body:' + body );
 375+ src = '';
343376 }
344 - console.log(data);
 377+ //console.log( 'Page ' + title + ': got ' + src );
 378+ self.manager.env.dp( 'Success for ' + title + ' :' + body + ':' );
 379+ self.manager.env.pageCache[title] = src;
 380+ callback(src, title);
 381+ self.manager.env.dp(data);
345382 }
346383 });
347384
@@ -389,6 +426,8 @@
390427 };
391428
392429
 430+/*********************** Template argument expansion *******************/
 431+
393432 /**
394433 * Expand template arguments with tokens from the containing frame.
395434 */
Index: trunk/extensions/VisualEditor/modules/parser/mediawiki.parser.environment.js
@@ -29,6 +29,10 @@
3030 };
3131
3232 MWParserEnvironment.prototype.KVtoHash = function ( kvs ) {
 33+ if ( ! kvs ) {
 34+ console.log( "Invalid kvs!: " + JSON.stringify( kvs, null, 2 ) );
 35+ return {};
 36+ }
3337 var res = {};
3438 for ( var i = 0, l = kvs.length; i < l; i++ ) {
3539 var kv = kvs[i],
@@ -106,13 +110,21 @@
107111 }
108112 for ( var i = 0, l = tokens.length; i < l; i++ ) {
109113 var token = tokens[i];
110 - //console.log( 'MWParserEnvironment.tokensToString, token: ' + JSON.stringify( token ) );
 114+ if ( ! token ) {
 115+ console.trace();
 116+ console.log( 'MWParserEnvironment.tokensToString, invalid token: ' +
 117+ JSON.stringify( token ) );
 118+ continue;
 119+ }
111120 if ( token.type === 'TEXT' ) {
112121 out.push( token.value );
 122+ } else if ( token.type === 'COMMENT' || token.type === 'NEWLINE' ) {
 123+ // strip comments and newlines
113124 } else {
114125 var tstring = JSON.stringify( token );
115 - //console.log ( 'MWParserEnvironment.tokensToString, non-text token: ' + tstring );
116 - //out.push( tstring );
 126+ console.log ( 'MWParserEnvironment.tokensToString, non-text token: ' +
 127+ tstring + JSON.stringify( tokens, null, 2 ) );
 128+ out.push( tstring );
117129 }
118130 }
119131 //console.log( 'MWParserEnvironment.tokensToString result: ' + out.join('') );
@@ -120,7 +132,17 @@
121133 };
122134
123135
 136+/**
 137+ * Simple debug helper
 138+ */
 139+MWParserEnvironment.prototype.dp = function ( ) {
 140+ if ( this.debug ) {
 141+ console.log( JSON.stringify( arguments, null, 2 ) );
 142+ }
 143+};
124144
 145+
 146+
125147 if (typeof module == "object") {
126148 module.exports.MWParserEnvironment = MWParserEnvironment;
127149 }

Status & tagging log