Last active
August 29, 2015 14:08
-
-
Save rajivnarayana/64f52a2f731707a56684 to your computer and use it in GitHub Desktop.
Simple compiler
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
/** | |
* A simple arithmetic language that consists of the following keywords | |
* | |
* reset | |
* reset x, creates a variable x if it doesnot exist and assigns 0 as its value | |
* increment | |
* increment x, increments a previously declared variable x, generates compiler warning if it doesnot have x already. | |
* loop | |
* loop x, loops the following statements until endloop x times, generates a compiler warning if x is not defined already. | |
* endloop | |
* limits the statements within a loop. | |
* | |
* Every line should have exactly one statement and should begin with a keyword followed by a variable except endloop | |
* A variable should begin with an alphabet. | |
* Arguments to the program should be positive numeric values only. | |
* | |
* Example program to increment x a given number of times | |
* reset x | |
* loop a | |
* increment x | |
* endloop | |
* | |
*/ | |
var Compiler = function() { | |
this.resetKeyword = new KeywordSymbol("reset", true); | |
this.incrementKeyword = new KeywordSymbol("increment", true); | |
this.loopKeyword = new KeywordSymbol("loop", true); | |
this.endLoopKeyword = new KeywordSymbol("endloop", false); | |
this.loopKeyword.shouldBeFollowedBySymbol(this.endLoopKeyword); | |
this.endLoopKeyword.shouldBePreceededBySymbol(this.loopKeyword); | |
this.parser = new Parser([this.resetKeyword, this.incrementKeyword, this.loopKeyword, this.endLoopKeyword]); | |
return this; | |
} | |
Compiler.prototype.compile = function(text) { | |
return this.parser.parse(text); | |
} | |
Compiler.prototype.constructRuntime = function(statements) { | |
var jsString = ""; | |
for (var i = 0; i < statements.length; i++) { | |
var statement = statements[i]; | |
if (statement.keywordSymbol == this.resetKeyword) { | |
jsString += "\n" + statement.variable + "= reset();"; | |
} else if (statement.keywordSymbol == this.incrementKeyword) { | |
jsString += "\n" + statement.variable + "= increment(" + statement.variable + ")"; | |
} else if (statement.keywordSymbol == this.loopKeyword) { | |
jsString += "\nloop(" + statement.variable + ", function() { \n"; | |
} else if (statement.keywordSymbol == this.endLoopKeyword) { | |
jsString += "\n});" | |
} | |
} | |
return jsString; | |
} | |
/** | |
* public method, | |
* @param, text, sourceCode to execute | |
*/ | |
Compiler.prototype.execute = function(text, arguments) { | |
try { | |
var statements = this.compile(text); | |
var variables = this.extractVariables(statements); | |
var evalString = "var fun = function() {\n"; | |
evalString += this.prependVariables(variables, arguments); | |
evalString += this.constructRuntime(statements); | |
evalString += this.appendReturnVariables(variables); | |
evalString += "}"; | |
console.log(evalString); | |
eval(evalString); | |
result = fun(); | |
return result; | |
} catch (err) { | |
alert(err.description); | |
} | |
} | |
Compiler.prototype.extractVariables = function(statements) { | |
var varSet = []; | |
for (var i = 0; i < statements.length; i++) { | |
if (!statements[i].keywordSymbol.acceptsVariable) { | |
continue; | |
} | |
var variable = statements[i].variable; | |
if (varSet.indexOf(variable) != -1) { | |
continue; | |
} | |
varSet[varSet.length] = variable; | |
}; | |
return varSet; | |
} | |
/** | |
* arguments will be indexed from 0 to n-1 and predefined variables will be from | |
*/ | |
Compiler.prototype.prependVariables = function(variables, arguments) { | |
var jsString = ""; | |
for (var i = 0; i < variables.length; i++) { | |
var variable = variables[i]; | |
if (arguments.hasOwnProperty(variable)) { | |
jsString += "var " + variable + " = " + arguments[variable] + ";"; | |
} else { | |
jsString += "var " + variable + " = 0;"; | |
} | |
} | |
return jsString; | |
} | |
Compiler.prototype.appendReturnVariables = function(variables) { | |
var jsString = "\nreturn {"; | |
for (var i = 0; i < variables.length; i++) { | |
var variable = variables[i]; | |
jsString += "'" + variable + "' : " + variable + "," | |
}; | |
jsString += "}"; | |
return jsString; | |
} | |
//--end Compiler-------------------------------// | |
//--begin Parser-------------------------------// | |
var Parser = function(keywordSymbols) { | |
this.keywordSymbols = keywordSymbols; | |
return this; | |
} | |
Parser.prototype.parse = function(str) { | |
var lines = this.getLines(str); | |
var compiledStatements = []; | |
var statementStackWaitingForMatches = []; | |
for (i = 0; i< lines.length; i++) { | |
var tokens = this.getWords(lines[i]); | |
if (tokens.length > 2) { | |
throw new ParseException(i+1, 3, "Unrecognized symbol", "Symbol "+tokens[2]+" not expected"); | |
} | |
if (!this.isValidKeyword(tokens[0])) { | |
throw new ParseException(i+1, 1, "Invalid keyword", "Every statement should begin with a keyword"); | |
} | |
var keywordSymbol = this.getKeywordSymbol(tokens[0]); | |
if (keywordSymbol.acceptsVariable) { | |
if (!this.isValidVariable(tokens[1])) { | |
throw new ParseException(i+1, 2, "Invalid variable", "Cannot be a valid variable "+tokens[1]); | |
} | |
} else { | |
if (tokens.length > 1) { | |
throw new ParseException(i + 1, 2, "Unrecognized symbol", "Symbol "+tokens[1]+" not expected after "+keywordSymbol.name); | |
} | |
} | |
var statement = new Statement(keywordSymbol, tokens[1]); | |
compiledStatements[compiledStatements.length] = statement; | |
if (keywordSymbol.symbolToFollow != null) { | |
statementStackWaitingForMatches[statementStackWaitingForMatches.length] = statement; | |
} | |
if (keywordSymbol.symbolToPreceed != null) { | |
if (statementStackWaitingForMatches.length == 0) { | |
throw new ParseException( i + 1 , 1, "Unmatched statement", "This statement should be preceeded by a "+keywordSymbol.name+" statement"); | |
} | |
if (statementStackWaitingForMatches[statementStackWaitingForMatches.length-1].keywordSymbol.name == statement.keywordSymbol.symbolToPreceed.name) { | |
statementStackWaitingForMatches.pop(); | |
} | |
} | |
} | |
if (statementStackWaitingForMatches.length > 0) { | |
throw new ParseException(lines.length , 1, "Open loop", "loop not closed for "+statementStackWaitingForMatches[0].keywordSymbol.name+" "+statementStackWaitingForMatches[0].variable); | |
} | |
return compiledStatements; | |
} | |
Parser.prototype.isValidKeyword = function(token) { | |
return this.getKeywordSymbol(token) != null; | |
} | |
/** | |
* Makes sure the given token matches one of the keywordSymbols | |
* return true, if is one of a given keywords. | |
*/ | |
Parser.prototype.getKeywordSymbol = function(token) { | |
for (var i = 0; i < this.keywordSymbols.length; i++) { | |
if (this.keywordSymbols[i].name === token) { | |
return this.keywordSymbols[i]; | |
} | |
}; | |
return null; | |
} | |
Parser.prototype.isValidVariable = function(token) { | |
if (this.isValidKeyword(token)) { | |
return false; | |
} | |
if (this.isSpecialVariable(token)) { | |
return true; | |
} | |
if (/^[a-zA-Z0-9]*$/.test(token)) { | |
return true; | |
} | |
return false; | |
} | |
//special kind of variable that starts with _ like _1, _2 etc | |
Parser.prototype.isSpecialVariable = function(token) { | |
// if (/^[_][0-9]*/.test(token)) { | |
// return true; | |
// } | |
return false; | |
} | |
/** | |
* Simply returns string tokens separated by new line character | |
*/ | |
Parser.prototype.getLines = function(text) { | |
return text.match(/[^\r\n]+/g); | |
} | |
/** | |
* return, array of tokens separated by a white space character. | |
*/ | |
Parser.prototype.getWords = function(text) { | |
return text.match(/\S+/g); | |
} | |
//---end of Parser------------------------------// | |
//----begin KeyworkSymbol----------------------------// | |
var KeywordSymbol = function(name, acceptsVariable) { | |
this.name = name; | |
this.acceptsVariable = acceptsVariable; | |
this.symbolToFollow = null; | |
this.symbolToPreceed = null; | |
return this; | |
} | |
KeywordSymbol.prototype.shouldBeFollowedBySymbol = function(keywordSymbol) { | |
this.symbolToFollow = keywordSymbol; | |
} | |
KeywordSymbol.prototype.shouldBePreceededBySymbol = function(keywordSymbol) { | |
this.symbolToPreceed = keywordSymbol; | |
} | |
//-----end Keyword Symbol----------------------------// | |
//---------------------------------// | |
var ParseException = function(lineNo, symbolNo, type, description) { | |
this.lineNo = lineNo; | |
this.symbolNo = symbolNo; | |
this.type = type; | |
this.description = description; | |
return this; | |
} | |
//---------------------------------// | |
//---------------------------------// | |
var Statement = function(keywordSymbol, variable) { | |
this.keywordSymbol = keywordSymbol; | |
this.variable = variable; | |
return this; | |
} | |
//---------------------------------// | |
//-----------Runtime---------------// | |
function increment(x) { | |
return x+1; | |
} | |
function loop(x, callback) { | |
var i; | |
for(i=0; i< x; i++) { | |
callback(); | |
} | |
} | |
function reset() { | |
return 0; | |
} | |
//-----------end Runtime---------------// | |
a - 1
x=0
y = 0
for a
x = y
y++
end for
a - b
x = 0
for b
z=0
y = 0
for a
z = y
y++
end for
x = z
end for
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
http://jsfiddle.net/nv631L97/1/