Languages

Build-In Support

EnlighterJS v2.11.1 is out including new Languages & Themes. View the Changelog

Javascript

Select Theme

Lorem ipsum dolor sit amet, consetetur sadipscing elitr, sed diam nonumy eirmod tempor invidunt ut labore et dolore magna aliquyam erat, sed diam voluptua. At vero eos et accusam et justo duo dolores et ea rebum. Stet clita kasd gubergren, no sea takimata sanctus est Lorem ipsum dolor sit amet. Lorem ipsum dolor sit amet, consetetur sadipscing elitr, sed diam nonumy eirmod tempor var another_greeting = 'Greetings, people of Earth.'; invidunt ut labore et dolore magna aliquyam erat, sed diam voluptua. At vero eos et accusam et justo duo dolores et ea rebum. Stet clita kasd gubergren, no sea takimata sanctus est Lorem ipsum dolor sit amet. Lorem ipsum dolor sit amet.

// Comments
/* This
 is a
 block
 * comment
 */

/** Numbers */
345;    // an "integer", although there is only one numeric type in JavaScript
34.5;   // a floating-point number
3.45e2; // another floating-point, equivalent to 345
0377;   // an octal integer equal to 255
0xFF;   // a hexadecimal integer equal to 255, the letters A-F may be upper- or lowercase

/** Strings */
var greeting = "Hello, world!";
var another_greeting = 'Greetings, people of Earth.';
var escaped_strings = "Hello from an \" escaped string" + 'this should \' work';

/** Regex */
var regex = new RegExp("pattern" [, "flags"]);
var literal = /pattern/gimy;

/** Snippet */
var Language = new Class({
	
	Implements: [Options],
	options: {
		matchType: "standard",
		strict: false
	},
	language: '',
	defaultTheme: 'standard',
	
	patterns: new Hash(),
	keywords: new Hash(),
	rules:    new Hash(),
	delimiters: new Hash({
		start: null,
		end: null
	}),

	/************************
	 * Common Regex Rules
	 ***********************/
	common: {	
		slashComments: /(?:^|[^\\])\/\/.*$/gm, // Matches a C style single-line comment.
		poundComments: /#.*$/gm,                 // Matches a Perl style single-line comment.
		multiComments: /\/\*[\s\S]*?\*\//gm,     // Matches a C style multi-line comment.
		aposStrings:   /'[^'\\]*(?:\\.[^'\\]*)*'/gm, // Matches a string enclosed by single quotes.
		quotedStrings: /"[^"\\]*(?:\\.[^"\\]*)*"/gm, // Matches a string enclosed by double quotes.
		strings:       /'[^'\\]*(?:\\.[^'\\]*)*'|"[^"\\]*(?:\\.[^"\\]*)*"/gm, // Matches both.
		properties:    /\.([\w]+)\s*/gi,   // Matches a property: .property style.
		methodCalls:   /\.([\w]+)\s*\(/gm, // Matches a method call: .methodName() style.
		functionCalls: /\b([\w]+)\s*\(/gm,   // Matches a function call: functionName() style.
		brackets:      /\{|\}|\(|\)|\[|\]/g, // Matches any of the common brackets.
		numbers:       /\b((?:(\d+)?\.)?[0-9]+|0x[0-9A-F]+)\b/gi // Matches integers, decimals, hexadecimals.
	},
	
	/************************
	 * Language Constructor
	 ***********************/
	initialize: function(lighter, theme, options, tokens) {
		this.setOptions(options);
		this.tokens = tokens || [];
		options = this.options;
		
		// Set Lighter/Language/Theme relationship.
		this.lighter = lighter;
		this.theme = new Theme[theme || this.defaultTheme](lighter, this);
		
		// Add delimiter rules if not in strict mode
		if (!options.strict) {
			if (this.delimiters.start) this.addLanguage('delimBeg', this.delimiters.start, 'de1');
			if (this.delimiters.end)   this.addLanguage('delimEnd', this.delimiters.end,   'de2');
		}
		
		// Set Keyword Rules from this.keywords object.
		this.keywords.each(function(keywordSet, ruleName) {
			if (keywordSet.csv != '') {
				this.addLanguage(ruleName, this.csvToRegExp(keywordSet.csv, "g"), keywordSet.alias);
			}
		}, this);
		
		// Set Rules from this.patterns object.
		this.patterns.each(function(regex, ruleName) {
			this.addLanguage(ruleName, regex.pattern, regex.alias);
		}, this);
		
		// Set builder object for matchType.
		this.builder = new Hash({
			'standard': this.findMatches,
			'lazy':     this.findMatchesLazy
		});
		
		/** Process source code based on match type. */
		var codeBeg = 0,
		    codeEnd = lighter.code.length,
		    codeSeg = '',
		    delim   = this.delimiters,
		    matches = [],
		    match   = null,
		    endMatch = null;
		
		if (!options.strict) {
			// Find matches through the complete source code.
			matches.extend(this.builder[options.matchType].pass(lighter.code, this)());
		} else if (delim.start && delim.end) {
			// Find areas between language delimiters and find matches there.
			while ((match = delim.start.exec(lighter.code)) != null ) {
				delim.end.lastIndex = delim.start.lastIndex;
				if ((endMatch = delim.end.exec(lighter.code)) != null ) {
					matches.push(new Token(match[0], 'de1', match.index));
					codeBeg = delim.start.lastIndex;
					codeEnd = endMatch.index-1;
					codeSeg = lighter.code.substring(codeBeg, codeEnd);
					matches.extend(this.builder[options.matchType].pass([codeSeg, codeBeg], this)());
					matches.push(new Token(endMatch[0], 'de2', endMatch.index));
				}
			}
		}
		this.tokens = matches;
	},
	
	/************************
	 * Regex Helper methods.
	 ***********************/
	addLanguage: function(languageName, RegEx, className) {
		this.rules[languageName] = RegEx;
		this.theme.addAlias(languageName, className);
	},
	csvToRegExp: function(csv, mod) {return new RegExp('\\b(' + csv.replace(/,\s*/g, '|') + ')\\b', mod);},
	delimToRegExp: function(beg, esc, end, mod, suffix) {
		beg = beg.escapeRegExp();
		if (esc) esc = esc.escapeRegExp();
		end = (end) ? end.escapeRegExp() : beg;
		pat = (esc) ? beg+"[^"+end+esc+'\\n]*(?:'+esc+'.[^'+end+esc+'\\n]*)*'+end : beg+"[^"+end+'\\n]*'+end;
		return new RegExp(pat+(suffix || ''), mod || '');
	},
	strictRegExp: function() {
		var regex = '(';
		for (var i = 0; i < arguments.length; i++) {
			regex += arguments[i].escapeRegExp();
			regex += (i < arguments.length - 1) ? '|' : '';
		}
		regex += ')';
		return new RegExp(regex, "gim");
	},
	
	/************************
	 * Match finding Methods
	 ***********************/
	findMatches: function(code, offset) {
		var tokens       = [],
		    startIndex  = 0,
		    matchIndex  = code.length
		    insertIndex = 0,
		    match      = null,
		    type       = null,
		    newToken    = null,
		    rule       = null,
		    rules      = {},
		    currentMatch = null,
		    futureMatch  = null;
		
		offset = (offset) ? offset : 0;
		this.rules.each(function(regex, rule) {
			rules[rule] = {pattern: regex, enabled: true, lastIndex: 0};
		}, this);
		    
		while(startIndex < code.length) {
			matchIndex = code.length;
			match = null;
			for (rule in rules) {
				rules[rule].pattern.lastIndex = startIndex;
				currentMatch = rules[rule].pattern.exec(code);
				if (currentMatch === null) {
					delete rules[rule];
				} else {
					if (currentMatch.index < matchIndex) {
			      match      = currentMatch;
			      type       = rule;
			      matchIndex = currentMatch.index;
			    } else if (currentMatch.index == matchIndex && match[0].length < currentMatch[0].length) {
						match      = currentMatch;
						type       = rule;
						matchIndex = currentMatch.index;
					}
			    rules[rule].nextIndex = rules[rule].pattern.lastIndex - currentMatch[0].length;
			  }
			}
			if (match != null) {
				index = (match[1] && match[0].contains(match[1])) ? match.index + match[0].indexOf(match[1]) : match.index;
				newToken = new Token(match[1] || match[0], type, index+offset);
				tokens.push(newToken);
				
				futureMatch = rules[type].pattern.exec(code);
	      if (!futureMatch) {
	      	rules[type].nextIndex = code.length;
	      } else {
	      	rules[type].nextIndex = rules[type].pattern.lastIndex - futureMatch[0].length;
	      }
				
				var min = code.length;
				for (rule in rules) {
					if (rules[rule].nextIndex < min) {
						min = rules[rule].nextIndex;
					}
				}
				startIndex = Math.max(min, newToken.end - offset);
			} else {
				break;
			}
		}
		return tokens;
	},
	findMatchesLazy: function(code, offset) {
		var tokens = this.tokens,
		    match = null
		    index = 0;
		
		offset = (offset) ? offset : 0;
		
		this.rules.each(function(regex, rule) {
			while ((match = regex.exec(code)) != null) {
				index = (match[1] && match[0].contains(match[1])) ? match.index + match[0].indexOf(match[1]) : match.index;
				tokens.push(new Token(match[1] || match[0], rule, index + offset));
			}
		}, this);
		return this.purgeTokens(tokens);
	},
	purgeTokens: function(tokens) {
		tokens = tokens.sort(this.compareTokens);
		for (var i = 0, j = 0; i < tokens.length; i++) {
			if (tokens[i] == null) continue;
			for (j = i+1; j < tokens.length && tokens[i] != null; j++) {
				if      (tokens[j] == null)            {continue;}
				else if (tokens[j].isBeyond(tokens[i])) {break;}
				else if (tokens[j].overlaps(tokens[i])) {tokens[i] = null;}
				else if (tokens[i].contains(tokens[j])) {tokens[j] = null;}
			}
		}
		return tokens.clean();
	},
	compareTokens: function(token1, token2) {return token1.index - token2.index;}
});

var Token = new Class({

	initialize: function(match, type, index) {
		this.text   = match;
		this.type   = type;
		this.index  = index;
		this.length = this.text.length;
		this.end    = this.index + this.length;
	},
	contains: function(token) {return (token.index >= this.index && token.index < this.end);},
	isBeyond: function(token) {return (this.index >= token.end);},
	overlaps: function(token) {return (this.index == token.index && this.length > token.length);},
	toString: function() {return this.index+' - '+this.text+' - '+this.end;}
});