2012-09-07 02:28:37 -07:00
|
|
|
var fs = require("fs");
|
|
|
|
|
var util = require("util");
|
|
|
|
|
|
|
|
|
|
// for tracking token states
|
|
|
|
|
var startState = { start: [] }, statesObj = { };
|
|
|
|
|
|
2012-09-08 19:37:24 -07:00
|
|
|
|
|
|
|
|
var args = process.argv.splice(2);
|
|
|
|
|
var tmLanguageFile = args[0];
|
|
|
|
|
var devMode = args[1];
|
|
|
|
|
|
2012-09-07 02:28:37 -07:00
|
|
|
var parseString = require("plist").parseString;
|
|
|
|
|
function parseLanguage(languageXml, callback) {
|
2012-09-08 19:37:24 -07:00
|
|
|
parseString(languageXml, function(_, language) {
|
|
|
|
|
callback(language[0])
|
|
|
|
|
});
|
2012-09-07 02:28:37 -07:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
function logDebug(string, obj) {
|
|
|
|
|
console.log(string, obj);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
String.prototype.splice = function( idx, rem, s ) {
|
|
|
|
|
return (this.slice(0,idx) + s + this.slice(idx + Math.abs(rem)));
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
String.prototype.replaceAt = function (index, char) {
|
|
|
|
|
return this.substr(0, index) + char + this.substr(index + 1);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
function keyCount(obj) {
|
|
|
|
|
return Object.keys(obj).length;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
|
|
|
|
|
Scrubbing is sometimes necessary, but there appears to be no
|
|
|
|
|
automated way to do it...
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
function cleanSingleCapture(match) {
|
|
|
|
|
// if there's a single "( )", screw that and make it "(?: )"
|
|
|
|
|
return match.replace("(", "(?:");
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
function cleanMultiCapture(match) {
|
|
|
|
|
// regexp will be a quoted string, so turn "\" into "\\"
|
|
|
|
|
var spaceFinderRegExp = new RegExp("\\\\s.| .", "g");
|
|
|
|
|
var m;
|
|
|
|
|
/*
|
|
|
|
|
essentially turns things like
|
|
|
|
|
|
|
|
|
|
\\s*(mixin) ([\\w\\-]+)\\s*(\\()
|
|
|
|
|
|
|
|
|
|
into
|
|
|
|
|
|
|
|
|
|
(\\s*mixin)( [\\w\\-]+)(\\s*\\()
|
|
|
|
|
|
|
|
|
|
so that mode parser stops complaining
|
|
|
|
|
|
|
|
|
|
while ((m = spaceFinderRegExp.exec(match)) != null) {
|
|
|
|
|
var idx = m.index;
|
|
|
|
|
var nextParenIdx = match.indexOf("(", idx);
|
|
|
|
|
|
|
|
|
|
if (nextParenIdx > idx) {
|
|
|
|
|
match = match.splice(idx, 0, "(").replaceAt(nextParenIdx + 1, '');
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
//console.log("match", match);
|
|
|
|
|
return match;
|
|
|
|
|
}
|
|
|
|
|
*/
|
|
|
|
|
|
|
|
|
|
// stupid yet necessary function, to transform JSON id comments into real comments
|
|
|
|
|
function restoreComments(objStr) {
|
2012-09-07 16:50:06 -07:00
|
|
|
return objStr.replace(/"\s+(\/\/.+)",/g, "\$1").replace(/ \/\/ ERROR/g, '", // ERROR');
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
function checkForLookBehind(str) {
|
2012-09-09 22:56:00 -07:00
|
|
|
var lookbehindRegExp = new RegExp("\\?<[=|!]", "g");
|
2012-09-07 16:50:06 -07:00
|
|
|
return lookbehindRegExp.test(str) ? str + " // ERROR: This contains a lookbehind, which JS does not support :(" : str;
|
2012-09-07 02:28:37 -07:00
|
|
|
}
|
|
|
|
|
|
2012-11-16 10:08:56 -05:00
|
|
|
function checkForInvariantRegex(str) {
|
|
|
|
|
return str.replace(/\?i\:/, "?:");
|
|
|
|
|
}
|
|
|
|
|
|
2012-09-11 10:39:32 +04:00
|
|
|
function removeXFlag(str) {
|
|
|
|
|
if (str.slice(0,4) == "(?x)") {
|
2012-09-18 23:28:33 +04:00
|
|
|
str = str.replace(/\\.|\[([^\]\\]|\\.)*?\]|\s+|(?:#[^\n]*)/g, function(s) {
|
|
|
|
|
if (s[0] == "[")
|
|
|
|
|
return s;
|
|
|
|
|
if (s[0] == "\\")
|
|
|
|
|
return /[#\s]/.test(s[1]) ? s[1] : s;
|
|
|
|
|
return "";
|
2012-09-11 10:39:32 +04:00
|
|
|
});
|
|
|
|
|
}
|
|
|
|
|
return str;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
function transformRegExp(str) {
|
|
|
|
|
str = removeXFlag(str);
|
|
|
|
|
str = checkForLookBehind(str);
|
2012-11-16 10:08:56 -05:00
|
|
|
str = checkForInvariantRegex(str);
|
2012-11-22 13:34:18 -05:00
|
|
|
str = str.replace(/\\n(?!\?).?/, '$'); // replace newlines by $ except if its postfixed by ?
|
2012-09-11 10:39:32 +04:00
|
|
|
return str;
|
|
|
|
|
}
|
|
|
|
|
|
2012-09-07 02:28:37 -07:00
|
|
|
function assembleStateObjs(strState, pattern) {
|
2012-11-22 13:34:18 -05:00
|
|
|
console.log("assembleStateObjs", strState, pattern);
|
2012-09-07 02:28:37 -07:00
|
|
|
var patterns = pattern.patterns;
|
|
|
|
|
var stateObj = {};
|
2012-09-08 19:37:24 -07:00
|
|
|
|
2012-09-07 02:28:37 -07:00
|
|
|
if (patterns) {
|
|
|
|
|
for (var p in patterns) {
|
|
|
|
|
stateObj = {}; // this is apparently necessary
|
|
|
|
|
|
|
|
|
|
if (patterns[p].include) {
|
|
|
|
|
stateObj.include = patterns[p].include;
|
|
|
|
|
}
|
|
|
|
|
else {
|
2012-11-22 13:34:18 -05:00
|
|
|
if (patterns[p].captures)
|
|
|
|
|
stateObj.token = extractCaptures(patterns[p].captures);
|
|
|
|
|
else
|
|
|
|
|
stateObj.token = patterns[p].name;
|
|
|
|
|
|
2012-09-11 10:39:32 +04:00
|
|
|
stateObj.regex = transformRegExp(patterns[p].match);
|
2012-09-07 02:28:37 -07:00
|
|
|
}
|
|
|
|
|
statesObj[strState].push(stateObj);
|
|
|
|
|
}
|
2012-11-22 13:34:18 -05:00
|
|
|
|
|
|
|
|
statesObj[strState].push({
|
|
|
|
|
token: pattern.name,
|
|
|
|
|
regex: transformRegExp(pattern.end),
|
|
|
|
|
merge: true,
|
|
|
|
|
next: "start"
|
|
|
|
|
});
|
2012-09-07 02:28:37 -07:00
|
|
|
|
|
|
|
|
stateObj = {};
|
2012-11-22 13:34:18 -05:00
|
|
|
stateObj.token = pattern.name;
|
|
|
|
|
stateObj.regex = ".";
|
|
|
|
|
stateObj.merge = true;
|
|
|
|
|
stateObj.next = strState;
|
2012-09-07 02:28:37 -07:00
|
|
|
}
|
|
|
|
|
else {
|
2012-11-22 13:34:18 -05:00
|
|
|
statesObj[strState].push({
|
|
|
|
|
token: pattern.name,
|
|
|
|
|
regex: transformRegExp(pattern.end),
|
|
|
|
|
merge: true,
|
|
|
|
|
next: "start"
|
|
|
|
|
});
|
|
|
|
|
|
2012-09-07 02:28:37 -07:00
|
|
|
stateObj = {};
|
2012-11-22 13:34:18 -05:00
|
|
|
stateObj.token = pattern.name;
|
|
|
|
|
stateObj.regex = ".";
|
|
|
|
|
stateObj.merge = true;
|
2012-09-07 02:28:37 -07:00
|
|
|
stateObj.next = strState;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
return stateObj;
|
|
|
|
|
}
|
|
|
|
|
|
2012-11-16 10:08:56 -05:00
|
|
|
function extractCaptures (captures) {
|
|
|
|
|
if (typeof captures === "object") {
|
|
|
|
|
var token = [];
|
|
|
|
|
|
|
|
|
|
Object.keys(captures).forEach(function (k) {
|
|
|
|
|
if (captures[k].name) {
|
|
|
|
|
token.push(captures[k].name);
|
|
|
|
|
}
|
|
|
|
|
else {
|
|
|
|
|
token.push(captures[k]);
|
|
|
|
|
}
|
|
|
|
|
});
|
|
|
|
|
|
|
|
|
|
return token;
|
|
|
|
|
}
|
|
|
|
|
else {
|
|
|
|
|
return captures;
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
2012-09-07 02:28:37 -07:00
|
|
|
function extractPatterns(patterns) {
|
|
|
|
|
var state = 0;
|
|
|
|
|
patterns.forEach(function(pattern) {
|
|
|
|
|
state++;
|
|
|
|
|
var i = 1;
|
2012-09-08 19:37:24 -07:00
|
|
|
var tokenObj = { token: [] };
|
2012-09-07 02:28:37 -07:00
|
|
|
|
|
|
|
|
if (pattern.comment) {
|
2012-09-11 10:39:32 +04:00
|
|
|
startState.start.push(" // " + pattern.comment.trim());
|
2012-09-07 02:28:37 -07:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// it needs a state transition
|
|
|
|
|
if (pattern.begin && pattern.end) {
|
|
|
|
|
var strState = "state_" + state;
|
|
|
|
|
if ( pattern.beginCaptures === undefined && pattern.endCaptures === undefined) {
|
2012-11-16 10:08:56 -05:00
|
|
|
tokenObj.token = tokenObj.token.concat(extractCaptures(pattern.captures));
|
2012-09-07 02:28:37 -07:00
|
|
|
}
|
|
|
|
|
else if (pattern.beginCaptures) {
|
2012-11-16 10:08:56 -05:00
|
|
|
tokenObj.token = tokenObj.token.concat(extractCaptures(pattern.beginCaptures));
|
2012-09-07 02:28:37 -07:00
|
|
|
}
|
|
|
|
|
else if (pattern.endCaptures) {
|
2012-11-16 10:08:56 -05:00
|
|
|
tokenObj.token = tokenObj.token.concat(extractCaptures(pattern.endCaptures));
|
2012-09-08 19:37:24 -07:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
if (tokenObj.token === undefined) {
|
|
|
|
|
if (pattern.name)
|
|
|
|
|
tokenObj.token.push(pattern.name);
|
|
|
|
|
else
|
|
|
|
|
logDebug("There's no token name for this state transition", pattern)
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
if (tokenObj.token === undefined) {
|
|
|
|
|
tokenObj.token.push(pattern.name);
|
2012-09-07 02:28:37 -07:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
statesObj[strState] = [ ];
|
|
|
|
|
statesObj[strState].push(assembleStateObjs(strState, pattern));
|
|
|
|
|
|
2012-09-11 10:39:32 +04:00
|
|
|
tokenObj.regex = transformRegExp(pattern.begin);
|
2012-09-07 02:28:37 -07:00
|
|
|
tokenObj.next = strState;
|
|
|
|
|
}
|
|
|
|
|
else if( ( pattern.begin || pattern.end ) && !( pattern.begin && pattern.end ) ) {
|
|
|
|
|
logDebug("Somehow, there's pattern.begin or pattern.end--but not both?", pattern);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
else if (pattern.captures) {
|
2012-11-16 10:08:56 -05:00
|
|
|
tokenObj.token = tokenObj.token.concat(extractCaptures(pattern.captures));
|
2012-09-11 10:39:32 +04:00
|
|
|
tokenObj.regex = transformRegExp(pattern.match);
|
2012-09-07 02:28:37 -07:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
else if (pattern.match) {
|
2012-09-08 19:37:24 -07:00
|
|
|
tokenObj.token.push(pattern.name);
|
2012-09-11 10:39:32 +04:00
|
|
|
tokenObj.regex = transformRegExp(pattern.match);
|
2012-09-07 02:28:37 -07:00
|
|
|
}
|
2012-09-08 19:37:24 -07:00
|
|
|
|
|
|
|
|
else if (pattern.include) {
|
2012-11-22 13:34:18 -05:00
|
|
|
// f*ck it
|
|
|
|
|
return;
|
2012-09-08 19:37:24 -07:00
|
|
|
}
|
|
|
|
|
|
2012-09-07 02:28:37 -07:00
|
|
|
else {
|
2012-09-09 02:56:49 -07:00
|
|
|
tokenObj.token.push("");
|
|
|
|
|
tokenObj.regex = "";
|
2012-09-07 02:28:37 -07:00
|
|
|
logDebug("I've gone through every choice, and have no clue what this is:", pattern);
|
|
|
|
|
}
|
2012-09-09 02:56:49 -07:00
|
|
|
|
|
|
|
|
// sometimes captures have names--not sure when or why
|
2012-11-16 10:08:56 -05:00
|
|
|
// if (pattern.name && tokenObj.token.indexOf(pattern.name) === -1) {
|
|
|
|
|
// tokenObj.token.push(pattern.name);
|
|
|
|
|
// }
|
2012-09-09 02:56:49 -07:00
|
|
|
|
|
|
|
|
startState.start.push(tokenObj);
|
2012-09-07 02:28:37 -07:00
|
|
|
});
|
|
|
|
|
|
|
|
|
|
var resultingObj = startState;
|
|
|
|
|
|
|
|
|
|
for (var state in statesObj) {
|
|
|
|
|
resultingObj[state] = statesObj[state];
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
return restoreComments(JSON.stringify(resultingObj, null, " "));
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
function fillTemplate(template, replacements) {
|
|
|
|
|
return template.replace(/%(.+?)%/g, function(str, m) {
|
|
|
|
|
return replacements[m] || "";
|
|
|
|
|
});
|
|
|
|
|
}
|
|
|
|
|
|
2012-09-08 19:37:24 -07:00
|
|
|
var modeTemplate = fs.readFileSync(__dirname + "/mode.tmpl.js", "utf8");
|
|
|
|
|
var modeHighlightTemplate = fs.readFileSync(__dirname + "/mode_highlight_rules.tmpl.js", "utf8");
|
2012-09-07 02:28:37 -07:00
|
|
|
|
|
|
|
|
function convertLanguage(name) {
|
|
|
|
|
var tmLanguage = fs.readFileSync(__dirname + "/" + name, "utf8");
|
|
|
|
|
parseLanguage(tmLanguage, function(language) {
|
2012-09-08 19:37:24 -07:00
|
|
|
var languageHighlightFilename = language.name.replace(/[-_]/g, "").toLowerCase();
|
|
|
|
|
var languageNameSanitized = language.name.replace(/-/g, "");
|
|
|
|
|
|
|
|
|
|
var languageHighlightFile = __dirname + "/../lib/ace/mode/" + languageHighlightFilename + "_highlight_rules.js";
|
|
|
|
|
var languageModeFile = __dirname + "/../lib/ace/mode/" + languageHighlightFilename + ".js";
|
|
|
|
|
|
|
|
|
|
console.log("Converting " + name + " to " + languageHighlightFile);
|
|
|
|
|
|
2012-09-09 22:56:00 -07:00
|
|
|
if (devMode) {
|
2012-09-08 19:37:24 -07:00
|
|
|
console.log(util.inspect(language.patterns, false, 4));
|
2012-09-09 22:56:00 -07:00
|
|
|
console.log(util.inspect(language.repository, false, 4));
|
|
|
|
|
}
|
|
|
|
|
|
2012-09-08 19:37:24 -07:00
|
|
|
var languageMode = fillTemplate(modeTemplate, {
|
|
|
|
|
language: languageNameSanitized,
|
|
|
|
|
languageHighlightFilename: languageHighlightFilename
|
|
|
|
|
});
|
2012-09-09 22:56:00 -07:00
|
|
|
|
2012-09-09 02:56:49 -07:00
|
|
|
var patterns = extractPatterns(language.patterns);
|
2012-09-09 22:56:00 -07:00
|
|
|
var repository = {};
|
|
|
|
|
|
|
|
|
|
if (language.repository) {
|
|
|
|
|
for (var r in language.repository) {
|
|
|
|
|
repository[r] = language.repository[r];
|
|
|
|
|
}
|
|
|
|
|
repository = restoreComments(JSON.stringify(repository, null, " "));
|
|
|
|
|
}
|
2012-11-16 10:08:56 -05:00
|
|
|
|
|
|
|
|
if (typeof repository === "string") {
|
|
|
|
|
repository = JSON.parse(repository);
|
|
|
|
|
}
|
|
|
|
|
|
2012-09-09 02:56:49 -07:00
|
|
|
var languageHighlightRules = fillTemplate(modeHighlightTemplate, {
|
|
|
|
|
language: languageNameSanitized,
|
|
|
|
|
languageTokens: patterns,
|
2012-10-01 22:52:30 -07:00
|
|
|
respositoryRules: "/*** START REPOSITORY RULES\n" + (Object.keys(repository).length === 0 ? "" : repository) + "\nEND REPOSITORY RULES ***/",
|
2012-09-09 02:56:49 -07:00
|
|
|
uuid: language.uuid
|
|
|
|
|
});
|
|
|
|
|
|
2012-09-08 19:37:24 -07:00
|
|
|
if (devMode) {
|
2012-10-01 22:52:30 -07:00
|
|
|
console.log(languageMode)
|
|
|
|
|
console.log(languageHighlightRules)
|
2012-09-08 19:37:24 -07:00
|
|
|
console.log("Not writing, 'cause we're in dev mode, baby.");
|
|
|
|
|
}
|
|
|
|
|
else {
|
|
|
|
|
fs.writeFileSync(languageHighlightFile, languageHighlightRules);
|
|
|
|
|
fs.writeFileSync(languageModeFile, languageMode);
|
|
|
|
|
}
|
2012-09-07 02:28:37 -07:00
|
|
|
});
|
|
|
|
|
}
|
|
|
|
|
|
2012-09-07 16:50:06 -07:00
|
|
|
if (tmLanguageFile === undefined) {
|
|
|
|
|
console.error("Please pass in a language file via the command line.");
|
|
|
|
|
process.exit(1);
|
|
|
|
|
}
|
2012-09-07 02:28:37 -07:00
|
|
|
convertLanguage(tmLanguageFile);
|