r108485 MediaWiki - Code Review archive

Repository:MediaWiki
Revision:r108484‎ | r108485 | r108486 >
Date:01:09, 10 January 2012
Author:gwicke
Status:deferred
Tags:
Comment:
More token transform and pipeline setup refactoring to support template
expansion better.
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/mediawiki.parser.js (modified) (history)
  • /trunk/extensions/VisualEditor/modules/parser/parse.js (modified) (history)
  • /trunk/extensions/VisualEditor/tests/parser/parserTests.js (modified) (history)

Diff [purge]

Index: trunk/extensions/VisualEditor/tests/parser/parserTests.js
@@ -58,7 +58,7 @@
5959
6060 var testWhiteList = require('./parserTests-whitelist.js').testWhiteList;
6161
62 -//_import(pj('parser', 'mediawiki.parser.environment.js'), ['MWParserEnvironment']);
 62+_import(pj('parser', 'mediawiki.parser.environment.js'), ['MWParserEnvironment']);
6363 _import(pj('parser', 'mediawiki.parser.js'), ['ParserPipeline']);
6464
6565 // WikiDom and serializers
@@ -552,11 +552,8 @@
553553 // }
554554 //});
555555
556 - // move this config out of here
557 - var config = {
558 - parserEnv: {}
559 - };
560 - var parserPipeline = new ParserPipeline( config );
 556+ var env = new MWParserEnvironment({});
 557+ var parserPipeline = new ParserPipeline( env );
561558
562559 var comments = [],
563560 pt = this;
Index: trunk/extensions/VisualEditor/modules/parser/mediawiki.TokenTransformManager.js
@@ -303,12 +303,12 @@
304304 * @param {Object} args, the argument map for templates
305305 * @param {Object} env, the environment.
306306 */
307 -function AsyncTokenTransformManager ( childFactory, args, env ) {
 307+function AsyncTokenTransformManager ( childFactories, args, env ) {
308308 // Factory function for new AsyncTokenTransformManager creation with
309309 // default transforms enabled
310310 // Also sets up a tokenizer and phase-1-transform depending on the input format
311311 // nestedAsyncTokenTransformManager = manager.newChildPipeline( inputType, args );
312 - this.childFactory = childFactory;
 312+ this.childFactories = childFactories;
313313 this._construct();
314314 this._reset( args, env );
315315 }
@@ -326,12 +326,33 @@
327327 * @returns {Object} Pipeline, which is an object with 'first' pointing to the
328328 * first stage of the pipeline, and 'last' pointing to the last stage.
329329 */
330 -AsyncTokenTransformManager.prototype.newChildPipeline = function ( inputType, args ) {
331 - var pipe = this.childFactory( inputType, args );
 330+AsyncTokenTransformManager.prototype.newChildPipeline = function ( inputType, args, title ) {
 331+ var pipe = this.childFactories.input( inputType, args );
 332+
 333+ // now set up a few things on the child AsyncTokenTransformManager.
 334+ var child = pipe.last;
 335+ // We assume that the title was already checked against this.loopCheck
 336+ // before!
 337+ child.loopCheck = new LoopCheck ( this.loopCheck, title );
 338+ // Same for depth!
 339+ child.depth = this.depth + 1;
332340 return pipe;
333341 };
334342
335343 /**
 344+ * Create a pipeline for attribute transformations.
 345+ *
 346+ * @method
 347+ * @param {String} Input type, currently only support 'text/wiki'.
 348+ * @param {Object} Template arguments
 349+ * @returns {Object} Pipeline, which is an object with 'first' pointing to the
 350+ * first stage of the pipeline, and 'last' pointing to the last stage.
 351+ */
 352+AsyncTokenTransformManager.prototype.newAttributePipeline = function ( inputType, args ) {
 353+ return this.childFactories.attributes( inputType, args );
 354+};
 355+
 356+/**
336357 * Reset the internal token and outstanding-callback state of the
337358 * TokenTransformManager, but keep registrations untouched.
338359 *
@@ -547,7 +568,7 @@
548569 localAccum = [],
549570 localAccumLength = 0,
550571 tokensLength = tokens.length,
551 - cb = undefined, // XXX: not meaningful for purely synchronous processing!
 572+ cb, // XXX: not meaningful for purely synchronous processing!
552573 token,
553574 // Top-level frame only in phase 3, as everything is already expanded.
554575 ts = this.transformers;
@@ -613,10 +634,43 @@
614635 };
615636
616637
 638+/********************** AttributeTransformManager *************************/
617639
 640+/**
 641+ * Utility transformation manager for attributes, using an attribute
 642+ * transformation pipeline (normally phase1 SyncTokenTransformManager and
 643+ * phase2 AsyncTokenTransformManager). This pipeline needs to be independent
 644+ * of the containing TokenTransformManager to isolate transforms from each
 645+ * other.
 646+ *
 647+ * @class
 648+ * @constructor
 649+ * @param {Object} Containing TokenTransformManager
 650+ */
 651+function AttributeTransformManager ( manager, callback ) {
 652+ this.callback = callback;
 653+ var pipe = manager.newAttributePipeline( manager.args );
 654+ pipe.addListener( 'chunk', this.onChunk.bind( this ) );
 655+ pipe.addListener( 'end', this.onEnd.bind( this ) );
 656+}
618657
 658+/**
 659+ * Collect chunks returned from the pipeline
 660+ */
 661+AttributeTransformManager.prototype.onChunk = function ( chunk ) {
 662+ this.callback( chunk, true );
 663+};
619664
 665+/**
 666+ * Empty the pipeline by returning to the parent
 667+ */
 668+AttributeTransformManager.prototype.onEnd = function ( ) {
 669+ this.callback( [], false );
 670+};
620671
 672+
 673+
 674+
621675 /******************************* TokenAccumulator *************************/
622676 /**
623677 * Token accumulators buffer tokens between asynchronous processing points,
@@ -730,6 +784,43 @@
731785 };
732786
733787
 788+/**
 789+ * Loop check helper class for AsyncTokenTransformManager.
 790+ *
 791+ * We use a bottom-up linked list to allow sharing of paths between async
 792+ * expansions.
 793+ *
 794+ * @class
 795+ * @constructor
 796+ */
 797+function LoopCheck ( parent, title ) {
 798+ if ( parent ) {
 799+ this.parent = parent;
 800+ } else {
 801+ this.parent = null;
 802+ }
 803+ this.title = title;
 804+}
 805+
 806+/**
 807+ * Check if expanding <title> would lead to a loop.
 808+ *
 809+ * @method
 810+ * @param {String} Title to check.
 811+ */
 812+LoopCheck.prototype.check = function ( title ) {
 813+ var elem = this;
 814+ do {
 815+ if ( elem.title === title ) {
 816+ // Loop detected
 817+ return true;
 818+ }
 819+ elem = elem.parent;
 820+ } while ( elem );
 821+ // No loop detected.
 822+ return false;
 823+};
 824+
734825 if (typeof module == "object") {
735826 module.exports.AsyncTokenTransformManager = AsyncTokenTransformManager;
736827 module.exports.SyncTokenTransformManager = SyncTokenTransformManager;
Index: trunk/extensions/VisualEditor/modules/parser/parse.js
@@ -7,9 +7,10 @@
88 ( function() {
99
1010 var ParserPipeline = require('./mediawiki.parser.js').ParserPipeline,
 11+ ParserEnv = require('./mediawiki.parser.environment.js').MWParserEnvironment,
1112 optimist = require('optimist');
1213
13 - var parser = new ParserPipeline();
 14+ var parser = new ParserPipeline( new ParserEnv({}) );
1415
1516
1617 process.stdin.resume();
Index: trunk/extensions/VisualEditor/modules/parser/mediawiki.parser.js
@@ -112,35 +112,64 @@
113113 * always an AsyncTokenTransformManager, which emits its output in events.
114114 */
115115 ParserPipeline.prototype.makeInputPipeline = function ( inputType, args ) {
116 - if ( inputType === 'text/wiki' ) {
117 - var wikiTokenizer = new PegTokenizer();
 116+ switch ( inputType ) {
 117+ case 'text/wiki':
 118+ var wikiTokenizer = new PegTokenizer();
118119
119 - /**
120 - * Token stream transformations.
121 - * This is where all the wiki-specific functionality is implemented.
122 - * See https://www.mediawiki.org/wiki/Future/Parser_development/Token_stream_transformations
123 - */
124 - var tokenPreProcessor = new TokenTransformManager.SyncTokenTransformManager ( this.env );
125 - tokenPreProcessor.listenForTokensFrom ( wikiTokenizer );
 120+ /**
 121+ * Token stream transformations.
 122+ * This is where all the wiki-specific functionality is implemented.
 123+ * See https://www.mediawiki.org/wiki/Future/Parser_development/Token_stream_transformations
 124+ */
 125+ // XXX: Use this.env.config.transforms['inputType'][stage] or
 126+ // somesuch to set up the transforms by input type
 127+ var tokenPreProcessor = new TokenTransformManager.SyncTokenTransformManager ( this.env );
 128+ tokenPreProcessor.listenForTokensFrom ( wikiTokenizer );
126129
127 - var tokenExpander = new TokenTransformManager.AsyncTokenTransformManager (
128 - this.makeInputPipeline.bind( this ), args, this.env );
129 - tokenExpander.listenForTokensFrom ( tokenPreProcessor );
130 -
131 - return { first: wikiTokenizer, last: tokenExpander };
132 - } else {
133 - throw "ParserPipeline.makeInputPipeline: Unsupported input type " + inputType;
 130+ var tokenExpander = new TokenTransformManager.AsyncTokenTransformManager (
 131+ {
 132+ 'input': this.makeInputPipeline.bind( this ),
 133+ 'attributes': this.makeAttributePipeline.bind( this )
 134+ },
 135+ args, this.env
 136+ );
 137+ tokenExpander.listenForTokensFrom ( tokenPreProcessor );
 138+
 139+ return { first: wikiTokenizer, last: tokenExpander };
 140+
 141+ default:
 142+ throw "ParserPipeline.makeInputPipeline: Unsupported input type " + inputType;
134143 }
135144 };
136145
137146
138147 /**
139 - * Parse an input
 148+ * Factory for attribute transformations, with input type implicit in the
 149+ * environment.
 150+ */
 151+ParserPipeline.prototype.makeAttributePipeline = function ( args ) {
 152+ /**
 153+ * Token stream transformations.
 154+ * This is where all the wiki-specific functionality is implemented.
 155+ * See https://www.mediawiki.org/wiki/Future/Parser_development/Token_stream_transformations
 156+ */
 157+ var tokenPreProcessor = new TokenTransformManager.SyncTokenTransformManager ( this.env );
 158+ var tokenExpander = new TokenTransformManager.AsyncTokenTransformManager (
 159+ this.makeInputPipeline.bind( this ), args, this.env );
 160+ tokenExpander.listenForTokensFrom ( tokenPreProcessor );
 161+
 162+ return { first: tokenPreProcessor, last: tokenExpander };
 163+};
 164+
 165+
 166+/**
 167+ * Feed the parser pipeline with some input, the output is emitted in events.
140168 *
141169 * @method
142170 * @param {Mixed} All arguments are passed through to the underlying input
143171 * pipeline's first element's process() method. For a wikitext pipeline (the
144 - * default), this would be the wikitext to tokenize.
 172+ * default), this would be the wikitext to tokenize:
 173+ * pipeline.parse ( wikiText );
145174 */
146175 ParserPipeline.prototype.parse = function ( ) {
147176 // Set the pipeline in motion by feeding the first element with the given
Index: trunk/extensions/VisualEditor/modules/parser/ext.core.TemplateHandler.js
@@ -45,36 +45,44 @@
4646 * processes the template.
4747 */
4848 TemplateHandler.prototype.onTemplate = function ( token, cb ) {
 49+
 50+ this.parentCB = cb;
 51+ this.origToken = token;
 52+
4953 // check for 'subst:'
5054 // check for variable magic names
5155 // check for msg, msgnw, raw magics
5256 // check for parser functions
5357
54 - // create a new frame for argument and title expansions
55 - var newFrame = {
 58+ // create a new temporary frame for argument and title expansions
 59+ var templateExpandData = {
5660 args: {},
5761 env: frame.env,
58 - target: token.attribs[0][1], // XXX: use data-target key instead!
59 - // Also handle templates and args in
60 - // target!
6162 outstanding: 0,
62 - cb: cb
 63+ cb: cb,
 64+ origToken: token
6365 },
64 - argcb,
 66+ transformCB,
6567 i = 0,
6668 kvs = [],
6769 res,
6870 kv;
6971 // XXX: transform the target
 72+ transformCB = this._returnArgValue.bind( this, { frame: templateExpandData } );
 73+ res = this.manager.transformTokens( token.target, transformCB );
 74+ if ( res.async ) {
 75+ newFrame.outstanding++;
 76+ }
 77+ newFrame.target = res.tokens;
7078
71 -
 79+
7280 // transform each argument (key and value), and handle asynchronous returns
7381 for ( var key in token.args ) {
7482 if ( token.hasOwnProperty( key ) ) {
7583 kv = { key: [], value: [] };
7684 // transform the value
77 - argCB = this._returnArgValue.bind( this, { index: i, frame: newFrame } );
78 - res = frame.transformTokens( frame, args[key], argCB );
 85+ transformCB = this._returnArgValue.bind( this, { frame: templateExpandData, index: i } );
 86+ res = this.manager.transformTokens( args[key], transformCB );
7987 if ( res.async ) {
8088 newFrame.outstanding++;
8189 }
@@ -90,6 +98,9 @@
9199 i++;
92100 }
93101 }
 102+
 103+ // Move the above to AttributeTransformer class
 104+
94105
95106 if ( newFrame.outstanding === 0 ) {
96107 return this._expandTemplate ( newFrame );
@@ -102,9 +113,8 @@
103114 * Callback for async argument value expansions
104115 */
105116 TemplateHandler.prototype._returnArgValue = function ( ref, tokens, notYetDone ) {
106 - var frame = ref.frame,
107 - res;
108 - frame.args[ref.index].push( tokens );
 117+ var frame = ref.frame;
 118+ frame.args[ref.index].value.push( tokens );
109119 if ( ! notYetDone ) {
110120 frame.outstanding--;
111121 if ( frame.outstanding === 0 ) {
@@ -115,17 +125,60 @@
116126 };
117127
118128 /**
 129+ * Callback for async argument key expansions
 130+ */
 131+TemplateHandler.prototype._returnArgKey = function ( ref, tokens, notYetDone ) {
 132+ var frame = ref.frame;
 133+ frame.args[ref.index].key.push( tokens );
 134+ if ( ! notYetDone ) {
 135+ frame.outstanding--;
 136+ if ( frame.outstanding === 0 ) {
 137+ // this calls back to frame.cb, so no return here.
 138+ this._expandTemplate( frame );
 139+ }
 140+ }
 141+};
 142+
 143+/**
 144+ * Callback for async target expansion
 145+ */
 146+TemplateHandler.prototype._returnTarget = function ( ref, tokens, notYetDone ) {
 147+ var frame = ref.frame;
 148+ frame.target.push( tokens );
 149+ if ( ! notYetDone ) {
 150+ frame.outstanding--;
 151+ if ( frame.outstanding === 0 ) {
 152+ // this calls back to frame.cb, so no return here.
 153+ this._expandTemplate( frame );
 154+ }
 155+ }
 156+};
 157+
 158+/**
119159 * Fetch, tokenize and token-transform a template after all arguments and the
120160 * target were expanded in frame.
121161 */
122162 TemplateHandler.prototype._expandTemplate = function ( frame ) {
 163+ // First, check the target for loops
 164+ this.manager.loopCheck.check( frame.target );
 165+
123166 // Create a new nested transformation pipeline for the input type
124167 // (includes the tokenizer and synchronous stage-1 transforms for
125168 // 'text/wiki' input).
126169 // Returned pipe (for now):
127170 // { first: tokenizer, last: AsyncTokenTransformManager }
128 - var pipe = this.manager.newChildPipeline( inputType, args );
 171+ this.inputPipeline = this.manager.newChildPipeline( inputType, args );
129172
 173+ // Hook up the AsyncTokenTransformManager output events to call back our
 174+ // parentCB.
 175+ this.inputPipeline.last.addListener( 'chunk', this._onChunk.bind ( this ) );
 176+ this.inputPipeline.last.addListener( 'end', this._onEnd.bind ( this ) );
 177+
 178+
 179+ // Resolve a possibly relative link
 180+ var templateName = this.env.resolveTitle( this.target, 'Template' );
 181+ this._fetchTemplateAndTitle( templateName, this._processTemplateAndTitle.bind( this ) );
 182+
130183 // Set up a pipeline:
131184 // fetch template source -> tokenizer
132185 // getInputPipeline( inputType )
@@ -151,9 +204,45 @@
152205
153206
154207 /**
 208+ * Convert AsyncTokenTransformManager output chunks to parent callbacks
 209+ */
 210+TemplateHandler.prototype._onChunk = function( chunk ) {
 211+ // We encapsulate the output by default, so collect tokens here.
 212+ this.resultTokens = this.resultTokens.concat( chunk );
 213+};
 214+
 215+/**
 216+ * Handle the end event by calling our parentCB with notYetDone set to false.
 217+ */
 218+TemplateHandler.prototype._onEnd = function( ) {
 219+ // Encapsulate the template in a single token, which contains all the
 220+ // information needed for the editor.
 221+ var res = {
 222+ type: 'container',
 223+ tokens: this.resultTokens, // The editor needs HTML serialization instead
 224+ args: this.manager.args, // Here, the editor needs wikitext.
 225+ attribs: this.origToken.attribs // Hmm..
 226+
 227+ };
 228+ this.parentCB( res, false );
 229+};
 230+
 231+
 232+
 233+/**
 234+ * Process a fetched template source
 235+ */
 236+TemplateHandler.prototype._processTemplateAndTitle = function( src, title ) {
 237+ // Feed the pipeline. XXX: Support different formats.
 238+ this.inputPipeline.process ( src );
 239+};
 240+
 241+
 242+
 243+/**
155244 * Fetch a template
156245 */
157 -TemplateHandler.prototype._fetchTemplateAndTitle = function( title, frame, callback ) {
 246+TemplateHandler.prototype._fetchTemplateAndTitle = function( title, callback ) {
158247 // @fixme normalize name?
159248 if (title in this.pageCache) {
160249 // @fixme should this be forced to run on next event?
@@ -163,7 +252,7 @@
164253 console.log(title);
165254 console.log(this.pageCache);
166255 $.ajax({
167 - url: frame.env.wgScriptPath + '/api' + frame.env.wgScriptExtension,
 256+ url: this.manager.env.wgScriptPath + '/api' + this.manager.env.wgScriptExtension,
168257 data: {
169258 format: 'json',
170259 action: 'query',
Index: trunk/extensions/VisualEditor/modules/parser/mediawiki.parser.environment.js
@@ -13,487 +13,64 @@
1414 this.domCache = options.domCache;
1515 };
1616
17 -$.extend(MWParserEnvironment.prototype, {
18 - // Does this need separate UI/content inputs?
19 - formatNum: function( num ) {
20 - return num + '';
21 - },
22 -
23 - getVariable: function( varname, options ) {
24 - //
25 - },
26 -
27 - /**
28 - * @return MWParserFunction
29 - */
30 - getParserFunction: function( name ) {
31 - if (name in this.parserFunctions) {
32 - return new this.parserFunctions[name]( this );
33 - } else {
34 - return null;
 17+MWParserEnvironment.prototype.lookupKV = function ( kvs, key ) {
 18+ var kv;
 19+ for ( var i = 0, l = kvs.length; i < l; i++ ) {
 20+ kv = kvs[i];
 21+ if ( kv[0] === key ) {
 22+ // found, return it.
 23+ return kv[1];
3524 }
36 - },
37 -
38 - /**
39 - * @return MWParserTagHook
40 - */
41 - getTagHook: function( name ) {
42 - if (name in this.tagHooks) {
43 - return new this.tagHooks[name](this);
44 - } else {
45 - return null;
46 - }
47 - },
48 -
49 - /**
50 - * @fixme do this for real eh
51 - */
52 - resolveTitle: function( name, namespace ) {
53 - // hack!
54 - if (name.indexOf(':') == -1 && typeof namespace ) {
55 - // hack hack hack
56 - name = namespace + ':' + name;
57 - }
58 - return name;
59 - },
60 -
61 - /**
62 - * Async.
63 - *
64 - * @todo make some optimizations for fetching multiple at once
65 - *
66 - * @param string name
67 - * @param function(text, err) callback
68 - */
69 - fetchTemplate: function( title, callback ) {
70 - this.fetchTemplateAndTitle( title, function( text, title, err ) {
71 - callback(title, err);
72 - });
73 - },
74 -
75 - fetchTemplateAndTitle: function( title, callback ) {
76 - // @fixme normalize name?
77 - if (title in this.pageCache) {
78 - // @fixme should this be forced to run on next event?
79 - callback( this.pageCache[title], title );
80 - } else {
81 - // whee fun hack!
82 - console.log(title);
83 - console.log(this.pageCache);
84 - $.ajax({
85 - url: wgScriptPath + '/api' + wgScriptExtension,
86 - data: {
87 - format: 'json',
88 - action: 'query',
89 - prop: 'revisions',
90 - rvprop: 'content',
91 - titles: name
92 - },
93 - success: function(data, xhr) {
94 - var src = null, title = null;
95 - $.each(data.query.pages, function(i, page) {
96 - if (page.revisions && page.revisions.length) {
97 - src = page.revisions[0]['*'];
98 - title = page.title;
99 - }
100 - });
101 - if (typeof src !== 'string') {
102 - callback(null, null, 'Page not found');
103 - } else {
104 - callback(src, title);
105 - }
106 - },
107 - error: function(msg) {
108 - callback(null, null, 'Page/template fetch failure');
109 - },
110 - dataType: 'json',
111 - cache: false // @fixme caching, versions etc?
112 - }, 'json');
113 - }
114 - },
115 -
116 - getTemplateDom: function( title, callback ) {
117 - var self = this;
118 - if (title in this.domCache) {
119 - callback(this.domCache[title], null);
120 - return;
121 - }
122 - this.fetchTemplateAndTitle( title, function( text, title, err ) {
123 - if (err) {
124 - callback(null, err);
125 - return;
126 - }
127 - self.pageCache[title] = text;
128 - self.parser.parseToTree( text, function( templateTree, err ) {
129 - this.domCache[title] = templateTree;
130 - callback(templateTree, err);
131 - });
132 - });
133 - },
134 -
135 - braceSubstitution: function( templateNode, frame, callback ) {
136 - // stuff in Parser.braceSubstitution
137 - // expand/flatten the 'title' piece (to get the template reference)
138 - var self = this;
139 - frame.flatten(self.resolveTitle(templateNode.name, 'Template'), function(templateName, err) {
140 - if (err) {
141 - callback(null, err);
142 - return;
143 - }
144 - var out = {
145 - type: 'placeholder',
146 - orig: templateNode,
147 - contents: []
148 - };
149 -
150 - // check for 'subst:'
151 - // check for variable magic names
152 - // check for msg, msgnw, raw magics
153 - // check for parser functions
154 -
155 - // resolve template name
156 - // load template w/ canonical name
157 - // load template w/ variant names
158 - // recursion depth check
159 - // fetch from DB or interwiki
160 - // infinte loop check
161 - self.getTemplateDom(templateName, function(dom, err) {
162 - // Expand in-place!
163 - var templateFrame = frame.newChild(templateNode.params || {});
164 - templateFrame.expand(dom, 0, function(expandedTemplateNode) {
165 - out.contents = expandedTemplateNode.contents;
166 - callback(out);
167 - return; // done
168 - });
169 - return; // wait for async
170 - });
171 - });
172 - },
173 -
174 - argSubstitution: function( argNode, frame, callback ) {
175 - frame.flatten(argNode.name, function(argName, err) {
176 - if (err) {
177 - callback(null, err);
178 - return;
179 - }
180 -
181 - var arg = frame.getArgument(argName);
182 - console.log(argName, arg, frame);
183 - if (arg === false && 'params' in argNode && argNode.params.length) {
184 - // No match in frame, use the supplied default
185 - arg = argNode.params[0].val;
186 - }
187 - var out = {
188 - type: 'placeholder',
189 - orig: argNode,
190 - contents: [arg]
191 - };
192 - callback(out);
193 - });
19425 }
 26+ // nothing found!
 27+ return null;
 28+};
19529
19630
197 -});
 31+// Does this need separate UI/content inputs?
 32+MWParserEnvironment.prototype.formatNum = function( num ) {
 33+ return num + '';
 34+};
19835
199 -function PPFrame(env) {
200 - this.env = env;
201 - this.loopCheckHash = [];
202 - this.depth = 0;
203 -}
204 -
205 -// Flag constants
206 -$.extend(PPFrame, {
207 - NO_ARGS: 1,
208 - NO_TEMPLATES: 2,
209 - STRIP_COMMENTS: 4,
210 - NO_IGNORE: 8,
211 - RECOVER_COMMENTS: 16
212 -});
213 -PPFrame.RECOVER_ORIG = PPFrame.NO_ARGS
214 - | PPFrame.NO_TEMPLATES
215 - | PPFrame.STRIP_COMMENTS
216 - | PPFrame.NO_IGNORE
217 - | PPFrame.RECOVER_COMMENTS;
218 -
219 -$.extend(PPFrame.prototype, {
220 - newChild: function(args, title) {
 36+MWParserEnvironment.prototype.getVariable = function( varname, options ) {
22137 //
222 - var child = new PPFrame(this.env);
223 - child.args = args || {};
224 - child.title = title;
225 - return child;
226 - },
 38+};
22739
228 - /**
229 - * Using simple recursion for now -- PHP version is a little fancier.
230 - *
231 - * The iterator loop is set off in a closure so we can continue it after
232 - * waiting for an asynchronous template fetch.
233 - *
234 - * Note that this is inefficient, as we have to wait for the entire round
235 - * trip before continuing -- in browser-based work this may be particularly
236 - * slow. This can be mitigated by prefetching templates based on previous
237 - * knowledge or an initial tree-walk.
238 - *
239 - * @param {object} tree
240 - * @param {number} flags
241 - * @param {function(tree, error)} callback
242 - */
243 - expand: function(root, flags, callback) {
244 - /**
245 - * Clone a node, but give the clone an empty contents
246 - */
247 - var cloneNode = function(node) {
248 - var out = $.extend({}, node);
249 - out.contents = [];
250 - return out;
251 - }
252 -
253 - // stub node to write into
254 - var rootOut = cloneNode(root);
255 -
256 - var self = this,
257 - env = self.env,
258 - expansionDepth = 0,
259 - outStack = [{contents: []}, cloneNode(root)],
260 - iteratorStack = [false, root],
261 - indexStack = [0, 0],
262 - contextNode = false,
263 - newIterator = false,
264 - continuing = false,
265 - iters = 0,
266 - maxIters = 10; // for debugging
267 -
268 - if (env.debug) {
269 - var $chunk = $('<div style="border: solid 1px blue">').append('<hr>');
270 - $chunk.append('<h3>Original</h3>');
271 - $chunk.nodeTree(root);
272 - $chunk.appendTo('body');
273 - var debug = function(label, node) {
274 - $('<h3></h3>').text(label).appendTo($chunk);
275 - if (typeof node == "string" || typeof node == "number") {
276 - $('<p>').text(node).appendTo($chunk);
277 - } else if (node) {
278 - $chunk.nodeTree(node);
279 - }
280 - };
281 - } else {
282 - var debug = function() {};
283 - }
284 - var iteration = function() {
285 - // This while loop is a tail call recursion optimization simulator :)
286 - while (iteratorStack.length > 1) {
287 - var level = outStack.length - 1,
288 - iteratorNode = iteratorStack[level],
289 - out = outStack[level],
290 - index = indexStack[level]; // ????
291 -
292 - if (env.debug) {
293 - $chunk.append('<hr>');
294 - iters++;
295 - var $h = $('<h3>').text('iter ' + iters).attr('id', 'iter' + iters);
296 - if (iters > 1) {
297 - $h.append(' ');
298 - $('<a>').attr('href', '#iter' + (iters - 1)).text('prev').appendTo($h);
299 - }
300 - $h.append(' ');
301 - $('<a>').attr('href', '#iter' + (iters + 1)).text('next').appendTo($h);
302 - $chunk.append($h);
303 -
304 - if (iters > maxIters) {
305 - debug('aborted');
306 - return;
307 - }
308 - $chunk.append('<h3>level ' + level + '</h3>');
309 - }
310 - debug('index', index);
311 - if (continuing) {
312 - // If we're re-entering from an asynchronous data fetch,
313 - // skip over this part, we've done it before.
314 - continuing = false;
315 - } else {
316 - newIterator = false;
317 - if (index >= iteratorNode.contents.length) {
318 - // All done with this iterator.
319 - iteratorStack[level] = false;
320 - contextNode = false;
321 - } else {
322 - // Increment for the next round...
323 - contextNode = iteratorNode.contents[index];
324 - indexStack[level]++;
325 - index++;
326 - }
327 - debug('contextNode', contextNode);
328 - debug('indexStack (next)', indexStack);
329 - debug('outStack', outStack);
330 -
331 - if (contextNode === false) {
332 - // nothing to do
333 - } else if (typeof contextNode === 'string') {
334 - out.contents.push(contextNode);
335 - } else if (contextNode.type === 'template') {
336 - // Double-brace expansion
337 - continuing = true;
338 - self.env.braceSubstitution(contextNode, self, function(replacementNode, err) {
339 - //out.contents.push(replacementNode);
340 - newIterator = replacementNode;
341 - // ... and continue on the next node!
342 - iteration();
343 - });
344 - return; // pause for async work...
345 - } else if (contextNode.type == 'tplarg') {
346 - // Triple-brace expansion
347 - continuing = true;
348 - self.env.argSubstitution(contextNode, self, function(replacementNode, err) {
349 - //out.contents.push(replacementNode);
350 - newIterator = replacementNode;
351 - // ... and continue on the next node!
352 - iteration();
353 - });
354 - return; // pause for async work...
355 - } else {
356 - if ('contents' in contextNode && contextNode.contents.length) {
357 - // Generic recursive expansion
358 - newIterator = contextNode;
359 - debug('diving into child');
360 - } else {
361 - // No children; push as-is.
362 - out.contents.push(contextNode);
363 - debug('no children');
364 - }
365 - }
366 - }
367 -
368 - if (newIterator !== false) {
369 - outStack.push(cloneNode(newIterator));
370 - iteratorStack.push(newIterator);
371 - indexStack.push(0);
372 - debug('iterator stack push!');
373 - debug('outStack', outStack);
374 - debug('iteratorStack', iteratorStack);
375 - debug('indexStack', indexStack);
376 - } else if ( iteratorStack[level] === false) {
377 - // Return accumulated value to parent
378 - // With tail recursion
379 - debug('returning output up the stack');
380 - while (iteratorStack[level] === false && level > 0) {
381 - outStack[level - 1].contents.push(out);
382 - outStack.pop();
383 - iteratorStack.pop();
384 - indexStack.pop();
385 - level--;
386 - }
387 - }
388 - debug('end of iteration');
389 -
390 - // hack!
391 - if (iteratorStack.length > 1) {
392 - // Run us after running the event loop
393 - setTimeout(iteration, 0);
394 - return;
395 - }
396 - }
397 - // We've reached the end of the loop!
398 - --expansionDepth;
399 - var finalOut = outStack.pop().contents[0];
400 - debug('done', finalOut);
401 - callback(finalOut, null);
402 - };
403 - iteration();
404 - },
405 -
406 - flatten: function(root, callback) {
407 - new MWTreeSerializer(this).treeToSource(root, callback);
408 - },
409 -
410 - implodeWithFlags: function(sep, flags) {
411 -
412 - },
413 -
414 - implode: function(sep) {
415 -
416 - },
417 -
418 - virtualImport: function(sep) {
419 -
420 - },
421 -
422 - virtualBracketedImplode: function(start, sep, end /*, ... */ ) {
423 -
424 - },
425 -
426 - isEmpty: function() {
427 -
428 - },
429 -
430 - getArguments: function() {
431 -
432 - },
433 -
434 - getNumberedArguments: function() {
435 -
436 - },
437 -
438 - getNamedArguments: function() {
439 -
440 - },
441 -
442 - getArgument: function( name ) {
443 - if (name in this.args) {
444 - return this.args[name];
445 - } else {
446 - return false;
447 - }
448 - },
449 -
450 - loopCheck: function(title) {
451 - },
452 -
453 - isTemplate: function() {
454 -
455 - }
456 -
457 -});
458 -
459 -
460 -
46140 /**
462 - * @parm MWParserEnvironment env
463 - * @constructor
 41+ * @return MWParserFunction
46442 */
465 -MWParserTagHook = function( env ) {
466 - if (!env) {
467 - throw new Error( 'Tag hook requires a parser environment.' );
 43+MWParserEnvironment.prototype.getParserFunction = function( name ) {
 44+ if (name in this.parserFunctions) {
 45+ return new this.parserFunctions[name]( this );
 46+ } else {
 47+ return null;
46848 }
469 - this.env = env;
47049 };
47150
47251 /**
473 - * @param string text (or a parse tree?)
474 - * @param object params map of named parameters (strings or parse frames?)
475 - * @return either a string or a parse frame -- finalize this?
 52+ * @return MWParserTagHook
47653 */
477 -MWParserTagHook.execute = function( text, params ) {
478 - return '';
479 -};
480 -
481 -
482 -MWParserFunction = function( env) {
483 - if (!env) {
484 - throw new Error( 'Parser function requires a parser environment.');
 54+MWParserEnvironment.prototype.getTagHook = function( name ) {
 55+ if (name in this.tagHooks) {
 56+ return new this.tagHooks[name](this);
 57+ } else {
 58+ return null;
48559 }
486 - this.env = env;
48760 };
48861
48962 /**
490 - * @param string text (or a parse tree?)
491 - * @param object params map of named parameters (strings or parse frames?)
492 - * @return either a string or a parse frame -- finalize this?
 63+ * @fixme do this for real eh
49364 */
494 -MWParserFunction.execute = function( text, params ) {
495 - return '';
 65+MWParserEnvironment.prototype.resolveTitle = function( name, namespace ) {
 66+ // hack!
 67+ if (name.indexOf(':') == -1 && typeof namespace ) {
 68+ // hack hack hack
 69+ name = namespace + ':' + name;
 70+ }
 71+ return name;
49672 };
49773
 74+
49875 if (typeof module == "object") {
49976 module.exports.MWParserEnvironment = MWParserEnvironment;
50077 }

Status & tagging log