http://www.mediawiki.org/wiki/Special:Code/MediaWiki/97979
Revision: 97979
Author: brion
Date: 2011-09-24 00:53:14 +0000 (Sat, 24 Sep 2011)
Log Message:
-----------
Commit some more of this half-rewritten code before I lose it. :)
Expansion bits half-ported from the PHP, needs to be finished & the formats
normalized a bit.
Attempting to model the template expansion in a node tree on PPFrame::expand
from preprocessor, but outputting another node tree (this is broken right now)
rather than string output.
It needs to be able to exit and re-enter later when fetching templates
asynchronously, though it doesn't use a very efficient plan yet (just waits).
Modified Paths:
--------------
trunk/extensions/ParserPlayground/modules/ext.parserPlayground.pegParser.js
trunk/extensions/ParserPlayground/modules/mediawiki.parser.environment.js
trunk/extensions/ParserPlayground/modules/pegParser.pegjs.txt
Added Paths:
-----------
trunk/extensions/ParserPlayground/tests/expansionTest.js
trunk/extensions/ParserPlayground/tests/tests.html
Modified:
trunk/extensions/ParserPlayground/modules/ext.parserPlayground.pegParser.js
===================================================================
--- trunk/extensions/ParserPlayground/modules/ext.parserPlayground.pegParser.js
2011-09-24 00:37:11 UTC (rev 97978)
+++ trunk/extensions/ParserPlayground/modules/ext.parserPlayground.pegParser.js
2011-09-24 00:53:14 UTC (rev 97979)
@@ -9,8 +9,8 @@
* to point at the MW page name containing the parser peg definition; default
* is 'MediaWiki:Gadget-ParserPlayground-PegParser.pegjs'.
*/
-function PegParser(options) {
- this.options = options;
+function PegParser(env) {
+ this.env = env || {};
}
PegParser.src = false;
@@ -34,8 +34,58 @@
* @param {function(tree, error)} callback
*/
PegParser.prototype.expandTree = function(tree, callback) {
- // no-op!
- callback(tree, null);
+ var self = this;
+ var subParseArray = function(listOfTrees) {
+ var content = [];
+ $.each(listOfTrees, function(i, subtree) {
+ self.expandTree(subtree, function(substr, err) {
+ content.push(tree);
+ });
+ });
+ return content;
+ };
+ var src;
+ if (typeof tree === "string") {
+ callback(tree);
+ return;
+ }
+ if (tree.type == 'template') {
+ // expand a template node!
+
+ // Resolve a possibly relative link
+ var templateName = this.env.resolveTitle( tree.target,
'Template' );
+ this.env.fetchTemplate( tree.target, tree.params || {},
function( templateSrc, error ) {
+ // @fixme should pre-parse/cache these too?
+ self.parseToTree( templateSrc, function( templateTree,
error ) {
+ if ( error ) {
+ callback({
+ type: 'placeholder',
+ orig: tree,
+ content: [
+ {
+ // @fixme
broken link?
+ type: 'link',
+ target:
templateName
+ }
+ ]
+ });
+ } else {
+ callback({
+ type: 'placeholder',
+ orig: tree,
+ content:
self.env.expandTemplateArgs( templateTree, tree.params )
+ });
+ }
+ })
+ } );
+ // Wait for async...
+ return;
+ }
+ var out = $.extend( tree ); // @fixme prefer a deep copy?
+ if (tree.content) {
+ out.content = subParseArray(tree.content);
+ }
+ callback(out);
};
PegParser.prototype.initSource = function(callback) {
Modified:
trunk/extensions/ParserPlayground/modules/mediawiki.parser.environment.js
===================================================================
--- trunk/extensions/ParserPlayground/modules/mediawiki.parser.environment.js
2011-09-24 00:37:11 UTC (rev 97978)
+++ trunk/extensions/ParserPlayground/modules/mediawiki.parser.environment.js
2011-09-24 00:53:14 UTC (rev 97979)
@@ -1,11 +1,15 @@
var MWParserEnvironment = function(opts) {
var options = {
tagHooks: {},
- parserFunctions: {}
+ parserFunctions: {},
+ pageCache: {}, // @fixme use something with managed space
+ domCache: {}
};
$.extend(options, opts);
this.tagHooks = options.tagHooks;
this.parserFunctions = options.parserFunctions;
+ this.pageCache = options.pageCache;
+ this.domCache = options.domCache;
};
$.extend(MWParserEnvironment.prototype, {
@@ -38,12 +42,346 @@
} else {
return null;
}
+ },
+
+ /**
+ * @fixme do this for real eh
+ */
+ resolveTitle: function( name, namespace ) {
+ // hack!
+ if (name.indexOf(':') == 0 && typeof namespace ) {
+ // hack hack hack
+ name = namespace + ':' + name;
+ }
+ return name;
+ },
+
+ /**
+ * Async.
+ *
+ * @todo make some optimizations for fetching multiple at once
+ *
+ * @param string name
+ * @param function(text, err) callback
+ */
+ fetchTemplate: function( title, callback ) {
+ this.fetchTemplateAndTitle( title, function( text, title, err )
{
+ callback(title, err);
+ });
+ },
+
+ fetchTemplateAndTitle: function( title, callback ) {
+ // @fixme normalize name?
+ if (title in this.pageCache) {
+ // @fixme should this be forced to run on next event?
+ callback( this.pageCache[title] );
+ } else {
+ // whee fun hack!
+ $.ajax({
+ url: wgScriptPath + '/api' + wgScriptExtension,
+ data: {
+ format: 'json',
+ action: 'query',
+ prop: 'revisions',
+ rvprop: 'content',
+ titles: name
+ },
+ success: function(data, xhr) {
+ var src = null, title = null;
+ $.each(data.query.pages, function(i,
page) {
+ if (page.revisions &&
page.revisions.length) {
+ src =
page.revisions[0]['*'];
+ title = page.title;
+ }
+ });
+ if (typeof src !== 'string') {
+ callback(null, null, 'Page not
found');
+ } else {
+ callback(src, title);
+ }
+ },
+ error: function(msg) {
+ callback(null, null, 'Page/template
fetch failure');
+ },
+ dataType: 'json',
+ cache: false // @fixme caching, versions etc?
+ }, 'json');
+ }
+ },
+
+ getTemplateDom: function( title, callback ) {
+ var self = this;
+ if (title in this.domCache) {
+ callback(this.domCache[title], null);
+ }
+ this.fetchTemplateAndTitle( title, function( text, title, err )
{
+ if (err) {
+ callback(null, err);
+ return;
+ }
+ self.pageCache[title] = text;
+ self.parser.parseToTree( text, function( templateTree,
err ) {
+ this.domCache[title] = templateTree;
+ callback(templateTree, err);
+ });
+ });
+ },
+
+ braceSubstitution: function( templateNode, frame, callback ) {
+ // stuff in Parser.braceSubstitution
+ // expand/flatten the 'title' piece (to get the template
reference)
+ frame.flatten(templateNode.name, function(templateName, err) {
+ if (err) {
+ callback(null, err);
+ return;
+ }
+ var out = {
+ type: 'placeholder',
+ orig: templateNode,
+ contents: []
+ };
+
+ // check for 'subst:'
+ // check for variable magic names
+ // check for msg, msgnw, raw magics
+ // check for parser functions
+
+ // resolve template name
+ // load template w/ canonical name
+ // load template w/ variant names
+ // recursion depth check
+ // fetch from DB or interwiki
+ // infinte loop check
+ this.getTemplateDom(templateName, function(dom, err) {
+ // Expand in-place!
+ var templateFrame =
frame.newChild(templateName.params || []);
+ templateFrame.expand(dom, 0,
function(expandedTemplateNode) {
+ out.contents =
expandedTemplateNode.contents;
+ callback(out);
+ return; // done
+ });
+ return; // wait for async
+ });
+ });
+ },
+
+ argSubstitution: function( argNode, frame, callback ) {
+ frame.flatten(argNode.name, function(argName, err) {
+ if (err) {
+ callback(null, err);
+ return;
+ }
+
+ var arg = frame.getArgument(argName);
+ if (arg === false && 'params' in argNode &&
argNode.params.length) {
+ // No match in frame, use the supplied default
+ arg = argNode.params[0].val;
+ }
+ var out = {
+ type: 'placeholder',
+ orig: argNode,
+ contents: [arg]
+ };
+ callback(out);
+ });
}
-
+
+
});
+function PPFrame(env) {
+ this.env = env;
+ this.loopCheckHash = [];
+ this.depth = 0;
+}
+// Flag constants
+$.extend(PPFrame, {
+ NO_ARGS: 1,
+ NO_TEMPLATES: 2,
+ STRIP_COMMENTS: 4,
+ NO_IGNORE: 8,
+ RECOVER_COMMENTS: 16
+});
+PPFrame.RECOVER_ORIG = PPFrame.NO_ARGS
+ | PPFrame.NO_TEMPLATES
+ | PPFrame.STRIP_COMMENTS
+ | PPFrame.NO_IGNORE
+ | PPFrame.RECOVER_COMMENTS;
+$.extend(PPFrame.prototype, {
+ newChild: function(args, title) {
+ //
+ },
+
+ /**
+ * Using simple recursion for now -- PHP version is a little fancier.
+ *
+ * The iterator loop is set off in a closure so we can continue it after
+ * waiting for an asynchronous template fetch.
+ *
+ * Note that this is inefficient, as we have to wait for the entire
round
+ * trip before continuing -- in browser-based work this may be
particularly
+ * slow. This can be mitigated by prefetching templates based on
previous
+ * knowledge or an initial tree-walk.
+ *
+ * @param {object} tree
+ * @param {number} flags
+ * @param {function(tree, error)} callback
+ */
+ expand: function(root, flags, callback) {
+ /**
+ * Clone a node, but give the clone an empty contents
+ */
+ var cloneNode = function(node) {
+ var out = $.extend({}, node);
+ out.contents = [];
+ return out;
+ }
+ // stub node to write into
+ var rootOut = cloneNode(root);
+
+ var self = this,
+ expansionDepth = 0,
+ outStack = [{contents: []}, rootOut],
+ iteratorStack = [false, root],
+ indexStack = [0, 0],
+ contextNode = false,
+ newIterator = false,
+ continuing = false;
+
+ var iteration = function() {
+ // This while loop is a tail call recursion
optimization simulator :)
+ while (iteratorStack.length > 1) {
+ var level = outStack.length - 1,
+ iteratorNode = iteratorStack[level],
+ out = outStack[level],
+ index = indexStack[level]; // ????
+
+ if (continuing) {
+ // If we're re-entering from an
asynchronous data fetch,
+ // skip over this part, we've done it
before.
+ continuing = false;
+ } else {
+ if ($.isArray(iteratorNode)) {
+ if (index >=
iteratorNode.length) {
+ // All done with this
iterator.
+ iteratorStack[level] =
false;
+ contextNode = false;
+ } else {
+ contextNode =
iteratorNode[index];
+ indexStack[level]++;
+ }
+ } else {
+ // Copy to contextNode and then
delete from iterator stack,
+ // because this is not an
iterator but we do have to execute it once
+ contextNode =
iteratorStack[level];
+ iteratorStack[level] = false;
+ }
+ }
+
+ if (contextNode === false) {
+ // nothing to do
+ } else if (typeof contextNode === 'string') {
+ out.contents.push(contextNode);
+ } else if (contextNode.type === 'template') {
+ // Double-brace expansion
+ continuing = true;
+ self.env.braceSubstitution(contextNode,
self, function(replacementNode, err) {
+
out.contents.push(replacementNode);
+ // ... and continue on the next
node!
+ iteration();
+ });
+ return; // pause for async work...
+ } else if (contextNode.type == 'tplarg') {
+ // Triple-brace expansion
+ continuing = true;
+ self.env.argSubstitution(contextNode,
self, function(replacementNode, err) {
+
out.contents.push(replacementNode);
+ // ... and continue on the next
node!
+ iteration();
+ });
+ return; // pause for async work...
+ } else {
+ if ('content' in contextNode &&
contextNode.content.length) {
+ // Generic recursive expansion
+ newIterator = contextNode;
+ } else {
+ // No children; push as-is.
+ out.contents.push(contextNode);
+ }
+ }
+
+ if (newIterator !== false) {
+ outStack.push(cloneNode(newIterator));
+ iteratorStack.push(newIterator);
+ indexStack.push(0);
+ } else if ( iteratorStack[level] === false) {
+ // Return accumulated value to parent
+ // With tail recursion
+ while (iteratorStack[level] === false
&& level > 0) {
+ outStack[level -
1].contents.push(out);
+ outStack.pop();
+ iteratorStack.pop();
+ indexStack.pop();
+ level--;
+ }
+ }
+ }
+ // We've reached the end of the loop!
+ --expansionDepth;
+ callback(outStack.pop(), null);
+ };
+ iteration();
+ },
+
+ implodeWithFlags: function(sep, flags) {
+
+ },
+
+ implode: function(sep) {
+
+ },
+
+ virtualImport: function(sep) {
+
+ },
+
+ virtualBracketedImplode: function(start, sep, end /*, ... */ ) {
+
+ },
+
+ isEmpty: function() {
+
+ },
+
+ getArguments: function() {
+
+ },
+
+ getNumberedArguments: function() {
+
+ },
+
+ getNamedArguments: function() {
+
+ },
+
+ getArgument: function( name ) {
+
+ },
+
+ loopCheck: function(title) {
+ },
+
+ isTemplate: function() {
+
+ }
+
+});
+
+
+
/**
* @parm MWParserEnvironment env
* @constructor
@@ -67,7 +405,7 @@
MWParserFunction = function( env) {
if (!env) {
- throw new Error( 'Parser funciton requires a parser
environment.');
+ throw new Error( 'Parser function requires a parser
environment.');
}
this.env = env;
};
Modified: trunk/extensions/ParserPlayground/modules/pegParser.pegjs.txt
===================================================================
--- trunk/extensions/ParserPlayground/modules/pegParser.pegjs.txt
2011-09-24 00:37:11 UTC (rev 97978)
+++ trunk/extensions/ParserPlayground/modules/pegParser.pegjs.txt
2011-09-24 00:53:14 UTC (rev 97979)
@@ -174,6 +174,17 @@
};
}
+tplarg = "{{{" name:link_target params:("|" p:template_param { return p })*
"}}}" {
+ var obj = {
+ type: 'tplarg',
+ name: name
+ };
+ if (params && params.length) {
+ obj.params = params;
+ }
+ return obj;
+}
+
template_param_name
= h:( !"}}" x:([^=|]) { return x } )* { return h.join(''); }
Added: trunk/extensions/ParserPlayground/tests/expansionTest.js
===================================================================
--- trunk/extensions/ParserPlayground/tests/expansionTest.js
(rev 0)
+++ trunk/extensions/ParserPlayground/tests/expansionTest.js 2011-09-24
00:53:14 UTC (rev 97979)
@@ -0,0 +1,66 @@
+var pageDatabase = {
+ 'Template:Parens': {
+ type: 'root',
+ contents: [
+ '(',
+ {
+ type: 'tplarg',
+ /*
+ contents: [
+ '1'
+ ]*/
+ name: '1'
+ },
+ ')'
+ ]
+ },
+ 'ParenCaller': {
+ type: 'root',
+ contents: [
+ {
+ type: 'template',
+ /*
+ contents: [
+ {
+ type: 'title',
+ contents: [
+ 'Parens'
+ ]
+ },
+ {
+ type: 'part',
+ contents: [
+ {
+ type: 'name',
+ index: 1
+ },
+ {
+ type: 'value',
+ contents: [
+ 'bizbax'
+ ]
+ }
+ ]
+ }
+ ]*/
+ name: 'Parens',
+ params: {
+ 1: 'bizbax'
+ }
+ }
+ ]
+ }
+};
+
+var env = new MWParserEnvironment({
+ 'domCache': pageDatabase
+});
+var frame = new PPFrame(env);
+frame.expand(pageDatabase['ParenCaller'], 0, function(node, err) {
+ if (err) {
+ console.log('error', err);
+ } else {
+ console.log(node);
+ }
+});
+
Property changes on: trunk/extensions/ParserPlayground/tests/expansionTest.js
___________________________________________________________________
Added: svn:eol-style
+ native
Added: trunk/extensions/ParserPlayground/tests/tests.html
===================================================================
--- trunk/extensions/ParserPlayground/tests/tests.html
(rev 0)
+++ trunk/extensions/ParserPlayground/tests/tests.html 2011-09-24 00:53:14 UTC
(rev 97979)
@@ -0,0 +1,6 @@
+<!DOCTYPE html>
+
+<script src="../../../resources/jquery/jquery.js"></script>
+<script src="../modules/mediawiki.parser.environment.js"></script>
+
+<script src="expansionTest.js"></script>
_______________________________________________
MediaWiki-CVS mailing list
[email protected]
https://lists.wikimedia.org/mailman/listinfo/mediawiki-cvs