package { /******************************************************************************* * Copyright (c) 2009 Andreas Rozek * * * * Permission is hereby granted, free of charge, to any person obtaining a copy * * of this software and associated documentation files (the "Software"),to deal * * in the Software without restriction, including without limitation the rights * * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell * * copies of the Software, and to permit persons to whom the Software is fur- * * nished to do so, subject to the following conditions: * * * * The above copyright notice and this permission notice shall be included in * * all copies or substantial portions of the Software. * * * * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR * * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, * * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE * * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIA- * * BILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, * * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN * * THE SOFTWARE. * * * * Additionally, any modifications to the original Software must be clearly * * marked in a way, that the original author will never be considered as the * * author of these modifications! * *******************************************************************************/ public class MokkaScript { //------------------------------------------------------------------------------ // allowArray validates an optional array argument //------------------------------------------------------------------------------ public static function allowArray (Argument:*, Name:String):void { if (Argument !== undefined) { expectArray(Argument,Name); }; }; //------------------------------------------------------------------------------ // allowBoolean validates an optional boolean argument //------------------------------------------------------------------------------ public static function allowBoolean (Argument:*, Name:String):void { if (Argument !== undefined) { expectBoolean(Argument,Name); }; }; //------------------------------------------------------------------------------ // allowFunction validates an optional function argument //------------------------------------------------------------------------------ public static function allowFunction (Argument:*, Name:String):void { if (Argument !== undefined) { expectFunction(Argument,Name); }; }; //------------------------------------------------------------------------------ // allowIdentifier validates an optional identifier argument //------------------------------------------------------------------------------ public static function allowIdentifier (Argument:*, Name:String):void { if (Argument !== undefined) { expectIdentifier(Argument,Name); }; }; //------------------------------------------------------------------------------ // allowInteger validates an optional integer argument //------------------------------------------------------------------------------ public static function allowInteger ( Argument:*, Name:String, minValue:Number = NaN, maxValue:Number = NaN ):void { if (Argument !== undefined) { expectInteger(Argument,Name, minValue,maxValue); }; }; //------------------------------------------------------------------------------ // allowNNumbers validates an optional series of numeric arguments //------------------------------------------------------------------------------ public static function allowNNumbers ( ArgumentList:*, NamePrefix:String, nMin:int = 0, nMax:int = 2147483647, minValue:Number = NaN, maxValue:Number = NaN, withMin:Boolean = true, withMax:Boolean = true ):void { if (ArgumentList !== undefined) { expectNNumbers( ArgumentList,NamePrefix, nMin,nMax, minValue,maxValue, withMin,withMax ); }; }; //------------------------------------------------------------------------------ // allowNumber validates an optional numeric argument //------------------------------------------------------------------------------ public static function allowNumber ( Argument:*, Name:String, minValue:Number = NaN, maxValue:Number = NaN, withMin:Boolean = true, withMax:Boolean = true ):void { if (Argument !== undefined) { expectNumber(Argument,Name, minValue,maxValue, withMin,withMax); }; }; //------------------------------------------------------------------------------ // allowString validates an optional literal argument //------------------------------------------------------------------------------ public static function allowString ( Argument:*, Name:String, neverEmpty:Boolean = false ):void { if (Argument !== undefined) { expectString(Argument,Name, neverEmpty); }; }; //------------------------------------------------------------------------------ // expectAny validates an arbitrary mandatory argument //------------------------------------------------------------------------------ public static function expectAny (Argument:*, Name:String):void { if (Argument === undefined) { throw new Error("MissingArgument: no " + quoted(Name) + " given"); }; }; //------------------------------------------------------------------------------ // expectArray validates a mandatory array argument //------------------------------------------------------------------------------ public static function expectArray (Argument:*, Name:String):void { if (Argument === undefined) { throw new Error("MissingArgument: no " + quoted(Name) + " given"); }; if (!(Argument is Array)) { throw new TypeError("the given " + quoted(Name) + " is not an array"); }; }; //------------------------------------------------------------------------------ // expectBoolean validates a mandatory boolean argument //------------------------------------------------------------------------------ public static function expectBoolean (Argument:*, Name:String):void { if (Argument === undefined) { throw new Error("MissingArgument: no " + quoted(Name) + " given"); }; if (typeof(Argument) !== "boolean") { throw new TypeError("the given " + quoted(Name) + " does not contain a boolean value"); }; }; //------------------------------------------------------------------------------ // expectFunction validates a mandatory function argument //------------------------------------------------------------------------------ public static function expectFunction (Argument:*, Name:String):void { if (Argument === undefined) { throw new Error("MissingArgument: no " + quoted(Name) + " given"); }; if (typeof(Argument) !== "function") { throw new TypeError("the given " + quoted(Name) + " is not a function"); }; }; //------------------------------------------------------------------------------ // expectIdentifier validates a mandatory identifier argument //------------------------------------------------------------------------------ public static function expectIdentifier (Argument:*, Name:String):void { if (Argument === undefined) { throw new Error("MissingArgument: no " + quoted(Name) + " given"); }; if ((typeof(Argument) !== "string") || !isIdentifier(Argument)) { throw new TypeError("the given " + quoted(Name) + " is not a valid identifier"); }; }; //------------------------------------------------------------------------------ // expectInteger validates a mandatory integer argument //------------------------------------------------------------------------------ public static function expectInteger ( Argument:*, Name:String, minValue:Number = NaN, maxValue:Number = NaN ):void { if (Argument === undefined) { throw new Error("MissingArgument: no " + quoted(Name) + " given"); }; if ((typeof(Argument) !== "number") || (Math.floor(Argument) !== Argument)) { throw new TypeError("the given " + quoted(Name) + " is not an integer"); }; if (!isNaN(minValue)) { minValue = Math.ceil(minValue); if (!isNaN(maxValue) && (minValue > Math.floor(maxValue))) { throw new RangeError( "the given 'minValue' is larger than the given 'maxValue' (" + minValue + " > " + Math.floor(maxValue) + ")" ); }; if (Argument < minValue) { throw new RangeError( "the given " + quoted(Name) + " is below the allowed minimum (" + Argument + " < " + minValue + ")" ); }; }; if (!isNaN(maxValue)) { maxValue = Math.floor(maxValue); if (Argument > maxValue) { throw new RangeError( "the given " + quoted(Name) + " is above the allowed maximum (" + Argument + " > " + maxValue + ")" ); }; }; }; //------------------------------------------------------------------------------ // expectNNumbers validates a series of mandatory numeric arguments //------------------------------------------------------------------------------ public static function expectNNumbers ( ArgumentList:*, NamePrefix:String, nMin:int = 0, nMax:int = 2147483647, minValue:Number = NaN, maxValue:Number = NaN, withMin:Boolean = true, withMax:Boolean = true ):void { if (ArgumentList === undefined) { throw new Error("MissingArgument: no " + quoted(NamePrefix) + " given"); }; if (!(ArgumentList is Array)) { throw new TypeError("the given " + quoted(NamePrefix) + " list is not an array"); }; if (nMin > nMax) { throw new RangeError( "the given 'nMin' is larger than the given 'nMax' (" + nMin + " > " + nMax + ")" ); }; for (var i:int = 0; i < ArgumentList.length; i++) { try { expectNumber( ArgumentList[i], NamePrefix, minValue,maxValue, withMin,withMax ); } catch (Signal:String) { var PrefixPosition:int = Signal.indexOf("'" + NamePrefix + "'"); throw Signal.slice(0,PrefixPosition) + "'" + NamePrefix + i + "'" + Signal.slice(PrefixPosition+2+NamePrefix.length); }; }; if (ArgumentList.length < nMin) { throw new Error("MissingArgument: the given " + quoted(NamePrefix) + " list contains fewer than the required " + nMin + " elements"); }; if (ArgumentList.length > nMax) { throw new Error("ExtraArgument: the given " + quoted(NamePrefix) + " list contains more than the allowed " + nMax + " elements"); }; }; //------------------------------------------------------------------------------ // expectNumber validates a mandatory numeric argument //------------------------------------------------------------------------------ public static function expectNumber ( Argument:*, Name:String, minValue:Number = NaN, maxValue:Number = NaN, withMin:Boolean = true, withMax:Boolean = true ):void { if (Argument === undefined) { throw new Error("MissingArgument: no " + quoted(Name) + " given"); }; if (typeof(Argument) !== "number") { throw new TypeError("the given " + quoted(Name) + " does not contain a number"); }; if (!isNaN(minValue)) { if (!isNaN(maxValue)) { if (minValue > maxValue) { throw new RangeError( "the given 'minValue' is larger than the given 'maxValue' (" + minValue + " > " + maxValue + ")" ); }; if ((minValue === maxValue) && !(withMin && withMax)) { throw new RangeError("the given range 'minValue'...'maxValue' is empty"); }; }; if (withMin) { if (Argument < minValue) { throw new RangeError( "the given " + quoted(Name) + " is below the allowed minimum (" + Argument + " < " + minValue + ")" ); }; } else { if (Argument <= minValue) { throw new RangeError( "the given " + quoted(Name) + " is below the allowed minimum (" + Argument + " <= " + minValue + ")" ); }; }; }; if (!isNaN(maxValue)) { if (withMax) { if (Argument > maxValue) { throw new RangeError( "the given " + quoted(Name) + " is above the allowed maximum (" + Argument + " > " + maxValue + ")" ); }; } else { if (Argument >= maxValue) { throw new Error( "the given " + quoted(Name) + " is above the allowed maximum (" + Argument + " >= " + maxValue + ")" ); }; }; }; }; //------------------------------------------------------------------------------ // expectString validates a mandatory literal argument //------------------------------------------------------------------------------ private static var emptyRegExp:* = /^\s*$/; public static function expectString ( Argument:*, Name:String, neverEmpty:Boolean = false ):void { if (Argument === undefined) { throw new Error("MissingArgument: no " + quoted(Name) + " given"); }; if (typeof(Argument) !== "string") { throw new TypeError("the given " + quoted(Name) + " does not contain a string"); }; if (neverEmpty && emptyRegExp.test(Argument)) { throw new Error("EmptyArgument: the given " + quoted(Name) + " is empty"); }; }; //------------------------------------------------------------------------------ // isIdentifier is the given argument a valid (JavaScript) identifier? //------------------------------------------------------------------------------ private static var IdentifierPartRegExp:RegExp = /[$_A-Za-z0-9]/; // /[$_A-Za-z0-9\u00c0-\u1fff\u2800-\ufffd]/; private static function isIdentifierPart (Candidate:String):Boolean { return IdentifierPartRegExp.test(Candidate); }; private static var IdentifierStartRegExp:RegExp = /[$_A-Za-z]/; // /[$_A-Za-z\u00c0-\u1fff\u2800-\ufffd]/; private static function isIdentifierStart (Candidate:String):Boolean { return IdentifierStartRegExp.test(Candidate); }; private static var IdentifierFullRegExp:RegExp = /^[$_A-Za-z\u00c0-\u1fff\u2800-\ufffd][$_A-Za-z0-9\u00c0-\u1fff\u2800-\ufffd]*$/; public static function isIdentifier (Candidate:String):Boolean { return IdentifierFullRegExp.test(Candidate); }; //------------------------------------------------------------------------------ // newInstance "instantiates" a given prototype //------------------------------------------------------------------------------ public static function newInstance (Prototype:Object):Object { var Instance:Function = function ():void {}; Instance.prototype = Prototype; return new Instance(); }; //------------------------------------------------------------------------------ // printable yields a printable copy of the given argument //------------------------------------------------------------------------------ public static function printable (Argument:String):String { return Argument.replace(/[\u0000-\u001F\u007F-\u9F]/g,"."); }; //------------------------------------------------------------------------------ // quoted yields a printable copy of the given argument within quotes //------------------------------------------------------------------------------ public static function quoted ( Argument:String, Quote:String = "\"" ):String { return Quote + printable(Argument) + Quote; }; //------------------------------------------------------------------------------ // trimmed removes leading and trailing whitespace //------------------------------------------------------------------------------ public static function trimmed (Argument:String):String { return Argument.replace(/^\s+/,"").replace(/\s+$/,""); }; /******************************************************************************* * * * actual MokkaScript Interpreter * * * *******************************************************************************/ //------------------------------------------------------------------------------ // ValueOf parses and evaluates the given MokkaScript source code //------------------------------------------------------------------------------ public static function ValueOf (SourceCode:String, GlobalObject:Object):* { var SourceLength:int = SourceCode.length; // just a shortcut var SourceType:String = "program"; /**** provide access to the global object ****/ // var GlobalObject:Object; // refers to the global object // var getGlobal:Function = function ():Object {return this}; // GlobalObject = getGlobal(); // delete getGlobal; /**** let the global object look more like a "classic" JavaScript one ****/ GlobalObject = GlobalObject || {}; GlobalObject["NaN"] = NaN; GlobalObject["Infinity"] = Infinity; GlobalObject["undefined"] = undefined; // GlobalObject["eval"] = eval; // comes later GlobalObject["parseInt"] = parseInt; GlobalObject["parseFloat"] = parseFloat; GlobalObject["isNaN"] = isNaN; GlobalObject["isFinite"] = isFinite; GlobalObject["decodeURI"] = decodeURI; GlobalObject["decodeURIComponent"] = decodeURIComponent; GlobalObject["encodeURI"] = encodeURI; GlobalObject["encodeURIComponent"] = encodeURIComponent; GlobalObject["Object"] = Object; GlobalObject["Function"] = Function; GlobalObject["Array"] = Array; GlobalObject["String"] = String; GlobalObject["Boolean"] = Boolean; GlobalObject["Number"] = Number; GlobalObject["Date"] = Date; GlobalObject["RegExp"] = RegExp; GlobalObject["Error"] = Error; GlobalObject["EvalError"] = EvalError; GlobalObject["RangeError"] = RangeError; GlobalObject["ReferenceError"] = ReferenceError; GlobalObject["SyntaxError"] = SyntaxError; GlobalObject["TypeError"] = TypeError; GlobalObject["URIError"] = URIError; GlobalObject["Math"] = Math; /**** initialize scanner ****/ var SourcePosition:int = 0; // flat position of currently processed char var curChar:String = SourceCode.charAt(0); // currently processed char /**** initialize tokenizer ****/ var TokenStack:Array = new Array(); // stack of pending tokens //------------------------------------------------------------------------------ // SourceLocation locates a given position in the "SourceCode" //------------------------------------------------------------------------------ var SourceLocation:Function = function ( SourceCode:String, SourceType:String, SourcePosition:int ):String { var Result:String = " in " + SourceType + " source "; var SourceLength:int = SourceCode.length; // primarily a shortcut if (SourcePosition >= SourceLength) { return Result + " (at an unknown location)"; }; /**** start looking for the requested position ****/ var LineCount:int = 1; // human-readable line numbering var LineStart:int = 0; // start position of the current line var CharCount:int = 0; // loop variable do { var curChar:String = SourceCode.charAt(CharCount); if ((curChar === "\n") || (curChar === "\r")) { // EOL encountered var nextChar:String = SourceCode.charAt(CharCount+1); if ( ((nextChar === "\n") || (nextChar === "\r")) && (nextChar !== curChar) ) { // EOL sequence encountered CharCount += 1; // no need to update curChar }; LineCount += 1; LineStart = CharCount+1; }; CharCount += 1; } while (CharCount <= SourcePosition); /**** then describe the situation at the given position ****/ if ((curChar === "\n") || (curChar === "\r")) { // strange - exactly at EOL return " at line " + LineCount + ", column 0: (i.e., exactly at EOL)"; }; Result += " at line " + LineCount + ", column " + (SourcePosition-LineStart+1) + ": "; do { // CharCount starts at SourcePosition+1 curChar = SourceCode.charAt(CharCount); if ((curChar === "\n") || (curChar === "\r")) break;// EOL encountered if (curChar === "") break;// EOF encountered CharCount += 1; } while (CharCount <= SourceLength);// "<=" is ok: lets hit EOF explicitly /**** extract the full source line and strip it down (if necessary) ****/ var fullLine:String = SourceCode.slice(LineStart,CharCount); if (fullLine.length > 80) { var ColPos:int = SourcePosition-LineStart;// not human-readable indexing if (fullLine.length-ColPos > 40) { // strip trailing characters fullLine = fullLine.slice(0,ColPos+37) + "..."; }; if ((fullLine.length > 80) && (ColPos > 40)) {//strip leading characters fullLine = "..." + fullLine.slice(ColPos-37); }; }; return Result + "\"" + printable(fullLine) + "\""; }; /* in # source at line #, column #: "...this is the affected line..." */ //------------------------------------------------------------------------------ // isKeyword keyword detection //------------------------------------------------------------------------------ var KeywordList:Array = [ "break", "else", "new", "var", "case", "finally", "return", "while", "catch", "for", "switch", "continue", "function", "this", "default", "if", "throw", "delete", "in", "try", "do", "instanceof", "typeof", "true","false","NaN","Infinity","null","undefined" ]; var isKeyword:Object = {}; for (var i:int = 0; i < KeywordList.length; i++) { isKeyword[KeywordList[i]] = true; }; //delete KeywordList; //------------------------------------------------------------------------------ // isReservedType reserved type detection //------------------------------------------------------------------------------ var ReservedTypeList:Array = [ "boolean", "byte", "char", "double", "float", "int", "long", "short", "void" ]; var isReservedType:Object = {}; for (i = 0; i < ReservedTypeList.length; i++) { isReservedType[ReservedTypeList[i]] = true; }; //delete ReservedTypeList; //------------------------------------------------------------------------------ // isReservedWord reserved word detection //------------------------------------------------------------------------------ var ReservedWordList:Array = [ "abstract", "enum", "int", "short", "boolean", "export", "interface", "static", "byte", "extends", "long", "super", "char", "final", "native", "synchronized", "class", "float", "package", "throws", "const", "goto", "private", "transient", "debugger", "implements", "protected", "volatile", "double", "import", "public", "with" ]; var isReservedWord:Object = {}; for (i = 0; i < ReservedWordList.length; i++) { isReservedWord[ReservedWordList[i]] = true; }; //delete ReservedWordList; //------------------------------------------------------------------------------ // isPunctuator punctuator detection //------------------------------------------------------------------------------ var PunctuatorList:Array = [ "{","[","(",")","]","}",",",";",":" ]; var isPunctuator:Object = {}; for (i = 0; i < PunctuatorList.length; i++) { isPunctuator[PunctuatorList[i]] = true; }; //delete PunctuatorList; //------------------------------------------------------------------------------ // Operator# operator detection //------------------------------------------------------------------------------ var AssignmentList:Array = ["=", "+=","-=","*=","/=","%="]; var isAssignmentOperator:Object = {}; for (i = 0; i < AssignmentList.length; i++) { isAssignmentOperator[AssignmentList[i]] = true; }; //delete AssignmentList; var OperatorList:Array = ["in", "instanceof", "typeof"]; var isOperator:Object = {}; for (i = 0; i < OperatorList.length; i++) { isOperator[OperatorList[i]] = true; }; //delete OperatorList; var OperatorList1:Array = [".","?","+","-","!","*","/","%","<","=",">"]; // without ":"! var OperatorList2:Array = ["<=",">=","+=","-=","*=","/=","%=","&&","||"]; var OperatorList3:Array = ["===","!=="]; var isOperator1:Object = {}; for (i = 0; i < OperatorList1.length; i++) { isOperator1[OperatorList1[i]] = true; isOperator[OperatorList1[i]] = true; }; //delete OperatorList1; var isOperator2:Object = {}; for (i = 0; i < OperatorList2.length; i++) { isOperator2[OperatorList2[i]] = true; isOperator[OperatorList2[i]] = true; }; //delete OperatorList2; var isOperator3:Object = {}; for (i = 0; i < OperatorList3.length; i++) { isOperator3[OperatorList3[i]] = true; isOperator[OperatorList3[i]] = true; }; //delete OperatorList3; //------------------------------------------------------------------------------ // OperatorPriority //------------------------------------------------------------------------------ var PriorityList0:Array = ["=","+=","-=","*=","/=","%="]; var PriorityList1:Array = ["?"]; // without ":"! var PriorityList2:Array = ["&&","||"]; var PriorityList3:Array = ["===","!=="]; var PriorityList4:Array = ["<","<=",">=",">","in","instanceof"]; var PriorityList5:Array = ["+","-"]; var PriorityList6:Array = ["*","/","%"]; //var PriorityList7:Array = ["!","+","-","typeof"]; //var PriorityList8:Array = ["++","--"]; //var PriorityList9:Array = ["[","("]; var OperatorPriority:Object = {}; for (i = 0; i < PriorityList0.length; i++) { OperatorPriority[PriorityList0[i]] = 0; }; //delete PriorityList0; for (i = 0; i < PriorityList1.length; i++) { OperatorPriority[PriorityList1[i]] = 1; }; //delete PriorityList1; for (i = 0; i < PriorityList2.length; i++) { OperatorPriority[PriorityList2[i]] = 2; }; //delete PriorityList2; for (i = 0; i < PriorityList3.length; i++) { OperatorPriority[PriorityList3[i]] = 3; }; //delete PriorityList3; for (i = 0; i < PriorityList4.length; i++) { OperatorPriority[PriorityList4[i]] = 4; }; //delete PriorityList4; for (i = 0; i < PriorityList5.length; i++) { OperatorPriority[PriorityList5[i]] = 5; }; //delete PriorityList5; for (i = 0; i < PriorityList6.length; i++) { OperatorPriority[PriorityList6[i]] = 6; }; //delete PriorityList6; /******************************************************************************* * * * Scanner * * * *******************************************************************************/ //------------------------------------------------------------------------------ // atEOF has the source's end been reached? //------------------------------------------------------------------------------ var atEOF:Function = function ():Boolean { return (SourcePosition >= SourceLength); }; //------------------------------------------------------------------------------ // atEOL has the end of the current line been reached? //------------------------------------------------------------------------------ var atEOL:Function = function ():Boolean { return (curChar === "\n") || (curChar === "\r"); }; //------------------------------------------------------------------------------ // is... checks if a character belongs to a given character class //------------------------------------------------------------------------------ var isDigit:Function = function (Candidate:String):Boolean { return (Candidate !== "") && ("0123456789".indexOf(Candidate) >= 0); }; var isEOF:Function = function (Candidate:String):Boolean { return (Candidate === ""); }; var isEOL:Function = function (Candidate:String):Boolean { return (Candidate === "\n") || (Candidate === "\r"); }; var isHexDigit:Function = function (Candidate:String):Boolean { return (Candidate !== "") && ("0123456789abcdefABCDEF".indexOf(Candidate) >= 0); }; var WhitespaceRegExp:RegExp = /\s/; var isWhitespace:Function = function (Candidate:String):Boolean { return WhitespaceRegExp.test(Candidate); }; //------------------------------------------------------------------------------ // futureChar(s) peeks at the next character(s) in "SourceCode" //------------------------------------------------------------------------------ var futureChar:Function = function ():String { return ( SourcePosition < SourceLength-1 ? SourceCode.charAt(SourcePosition+1) : "" ); }; var futureChars:Function = function (n:int):String { return SourceCode.slice(SourcePosition+1,SourcePosition+n); }; //------------------------------------------------------------------------------ // futureTextIs compares the next characters in "SourceCode" with a given text //------------------------------------------------------------------------------ var futureTextIs:Function = function (Argument:String):Boolean { return (futureChars(Argument.length) === Argument); }; //------------------------------------------------------------------------------ // proceed proceeds to the next character in "SourceCode" //------------------------------------------------------------------------------ var proceed:Function = function ():void { if (SourcePosition < SourceLength) { SourcePosition += 1; curChar = SourceCode.charAt(SourcePosition); /**** if need be: skip a complete EOL sequence ****/ if (isEOL(curChar) && isEOL(futureChar()) && (curChar !== futureChar())) { SourcePosition += 1; }; } else { curChar = ""; }; }; //------------------------------------------------------------------------------ // skipBlockComment skips all characters of a block comment //------------------------------------------------------------------------------ var skipBlockComment:Function = function ():void { proceed(); // skip "*" of "/*" do { proceed(); } while ((curChar !== "*") || (futureChar() !== "/")); proceed(); proceed(); // skip behind "*/" }; //------------------------------------------------------------------------------ // skipLineComment skips all characters in "SourceCode" up to EOL //------------------------------------------------------------------------------ var skipLineComment:Function = function ():void { do { proceed(); } while (!isEOL(curChar) && !isEOF(curChar)); // also handles EOL sequences }; //------------------------------------------------------------------------------ // skipWhitespace skips all following whitespace characters in "SourceCode" //------------------------------------------------------------------------------ var skipWhitespace:Function = function ():void { while (true) { switch (curChar) { case "\n": case "\r": break; // EOL char or sequence case "/": switch (futureChar()) { // potential block or line comment case "/": skipLineComment(); continue; case "*": skipBlockComment(); continue; default: return; // this "/" does not belong to a comment }; case "": return; default: if (!isWhitespace(curChar)) return; }; proceed(); }; }; /******************************************************************************* * * * Tokenizer * * * *******************************************************************************/ //------------------------------------------------------------------------------ // deferToken pushes an unused token back onto the stack //------------------------------------------------------------------------------ var deferToken:Function = function (Token:Object):void { TokenStack.push(Token); }; //------------------------------------------------------------------------------ // expectToken expects (and consumes) a given token //------------------------------------------------------------------------------ var expectToken:Function = function ( TokenType:String, Description:String = "", Hint:String = "" ):void { var curToken:Object = nextToken(); if (curToken.Type !== TokenType) { if (Description === "") Description = "'" + TokenType + "'"; throw new SyntaxError( Description + " expected" + (Hint !== "" ? " (" + Hint + ")" : "") + SourceLocation(SourceCode, SourceType, curToken.Position) ); }; }; //------------------------------------------------------------------------------ // futureToken performs a token lookahead //------------------------------------------------------------------------------ var futureToken:Function = function ():Object { var Result:Object = nextToken(); deferToken(Result); return Result; }; //------------------------------------------------------------------------------ // nextToken yields the next pending token from the source code //------------------------------------------------------------------------------ var nextToken:Function = function (OperatorPreferred:Boolean = true):Object { if (TokenStack.length > 0) return TokenStack.pop(); // taken from stack skipWhitespace(); if (curChar === "") { // EOF reached return {Type:"eof", Position:SourceLength, Value:null}; }; var Candidate:String = SourceCode.slice(SourcePosition,SourcePosition+2); // ignores "="! if (isAssignmentOperator[Candidate]) { // scan assignment token proceed(); proceed(); return {Type:Candidate, Position:SourcePosition-1, Value:Candidate}; }; Candidate = curChar; if (isPunctuator[curChar]) { // any kind of punctuator proceed(); return {Type:Candidate, Position:SourcePosition-1, Value:Candidate}; }; if (curChar === "0") { // scan hexadecimal number literals if (futureChar() === "x") {return HexToken()}; }; if (isDigit(curChar)) { // scan decimal number literal return NumberToken(); }; if ((curChar === '"') || (curChar === "'")) { // scan string literal return StringToken(); }; if ((curChar === "/") && !OperatorPreferred) { // scan RegExp literal return RegExpToken(); // "OperatorPreferred" is neither elegant nor safe! }; if (isIdentifierStart(curChar)) { // keyword, reserved word or identifier return IdentifierToken(); }; var StartPosition:int = SourcePosition; // operator token Candidate = SourceCode.slice(SourcePosition,SourcePosition+3); if (isOperator3[Candidate]) { proceed(); proceed(); proceed(); return {Type:Candidate, Position:StartPosition, Value:Candidate}; }; Candidate = SourceCode.slice(SourcePosition,SourcePosition+2); if (isOperator2[Candidate]) { proceed(); proceed(); return {Type:Candidate, Position:StartPosition, Value:Candidate}; }; Candidate = curChar; if (isOperator1[Candidate]) { proceed(); return {Type:Candidate, Position:StartPosition, Value:Candidate}; }; if (curChar === "=") { // another assignment token proceed(); return {Type:"=", Position:StartPosition, Value:"="}; }; /**** that was it - there is no other choice left ****/ throw new SyntaxError( "unrecognized character " + quoted(curChar) + SourceLocation(SourceCode, SourceType, SourcePosition) ); }; //------------------------------------------------------------------------------ // HexToken extracts a hexadecimal token from "SourceCode" //------------------------------------------------------------------------------ var HexToken:Function = function ():Object { var StartPos:int = SourcePosition; // positioned on the "0" in "0x" proceed(); proceed(); // skip "0x" if (!isHexDigit(curChar)) throw new SyntaxError( "incomplete hexadecimal literal" + SourceLocation(SourceCode, SourceType, SourcePosition) ); proceed(); // skip first hexadecimal digit while (isHexDigit(curChar)) proceed(); return { Type:"literal", Position:StartPos, Value:Number(SourceCode.slice(StartPos,SourcePosition)) }; }; //------------------------------------------------------------------------------ // NumberToken extracts a number token from "SourceCode" //------------------------------------------------------------------------------ var NumberToken:Function = function ():Object { var StartPos:int = SourcePosition; // positioned at the first digit while (isDigit(curChar)) proceed(); // skip all digits of integral part if ((curChar === ".") && isDigit(futureChar())) { // decimal point found proceed(); // skip "." while (isDigit(curChar)) proceed();// skip all digits of fractional part }; if ((curChar === "e") || (curChar === "E")) { // exponent found proceed(); // skip "e" if ((curChar === "+") || (curChar === "-")) { // sign found proceed(); // skip sign }; if (!isDigit(curChar)) throw new SyntaxError( "incomplete exponent in number literal" + SourceLocation(SourceCode, SourceType, SourcePosition) ); while (isDigit(curChar)) proceed(); }; return { Type:"literal", Position:StartPos, Value:Number(SourceCode.slice(StartPos,SourcePosition)) }; }; //------------------------------------------------------------------------------ // StringToken extracts a string token from "SourceCode" //------------------------------------------------------------------------------ var StringToken:Function = function ():Object { var Quote:String = curChar; // either ' or " var Result:String = "";// final literal (with all escape sequences resolved) var StartPos:int = SourcePosition; // positioned on the first quote proceed(); // skip quote while (true) { switch (curChar) { case "": throw new SyntaxError("EOF encountered before end of string" + SourceLocation(SourceCode, SourceType, SourcePosition)); case "\r": case "\n": throw new SyntaxError("EOL encountered before end of string" + SourceLocation(SourceCode, SourceType, SourcePosition)); case Quote: proceed(); // skip final quote return {Type:"literal", Position:StartPos, Value:Result}; case "\\": switch (futureChar()) { case '"': // handle double quote case "'": // handle single quote case "\\": // handle backslash case "/": // handle slash proceed(); Result += curChar; proceed(); break; case "b": // handle backspace proceed(); proceed(); Result += "\b"; break; case "f": // handle form feed proceed(); proceed(); Result += "\f"; break; case "n": // handle newline proceed(); proceed(); Result += "\n"; break; case "r": // handle carriage return proceed(); proceed(); Result += "\r"; break; case "t": // handle horizontal tab proceed(); proceed(); Result += "\t"; break; case "u": // handle unicode escape sequence if ( !isHexDigit(SourceCode.charAt(SourcePosition+2)) || !isHexDigit(SourceCode.charAt(SourcePosition+3)) || !isHexDigit(SourceCode.charAt(SourcePosition+4)) || !isHexDigit(SourceCode.charAt(SourcePosition+5)) ) { throw new SyntaxError("illegal unicode escape sequence in " + "string literal" + SourceLocation(SourceCode, SourceType, SourcePosition)); }; Result += String.fromCharCode( parseInt(SourceCode.slice(SourcePosition+2,SourcePosition+6),16) ); break; default: throw new SyntaxError("unsupported escape sequence in string" + "literal" + SourceLocation(SourceCode, SourceType, SourcePosition) ); }; break; default: Result += curChar; proceed(); }; }; return null; // just to satisfy the compiler }; //------------------------------------------------------------------------------ // RegExpToken (sloppily) extracts a regular expression token from "SourceCode" //------------------------------------------------------------------------------ var RegExpToken:Function = function ():Object { var StartPos:int = SourcePosition+1; // positioned after the first "/" proceed(); // skip leading "/" while (curChar !== "/") { switch (curChar) { case "\\": skipREescape(); break; case "[": skipREclass(); break; default: proceed(); }; }; var StopPos:int = SourcePosition; proceed(); // skip trailing "/" /**** look for trailing modifiers ****/ var ModifierStart:int = SourcePosition; while ("gim".indexOf(curChar) !== -1) { proceed(); }; var ModifierStop:int = SourcePosition; /**** that's it! ****/ return { Type:"literal", Position:StartPos, Value:new RegExp(SourceCode.slice(StartPos,StopPos),SourceCode.slice(ModifierStart,ModifierStop)) }; }; //------------------------------------------------------------------------------ // skipREclass skips a character set specification within a regular expression //------------------------------------------------------------------------------ var skipREclass:Function = function ():void { proceed(); // skip leading "[" while (curChar !== "]") { if (curChar === "\\") { skipREescape(); } else { proceed(); }; }; proceed(); // skip trailing "]" }; //------------------------------------------------------------------------------ // skipREescape skips an escape sequence within a regular expression //------------------------------------------------------------------------------ var skipREescape:Function = function ():void { proceed(); // skip leading "\" if (curChar === "u") { // handle unicode escape sequence if ( !isHexDigit(SourceCode.charAt(SourcePosition+1)) || !isHexDigit(SourceCode.charAt(SourcePosition+2)) || !isHexDigit(SourceCode.charAt(SourcePosition+3)) || !isHexDigit(SourceCode.charAt(SourcePosition+4)) ) throw new SyntaxError( "illegal unicode escape sequence in RegExp literal" + SourceLocation(SourceCode, SourceType, SourcePosition) ); SourcePosition += 5; curChar = SourceCode.charAt(SourcePosition); return; }; if (curChar === "x") { // handle hexadecimal escape sequence if ( !isHexDigit(SourceCode.charAt(SourcePosition+1)) || !isHexDigit(SourceCode.charAt(SourcePosition+2)) ) throw new SyntaxError( "illegal hexadecimal escape sequence in RegExp literal" + SourceLocation(SourceCode, SourceType, SourcePosition) ); SourcePosition += 3; curChar = SourceCode.charAt(SourcePosition); return; }; if (curChar === "c") { // handle control character proceed(); proceed(); // skip "c" and following character return; }; proceed(); // just skip the character behind the "\" }; //------------------------------------------------------------------------------ // IdentifierToken extracts an identifier token from "SourceCode" //------------------------------------------------------------------------------ var IdentifierToken:Function = function ():Object { var StartPos:int = SourcePosition; // positioned on the first character proceed(); // skip that character while (isIdentifierPart(curChar)) proceed(); var Identifier:String = SourceCode.slice(StartPos,SourcePosition); switch (Identifier) { case "true": return {Type:"literal", Position:StartPos, Value:true}; case "false": return {Type:"literal", Position:StartPos, Value:false}; case "NaN": return {Type:"literal", Position:StartPos, Value:NaN}; case "Infinity": return {Type:"literal", Position:StartPos, Value:Infinity}; case "null": return {Type:"literal", Position:StartPos, Value:null}; case "undefined": return {Type:"literal", Position:StartPos, Value:undefined}; }; if (isKeyword[Identifier]) return { // MokkaScript keyword Type:Identifier, Position:StartPos, Value:Identifier }; if (isReservedType[Identifier]) return { // may only be used as type spec! Type:Identifier, Position:StartPos, Value:Identifier }; if (isReservedWord[Identifier]) { // future reserved word throw new SyntaxError("the given identifier is reserved" + SourceLocation(SourceCode, SourceType, SourcePosition)); }; return {Type:"identifier", Position:StartPos, Value:Identifier}; }; /******************************************************************************* * * * Parser * * * *******************************************************************************/ var curScope:Object = null; //------------------------------------------------------------------------------ // ScopePrototype //------------------------------------------------------------------------------ var ScopePrototype:Object = { Parent: null, VariableTable: {}, FunctionTable: {}, LevelStack: [], registerFunction: function ( TokenPosition:int, Identifier:String, Definition:Object ):void { if (this.VariableTable[Identifier] !== undefined) { throw new SyntaxError( "attempt to redefine variable " + quoted(Identifier) + " as a function" + SourceLocation(SourceCode, SourceType, TokenPosition) ); }; if (this.FunctionTable[Identifier] !== undefined) { throw new SyntaxError( "attempt to redefine function " + quoted(Identifier) + SourceLocation(SourceCode, SourceType, TokenPosition) ); }; this.FunctionTable[Identifier] = Definition; }, registerVariable: function (TokenPosition:int, Identifier:String):void { if (this.FunctionTable[Identifier] !== undefined) { throw new SyntaxError( "attempt to redefine function " + quoted(Identifier) + " as a variable" + SourceLocation(SourceCode, SourceType, TokenPosition) ); }; if (this.VariableTable[Identifier] !== undefined) { throw new SyntaxError( "attempt to redefine variable " + quoted(Identifier) + SourceLocation(SourceCode, SourceType, TokenPosition) ); }; this.VariableTable[Identifier] = true; } }; //------------------------------------------------------------------------------ // openScope opens a new scope //------------------------------------------------------------------------------ var openScope:Function = function ():Object { var newScope:Object = newInstance(ScopePrototype); newScope.Parent = curScope; newScope.VariableTable = {}; newScope.FunctionTable = {}; curScope = newScope; return newScope; }; //------------------------------------------------------------------------------ // closeScope closes the current scope //------------------------------------------------------------------------------ var closeScope:Function = function ():void { curScope = curScope.Parent; }; //------------------------------------------------------------------------------ // openNestLevel opens a new nest level //------------------------------------------------------------------------------ var openNestLevel:Function = function (Label:String):void { curScope.LevelStack.push({Label:Label, Id:null}); }; //------------------------------------------------------------------------------ // IdOfNestLevel yields the id of a given nest level //------------------------------------------------------------------------------ var IdOfNestLevel:Function = function (Label:*):Object { var LevelStack:Array = curScope.LevelStack; var StackDepth:int = LevelStack.length; if (StackDepth === 0) return null; if (Label !== null) { for (var i:int = 0; i < StackDepth; i++) { if (LevelStack[i].Label === Label) { if (LevelStack[i].Id === null) LevelStack[i].Id = {}; return LevelStack[i].Id; }; }; return null; }; StackDepth -= 1; // just an abbreviation if (LevelStack[StackDepth].Id === null) LevelStack[StackDepth].Id = {}; return LevelStack[StackDepth].Id; }; //------------------------------------------------------------------------------ // IdOfTopMostLevel yields the id of the topmost nest level (if present) //------------------------------------------------------------------------------ var IdOfTopMostLevel:Function = function ():Object { var LevelStack:Array = curScope.LevelStack; return LevelStack[LevelStack.length-1].Id; }; //------------------------------------------------------------------------------ // closeNestLevel closes the topmost nest level //------------------------------------------------------------------------------ var closeNestLevel:Function = function ():void { curScope.LevelStack.pop(); }; //------------------------------------------------------------------------------ // parsedIdentifier parse a single identifier //------------------------------------------------------------------------------ var parsedIdentifier:Function = function (Mode:String = "withoutType"):String { var curToken:Object = nextToken(); if (curToken.Type === "identifier") { if (Mode === "withType") {skipTypeSpecification()}; return curToken.Value; } else { if (isKeyword[curToken.Type]) { throw new SyntaxError( "reserved words must not be used as variable names" + SourceLocation(SourceCode, SourceType, curToken.Position) ); } else { throw new SyntaxError( "identifier expected" + SourceLocation(SourceCode, SourceType, curToken.Position) ); }; }; }; //------------------------------------------------------------------------------ // skipTypeSpecification skips an optional type specification //------------------------------------------------------------------------------ var skipTypeSpecification:Function = function ():void { if (futureToken().Type === ":") { var auxToken:Object = nextToken(); auxToken = nextToken(); switch (auxToken.Type) { case "*": break; case "identifier": if (isKeyword[auxToken.Type]) { throw new SyntaxError( "JavaScript keywords must not be used as type specifications" + SourceLocation(SourceCode, SourceType, auxToken.Position) ); }; break; default: if (isReservedType[auxToken.Type]) break; throw new SyntaxError( "type specification expected" + SourceLocation(SourceCode, SourceType, auxToken.Position) ); }; }; }; //------------------------------------------------------------------------------ // parsedArgumentList parses an argument list //------------------------------------------------------------------------------ var parsedArgumentList:Function = function ():Array { var ArgumentList:Array = []; while (futureToken().Type !== ")") { ArgumentList.push(parsedExpression()); if (futureToken().Type !== ")") {expectToken(",")}; }; return ArgumentList; }; //------------------------------------------------------------------------------ // parsedProgram parses the current source code as a complete "program" //------------------------------------------------------------------------------ var parsedProgram:Function = function ():Object { openScope("program"); var ProgramScope:Object = curScope; var StatementList:Array = parsedBlock("eof"); closeScope(); return ProgramNode( "(main program)", "program", ProgramScope.FunctionTable, ProgramScope.VariableTable, StatementList ); }; //------------------------------------------------------------------------------ // parsedBlock parses a complete block of statements //------------------------------------------------------------------------------ var parsedBlock:Function = function (Delimiter:String = "}"):Array { var StatementList:Array; if (Delimiter === "}") {expectToken("{")}; StatementList = parsedStatementList(";"); if (Delimiter === "}") { expectToken("}", "}", StatementList !== null ? "perhaps because of a missing ';' behind the previous command" : ""); } else { expectToken("eof", "end-of-source", StatementList !== null ? "perhaps because of a missing ';' behind the previous command" : ""); }; return StatementList; }; //------------------------------------------------------------------------------ // parsedStatementList parses a sequence of statements //------------------------------------------------------------------------------ var parsedStatementList:Function = function ( Delimiter:String, withinSwitch:Boolean = false ):Array { var StatementList:Array = []; StatementLoop: while (true) { switch (futureToken().Type) { case "}": case "eof": break StatementLoop; case ")": case ";": if (Delimiter === ",") {break StatementLoop}; case "case": case "default": if (withinSwitch) {break StatementLoop}; default: /* nop */ }; var Statement:Object = parsedStatement(Delimiter); if (Statement !== null) StatementList.push(Statement); if (futureToken().Type !== Delimiter) break; expectToken(Delimiter); }; return (StatementList.length === 0 ? null : StatementList); }; //------------------------------------------------------------------------------ // parsedStatement parses a single statement //------------------------------------------------------------------------------ var isAssignment:Object = { setVar:true, incVar:true, decVar:true, setProperty:true, incProperty:true, decProperty:true, setItem:true, incItem:true, decItem:true }; var isInvocation:Object = { call:true, callVar:true, callProperty:true, callItem:true }; var parsedStatement:Function = function (Delimiter:String = ";"):Object { switch (futureToken().Type) { case ";": case "}": case "eof": return null; case ")": case ",": if (Delimiter === ",") {return null}; default: /* nop */ }; /**** handle "classic" statements first ****/ var curToken:Object = nextToken(); switch (curToken.Type) { case "var": return parsedVarStatement (curToken.Position, Delimiter); case "delete": break; case "function": break; case "if": return parsedIfStatement (curToken.Position); case "switch": return parsedSwitchStatement (curToken.Position); case "for": return parsedForStatement (curToken.Position); case "do": return parsedDoStatement (curToken.Position); case "while": return parsedWhileStatement (curToken.Position); case "try": return parsedTryStatement (curToken.Position); case "break": return parsedBreakStatement (curToken.Position); case "continue": return parsedContinueStatement (curToken.Position); case "return": return parsedReturnStatement (curToken.Position); case "throw": return parsedThrowStatement (curToken.Position); case "literal": case "this": break; // may start an expression case "identifier": break; // dto. case "eof": throw new SyntaxError( "unexpected end-of-file" + SourceLocation(SourceCode, SourceType, curToken.Position) ); default: throw new SyntaxError( "unexpected " + quoted(curToken.Type) + SourceLocation(SourceCode, SourceType, curToken.Position) ); }; /**** check for a labelled statement ****/ if ((curToken.Type === "identifier") && (futureToken().Type === ":")) { var Label:String = curToken.Value; expectToken(":"); // consume the ":" curToken = nextToken(); // that's the first token after the label switch (curToken.Type) { case "switch": return parsedSwitchStatement(curToken.Position, Label); case "for": return parsedForStatement (curToken.Position, Label); case "do": return parsedDoStatement (curToken.Position, Label); case "while": return parsedWhileStatement (curToken.Position, Label); default: throw new SyntaxError( "'for', 'do', 'while' or 'switch' statement expected after label" + SourceLocation(SourceCode, SourceType, curToken.Position) ); }; }; /**** only assignments and invocations are left ****/ deferToken(curToken); var Statement:Object = parsedExpression(); if (isAssignment[Statement.Type]) {return Statement}; if (isInvocation[Statement.Type]) {return Statement}; switch (Statement.Type) { case "function": if (Statement.Name === null) { throw new SyntaxError( "'function' statement without function name" + SourceLocation(SourceCode, SourceType, Statement.Position) ); } else { return Statement; }; case "deleteVar": case "deleteProperty": case "deleteItem": return Statement; default: throw new SyntaxError( "assignment or invocation expected" + SourceLocation(SourceCode, SourceType, curToken.Position) ); }; }; //------------------------------------------------------------------------------ // parsedVarStatement parses a "var" statement //------------------------------------------------------------------------------ var parsedVarStatement:Function = function ( TokenPosition:int, Delimiter:String ):Object { var Identifier:String = parsedIdentifier("withType"); curScope.registerVariable(TokenPosition, Identifier); /**** look for an optional initializer ****/ var curToken:Object = nextToken(); if (curToken.Type === "=") { var Initializer:Object = parsedExpression(); curToken = nextToken(); } else { Initializer = null; }; /**** check if multiple variables are to be defined ****/ if ((curToken.Type === ",") && (Delimiter === ";")) { // special trick! deferToken({Type:"var", Position:curToken.Position, Value:"var"}); deferToken({Type:Delimiter, Position:curToken.Position, Value:Delimiter}); } else { deferToken(curToken); }; /**** treat initializers like normal assignments ****/ if (Initializer !== null) { return SetVarNode(TokenPosition,Identifier,"=",Initializer); } else { return null; }; }; //------------------------------------------------------------------------------ // parsedDeletion parses a "delete" statement or expression //------------------------------------------------------------------------------ var parsedDeletion:Function = function (TokenPosition:int):Object { var Target:Object = parsedOperand(); switch (Target.Type) { case "getVar": return DeleteVarNode(TokenPosition, Target.Identifier); case "getProperty": return DeletePropertyNode( TokenPosition, Target.Target, Target.Identifier ); case "getItem": return DeleteItemNode( TokenPosition, Target.Target, Target.Key ); default: throw new SyntaxError( "invalid operand for 'delete'" + SourceLocation(SourceCode, SourceType, TokenPosition) ); }; }; //------------------------------------------------------------------------------ // parsedFunctionDefinition parses a "function" definition //------------------------------------------------------------------------------ var parsedFunctionDefinition:Function = function (TokenPosition:int):Object { if (futureToken().Type !== "(") { var Identifier:String = parsedIdentifier(); } else { Identifier = null; }; /**** parse parameter list ****/ expectToken("("); var ParameterList:Array = []; if (futureToken().Type !== ")") while (true) { var Parameter:String = parsedIdentifier("withType"); for (var i:int = 0; i < ParameterList.length; i++) { if (ParameterList[i] === Parameter) throw new SyntaxError( "Parameter " + quoted(Parameter) + " has already been used" + SourceLocation(SourceCode, SourceType, TokenPosition) ); }; ParameterList.push(Parameter); if (futureToken().Type === ")") { break; } else { expectToken(","); }; }; expectToken(")"); skipTypeSpecification(); // skip an optional type specification /**** now parse the function body ****/ openScope("function"); var FunctionScope:Object = curScope; for (i = 0; i < ParameterList.length; i++) { curScope.registerVariable(TokenPosition, ParameterList[i]); }; var StatementList:Array = parsedBlock("}"); closeScope(); /**** that's it! ****/ var Result:Object = FunctionNode( TokenPosition, Identifier, ParameterList, FunctionScope.FunctionTable, FunctionScope.VariableTable, StatementList ); if (Identifier !== null) { curScope.registerFunction(TokenPosition, Identifier, Result); }; return Result; }; //------------------------------------------------------------------------------ // parsedIfStatement parses an "if" statement //------------------------------------------------------------------------------ var parsedIfStatement:Function = function (TokenPosition:int):Object { expectToken("("); var Condition:Object = parsedExpression(); expectToken(")"); var ThenList:Array = parsedBlock("}"); if (futureToken().Type === "else") { expectToken("else"); if (futureToken().Type === "if") { // cascaded if var curToken:Object = nextToken(); var ElseList:Array = [parsedIfStatement(curToken.Position)]; } else { ElseList = parsedBlock("}"); // if-then-else }; } else { // if-then ElseList = null; }; return IfNode(TokenPosition, Condition, ThenList, ElseList); }; //------------------------------------------------------------------------------ // parsedSwitchStatement parses a "switch" statement //------------------------------------------------------------------------------ var parsedSwitchStatement:Function = function ( TokenPosition:int, Label:String = null ):Object { var CaseItemList:Array = []; var CaseItemMap:Array = []; var DoList:Array = []; var DefaultMap:int = -1; expectToken("("); var Discriminant:Object = parsedExpression(); expectToken(")"); openNestLevel(Label); expectToken("{"); var curToken:Object = nextToken(); while (curToken.Type !== "}") { if ((curToken.Type !== "case") && (curToken.Type !== "default")) { throw new SyntaxError( "'case' or 'default' expected" + SourceLocation(SourceCode, SourceType, curToken.Position) ); }; if (curToken.Type === "case") { CaseItemList.push(parsedExpression()); CaseItemMap.push(DoList.length); } else { if (DefaultMap !== -1) throw new SyntaxError( "the 'default' clause has already been defined" + SourceLocation(SourceCode, SourceType, curToken.Position) ); DefaultMap = DoList.length; }; expectToken(":"); curToken = nextToken(); if ( (curToken.Type !== "case") && (curToken.Type !== "default") ) { deferToken(curToken); DoList.push(parsedStatementList(";", true)); curToken = nextToken(); }; }; // expectToken("}"); var Id:Object = IdOfTopMostLevel(); // remember the "break" id closeNestLevel(); return SwitchNode( TokenPosition, Id, Discriminant, CaseItemList, CaseItemMap, DoList, DefaultMap ); }; //------------------------------------------------------------------------------ // parsedForStatement parses a "for" statement //------------------------------------------------------------------------------ var parsedForStatement:Function = function ( TokenPosition:int, Label:String = "" ):Object { expectToken("("); var ForInFound:Boolean = false; var curToken:Object = nextToken(); var varToken:Object = null; if (curToken.Type === "var") { varToken = curToken; curToken = nextToken(); }; var IdentifierToken:Object = curToken; if (curToken.Type === "identifier") { switch (futureToken().Type) { case "in": ForInFound = true; break; case ":": var ColonToken:Object = nextToken(); // skip the ":" curToken = nextToken();// expect a type specification ForInFound = ( (curToken.Type === "identifier") || (curToken.Type === "*") || isReservedType[curToken.Type] ) && (futureToken().Type === "in"); deferToken(curToken); deferToken(ColonToken); break; default: ForInFound = false; }; }; deferToken(IdentifierToken); if (varToken !== null) deferToken(varToken); if (ForInFound) { return parsedForInStatement(TokenPosition, Label); } else { return parsedForNextStatement(TokenPosition, Label); }; }; //------------------------------------------------------------------------------ // parsedForNextStatement parses a classical "for" statement //------------------------------------------------------------------------------ var parsedForNextStatement:Function = function ( TokenPosition:int, Label:String = "" ):Object { // expectToken("("); var Initializer:Array = parsedStatementList(","); expectToken(";"); if (futureToken().Type !== ";") { var Condition:Object = parsedExpression(); } else { Condition = null; }; expectToken(";"); var Incrementor:Array = parsedStatementList(","); expectToken(")"); openNestLevel(Label); var StatementList:Array = parsedBlock("}"); var Id:Object = IdOfTopMostLevel(); // remember the "break" id closeNestLevel(); return ForNode( TokenPosition, Id, Initializer, Condition, Incrementor, StatementList ); }; //------------------------------------------------------------------------------ // parsedForInStatement parses a "for-in" statement //------------------------------------------------------------------------------ var parsedForInStatement:Function = function ( TokenPosition:int, Label:String = "" ):Object { // expectToken("("); var hasVar:Boolean = (futureToken().Type === "var"); if (hasVar) {expectToken("var")}; var Identifier:String = parsedIdentifier(hasVar ? "withType" : "withoutType"); if (hasVar) {curScope.registerVariable(TokenPosition,Identifier)}; expectToken("in"); var Generator:Object = parsedExpression(); expectToken(")"); openNestLevel(Label); var StatementList:Array = parsedBlock("}"); var Id:Object = IdOfTopMostLevel(); // remember the "break" id closeNestLevel(); return ForInNode(TokenPosition, Id, Identifier, Generator, StatementList); }; //------------------------------------------------------------------------------ // parsedDoStatement parses a "do" statement //------------------------------------------------------------------------------ var parsedDoStatement:Function = function ( TokenPosition:int, Label:String = "" ):Object { openNestLevel(Label); var StatementList:Array = parsedBlock("}"); var Id:Object = IdOfTopMostLevel(); // remember the "break" id closeNestLevel(); expectToken("while"); expectToken("("); var Condition:Object = parsedExpression(); expectToken(")"); return DoNode( TokenPosition, Id, Condition, StatementList ); }; //------------------------------------------------------------------------------ // parsedWhileStatement parses a "while" statement //------------------------------------------------------------------------------ var parsedWhileStatement:Function = function ( TokenPosition:int, Label:String = "" ):Object { expectToken("("); var Condition:Object = parsedExpression(); expectToken(")"); openNestLevel(Label); var StatementList:Array = parsedBlock("}"); var Id:Object = IdOfTopMostLevel(); // remember the "break" id closeNestLevel(); return WhileNode( TokenPosition, Id, Condition, StatementList ); }; //------------------------------------------------------------------------------ // parsedTryStatement parses a "try" statement //------------------------------------------------------------------------------ var parsedTryStatement:Function = function (TokenPosition:int):Object { var TryList:Array = parsedBlock("}"); if (futureToken().Type === "catch") { expectToken("catch"); expectToken("("); var hasVar:Boolean = (futureToken().Type === "var"); if (hasVar) {expectToken("var")}; var Identifier:String = parsedIdentifier("withType"); if (hasVar) {curScope.registerVariable(TokenPosition,Identifier)}; expectToken(")"); var CatchList:Array = parsedBlock("}"); } else { Identifier = null; CatchList = null; }; if (futureToken().Type === "finally") { expectToken("finally"); var FinallyList:Array = parsedBlock("}"); } else { FinallyList = null; }; return TryNode( TokenPosition, TryList, Identifier, CatchList, FinallyList ); }; //------------------------------------------------------------------------------ // parsedBreakStatement parses an "break" statement //------------------------------------------------------------------------------ var parsedBreakStatement:Function = function (TokenPosition:int):Object { if (futureToken().Type === "identifier") { var Label:String = nextToken().Value; var Id:Object = IdOfNestLevel(Label); if (Id === null) throw new SyntaxError( "no 'for', 'do', 'while' or 'switch' statement uses the given " + "'break' label (" + quoted(Label) + ")" + SourceLocation(SourceCode, SourceType, TokenPosition) ); } else { Id = IdOfNestLevel(null); if (Id === null) throw new SyntaxError( "'break' outside any 'for', 'do', 'while' or 'switch' statement" + SourceLocation(SourceCode, SourceType, TokenPosition) ); }; return BreakNode(TokenPosition, Id); }; //------------------------------------------------------------------------------ // parsedContinueStatement parses a "continue" statement //------------------------------------------------------------------------------ var parsedContinueStatement:Function = function (TokenPosition:int):Object { if (futureToken().Type === "identifier") { var Label:String = nextToken().Value; var Id:Object = IdOfNestLevel(Label); if (Id === null) throw new SyntaxError( "no 'for', 'do' or 'while' statement uses the given " + "'continue' label (" + quoted(Label) + ")" + SourceLocation(SourceCode, SourceType, TokenPosition) ); } else { Id = IdOfNestLevel(null); if (Id === null) throw new SyntaxError( "'continue' outside any 'for', 'do' or 'while' statement" + SourceLocation(SourceCode, SourceType, TokenPosition) ); }; return ContinueNode(TokenPosition, Id); }; //------------------------------------------------------------------------------ // parsedReturnStatement parses a "return" statement //------------------------------------------------------------------------------ var parsedReturnStatement:Function = function (TokenPosition:int):Object { var Result:Object = undefined; switch (futureToken().Type) { case "eof": case ";": case "}": case ")": case ",": break; default: Result = parsedExpression(); }; if ((curScope.Parent === null) && (SourceType !== "eval")) { throw new SyntaxError( "'return' outside a function" + SourceLocation(SourceCode, SourceType, TokenPosition) ); }; return ReturnNode(TokenPosition, Result); }; //------------------------------------------------------------------------------ // parsedThrowStatement parses a "throw" statement //------------------------------------------------------------------------------ var parsedThrowStatement:Function = function (TokenPosition:int):Object { var Signal:Object = parsedExpression(); return ThrowNode(TokenPosition, Signal); }; //------------------------------------------------------------------------------ // parsedExpression parses (and yields) a given (sub-)expression //------------------------------------------------------------------------------ var parsedExpression:Function = function ( leftOperand:Object = null, Operator:String = "", OperatorPosition:int = 0 ):Object { var minPriority:int, curPriority:int; if (leftOperand === null) { leftOperand = parsedOperand(); var curToken:Object = nextToken(); if (isOperator[curToken.Type] !== true) { deferToken(curToken); return leftOperand; }; Operator = curToken.Value; OperatorPosition = curToken.Position; minPriority = 0; curPriority = OperatorPriority[Operator]; } else { curPriority = OperatorPriority[Operator]; minPriority = curPriority; }; var rightOperand:Object = ( isAssignmentOperator[Operator] ? parsedExpression() : parsedOperand() ); while (true) { curToken = nextToken(true); // prefer operator over RegExp literal if (isOperator[curToken.Type] !== true) { deferToken(curToken); return InfixNode(OperatorPosition, Operator, leftOperand, rightOperand); }; var newPriority:int = OperatorPriority[curToken.Value]; if (newPriority < minPriority) { deferToken(curToken); return InfixNode(OperatorPosition, Operator, leftOperand, rightOperand); }; if (newPriority <= curPriority) { leftOperand = InfixNode( OperatorPosition, Operator, leftOperand, rightOperand ); Operator = curToken.Value; OperatorPosition = curToken.Position; curPriority = OperatorPriority[Operator]; rightOperand = ( isAssignmentOperator[Operator] ? parsedExpression() : parsedOperand() ); } else { // newPriority > curPriority rightOperand = parsedExpression( rightOperand, curToken.Value, curToken.Position ); }; }; return null; // just to satisfy the compiler! }; //------------------------------------------------------------------------------ // parsedOperand parses (and yields) a single operand //------------------------------------------------------------------------------ var parsedOperand:Function = function (Mode:String = "withInvocation"):Object { var curToken:Object = nextToken(false); // prefer RegExp over operator switch (curToken.Type) { case "eof": throw new SyntaxError( "unexpected end-of-input in expression" + SourceLocation(SourceCode, SourceType, curToken.Position) ); case "literal": return qualified(LiteralNode(curToken.Position, curToken.Value), Mode); case "{": return qualified(parsedObjectLiteral(curToken.Position), Mode); case "[": return qualified(parsedArrayLiteral(curToken.Position), Mode); case "function": return qualified(parsedFunctionDefinition(curToken.Position), Mode); case "new": return qualified(parsedInstantiation(curToken.Position), Mode); case "delete": return qualified(parsedDeletion(curToken.Position), Mode); case "(": // grouped sub-expression var SubExpression:Object = parsedExpression(); expectToken(")"); return qualified(SubExpression, Mode); case "+": // prefix operator "+" var Operand:Object = parsedOperand(); if ((Operand.Type === "literal") && (typeof(Operand.Value) === "number")) { return Operand; } else { return PrefixNode(curToken.Position, "+", Operand); }; case "-": // prefix operator "-" Operand = parsedOperand(); if ((Operand.Type === "literal") && (typeof(Operand.Value) === "number")) { Operand.Value = -Operand.Value; return Operand; } else { return PrefixNode(curToken.Position, "-", Operand); }; case "!": // prefix operator "!" Operand = parsedOperand(); if ((Operand.Type === "literal") && (typeof(Operand.Value) === "boolean")) { Operand.Value = !Operand.Value; return Operand; } else { return PrefixNode(curToken.Position, "!", Operand); }; case "typeof": // prefix operator "typeof" return PrefixNode(curToken.Position, "typeof", parsedOperand()); case "this": return qualified(ThisNode(curToken.Position), Mode); case "identifier": return qualified(GetVarNode(curToken.Position,curToken.Value), Mode); default: if (isReservedType[curToken.Type]) { throw new SyntaxError( "the '" + curToken.Type + "' keyword may only be used for type specifications" + SourceLocation(SourceCode, SourceType, curToken.Position) ); } else { throw new SyntaxError( "operand expected" + SourceLocation(SourceCode, SourceType, curToken.Position) ); }; }; }; //------------------------------------------------------------------------------ // qualified parses a any "qualifications" behind an operand //------------------------------------------------------------------------------ var qualified:Function = function ( BaseNode:Object, Mode:String = "withInvocation" ):Object { while (true) { var curToken:Object = nextToken(); switch (curToken.Type) { case ".": BaseNode = GetPropertyNode( curToken.Position, BaseNode, parsedIdentifier() ); break; case "[": BaseNode = GetItemNode( curToken.Position, BaseNode, parsedExpression() ); expectToken("]"); break; case "(": if (Mode !== "withoutInvocation") { BaseNode = parsedInvocation(curToken.Position, BaseNode); break; }; // explicit fall-through! default: deferToken(curToken); return BaseNode; }; }; return null; // just to satisfy the compiler }; //------------------------------------------------------------------------------ // parsedInstantiation parses a "new" expression //------------------------------------------------------------------------------ var parsedInstantiation:Function = function (TokenPosition:int):Object { var Target:Object = parsedOperand("withoutInvocation"); expectToken("("); var ArgumentList:Array = parsedArgumentList(); expectToken(")"); return NewNode(TokenPosition, Target, ArgumentList); }; //------------------------------------------------------------------------------ // parsedInvocation parses an invocation //------------------------------------------------------------------------------ var parsedInvocation:Function = function ( TokenPosition:int, BaseNode:Object ):Object { // expectToken("("); var ArgumentList:Array = parsedArgumentList(); expectToken(")"); switch (BaseNode.Type) { case "getVar": return CallVarNode(TokenPosition, BaseNode.Identifier, ArgumentList); case "getProperty": return CallPropertyNode( TokenPosition, BaseNode.Target, BaseNode.Identifier, ArgumentList ); case "getItem": return CallItemNode( TokenPosition, BaseNode.Target, BaseNode.Key, ArgumentList ); default: return CallNode(TokenPosition, BaseNode, ArgumentList); }; }; //------------------------------------------------------------------------------ // parsedObjectLiteral parses an object literal //------------------------------------------------------------------------------ var parsedObjectLiteral:Function = function (TokenPosition:int):Object { // expectToken("{"); var KeyList:Array = []; var ValueList:Array = []; if (futureToken().Type !== "}") while (true) { var curToken:Object = nextToken(); if ((curToken.Type !== "identifier") && (curToken.Type !== "literal")) { throw new SyntaxError( "property name (or item key) expected" + SourceLocation(SourceCode, SourceType, curToken.Position) ); }; if ( (typeof(curToken.Value) !== "string") && (typeof(curToken.Value) !== "number") ) throw new SyntaxError( "only strings and numbers may be used as keys" + SourceLocation(SourceCode, SourceType, curToken.Position) ); KeyList.push(curToken.Value); expectToken(":"); ValueList.push(parsedExpression()); if (futureToken().Type !== ",") break; expectToken(","); }; expectToken("}"); return ObjectNode(TokenPosition, KeyList, ValueList); }; //------------------------------------------------------------------------------ // parsedArrayLiteral parses an array literal //------------------------------------------------------------------------------ var parsedArrayLiteral:Function = function (TokenPosition:int):Object { // expectToken("["); var ValueList:Array = []; if (futureToken().Type !== "]") while (true) { ValueList.push(parsedExpression()); if (futureToken().Type !== ",") break; expectToken(","); }; expectToken("]"); return ArrayNode(TokenPosition, ValueList); }; /******************************************************************************* * * * Evaluator * * * *******************************************************************************/ var MokkaBreak:Object = {}; var MokkaContinue:Object = {}; var MokkaReturn:Object = {}; var MokkaThrow:Object = {}; var MokkaStop:Object = {}; //------------------------------------------------------------------------------ // extendedException adds a stack trace to the given exception //------------------------------------------------------------------------------ var extendedException:Function = function ( Signal:*, Context:Object, SourcePosition:int ):* { if (Signal === MokkaStop) return MokkaStop; // never touch a MokkaStop var Extension:String = ""; switch (Context.Type) { case "program": Extension += "main program"; break; case "function": if (Context.Name === undefined) { Extension += "anonymous function"; } else { Extension += "function " + quoted(Context.Name); }; break; case "Function": Extension += "interpreted function"; break; case "eval": Extension += "'eval' function"; break; }; Extension += SourceLocation(Context.SourceCode, Context.SourceType, SourcePosition); if (typeof(Signal) === "string") { return "\n" + (Signal.indexOf("\n") < 0 ? "in " : "from ") + Extension; }; if ((typeof(Signal) === "object") && ("message" in Signal)) { Signal.message += "\n" + (Signal.message.indexOf("\n") < 0 ? "in " : "from ") + Extension; }; return Signal; }; //------------------------------------------------------------------------------ // EvaluationOf evaluates a list of statements //------------------------------------------------------------------------------ var EvaluationOf:Function = function ( StatementList:Array, Context:Object ):* { var Result:* = undefined; for (var i:int = 0; i < StatementList.length; i++) { Result = StatementList[i].evaluatedWithin(Context); }; return Result; }; //------------------------------------------------------------------------------ // ProgramNode implements the evaluation of a complete program //------------------------------------------------------------------------------ var ProgramNode:Function = function ( Name:String, Type:String, FunctionTable:Object, VariableTable:Object, StatementList:Array ):Object { return { SourcePosition: 0, evaluatedFor: function MainProgram (Arguments:Array):* { var Context:Object = { Name: Name, // the name of this function (if known) Type: Type, // type of context Target: GlobalObject, // current target object Variables: {}, // current list of (local) variables SourceCode: SourceCode, // underlying source code SourceType: SourceType, // type of source (e.g., "program" or "eval") ExitId: undefined, // id for "exit" or "continue" Result: undefined, // expression behind "return" or "throw" assertVariable: function (Identifier:String):void { if (!(Identifier in this.Variables)) { this.Variables[Identifier] = undefined; }; }, createVariable: function (Identifier:String, Value:*):void { this.Variables[Identifier] = Value; }, deleteVariable: function (Identifier:String):Boolean { if (Identifier in this.Variables) { this.Variables[Identifier] = undefined;//never really delete var.s return true; } else { return delete GlobalObject[Identifier]; }; }, getVariable: function (Identifier:String):* { if (Identifier in this.Variables) { return this.Variables[Identifier]; } else { return GlobalObject[Identifier]; }; }, setVariable: function (Identifier:String, Value:*):* { if (Identifier in this.Variables) { return this.Variables[Identifier] = Value; } else { return (GlobalObject[Identifier] = Value); }; } }; /**** register any declared functions ****/ for (var FunctionName:String in FunctionTable) { // if (FunctionTable.hasOwnProperty(FunctionName)) { Context.createVariable( FunctionName, FunctionTable[FunctionName].evaluatedWithin(Context) ); // }; }; /**** register any declared variables ****/ Context.createVariable("arguments", Arguments); Arguments.callee = MainProgram; for (var VariableName:String in VariableTable ) { // if (VariableTable.hasOwnProperty(VariableName)) { Context.assertVariable(VariableName); // }; }; /**** finally evaluate this program ****/ if (StatementList !== null) { try { return EvaluationOf(StatementList, Context);// propagate any signals } catch (Signal:*) { if (Context.Type === "eval") { switch (Signal) { case MokkaReturn: return Context.Result; case MokkaThrow: throw Context.Result; }; }; throw Signal; // propagate any (other) signal }; } else { return undefined; }; } }; }; //------------------------------------------------------------------------------ // FunctionNode implements a function definition //------------------------------------------------------------------------------ var FunctionNode:Function = function ( SourcePosition:int, Identifier:String, ParameterList:Array, FunctionTable:Object, VariableTable:Object, StatementList:Array ):Object { return { Type: "function", Name: Identifier, // additional info for the parser Position: SourcePosition, // dto. SourceCode: SourceCode, // keep a reference to the underlying source code SourceType: SourceType, evaluatedWithin: function (Context:Object):* { return ScriptFunction( // actually instantiates this function Identifier, ParameterList, FunctionTable, VariableTable, Context, StatementList, this.SourceCode, this.SourceType, SourcePosition ); } }; }; //------------------------------------------------------------------------------ // ArrayNode implements array literals //------------------------------------------------------------------------------ var ArrayNode:Function = function ( SourcePosition:int, ItemList:Array ):Object { return { Type: "array", evaluatedWithin: function (Context:Object):* { var Result:Array = new Array(ItemList.length); for (var i:int = 0; i < ItemList.length; i++) { Result[i] = ItemList[i].evaluatedWithin(Context); }; return Result; } }; }; //------------------------------------------------------------------------------ // ObjectNode implements object literals //------------------------------------------------------------------------------ var ObjectNode:Function = function ( SourcePosition:int, KeyList:Array, ValueList:Array ):Object { return { Type: "object", evaluatedWithin: function (Context:Object):* { var Result:Object = {}; for (var i:int = 0; i < KeyList.length; i++) { Result[KeyList[i]] = ValueList[i].evaluatedWithin(Context); }; return Result; } }; }; //------------------------------------------------------------------------------ // LiteralNode implements literals //------------------------------------------------------------------------------ var LiteralNode:Function = function ( SourcePosition:int, Value:* ):Object { return { Type: "literal", evaluatedWithin: function (Context:Object):* { return Value; } }; }; //------------------------------------------------------------------------------ // ThisNode implements "this" //------------------------------------------------------------------------------ var ThisNode:Function = function ( SourcePosition:int ):Object { return { Type: "this", evaluatedWithin: function (Context:Object):* { return Context.Target; } }; }; //------------------------------------------------------------------------------ // PrefixNode implements all "real" prefix operators //------------------------------------------------------------------------------ var PrefixNode:Function = function ( SourcePosition:int, Operator:String, Operand:Object ):Object { return { Type: "prefix", evaluatedWithin: function (Context:Object):* { var OperandValue:* = Operand.evaluatedWithin(Context); try { switch (Operator) { case "+": if (typeof(OperandValue) !== "number") { throw new TypeError("the given operand is not numeric"); } else { return OperandValue; }; case "-": if (typeof(OperandValue) !== "number") { throw new TypeError("the given operand is not numeric"); } else { return -Number(OperandValue); }; case "!": return !OperandValue; case "typeof": return typeof OperandValue; }; } catch (Signal:*) { throw extendedException(Signal, Context, SourcePosition); }; } }; }; //------------------------------------------------------------------------------ // InfixNode implements all "normal" infix operators //------------------------------------------------------------------------------ var InfixNode:Function = function ( SourcePosition:int, Operator:String, leftOperand:Object, rightOperand:Object ):Object { if (Operator === "?") { // a *very* special operator expectToken(":"); return IifNode( SourcePosition, leftOperand, rightOperand, parsedExpression() ); }; if (isAssignmentOperator[Operator]) { // a somewhat special "infix" operator switch (leftOperand.Type) { case "getVar": return SetVarNode( SourcePosition, leftOperand.Identifier, Operator, rightOperand ); case "getProperty": return SetPropertyNode( SourcePosition, leftOperand.Target, leftOperand.Identifier, Operator, rightOperand ); case "getItem": return SetItemNode( SourcePosition, leftOperand.Target, leftOperand.Key, Operator, rightOperand ); default: throw new SyntaxError( "invalid left-hand-side for an assignment expression" + SourceLocation(SourceCode, SourceType, SourcePosition) ); }; }; if ((Operator === "&&") || (Operator === "||")) { // short-cuttable return Infix2Node(SourcePosition, Operator, leftOperand, rightOperand); }; return { Type: "infix", evaluatedWithin: function (Context:Object):* { var leftValue:* = leftOperand.evaluatedWithin(Context); var rightValue:* = rightOperand.evaluatedWithin(Context); try { switch (Operator) { case "*": return (leftValue * rightValue); case "/": return (leftValue / rightValue); case "%": return (leftValue % rightValue); case "+": return (leftValue + rightValue); case "-": return (leftValue - rightValue); case "===": return (leftValue === rightValue); case "!==": return (leftValue !== rightValue); case "<": return (leftValue < rightValue); case "<=": return (leftValue <= rightValue); case ">=": return (leftValue >= rightValue); case ">": return (leftValue > rightValue); case "in": return (leftValue in rightValue); case "instanceof": return (leftValue is rightValue); }; } catch (Signal:*) { throw extendedException(Signal, Context, SourcePosition); }; } }; }; //------------------------------------------------------------------------------ // Infix2Node implements the infix operators "&&" and "||" //------------------------------------------------------------------------------ var Infix2Node:Function = function ( SourcePosition:int, Operator:String, leftOperand:Object, rightOperand:Object ):Object { return { Type: "infix", evaluatedWithin: function (Context:Object):* { var leftValue:* = leftOperand.evaluatedWithin(Context); switch (Operator) { case "&&": return leftOperand && rightOperand.evaluatedWithin(Context); case "||": return leftOperand || rightOperand.evaluatedWithin(Context); }; } }; }; //------------------------------------------------------------------------------ // IifNode implements the ternary operator //------------------------------------------------------------------------------ var IifNode:Function = function ( SourcePosition:int, Condition:Object, ThenValue:Object, ElseValue:Object ):Object { return { Type: "?", evaluatedWithin: function (Context:Object):* { return Condition.evaluatedWithin(Context) ? ThenValue.evaluatedWithin(Context) : ElseValue.evaluatedWithin(Context); } }; }; //------------------------------------------------------------------------------ // DecVarNode implements variable decrementation //------------------------------------------------------------------------------ var DecVarNode:Function = function ( SourcePosition:int, Identifier:String ):Object { return { Type: "decVar", evaluatedWithin: function (Context:Object):* { try { return Context.setVariable(Identifier,Context.getVariable(Identifier)-1); } catch (Signal:*) { throw extendedException(Signal, Context, SourcePosition); }; } }; }; //------------------------------------------------------------------------------ // DeleteVarNode implements variable deletion //------------------------------------------------------------------------------ var DeleteVarNode:Function = function ( SourcePosition:int, Identifier:String ):Object { return { Type: "deleteVar", evaluatedWithin: function (Context:Object):* { try { return Context.deleteVariable(Identifier); } catch (Signal:*) { throw extendedException(Signal, Context, SourcePosition); }; } }; }; //------------------------------------------------------------------------------ // GetVarNode implements variable retrieval //------------------------------------------------------------------------------ var GetVarNode:Function = function ( SourcePosition:int, Identifier:String ):Object { return { Type: "getVar", Identifier: Identifier, // additional info for the parser evaluatedWithin: function (Context:Object):* { try { return Context.getVariable(Identifier); } catch (Signal:*) { throw extendedException(Signal, Context, SourcePosition); }; } }; }; //------------------------------------------------------------------------------ // IncVarNode implements variable incrementation //------------------------------------------------------------------------------ var IncVarNode:Function = function ( SourcePosition:int, Identifier:String ):Object { return { Type: "incVar", evaluatedWithin: function (Context:Object):* { try { return Context.setVariable(Identifier,Context.getVariable(Identifier)+1); } catch (Signal:*) { throw extendedException(Signal, Context, SourcePosition); }; } }; }; //------------------------------------------------------------------------------ // SetVarNode implements variable assignment //------------------------------------------------------------------------------ var SetVarNode:Function = function ( SourcePosition:int, Identifier:String, Operator:String, Value:Object ):Object { return { Type: "setVar", evaluatedWithin: function (Context:Object):* { var newValue:* = Value.evaluatedWithin(Context); try { switch (Operator) { case "=": break; case "+=": newValue = Context.getVariable(Identifier) + newValue; break; case "-=": newValue = Context.getVariable(Identifier) - newValue; break; case "*=": newValue = Context.getVariable(Identifier) * newValue; break; case "/=": newValue = Context.getVariable(Identifier) / newValue; break; case "%=": newValue = Context.getVariable(Identifier) % newValue; break; }; return Context.setVariable(Identifier,newValue); } catch (Signal:*) { throw extendedException(Signal, Context, SourcePosition); }; } }; }; //------------------------------------------------------------------------------ // DecPropertyNode implements property decrementation //------------------------------------------------------------------------------ var DecPropertyNode:Function = function ( SourcePosition:int, Target:Object, Identifier:String ):Object { return { Type: "decProperty", evaluatedWithin: function (Context:Object):* { var TargetObject:* = Target.evaluatedWithin(Context); try { return (TargetObject[Identifier] -= 1); } catch (Signal:*) { throw extendedException(Signal, Context, SourcePosition); }; } }; }; //------------------------------------------------------------------------------ // DeletePropertyNode implements property deletion //------------------------------------------------------------------------------ var DeletePropertyNode:Function = function ( SourcePosition:int, Target:Object, Identifier:String ):Object { return { Type: "deleteProperty", evaluatedWithin: function (Context:Object):* { var TargetObject:* = Target.evaluatedWithin(Context); try { return (delete TargetObject[Identifier]); } catch (Signal:*) { throw extendedException(Signal, Context, SourcePosition); }; } }; }; //------------------------------------------------------------------------------ // GetPropertyNode implements property retrieval //------------------------------------------------------------------------------ var GetPropertyNode:Function = function ( SourcePosition:int, Target:Object, Identifier:String ):Object { return { Type: "getProperty", Target: Target, // additional info for the parser Identifier: Identifier, // dto. evaluatedWithin: function (Context:Object):* { var TargetObject:* = Target.evaluatedWithin(Context); try { return TargetObject[Identifier]; } catch (Signal:*) { throw extendedException(Signal, Context, SourcePosition); }; } }; }; //------------------------------------------------------------------------------ // IncPropertyNode implements property incrementation //------------------------------------------------------------------------------ var IncPropertyNode:Function = function ( SourcePosition:int, Target:Object, Identifier:String ):Object { return { Type: "incProperty", evaluatedWithin: function (Context:Object):* { var TargetObject:* = Target.evaluatedWithin(Context); try { return (TargetObject[Identifier] += 1); } catch (Signal:*) { throw extendedException(Signal, Context, SourcePosition); }; } }; }; //------------------------------------------------------------------------------ // SetPropertyNode implements property assignment //------------------------------------------------------------------------------ var SetPropertyNode:Function = function ( SourcePosition:int, Target:Object, Identifier:String, Operator:String, Value:Object ):Object { return { Type: "setProperty", evaluatedWithin: function (Context:Object):* { var TargetObject:* = Target.evaluatedWithin(Context); var TargetValue:* = Value.evaluatedWithin(Context); try { switch (Operator) { case "=": return (TargetObject[Identifier] = TargetValue); case "+=": return (TargetObject[Identifier] += TargetValue); case "-=": return (TargetObject[Identifier] -= TargetValue); case "*=": return (TargetObject[Identifier] *= TargetValue); case "/=": return (TargetObject[Identifier] /= TargetValue); case "%=": return (TargetObject[Identifier] %= TargetValue); }; } catch (Signal:*) { throw extendedException(Signal, Context, SourcePosition); }; } }; }; //------------------------------------------------------------------------------ // DecItemNode implements item decrementation //------------------------------------------------------------------------------ var DecItemNode:Function = function ( SourcePosition:int, Target:Object, Key:Object ):Object { return { Type: "decItem", evaluatedWithin: function (Context:Object):* { var TargetObject:* = Target.evaluatedWithin(Context); var TargetKey:* = Key.evaluatedWithin(Context); try { return (TargetObject[TargetKey] -= 1); } catch (Signal:*) { throw extendedException(Signal, Context, SourcePosition); }; } }; }; //------------------------------------------------------------------------------ // DeleteItemNode implements item deletion //------------------------------------------------------------------------------ var DeleteItemNode:Function = function ( SourcePosition:int, Target:Object, Key:Object ):Object { return { Type: "deleteItem", evaluatedWithin: function (Context:Object):* { var TargetObject:* = Target.evaluatedWithin(Context); var TargetKey:* = Key.evaluatedWithin(Context); try { return (delete TargetObject[TargetKey]); } catch (Signal:*) { throw extendedException(Signal, Context, SourcePosition); }; } }; }; //------------------------------------------------------------------------------ // GetItemNode implements item retrieval //------------------------------------------------------------------------------ var GetItemNode:Function = function ( SourcePosition:int, Target:Object, Key:Object ):Object { return { Type: "getItem", Target: Target, // additional info for the parser Key: Key, // dto. evaluatedWithin: function (Context:Object):* { var TargetObject:* = Target.evaluatedWithin(Context); var TargetKey:* = Key.evaluatedWithin(Context); try { return TargetObject[TargetKey]; } catch (Signal:*) { throw extendedException(Signal, Context, SourcePosition); }; } }; }; //------------------------------------------------------------------------------ // IncItemNode implements item incrementation //------------------------------------------------------------------------------ var IncItemNode:Function = function ( SourcePosition:int, Target:Object, Key:Object ):Object { return { Type: "incItem", evaluatedWithin: function (Context:Object):* { var TargetObject:* = Target.evaluatedWithin(Context); var TargetKey:* = Key.evaluatedWithin(Context); try { return (TargetObject[TargetKey] += 1); } catch (Signal:*) { throw extendedException(Signal, Context, SourcePosition); }; } }; }; //------------------------------------------------------------------------------ // SetItemNode implements item assignment //------------------------------------------------------------------------------ var SetItemNode:Function = function ( SourcePosition:int, Target:Object, Key:Object, Operator:String, Value:Object ):Object { return { Type: "setItem", evaluatedWithin: function (Context:Object):* { var TargetObject:* = Target.evaluatedWithin(Context); var TargetKey:* = Key.evaluatedWithin(Context); var TargetValue:* = Value.evaluatedWithin(Context); try { switch (Operator) { case "=": return (TargetObject[TargetKey] = TargetValue); case "+=": return (TargetObject[TargetKey] += TargetValue); case "-=": return (TargetObject[TargetKey] -= TargetValue); case "*=": return (TargetObject[TargetKey] *= TargetValue); case "/=": return (TargetObject[TargetKey] /= TargetValue); case "%=": return (TargetObject[TargetKey] %= TargetValue); }; } catch (Signal:*) { throw extendedException(Signal, Context, SourcePosition); }; } }; }; //------------------------------------------------------------------------------ // CallNode implements a generic invocation //------------------------------------------------------------------------------ var CallNode:Function = function ( SourcePosition:int, Callee:Object, ArgumentList:Array ):Object { return { Type: "call", evaluatedWithin: function (Context:Object):* { return invoke( Callee.evaluatedWithin(Context), GlobalObject, ArgumentList, Context, SourcePosition ); } }; }; //------------------------------------------------------------------------------ // CallVarNode implements variable invocation //------------------------------------------------------------------------------ var CallVarNode:Function = function ( SourcePosition:int, Identifier:String, ArgumentList:Array ):Object { return { Type: "callVar", evaluatedWithin: function (Context:Object):* { return invoke( Context.getVariable(Identifier), GlobalObject, ArgumentList, Context, SourcePosition ); } }; }; //------------------------------------------------------------------------------ // CallPropertyNode implements property invocation //------------------------------------------------------------------------------ var CallPropertyNode:Function = function ( SourcePosition:int, Target:Object, Identifier:String, ArgumentList:Array ):Object { return { Type: "callProperty", evaluatedWithin: function (Context:Object):* { var TargetValue:* = Target.evaluatedWithin(Context); return invoke( TargetValue[Identifier], TargetValue, ArgumentList, Context, SourcePosition ); } }; }; //------------------------------------------------------------------------------ // CallItemNode implements item invocation //------------------------------------------------------------------------------ var CallItemNode:Function = function ( SourcePosition:int, Target:Object, Key:Object, ArgumentList:Array ):Object { return { Type: "callItem", evaluatedWithin: function (Context:Object):* { var TargetValue:* = Target.evaluatedWithin(Context); var TargetKey:* = Key.evaluatedWithin(Context); return invoke( TargetValue[TargetKey], TargetValue, ArgumentList, Context, SourcePosition ); } }; }; //------------------------------------------------------------------------------ // invoke invokes a given object //------------------------------------------------------------------------------ var invoke:Function = function ( Callee:*, Target:Object, ArgumentList:Array, Context:Object, SourcePosition:int ):* { var Arguments:Array = []; for (var i:int = 0; i < ArgumentList.length; i++) { Arguments.push(ArgumentList[i].evaluatedWithin(Context)); }; try { if (Callee === Function) { // not directly supported by AS3! return newFunction(Context,Arguments); // own implementation } else { return Callee.apply(Target,Arguments); }; } catch (Signal:*) { throw extendedException(Signal, Context, SourcePosition); }; }; //------------------------------------------------------------------------------ // NewNode implements instantiations //------------------------------------------------------------------------------ var NewNode:Function = function ( SourcePosition:int, Target:Object, ArgumentList:Array ):Object { return { Type: "new", evaluatedWithin: function (Context:Object):* { var Constructor:* = Target.evaluatedWithin(Context); var Arguments:Array = []; for (var i:int = 0; i < ArgumentList.length; i++) { Arguments.push(ArgumentList[i].evaluatedWithin(Context)); }; /**** take care of special built-in constructors ****/ try { if (Constructor === Boolean) return new Boolean(Arguments[0]); if (Constructor === Number) return new Number(Arguments[0]); if (Constructor === String) return new String(Arguments[0]); if (Constructor === Date) { switch (ArgumentList.length) { case 0: return new Date(); case 1: return new Date(Arguments[0]); case 2: return new Date(Arguments[0],Arguments[1]); case 3: return new Date(Arguments[0],Arguments[1],Arguments[2]); case 4: return new Date( Arguments[0],Arguments[1],Arguments[2],Arguments[3] ); case 5: return new Date( Arguments[0],Arguments[1],Arguments[2],Arguments[3], Arguments[4] ); case 6: return new Date( Arguments[0],Arguments[1],Arguments[2],Arguments[3], Arguments[4],Arguments[5] ); default: return new Date( Arguments[0],Arguments[1],Arguments[2],Arguments[3], Arguments[4],Arguments[5],Arguments[6] ); }; }; if (Constructor === RegExp) { return new RegExp(Arguments[0],Arguments[1]); }; if (Constructor === Error) return new Error(Arguments[0]); if (Constructor === EvalError) return new EvalError(Arguments[0]); if (Constructor === RangeError) return new RangeError(Arguments[0]); if (Constructor === ReferenceError) return new ReferenceError(Arguments[0]); if (Constructor === SyntaxError) return new SyntaxError(Arguments[0]); if (Constructor === TypeError) return new TypeError(Arguments[0]); if (Constructor === URIError) return new URIError(Arguments[0]); if (Constructor === Array) { if ((ArgumentList.length === 1) && (typeof Arguments[0] === "number")) { return new Array(Arguments[0]); // foreseen # of elements given } else { return Arguments; // ;-) }; }; if (Constructor === Object) { return new Object(); }; if (Constructor === Function) { // not directly supported by AS3! return newFunction(Context,Arguments,SourcePosition); // own implementation }; /**** otherwise create a new object ****/ if (typeof(Constructor) !== "function") throw new TypeError( "the given 'constructor' is not a function" ); var Result:Object = newInstance(Constructor.prototype); Constructor.apply(Result,Arguments); return Result; } catch (Signal:*) { throw extendedException(Signal, Context, SourcePosition); }; } }; }; //------------------------------------------------------------------------------ // newFunction creates a new function object //------------------------------------------------------------------------------ var newFunction:Function = function ( Context:Object, Arguments:Array, SourcePosition:int ):Function { if (Arguments.length === 0) return new Function(); /**** prepare the given arguments ****/ if (Arguments.length > 1) { var Parameters:String = String(Arguments[0]); for (var i:int = 1; i < Arguments.length-1; i++) { Parameters += "," + Arguments[i]; }; }; SourceCode = Arguments[Arguments.length-1]; // occurs at run-time!!!! /**** prepare the parameter list ****/ var ParameterList:Array = Parameters.split(","); for (i = 0; i < ParameterList.length; i++) { ParameterList[i] = trimmed(ParameterList[i]); if (isReservedWord(ParameterList[i])) throw new SyntaxError( "a reserved word (" + quoted(ParameterList[i]) + ") can not be used as parameter name" ); if (!isIdentifier(ParameterList[i])) throw new SyntaxError( "the given parameter name (" + quoted(ParameterList[i]) + ") is " + "not a valid JavaScript identifier" ); }; /**** parse the given function body and create a function object ****/ SourceLength = SourceCode.length; // just a shortcut SourceType = "function"; SourcePosition = 0; // flat position of currently processed char curChar = SourceCode.charAt(0); // currently processed char TokenStack = new Array(); // stack of pending tokens openScope(); var StatementList:Array = parsedStatementList(); var localScope:Object = curScope; // preserve curScope for a moment closeScope(); return ScriptFunction( null, ParameterList, localScope.FunctionTable, localScope.VariableTable, curScope, StatementList, "Function", SourceCode, 0 ); }; //------------------------------------------------------------------------------ // ScriptFunction creates a new function object //------------------------------------------------------------------------------ var ScriptFunction:Function = function ( Name:String, ParameterList:Array, FunctionTable:Object, VariableTable:Object, Scope:Object, StatementList:Array, SourceCode:String, SourceType:String, SourcePosition:int ):Function { var Result:Function = function ScriptFunction (...Arguments:*):* { var Context:Object = { Name: Name, // the name of this function (if known) Type: "function", // type of context Scope: Scope, // outer scope of this function Target: this, // current target object Variables: {}, // current list of (local) variables SourceCode: SourceCode, // underlying source code SourceType: SourceType, // type of source (e.g., "program" or "eval") Label: undefined, // label for "break" or "continue" Result: undefined, // expression behind "return" or "throw" assertVariable: function (Identifier:String):void { if (!(Identifier in this.Variables)) { this.Variables[Identifier] = undefined; }; }, createVariable: function (Identifier:String, Value:*):void { this.Variables[Identifier] = Value; }, deleteVariable: function (Identifier:String):Boolean { if (Identifier in this.Variables) { this.Variables[Identifier] = undefined; // never really delete var.s return true; } else { return this.Scope.deleteVariable(Identifier); }; }, getVariable: function (Identifier:String):* { if (Identifier in this.Variables) { return this.Variables[Identifier]; } else { return this.Scope.getVariable(Identifier); }; }, setVariable: function (Identifier:String, Value:*):* { if (Identifier in this.Variables) { return this.Variables[Identifier] = Value; } else { return this.Scope.setVariable(Identifier,Value); }; } }; /**** in case of a named function: register the function itself ****/ if (Name !== null) { Context.createVariable(Name,Result); }; /**** grab the given arguments ****/ for (var i:int = 0; i < ParameterList.length; i++) { Context.createVariable(ParameterList[i],Arguments[i]); }; /**** register any declared functions ****/ for (var FunctionName:String in FunctionTable) { // if (FunctionTable.hasOwnProperty(FunctionName)) { Context.createVariable( FunctionName, FunctionTable[FunctionName].evaluatedWithin(Context) ); // }; }; /**** register any declared variables ****/ Context.createVariable("arguments", Arguments); Arguments.callee = ScriptFunction; for (var VariableName:String in VariableTable ) { // if (VariableTable.hasOwnProperty(VariableName)) { Context.assertVariable(VariableName); // }; }; /**** finally evaluate this function ****/ if (StatementList !== null) { try { return EvaluationOf(StatementList, Context); } catch (Signal:*) { if (Signal === MokkaReturn) { return Context.Result; } else { throw Signal; }; }; }; }; return Result; }; //------------------------------------------------------------------------------ // IfNode implements the "if" statement //------------------------------------------------------------------------------ var IfNode:Function = function ( SourcePosition:int, Condition:Object, ThenList:Array, ElseList:Array ):Object { return { Type: "if", evaluatedWithin: function (Context:Object):* { if (Condition.evaluatedWithin(Context)) { if (ThenList !== null) { return EvaluationOf(ThenList, Context); } else { return undefined; }; } else { if (ElseList !== null) { return EvaluationOf(ElseList, Context); } else { return undefined; }; }; } }; }; //------------------------------------------------------------------------------ // SwitchNode implements the "switch" statement //------------------------------------------------------------------------------ var SwitchNode:Function = function ( SourcePosition:int, Id:Object, Discriminant:Object, CaseItemList:Array, CaseItemMap:Array, DoList:Array, DefaultMap:int ):Object { return { Type: "switch", evaluatedWithin: function (Context:Object):* { var DiscriminantValue:* = Discriminant.evaluatedWithin(Context); var CaseIndex:int = DefaultMap; // might be -1, that's ok! for (var i:int = 0; i < CaseItemList.length; i++) { if (CaseItemList[i].evaluatedWithin(Context) === DiscriminantValue) { CaseIndex = CaseItemMap[i]; break; }; }; if (CaseIndex === -1) return undefined; // no matching condition var Result:*; for (i = CaseIndex; i < DoList.length; i++) { // handles fall-through try { Result = EvaluationOf(DoList[i],Context); } catch (Signal:*) { if (Signal === MokkaBreak) { if ((Context.Id === null) || (Context.Id === Id)) { return Result; }; }; throw Signal; // propagate any other exception }; }; return Result; } }; }; //------------------------------------------------------------------------------ // ForNode implements the classical "for" statement //------------------------------------------------------------------------------ var ForNode:Function = function ( SourcePosition:int, Id:Object, InitializerList:Array, Condition:Object, IncrementorList:Array, StatementList:Array ):Object { return { Type: "for", evaluatedWithin: function (Context:Object):* { if (InitializerList !== null) { var Result:* = EvaluationOf(InitializerList, Context); }; while ( (Condition === null) || Condition.evaluatedWithin(Context) ) { if (StatementList !== null) { try { Result = EvaluationOf(StatementList, Context); } catch (Signal:*) { switch (Signal) { case MokkaBreak: if ((Context.Id === null) || (Context.Id === Id)) { return Result; } else { throw Signal; // propagate break }; case MokkaContinue: if ((Context.Id === null) || (Context.Id === Id)) { break; } else { throw Signal; // propagate continue }; default: throw Signal; // propagate any other exception }; }; }; if (IncrementorList !== null) { Result = EvaluationOf(IncrementorList, Context); }; }; return Result; } }; }; //------------------------------------------------------------------------------ // ForInNode implements the "for-in" statement //------------------------------------------------------------------------------ var ForInNode:Function = function ( SourcePosition:int, Id:Object, Identifier:String, Generator:Object, StatementList:Array ):Object { return { Type: "forin", evaluatedWithin: function (Context:Object):* { for (var LoopVariable:String in Generator.evaluatedWithin(Context)) { Context.setVariable(Identifier, LoopVariable); if (StatementList !== null) { try { var Result:* = EvaluationOf(StatementList, Context); } catch (Signal:*) { if (Signal === MokkaBreak) { if ((Context.Id === null) || (Context.Id === Id)) { return Result; } else { throw Signal; // propagate break }; }; if (Signal === MokkaContinue) { if ((Context.Id === null) || (Context.Id === Id)) { continue; } else { throw Signal; // propagate continue }; }; throw Signal; // propagate any other exception /* switch (Signal) { // switch produces "unbalanced scope" error! #1031 case MokkaBreak: if ((Context.Id === null) || (Context.Id === Id)) { return Result; // } else { // throw Signal; // propagate break }; case MokkaContinue: if ((Context.Id === null) || (Context.Id === Id)) { continue; // } else { // throw Signal; // propagate continue }; default: throw Signal; // propagate any other exception }; */ }; }; }; return Result; } }; }; //------------------------------------------------------------------------------ // DoNode implements the "do" statement //------------------------------------------------------------------------------ var DoNode:Function = function ( SourcePosition:int, Id:Object, Condition:Object, StatementList:Array ):Object { return { Type: "do", evaluatedWithin: function (Context:Object):* { var Result:* = undefined; do { if (StatementList !== null) { try { Result = EvaluationOf(StatementList, Context); } catch (Signal:*) { if (Signal === MokkaBreak) { if ((Context.Id === null) || (Context.Id === Id)) { return Result; } else { throw Signal; // propagate break }; }; if (Signal === MokkaContinue) { if ((Context.Id === null) || (Context.Id === Id)) { continue; } else { throw Signal; // propagate continue }; }; throw Signal; // propagate any other exception /* switch (Signal) {// "switch" causes "unbalanced scope error" #1031 case MokkaBreak: if ((Context.Id === null) || (Context.Id === Id)) { return Result; } else { throw Signal; // propagate break }; case MokkaContinue: if ((Context.Id === null) || (Context.Id === Id)) { continue; } else { throw Signal; // propagate continue }; default: throw Signal; // propagate any other exception }; */ }; }; } while (Condition.evaluatedWithin(Context)); return Result; } }; }; //------------------------------------------------------------------------------ // WhileNode implements the "while" statement //------------------------------------------------------------------------------ var WhileNode:Function = function ( SourcePosition:int, Id:Object, Condition:Object, StatementList:Array ):Object { return { Type: "while", evaluatedWithin: function (Context:Object):* { var Result:* = undefined; while (Condition.evaluatedWithin(Context)) { if (StatementList !== null) { try { Result = EvaluationOf(StatementList, Context); } catch (Signal:*) { if (Signal === MokkaBreak) { if ((Context.Id === null) || (Context.Id === Id)) { return Result; } else { throw Signal; // propagate break }; }; if (Signal === MokkaContinue) { if ((Context.Id === null) || (Context.Id === Id)) { continue; } else { throw Signal; // propagate continue }; }; throw Signal; // propagate any other exception /* switch (Signal) {// "switch" causes "unbalanced scope error" #1031 case MokkaBreak: if ((Context.Id === null) || (Context.Id === Id)) { return Result; } else { throw Signal; // propagate break }; case MokkaContinue: if ((Context.Id === null) || (Context.Id === Id)) { continue; } else { throw Signal; // propagate continue }; default: throw Signal; // propagate any other exception }; */ }; }; }; return Result; } }; }; //------------------------------------------------------------------------------ // TryNode implements the "try" statement //------------------------------------------------------------------------------ var TryNode:Function = function ( SourcePosition:int, TryList:Array, Identifier:String, CatchList:Array, FinallyList:Array ):Object { return { Type: "try", evaluatedWithin: function (Context:Object):* { try { var Result:* = undefined; if (TryList !== null) { Result = EvaluationOf(TryList, Context); }; } catch (Signal:*) { switch (Signal) { case MokkaStop: // abort evaluation throw Signal; case MokkaBreak: // break loop case MokkaContinue: // continue loop case MokkaReturn: // return from function if (FinallyList !== null) { Result = EvaluationOf(FinallyList, Context); }; throw Signal; // propagate pseudo-exception case MokkaThrow: // user-defined exception default: // system exception (outside MokkaScript) if (CatchList !== null) { try { Context.setVariable( Identifier, (Signal === MokkaThrow ? Context.Result : Signal) ); Result = EvaluationOf(CatchList, Context); } catch (innerSignal:*) { if (FinallyList !== null) { Result = EvaluationOf(FinallyList, Context); }; throw innerSignal; }; }; if (FinallyList !== null) { Result = EvaluationOf(FinallyList, Context); }; }; }; if (FinallyList === null) { return Result; } else { return EvaluationOf(FinallyList, Context); }; } }; }; //------------------------------------------------------------------------------ // BreakNode implements the "break" statement //------------------------------------------------------------------------------ var BreakNode:Function = function (SourcePosition:int, Id:Object):Object { return { Type: "break", evaluatedWithin: function (Context:Object):* { Context.Id = Id; throw MokkaBreak; } }; }; //------------------------------------------------------------------------------ // ContinueNode implements the "continue" statement //------------------------------------------------------------------------------ var ContinueNode:Function = function (SourcePosition:int, Id:Object):Object { return { Type: "continue", evaluatedWithin: function (Context:Object):* { Context.Id = Id; throw MokkaContinue; } }; }; //------------------------------------------------------------------------------ // ReturnNode implements the "return" statement //------------------------------------------------------------------------------ var ReturnNode:Function = function (SourcePosition:int, Result:Object):Object { return { Type: "return", evaluatedWithin: function (Context:Object):* { Context.Result = ( Result === null ? undefined : Result.evaluatedWithin(Context) ); throw MokkaReturn; } }; }; //------------------------------------------------------------------------------ // ThrowNode implements the "throw" statement //------------------------------------------------------------------------------ var ThrowNode:Function = function (SourcePosition:int, Signal:Object):Object { return { Type: "throw", evaluatedWithin: function (Context:Object):* { Context.Result = Signal.evaluatedWithin(Context); throw MokkaThrow; } }; }; /******************************************************************************* * * * built-in Methods * * * *******************************************************************************/ //------------------------------------------------------------------------------ // eval parses and evaluates a given source code //------------------------------------------------------------------------------ var eval:Function = function (Source:String):* { if (typeof(Source) !== "string") throw new TypeError( "the given 'SourceCode' is not a string" ); SourceCode = Source; // occurs at run-time!!!! /**** parse the given source code ****/ SourceLength = SourceCode.length; // just a shortcut SourceType = "eval"; SourcePosition = 0; // flat position of currently processed char curChar = SourceCode.charAt(0); // currently processed char TokenStack = new Array(); // stack of pending tokens curScope = null; openScope(); var StatementList:Array = parsedBlock("eof"); var localScope:Object = curScope; // preserve curScope for a moment closeScope(); /**** and evaluate it ****/ return ProgramNode( "(eval source)", "eval", localScope.FunctionTable, localScope.VariableTable, StatementList ).evaluatedFor([]); }; GlobalObject.eval = eval; var MokkaEval:Function = eval; // calms down the compiler /******************************************************************************* * * * Assertions Methods (for Unit Testing) * * * *******************************************************************************/ //------------------------------------------------------------------------------ // assert evaluates the given code and expects "true" as result //------------------------------------------------------------------------------ var assert:Function = function (Source:String):void { var Result:* = MokkaEval(Source); if (Result !== true) throw new Error( "AssertionFailed: unexpected result\n" + "SourceCode: " + quoted(Source) + "\n" + "did not yield 'true'" ); }; //------------------------------------------------------------------------------ // assertException evaluates the given code and expects an exception //------------------------------------------------------------------------------ var assertException:Function = function (Type:*, Source:String):void { try { var Result:* = MokkaEval(Source); } catch (Signal:*) { if (!(Signal is Type)) { throw new Error( "AssertionFailed: unexpected type of exception\n" + "SourceCode: " + quoted(Source) + "\n" + "expected Exception: " + quoted(Type) + "\n" + "actual Exception: " + quoted(Signal) ); }; return; }; throw new Error( "AssertionFailed: no exception thrown\n" + "SourceCode: " + quoted(Source) ); }; //------------------------------------------------------------------------------ // assertInstance evaluates the given code and expects a specific instance //------------------------------------------------------------------------------ var assertInstance:Function = function (Instance:*, Source:String):void { var Result:* = MokkaEval(Source); if (!(Result is Instance)) { throw new Error( "AssertionFailed: unexpected type of result\n" + "SourceCode: " + quoted(Source) ); }; }; //------------------------------------------------------------------------------ // assertType evaluates the given code and expects a result of a specific type //------------------------------------------------------------------------------ var assertType:Function = function (Type:String, Source:String):void { var Result:* = MokkaEval(Source); if (typeof(Result) !== Type) { throw new Error( "AssertionFailed: unexpected type of result\n" + "SourceCode: " + quoted(Source) + "\n" + "expected Type: " + quoted(Type) + "\n" + "actual Type: " + quoted(typeof(Result)) ); }; }; //------------------------------------------------------------------------------ // assertValue evaluates the given code and expects a specific result //------------------------------------------------------------------------------ var assertValue:Function = function (Value:*, Source:String):void { var Result:* = MokkaEval(Source); if (Result !== Value) { throw new Error( "AssertionFailed: unexpected result\n" + "SourceCode: " + quoted(Source) + "\n" + "expected Result: " + quoted(Value) + "\n" + "actual Result: " + quoted(Result) ); }; }; GlobalObject.assert = assert; GlobalObject.assertException = assertException; GlobalObject.assertInstance = assertInstance; GlobalObject.assertType = assertType; GlobalObject.assertValue = assertValue; /******************************************************************************* * * * actual MokkaScript Interpreter (continued) * * * *******************************************************************************/ /**** parse the given source code ****/ if (futureToken().Type === "(eof)") { return null; }; var firstNode:Object = parsedProgram(); /**** and finally evaluate it ****/ return firstNode.evaluatedFor([]); } }}