Also see Raw version, Minified version.
1 //!debugon
2 // Copyright 2006 Tom W.M. (http://freecog.net/)
3 // 11:27 p.m. Tuesday, April 11, 2006
4
5 // Use CodeProcessor.process(els) to add line numbers and syntax
6 // highlighting. The node must be identifiable as acceptable to each
7 // appropriate processor module. For the moment, this means it must
8 // have a certain class name--"ln-con" for line numbering, "js-con" for
9 // JavaScript.
10
11 // Dependencies:
12 // * Dean Edwards' Base class
13 // * My AsyncQueue class
14 // * MochiKit.Base
15 // * MochiKit.Async
16
17 // Known bugs:
18 // * Multiple division symbols on a line could potentially confuse
19 // the JavaScript highlighter.
20
21 // Note regarding mixing of object and regular expression literals:
22 // There is a bug in both my editor's syntax higlighting
23 // (Notepad2) and JSMin. Neither recognizes RE literals that
24 // follow a colon (JSMin's code states that it only recognizes
25 // those following "(", ",", or "="). To work around this, all
26 // RE literals that would be effected have been wrapped in
27 // parenthesis. Unfortunately, this makes them even more
28 // illegible (though not as much as converting them to strings
29 // would).
30
31
32 CodeProcessor = {
33 // List of processors.
34 processors: [],
35 // Registers a processor constructor. Processor.test() should be a
36 // function that returns true if the processor will accept a DOM
37 // node.
38 add: function(name, processor) {
39 CodeProcessor.processors.push({
40 name: name,
41 processor: processor,
42 test: processor.test
43 });
44 return processor;
45 },
46 // Applies each of the processors to the passed element(s).
47 // Accepts a string ID, element, or array-like object of the same.
48 process: function(els) {
49 // Put non-array-like objects into an array.
50 if (!MochiKit.Base.isArrayLike(els)) {
51 els = [els];
52 }
53 var results = [];
54 for (var x = 0, y = els.length; x < y; x++) {
55 var dom_node = els[x];
56 if (typeof dom_node == 'string')
57 dom_node = document.getElementById(dom_node);
58 results.push(CodeProcessor._process_element(dom_node));
59 }
60 return results;
61 },
62 // Processes an individual element.
63 _process_element: function(dom_node) {
64 var CP = CodeProcessor;
65
66 var filter_classes = ['ann-bracket', 'ann-default', 'ann-ellip'];
67 var filter_ids = [];
68
69 var className = dom_node.className;
70 var m = className.match(this.SELECT_BY_CLASS_RE);
71 if (m) {
72 var classes = m[1].substring(3).split('---');
73 filter_classes = filter_classes.concat(classes);
74 }
75
76 m = className.match(this.SELECT_BY_ID_RE);
77 if (m) filter_ids = m[1].substring(3).split('---');
78
79 var filterCount = 0;
80 // The filter function:
81 var filter = function(node) {
82 var id = node.id;
83 for (var i = 0; i < filter_ids.length; i++) {
84 if (id == filter_ids[i]) return true;
85 }
86 var cls = " " + node.className.replace(/\s+/g, " ") + " ";
87 for (var i = 0; i < filter_classes.length; i++) {
88 var test = " " + filter_classes[i] + " ";
89 if (cls.indexOf(test) > -1) return true;
90 }
91 filterCount++;
92 return false;
93 };
94
95 // Processors that accepted dom_node
96 var processors = [];
97 // Deferred to chain the deferred properties to.
98 var chain = MochiKit.Async.succeed();
99
100 for (var i = 0, j = CP.processors.length; i < j; i++) {
101 chain.addCallback(function(proc) {
102 if (proc.test(dom_node)) {
103 var p = new proc.processor(filter, dom_node);
104 processors.push(p);
105 if (p.filter_classes)
106 filter_classes = filter_classes.concat(p.filter_classes);
107 if (p.filter_ids)
108 filter_ids = filter_ids.concat(p.filter_ids);
109 filter = p.node_filter || filter;
110 return p.deferred;
111 }
112 }, CP.processors[i]);
113 chain.addErrback(log); // debug
114 }
115
116 var deferred = new MochiKit.Async.Deferred();
117 chain.addCallback(function() {
118 deferred.callback(dom_node, filter);
119 });
120
121 return {
122 processors: processors,
123 deferred: deferred
124 };
125 },
126 SELECT_BY_CLASS_RE: (/filterByClass(---[a-zA-Z][-a-zA-Z0-9]+)*/),
127 SELECT_BY_ID_RE: (/filterByID(---[a-zA-Z][-a-zA-Z0-9\.]+)*/),
128 match_class: function(re, el) {
129 return el.className.match(new RegExp("(^|\\s)" + re + "(\\s|$)"));
130 },
131
132 PARENTHESIS_RE: ( /^([\(\)])/ ),
133 BRACKETS_RE: ( /^([\[\]])/ ),
134 BRACES_RE: ( /^([\{\}])/ ),
135 STRING_SQUOT_RE: ( /^(''|'(\\\\|\\'|[^'])+')/ ),
136 STRING_DQUOT_RE: ( /^(""|"(\\\\|\\"|[^"])+")/ ),
137
138 // Generally useful constants for use in processor modules
139 C_LINEEND_COMMENT_CTX: { // Single line comment
140 start: ( /^(\/\/[^\r\n]*)/ ),
141 end: ( /^(.*?)[\r\n]/ ),
142 cls: 'c-sl-com'
143 },
144 APOS_LINEEND_COMMENT_CTX: { // Single line comment
145 start: ( /^('[^\r\n]*)/ ),
146 end: ( /^(.*?)[\r\n]/ ),
147 cls: 'apos-com'
148 },
149 C_MULTILINE_COMMENT_CTX: { // Multi-line comment
150 start: ( /^(\/\*[\s\S]*)/ ),
151 end: ( /^([\s\S]*?\*\/)/ ),
152 cls: 'c-ml-com'
153 },
154 DQUOTE_CTX: { // Double quote string
155 start: ( /^(".*)/ ),
156 end: ( /^("|(\\"|[^"])")/ ),
157 cls: 'dquote-str'
158 },
159 SQUOTE_CTX: { // Single quote string
160 start: ( /^('.*)/ ),
161 end: ( /^('|(\\\\\|\\'|[^'])+')/ ),
162 cls: 'squote-str'
163 },
164
165 with_other_class: function(ctx, new_cls) {
166 var o = {};
167 MochiKit.Base.update(o, ctx);
168 o.cls = new_cls;
169 return o;
170 },
171
172 toString: function(){ return '[object CodeProcessor]'; }
173 };
174
175
176 /**********************************************************************/
177
178
179 /**
180 * Allows for iterable-like processing of text nodes in a DOM tree,
181 * traversing depth-first. This is intended to be subclassed for
182 * use.
183 *
184 * @constructor
185 * @id NodeQueue
186 */
187 NodeQueue = Base.extend({
188 /**
189 * Initializes the queue. The children of the (optional)
190 * DOM node passed as the second argument will be inserted
191 * into the queue.
192 * @method
193 * @id NodeQueue.prototype.init_node_queue
194 */
195 init_node_queue: function(dom_node) {
196 this._current = null;
197 this._queue = []; // FIFO
198 switch (arguments.length) {
199 case 0:
200 break;
201 case 1:
202 this.prepend_children(dom_node);
203 break;
204 default:
205 throw "Invalid arguments to init_node_queue()";
206 }
207 },
208 /**
209 * Dispatches the node currently at the top of the queue to a
210 * processing function.
211 * @method
212 * @id NodeQueue.prototype.dispatch_node
213 */
214 dispatch_node: function() {
215 this._current = this._current || this._queue.shift();
216 if (!this._current) this.end_node_queue();
217
218 //log("@@@ Loop. this._current = " + repr(this._current));
219
220 switch (this._current.nodeType) {
221 case 1: // ELEMENT_NODE
222 this.process_element_node(this._current);
223 break;
224 case 3: // TEXT_NODE
225 this.process_text_node(this._current);
226 break;
227 default:
228 // Just skip anything else.
229 this.advance_queue();
230 break;
231 }
232 },
233 /**
234 * Discards anything currently being processed, so that
235 * `dispatch_node` fetches the next node from the queue.
236 * This must be called once each time `process_element_node`
237 * and `process_text_node` are finished with a node, or the
238 * queue will not advance.
239 * @method
240 * @id NodeQueue.prototype.advance_queue
241 */
242 advance_queue: function() {
243 this._current = null;
244 },
245 /**
246 * Prepends children into the queue for processing.
247 * This must be called with every element that is
248 * passed to `process_element_node`, or its children
249 * will not be processed.
250 * @method
251 * @id NodeQueue.prototype.prepend_children
252 */
253 prepend_children: function(element) {
254 for (var i = element.childNodes.length - 1; i >= 0; i--) {
255 var n = element.childNodes[i];
256 var num = this._queue.unshift(n);
257 }
258 //log("### " + element.childNodes.length + " childNodes unshift'd.");
259 },
260 /**
261 * Gets called with each element that passes through
262 * the queue, placing their child nodes into the queue.
263 * Override this in a subclass for different behavior.
264 * @method
265 * @id NodeQueue.prototype.process_element_node
266 */
267 process_element_node: function(node) {
268 this.prepend_children(node);
269 this.advance_queue();
270 },
271 /**
272 * Gets called with each text node that passes through
273 * the queue. Override this in a subclass.
274 * @method
275 * @id NodeQueue.prototype.process_text_node
276 */
277 process_text_node: function(node) {
278 // override in subclass
279 this.advance_queue();
280 },
281 /**
282 * Gets called when the queue is exhausted. Override this
283 * in a subclass if you need it to do something.
284 * @method
285 * @id NodeQueue.prototype.end_node_queue
286 */
287 end_node_queue: function() {},
288 toString: function() { return '[object NodeQueue instance]'; }
289 });
290
291
292 /**
293 * Extends `NodeQueue` to allow for filtering of the elements it
294 * processes. Created to allow simple processing of all the text
295 * nodes in a tree.
296 *
297 * @id FilteredNodeQueue
298 * @constructor
299 */
300 FilteredNodeQueue = NodeQueue.extend({
301 /**
302 * Initializes the queue. Pass a predicate that identifies
303 * acceptable nodes (required) and a DOM node (optional) to
304 * seed the queue with (placing its child nodes in the queue).
305 * @method
306 * @id FilteredNodeQueue.prototype.init_node_queue
307 */
308 init_node_queue: function(node_filter, dom_node) {
309 this.__node_filter = node_filter;
310 if (this.__node_filter(dom_node))
311 this.base();
312 else
313 this.base(dom_node);
314 },
315 /**
316 * Places the child nodes of every element that passes
317 * through the queue in the queue unless the element
318 * is filtered.
319 * @method
320 * @id FilteredNodeQueue.prototype.process_element_node
321 */
322 process_element_node: function(node) {
323 if (!this.__node_filter(node))
324 this.prepend_children(node);
325 this.advance_queue();
326 },
327 toString: function() { return '[object FilteredNodeQueue instance]'; }
328 });
329
330
331 /**********************************************************************/
332
333 /**
334 * Adds line numbers to source code.
335 *
336 * @id CodeProcessor.LineNumProc
337 * @constructor
338 */
339 CodeProcessor.LineNumProc = FilteredNodeQueue.extend({
340 constructor: function(node_filter, dom_node) {
341 this.dom_node = dom_node;
342 this.node_filter = node_filter;
343 this.init_node_queue(node_filter, dom_node);
344 this.number_nodes = [];
345 this.filter_classes = [this.NUMBER_CLASS];
346 this._counter = 1;
347
348 this.deferred = (new AsyncQueue(this))
349 // Create containers:
350 .next(function() {
351 // Line 1:
352 dom_node.insertBefore(this.create_number_container(),
353 dom_node.firstChild);
354 })
355 // The rest of the lines:
356 .loop(this.dispatch_node)
357 // Calculate how many characters wide
358 // the numbering shoudl be:
359 .next(this.init_number_insertion)
360 // Insert the text into the containers:
361 .loop(this.insert_number)
362 // Done!
363 //.next(partial(log, "LineNumProc done."))
364 .go();
365 },
366 /**
367 * Creates the <span> that contains the line number, incrementing
368 * the counter, appending it to the `number_nodes` array and
369 * returning it.
370 * @method
371 * @id CodeProcessor.LineNumProc.prototype.create_number_container
372 */
373 create_number_container: function() {
374 this._counter++;
375 var el = document.createElement('span');
376 el.className = this.NUMBER_CLASS;
377 this.number_nodes.push(el);
378 return el;
379 },
380 /**
381 * Searches through text nodes for line breaks. The text node is
382 * split after the break, and the line number container inserted.
383 * @method
384 * @id CodeProcessor.LineNumProc.prototype.process_text_node
385 */
386 process_text_node: function(node) {
387 var text = node.nodeValue;
388 var m = text.match(/[^\r\n]*(\r\n|[\r\n])/);
389 if (m) {
390 var t = document.createTextNode(m[0]);
391 node.parentNode.insertBefore(t, node);
392 node.parentNode.insertBefore(
393 this.create_number_container(), node);
394 text = text.substring(m[0].length);
395 if (!text) {
396 node.parentNode.removeChild(node);
397 this.advance_queue();
398 } else {
399 node.nodeValue = text;
400 }
401 } else {
402 this.advance_queue();
403 }
404 },
405 /**
406 * Stop the `AsyncQueue` instance's loop when the node queue
407 * is exhausted.
408 * @method
409 * @id CodeProcessor.LineNumProc.prototype.end_node_queue
410 */
411 end_node_queue: AsyncQueue.stop_loop,
412 /**
413 * Sets the value this._precision to the correct number of places
414 * for the value of `this._counter`.
415 * @method
416 * @id CodeProcessor.LineNumProc.prototype.init_number_insertion
417 */
418 init_number_insertion: function() {
419 var p = 1, c = this._counter;
420 while (Math.pow(10, p) < c) {
421 p++;
422 }
423 // Precision is 2 at minimum, so that multiple short code
424 // examples on a single page will match up.
425 this._precision = Math.max(2, p);
426 this._index = 0;
427 },
428 /**
429 * Pads a number's string representation to the number of places
430 * in `this._precision`
431 * @method
432 * @id CodeProcessor.LineNumProc.prototype.
433 */
434 pad: function(num) {
435 num = "" + num;
436 while (num.length < this._precision) {
437 num = "\xA0" + num;
438 }
439 return num;
440 },
441 /**
442 * Insert
443 * @method
444 * @id CodeProcessor.LineNumProc.prototype.insert_number
445 */
446 insert_number: function() {
447 var node = this.number_nodes[this._index++];
448 if (!node) {
449 throw AsyncQueue.StopLoop;
450 }
451 var text = this.pad(this._index) + '.\xA0';
452 var num = document.createTextNode(text);
453 node.appendChild(num);
454 },
455 CONTAINER_CLASS: 'ln-con',
456 NUMBER_CLASS: 'ln-num',
457 toString: function(){ return '[object CodeProcessor.LineNumProc instance]'; }
458 }, {
459 /**
460 * Predicate that accepts DOMElements with classes of "linenumbers"
461 * "ln-con".
462 * @method
463 * @id CodeProcessor.LineNumProc.prototype.insert_number
464 */
465 test: MochiKit.Base.partial(CodeProcessor.match_class, "(linenumbers|ln-con)")
466 });
467
468 CodeProcessor.add("LineNumProc", CodeProcessor.LineNumProc);
469
470
471 /**********************************************************************/
472
473 // JavaScript Highlighter module for SyntaxHighlighter
474 // Copyright 2006 Tom W.M. (http://freecog.net/)
475 // 12:15 p.m. Monday, July 03, 2006
476
477 CodeProcessor.JSHighlighter = FilteredNodeQueue.extend({
478 constructor: function(node_filter, dom_node) {
479 //log("Instantiating JSHighlighter()");
480 this.dom_node = dom_node;
481 this.node_filter = node_filter;
482 this.init_node_queue(node_filter, dom_node);
483
484 this.deferred = (new AsyncQueue(this))
485 .loop(this.dispatch_node)
486 //.next(partial(log, "JSHighlighter done."))
487 .go();
488 },
489 end_node_queue: AsyncQueue.stop_loop,
490 process_text_node: function(node) {
491 var text = node.nodeValue;
492
493 // Empty text nodes trigger an infinite loop.
494 if (!text) {
495 //log("<<< Empty text node discarded.");
496 node.parentNode.removeChild(node);
497 this.advance_queue();
498 return;
499 }
500
501 var ctx = this._context;
502 if (ctx) { // Is there an open context?
503 var m = text.match(ctx.end);
504 if (m) { // Is the end of the context?
505 if (m[1])
506 this._process_match(ctx, m[1], node, text);
507 if (ctx.postprocessor) {
508 ctx.postprocessor(this._context_contents);
509 }
510 this._context = null;
511 //log("XXX Context closed. m = " + repr(m));
512 } else {
513 // We haven't reached the end yet. Devour the
514 // entire text node.
515 this._process_match(ctx, text, node, text);
516 //log("--- Context devoured " + repr(text));
517 }
518 return;
519
520 } else { // Process normally.
521 var REs = this._get_chunks();
522 for (var i = 0; i < REs.length; i++) {
523 var o = REs[i];
524 var m = text.match(o.re);
525 if (m) {
526 this._process_match(o, m[1], node, text);
527 //log(">>> RE " + i + " matched " + repr(m) + ". cls = " + repr(o.cls));
528 return;
529 }
530 }
531 }
532
533 // Hmm... looks like none of the REs matched.
534 // We could be dealing with a comment or string
535 // that has been broken up.
536 var cs = this._get_contexts();
537 for (var i = 0; i < cs.length; i++) {
538 var o = cs[i];
539 var m = text.match(o.start);
540 if (m) {
541 this._context = o;
542 this._context_contents = [];
543 this._process_match(o, m[1], node, text);
544 //log("+++ Context opened. m = " + repr(m));
545 return;
546 }
547 }
548
549 // If all else fails, move forward a character.
550 var t = document.createTextNode(text.charAt(0));
551 text = text.substring(1);
552 node.nodeValue = text;
553 node.parentNode.insertBefore(t, node);
554 },
555 // Turns a regular expression match into the appropriate DOM nodes.
556 _process_match: function(o, m, node, text) {
557 var t = document.createTextNode(m);
558 if (o.cls) {
559 var e = document.createElement('span');
560 e.className = o.cls;
561 e.appendChild(t);
562 }
563 node.parentNode.insertBefore(e || t, node);
564 if (this._context) {
565 this._context_contents.push(e || t);
566 }
567 text = text.substring(m.length);
568 if (text) {
569 node.nodeValue = text; // Probably slow.
570 } else { // We're done with the text node.
571 node.parentNode.removeChild(node);
572 this.advance_queue();
573 }
574 },
575
576 // Maps regular expressions to classes.
577 _get_chunks: function() {
578 var C = CodeProcessor;
579 var J = C.JSHighlighter;
580 return this.__chunks_cache || (
581 this.__chunks_cache = [
582 {re: J.SKIP_RE, cls: null},
583 {re: C.PARENTHESIS_RE, cls: "js-par"},
584 {re: C.BRACKETS_RE, cls: "js-bra"},
585 {re: C.BRACES_RE, cls: "js-br"},
586 {re: J.KEYWORD_RE, cls: "js-kwd"},
587 {re: J.BUILTIN_RE, cls: "js-id js-builtin"},
588 {re: J.BASE_RE, cls: "js-id js-base"},
589 {re: J.OPERATOR_W_RE, cls: "js-op"},
590 {re: J.NUMBER_RE, cls: "js-num"},
591 {re: J.NUMBER_W_RE, cls: "js-num"},
592 //{re: J.LONE_DOT, cls: null},
593 {re: J.KEY_RE, cls: "js-id js-key"},
594 {re: J.CONSTANT_RE, cls: "js-id js-const"},
595 {re: J.ID_RE, cls: "js-id"},
596 {re: J.COMMENT_M_RE, cls: "js-com"},
597 {re: J.RE_RE, cls: "js-re"},
598 {re: J.COMMENT_S_RE, cls: "js-com"},
599 {re: J.OPERATOR_S_RE, cls: "js-op"},
600 {re: C.STRING_SQUOT_RE, cls: "js-str"},
601 {re: C.STRING_DQUOT_RE, cls: "js-str"}
602 ]);
603 },
604 _get_contexts: function() {
605 var C = CodeProcessor;
606 return this.__context_cache || (
607 this.__context_cache = [
608 C.with_other_class(C.C_LINEEND_COMMENT_CTX, 'js-com'),
609 C.with_other_class(C.C_MULTILINE_COMMENT_CTX, 'js-com'),
610 C.with_other_class(C.DQUOTE_CTX, 'js-str'),
611 C.with_other_class(C.SQUOTE_CTX, 'js-str')
612 ]);
613 },
614 toString: function(){ return '[object CodeProcessor.JSHighlighter instance]'; }
615 }, {
616 // All regular expressions are constructed so that the first group
617 // is the relevant chunk of text.
618
619 // Should ? and : be operators? Something else?
620 SKIP_RE: ( /^(([\s,;\?:]|\.(?![$_a-zA-Z]))+)/ ),
621 //LONE_DOT: ( /^(\.)/ ),
622
623 // There are far too many of these silly keywords.
624 // Should this list include "true" and "false"?
625 KEYWORD_RE: new RegExp("^(" +
626 "abstract|boolean|break|byte|case|catch|char|class|const|" +
627 "continue|debugger|default|delete|double|do|else|enum|export|" +
628 "extends|final|finally|float|for|function|goto|if|implements|" +
629 "import|interface|int|in|long|native|new|package|private|" +
630 "protected|public|return|short|static|super|synchronized|" +
631 "switch|this|throws|throw|transient|try|var|volatile|void|" +
632 "while|with)(?![0-9a-zA-Z\\$_])"),
633
634 NUMBER_RE: ( /^([+-]?(0x[0-9a-f]+|[0-9]+\.[0-9]*|\.?[0-9]+)(e[+-]?[0-9]+)?)/i ),
635
636 // Should NaN and Infitity be styled as numbers, or keywords?
637 NUMBER_W_RE: ( /^([+-]?Infinity|NaN)(?![0-9a-zA-Z\$_])/ ),
638
639 RE_RE: ( /^(\/(\\\\|\\\/|[^\/\n\r])*[^\\]\/g?i?m?)[ \t]*([\)\}\],;.\n\t]|$)/ ),
640
641 // Matches the keyword operators. Must go *before* the identifier RE,
642 // because these are also all valid identifiers.
643 OPERATOR_W_RE: ( /^(delete|instanceof|typeof)(?![0-9a-zA-Z\$_])/ ),
644
645 // Matches symbol operators. Must go after the regular expression RE
646 // and the single and multiline comment REs, or it will swallow the
647 // beginning of those constructs.
648 OPERATOR_S_RE: new RegExp(
649 "^(\\+=|-=|>=|<=|>{1,3}|<{1,2}|\\+{1,2}|-{1,2}|={1,3}|" +
650 "\\*=?|\\\/=?(?![\\\/\\*])|%=?|!=|&&?|\\|\\|?|!!?|\\^|~)"),
651
652 ID_RE: ( /^([$_A-Z][A-Z0-9$_]*)/i ),
653 // A constant is an uppercase identifier of only letters, numbers
654 // and underscores.
655 CONSTANT_RE: ( /^([_A-Z][A-Z0-9_]+)([^a-zA-Z0-9_$]|$)/ ),
656
657 COMMENT_S_RE: ( /^(\/\/.*?)[\r\n]/ ),
658 COMMENT_M_RE: ( /^(\/\*[\s\S]*?\*\/)/ ),
659 BUILTIN_RE: ( /^(true|false|null|undefined)(?!([0-9a-zA-Z\$_]))/ ),
660
661 // This is intended to match a key in an object literal, though it
662 // will also match case statement.
663 KEY_RE: ( /^([$_a-zA-Z][a-zA-Z0-9$_]*):/ ),
664
665 // Special methods in Dean Edwards' Base class.
666 // I'd include "Base", but it conflicts with MochiKit.Base
667 BASE_RE: ( /^(base|extend|implement)(?![0-9a-zA-Z\$_])/ ),
668
669
670 // Checks for the presence of the class 'js-con'.
671 //test: function(n) {
672 // return (" " + n.className.replace(/\s+/g, " ") + " ")
673 // .indexOf(' js-con ') > -1;
674 //}
675 test: MochiKit.Base.partial(CodeProcessor.match_class, "(js-con|javascript)")
676 });
677
678 // Register
679 CodeProcessor.add("JSHighlighter", CodeProcessor.JSHighlighter);
680
681
682 /**********************************************************************/
683
684 CodeProcessor.PBasic2Highlighter = CodeProcessor.JSHighlighter.extend({
685 _get_chunks: function() {
686 var C = CodeProcessor;
687 var P = C.PBasic2Highlighter;
688 return this.__chunks_cache || (
689 this.__chunks_cache = [
690 {re: /\s+\.\\,/, cls: null},
691 {re: C.PARENTHESIS_RE, cls: 'pb2-par'},
692 {re: C.BRACKETS_RE, cls: 'pb2-bra'},
693 {re: P.NUMBER_RE, cls: 'pb2-num'},
694 {re: P.OPERATOR_RE, cls: 'pb2-op'},
695 {re: P.RESERVED_WORD_RE, cls: 'pb2-kwd'},
696 {re: P.WORD_OPERATOR_RE, cls: 'pb2-op'},
697 {re: P.TYPE_RE, cls: 'pb2-type'},
698 {re: P.CONVERSION_FORMATTER_RE, cls: 'pb2-conv'},
699 {re: P.LABEL_RE, cls: 'pb2-id pb2-label'},
700 {re: P.SYMBOL_TYPE_RE, cls: 'pb2-symb'},
701 {re: P.BUILTIN_RE, cls: 'pb2-id pb2-builtin'},
702 {re: P.ALIAS_MODIFIER_RE, cls: 'pb2-aliasmod'},
703 {re: P.DEBUG_CONTROL_RE, cls: 'pb2-ctrl'},
704 {re: P.ID_RE, cls: 'pb2-id'},
705 {re: C.STRING_DQUOT_RE, cls: 'pb2-str'},
706 {re: P.COND_COMPILATION_RE, cls: 'pb2-cc'}
707 ]);
708 },
709 _get_contexts: function() {
710 var C = CodeProcessor;
711 return this.__context_cache || (
712 this.__context_cache = [
713 C.with_other_class(C.APOS_LINEEND_COMMENT_CTX , 'pb2-com'),
714 C.with_other_class(C.DQUOTE_CTX, 'pb2-str')
715 ]);
716 },
717 toString: function(){ return '[object CodeProcessor.PBasic2Highlighter instance]'; }
718 }, {
719 LABEL_RE: ( /^([A-Z][A-Za-z0-9_]*):/ ),
720
721 NUMBER_RE: ( /^(-?(\d+|%[10]+|$[a-zA-Z0-9]+))/ ),
722
723 ID_RE: ( /^([a-z][a-z0-9]*)/i ),
724
725 // Symbol include for BS1 compatability:
726 SYMBOL_TYPE_RE: ( /^(PIN|CON|VAR|SYMBOL)([^a-zA-Z0-9_]|$)/ ),
727 TYPE_RE: ( /^(Word|Byte|Nib|Bit)([^a-zA-Z0-9_]|$)/ ),
728 BUILTIN_RE: new RegExp('^(' +
729 '(IN|OUT|DIR)([SLHA-D]|[0-9]|1[0-5])|' + // Pins
730 'W([0-9]|1[0-2])' + // Words
731 'B(1?[0-9]|2[0-5])' + // Bytes
732 ')([^a-zA-Z0-9_]|$)', 'i'
733 ),
734 CONVERSION_FORMATTER_RE: new RegExp('^((' +
735 'S?DEC|([SI]|IS)?HEX|([SI]|IS)?BIN|S?NUM|(WAIT|SP)?STR|SKIP|' +
736 'WAIT' +
737 ')\d*)([^a-zA-Z0-9_]|$)', 'i'
738 ),
739 ALIAS_MODIFIER_RE: new RegExp('^(' +
740 '(HIGH|LOW)(BYTE|NIB|BIT)|BYTE[01]|NIB[0-3]|BIT([0-9]|1[0-5])' +
741 ')([^a-zA-Z0-9_]|$)', 'i'
742 ),
743 DEBUG_CONTROL_RE: new RegExp('^(' +
744 'LF|CLS|CRS(RXY|RLF|RRT|RUP|RDN|RX|RY)|CLR(EOL|DN)|CR' +
745 ')([^a-zA-Z0-9_]|$)', 'i'
746 ),
747 RESERVED_WORD_RE: new RegExp('^(' +
748 'AUXIO|BRANCH|BUTTON|COMPARE|CONFIGPIN|COUNT|DATA|DEBUG(IN)?|' +
749 'DO|LOOP|WHILE|UNTIL|DTMFOUT|EEPROM|EXIT|FOR|NEXT|TO|STEP|' +
750 'FREQOUT|GET|GO(SUB|TO)|HIGH|I2C(IN|OUT)|(END)?IF|THEN|ELSE(IF)?|' +
751 'INPUT|IOTERM|LCD(CMD|IN|OUT)|LOOK(DOWN|UP)|LOW|MAINIO|NAP|ON|' +
752 'OUTPUT|OW(IN|OUT)|PAUSE|POLL(IN|MODE|OUT|RUN|WAIT)|POT|' +
753 'PULS(IN|OUT)|PUT|PWM|RANDOM|RCTIME|READ|RETURN|REVERSE|RUN|' +
754 '(END)?SELECT|CASE|SER(IN|OUT)|SHIFT(IN|OUT)|SLEEP|SOUND|STOP|' +
755 'STORE|TOGGLE|THRESOLD|WRITE|XOUT|END)([^a-zA-Z0-9_]|$)', 'i'
756 ),
757 OPERATOR_RE: ( /^(=|<[><=]?|>[>=]?|\+|-|\*[\*\/]?|\/\/?|&\/?|\|\/?|\^\/?|~|\?)/ ),
758 WORD_OPERATOR_RE: new RegExp('^(' +
759 'MIN|MAX|DIG|REV|AND|X?OR|ATN|HYP|ABS|[DN]CD|SIN|COS|SQR|NOT' +
760 ')([^a-zA-Z0-9_]|$)', 'i'
761 ),
762 COND_COMPILATION_RE: ( /^(#[A-Z]+)[^A-Z]/ ),
763 test: MochiKit.Base.partial(CodeProcessor.match_class, "(pbasic2?)")
764 });
765
766 // Register
767 CodeProcessor.add("PBasic2Highlighter", CodeProcessor.PBasic2Highlighter);
768
769
770 /**********************************************************************/
771
772 // JavaScript Highlighter module for SyntaxHighlighter
773 // Copyright 2006 Tom W.M. (http://freecog.net/)
774 // 12:15 p.m. Monday, July 03, 2006
775
776 // A good syntax overview: http://www.blooberry.com/indexdot/css/syntax/syntax.htm
777
778 /*
779 Units:
780 %|em|ex|px|in|cm|mm|pt|pc|deg|grad|rad|s|ms|Hz|kHz
781
782 Parsing outline
783
784 1: General Block
785 1.1: Whitespace
786 1.2: Comments
787 1.3: At-Rule
788 /^@[^\s]+(?=(\s|$))/
789 ... optional arguments (url, string, chunks) ...
790 ... block (parse contents) or ;
791 1.4: Rule-Set
792 selector (chunks)
793 ... declaration block (parse contents, see 2) ...
794
795 2: Declaration Block
796 2.1: Whitespace
797 2.2: Comments
798 2.3: Declarations -- the following in order
799 2.3.1: /^[^\s:]+/ property
800 If it begins with a _, mark it as a hack.
801 2.3.2: /^:/ colon
802 2.3.3: chunks (3)
803 2.3.4: /^!important/ optionally
804 2.3.5: /^;|(?=})| termination
805
806 3: Chunks -- general whitespace-separated values.
807 Used to represent selectors, @rule arguments and property values.
808 The series of chunks contintues until {, }, or ; are encountered
809 outside of a string or URL.
810 3.1: whitespace -- skip
811 3.2: URLs /^url\([^\)]+?\)/
812 3.3: string -- open string context
813 3.4: colors
814 Should this include the currentColor keyword?
815 A swatch of the appropriate color should be generated following
816 the color.
817 3.5: possible color /^[a-z]{3,}(?=[\s;!}]|$)/i
818 Test if it is actually a color by setting and getting it as a
819 color property--if it works, generate a color swatch.
820 3.6: other stuff -- skip
821 */
822
823 /**
824 * @constructor
825 * @id CodeProcessor.CSSHighlighter
826 */
827 CodeProcessor.CSSHighlighter = FilteredNodeQueue.extend({
828 constructor: function(dom_node, node_filter) {
829 //log("Instantiating JSHighlighter()");
830 this.dom_node = dom_node;
831 this.node_filter = node_filter;
832 this.init_node_queue(node_filter, dom_node);
833
834 this._stack = CSSHighlighter._get_base_stack();
835
836 this.deferred = (new AsyncQueue(this))
837 .loop(this.dispatch_node)
838 .next(partial(log, "CSSHighlighter done."))
839 .go();
840 },
841 end_node_queue: AsyncQueue.stop_loop,
842 process_text_node: function(node) {
843
844 },
845 toString: function(){ return '[object CodeProcessor.CSSHighlighter instance]'; }
846 }, (function(){
847 // Regular expression fragments:
848
849 // Characters that cannot be part of a token.
850 var token_break = ':;\s';
851
852 // Hex, RGB(A) and HSL(A) colors
853 var value_colors = '#([0-9a-f]{3}){1,2}|' +
854 '(rgb|hsl)a?\((\s*\d+%?\s*,){2}\s*\d+%?\s*' +
855 '(,\s*(1|0|0?.\d+)\s*))';
856
857 // CSS2.1 named colors:
858 var named_colors = 'maroon|red|orange|yellow|olive|' +
859 'purple|fuchsia|white|lime|green|navy|blue|aqua|' +
860 'teal|black|silver|gray';
861 // CSS3 named colors from MochiKit, if available
862 if (MochiKit.Color && MochiKit.Color.Color &&
863 MochiKit.Color.Color._namedColors) {
864 named_colors = MochiKit.Base.keys(MochiKit.Color.Color._namedColors);
865 named_colors.sort();
866 named_colors.reverse();
867 named_colors = MochiKit.Base.keys().join('|');
868 }
869
870 var system_colors = 'Active(Border|Caption)|' +
871 'AppWorkspace|Background|Button(Face|' +
872 'Highlight|Shadow|Text)|Caption|GrayText|' +
873 'Highlight(Text)?|Inactive(Border|Caption' +
874 '(Text)?)|Info(Background|Text)|Menu(Text)?|' +
875 'Scrollbar|ThreeD(DarkShadow|Face|Highlight|' +
876 'LightShadow|Shadow)|Window(Frame|Text)';
877
878 // All valid colors
879 var colors = [value_colors, named_colors, system_colors].join('|');
880
881 var vendor_prefixes = '-(ms|moz|o|atsc|wap)-';
882 var vendor_properties = 'filter|'
883
884 // Select CSS 2.1 properties (plus some from CSS 3).
885 var props = 'azimuth|background(-(attachment|' +
886 'color|image|position|repeat))?|border(-(' +
887 'bottom|left|right|top)(-(color|style|width' +
888 '))?|(collapse|color|spacing|style|width))?|' +
889 'bottom|box-sizing|caption-side|clear|clip|' +
890 'color|content(-(increment|reset))?|cue(-(' +
891 'after|before))?|cursor|direction|display|' +
892 'elevation|empty-cells|float|font(-(family|' +
893 'size|style|variant|weight))?|height|left|' +
894 'letter-spacing|line-height|list-style(-(' +
895 'image|position|type))?|margin(-(bottom|left|' +
896 'right|top))?|(max|min)-(height|width)|orphans' +
897 '|outline(-(color|style|width))?|overflow(-x|' +
898 '-y)?|padding(-(bottom|left|right|top))?|page' +
899 '-break-(after|before|inside)|pause(-(after|' +
900 'before))?|pitch(-range)?|play-during|position' +
901 '|quotes|richness|right|speak(-(header|numeral' +
902 '|punctuation))?speech-rate|stress|table-layou' +
903 't|text-(align|decoration|indent|transform)top' +
904 '|unicode-bidi|vertical-align|visibility|voice' +
905 '-family|volume|white-space|widows|width|word-' +
906 'spacing|z-index';
907 var string = '(""|"(\\\\|\\"|[^"])+")';
908
909 var url = 'url\(\s*(' + string + '|' +
910 '([\w!#$%&*\-~\/])*' + ')\s*\)';
911
912 function re(base, follows, flags) {
913 var re = '^(' + base + ')' + (follows || '');
914 return (flags) ? new RegExp(re, flags) : new RegExp(re);
915 }
916
917 return {
918 test: MochiKit.Base.partial(CodeProcessor.match_class, "(css-con|css)"),
919 STRING: re(string),
920 URL: re(url),
921 COLOR: re(colors, token_break, 'i')
922 };
923 })());
924
925
926 // Register
927 // Not implemented yet.
928 //CodeProcessor.add("CSSHighlighter", CodeProcessor.CSSHighlighter);
© 2004–2010 Tom W. Most