function introspect(obj) {
   var result = "";
   for (var i in obj)
	result += "[" + i + "]='" + obj[i] + "' ";
   return result;
}

function WikiPage() {

	WikiPage.grammar = [];
	WikiPage.actions = [];

	// Add a production rule to the wiki grammar.
	// 'rule' is the regexp to match and 'action' is the function to call on a match
	// Rules are evaluated in the order they are defined
	var rule = function (/*string*/ rule, /*function*/ action) {
		WikiPage.grammar.push(rule);
		WikiPage.actions.push(action);
	}

	// typographic styles
	var styles = {
		//wiki  html   enabled?
		"//":  ["em",  false],
		"**":  ["b",   false],
		"##":  ["tt",  false],
		"__":  ["u",   false],
		"--":  ["del", false],
		"^^":  ["sup", false],
		",,":  ["sub", false],
		"''":  ["em",  false], // LEGACY
		"'''": ["b",   false], // LEGACY
	}
	rule("\\*\\*|//|##|__|--\\b|\\b--|\\^\\^|,,|'''|''", function (m) {
		var style = styles[m];
		style[1] ^= true; // toggle style enable (note, we do not consider nesting)
		return ['</', '<'][style[1]] + style[0] + '>'
	});

	// titles
	var h_level = 0;
	rule("={2,6}", function (m) {
		if (!h_level) {
			h_level = m.length - 1;
			var link = 'TODO' // permalink(self.line)
			return '</p><h' + h_level + ' id="' + link + '"><a class="heading" href="#' + link + '">¶</a> '
		} else {
			var l = h_level;
			h_level = 0;
			return '</h' + l + '><p>\n';
		}
	});

	rule("\\\\",   function () { return '<br />' });
	rule("^-{3,}", function (m) { return '\n<hr size="' + (m.length - 2) + ' noshade="noshade" />\n'; });
	rule("--",     function () { return '&mdash;' });
	rule("\b(?:FIXME|TODO|DONE)\b", function (m) { return '<strong class="highlight ' + m + '">' + m + '</strong>' });

	// macro \<\<([^\s\|\>]+)(?:\s*\|\s*([^\>]+)|)\>\>)
	// hurl  \[\[([^\s\|]+)(?:\s*\|\s*([^\]]+)|)\]\])

	var in_html = 0;
	rule("<(?:|/)(?:br|hr|div|span|form|iframe|input|textarea|a|img|h[1-5])\b", function (m) {
		// closing tag?
		if (m[1] == '/') {
			--in_html;
			if (!in_html && word.startsWith('</div'))
				m += '<p>';
		} else {
			if (!in_html && m.startsWith('<div'))
				m = '</p>' + m;
			in_html++;
		}
		return m; // pass through
	});

	WikiPage.regexp_str = WikiPage.grammar.map(function (x) { return '(' + x + ')' }).join('|')
	WikiPage.regexp = RegExp(WikiPage.regexp_str, "g");
	//DEBUG: print ("grammar='" + WikiPage.regexp_str + "'");
}

// Parse a wiki line and return 
WikiPage.prototype.parse = function (l) {
	this.line = l; // Store in instance for rules
	return '<p>' + l.replace(WikiPage.regexp, function (match) {
		//DEBUG: assert(arguments.length == WikiPage.actions.length - 3);
		for (var i = 0, len = WikiPage.actions.length; i < len; ++i) {
			//DEBUG: print("arguments=" + introspect(Array.prototype.slice.call(arguments)));
			//DEBUG: print("i=" + i + " arguments[i+1]=" + arguments[i+1]);
			if (arguments[i+1]) {
				//DEBUG: print("invoking " + WikiPage.actions[i] + " for " + match);
				return WikiPage.actions[i](match);
			}
		}
	}) + '</p>';
}
