//!debugon
// Copyright 2006 Tom W.M. (http://freecog.net/)
// 11:27 p.m. Tuesday, April 11, 2006

// Use CodeProcessor.process(els) to add line numbers and syntax 
// highlighting.  The node must be identifiable as acceptable to each
// appropriate processor module.  For the moment, this means it must
// have a certain class name--"ln-con" for line numbering, "js-con" for
// JavaScript.

// Dependencies:
//  * Dean Edwards' Base class
//  * My AsyncQueue class
//  * MochiKit.Base
//  * MochiKit.Async

// Known bugs:
//  * Multiple division symbols on a line could potentially confuse
//    the JavaScript highlighter.

// Note regarding mixing of object and regular expression literals:
//    There is a bug in both my editor's syntax higlighting 
//    (Notepad2) and JSMin.  Neither recognizes RE literals that
//    follow a colon (JSMin's code states that it only recognizes
//    those following "(", ",", or "=").  To work around this, all
//    RE literals that would be effected have been wrapped in
//    parenthesis.  Unfortunately, this makes them even more
//    illegible (though not as much as converting them to strings
//    would).


CodeProcessor = {
	// List of processors.
	processors: [],
	// Registers a processor constructor.  Processor.test() should be a 
	// function that returns true if the processor will accept a DOM
	// node.
	add: function(name, processor) {
		CodeProcessor.processors.push({
			name: name,
			processor: processor,
			test: processor.test
		});
		return processor;
	},
	// Applies each of the processors to the passed element(s).
	// Accepts a string ID, element, or array-like object of the same.
	process: function(els) {
		// Put non-array-like objects into an array.
		if (!MochiKit.Base.isArrayLike(els)) {
			els = [els];
		}
		var results = [];
		for (var x = 0, y = els.length; x < y; x++) {
			var dom_node = els[x];
			if (typeof dom_node == 'string')
				dom_node = document.getElementById(dom_node);
			results.push(CodeProcessor._process_element(dom_node));
		}
		return results;
	},
	// Processes an individual element.
	_process_element: function(dom_node) {
		var CP = CodeProcessor;
		
		var filter_classes = ['ann-bracket', 'ann-default', 'ann-ellip'];
		var filter_ids = [];
		
		var className = dom_node.className;
		var m = className.match(this.SELECT_BY_CLASS_RE);
		if (m) { 
			var classes = m[1].substring(3).split('---');
			filter_classes = filter_classes.concat(classes);
		}
		
		m = className.match(this.SELECT_BY_ID_RE);
		if (m) filter_ids = m[1].substring(3).split('---');
		
		var filterCount = 0;
		// The filter function:
		var filter = function(node) {
			var id = node.id;
			for (var i = 0; i < filter_ids.length; i++) {
				if (id == filter_ids[i]) return true;
			}
			var cls = " " + node.className.replace(/\s+/g, " ") + " ";
			for (var i = 0; i < filter_classes.length; i++) {
				var test = " " + filter_classes[i] + " ";
				if (cls.indexOf(test) > -1) return true;
			}
			filterCount++;
			return false;
		};
		
		// Processors that accepted dom_node
		var processors = [];
		// Deferred to chain the deferred properties to.
		var chain = MochiKit.Async.succeed();
		
		for (var i = 0, j = CP.processors.length; i < j; i++) {
			chain.addCallback(function(proc) {
				if (proc.test(dom_node)) {
					var p = new proc.processor(filter, dom_node);
					processors.push(p);
					if (p.filter_classes)
						filter_classes = filter_classes.concat(p.filter_classes);
					if (p.filter_ids)
						filter_ids = filter_ids.concat(p.filter_ids);
					filter = p.node_filter || filter;
					return p.deferred;
				}
			}, CP.processors[i]);
			chain.addErrback(log); // debug
		}
		
		var deferred = new MochiKit.Async.Deferred();
		chain.addCallback(function() {
			deferred.callback(dom_node, filter);
		});
		
		return {
			processors: processors,
			deferred: deferred
		};
	},
	SELECT_BY_CLASS_RE: (/filterByClass(---[a-zA-Z][-a-zA-Z0-9]+)*/),
	SELECT_BY_ID_RE: (/filterByID(---[a-zA-Z][-a-zA-Z0-9\.]+)*/),
	match_class: function(re, el) {
		return el.className.match(new RegExp("(^|\\s)" + re + "(\\s|$)"));
	},
	
	PARENTHESIS_RE: ( /^([\(\)])/ ),
	BRACKETS_RE: ( /^([\[\]])/ ),
	BRACES_RE: ( /^([\{\}])/ ),
	STRING_SQUOT_RE: ( /^(''|'(\\\\|\\'|[^'])+')/ ),
	STRING_DQUOT_RE: ( /^(""|"(\\\\|\\"|[^"])+")/ ),
	
	// Generally useful constants for use in processor modules
	C_LINEEND_COMMENT_CTX: { // Single line comment
		start: ( /^(\/\/[^\r\n]*)/ ),
		end: ( /^(.*?)[\r\n]/ ),
		cls: 'c-sl-com'
	},
	APOS_LINEEND_COMMENT_CTX: { // Single line comment
		start: ( /^('[^\r\n]*)/ ),
		end: ( /^(.*?)[\r\n]/ ),
		cls: 'apos-com'
	},
	C_MULTILINE_COMMENT_CTX: { // Multi-line comment
		start: ( /^(\/\*[\s\S]*)/ ),
		end: ( /^([\s\S]*?\*\/)/ ),
		cls: 'c-ml-com'
	},
	DQUOTE_CTX: { // Double quote string
		start: ( /^(".*)/ ),
		end: ( /^("|(\\"|[^"])")/ ),
		cls: 'dquote-str'
	},
	SQUOTE_CTX: { // Single quote string
		start: ( /^('.*)/ ),
		end: ( /^('|(\\\\\|\\'|[^'])+')/ ),
		cls: 'squote-str'
	},
	
	with_other_class: function(ctx, new_cls) {
		var o = {};
		MochiKit.Base.update(o, ctx);
		o.cls = new_cls;
		return o;
	},
	
	toString: function(){ return '[object CodeProcessor]'; }
};


/**********************************************************************/


/**
* Allows for iterable-like processing of text nodes in a DOM tree,
* traversing depth-first.  This is intended to be subclassed for
* use.
*
* @constructor
* @id NodeQueue
*/
NodeQueue = Base.extend({
	/**
	* Initializes the queue.  The children of the (optional)
	* DOM node passed as the second argument will be inserted
	* into the queue.
	* @method
	* @id NodeQueue.prototype.init_node_queue
	*/
	init_node_queue: function(dom_node) {
		this._current = null;
		this._queue = []; // FIFO
		switch (arguments.length) {
			case 0:
				break;
			case 1:
				this.prepend_children(dom_node);
				break;
			default:
				throw "Invalid arguments to init_node_queue()";
		}
	},
	/**
	* Dispatches the node currently at the top of the queue to a 
	* processing function.
	* @method
	* @id NodeQueue.prototype.dispatch_node
	*/
	dispatch_node: function() {
		this._current = this._current || this._queue.shift();
		if (!this._current) this.end_node_queue();
		
		//log("@@@    Loop. this._current = " + repr(this._current));
		
		switch (this._current.nodeType) {
			case 1: // ELEMENT_NODE
				this.process_element_node(this._current);
				break;
			case 3: // TEXT_NODE
				this.process_text_node(this._current);
				break;
			default:
				// Just skip anything else.
				this.advance_queue();
				break;
		}
	},
	/**
	* Discards anything currently being processed, so that
	* `dispatch_node` fetches the next node from the queue.
	* This must be called once each time `process_element_node`
	* and `process_text_node` are finished with a node, or the
	* queue will not advance.
	* @method
	* @id NodeQueue.prototype.advance_queue
	*/
	advance_queue: function() {
		this._current = null;
	},
	/**
	* Prepends children into the queue for processing.
	* This must be called with every element that is 
	* passed to `process_element_node`, or its children
	* will not be processed.
	* @method
	* @id NodeQueue.prototype.prepend_children
	*/
	prepend_children: function(element) {
		for (var i = element.childNodes.length - 1; i >= 0; i--) {
			var n = element.childNodes[i];
			var num = this._queue.unshift(n);
		}
		//log("###    " + element.childNodes.length + " childNodes unshift'd.");
	},
	/**
	* Gets called with each element that passes through
	* the queue, placing their child nodes into the queue.
	* Override this in a subclass for different behavior.
	* @method
	* @id NodeQueue.prototype.process_element_node
	*/
	process_element_node: function(node) {
		this.prepend_children(node);
		this.advance_queue();
	},
	/**
	* Gets called with each text node that passes through
	* the queue.  Override this in a subclass.
	* @method
	* @id NodeQueue.prototype.process_text_node
	*/
	process_text_node: function(node) {
		// override in subclass
		this.advance_queue();
	},
	/**
	* Gets called when the queue is exhausted.  Override this
	* in a subclass if you need it to do something.
	* @method
	* @id NodeQueue.prototype.end_node_queue
	*/
	end_node_queue: function() {},
	toString: function() { return '[object NodeQueue instance]'; }
});


/**
* Extends `NodeQueue` to allow for filtering of the elements it
* processes.  Created to allow simple processing of all the text
* nodes in a tree.
*
* @id FilteredNodeQueue
* @constructor
*/
FilteredNodeQueue = NodeQueue.extend({
	/**
	* Initializes the queue.  Pass a predicate that identifies
	* acceptable nodes (required) and a DOM node (optional) to 
	* seed the queue with (placing its child nodes in the queue).
	* @method
	* @id FilteredNodeQueue.prototype.init_node_queue
	*/
	init_node_queue: function(node_filter, dom_node) {
		this.__node_filter = node_filter;
		if (this.__node_filter(dom_node))
			this.base();
		else
			this.base(dom_node);
	},
	/**
	* Places the child nodes of every element that passes
	* through the queue in the queue unless the element
	* is filtered.
	* @method
	* @id FilteredNodeQueue.prototype.process_element_node
	*/
	process_element_node: function(node) {
		if (!this.__node_filter(node))
			this.prepend_children(node);
		this.advance_queue();
	},
	toString: function() { return '[object FilteredNodeQueue instance]'; }
});


/**********************************************************************/

/**
* Adds line numbers to source code.
*
* @id CodeProcessor.LineNumProc
* @constructor
*/
CodeProcessor.LineNumProc = FilteredNodeQueue.extend({
	constructor: function(node_filter, dom_node) {
		this.dom_node = dom_node;
		this.node_filter = node_filter;
		this.init_node_queue(node_filter, dom_node);
		this.number_nodes = [];
		this.filter_classes = [this.NUMBER_CLASS];
		this._counter = 1;
		
		this.deferred = (new AsyncQueue(this))
			// Create containers:
			.next(function() {
				// Line 1:
				dom_node.insertBefore(this.create_number_container(),
				                     dom_node.firstChild);
			})
			    // The rest of the lines:
			.loop(this.dispatch_node)
			// Calculate how many characters wide
			// the numbering shoudl be:
			.next(this.init_number_insertion)
			// Insert the text into the containers:
			.loop(this.insert_number)
			// Done!
			//.next(partial(log, "LineNumProc done."))
			.go();
	},
	/**
	* Creates the <span> that contains the line number, incrementing
	* the counter, appending it to the `number_nodes` array and 
	* returning it.
	* @method
	* @id CodeProcessor.LineNumProc.prototype.create_number_container
	*/
	create_number_container: function() {
		this._counter++;
		var el = document.createElement('span');
		el.className = this.NUMBER_CLASS;
		this.number_nodes.push(el);
		return el;
	},
	/**
	* Searches through text nodes for line breaks.  The text node is
	* split after the break, and the line number container inserted.
	* @method
	* @id CodeProcessor.LineNumProc.prototype.process_text_node
	*/
	process_text_node: function(node) {
		var text = node.nodeValue;
		var m = text.match(/[^\r\n]*(\r\n|[\r\n])/);
		if (m) {
			var t = document.createTextNode(m[0]);
			node.parentNode.insertBefore(t, node);
			node.parentNode.insertBefore(
				this.create_number_container(), node);
			text = text.substring(m[0].length);
			if (!text) {
				node.parentNode.removeChild(node);
				this.advance_queue();
			} else {
				node.nodeValue = text;
			}
		} else {
			this.advance_queue();
		}
	},
	/**
	* Stop the `AsyncQueue` instance's loop when the node queue
	* is exhausted.
	* @method
	* @id CodeProcessor.LineNumProc.prototype.end_node_queue
	*/
	end_node_queue: AsyncQueue.stop_loop,
	/**
	* Sets the value this._precision to the correct number of places
	* for the value of `this._counter`.
	* @method
	* @id CodeProcessor.LineNumProc.prototype.init_number_insertion
	*/
	init_number_insertion: function() {
		var p = 1, c = this._counter;
		while (Math.pow(10, p) < c) {
			p++;
		}
		// Precision is 2 at minimum, so that multiple short code 
		// examples on a single page will match up.
		this._precision = Math.max(2, p);
		this._index = 0;
	},
	/**
	* Pads a number's string representation to the number of places
	* in `this._precision`
	* @method
	* @id CodeProcessor.LineNumProc.prototype.
	*/
	pad: function(num) {
		num = "" + num;
		while (num.length < this._precision) {
			num = "\xA0" + num;
		}
		return num;
	},
	/**
	* Insert 
	* @method
	* @id CodeProcessor.LineNumProc.prototype.insert_number
	*/
	insert_number: function() {
		var node = this.number_nodes[this._index++];
		if (!node) {
			throw AsyncQueue.StopLoop;
		}
		var text = this.pad(this._index) + '.\xA0';
		var num = document.createTextNode(text);
		node.appendChild(num);
	},
	CONTAINER_CLASS: 'ln-con',
	NUMBER_CLASS: 'ln-num',
	toString: function(){ return '[object CodeProcessor.LineNumProc instance]'; }
}, {
	/**
	* Predicate that accepts DOMElements with classes of "linenumbers"
	* "ln-con".
	* @method
	* @id CodeProcessor.LineNumProc.prototype.insert_number
	*/
	test: MochiKit.Base.partial(CodeProcessor.match_class, "(linenumbers|ln-con)")
});

CodeProcessor.add("LineNumProc", CodeProcessor.LineNumProc);


/**********************************************************************/

// JavaScript Highlighter module for SyntaxHighlighter
// Copyright 2006 Tom W.M. (http://freecog.net/)
// 12:15 p.m. Monday, July 03, 2006

CodeProcessor.JSHighlighter = FilteredNodeQueue.extend({
	constructor: function(node_filter, dom_node) {
		//log("Instantiating JSHighlighter()");
		this.dom_node = dom_node;
		this.node_filter = node_filter;
		this.init_node_queue(node_filter, dom_node);
		
		this.deferred = (new AsyncQueue(this))
			.loop(this.dispatch_node)
			//.next(partial(log, "JSHighlighter done."))
			.go();
	},
	end_node_queue: AsyncQueue.stop_loop,
	process_text_node: function(node) {
		var text = node.nodeValue;
		
		// Empty text nodes trigger an infinite loop.
		if (!text) {
			//log("<<<    Empty text node discarded.");
			node.parentNode.removeChild(node);
			this.advance_queue();
			return;
		}
		
		var ctx = this._context;
		if (ctx) { // Is there an open context?
			var m = text.match(ctx.end);
			if (m) { // Is the end of the context?
				if (m[1]) 
					this._process_match(ctx, m[1], node, text);
				if (ctx.postprocessor) {
					ctx.postprocessor(this._context_contents);
				}
				this._context = null;
				//log("XXX    Context closed. m = " + repr(m));
			} else {
				// We haven't reached the end yet.  Devour the
				// entire text node.
				this._process_match(ctx, text, node, text);
				//log("---    Context devoured " + repr(text));
			}
			return;
			
		} else { // Process normally.
			var REs = this._get_chunks();
			for (var i = 0; i < REs.length; i++) {
				var o = REs[i];
				var m = text.match(o.re);
				if (m) {
					this._process_match(o, m[1], node, text);
					//log(">>>    RE " + i + " matched " + repr(m) + ". cls = " + repr(o.cls));
					return;
				}
			}
		}
		
		// Hmm... looks like none of the REs matched.
		// We could be dealing with a comment or string
		// that has been broken up.
		var cs = this._get_contexts();
		for (var i = 0; i < cs.length; i++) {
			var o = cs[i];
			var m = text.match(o.start);
			if (m) {
				this._context = o;
				this._context_contents = [];
				this._process_match(o, m[1], node, text);
				//log("+++    Context opened. m = " + repr(m));
				return;
			}
		}
		
		// If all else fails, move forward a character.
		var t = document.createTextNode(text.charAt(0));
		text = text.substring(1);
		node.nodeValue = text;
		node.parentNode.insertBefore(t, node);
	},
	// Turns a regular expression match into the appropriate DOM nodes.
	_process_match: function(o, m, node, text) {
		var t = document.createTextNode(m);
		if (o.cls) {
			var e = document.createElement('span');
			e.className = o.cls;
			e.appendChild(t);
		}
		node.parentNode.insertBefore(e || t, node);
		if (this._context) {
			this._context_contents.push(e || t);
		}
		text = text.substring(m.length);
		if (text) {
			node.nodeValue = text; // Probably slow.
		} else { // We're done with the text node.
			node.parentNode.removeChild(node);
			this.advance_queue();
		}
	},
	
	// Maps regular expressions to classes.
	_get_chunks: function() {
		var C = CodeProcessor;
		var J = C.JSHighlighter;
		return this.__chunks_cache || (
			this.__chunks_cache = [
				{re: J.SKIP_RE,         cls: null},
				{re: C.PARENTHESIS_RE,  cls: "js-par"},
				{re: C.BRACKETS_RE,     cls: "js-bra"},
				{re: C.BRACES_RE,       cls: "js-br"},
				{re: J.KEYWORD_RE,      cls: "js-kwd"},
				{re: J.BUILTIN_RE,      cls: "js-id js-builtin"},
				{re: J.BASE_RE,         cls: "js-id js-base"},
				{re: J.OPERATOR_W_RE,   cls: "js-op"},
				{re: J.NUMBER_RE,       cls: "js-num"},
				{re: J.NUMBER_W_RE,     cls: "js-num"},
				//{re: J.LONE_DOT,        cls: null},
				{re: J.KEY_RE,          cls: "js-id js-key"},
				{re: J.CONSTANT_RE,     cls: "js-id js-const"},
				{re: J.ID_RE,           cls: "js-id"},
				{re: J.COMMENT_M_RE,    cls: "js-com"},
				{re: J.RE_RE,           cls: "js-re"},
				{re: J.COMMENT_S_RE,    cls: "js-com"},
				{re: J.OPERATOR_S_RE,   cls: "js-op"},
				{re: C.STRING_SQUOT_RE, cls: "js-str"},
				{re: C.STRING_DQUOT_RE, cls: "js-str"}
			]);
	},
	_get_contexts: function() {
		var C = CodeProcessor;
		return this.__context_cache || (
			this.__context_cache = [
				C.with_other_class(C.C_LINEEND_COMMENT_CTX, 'js-com'),
				C.with_other_class(C.C_MULTILINE_COMMENT_CTX, 'js-com'),
				C.with_other_class(C.DQUOTE_CTX, 'js-str'),
				C.with_other_class(C.SQUOTE_CTX, 'js-str')
			]);
	},
	toString: function(){ return '[object CodeProcessor.JSHighlighter instance]'; }
}, {
	// All regular expressions are constructed so that the first group 
	// is the relevant chunk of text.
	
	// Should ? and : be operators?  Something else?
	SKIP_RE: ( /^(([\s,;\?:]|\.(?![$_a-zA-Z]))+)/ ),
	//LONE_DOT: ( /^(\.)/ ),
	
	// There are far too many of these silly keywords.
	// Should this list include "true" and "false"?
	KEYWORD_RE: new RegExp("^(" +
		"abstract|boolean|break|byte|case|catch|char|class|const|" +
		"continue|debugger|default|delete|double|do|else|enum|export|" +
		"extends|final|finally|float|for|function|goto|if|implements|" +
		"import|interface|int|in|long|native|new|package|private|" +
		"protected|public|return|short|static|super|synchronized|" +
		"switch|this|throws|throw|transient|try|var|volatile|void|" +
		"while|with)(?![0-9a-zA-Z\\$_])"),
	
	NUMBER_RE: ( /^([+-]?(0x[0-9a-f]+|[0-9]+\.[0-9]*|\.?[0-9]+)(e[+-]?[0-9]+)?)/i ),
	
	// Should NaN and Infitity be styled as numbers, or keywords?
	NUMBER_W_RE: ( /^([+-]?Infinity|NaN)(?![0-9a-zA-Z\$_])/ ),
	
	RE_RE: ( /^(\/(\\\\|\\\/|[^\/\n\r])*[^\\]\/g?i?m?)[ \t]*([\)\}\],;.\n\t]|$)/ ),
	
	// Matches the keyword operators.  Must go *before* the identifier RE,
	// because these are also all valid identifiers.
	OPERATOR_W_RE: ( /^(delete|instanceof|typeof)(?![0-9a-zA-Z\$_])/ ),
	
	// Matches symbol operators.  Must go after the regular expression RE
	// and the single and multiline comment REs, or it will swallow the 
	// beginning of those constructs.
	OPERATOR_S_RE: new RegExp(
		"^(\\+=|-=|>=|<=|>{1,3}|<{1,2}|\\+{1,2}|-{1,2}|={1,3}|" + 
		"\\*=?|\\\/=?(?![\\\/\\*])|%=?|!=|&&?|\\|\\|?|!!?|\\^|~)"),
	
	ID_RE: ( /^([$_A-Z][A-Z0-9$_]*)/i ),
	// A constant is an uppercase identifier of only letters, numbers
	// and underscores.
	CONSTANT_RE: ( /^([_A-Z][A-Z0-9_]+)([^a-zA-Z0-9_$]|$)/ ),
	
	COMMENT_S_RE: ( /^(\/\/.*?)[\r\n]/ ),
	COMMENT_M_RE: ( /^(\/\*[\s\S]*?\*\/)/ ),
	BUILTIN_RE: ( /^(true|false|null|undefined)(?!([0-9a-zA-Z\$_]))/ ),
	
	// This is intended to match a key in an object literal, though it
	// will also match case statement.
	KEY_RE: ( /^([$_a-zA-Z][a-zA-Z0-9$_]*):/ ),
	
	// Special methods in Dean Edwards' Base class.
	// I'd include "Base", but it conflicts with MochiKit.Base
	BASE_RE: ( /^(base|extend|implement)(?![0-9a-zA-Z\$_])/ ),
	
	
	// Checks for the presence of the class 'js-con'.
	//test: function(n) {
	//	return (" " + n.className.replace(/\s+/g, " ") + " ")
	//	         .indexOf(' js-con ') > -1;
	//}
	test: MochiKit.Base.partial(CodeProcessor.match_class, "(js-con|javascript)")
});

// Register
CodeProcessor.add("JSHighlighter", CodeProcessor.JSHighlighter);


/**********************************************************************/

CodeProcessor.PBasic2Highlighter = CodeProcessor.JSHighlighter.extend({
	_get_chunks: function() {
		var C = CodeProcessor;
		var P = C.PBasic2Highlighter;
		return this.__chunks_cache || (
			this.__chunks_cache = [
				{re: /\s+\.\\,/,                cls: null},
				{re: C.PARENTHESIS_RE,          cls: 'pb2-par'},
				{re: C.BRACKETS_RE,             cls: 'pb2-bra'},
				{re: P.NUMBER_RE,               cls: 'pb2-num'},
				{re: P.OPERATOR_RE,             cls: 'pb2-op'},
				{re: P.RESERVED_WORD_RE,        cls: 'pb2-kwd'},
				{re: P.WORD_OPERATOR_RE,        cls: 'pb2-op'},
				{re: P.TYPE_RE,                 cls: 'pb2-type'},
				{re: P.CONVERSION_FORMATTER_RE, cls: 'pb2-conv'},
				{re: P.LABEL_RE,                cls: 'pb2-id pb2-label'},
				{re: P.SYMBOL_TYPE_RE,          cls: 'pb2-symb'},
				{re: P.BUILTIN_RE,              cls: 'pb2-id pb2-builtin'},
				{re: P.ALIAS_MODIFIER_RE,       cls: 'pb2-aliasmod'},
				{re: P.DEBUG_CONTROL_RE,        cls: 'pb2-ctrl'},
				{re: P.ID_RE,                   cls: 'pb2-id'},
				{re: C.STRING_DQUOT_RE,         cls: 'pb2-str'},
				{re: P.COND_COMPILATION_RE,     cls: 'pb2-cc'}
			]);
	},
	_get_contexts: function() {
		var C = CodeProcessor;
		return this.__context_cache || (
			this.__context_cache = [
				C.with_other_class(C.APOS_LINEEND_COMMENT_CTX , 'pb2-com'),
				C.with_other_class(C.DQUOTE_CTX, 'pb2-str')
			]);
	},
	toString: function(){ return '[object CodeProcessor.PBasic2Highlighter instance]'; }
}, {
	LABEL_RE: ( /^([A-Z][A-Za-z0-9_]*):/ ),
	
	NUMBER_RE: ( /^(-?(\d+|%[10]+|$[a-zA-Z0-9]+))/ ),
	
	ID_RE: ( /^([a-z][a-z0-9]*)/i ),
	
	// Symbol include for BS1 compatability:
	SYMBOL_TYPE_RE: ( /^(PIN|CON|VAR|SYMBOL)([^a-zA-Z0-9_]|$)/ ),
	TYPE_RE: ( /^(Word|Byte|Nib|Bit)([^a-zA-Z0-9_]|$)/ ),
	BUILTIN_RE: new RegExp('^(' +
		'(IN|OUT|DIR)([SLHA-D]|[0-9]|1[0-5])|' + // Pins
		'W([0-9]|1[0-2])' + // Words
		'B(1?[0-9]|2[0-5])' + // Bytes
		')([^a-zA-Z0-9_]|$)', 'i'
	),
	CONVERSION_FORMATTER_RE: new RegExp('^((' +
		'S?DEC|([SI]|IS)?HEX|([SI]|IS)?BIN|S?NUM|(WAIT|SP)?STR|SKIP|' +
		'WAIT' +
		')\d*)([^a-zA-Z0-9_]|$)', 'i'
	),
	ALIAS_MODIFIER_RE: new RegExp('^(' + 
		'(HIGH|LOW)(BYTE|NIB|BIT)|BYTE[01]|NIB[0-3]|BIT([0-9]|1[0-5])' +
		')([^a-zA-Z0-9_]|$)', 'i'
	),
	DEBUG_CONTROL_RE: new RegExp('^(' + 
		'LF|CLS|CRS(RXY|RLF|RRT|RUP|RDN|RX|RY)|CLR(EOL|DN)|CR' +
		')([^a-zA-Z0-9_]|$)', 'i'
	),
	RESERVED_WORD_RE: new RegExp('^(' +
		'AUXIO|BRANCH|BUTTON|COMPARE|CONFIGPIN|COUNT|DATA|DEBUG(IN)?|' +
		'DO|LOOP|WHILE|UNTIL|DTMFOUT|EEPROM|EXIT|FOR|NEXT|TO|STEP|' + 
		'FREQOUT|GET|GO(SUB|TO)|HIGH|I2C(IN|OUT)|(END)?IF|THEN|ELSE(IF)?|' +
		'INPUT|IOTERM|LCD(CMD|IN|OUT)|LOOK(DOWN|UP)|LOW|MAINIO|NAP|ON|' +
		'OUTPUT|OW(IN|OUT)|PAUSE|POLL(IN|MODE|OUT|RUN|WAIT)|POT|' +
		'PULS(IN|OUT)|PUT|PWM|RANDOM|RCTIME|READ|RETURN|REVERSE|RUN|' + 
		'(END)?SELECT|CASE|SER(IN|OUT)|SHIFT(IN|OUT)|SLEEP|SOUND|STOP|' +
		'STORE|TOGGLE|THRESOLD|WRITE|XOUT|END)([^a-zA-Z0-9_]|$)', 'i'
	),
	OPERATOR_RE: ( /^(=|<[><=]?|>[>=]?|\+|-|\*[\*\/]?|\/\/?|&\/?|\|\/?|\^\/?|~|\?)/ ),
	WORD_OPERATOR_RE: new RegExp('^(' + 
		'MIN|MAX|DIG|REV|AND|X?OR|ATN|HYP|ABS|[DN]CD|SIN|COS|SQR|NOT' +
		')([^a-zA-Z0-9_]|$)', 'i'
	),
	COND_COMPILATION_RE: ( /^(#[A-Z]+)[^A-Z]/ ),
	test: MochiKit.Base.partial(CodeProcessor.match_class, "(pbasic2?)")
});

// Register
CodeProcessor.add("PBasic2Highlighter", CodeProcessor.PBasic2Highlighter);


/**********************************************************************/

// JavaScript Highlighter module for SyntaxHighlighter
// Copyright 2006 Tom W.M. (http://freecog.net/)
// 12:15 p.m. Monday, July 03, 2006

// A good syntax overview: http://www.blooberry.com/indexdot/css/syntax/syntax.htm

/*
Units:
%|em|ex|px|in|cm|mm|pt|pc|deg|grad|rad|s|ms|Hz|kHz

Parsing outline

1: General Block
	1.1: Whitespace
	1.2: Comments
	1.3: At-Rule
		/^@[^\s]+(?=(\s|$))/
		... optional arguments (url, string, chunks) ... 
		... block (parse contents) or ;
	1.4: Rule-Set
		selector (chunks)
		... declaration block (parse contents, see 2) ...
	
2: Declaration Block
	2.1: Whitespace
	2.2: Comments
	2.3: Declarations -- the following in order
		2.3.1: /^[^\s:]+/     property
		     If it begins with a _, mark it as a hack.
		2.3.2: /^:/           colon
		2.3.3: chunks (3)
		2.3.4: /^!important/  optionally
		2.3.5: /^;|(?=})|     termination

3: Chunks -- general whitespace-separated values.
   Used to represent selectors, @rule arguments and property values.
   The series of chunks contintues until {, }, or ; are encountered
   outside of a string or URL.
	3.1: whitespace -- skip
	3.2: URLs /^url\([^\)]+?\)/
	3.3: string -- open string context
	3.4: colors
	     Should this include the currentColor keyword?
	     A swatch of the appropriate color should be generated following
	     the color.
	3.5: possible color /^[a-z]{3,}(?=[\s;!}]|$)/i
	     Test if it is actually a color by setting and getting it as a 
	     color property--if it works, generate a color swatch.
	3.6: other stuff -- skip
*/

/**
* @constructor
* @id CodeProcessor.CSSHighlighter
*/
CodeProcessor.CSSHighlighter = FilteredNodeQueue.extend({
	constructor: function(dom_node, node_filter) {
		//log("Instantiating JSHighlighter()");
		this.dom_node = dom_node;
		this.node_filter = node_filter;
		this.init_node_queue(node_filter, dom_node);
		
		this._stack = CSSHighlighter._get_base_stack();
		
		this.deferred = (new AsyncQueue(this))
			.loop(this.dispatch_node)
			.next(partial(log, "CSSHighlighter done."))
			.go();
	},
	end_node_queue: AsyncQueue.stop_loop,
	process_text_node: function(node) {
		
	},
	toString: function(){ return '[object CodeProcessor.CSSHighlighter instance]'; }
}, (function(){
	// Regular expression fragments:
	
	// Characters that cannot be part of a token.
	var token_break = ':;\s';
	
	// Hex, RGB(A) and HSL(A) colors
	var value_colors = '#([0-9a-f]{3}){1,2}|' + 
		'(rgb|hsl)a?\((\s*\d+%?\s*,){2}\s*\d+%?\s*' + 
		'(,\s*(1|0|0?.\d+)\s*))';
	
	// CSS2.1 named colors:
	var named_colors = 'maroon|red|orange|yellow|olive|' +
		'purple|fuchsia|white|lime|green|navy|blue|aqua|' +
		'teal|black|silver|gray';
	// CSS3 named colors from MochiKit, if available
	if (MochiKit.Color && MochiKit.Color.Color &&
	        MochiKit.Color.Color._namedColors) {
		named_colors = MochiKit.Base.keys(MochiKit.Color.Color._namedColors);
		named_colors.sort();
		named_colors.reverse();
		named_colors = MochiKit.Base.keys().join('|');
	}
	
	var system_colors = 'Active(Border|Caption)|' + 
		'AppWorkspace|Background|Button(Face|' + 
		'Highlight|Shadow|Text)|Caption|GrayText|' + 
		'Highlight(Text)?|Inactive(Border|Caption' + 
		'(Text)?)|Info(Background|Text)|Menu(Text)?|' + 
		'Scrollbar|ThreeD(DarkShadow|Face|Highlight|' + 
		'LightShadow|Shadow)|Window(Frame|Text)';
	
	// All valid colors
	var colors = [value_colors, named_colors, system_colors].join('|');
	
	var vendor_prefixes = '-(ms|moz|o|atsc|wap)-';
	var vendor_properties = 'filter|'
	
	// Select CSS 2.1 properties (plus some from CSS 3).
	var props = 'azimuth|background(-(attachment|' + 
		'color|image|position|repeat))?|border(-(' + 
		'bottom|left|right|top)(-(color|style|width' + 
		'))?|(collapse|color|spacing|style|width))?|' + 
		'bottom|box-sizing|caption-side|clear|clip|' + 
		'color|content(-(increment|reset))?|cue(-(' + 
		'after|before))?|cursor|direction|display|' + 
		'elevation|empty-cells|float|font(-(family|' + 
		'size|style|variant|weight))?|height|left|' + 
		'letter-spacing|line-height|list-style(-(' + 
		'image|position|type))?|margin(-(bottom|left|' + 
		'right|top))?|(max|min)-(height|width)|orphans' + 
		'|outline(-(color|style|width))?|overflow(-x|' + 
		'-y)?|padding(-(bottom|left|right|top))?|page' + 
		'-break-(after|before|inside)|pause(-(after|' + 
		'before))?|pitch(-range)?|play-during|position' + 
		'|quotes|richness|right|speak(-(header|numeral' + 
		'|punctuation))?speech-rate|stress|table-layou' + 
		't|text-(align|decoration|indent|transform)top' + 
		'|unicode-bidi|vertical-align|visibility|voice' +
		'-family|volume|white-space|widows|width|word-' +
		'spacing|z-index';
	var string = '(""|"(\\\\|\\"|[^"])+")';
	
	var url = 'url\(\s*(' + string + '|' +
	        '([\w!#$%&*\-~\/])*' + ')\s*\)';
	
	function re(base, follows, flags) {
		var re = '^(' + base + ')' + (follows || '');
		return (flags) ? new RegExp(re, flags) : new RegExp(re);
	}
	
	return {
		test: MochiKit.Base.partial(CodeProcessor.match_class, "(css-con|css)"),
		STRING: re(string),
		URL: re(url),
		COLOR: re(colors, token_break, 'i')
	};
})());


// Register
// Not implemented yet.
//CodeProcessor.add("CSSHighlighter", CodeProcessor.CSSHighlighter);