// Acorn is a tiny, fast JavaScript parser written in JavaScript. // // Acorn was written by Marijn Haverbeke and released under an MIT // license. The Unicode regexps (for identifiers and whitespace) were // taken from [Esprima](http://esprima.org) by Ariya Hidayat. // // Git repositories for Acorn are available at // // http://marijnhaverbeke.nl/git/acorn // https://github.com/marijnh/acorn.git // // Please use the [github bug tracker][ghbt] to report issues. // // [ghbt]: https://github.com/marijnh/acorn/issues // // Objective-J extensions made by Martin Carlberg // // Git repositories for Acorn with Objective-J extension is available at // // https://github.com/mrcarlberg/acorn.git // // This file defines the main parser interface. The library also comes // with a [error-tolerant parser][dammit] and an // [abstract syntax tree walker][walk], defined in other files. // // [dammit]: acorn_loose.js // [walk]: util/walk.js (function(exports, walk) { "use strict"; exports.version = "0.3.3-objj-3"; // The main exported interface (under `self.acorn` when in the // browser) is a `parse` function that takes a code string and // returns an abstract syntax tree as specified by [Mozilla parser // API][api], with the caveat that the SpiderMonkey-specific syntax // (`let`, `yield`, inline XML, etc) is not recognized. // // [api]: https://developer.mozilla.org/en-US/docs/SpiderMonkey/Parser_API var options, input, inputLen, sourceFile; exports.parse = function(inpt, opts) { input = String(inpt); inputLen = input.length; setOptions(opts); initPreprocessorState(); if (options.macros) defineMacros(options.macros); initTokenState(); return parseTopLevel(options.program); }; // A second optional argument can be given to further configure // the parser process. These options are recognized: var defaultOptions = exports.defaultOptions = { // `ecmaVersion` indicates the ECMAScript version to parse. Must // be either 3 or 5. This // influences support for strict mode, the set of reserved words, and // support for getters and setter. ecmaVersion: 5, // Turn on `strictSemicolons` to prevent the parser from doing // automatic semicolon insertion. strictSemicolons: false, // When `allowTrailingCommas` is false, the parser will not allow // trailing commas in array and object literals. allowTrailingCommas: true, // By default, reserved words are not enforced. Enable // `forbidReserved` to enforce them. forbidReserved: false, // When `trackComments` is turned on, the parser will attach // `commentsBefore` and `commentsAfter` properties to AST nodes // holding arrays of strings. A single comment may appear in both // a `commentsBefore` and `commentsAfter` array (of the nodes // after and before it), but never twice in the before (or after) // array of different nodes. trackComments: false, // When `trackCommentsIncludeLineBreak` is turned on, the parser will // include, if present, the line break before the comment and all // the whitespace in between. trackCommentsIncludeLineBreak: false, // When `trackSpaces` is turned on, the parser will attach // `spacesBefore` and `spacesAfter` properties to AST nodes // holding arrays of strings. The same spaces may appear in both // a `spacesBefore` and `spacesAfter` array (of the nodes // after and before it), but never twice in the before (or after) // array of different nodes. trackSpaces: false, // When `locations` is on, `loc` properties holding objects with // `start` and `end` properties in `{line, column}` form (with // line being 1-based and column 0-based) will be attached to the // nodes. locations: false, // A function can be passed as `onComment` option, which will // cause Acorn to call that function with `(block, text, start, // end)` parameters whenever a comment is skipped. `block` is a // boolean indicating whether this is a block (`/* */`) comment, // `text` is the content of the comment, and `start` and `end` are // character offsets that denote the start and end of the comment. // When the `locations` option is on, two more parameters are // passed, the full `{line, column}` locations of the start and // end of the comments. onComment: null, // Nodes have their start and end characters offsets recorded in // `start` and `end` properties (directly on the node, rather than // the `loc` object, which holds line/column data. To also add a // [semi-standardized][range] `range` property holding a `[start, // end]` array with the same numbers, set the `ranges` option to // `true`. // // [range]: https://bugzilla.mozilla.org/show_bug.cgi?id=745678 ranges: false, // It is possible to parse multiple files into a single AST by // passing the tree produced by parsing the first file as // `program` option in subsequent parses. This will add the // toplevel forms of the parsed file to the `Program` (top) node // of an existing parse tree. program: null, // When `location` is on, you can pass this to record the source // file in every node's `loc` object. sourceFile: null, // Turn on objj to allow Objective-J syntax objj: true, // Turn on preprocess to allow C preprocess derectives. // #define macro1 // #define macro2 console.log("Hello") // #define macro3(x,y,z) if (x > y && y > z) console.log("Touchdown!!!") // #if macro1 // #else // #endif // etc... preprocess: true, // Preprocess 'get include file' function. It should return an object with two attributes // 'include': a string with the file to be included // 'sourceFile': is optional and should be a string with the filename. It will // be included in the locations property as 'source' // Return null if file can't be found. The parser will raise an exception preprocessGetIncludeFile: defaultGetIncludeFile, // Preprocess add macro function preprocessAddMacro: defaultAddMacro, // Preprocess get macro function preprocessGetMacro: defaultGetMacro, // Preprocess undefine macro function. To delete a macro preprocessUndefineMacro: defaultUndefineMacro, // Preprocess is macro function preprocessIsMacro: defaultIsMacro, // An array of macro objects and/or text definitions may be passed in. // Definitions may be in one of two forms: // macro // macro=body macros: null, // Turn off lineNoInErrorMessage to exclude line number in error messages // Needs to be on to run test cases lineNoInErrorMessage: true, // Array of files to parse before parsing the main file. Each item is an // object containing the properties 'include' and 'sourceFile'. 'include' // has the content from the file and 'sourceFile' has the file path. // The preprocess options must be turn on for this. preIncludeFiles: null }; function setOptions(opts) { options = opts || {}; for (var opt in defaultOptions) if (!Object.prototype.hasOwnProperty.call(options, opt)) options[opt] = defaultOptions[opt]; sourceFile = options.sourceFile || null; } var macros; var macrosIsPredicate; var macrosMakeBuiltin = function(name, macro, endPos) {return new Macro(name, macro, null, endPos - name.length)} var macrosBuiltinMacros = { __OBJJ__: function() {return macrosMakeBuiltin("__OBJJ__", options.objj ? "1" : null, tokPos)} } macrosBuiltinMacros["__" + "BROWSER" + "__"] = function() {return macrosMakeBuiltin("__BROWSER__", typeof(window) !== "undefined" ? "1" : null, tokPos)}; macrosBuiltinMacros["__" + "LINE" + "__"] = function() {return macrosMakeBuiltin("__LINE__", String(options.locations ? tokCurLine : getLineInfo(input, tokPos).line), tokPos)}; macrosBuiltinMacros["__" + "DATE" + "__"] = function() {var date, day; return macrosMakeBuiltin("__DATE__", (date = new Date(), day = String(date.getDate()), ["Jan", "Feb", "Mar", "Apr", "May", "Jun", "Jul", "Aug", "Sep", "Oct", "Nov", "Dec"][date.getMonth()] + (day.length > 1 ? " " : " ") + day + " " + date.getFullYear()), tokPos)}; macrosBuiltinMacros["__" + "TIME" + "__"] = function() {var date; return macrosMakeBuiltin("__TIME__", (date = new Date(), ("0" + date.getHours()).slice(-2) + ":" + ("0" + date.getMinutes()).slice(-2) + ":" + ("0" + date.getSeconds()).slice(-2)), tokPos)}; function defaultGetIncludeFile(filename) { return {include: "#define FOO(x) x\n", sourceFile: filename}; } function defaultAddMacro(macro) { macros[macro.identifier] = macro; macrosIsPredicate = null; } function defaultGetMacro(macroIdentifier) { return macros[macroIdentifier]; } function defaultUndefineMacro(macroIdentifier) { delete macros[macroIdentifier]; macrosIsPredicate = null; } function defaultIsMacro(macroIdentifier) { return (macrosIsPredicate || (macrosIsPredicate = makePredicate(Object.keys(macros).concat(Object.keys(macrosBuiltinMacros).filter(function(key) {return this[key]().macro != null}, macrosBuiltinMacros)).join(" "))))(macroIdentifier); } function preprocessBuiltinMacro(macroIdentifier) { var builtinMacro = macrosBuiltinMacros[macroIdentifier]; return builtinMacro ? builtinMacro() : null; } function defineMacros(macroArray) { for (var i = 0, size = macroArray.length; i < size; i++) { var savedInput = input; var macroDefinition = macroArray[i].trim(); var pos = macroDefinition.indexOf("="); if (pos === 0) raise(0, "Invalid macro definition: '" + macroDefinition + "'"); // If there is no macro body, define the name with the value 1 var name, body; if (pos > 0) { name = macroDefinition.slice(0, pos); body = macroDefinition.slice(pos + 1); } else { name = macroDefinition; } if (macrosBuiltinMacros.hasOwnProperty(name)) raise(0, "'" + name + "' is a predefined macro name"); input = name + (body != null ? " " + body : ""); inputLen = input.length; initTokenState(); preprocessParseDefine(); input = savedInput; inputLen = input.length; } } // The `getLineInfo` function is mostly useful when the // `locations` option is off (for performance reasons) and you // want to find the line/column position for a given character // offset. `input` should be the code string that the offset refers // into. var getLineInfo = exports.getLineInfo = function(input, offset) { for (var line = 1, cur = 0;;) { lineBreak.lastIndex = cur; var match = lineBreak.exec(input); if (match && match.index < offset) { ++line; cur = match.index + match[0].length; } else break; } return {line: line, column: offset - cur, lineStart: cur, lineEnd: (match ? match.index + match[0].length : input.length)}; }; // Acorn is organized as a tokenizer and a recursive-descent parser. // The `tokenize` export provides an interface to the tokenizer. // Because the tokenizer is optimized for being efficiently used by // the Acorn parser itself, this interface is somewhat crude and not // very modular. Performing another parse or call to `tokenize` will // reset the internal state, and invalidate existing tokenizers. exports.tokenize = function(inpt, opts) { input = String(inpt); inputLen = input.length; setOptions(opts); initTokenState(); initPreprocessorState(); var t = {}; function getToken(forceRegexp) { readToken(forceRegexp); t.start = tokStart; t.end = tokEnd; t.startLoc = tokStartLoc; t.endLoc = tokEndLoc; t.type = tokType; t.value = tokVal; return t; } getToken.jumpTo = function(pos, reAllowed) { tokPos = pos; if (options.locations) { tokCurLine = 1; tokLineStart = lineBreak.lastIndex = 0; var match; while ((match = lineBreak.exec(input)) && match.index < pos) { ++tokCurLine; tokLineStart = match.index + match[0].length; } } tokRegexpAllowed = reAllowed; skipSpace(); }; return getToken; }; // State is kept in (closure-)global variables. We already saw the // `options`, `input`, and `inputLen` variables above. // The current position of the tokenizer in the input. var tokPos; // The start and end offsets of the current token. // First tokstart is the same as tokStart except when the preprocessor finds a macro. // Then the tokFirstStart points to the start of the token that will be replaced by the macro. // tokStart then points at the macros first // tokMacroOffset is the offset to the current macro for the current token // tokPosMacroOffset is the offset to the current macro for the current tokPos var tokFirstStart, firstTokEnd, tokStart, tokEnd, tokMacroOffset, tokPosMacroOffset, lastTokMacroOffset; // This is the end position for the last token in the current `input` buffer. Both regular and preprocess // tokens count. It is '0' when entering a new buffer. It is used to determinate if a preprocess (#if) token // is at the begining of a line var localLastEnd; // Is null except when a macro has ended and then contains the position of the last position in the tokens macro. // `lastEndOfFile` contains this value for the last token. It is used to allow a semicolon to be inserted at the // end of a macro. You can say an end of file is equivalent to a new line. var firstEndOfFile, lastEndOfFile; // When `options.locations` is true, these hold objects // containing the tokens start and end line/column pairs. var tokStartLoc, tokEndLoc; // The type and value of the current token. Token types are objects, // named by variables against which they can be compared, and // holding properties that describe them (indicating, for example, // the precedence of an infix operator, and the original name of a // keyword token). The kind of value that's held in `tokVal` depends // on the type of the token. For literals, it is the literal value, // for operators, the operator name, and so on. var tokType, tokVal; // These are used to hold arrays of comments when // `options.trackComments` is true. var tokCommentsBefore, tokCommentsAfter, lastTokCommentsAfter; // These are used to hold arrays of spaces when // `options.trackSpaces` is true. var tokSpacesBefore, tokSpacesAfter, lastTokSpacesAfter; // Interal state for the tokenizer. To distinguish between division // operators and regular expressions, it remembers whether the last // token was one that is allowed to be followed by an expression. // (If it is, a slash is probably a regexp, if it isn't it's a // division operator. See the `parseStatement` function for a // caveat.) var tokRegexpAllowed, tokComments, tokSpaces; // When `options.locations` is true, these are used to keep // track of the current line, and know when a new line has been // entered. var tokCurLine, tokLineStart; // Same as input but for the current token. If options.preprocess is used // this can differ due to macros. var tokInput, preTokInput, lastEndInput; // These store the position of the previous token, which is useful // when finishing a node and assigning its `end` position. var lastStart, lastEnd, lastEndLoc; // This is the tokenizer's state for Objective-J. 'nodeMessageSendObjectExpression' // is used to store the expression that is already parsed when a subscript was // not really a subscript. var nodeMessageSendObjectExpression; // This is the parser's state. `inFunction` is used to reject // `return` statements outside of functions, `labels` to verify that // `break` and `continue` have somewhere to jump to, and `strict` // indicates whether strict mode is on. var inFunction, labels, strict; // These are used by the preprocess tokenizer. var preTokType, preTokVal, preTokStart, preTokEnd; var preLastStart, preLastEnd; var preprocessStack; var preprocessStackLastItem; var preprocessOnlyTransformArgumentsForLastToken; var preprocessMacroParameterListMode; var preprocessIsParsingPreprocess; var preprocessParameterScope; var preTokParameterScope; var preprocessOverrideTokEndLoc; var preprocessDontConcatenate; // Don't concatenate tokens when finishing preprocess tokens // True if we are concatenating two tokens. This is needed to handle when the second part is an empty macro // This is also used when stingifying tokens to get an empty macro var preConcatenating; // True if we are skipping token when finding #else or #endif after and #if var preNotSkipping; var preIfLevel; // This function is used to raise exceptions on parse errors. It // takes either a `{line, column}` object or an offset integer (into // the current `input`) as `pos` argument. It attaches the position // to the end of the error message, and then raises a `SyntaxError` // with that message. function raise(pos, message) { if (typeof pos == "number") pos = getLineInfo(input, pos); if (options.lineNoInErrorMessage) message += " (" + pos.line + ":" + pos.column + ")"; var syntaxError = new SyntaxError(message); syntaxError.messageOnLine = pos.line; syntaxError.messageOnColumn = pos.column; syntaxError.lineStart = pos.lineStart; syntaxError.lineEnd = pos.lineEnd; syntaxError.fileName = sourceFile; throw syntaxError; } // Reused empty array added for node fields that are always empty. var empty = []; // ## Token types // The assignment of fine-grained, information-carrying type objects // allows the tokenizer to store the information it has about a // token in a way that is very cheap for the parser to look up. // All token type variables start with an underscore, to make them // easy to recognize. // These are the general types. The `type` property is only used to // make them recognizeable when debugging. var _num = {type: "num"}, _regexp = {type: "regexp"}, _string = {type: "string"}; var _name = {type: "name"}, _eof = {type: "eof"}, _eol = {type: "eol"}; // Keyword tokens. The `keyword` property (also used in keyword-like // operators) indicates that the token originated from an // identifier-like word, which is used when parsing property names. // // The `beforeExpr` property is used to disambiguate between regular // expressions and divisions. It is set on all token types that can // be followed by an expression (thus, a slash after them would be a // regular expression). // // `isLoop` marks a keyword as starting a loop, which is important // to know when parsing a label, in order to allow or disallow // continue jumps to that label. var _break = {keyword: "break"}, _case = {keyword: "case", beforeExpr: true}, _catch = {keyword: "catch"}; var _continue = {keyword: "continue"}, _debugger = {keyword: "debugger"}, _default = {keyword: "default"}; var _do = {keyword: "do", isLoop: true}, _else = {keyword: "else", beforeExpr: true}; var _finally = {keyword: "finally"}, _for = {keyword: "for", isLoop: true}, _function = {keyword: "function"}; var _if = {keyword: "if"}, _return = {keyword: "return", beforeExpr: true}, _switch = {keyword: "switch"}; var _throw = {keyword: "throw", beforeExpr: true}, _try = {keyword: "try"}, _var = {keyword: "var"}; var _while = {keyword: "while", isLoop: true}, _with = {keyword: "with"}, _new = {keyword: "new", beforeExpr: true}; var _this = {keyword: "this"}; var _void = {keyword: "void", prefix: true, beforeExpr: true}; // The keywords that denote values. var _null = {keyword: "null", atomValue: null}, _true = {keyword: "true", atomValue: true}; var _false = {keyword: "false", atomValue: false}; // Some keywords are treated as regular operators. `in` sometimes // (when parsing `for`) needs to be tested against specifically, so // we assign a variable name to it for quick comparing. var _in = {keyword: "in", binop: 7, beforeExpr: true}; // Objective-J @ keywords var _implementation = {keyword: "implementation"}, _outlet = {keyword: "outlet"}, _accessors = {keyword: "accessors"}; var _end = {keyword: "end"}, _import = {keyword: "import"}; var _action = {keyword: "action"}, _selector = {keyword: "selector"}, _class = {keyword: "class"}, _global = {keyword: "global"}; var _dictionaryLiteral = {keyword: "{"}, _arrayLiteral = {keyword: "["}; var _ref = {keyword: "ref"}, _deref = {keyword: "deref"}; var _protocol = {keyword: "protocol"}, _optional = {keyword: "optional"}, _required = {keyword: "required"}; var _interface = {keyword: "interface"}; var _typedef = {keyword: "typedef"}; // Objective-J keywords var _filename = {keyword: "filename"}, _unsigned = {keyword: "unsigned", okAsIdent: true}, _signed = {keyword: "signed", okAsIdent: true}; var _byte = {keyword: "byte", okAsIdent: true}, _char = {keyword: "char", okAsIdent: true}, _short = {keyword: "short", okAsIdent: true}; var _int = {keyword: "int", okAsIdent: true}, _long = {keyword: "long", okAsIdent: true}, _id = {keyword: "id", okAsIdent: true}; var _boolean = {keyword: "BOOL", okAsIdent: true}, _SEL = {keyword: "SEL", okAsIdent: true}, _float = {keyword: "float", okAsIdent: true}; var _double = {keyword: "double", okAsIdent: true}; var _preprocess = {keyword: "#"}; // Preprocessor keywords var _preDefine = {keyword: "define"}; var _preUndef = {keyword: "undef"}; var _preIfdef = {keyword: "ifdef"}; var _preIfndef = {keyword: "ifndef"}; var _preIf = {keyword: "if"}; var _preElse = {keyword: "else"}; var _preEndif = {keyword: "endif"}; var _preElseIf = {keyword: "elif"}; var _preElseIfTrue = {keyword: "elif (True)"}; var _preElseIfFalse = {keyword: "elif (false)"}; var _prePragma = {keyword: "pragma"}; var _preDefined = {keyword: "defined"}; var _preBackslash = {keyword: "\\"} var _preError = {keyword: "error"}; var _preWarning = {keyword: "warning"}; var _preprocessParamItem = {type: "preprocessParamItem"} var _preprocessSkipLine = {type: "skipLine"} var _preInclude = {keyword: "include"}; // Map keyword names to token types. var keywordTypes = {"break": _break, "case": _case, "catch": _catch, "continue": _continue, "debugger": _debugger, "default": _default, "do": _do, "else": _else, "finally": _finally, "for": _for, "function": _function, "if": _if, "return": _return, "switch": _switch, "throw": _throw, "try": _try, "var": _var, "while": _while, "with": _with, "null": _null, "true": _true, "false": _false, "new": _new, "in": _in, "instanceof": {keyword: "instanceof", binop: 7, beforeExpr: true}, "this": _this, "typeof": {keyword: "typeof", prefix: true, beforeExpr: true}, "void": _void, "delete": {keyword: "delete", prefix: true, beforeExpr: true} }; // Map Objective-J keyword names to token types. var keywordTypesObjJ = {"IBAction": _action, "IBOutlet": _outlet, "unsigned": _unsigned, "signed": _signed, "byte": _byte, "char": _char, "short": _short, "int": _int, "long": _long, "id": _id, "float": _float, "BOOL": _boolean, "SEL": _SEL, "double": _double}; // Map Objective-J "@" keyword names to token types. var objJAtKeywordTypes = {"implementation": _implementation, "outlet": _outlet, "accessors": _accessors, "end": _end, "import": _import, "action": _action, "selector": _selector, "class": _class, "global": _global, "ref": _ref, "deref": _deref, "protocol": _protocol, "optional": _optional, "required": _required, "interface": _interface, "typedef": _typedef}; // Map Preprocessor keyword names to token types. var keywordTypesPreprocessor = {"define": _preDefine, "pragma": _prePragma, "ifdef": _preIfdef, "ifndef": _preIfndef, "undef": _preUndef, "if": _preIf, "endif": _preEndif, "else": _preElse, "elif": _preElseIf, "defined": _preDefined, "warning": _preWarning, "error": _preError, "include": _preInclude}; // Punctuation token types. Again, the `type` property is purely for debugging. var _bracketL = {type: "[", beforeExpr: true}, _bracketR = {type: "]"}, _braceL = {type: "{", beforeExpr: true}; var _braceR = {type: "}"}, _parenL = {type: "(", beforeExpr: true}, _parenR = {type: ")"}; var _comma = {type: ",", beforeExpr: true}, _semi = {type: ";", beforeExpr: true}; var _colon = {type: ":", beforeExpr: true}, _dot = {type: "."}, _question = {type: "?", beforeExpr: true}; // Objective-J token types var _dotdotdot = {type: "..."}; // Operators. These carry several kinds of properties to help the // parser use them properly (the presence of these properties is // what categorizes them as operators). // // `binop`, when present, specifies that this operator is a binary // operator, and will refer to its precedence. // // `prefix` and `postfix` mark the operator as a prefix or postfix // unary operator. `isUpdate` specifies that the node produced by // the operator should be of type UpdateExpression rather than // simply UnaryExpression (`++` and `--`). // // `isAssign` marks all of `=`, `+=`, `-=` etcetera, which act as // binary operators with a very low precedence, that should result // in AssignmentExpression nodes. var _slash = {binop: 10, beforeExpr: true, preprocess: true}, _eq = {isAssign: true, beforeExpr: true, preprocess: true}; var _assign = {isAssign: true, beforeExpr: true}, _plusmin = {binop: 9, prefix: true, beforeExpr: true, preprocess: true}; var _incdec = {postfix: true, prefix: true, isUpdate: true}, _prefix = {prefix: true, beforeExpr: true, preprocess: true}; var _bin1 = {binop: 1, beforeExpr: true, preprocess: true}, _bin2 = {binop: 2, beforeExpr: true, preprocess: true}; var _bin3 = {binop: 3, beforeExpr: true, preprocess: true}, _bin4 = {binop: 4, beforeExpr: true, preprocess: true}; var _bin5 = {binop: 5, beforeExpr: true, preprocess: true}, _bin6 = {binop: 6, beforeExpr: true, preprocess: true}; var _bin7 = {binop: 7, beforeExpr: true, preprocess: true}, _bin8 = {binop: 8, beforeExpr: true, preprocess: true}; var _bin10 = {binop: 10, beforeExpr: true, preprocess: true}; // Provide access to the token types for external users of the // tokenizer. exports.tokTypes = {bracketL: _bracketL, bracketR: _bracketR, braceL: _braceL, braceR: _braceR, parenL: _parenL, parenR: _parenR, comma: _comma, semi: _semi, colon: _colon, dot: _dot, question: _question, slash: _slash, eq: _eq, name: _name, eof: _eof, num: _num, regexp: _regexp, string: _string}; for (var kw in keywordTypes) exports.tokTypes["_" + kw] = keywordTypes[kw]; // This is a trick taken from Esprima. It turns out that, on // non-Chrome browsers, to check whether a string is in a set, a // predicate containing a big ugly `switch` statement is faster than // a regular expression, and on Chrome the two are about on par. // This function uses `eval` (non-lexical) to produce such a // predicate from a space-separated string of words. // // It starts by sorting the words by length. function makePredicate(words) { words = words.split(" "); var f = "", cats = []; out: for (var i = 0; i < words.length; ++i) { for (var j = 0; j < cats.length; ++j) if (cats[j][0].length == words[i].length) { cats[j].push(words[i]); continue out; } cats.push([words[i]]); } function compareTo(arr) { if (arr.length == 1) return f += "return str === " + JSON.stringify(arr[0]) + ";"; f += "switch(str){"; for (var i = 0; i < arr.length; ++i) f += "case " + JSON.stringify(arr[i]) + ":"; f += "return true}return false;"; } // When there are more than three length categories, an outer // switch first dispatches on the lengths, to save on comparisons. if (cats.length > 3) { cats.sort(function(a, b) {return b.length - a.length;}); f += "switch(str.length){"; for (var i = 0; i < cats.length; ++i) { var cat = cats[i]; f += "case " + cat[0].length + ":"; compareTo(cat); } f += "}"; // Otherwise, simply generate a flat `switch` statement. } else { compareTo(words); } return new Function("str", f); } exports.makePredicate = makePredicate; // The ECMAScript 3 reserved word list. var isReservedWord3 = makePredicate("abstract boolean byte char class double enum export extends final float goto implements import int interface long native package private protected public short static super synchronized throws transient volatile"); // ECMAScript 5 reserved words. var isReservedWord5 = makePredicate("class enum extends super const export import"); // The additional reserved words in strict mode. var isStrictReservedWord = makePredicate("implements interface let package private protected public static yield"); // The forbidden variable names in strict mode. var isStrictBadIdWord = makePredicate("eval arguments"); // And the keywords. var isKeyword = makePredicate("break case catch continue debugger default do else finally for function if return switch throw try var while with null true false instanceof typeof void delete new in this"); // The Objective-J keywords. var isKeywordObjJ = makePredicate("IBAction IBOutlet byte char short int long float unsigned signed id BOOL SEL double"); // The preprocessor keywords. var isKeywordPreprocessor = makePredicate("define undef pragma if ifdef ifndef else elif endif defined error warning include"); // ## Character categories // Big ugly regular expressions that match characters in the // whitespace, identifier, and identifier-start categories. These // are only applied when a character is found to actually have a // code point above 128. var nonASCIIwhitespace = /[\u1680\u180e\u2000-\u200a\u2028\u2029\u202f\u205f\u3000\ufeff]/; var nonASCIIwhitespaceNoNewLine = /[\u1680\u180e\u2000-\u200a\u202f\u205f\u3000\ufeff]/; var nonASCIIidentifierStartChars = "\xaa\xb5\xba\xc0-\xd6\xd8-\xf6\xf8-\u02c1\u02c6-\u02d1\u02e0-\u02e4\u02ec\u02ee\u0370-\u0374\u0376\u0377\u037a-\u037d\u0386\u0388-\u038a\u038c\u038e-\u03a1\u03a3-\u03f5\u03f7-\u0481\u048a-\u0527\u0531-\u0556\u0559\u0561-\u0587\u05d0-\u05ea\u05f0-\u05f2\u0620-\u064a\u066e\u066f\u0671-\u06d3\u06d5\u06e5\u06e6\u06ee\u06ef\u06fa-\u06fc\u06ff\u0710\u0712-\u072f\u074d-\u07a5\u07b1\u07ca-\u07ea\u07f4\u07f5\u07fa\u0800-\u0815\u081a\u0824\u0828\u0840-\u0858\u08a0\u08a2-\u08ac\u0904-\u0939\u093d\u0950\u0958-\u0961\u0971-\u0977\u0979-\u097f\u0985-\u098c\u098f\u0990\u0993-\u09a8\u09aa-\u09b0\u09b2\u09b6-\u09b9\u09bd\u09ce\u09dc\u09dd\u09df-\u09e1\u09f0\u09f1\u0a05-\u0a0a\u0a0f\u0a10\u0a13-\u0a28\u0a2a-\u0a30\u0a32\u0a33\u0a35\u0a36\u0a38\u0a39\u0a59-\u0a5c\u0a5e\u0a72-\u0a74\u0a85-\u0a8d\u0a8f-\u0a91\u0a93-\u0aa8\u0aaa-\u0ab0\u0ab2\u0ab3\u0ab5-\u0ab9\u0abd\u0ad0\u0ae0\u0ae1\u0b05-\u0b0c\u0b0f\u0b10\u0b13-\u0b28\u0b2a-\u0b30\u0b32\u0b33\u0b35-\u0b39\u0b3d\u0b5c\u0b5d\u0b5f-\u0b61\u0b71\u0b83\u0b85-\u0b8a\u0b8e-\u0b90\u0b92-\u0b95\u0b99\u0b9a\u0b9c\u0b9e\u0b9f\u0ba3\u0ba4\u0ba8-\u0baa\u0bae-\u0bb9\u0bd0\u0c05-\u0c0c\u0c0e-\u0c10\u0c12-\u0c28\u0c2a-\u0c33\u0c35-\u0c39\u0c3d\u0c58\u0c59\u0c60\u0c61\u0c85-\u0c8c\u0c8e-\u0c90\u0c92-\u0ca8\u0caa-\u0cb3\u0cb5-\u0cb9\u0cbd\u0cde\u0ce0\u0ce1\u0cf1\u0cf2\u0d05-\u0d0c\u0d0e-\u0d10\u0d12-\u0d3a\u0d3d\u0d4e\u0d60\u0d61\u0d7a-\u0d7f\u0d85-\u0d96\u0d9a-\u0db1\u0db3-\u0dbb\u0dbd\u0dc0-\u0dc6\u0e01-\u0e30\u0e32\u0e33\u0e40-\u0e46\u0e81\u0e82\u0e84\u0e87\u0e88\u0e8a\u0e8d\u0e94-\u0e97\u0e99-\u0e9f\u0ea1-\u0ea3\u0ea5\u0ea7\u0eaa\u0eab\u0ead-\u0eb0\u0eb2\u0eb3\u0ebd\u0ec0-\u0ec4\u0ec6\u0edc-\u0edf\u0f00\u0f40-\u0f47\u0f49-\u0f6c\u0f88-\u0f8c\u1000-\u102a\u103f\u1050-\u1055\u105a-\u105d\u1061\u1065\u1066\u106e-\u1070\u1075-\u1081\u108e\u10a0-\u10c5\u10c7\u10cd\u10d0-\u10fa\u10fc-\u1248\u124a-\u124d\u1250-\u1256\u1258\u125a-\u125d\u1260-\u1288\u128a-\u128d\u1290-\u12b0\u12b2-\u12b5\u12b8-\u12be\u12c0\u12c2-\u12c5\u12c8-\u12d6\u12d8-\u1310\u1312-\u1315\u1318-\u135a\u1380-\u138f\u13a0-\u13f4\u1401-\u166c\u166f-\u167f\u1681-\u169a\u16a0-\u16ea\u16ee-\u16f0\u1700-\u170c\u170e-\u1711\u1720-\u1731\u1740-\u1751\u1760-\u176c\u176e-\u1770\u1780-\u17b3\u17d7\u17dc\u1820-\u1877\u1880-\u18a8\u18aa\u18b0-\u18f5\u1900-\u191c\u1950-\u196d\u1970-\u1974\u1980-\u19ab\u19c1-\u19c7\u1a00-\u1a16\u1a20-\u1a54\u1aa7\u1b05-\u1b33\u1b45-\u1b4b\u1b83-\u1ba0\u1bae\u1baf\u1bba-\u1be5\u1c00-\u1c23\u1c4d-\u1c4f\u1c5a-\u1c7d\u1ce9-\u1cec\u1cee-\u1cf1\u1cf5\u1cf6\u1d00-\u1dbf\u1e00-\u1f15\u1f18-\u1f1d\u1f20-\u1f45\u1f48-\u1f4d\u1f50-\u1f57\u1f59\u1f5b\u1f5d\u1f5f-\u1f7d\u1f80-\u1fb4\u1fb6-\u1fbc\u1fbe\u1fc2-\u1fc4\u1fc6-\u1fcc\u1fd0-\u1fd3\u1fd6-\u1fdb\u1fe0-\u1fec\u1ff2-\u1ff4\u1ff6-\u1ffc\u2071\u207f\u2090-\u209c\u2102\u2107\u210a-\u2113\u2115\u2119-\u211d\u2124\u2126\u2128\u212a-\u212d\u212f-\u2139\u213c-\u213f\u2145-\u2149\u214e\u2160-\u2188\u2c00-\u2c2e\u2c30-\u2c5e\u2c60-\u2ce4\u2ceb-\u2cee\u2cf2\u2cf3\u2d00-\u2d25\u2d27\u2d2d\u2d30-\u2d67\u2d6f\u2d80-\u2d96\u2da0-\u2da6\u2da8-\u2dae\u2db0-\u2db6\u2db8-\u2dbe\u2dc0-\u2dc6\u2dc8-\u2dce\u2dd0-\u2dd6\u2dd8-\u2dde\u2e2f\u3005-\u3007\u3021-\u3029\u3031-\u3035\u3038-\u303c\u3041-\u3096\u309d-\u309f\u30a1-\u30fa\u30fc-\u30ff\u3105-\u312d\u3131-\u318e\u31a0-\u31ba\u31f0-\u31ff\u3400-\u4db5\u4e00-\u9fcc\ua000-\ua48c\ua4d0-\ua4fd\ua500-\ua60c\ua610-\ua61f\ua62a\ua62b\ua640-\ua66e\ua67f-\ua697\ua6a0-\ua6ef\ua717-\ua71f\ua722-\ua788\ua78b-\ua78e\ua790-\ua793\ua7a0-\ua7aa\ua7f8-\ua801\ua803-\ua805\ua807-\ua80a\ua80c-\ua822\ua840-\ua873\ua882-\ua8b3\ua8f2-\ua8f7\ua8fb\ua90a-\ua925\ua930-\ua946\ua960-\ua97c\ua984-\ua9b2\ua9cf\uaa00-\uaa28\uaa40-\uaa42\uaa44-\uaa4b\uaa60-\uaa76\uaa7a\uaa80-\uaaaf\uaab1\uaab5\uaab6\uaab9-\uaabd\uaac0\uaac2\uaadb-\uaadd\uaae0-\uaaea\uaaf2-\uaaf4\uab01-\uab06\uab09-\uab0e\uab11-\uab16\uab20-\uab26\uab28-\uab2e\uabc0-\uabe2\uac00-\ud7a3\ud7b0-\ud7c6\ud7cb-\ud7fb\uf900-\ufa6d\ufa70-\ufad9\ufb00-\ufb06\ufb13-\ufb17\ufb1d\ufb1f-\ufb28\ufb2a-\ufb36\ufb38-\ufb3c\ufb3e\ufb40\ufb41\ufb43\ufb44\ufb46-\ufbb1\ufbd3-\ufd3d\ufd50-\ufd8f\ufd92-\ufdc7\ufdf0-\ufdfb\ufe70-\ufe74\ufe76-\ufefc\uff21-\uff3a\uff41-\uff5a\uff66-\uffbe\uffc2-\uffc7\uffca-\uffcf\uffd2-\uffd7\uffda-\uffdc"; var nonASCIIidentifierChars = "\u0300-\u036f\u0483-\u0487\u0591-\u05bd\u05bf\u05c1\u05c2\u05c4\u05c5\u05c7\u0610-\u061a\u0620-\u0649\u0672-\u06d3\u06e7-\u06e8\u06fb-\u06fc\u0730-\u074a\u0800-\u0814\u081b-\u0823\u0825-\u0827\u0829-\u082d\u0840-\u0857\u08e4-\u08fe\u0900-\u0903\u093a-\u093c\u093e-\u094f\u0951-\u0957\u0962-\u0963\u0966-\u096f\u0981-\u0983\u09bc\u09be-\u09c4\u09c7\u09c8\u09d7\u09df-\u09e0\u0a01-\u0a03\u0a3c\u0a3e-\u0a42\u0a47\u0a48\u0a4b-\u0a4d\u0a51\u0a66-\u0a71\u0a75\u0a81-\u0a83\u0abc\u0abe-\u0ac5\u0ac7-\u0ac9\u0acb-\u0acd\u0ae2-\u0ae3\u0ae6-\u0aef\u0b01-\u0b03\u0b3c\u0b3e-\u0b44\u0b47\u0b48\u0b4b-\u0b4d\u0b56\u0b57\u0b5f-\u0b60\u0b66-\u0b6f\u0b82\u0bbe-\u0bc2\u0bc6-\u0bc8\u0bca-\u0bcd\u0bd7\u0be6-\u0bef\u0c01-\u0c03\u0c46-\u0c48\u0c4a-\u0c4d\u0c55\u0c56\u0c62-\u0c63\u0c66-\u0c6f\u0c82\u0c83\u0cbc\u0cbe-\u0cc4\u0cc6-\u0cc8\u0cca-\u0ccd\u0cd5\u0cd6\u0ce2-\u0ce3\u0ce6-\u0cef\u0d02\u0d03\u0d46-\u0d48\u0d57\u0d62-\u0d63\u0d66-\u0d6f\u0d82\u0d83\u0dca\u0dcf-\u0dd4\u0dd6\u0dd8-\u0ddf\u0df2\u0df3\u0e34-\u0e3a\u0e40-\u0e45\u0e50-\u0e59\u0eb4-\u0eb9\u0ec8-\u0ecd\u0ed0-\u0ed9\u0f18\u0f19\u0f20-\u0f29\u0f35\u0f37\u0f39\u0f41-\u0f47\u0f71-\u0f84\u0f86-\u0f87\u0f8d-\u0f97\u0f99-\u0fbc\u0fc6\u1000-\u1029\u1040-\u1049\u1067-\u106d\u1071-\u1074\u1082-\u108d\u108f-\u109d\u135d-\u135f\u170e-\u1710\u1720-\u1730\u1740-\u1750\u1772\u1773\u1780-\u17b2\u17dd\u17e0-\u17e9\u180b-\u180d\u1810-\u1819\u1920-\u192b\u1930-\u193b\u1951-\u196d\u19b0-\u19c0\u19c8-\u19c9\u19d0-\u19d9\u1a00-\u1a15\u1a20-\u1a53\u1a60-\u1a7c\u1a7f-\u1a89\u1a90-\u1a99\u1b46-\u1b4b\u1b50-\u1b59\u1b6b-\u1b73\u1bb0-\u1bb9\u1be6-\u1bf3\u1c00-\u1c22\u1c40-\u1c49\u1c5b-\u1c7d\u1cd0-\u1cd2\u1d00-\u1dbe\u1e01-\u1f15\u200c\u200d\u203f\u2040\u2054\u20d0-\u20dc\u20e1\u20e5-\u20f0\u2d81-\u2d96\u2de0-\u2dff\u3021-\u3028\u3099\u309a\ua640-\ua66d\ua674-\ua67d\ua69f\ua6f0-\ua6f1\ua7f8-\ua800\ua806\ua80b\ua823-\ua827\ua880-\ua881\ua8b4-\ua8c4\ua8d0-\ua8d9\ua8f3-\ua8f7\ua900-\ua909\ua926-\ua92d\ua930-\ua945\ua980-\ua983\ua9b3-\ua9c0\uaa00-\uaa27\uaa40-\uaa41\uaa4c-\uaa4d\uaa50-\uaa59\uaa7b\uaae0-\uaae9\uaaf2-\uaaf3\uabc0-\uabe1\uabec\uabed\uabf0-\uabf9\ufb20-\ufb28\ufe00-\ufe0f\ufe20-\ufe26\ufe33\ufe34\ufe4d-\ufe4f\uff10-\uff19\uff3f"; var nonASCIIidentifierStart = new RegExp("[" + nonASCIIidentifierStartChars + "]"); var nonASCIIidentifier = new RegExp("[" + nonASCIIidentifierStartChars + nonASCIIidentifierChars + "]"); // Whether a single character denotes a newline. var newline = /[\n\r\u2028\u2029]/; // Matches a whole line break (where CRLF is considered a single // line break). Used to count lines. var lineBreak = /\r\n|[\n\r\u2028\u2029]/g; // Test whether a given character code starts an identifier. var isIdentifierStart = exports.isIdentifierStart = function(code) { if (code < 65) return code === 36; if (code < 91) return true; if (code < 97) return code === 95; if (code < 123)return true; return code >= 0xaa && nonASCIIidentifierStart.test(String.fromCharCode(code)); }; // Test whether a given character is part of an identifier. var isIdentifierChar = exports.isIdentifierChar = function(code) { if (code < 48) return code === 36; if (code < 58) return true; if (code < 65) return false; if (code < 91) return true; if (code < 97) return code === 95; if (code < 123)return true; return code >= 0xaa && nonASCIIidentifier.test(String.fromCharCode(code)); }; // ## Tokenizer // These are used when `options.locations` is on, for the // `tokStartLoc` and `tokEndLoc` properties. function line_loc_t() { this.line = tokCurLine; this.column = tokPos - tokLineStart; if (preprocessStackLastItem) { var macro = preprocessStackLastItem.macro; var locationOffset = macro.locationOffset; if (locationOffset) { var macroCurrentLine = locationOffset.line; if (macroCurrentLine) this.line += macroCurrentLine; var macroCurrentLineStart = locationOffset.column; // Only add column offset if we are on the first line if (macroCurrentLineStart) this.column += tokPosMacroOffset - (tokCurLine === 1 ? macroCurrentLineStart : 0); } } } function PositionOffset(line, column) { this.line = line - 1; // Line start on one so we have to convert it to an offset this.column = column; if (preprocessStackLastItem) { var macro = preprocessStackLastItem.macro; var locationOffset = macro.locationOffset; if (locationOffset) { var macroCurrentLine = locationOffset.line; if (macroCurrentLine) this.line += macroCurrentLine; var macroCurrentLineStart = locationOffset.column; if (macroCurrentLineStart) this.column += macroCurrentLineStart; } } } // Reset the token state. Used at the start of a parse. function initTokenState() { tokCurLine = 1; tokPos = tokLineStart = lastTokMacroOffset = tokMacroOffset = tokPosMacroOffset = 0; tokRegexpAllowed = true; tokComments = null; tokSpaces = null; skipSpace(); } // Reset the token state. Used at the start of a parse. function initPreprocessorState() { macros = Object.create(null); macrosIsPredicate = null; preprocessParameterScope = null; preTokParameterScope = null; preprocessMacroParameterListMode = false; preprocessIsParsingPreprocess = false; preprocessStack = []; preprocessStackLastItem = null; preprocessOnlyTransformArgumentsForLastToken = null; preprocessDontConcatenate = false; preNotSkipping = true; preConcatenating = false; preIfLevel = []; } // Called at the end of every token. Sets `tokEnd`, `tokVal`, // `tokCommentsAfter`, `tokSpacesAfter`, and `tokRegexpAllowed`, and skips the space // after the token, so that the next one's `tokStart` will point at // the right position. function finishToken(type, val, overrideTokEnd) { if (overrideTokEnd) { firstTokEnd = tokEnd = overrideTokEnd; if (options.locations) tokEndLoc = preprocessOverrideTokLoc; } else { firstTokEnd = tokEnd = tokPos; if (options.locations) tokEndLoc = new line_loc_t; } tokType = type; var ch = skipSpace(); if (ch === 35 && options.preprocess && input.charCodeAt(tokPos + 1) === 35) { // '##' var val1 = val != null ? val : type.keyword || type.type; tokPos += 2; if (val1 != null) { // Save current line and current line start. This is needed when option.locations is true var positionOffset = options.locations && new PositionOffset(tokCurLine, tokLineStart); // Save positions on first token to get start and end correct on node if cancatenated token is invalid var saveTokInput = tokInput, saveTokEnd = tokEnd, saveTokStart = tokStart, start = tokStart + tokMacroOffset, variadicName = preprocessStackLastItem && preprocessStackLastItem.macro && preprocessStackLastItem.macro.variadicName; skipSpace(); if (variadicName && variadicName === input.slice(tokPos, tokPos + variadicName.length)) var isVariadic = true; preConcatenating = true; readToken(null, 2); // Don't transform macros preConcatenating = false; var val2 = tokVal != null ? tokVal : tokType.keyword || tokType.type; if (val2 != null) { // Skip token if it is a ',' concatenated with an empty variadic parameter if (isVariadic && val1 === "," && val2 === "") return readToken(); var concat = "" + val1 + val2, val2TokStart = tokStart + tokPosMacroOffset; // If the macro defines anything add it to the preprocess input stack var concatMacro = new Macro(null, concat, null, start, false, null, false, positionOffset); var r = readTokenFromMacro(concatMacro, tokPosMacroOffset, preprocessStackLastItem ? preprocessStackLastItem.parameterDict : null, null, tokPos, next, null); // Consumed the whole macro in one bite? If not the tokenizer can't create a single token from the two concatenated tokens if (preprocessStackLastItem && preprocessStackLastItem.macro === concatMacro) { tokType = type; tokStart = saveTokStart; tokEnd = saveTokEnd; tokInput = saveTokInput; tokPosMacroOffset = val2TokStart - val1.length; // reset the macro offset to the second token to get start and end correct on node if (!isVariadic) /*raise(tokStart,*/console.log("Warning: pasting formed '" + concat + "', an invalid preprocessing token"); } else return r; } } } tokVal = val; lastTokCommentsAfter = tokCommentsAfter; lastTokSpacesAfter = tokSpacesAfter; tokCommentsAfter = tokComments; tokSpacesAfter = tokSpaces; tokRegexpAllowed = type.beforeExpr; } function skipBlockComment(lastIsNewlinePos, dontTrack) { var startLoc = options.onComment && options.locations && new line_loc_t; var start = tokPos, end = input.indexOf("*/", tokPos += 2); if (end === -1) raise(tokPos - 2, "Unterminated comment"); tokPos = end + 2; if (options.locations) { lineBreak.lastIndex = start; var match; while ((match = lineBreak.exec(input)) && match.index < tokPos) { ++tokCurLine; tokLineStart = match.index + match[0].length; } } if (!dontTrack) { if (options.onComment) options.onComment(true, input.slice(start + 2, end), start, tokPos, startLoc, options.locations && new line_loc_t); if (options.trackComments) (tokComments || (tokComments = [])).push(input.slice(lastIsNewlinePos != null && options.trackCommentsIncludeLineBreak ? lastIsNewlinePos : start, tokPos)); } } function skipLineComment(lastIsNewlinePos, dontTrack) { var start = tokPos; var startLoc = options.onComment && options.locations && new line_loc_t; var ch = input.charCodeAt(tokPos+=2); while (tokPos < inputLen && ch !== 10 && ch !== 13 && ch !== 8232 && ch !== 8233) { ++tokPos; ch = input.charCodeAt(tokPos); } if (!dontTrack) { if (options.onComment) options.onComment(false, input.slice(start + 2, tokPos), start, tokPos, startLoc, options.locations && new line_loc_t); if (options.trackComments) (tokComments || (tokComments = [])).push(input.slice(lastIsNewlinePos != null && options.trackCommentsIncludeLineBreak ? lastIsNewlinePos : start, tokPos)); } } function preprocesSkipRestOfLine() { var ch = input.charCodeAt(tokPos); var last; // If the last none whitespace character is a '\' the line will continue on the the next line. // Here we break the way gcc works as it joins the lines first and then tokenize it. Because of // this we can't have a newline in the middle of a word. while (tokPos < inputLen && ((ch !== 10 && ch !== 13 && ch !== 8232 && ch !== 8233) || last === 92)) { // White space and '\' if (ch != 32 && ch != 9 && ch != 160 && (ch < 5760 || !nonASCIIwhitespaceNoNewLine.test(String.fromCharCode(ch)))) last = ch; ch = input.charCodeAt(++tokPos); } } // Called at the start of the parse and after every token. Skips // whitespace and comments, and, if `options.trackComments` is on, // will store all skipped comments in `tokComments`. If // `options.trackSpaces` is on, will store the last skipped spaces in // `tokSpaces`. // Returns the char code of the first none whitespace or comment function skipSpace() { tokComments = null; tokSpaces = null; return onlySkipSpace(); } // Returns the char code of the first none whitespace or comment function onlySkipSpace(dontSkipEOL, dontSkipMacroBoundary, dontSkipComments) { var spaceStart = tokPos, lastIsNewlinePos, ch; for(;;) { ch = input.charCodeAt(tokPos); if (ch === 32) { // ' ' ++tokPos; } else if (ch === 13) { if (dontSkipEOL) break; lastIsNewlinePos = tokPos; ++tokPos; var next = input.charCodeAt(tokPos); if (next === 10) { ++tokPos; } if (options.locations) { ++tokCurLine; tokLineStart = tokPos; } } else if (ch === 10) { if (dontSkipEOL) break; lastIsNewlinePos = tokPos; ++tokPos; if (options.locations) { ++tokCurLine; tokLineStart = tokPos; } } else if (ch === 9) { ++tokPos; } else if (ch === 47) { // '/' if (dontSkipComments) break; var next = input.charCodeAt(tokPos+1); if (next === 42) { // '*' if (options.trackSpaces) (tokSpaces || (tokSpaces = [])).push(input.slice(spaceStart, tokPos)); skipBlockComment(lastIsNewlinePos); spaceStart = tokPos; } else if (next === 47) { // '/' if (options.trackSpaces) (tokSpaces || (tokSpaces = [])).push(input.slice(spaceStart, tokPos)); skipLineComment(lastIsNewlinePos); spaceStart = tokPos; } else break; } else if (ch === 160 || ch === 11 || ch === 12 || (ch >= 5760 && nonASCIIwhitespace.test(String.fromCharCode(ch)))) { // '\xa0', VT, FF, Unicode whitespaces ++tokPos; } else if (tokPos >= inputLen) { if (options.preprocess) { if (dontSkipMacroBoundary) return true; if (!preprocessStack.length) break; // If this is the first end of file after the token save to position to allow a semicolon to be inserted // the end of file, if needed. if (firstEndOfFile == null) firstEndOfFile = tokPos; // If we are at the end of the input inside a macro continue at last position var lastItem = preprocessStack.pop(); tokPos = lastItem.end; input = lastItem.input; inputLen = lastItem.inputLen; tokCurLine = lastItem.currentLine; tokLineStart = lastItem.currentLineStart; preprocessOnlyTransformArgumentsForLastToken = lastItem.onlyTransformArgumentsForLastToken; preprocessParameterScope = lastItem.parameterScope; tokPosMacroOffset = lastItem.macroOffset; sourceFile = lastItem.sourceFile; firstTokEnd = lastItem.lastEnd; // Set the last item var lastIndex = preprocessStack.length; preprocessStackLastItem = lastIndex ? preprocessStack[lastIndex - 1] : null; return onlySkipSpace(dontSkipEOL); } else { break; } } else if (ch === 92) { // '\' if (!options.preprocess) break; // Check if we have an escaped newline. We are using a relaxed treatment of escaped newlines like gcc. // We allow spaces, horizontal and vertical tabs, and form feeds between the backslash and the subsequent newline var pos = tokPos + 1; ch = input.charCodeAt(pos); while (pos < inputLen && (ch === 32 || ch === 9 || ch === 11 || ch === 12 || (ch >= 5760 && nonASCIIwhitespaceNoNewLine.test(String.fromCharCode(ch))))) ch = input.charCodeAt(++pos); lineBreak.lastIndex = 0; var match = lineBreak.exec(input.slice(pos, pos + 2)); if (match && match.index === 0) { tokPos = pos + match[0].length; if (options.locations) { ++tokCurLine; tokLineStart = tokPos; } } else { break; } } else { break; } } return ch; } // ### Token reading // This is the function that is called to fetch the next token. It // is somewhat obscure, because it works in character codes rather // than characters, and because operator parsing has been inlined // into it. // // All in the name of speed. // // The `forceRegexp` parameter is used in the one case where the // `tokRegexpAllowed` trick does not work. See `parseStatement`. function readToken_dot(code, finisher) { var next = input.charCodeAt(tokPos+1); if (next >= 48 && next <= 57) return readNumber(String.fromCharCode(code), finisher); if (next === 46 && options.objj && input.charCodeAt(tokPos+2) === 46) { //'.' tokPos += 3; return finisher(_dotdotdot); } ++tokPos; return finisher(_dot); } function readToken_slash(finisher) { // '/' var next = input.charCodeAt(tokPos+1); if (tokRegexpAllowed) {++tokPos; return readRegexp();} if (next === 61) return finishOp(_assign, 2, finisher); return finishOp(_slash, 1, finisher); } function readToken_mult_modulo(finisher) { // '%*' var next = input.charCodeAt(tokPos+1); if (next === 61) return finishOp(_assign, 2, finisher); return finishOp(_bin10, 1, finisher); } function readToken_pipe_amp(code, finisher) { // '|&' var next = input.charCodeAt(tokPos+1); if (next === code) return finishOp(code === 124 ? _bin1 : _bin2, 2, finisher); if (next === 61) return finishOp(_assign, 2, finisher); return finishOp(code === 124 ? _bin3 : _bin5, 1, finisher); } function readToken_caret(finisher) { // '^' var next = input.charCodeAt(tokPos+1); if (next === 61) return finishOp(_assign, 2, finisher); return finishOp(_bin4, 1, finisher); } function readToken_plus_min(code, finisher) { // '+-' var next = input.charCodeAt(tokPos+1); if (next === code) return finishOp(_incdec, 2, finisher); if (next === 61) return finishOp(_assign, 2, finisher); return finishOp(_plusmin, 1, finisher); } function readToken_lt_gt(code, finisher) { // '<>' if (code === 60 && (tokType === _import || preTokType === _preInclude) && options.objj) { // '<' for (var start = tokPos + 1;;) { var ch = input.charCodeAt(++tokPos); if (ch === 62) // '>' return finisher(_filename, input.slice(start, tokPos++)); if (tokPos >= inputLen || ch === 13 || ch === 10 || ch === 8232 || ch === 8233) raise(tokStart, "Unterminated import statement"); } } var next = input.charCodeAt(tokPos+1); var size = 1; if (next === code) { size = code === 62 && input.charCodeAt(tokPos+2) === 62 ? 3 : 2; if (input.charCodeAt(tokPos + size) === 61) return finishOp(_assign, size + 1, finisher); return finishOp(_bin8, size, finisher); } if (next === 61) size = input.charCodeAt(tokPos+2) === 61 ? 3 : 2; return finishOp(_bin7, size, finisher); } function readToken_eq_excl(code, finisher) { // '=!' var next = input.charCodeAt(tokPos+1); if (next === 61) return finishOp(_bin6, input.charCodeAt(tokPos+2) === 61 ? 3 : 2, finisher); return finishOp(code === 61 ? _eq : _prefix, 1, finisher); } function readToken_at(code, finisher) { // '@' var next = input.charCodeAt(++tokPos); if (next === 34 || next === 39) // Read string if "'" or '"' return readString(next, finisher); if (next === 123) // Read dictionary literal if "{" return finisher(_dictionaryLiteral); if (next === 91) // Read array literal if "[" return finisher(_arrayLiteral); var word = readWord1(), token = objJAtKeywordTypes[word]; if (!token) raise(tokStart, "Unrecognized Objective-J keyword '@" + word + "'"); return finisher(token); } function readToken_preprocess(finisher) { // '#' ++tokPos; preprocessSkipSpace(); preprocessReadToken(false, true); // Dont track and it is a preprocessToken switch (preTokType) { case _preDefine: if (preNotSkipping) { preprocessParseDefine(); } else { return finisher(_preDefine); } break; case _preUndef: preprocessReadToken(); options.preprocessUndefineMacro(preprocessGetIdent()); break; case _preIf: if (preNotSkipping) { // We dont't allow regex when parsing preprocess expression var saveTokRegexpAllowed = tokRegexpAllowed; tokRegexpAllowed = false; preIfLevel.push(_preIf); preprocessReadToken(false, false, true); var expr = preprocessParseExpression(true); // Process macros var test = preprocessEvalExpression(expr); if (!test) { preNotSkipping = false; preprocessSkipToElseOrEndif(); } tokRegexpAllowed = saveTokRegexpAllowed; } else { return finisher(_preIf); } break; case _preIfdef: if (preNotSkipping) { preIfLevel.push(_preIf); preprocessReadToken(); var ident = preprocessGetIdent(); var test = options.preprocessIsMacro(ident); if (!test) { preNotSkipping = false preprocessSkipToElseOrEndif(); } } else { return finisher(_preIfdef); } break; case _preIfndef: if (preNotSkipping) { preIfLevel.push(_preIf); preprocessReadToken(); var ident = preprocessGetIdent(); var test = options.preprocessIsMacro(ident); if (test) { preNotSkipping = false preprocessSkipToElseOrEndif(); } } else { //preprocesSkipRestOfLine(); return finisher(_preIfndef); } break; case _preElse: if (preIfLevel.length) { if (preNotSkipping) { if(preIfLevel[preIfLevel.length - 1] === _preIf) { preIfLevel[preIfLevel.length - 1] = _preElse; preNotSkipping = false; finisher(_preElse); preprocessReadToken(); preprocessSkipToElseOrEndif(true); // no else } else raise(preTokStart, "#else after #else"); } else { preIfLevel[preIfLevel.length - 1] = _preElse; return finisher(_preElse); } } else raise(preTokStart, "#else without #if"); break; case _preElseIf: if (preIfLevel.length) { if (preNotSkipping) { if(preIfLevel[preIfLevel.length - 1] === _preIf) { preNotSkipping = false; finisher(_preElseIf); preprocessReadToken(); preprocessSkipToElseOrEndif(true); // no else } else raise(preTokStart, "#elsif after #else"); } else { // We dont't allow regex when parsing preprocess expression var saveTokRegexpAllowed = tokRegexpAllowed; tokRegexpAllowed = false; preNotSkipping = true; preprocessReadToken(false, false, true); var expr = preprocessParseExpression(true); preNotSkipping = false; tokRegexpAllowed = saveTokRegexpAllowed; var test = preprocessEvalExpression(expr); return finisher(test ? _preElseIfTrue : _preElseIfFalse); } } else raise(preTokStart, "#elif without #if"); break; case _preEndif: if (preIfLevel.length) { if (preNotSkipping) { preIfLevel.pop(); break; } } else { raise(preTokStart, "#endif without #if"); } return finisher(_preEndif); break; case _prePragma: preprocesSkipRestOfLine(); break; case _prefix: preprocesSkipRestOfLine(); break; case _preWarning: preprocessReadToken(false, false, true); var expr = preprocessParseExpression(); console.log("Warning: " + String(preprocessEvalExpression(expr))); break; case _preError: var start = preTokStart; preprocessReadToken(false, false, true); var expr = preprocessParseExpression(); raise(start, "Error: " + String(preprocessEvalExpression(expr))); break; case _preInclude: if (!preNotSkipping) { return finisher(_preInclude); } preprocessReadToken(); if (preTokType === _string) var localfilepath = true; else if (preTokType ===_filename) var localfilepath = false; else raise(preTokStart, "Expected \"FILENAME\" or : " + (preTokType.keyword || preTokType.type)); var theFileName = preTokVal; var includeDict = options.preprocessGetIncludeFile(preTokVal, localfilepath) || raise(preTokStart, "'" + theFileName + "' file not found"); var includeString = includeDict.include; var includeMacro = new Macro(null, includeString, null, 0, false, null, false, null, includeDict.sourceFile); preprocessFinishToken(_preprocess, null, null, true); // skipEOL pushMacroToStack(includeMacro, includeMacro.macro, tokPosMacroOffset, null, null, tokPos, null, true); // isIncludeFile skipSpace(); readToken(null, null, true); // Stealth return; break; default: if (preprocessStackLastItem) { // If the current macro has parameters check if this word is one of them and should be stringifyed if (preprocessStackLastItem.parameterDict && preprocessStackLastItem.macro.isParameterFunction()(preTokVal)) { var macro = preprocessStackLastItem.parameterDict[preTokVal]; if (macro) { return finishToken(_string, macro.macro); } } } raise(preTokStart, "Invalid preprocessing directive"); preprocesSkipRestOfLine(); // Return the complete line as a token to make it possible to create a PreProcessStatement if we are between two statements return finisher(_preprocess); //raise(tokPos, "Invalid preprocessing directive '" + (preTokType.keyword || preTokVal) + "' " + input.slice(tokStart, tokPos)); } // Drop the regular token as this was a preprocess token and then read next token //tokPos = preTokStart; if (preTokType === _eol && options.trackSpaces) { if (tokSpaces && tokSpaces.length) tokSpaces.push("\n" + tokSpaces.pop()); else tokSpaces = ["\n"]; } preprocessFinishToken(preTokType, null, null, true); // skipEOL return next(true) // Stealth } function preprocessParseDefine() { preprocessIsParsingPreprocess = true; preprocessReadToken(); var macroIdentifierEnd = preTokEnd; // We don't want to concatenate tokens when creating macros preprocessDontConcatenate = true; // Get position offset now as ´tokCurLine´ and ´tokLineStart´ points to next token. var positionOffset = options.locations && new PositionOffset(tokCurLine, tokLineStart); var macroIdentifier = preprocessGetIdent(); // '(' Must follow directly after identifier to be a valid macro with parameters if (input.charCodeAt(macroIdentifierEnd) === 40) { // '(' preprocessExpect(_parenL); var parameters = []; var variadic = false; var first = true; while (!preprocessEat(_parenR)) { if (variadic) raise(preTokStart, "Variadic parameter must be last"); if (!first) preprocessExpect(_comma, "Expected ',' between macro parameters"); else first = false; parameters.push(preprocessEat(_dotdotdot) ? variadic = true && "__VA_ARGS__" : preprocessGetIdent()); if (preprocessEat(_dotdotdot)) variadic = true; // Get a new position offset as macro has parameters. This is needed if line has escaped (backslash) newline positionOffset = options.locations && new PositionOffset(tokCurLine, tokLineStart); } } var start = preTokStart; while(preTokType !== _eol && preTokType !== _eof) preprocessReadToken(); preprocessDontConcatenate = false; var macroString = preTokInput.slice(start, preTokStart); macroString = macroString.replace(/\\/g, " "); // If variadic get the last parameter for the variadic parameter name options.preprocessAddMacro(new Macro(macroIdentifier, macroString, parameters, start, false, null, variadic && parameters[parameters.length - 1], positionOffset)); preprocessIsParsingPreprocess = false; } function preprocessEvalExpression(expr) { return walk.recursive(expr, {}, { LogicalExpression: function(node, st, c) { var left = node.left, right = node.right; switch (node.operator) { case "||": return c(left, st) || c(right, st); case "&&": return c(left, st) && c(right, st); } }, BinaryExpression: function(node, st, c) { var left = node.left, right = node.right; switch(node.operator) { case "+": return c(left, st) + c(right, st); case "-": return c(left, st) - c(right, st); case "*": return c(left, st) * c(right, st); case "/": return c(left, st) / c(right, st); case "%": return c(left, st) % c(right, st); case "<": return c(left, st) < c(right, st); case ">": return c(left, st) > c(right, st); case "^": return c(left, st) ^ c(right, st); case "&": return c(left, st) & c(right, st); case "|": return c(left, st) | c(right, st); case "==": return c(left, st) == c(right, st); case "===": return c(left, st) === c(right, st); case "!=": return c(left, st) != c(right, st); case "!==": return c(left, st) !== c(right, st); case "<=": return c(left, st) <= c(right, st); case ">=": return c(left, st) >= c(right, st); case ">>": return c(left, st) >> c(right, st); case ">>>": return c(left, st) >>> c(right, st); case "<<": return c(left, st) << c(right, st); } }, UnaryExpression: function(node, st, c) { var arg = node.argument; switch (node.operator) { case "-": return -c(arg, st); case "+": return +c(arg, st); case "!": return !c(arg, st); case "~": return ~c(arg, st); } }, Literal: function(node, st, c) { return node.value; }, Identifier: function(node, st, c) { // If it is not macro expanded it should be counted as a zero return 0; }, DefinedExpression: function(node, st, c) { var objectNode = node.object; if (objectNode.type === "Identifier") { // If the macro has parameters it will not expand and we have to check here if it exists var name = objectNode.name, macro = options.preprocessGetMacro(name) || preprocessBuiltinMacro(name); return macro || 0; } else { return c(objectNode, st); } } }, {}); } function getTokenFromCode(code, finisher, allowEndOfLineToken) { switch(code) { // The interpretation of a dot depends on whether it is followed // by a digit. case 46: // '.' return readToken_dot(code, finisher); // Punctuation tokens. case 40: ++tokPos; return finisher(_parenL); case 41: ++tokPos; return finisher(_parenR); case 59: ++tokPos; return finisher(_semi); case 44: ++tokPos; return finisher(_comma); case 91: ++tokPos; return finisher(_bracketL); case 93: ++tokPos; return finisher(_bracketR); case 123: ++tokPos; return finisher(_braceL); case 125: ++tokPos; return finisher(_braceR); case 58: ++tokPos; return finisher(_colon); case 63: ++tokPos; return finisher(_question); // '0x' is a hexadecimal number. case 48: // '0' var next = input.charCodeAt(tokPos+1); if (next === 120 || next === 88) return readHexNumber(finisher); // Anything else beginning with a digit is an integer, octal // number, or float. case 49: case 50: case 51: case 52: case 53: case 54: case 55: case 56: case 57: // 1-9 return readNumber(false, finisher); // Quotes produce strings. case 34: case 39: // '"', "'" return readString(code, finisher); // Operators are parsed inline in tiny state machines. '=' (61) is // often referred to. `finishOp` simply skips the amount of // characters it is given as second argument, and returns a token // of the type given by its first argument. case 47: // '/' return readToken_slash(finisher); case 37: case 42: // '%*' return readToken_mult_modulo(finisher); case 124: case 38: // '|&' return readToken_pipe_amp(code, finisher); case 94: // '^' return readToken_caret(finisher); case 43: case 45: // '+-' return readToken_plus_min(code, finisher); case 60: case 62: // '<>' return readToken_lt_gt(code, finisher); case 61: case 33: // '=!' return readToken_eq_excl(code, finisher); case 126: // '~' return finishOp(_prefix, 1, finisher); case 64: // '@' if (options.objj) return readToken_at(code, finisher); return false; case 35: // '#' if (options.preprocess) { if (preprocessIsParsingPreprocess) { ++tokPos; return finisher(_preprocess); } // Check if it is the first token on the line lineBreak.lastIndex = 0; var match = lineBreak.exec(input.slice(localLastEnd, tokPos)); if (lastEnd !== 0 && lastEnd !== tokPos && !match && ((preprocessStackLastItem && !preprocessStackLastItem.isIncludeFile) || tokPos !== 0)) { if (preprocessStackLastItem) { // Stringify next token return preprocessStringify(); } else { raise(tokPos, "Preprocessor directives may only be used at the beginning of a line"); } } return readToken_preprocess(finisher); } return false; case 92: // '\' if (options.preprocess) { return finishOp(_preBackslash, 1, finisher); } return false; } if (allowEndOfLineToken) { if (code === 13 || code === 10 || code === 8232 || code === 8233) { if (options.locations) { ++tokCurLine; tokLineStart = tokPos; } return finishOp(_eol, (code === 13 && input.charCodeAt(tokPos+1) === 10) ? 2 : 1, finisher); } } return false; } // Stringify next token and return with it as a literal string. function preprocessStringify() { var saveStackLength = preprocessStack.length, saveLastItem = preprocessStackLastItem; tokPos++; // Skip '#' preConcatenating = true; // To get empty sting if macro is empty next(false, 2); // Don't prescan arguments preConcatenating = false; var start = tokStart + tokMacroOffset; var positionOffset = options.locations && new PositionOffset(tokCurLine, tokLineStart); var string; if (tokType === _string) { var quote = tokInput.slice(tokStart, tokStart + 1); var escapedQuote = quote === '"' ? '\\"' : "'"; string = escapedQuote; string += preprocessStringifyEscape(tokVal); string += escapedQuote; } else { string = tokVal != null ? tokVal : tokType.keyword || tokType.type; } while (preprocessStack.length > saveStackLength && saveLastItem === preprocessStack[saveStackLength - 1]) { preConcatenating = true; // To get empty sting if macro is empty next(false, 2); // Don't prescan arguments preConcatenating = false; // Add a space if there is one or more withespaces if (lastEnd !== tokStart) string += " "; if (tokType === _string) { var quote = tokInput.slice(tokStart, tokStart + 1); var escapedQuote = quote === '"' ? '\\"' : "'"; string += escapedQuote; string += preprocessStringifyEscape(tokVal); string += escapedQuote; } else { string += tokVal != null ? tokVal : tokType.keyword || tokType.type; } } var stringifyMacro = new Macro(null, '"' + string + '"', null, start, false, null, false, positionOffset); return readTokenFromMacro(stringifyMacro, tokPosMacroOffset, null, null, tokPos, next); } // Escape characters in stringify string. function preprocessStringifyEscape(aString) { for (var escaped = "", pos = 0, size = aString.length, ch = aString.charCodeAt(pos); pos < size; ch = aString.charCodeAt(++pos)) { switch (ch) { case 34: escaped += '\\\\\\"'; break; // " case 10: escaped += "\\\\n"; break; // LF (\n) case 13: escaped += "\\\\r"; break; // CR (\r) case 9: escaped += "\\\\t"; break; // TAB (\t) case 8: escaped += "\\\\b"; break; // BS (\b) case 11: escaped += "\\\\v"; break; // VT (\v) case 0x00A0: escaped += "\\\\u00A0"; break; // CR (\r) case 0x2028: escaped += "\\\\u2028"; break; // LINE SEPARATOR case 0x2029: escaped += "\\\\u2029"; break; // PARAGRAPH SEPARATOR case 92: escaped += "\\\\"; break; // BACKSLASH default: escaped += aString.charAt(pos); break; } } return escaped; } // Skip whitespaces sometimes without line breaks // Returns true if it stops at a line break. function preprocessSkipSpace(dontSkipComments, skipEOL) { var ch = onlySkipSpace(!skipEOL, false, dontSkipComments); // Can't see that this line break test is used anymore //lineBreak.lastIndex = 0; //var match = lineBreak.exec(input.slice(tokPos, tokPos + 2)); //return (match && match.index === 0); return ch; } function preprocessSkipToElseOrEndif(skipElse) { var ifLevel = []; while (ifLevel.length > 0 || (preTokType !== _preEndif && ((preTokType !== _preElse && preTokType !== _preElseIfTrue) || skipElse))) { switch (preTokType) { case _preIf: case _preIfdef: case _preIfndef: ifLevel.push(_preIf); break; case _preElse: if (ifLevel[ifLevel.length - 1] !== _preIf) raise(preTokStart, "#else after #else"); else ifLevel[ifLevel.length - 1] = _preElse; break; case _preElseIf: if (ifLevel[ifLevel.length - 1] !== _preIf) raise(preTokStart, "#elif after #else"); break; case _preEndif: ifLevel.pop(); break; case _eof: preNotSkipping = true; raise(preTokStart, "Missing #endif"); } preprocessReadToken(true); } preNotSkipping = true; if (preTokType === _preEndif) preIfLevel.pop(); } // preprocessToken is used to cancel preNotSkipping when calling from readToken_preprocess. // FIXME: Refactor to not use this parameter preprocessToken. It is kind of confusing and it should be possible to do in another way function preprocessReadToken(skipComments, preprocessToken, processMacros, onlyTransformMacroArguments) { preTokStart = tokPos; preTokInput = input; preTokParameterScope = preprocessParameterScope; if (tokPos >= inputLen) return preprocessFinishToken(_eof); var code = input.charCodeAt(tokPos); if (!preprocessToken && !preNotSkipping && code !== 35) { // '#' // If we are skipping take the whole line if the token does not start with '#' (preprocess tokens) preprocesSkipRestOfLine(); preprocessFinishToken(_preprocessSkipLine, input.slice(preTokStart, tokPos)); preprocessSkipSpace(true, true); // Don't skip comments and skip EOL return; } else if (preprocessMacroParameterListMode && code !== 41 && code !== 44) { // ')', ',' var parenLevel = 0; // If we are parsing a macro parameter list parentheses within each argument must balance while(tokPos < inputLen && (parenLevel || (code !== 41 && code !== 44))) { // ')', ',' if (code === 40) // '(' parenLevel++; if (code === 41) // ')' parenLevel--; if (code === 34 || code === 39) {// '"' "'" We have a quote so go all the way to the end of the quote var quote = code; code = input.charCodeAt(++tokPos); while(tokPos < inputLen && code !== quote) { if (code === 92) { // '\' code = input.charCodeAt(++tokPos); if (code !== quote) continue; } code = input.charCodeAt(++tokPos); } } code = input.charCodeAt(++tokPos); } return preprocessFinishToken(_preprocessParamItem, input.slice(preTokStart, tokPos)); } if (isIdentifierStart(code) || (code === 92 /* '\' */ && input.charCodeAt(tokPos +1) === 117 /* 'u' */)) return preprocessReadWord(processMacros); if (getTokenFromCode(code, skipComments ? preprocessFinishTokenSkipComments : preprocessFinishToken, true) === false) { // Allow _eol token // If we are here, we either found a non-ASCII identifier // character, or something that's entirely disallowed. var ch = String.fromCharCode(code); if (ch === "\\" || nonASCIIidentifierStart.test(ch)) return preprocessReadWord(processMacros); raise(tokPos, "Unexpected character '" + ch + "'"); } } function preprocessReadWord(processMacros, onlyTransformMacroArguments) { var word = readWord1(); var type = _name; if (processMacros && options.preprocess) { var readMacroWordReturn = readMacroWord(word, preprocessNext, onlyTransformMacroArguments); if (readMacroWordReturn === true) return true; } if (!containsEsc && isKeywordPreprocessor(word)) type = keywordTypesPreprocessor[word]; preprocessFinishToken(type, word, readMacroWordReturn, false, processMacros); // If readMacroWord returns anything except 'true' it is the real tokEndPos } function preprocessFinishToken(type, val, overrideTokEnd, skipEOL, processMacros) { preTokType = type; preTokVal = val; preTokEnd = overrideTokEnd || tokPos; if (type !== _eol) firstTokEnd = preTokEnd; //tokRegexpAllowed = type.beforeExpr; var ch = preprocessSkipSpace(false, skipEOL); // Skip comments if (ch === 35 && options.preprocess && !preprocessDontConcatenate && input.charCodeAt(tokPos + 1) === 35) { // '##' var val1 = val != null ? val : type.keyword || type.type; tokPos += 2; if (val1 != null) { // Save current line and current line start. This is needed when option.locations is true var positionOffset = options.locations && new PositionOffset(tokCurLine, tokLineStart); // Save positions on first token to get start and end correct on node if cancatenated token is invalid var saveTokInput = tokInput, saveTokEnd = preTokEnd, saveTokStart = preTokStart, start = preTokStart + tokMacroOffset, variadicName = preprocessStackLastItem && preprocessStackLastItem.macro && preprocessStackLastItem.macro.variadicName; skipSpace(); if (variadicName && variadicName === input.slice(tokPos, tokPos + variadicName.length)) var isVariadic = true; preConcatenating = true; preprocessReadToken(null, null, processMacros, 2); // 2 = Don't transform macros only arguments preConcatenating = false; var val2 = preTokVal != null ? preTokVal : preTokType.keyword || preTokType.type; if (val2 != null) { // Skip token if it is a ',' concatenated with an empty variadic parameter if (isVariadic && val1 === "," && val2 === "") return preprocessReadToken(); var concat = "" + val1 + val2, val2TokStart = preTokStart + tokPosMacroOffset; // If the macro defines anything add it to the preprocess input stack var concatMacro = new Macro(null, concat, null, start, false, null, false, positionOffset); var r = readTokenFromMacro(concatMacro, tokPosMacroOffset, preprocessStackLastItem ? preprocessStackLastItem.parameterDict : null, null, tokPos, preprocessNext, null); // Consumed the whole macro in one bite? If not the tokenizer can't create a single token from the two concatenated tokens if (preprocessStackLastItem && preprocessStackLastItem.macro === concatMacro) { // FIXME: Should change this to 'preTokType' and friends preTokType = type; preTokStart = saveTokStart; preTokEnd = saveTokEnd; tokInput = saveTokInput; tokPosMacroOffset = val2TokStart - val1.length; // reset the macro offset to the second token to get start and end correct on node if (!isVariadic) /*raise(tokStart,*/console.log("Warning: pasting formed '" + concat + "', an invalid preprocessing token"); } else return r; } } } } // FIXME: Find out if this is really used? function preprocessFinishTokenSkipComments(type, val) { preTokType = type; preTokVal = val; firstTokEnd = preTokEnd = tokPos; preprocessSkipSpace(true); // 'true' for don't skip comments } // Continue to the next token. function preprocessNext(stealth, onlyTransformArguments, forceRegexp, processMacros) { if (!stealth) { preLastStart = preTokStart; preLastEnd = preTokEnd; } localLastEnd = firstTokEnd; return preprocessReadToken(false, false, processMacros, onlyTransformArguments); } // Predicate that tests whether the next token is of the given // type, and if yes, consumes it as a side effect. function preprocessEat(type, processMacros) { if (preTokType === type) { preprocessNext(false, false, null, processMacros); return true; } } // Expect a token of a given type. If found, consume it, otherwise, // raise with errorMessage or an unexpected token error. function preprocessExpect(type, errorMessage, processMacros) { if (preTokType === type) preprocessNext(false, undefined, null, processMacros); else raise(preTokStart, errorMessage || "Unexpected token"); } function debug() { // debugger; } function preprocessGetIdent(processMacros) { var ident = preTokType === _name ? preTokVal : ((!options.forbidReserved || preTokType.okAsIdent) && preTokType.keyword) || debug(); //raise(preTokStart, "Expected Macro identifier"); //tokRegexpAllowed = false; preprocessNext(false, false, null, processMacros); return ident; } function preprocessParseIdent(processMacros) { var node = startNode(); node.name = preprocessGetIdent(processMacros); return preprocessFinishNode(node, "Identifier"); } // Parse an expression — either a single token that is an // expression, an expression started by a keyword like `defined`, // or an expression wrapped in punctuation like `()`. // When `processMacros` is true any macros will we transformed to its definition function preprocessParseExpression(processMacros) { return preprocessParseExprOps(processMacros); } // Start the precedence parser. function preprocessParseExprOps(processMacros) { return preprocessParseExprOp(preprocessParseMaybeUnary(processMacros), -1, processMacros); } // Parse binary operators with the operator precedence parsing // algorithm. `left` is the left-hand side of the operator. // `minPrec` provides context that allows the function to stop and // defer further parser to one of its callers when it encounters an // operator that has a lower precedence than the set it is parsing. function preprocessParseExprOp(left, minPrec, processMacros) { var prec = preTokType.binop; if (prec) { if (!preTokType.preprocess) raise(preTokStart, "Unsupported macro operator"); if (prec > minPrec) { var node = startNodeFrom(left); node.left = left; node.operator = preTokVal; preprocessNext(false, false, null, processMacros); node.right = preprocessParseExprOp(preprocessParseMaybeUnary(processMacros), prec, processMacros); var node = preprocessFinishNode(node, /&&|\|\|/.test(node.operator) ? "LogicalExpression" : "BinaryExpression"); return preprocessParseExprOp(node, minPrec, processMacros); } } return left; } // Parse an unary expression if possible function preprocessParseMaybeUnary(processMacros) { if (preTokType.preprocess && preTokType.prefix) { var node = startNode(); node.operator = preTokVal; node.prefix = true; preprocessNext(false, false, null, processMacros); node.argument = preprocessParseMaybeUnary(processMacros); return preprocessFinishNode(node, "UnaryExpression"); } return preprocessParseExprAtom(processMacros); } // Parse an atomic macro expression — either a single token that is an // expression, an expression started by a keyword like `defined`, // or an expression wrapped in punctuation like `()`. function preprocessParseExprAtom(processMacros) { switch (preTokType) { case _name: return preprocessParseIdent(processMacros); case _num: case _string: return preprocessParseStringNumLiteral(processMacros); case _parenL: var tokStart1 = preTokStart; preprocessNext(false, false, null, processMacros); var val = preprocessParseExpression(processMacros); val.start = tokStart1; val.end = preTokEnd; preprocessExpect(_parenR, "Expected closing ')' in macro expression", processMacros); return val; case _preDefined: var node = startNode(); preprocessNext(false, false, null, processMacros); node.object = preprocessParseDefinedExpression(processMacros); return preprocessFinishNode(node, "DefinedExpression"); default: unexpected(); } } // Parse an 'Defined' macro expression — either a single token that is an // identifier, number, string or an expression wrapped in punctuation like `()`. function preprocessParseDefinedExpression(processMacros) { switch (preTokType) { case _name: return preprocessParseIdent(processMacros); case _num: case _string: return preprocessParseStringNumLiteral(processMacros); case _parenL: var tokStart1 = preTokStart; preprocessNext(false, false, null, processMacros); var val = preprocessParseDefinedExpression(processMacros); val.start = tokStart1; val.end = preTokEnd; preprocessExpect(_parenR, "Expected closing ')' in macro expression", processMacros); return val; default: unexpected(); } } function preprocessParseStringNumLiteral(processMacros) { var node = startNode(); node.value = preTokVal; node.raw = preTokInput.slice(preTokStart, preTokEnd); preprocessNext(false, false, null, processMacros); return preprocessFinishNode(node, "Literal"); } function preprocessFinishNode(node, type) { node.type = type; node.end = preLastEnd; return node; } function readToken(forceRegexp, onlyTransformMacroArguments, stealth) { tokCommentsBefore = tokComments; tokSpacesBefore = tokSpaces; if (!forceRegexp) tokStart = tokPos; else tokPos = tokStart + 1; if (!stealth) { tokFirstStart = tokStart; //tokFirstInput = input; } tokInput = input; tokMacroOffset = tokPosMacroOffset; preTokParameterScope = preprocessParameterScope; if (options.locations) tokStartLoc = new line_loc_t; if (forceRegexp) return readRegexp(); if (tokPos >= inputLen) return finishToken(_eof); var code = input.charCodeAt(tokPos); // Identifier or keyword. '\uXXXX' sequences are allowed in // identifiers, so '\' also dispatches to that. if (isIdentifierStart(code) || code === 92 /* '\' */) return readWord(null, onlyTransformMacroArguments, forceRegexp); var tok = getTokenFromCode(code, finishToken); if (tok === false) { // If we are here, we either found a non-ASCII identifier // character, or something that's entirely disallowed. var ch = String.fromCharCode(code); if (ch === "\\" || nonASCIIidentifierStart.test(ch)) return readWord(null, onlyTransformMacroArguments, forceRegexp); raise(tokPos, "Unexpected character '" + ch + "'"); } return tok; } function finishOp(type, size, finisher) { var str = input.slice(tokPos, tokPos + size); tokPos += size; finisher(type, str); } // Parse a regular expression. Some context-awareness is necessary, // since a '/' inside a '[]' set does not end the expression. function readRegexp() { var content = "", escaped, inClass, start = tokPos; for (;;) { if (tokPos >= inputLen) raise(start, "Unterminated regular expression"); var ch = input.charAt(tokPos); if (newline.test(ch)) raise(start, "Unterminated regular expression"); if (!escaped) { if (ch === "[") inClass = true; else if (ch === "]" && inClass) inClass = false; else if (ch === "/" && !inClass) break; escaped = ch === "\\"; } else escaped = false; ++tokPos; } var content = input.slice(start, tokPos); ++tokPos; // Need to use `readWord1` because '\uXXXX' sequences are allowed // here (don't ask). var mods = readWord1(); if (mods && !/^[gmsiy]*$/.test(mods)) raise(start, "Invalid regexp flag"); return finishToken(_regexp, new RegExp(content, mods)); } // Read an integer in the given radix. Return null if zero digits // were read, the integer value otherwise. When `len` is given, this // will return `null` unless the integer has exactly `len` digits. function readInt(radix, len) { var start = tokPos, total = 0; for (var i = 0, e = len == null ? Infinity : len; i < e; ++i) { var code = input.charCodeAt(tokPos), val; if (code >= 97) val = code - 97 + 10; // a else if (code >= 65) val = code - 65 + 10; // A else if (code >= 48 && code <= 57) val = code - 48; // 0-9 else val = Infinity; if (val >= radix) break; ++tokPos; total = total * radix + val; } if (tokPos === start || len != null && tokPos - start !== len) return null; return total; } function readHexNumber(finisher) { tokPos += 2; // 0x var val = readInt(16); if (val == null) raise(tokStart + 2, "Expected hexadecimal number"); if (isIdentifierStart(input.charCodeAt(tokPos))) raise(tokPos, "Identifier directly after number"); return finisher(_num, val); } // Read an integer, octal integer, or floating-point number. function readNumber(startsWithDot, finisher) { var start = tokPos, isFloat = false, octal = input.charCodeAt(tokPos) === 48; if (!startsWithDot && readInt(10) === null) raise(start, "Invalid number"); if (input.charCodeAt(tokPos) === 46) { ++tokPos; readInt(10); isFloat = true; } var next = input.charCodeAt(tokPos); if (next === 69 || next === 101) { // 'eE' next = input.charCodeAt(++tokPos); if (next === 43 || next === 45) ++tokPos; // '+-' if (readInt(10) === null) raise(start, "Invalid number"); isFloat = true; } if (isIdentifierStart(input.charCodeAt(tokPos))) raise(tokPos, "Identifier directly after number"); var str = input.slice(start, tokPos), val; if (isFloat) val = parseFloat(str); else if (!octal || str.length === 1) val = parseInt(str, 10); else if (/[89]/.test(str) || strict) raise(start, "Invalid number"); else val = parseInt(str, 8); return finisher(_num, val); } // Read a string value, interpreting backslash-escapes. function readString(quote, finisher) { tokPos++; var out = ""; for (;;) { if (tokPos >= inputLen) raise(tokStart, "Unterminated string constant"); var ch = input.charCodeAt(tokPos); if (ch === quote) { ++tokPos; return finisher(_string, out); } if (ch === 92) { // '\' ch = input.charCodeAt(++tokPos); var octal = /^[0-7]+/.exec(input.slice(tokPos, tokPos + 3)); if (octal) octal = octal[0]; while (octal && parseInt(octal, 8) > 255) octal = octal.slice(0, octal.length - 1); if (octal === "0") octal = null; ++tokPos; if (octal) { if (strict) raise(tokPos - 2, "Octal literal in strict mode"); out += String.fromCharCode(parseInt(octal, 8)); tokPos += octal.length - 1; } else { switch (ch) { case 110: out += "\n"; break; // 'n' -> '\n' case 114: out += "\r"; break; // 'r' -> '\r' case 120: out += String.fromCharCode(readHexChar(2)); break; // 'x' case 117: out += String.fromCharCode(readHexChar(4)); break; // 'u' case 85: out += String.fromCharCode(readHexChar(8)); break; // 'U' case 116: out += "\t"; break; // 't' -> '\t' case 98: out += "\b"; break; // 'b' -> '\b' case 118: out += "\u000b"; break; // 'v' -> '\u000b' case 102: out += "\f"; break; // 'f' -> '\f' case 48: out += "\0"; break; // 0 -> '\0' case 13: if (input.charCodeAt(tokPos) === 10) ++tokPos; // '\r\n' case 10: // ' \n' if (options.locations) { tokLineStart = tokPos; ++tokCurLine; } break; default: out += String.fromCharCode(ch); break; } } } else { if (ch === 13 || ch === 10 || ch === 8232 || ch === 8233) raise(tokStart, "Unterminated string constant"); out += String.fromCharCode(ch); // '\' ++tokPos; } } } // Used to read character escape sequences ('\x', '\u', '\U'). function readHexChar(len) { var n = readInt(16, len); if (n === null) raise(tokStart, "Bad character escape sequence"); return n; } // Used to signal to callers of `readWord1` whether the word // contained any escape sequences. This is needed because words with // escape sequences must not be interpreted as keywords. var containsEsc; // Read an identifier, and return it as a string. Sets `containsEsc` // to whether the word contained a '\u' escape. // // Only builds up the word character-by-character when it actually // containeds an escape, as a micro-optimization. function readWord1() { containsEsc = false; var word, first = true, start = tokPos; for (;;) { var ch = input.charCodeAt(tokPos); if (isIdentifierChar(ch)) { if (containsEsc) word += input.charAt(tokPos); ++tokPos; } else if (ch === 92) { // "\" if (!containsEsc) word = input.slice(start, tokPos); containsEsc = true; if (input.charCodeAt(++tokPos) != 117) // "u" raise(tokPos, "Expecting Unicode escape sequence \\uXXXX"); ++tokPos; var esc = readHexChar(4); var escStr = String.fromCharCode(esc); if (!escStr) raise(tokPos - 1, "Invalid Unicode escape"); if (!(first ? isIdentifierStart(esc) : isIdentifierChar(esc))) raise(tokPos - 4, "Invalid Unicode escape"); word += escStr; } else { break; } first = false; } return containsEsc ? word : input.slice(start, tokPos); } // Read an identifier or keyword token. Will check for reserved // words when necessary. Argument preReadWord is used to concatenate // The word is then passed in from caller. function readWord(preReadWord, onlyTransformMacroArguments, forceRegexp) { var word = preReadWord || readWord1(); var type = _name; if (options.preprocess) { var readMacroWordReturn = readMacroWord(word, next, onlyTransformMacroArguments, forceRegexp); if (readMacroWordReturn === true) return true; } if (!containsEsc) { if (isKeyword(word)) type = keywordTypes[word]; else if (options.objj && isKeywordObjJ(word)) type = keywordTypesObjJ[word]; else if (options.forbidReserved && (options.ecmaVersion === 3 ? isReservedWord3 : isReservedWord5)(word) || strict && isStrictReservedWord(word)) raise(tokStart, "The keyword '" + word + "' is reserved"); } return finishToken(type, word, readMacroWordReturn); // If readMacroWord returns anything except 'true' it is the real tokEndPos } // If the word is a macro return true as the token is already finished. If not just return 'undefined'. function readMacroWord(word, nextFinisher, onlyTransformArguments, forceRegexp) { var macro, lastStackItem = preprocessStackLastItem, oldParameterScope = preprocessParameterScope; if (lastStackItem) { var scope = preTokParameterScope || preprocessStackLastItem; // If the current macro has parameters check if this word is one of them and should be translated if (scope.parameterDict && scope.macro.isParameterFunction()(word)) { macro = scope.parameterDict[word]; // If it is a variadic macro and we can't find anything in the variadic parameter just get next token if (!macro && scope.macro.variadicName === word) { // Don't do this if we are stringifying or concatenating as we then want an empty string if (preConcatenating) { finishToken(_name, ""); return true; } else { onlySkipSpace(); nextFinisher(true, onlyTransformArguments, forceRegexp, true); // Stealth and Preprocess macros. } return true; } // Lets look ahead to find out if we find a '##' for token concatenate // We don't want to prescan spaces across macro boundary as the macro stack will fall apart // So we do a special prescan if we have to cross a boundary all in the name of speed if (onlySkipSpace(true, true) === true) { // don't skip EOL and don't skip macro boundary. if (preprocessPrescanFor(35, 35)) // Prescan across boundary for '##' as we crossed a boundary onlyTransformArguments = 2; } else if (input.charCodeAt(tokPos) === 35 && input.charCodeAt(tokPos + 1) === 35) { // '##' onlyTransformArguments = 2; } preprocessParameterScope = macro && macro.parameterScope; onlyTransformArguments--; } } // Does the word match against any of the known macro names // Don't match if: // 1. We already has found a argument macro // 2. We are doing concatenating. Here it is only valid for the last token. if (!macro && (!onlyTransformArguments && !preprocessOnlyTransformArgumentsForLastToken || tokPos < inputLen) && options.preprocessIsMacro(word)) { preprocessParameterScope = null; macro = options.preprocessGetMacro(word); if (macro) { // Check if this macro is already referenced by looking in the stack // Don't do it if the input in the stack is an argument. We want to simulate 'expand arguments first' if (!preprocessStackLastItem || !preprocessStackLastItem.macro.isArgument) { var i = preprocessStack.length, lastMacroItem; while (i > 0) { var item = preprocessStack[--i], macroItem = item.macro; if (macroItem.identifier === word && !(lastMacroItem && lastMacroItem.isArgument)) { macro = null; } lastMacroItem = macroItem; } } } else { macro = preprocessBuiltinMacro(word); } } if (macro) { var parameters; var hasParameters = macro.parameters; var nextIsParenL; if (hasParameters) { // Ok, we should have parameters for the macro. Lets look ahead to find out if we find a '(' // First save current position and loc for tokEndPos var pos = tokPos; var loc; if (options.locations) loc = new line_loc_t; if ((onlySkipSpace(true, true) === true && preprocessPrescanFor(40)) || input.charCodeAt(tokPos) === 40) { // '(' nextIsParenL = true; } else { // We didn't find a '(' so don't transform to the macro. Return the real tokEndPos so we get correct token end values. preprocessOverrideTokEndLoc = loc; return pos; } } if (!hasParameters || nextIsParenL) { // Now we know that we have a matching macro. Get parameters if needed if (nextIsParenL) { var variadicName = macro.variadicName; var first = true; var noParams = 0; parameters = Object.create(null); onlySkipSpace(true); //preprocessReadToken(); //preprocessMacroParameterListMode = true; //preprocessExpect(_parenL); //lastTokPos = tokPos; if (input.charCodeAt(tokPos++) !== 40) raise(tokPos - 1, "Expected '(' before macro prarameters"); onlySkipSpace(true, true, true); var code = input.charCodeAt(tokPos++); while (tokPos < inputLen && code !== 41) { if (first) first = false; else if (code === 44) { // ',' onlySkipSpace(true, true, true); code = input.charCodeAt(tokPos++); } else raise(tokPos - 1, "Expected ',' between macro parameters"); var ident = hasParameters[noParams++]; var variadicAndLastParameter = variadicName && hasParameters.length === noParams; var paramStart = tokPos - 1, parenLevel = 0; // Calculate current line and current line start. var positionOffset = options.locations && new PositionOffset(tokCurLine, tokLineStart); // When parsing a macro parameter list parentheses within each argument must balance // If it is variadic and we are on the last paramter collect all the rest of the parameters while(tokPos < inputLen && (parenLevel || (code !== 41 && (code !== 44 || variadicAndLastParameter)))) { // ')', ',' if (code === 40) // '(' parenLevel++; if (code === 41) // ')' parenLevel--; if (code === 34 || code === 39) {// '"' "'" We have a quote so go all the way to the end of the quote var quote = code; code = input.charCodeAt(tokPos++); while(tokPos < inputLen && code !== quote) { if (code === 92) { // '\' code = input.charCodeAt(tokPos++); if (code !== quote) continue; } code = input.charCodeAt(tokPos++); } } code = input.charCodeAt(tokPos++); } var val = input.slice(paramStart, tokPos - 1); //var val = preTokType === _preprocessParamItem ? preTokVal : ""; parameters[ident] = new Macro(ident, val, null, paramStart + tokMacroOffset, true, preTokParameterScope || preprocessStackLastItem, false, positionOffset); // true = 'Is argument', false = 'Not varadic' } if (code !== 41) raise(tokPos, "Expected ')' after macro prarameters"); onlySkipSpace(true, true); // Don't skip EOL and don't skip macro boundary //preprocessMacroParameterListMode = false; //preprocessExpect(_parenR); } // If the macro defines anything add it to the preprocess input stack return readTokenFromMacro(macro, tokPosMacroOffset, parameters, oldParameterScope, tokPos, nextFinisher, onlyTransformArguments, forceRegexp); } } } // Here we pre scan for first and second character. // The first thing should be to skip spaces and comments // Return true if the first characters after spaces are first and second // This is very simular to the function onlySkipSpace. Maybe the same // function can be used with some refactoring? function preprocessPrescanFor(first, second) { var i = preprocessStack.length; stackloop: while (i-- > 0) { var stackItem = preprocessStack[i], scanPos = stackItem.end, scanInput = stackItem.input, scanInputLen = stackItem.inputLen; for(;;) { var ch = scanInput.charCodeAt(scanPos); if (ch === 32) { // ' ' ++scanPos; } else if (ch === 13) { ++scanPos; var next = scanInput.charCodeAt(scanPos); if (next === 10) { ++scanPos; } } else if (ch === 10) { ++scanPos; } else if (ch === 9) { ++scanPos; } else if (ch === 47) { // '/' var next = scanInput.charCodeAt(scanPos+1); if (next === 42) { // '*' var end = scanInput.indexOf("*/", scanPos += 2); if (end === -1) raise(scanPos - 2, "Unterminated comment"); scanPos = end + 2; } else if (next === 47) { // '/' ch = scanInput.charCodeAt(scanPos += 2); while (scanPos < inputLen && ch !== 10 && ch !== 13 && ch !== 8232 && ch !== 8233) { ++scanPos; ch = scanInput.charCodeAt(scanPos); } } else break stackloop; } else if (ch === 160 || ch === 11 || ch === 12 || (ch >= 5760 && nonASCIIwhitespace.test(String.fromCharCode(ch)))) { // '\xa0', VT, FF, Unicode whitespaces ++scanPos; } else if (scanPos >= scanInputLen) { continue stackloop; } else if (ch === 92) { // '\' // Check if we have an escaped newline. We are using a relaxed treatment of escaped newlines like gcc. // We allow spaces, horizontal and vertical tabs, and form feeds between the backslash and the subsequent newline var pos = scanPos + 1; ch = scanInput.charCodeAt(pos); while (pos < scanInputLen && (ch === 32 || ch === 9 || ch === 11 || ch === 12 || (ch >= 5760 && nonASCIIwhitespaceNoNewLine.test(String.fromCharCode(ch))))) ch = scanInput.charCodeAt(++pos); lineBreak.lastIndex = 0; var match = lineBreak.exec(scanInput.slice(pos, pos + 2)); if (match && match.index === 0) { scanPos = pos + match[0].length; } else { break stackloop; } } else { break stackloop; } } } return scanInput && scanInput.charCodeAt(scanPos) === first && (second == null || scanInput.charCodeAt(scanPos + 1) === second); } // Push macro to stack and start read from it. // Just read next token if the macro is empty function readTokenFromMacro(macro, macroOffset, parameters, parameterScope, end, nextFinisher, onlyTransformArguments, forceRegexp) { var macroString = macro.macro; // If we are evaluation a macro expresion an empty macro definition means true or '1' if(!macroString && nextFinisher === preprocessNext) macroString = "1"; if (macroString) { pushMacroToStack(macro, macroString, macroOffset, parameters, parameterScope, end, onlyTransformArguments); } else if (preConcatenating) { // If we are concatenating or stringifying and the macro is empty just make an empty string. (nextFinisher === next ? finishToken : preprocessFinishToken)(_name, ""); return true; } // Now read the next token onlySkipSpace(); nextFinisher(true, onlyTransformArguments, forceRegexp, true); // Stealth and Preprocess macros return true; } // Push macro to stack and reset tokPos etc. // macroString is the string from the macro. It is usually 'macro.macro' but the caller can modify it if needed // includeFile is true if the macro should be treated as a regular file. In other words don't stringify words after '#' function pushMacroToStack(macro, macroString, macroOffset, parameters, parameterScope, end, onlyTransformArguments, isIncludeFile) { preprocessStackLastItem = {macro: macro, macroOffset: macroOffset, parameterDict: parameters, /*start: macroStart,*/ end:end, lastEnd: localLastEnd, inputLen: inputLen, tokStart: tokStart, onlyTransformArgumentsForLastToken: preprocessOnlyTransformArgumentsForLastToken, currentLine: tokCurLine, currentLineStart: tokLineStart, sourceFile: sourceFile}; if (parameterScope) preprocessStackLastItem.parameterScope = parameterScope; if (isIncludeFile) preprocessStackLastItem.isIncludeFile = isIncludeFile; preprocessStackLastItem.input = input; preprocessStack.push(preprocessStackLastItem); preprocessOnlyTransformArgumentsForLastToken = onlyTransformArguments; input = macroString; inputLen = macroString.length; tokPosMacroOffset = macro.start; tokPos = 0; tokCurLine = 1; tokLineStart = 0; firstTokEnd = 0; localLastEnd = 0; if (macro.sourceFile) sourceFile = macro.sourceFile; } // ident is the identifier name for the macro // macro is the macro string // parameters is an array with the parameters for the macro // start is the offset to where the macro is defined // isArgument is true if the macro is a parameter // parameterScope is the parameter scope // varadicName is the name of the varadic parameter if it is a varadic macro // locationOffset is the current line that the macro starts at and the position on the line var Macro = exports.Macro = function Macro(ident, macro, parameters, start, isArgument, parameterScope, variadicName, locationOffset, aSourceFile) { this.identifier = ident; if (macro != null) this.macro = macro; if (parameters) this.parameters = parameters; if (start != null) this.start = start; if (isArgument) this.isArgument = true; if (parameterScope) this.parameterScope = parameterScope; if (variadicName) this.variadicName = variadicName; if (locationOffset) this.locationOffset = locationOffset; if (aSourceFile) this.sourceFile = aSourceFile; } Macro.prototype.isParameterFunction = function() { return this.isParameterFunctionVar || (this.isParameterFunctionVar = makePredicate((this.parameters || []).join(" "))); } // ## Parser // A recursive descent parser operates by defining functions for all // syntactic elements, and recursively calling those, each function // advancing the input stream and returning an AST node. Precedence // of constructs (for example, the fact that `!x[1]` means `!(x[1])` // instead of `(!x)[1]` is handled by the fact that the parser // function that parses unary prefix operators is called first, and // in turn calls the function that parses `[]` subscripts — that // way, it'll receive the node for `x[1]` already parsed, and wraps // *that* in the unary operator node. // // Acorn uses an [operator precedence parser][opp] to handle binary // operator precedence, because it is much more compact than using // the technique outlined above, which uses different, nesting // functions to specify precedence, for all of the ten binary // precedence levels that JavaScript defines. // // [opp]: http://en.wikipedia.org/wiki/Operator-precedence_parser // ### Parser utilities // Continue to the next token. // Stealth is to preserve lastEnd etc to get correct end positions on nodes when the // preprocessor needs to drop one token and read next function next(stealth, onlyTransformArguments, forceRegexp) { if (!stealth) { lastStart = tokStart; lastEnd = tokEnd; lastEndInput = tokInput; lastEndOfFile = firstEndOfFile; lastEndLoc = tokEndLoc; lastTokMacroOffset = tokMacroOffset; } localLastEnd = firstTokEnd; firstEndOfFile = nodeMessageSendObjectExpression = null; return readToken(forceRegexp, onlyTransformArguments, stealth); } // Enter strict mode. Re-reads the next token to please pedantic // tests ("use strict"; 010; -- should fail). function setStrict(strct) { strict = strct; tokPos = lastEnd; while (tokPos < tokLineStart) { tokLineStart = input.lastIndexOf("\n", tokLineStart - 2) + 1; --tokCurLine; } skipSpace(); readToken(); } // Start an AST node, attaching a start offset and optionally a // `commentsBefore` property to it. function node_t() { this.type = null; this.start = tokStart + tokMacroOffset; this.end = null; } function node_loc_t() { this.start = tokStartLoc; this.end = null; if (sourceFile != null) this.source = sourceFile; } function startNode() { var node = new node_t(); if (options.trackComments && tokCommentsBefore) { node.commentsBefore = tokCommentsBefore; tokCommentsBefore = null; } if (options.trackSpaces && tokSpacesBefore) { node.spacesBefore = tokSpacesBefore; tokSpacesBefore = null; } if (options.locations) node.loc = new node_loc_t(); if (options.ranges) node.range = [tokStart, 0]; return node; } // Start a node whose start offset/comments information should be // based on the start of another node. For example, a binary // operator node is only started after its left-hand side has // already been parsed. function startNodeFrom(other) { var node = new node_t(); node.start = other.start; if (other.commentsBefore) { node.commentsBefore = other.commentsBefore; delete other.commentsBefore; } if (other.spacesBefore) { node.spacesBefore = other.spacesBefore; delete other.spacesBefore; } if (options.locations) { node.loc = new node_loc_t(); node.loc.start = other.loc.start; } if (options.ranges) node.range = [other.range[0], 0]; return node; } // Finish an AST node, adding `type`, `end`, and `commentsAfter` // properties. // // We keep track of the last node that we finished, in order // 'bubble' `commentsAfter` properties up to the biggest node. I.e. // in '`1 + 1 // foo', the comment should be attached to the binary // operator node, not the second literal node. The same is done on // `spacesAfter` var lastFinishedNode; function finishNode(node, type) { var nodeEnd = lastEnd + lastTokMacroOffset; node.type = type; node.end = nodeEnd; if (options.trackComments) { if (lastTokCommentsAfter) { node.commentsAfter = lastTokCommentsAfter; lastTokCommentsAfter = null; } else if (lastFinishedNode && lastFinishedNode.end === lastEnd && lastFinishedNode.commentsAfter) { node.commentsAfter = lastFinishedNode.commentsAfter; delete lastFinishedNode.commentsAfter; } if (!options.trackSpaces) lastFinishedNode = node; } if (options.trackSpaces) { if (lastTokSpacesAfter) { node.spacesAfter = lastTokSpacesAfter; lastTokSpacesAfter = null; } else if (lastFinishedNode && lastFinishedNode.end === lastEnd && lastFinishedNode.spacesAfter) { node.spacesAfter = lastFinishedNode.spacesAfter; delete lastFinishedNode.spacesAfter; } lastFinishedNode = node; } if (options.locations) node.loc.end = lastEndLoc; if (options.ranges) node.range[1] = nodeEnd; return node; } // Test whether a statement node is the string literal `"use strict"`. function isUseStrict(stmt) { return options.ecmaVersion >= 5 && stmt.type === "ExpressionStatement" && stmt.expression.type === "Literal" && stmt.expression.value === "use strict"; } // Predicate that tests whether the next token is of the given // type, and if yes, consumes it as a side effect. function eat(type) { if (tokType === type) { next(); return true; } } // Test whether a semicolon can be inserted at the current position. function canInsertSemicolon() { return !options.strictSemicolons && (tokType === _eof || tokType === _braceR || newline.test(lastEndInput.slice(lastEnd, lastEndOfFile || tokFirstStart)) || (nodeMessageSendObjectExpression && options.objj) || lastEndOfFile != null); } // Consume a semicolon, or, failing that, see if we are allowed to // pretend that there is a semicolon at this position. function semicolon() { if (!eat(_semi) && !canInsertSemicolon()) raise(tokStart, "Expected a semicolon"); } // Expect a token of a given type. If found, consume it, otherwise, // raise with errorMessage or an unexpected token error. function expect(type, errorMessage) { if (tokType === type) next(); else errorMessage ? raise(tokStart, errorMessage) : unexpected(); } // Raise an unexpected token error. function unexpected() { raise(tokStart, "Unexpected token"); } // Verify that a node is an lval — something that can be assigned // to. function checkLVal(expr) { if (expr.type !== "Identifier" && expr.type !== "MemberExpression" && expr.type !== "Dereference") raise(expr.start, "Assigning to rvalue"); if (strict && expr.type === "Identifier" && isStrictBadIdWord(expr.name)) raise(expr.start, "Assigning to " + expr.name + " in strict mode"); } // ### Statement parsing // Parse a program. Initializes the parser, reads any number of // statements, and wraps them in a Program node. Optionally takes a // `program` argument. If present, the statements will be appended // to its body instead of creating a new node. // If there are any pre include files they will be pushed onto the macro stack function parseTopLevel(program) { lastStart = localLastEnd = lastEnd = 0; if (options.preprocess) { var preIncludeFiles = options.preIncludeFiles; if (preIncludeFiles && preIncludeFiles.length) for (var i = preIncludeFiles.length - 1; i >= 0; i--) { var preIncludeFile = preIncludeFiles[i]; var preIncludeMacro = new Macro(null, preIncludeFile.include, null, 0, false, null, false, null, preIncludeFile.sourceFile); pushMacroToStack(preIncludeMacro, preIncludeMacro.macro, 0, null, null, tokPos, null, true); // isIncludeFile skipSpace(); } } if (options.locations) lastEndLoc = new line_loc_t; inFunction = strict = null; labels = []; readToken(); var node = program || startNode(), first = true; if (!program) node.body = []; while (tokType !== _eof) { var stmt = parseStatement(); node.body.push(stmt); if (first && isUseStrict(stmt)) setStrict(true); first = false; } return finishNode(node, "Program"); } var loopLabel = {kind: "loop"}, switchLabel = {kind: "switch"}; // Parse a single statement. // // If expecting a statement and finding a slash operator, parse a // regular expression literal. This is to handle cases like // `if (foo) /blah/.exec(foo);`, where looking at the previous token // does not help. function parseStatement() { if (tokType === _slash || tokType === _assign && tokVal == "/=") readToken(true); var starttype = tokType, node = startNode(); // This is a special case when trying figure out if this is a subscript to the former line or a new send message statement on this line... if (nodeMessageSendObjectExpression) { node.expression = parseMessageSendExpression(nodeMessageSendObjectExpression, nodeMessageSendObjectExpression.object); semicolon(); return finishNode(node, "ExpressionStatement"); } // Most types of statements are recognized by the keyword they // start with. Many are trivial to parse, some require a bit of // complexity. switch (starttype) { case _break: case _continue: next(); var isBreak = starttype === _break; if (eat(_semi) || canInsertSemicolon()) node.label = null; else if (tokType !== _name) unexpected(); else { node.label = parseIdent(); semicolon(); } // Verify that there is an actual destination to break or // continue to. for (var i = 0; i < labels.length; ++i) { var lab = labels[i]; if (node.label == null || lab.name === node.label.name) { if (lab.kind != null && (isBreak || lab.kind === "loop")) break; if (node.label && isBreak) break; } } if (i === labels.length) raise(node.start, "Unsyntactic " + starttype.keyword); return finishNode(node, isBreak ? "BreakStatement" : "ContinueStatement"); case _debugger: next(); semicolon(); return finishNode(node, "DebuggerStatement"); case _do: next(); labels.push(loopLabel); node.body = parseStatement(); labels.pop(); expect(_while, "Expected 'while' at end of do statement"); node.test = parseParenExpression(); semicolon(); return finishNode(node, "DoWhileStatement"); // Disambiguating between a `for` and a `for`/`in` loop is // non-trivial. Basically, we have to parse the init `var` // statement or expression, disallowing the `in` operator (see // the second parameter to `parseExpression`), and then check // whether the next token is `in`. When there is no init part // (semicolon immediately after the opening parenthesis), it is // a regular `for` loop. case _for: next(); labels.push(loopLabel); expect(_parenL, "Expected '(' after 'for'"); if (tokType === _semi) return parseFor(node, null); if (tokType === _var) { var init = startNode(); next(); parseVar(init, true); if (init.declarations.length === 1 && eat(_in)) return parseForIn(node, init); return parseFor(node, init); } var init = parseExpression(false, true); if (eat(_in)) {checkLVal(init); return parseForIn(node, init);} return parseFor(node, init); case _function: next(); return parseFunction(node, true); case _if: next(); node.test = parseParenExpression(); node.consequent = parseStatement(); node.alternate = eat(_else) ? parseStatement() : null; return finishNode(node, "IfStatement"); case _return: if (!inFunction) raise(tokStart, "'return' outside of function"); next(); // In `return` (and `break`/`continue`), the keywords with // optional arguments, we eagerly look for a semicolon or the // possibility to insert one. if (eat(_semi) || canInsertSemicolon()) node.argument = null; else { node.argument = parseExpression(); semicolon(); } return finishNode(node, "ReturnStatement"); case _switch: next(); node.discriminant = parseParenExpression(); node.cases = []; expect(_braceL, "Expected '{' in switch statement"); labels.push(switchLabel); // Statements under must be grouped (by label) in SwitchCase // nodes. `cur` is used to keep the node that we are currently // adding statements to. for (var cur, sawDefault; tokType != _braceR;) { if (tokType === _case || tokType === _default) { var isCase = tokType === _case; if (cur) finishNode(cur, "SwitchCase"); node.cases.push(cur = startNode()); cur.consequent = []; next(); if (isCase) cur.test = parseExpression(); else { if (sawDefault) raise(lastStart, "Multiple default clauses"); sawDefault = true; cur.test = null; } expect(_colon, "Expected ':' after case clause"); } else { if (!cur) unexpected(); cur.consequent.push(parseStatement()); } } if (cur) finishNode(cur, "SwitchCase"); next(); // Closing brace labels.pop(); return finishNode(node, "SwitchStatement"); case _throw: next(); if (newline.test(tokInput.slice(lastEnd, tokStart))) raise(lastEnd, "Illegal newline after throw"); node.argument = parseExpression(); semicolon(); return finishNode(node, "ThrowStatement"); case _try: next(); node.block = parseBlock(); node.handler = null; if (tokType === _catch) { var clause = startNode(); next(); expect(_parenL, "Expected '(' after 'catch'"); clause.param = parseIdent(); if (strict && isStrictBadIdWord(clause.param.name)) raise(clause.param.start, "Binding " + clause.param.name + " in strict mode"); expect(_parenR, "Expected closing ')' after catch"); clause.guard = null; clause.body = parseBlock(); node.handler = finishNode(clause, "CatchClause"); } node.guardedHandlers = empty; node.finalizer = eat(_finally) ? parseBlock() : null; if (!node.handler && !node.finalizer) raise(node.start, "Missing catch or finally clause"); return finishNode(node, "TryStatement"); case _var: next(); node = parseVar(node); semicolon(); return node; case _while: next(); node.test = parseParenExpression(); labels.push(loopLabel); node.body = parseStatement(); labels.pop(); return finishNode(node, "WhileStatement"); case _with: if (strict) raise(tokStart, "'with' in strict mode"); next(); node.object = parseParenExpression(); node.body = parseStatement(); return finishNode(node, "WithStatement"); case _braceL: return parseBlock(); case _semi: next(); return finishNode(node, "EmptyStatement"); // Objective-J case _interface: if (options.objj) { next(); node.classname = parseIdent(true); if (eat(_colon)) node.superclassname = parseIdent(true); else if (eat(_parenL)) { node.categoryname = parseIdent(true); expect(_parenR, "Expected closing ')' after category name"); } if (tokVal === '<') { next(); var protocols = [], first = true; node.protocols = protocols; while (tokVal !== '>') { if (!first) expect(_comma, "Expected ',' between protocol names"); else first = false; protocols.push(parseIdent(true)); } next(); } if (eat(_braceL)) { node.ivardeclarations = []; for (;;) { if (eat(_braceR)) break; parseIvarDeclaration(node); } node.endOfIvars = tokStart; } node.body = []; while(!eat(_end)) { if (tokType === _eof) raise(tokPos, "Expected '@end' after '@interface'"); node.body.push(parseClassElement()); } return finishNode(node, "InterfaceDeclarationStatement"); } break; // Objective-J case _implementation: if (options.objj) { next(); node.classname = parseIdent(true); if (eat(_colon)) node.superclassname = parseIdent(true); else if (eat(_parenL)) { node.categoryname = parseIdent(true); expect(_parenR, "Expected closing ')' after category name"); } if (tokVal === '<') { next(); var protocols = [], first = true; node.protocols = protocols; while (tokVal !== '>') { if (!first) expect(_comma, "Expected ',' between protocol names"); else first = false; protocols.push(parseIdent(true)); } next(); } if (eat(_braceL)) { node.ivardeclarations = []; for (;;) { if (eat(_braceR)) break; parseIvarDeclaration(node); } node.endOfIvars = tokStart; } node.body = []; while(!eat(_end)) { if (tokType === _eof) raise(tokPos, "Expected '@end' after '@implementation'"); node.body.push(parseClassElement()); } return finishNode(node, "ClassDeclarationStatement"); } break; // Objective-J case _protocol: // If next token is a left parenthesis it is a ProtocolLiteral expression so bail out if (options.objj && input.charCodeAt(tokPos) !== 40) { // '(' next(); node.protocolname = parseIdent(true); if (tokVal === '<') { next(); var protocols = [], first = true; node.protocols = protocols; while (tokVal !== '>') { if (!first) expect(_comma, "Expected ',' between protocol names"); else first = false; protocols.push(parseIdent(true)); } next(); } while(!eat(_end)) { if (tokType === _eof) raise(tokPos, "Expected '@end' after '@protocol'"); if (eat(_required)) continue; if (eat(_optional)) { while(!eat(_required) && tokType !== _end) { (node.optional || (node.optional = [])).push(parseProtocolClassElement()); } } else { (node.required || (node.required = [])).push(parseProtocolClassElement()); } } return finishNode(node, "ProtocolDeclarationStatement"); } break; // Objective-J case _import: if (options.objj) { next(); if (tokType === _string) node.localfilepath = true; else if (tokType ===_filename) node.localfilepath = false; else unexpected(); node.filename = parseStringNumRegExpLiteral(); return finishNode(node, "ImportStatement"); } break; // Objective-J case _preprocess: if (options.objj) { next(); return finishNode(node, "PreprocessStatement"); } break; // Objective-J case _class: if (options.objj) { next(); node.id = parseIdent(false); return finishNode(node, "ClassStatement"); } break; // Objective-J case _global: if (options.objj) { next(); node.id = parseIdent(false); return finishNode(node, "GlobalStatement"); } break; // This is a Objective-J statement case _typedef: if (options.objj) { next(); node.typedefname = parseIdent(true); return finishNode(node, "TypeDefStatement"); } break; } // The indentation is one step to the right here to make sure it // is the same as in the original acorn parser. Easier merge // If the statement does not start with a statement keyword or a // brace, it's an ExpressionStatement or LabeledStatement. We // simply start parsing an expression, and afterwards, if the // next token is a colon and the expression was a simple // Identifier node, we switch to interpreting it as a label. var maybeName = tokVal, expr = parseExpression(); if (starttype === _name && expr.type === "Identifier" && eat(_colon)) { for (var i = 0; i < labels.length; ++i) if (labels[i].name === maybeName) raise(expr.start, "Label '" + maybeName + "' is already declared"); var kind = tokType.isLoop ? "loop" : tokType === _switch ? "switch" : null; labels.push({name: maybeName, kind: kind}); node.body = parseStatement(); labels.pop(); node.label = expr; return finishNode(node, "LabeledStatement"); } else { node.expression = expr; semicolon(); return finishNode(node, "ExpressionStatement"); } } function parseIvarDeclaration(node) { var outlet; if (eat(_outlet)) outlet = true; var type = parseObjectiveJType(); if (strict && isStrictBadIdWord(type.name)) raise(type.start, "Binding " + type.name + " in strict mode"); for (;;) { var decl = startNode(); if (outlet) decl.outlet = outlet; decl.ivartype = type; decl.id = parseIdent(); if (strict && isStrictBadIdWord(decl.id.name)) raise(decl.id.start, "Binding " + decl.id.name + " in strict mode"); if (eat(_accessors)) { decl.accessors = {}; if (eat(_parenL)) { if (!eat(_parenR)) { for (;;) { var config = parseIdent(true); switch(config.name) { case "property": case "getter": expect(_eq, "Expected '=' after 'getter' accessor attribute"); decl.accessors[config.name] = parseIdent(true); break; case "setter": expect(_eq, "Expected '=' after 'setter' accessor attribute"); var setter = parseIdent(true); decl.accessors[config.name] = setter; if (eat(_colon)) setter.end = tokStart; setter.name += ":" break; case "readwrite": case "readonly": case "copy": decl.accessors[config.name] = true; break; default: raise(config.start, "Unknown accessors attribute '" + config.name + "'"); } if (!eat(_comma)) break; } expect(_parenR, "Expected closing ')' after accessor attributes"); } } } finishNode(decl, "IvarDeclaration") node.ivardeclarations.push(decl); if (!eat(_comma)) break; } semicolon(); } function parseMethodDeclaration(node) { node.methodtype = tokVal; expect(_plusmin, "Method declaration must start with '+' or '-'"); // If we find a '(' we have a return type to parse if (eat(_parenL)) { var typeNode = startNode(); if (eat(_action)) { node.action = finishNode(typeNode, "ObjectiveJActionType"); typeNode = startNode(); } if (!eat(_parenR)) { node.returntype = parseObjectiveJType(typeNode); expect(_parenR, "Expected closing ')' after method return type"); } } // Now we parse the selector var first = true, selectors = [], args = []; node.selectors = selectors; node.arguments = args; for (;;) { if (tokType !== _colon) { selectors.push(parseIdent(true)); if (first && tokType !== _colon) break; } else selectors.push(null); expect(_colon, "Expected ':' in selector"); var argument = {}; args.push(argument); if (eat(_parenL)) { argument.type = parseObjectiveJType(); expect(_parenR, "Expected closing ')' after method argument type"); } argument.identifier = parseIdent(false); if (tokType === _braceL || tokType === _semi) break; if (eat(_comma)) { expect(_dotdotdot, "Expected '...' after ',' in method declaration"); node.parameters = true; break; } first = false; } } function parseClassElement() { var element = startNode(); if (tokVal === '+' || tokVal === '-') { parseMethodDeclaration(element); eat(_semi); element.startOfBody = lastEnd; // Start a new scope with regard to labels and the `inFunction` // flag (restore them to their old value afterwards). var oldInFunc = inFunction, oldLabels = labels; inFunction = true; labels = []; element.body = parseBlock(true); inFunction = oldInFunc; labels = oldLabels; return finishNode(element, "MethodDeclarationStatement"); } else return parseStatement(); } function parseProtocolClassElement() { var element = startNode(); parseMethodDeclaration(element); semicolon(); return finishNode(element, "MethodDeclarationStatement"); } // Used for constructs like `switch` and `if` that insist on // parentheses around their expression. function parseParenExpression() { expect(_parenL, "Expected '(' before expression"); var val = parseExpression(); expect(_parenR, "Expected closing ')' after expression"); return val; } // Parse a semicolon-enclosed block of statements, handling `"use // strict"` declarations when `allowStrict` is true (used for // function bodies). function parseBlock(allowStrict) { var node = startNode(), first = true, strict = false, oldStrict; node.body = []; expect(_braceL, "Expected '{' before block"); while (!eat(_braceR)) { var stmt = parseStatement(); node.body.push(stmt); if (first && allowStrict && isUseStrict(stmt)) { oldStrict = strict; setStrict(strict = true); } first = false; } if (strict && !oldStrict) setStrict(false); return finishNode(node, "BlockStatement"); } // Parse a regular `for` loop. The disambiguation code in // `parseStatement` will already have parsed the init statement or // expression. function parseFor(node, init) { node.init = init; expect(_semi, "Expected ';' in for statement"); node.test = tokType === _semi ? null : parseExpression(); expect(_semi, "Expected ';' in for statement"); node.update = tokType === _parenR ? null : parseExpression(); expect(_parenR, "Expected closing ')' in for statement"); node.body = parseStatement(); labels.pop(); return finishNode(node, "ForStatement"); } // Parse a `for`/`in` loop. function parseForIn(node, init) { node.left = init; node.right = parseExpression(); expect(_parenR, "Expected closing ')' in for statement"); node.body = parseStatement(); labels.pop(); return finishNode(node, "ForInStatement"); } // Parse a list of variable declarations. function parseVar(node, noIn) { node.declarations = []; node.kind = "var"; for (;;) { var decl = startNode(); decl.id = parseIdent(); if (strict && isStrictBadIdWord(decl.id.name)) raise(decl.id.start, "Binding " + decl.id.name + " in strict mode"); decl.init = eat(_eq) ? parseExpression(true, noIn) : null; node.declarations.push(finishNode(decl, "VariableDeclarator")); if (!eat(_comma)) break; } return finishNode(node, "VariableDeclaration"); } // ### Expression parsing // These nest, from the most general expression type at the top to // 'atomic', nondivisible expression types at the bottom. Most of // the functions will simply let the function(s) below them parse, // and, *if* the syntactic construct they handle is present, wrap // the AST node that the inner parser gave them in another node. // Parse a full expression. The arguments are used to forbid comma // sequences (in argument lists, array literals, or object literals) // or the `in` operator (in for loops initalization expressions). function parseExpression(noComma, noIn) { var expr = parseMaybeAssign(noIn); if (!noComma && tokType === _comma) { var node = startNodeFrom(expr); node.expressions = [expr]; while (eat(_comma)) node.expressions.push(parseMaybeAssign(noIn)); return finishNode(node, "SequenceExpression"); } return expr; } // Parse an assignment expression. This includes applications of // operators like `+=`. function parseMaybeAssign(noIn) { var left = parseMaybeConditional(noIn); if (tokType.isAssign) { var node = startNodeFrom(left); node.operator = tokVal; node.left = left; next(); node.right = parseMaybeAssign(noIn); checkLVal(left); return finishNode(node, "AssignmentExpression"); } return left; } // Parse a ternary conditional (`?:`) operator. function parseMaybeConditional(noIn) { var expr = parseExprOps(noIn); if (eat(_question)) { var node = startNodeFrom(expr); node.test = expr; node.consequent = parseExpression(true); expect(_colon, "Expected ':' in conditional expression"); node.alternate = parseExpression(true, noIn); return finishNode(node, "ConditionalExpression"); } return expr; } // Start the precedence parser. function parseExprOps(noIn) { return parseExprOp(parseMaybeUnary(), -1, noIn); } // Parse binary operators with the operator precedence parsing // algorithm. `left` is the left-hand side of the operator. // `minPrec` provides context that allows the function to stop and // defer further parser to one of its callers when it encounters an // operator that has a lower precedence than the set it is parsing. function parseExprOp(left, minPrec, noIn) { var prec = tokType.binop; if (prec != null && (!noIn || tokType !== _in)) { if (prec > minPrec) { var node = startNodeFrom(left); node.left = left; node.operator = tokVal; next(); node.right = parseExprOp(parseMaybeUnary(), prec, noIn); var node = finishNode(node, /&&|\|\|/.test(node.operator) ? "LogicalExpression" : "BinaryExpression"); return parseExprOp(node, minPrec, noIn); } } return left; } // Parse unary operators, both prefix and postfix. function parseMaybeUnary() { if (tokType.prefix) { var node = startNode(), update = tokType.isUpdate; node.operator = tokVal; node.prefix = true; tokRegexpAllowed = true; next(); node.argument = parseMaybeUnary(); if (update) checkLVal(node.argument); else if (strict && node.operator === "delete" && node.argument.type === "Identifier") raise(node.start, "Deleting local variable in strict mode"); return finishNode(node, update ? "UpdateExpression" : "UnaryExpression"); } var expr = parseExprSubscripts(); while (tokType.postfix && !canInsertSemicolon()) { var node = startNodeFrom(expr); node.operator = tokVal; node.prefix = false; node.argument = expr; checkLVal(expr); next(); expr = finishNode(node, "UpdateExpression"); } return expr; } // Parse call, dot, and `[]`-subscript expressions. function parseExprSubscripts() { return parseSubscripts(parseExprAtom()); } function parseSubscripts(base, noCalls) { if (eat(_dot)) { var node = startNodeFrom(base); node.object = base; node.property = parseIdent(true); node.computed = false; return parseSubscripts(finishNode(node, "MemberExpression"), noCalls); } else { if (options.objj) var messageSendNode = startNode(); if (eat(_bracketL)) { var expr = parseExpression(); if (options.objj && tokType !== _bracketR) { messageSendNode.object = expr; nodeMessageSendObjectExpression = messageSendNode; return base; } var node = startNodeFrom(base); node.object = base; node.property = expr; node.computed = true; expect(_bracketR, "Expected closing ']' in subscript"); return parseSubscripts(finishNode(node, "MemberExpression"), noCalls); } else if (!noCalls && eat(_parenL)) { var node = startNodeFrom(base); node.callee = base; node.arguments = parseExprList(_parenR, tokType === _parenR ? null : parseExpression(true), false); return parseSubscripts(finishNode(node, "CallExpression"), noCalls); } } return base; } // Parse an atomic expression — either a single token that is an // expression, an expression started by a keyword like `function` or // `new`, or an expression wrapped in punctuation like `()`, `[]`, // or `{}`. function parseExprAtom() { switch (tokType) { case _this: var node = startNode(); next(); return finishNode(node, "ThisExpression"); case _name: return parseIdent(); case _num: case _string: case _regexp: return parseStringNumRegExpLiteral(); case _null: case _true: case _false: var node = startNode(); node.value = tokType.atomValue; node.raw = tokType.keyword; next(); return finishNode(node, "Literal"); case _parenL: var tokStartLoc1 = tokStartLoc, macroOffset = tokMacroOffset, tokStart1 = tokStart + macroOffset; next(); var val = parseExpression(); val.start = tokStart1; val.end = tokEnd + macroOffset; if (options.locations) { val.loc.start = tokStartLoc1; val.loc.end = tokEndLoc; } if (options.ranges) val.range = [tokStart1, tokEnd + lastTokMacroOffset]; expect(_parenR, "Expected closing ')' in expression"); return val; case _arrayLiteral: var node = startNode(), firstExpr = null; next(); expect(_bracketL, "Expected '[' at beginning of array literal"); if (tokType !== _bracketR) firstExpr = parseExpression(true, true); node.elements = parseExprList(_bracketR, firstExpr, true, true); return finishNode(node, "ArrayLiteral"); case _bracketL: var node = startNode(), firstExpr = null; next(); if (tokType !== _comma && tokType !== _bracketR) { firstExpr = parseExpression(true, true); if (tokType !== _comma && tokType !== _bracketR) return parseMessageSendExpression(node, firstExpr); } node.elements = parseExprList(_bracketR, firstExpr, true, true); return finishNode(node, "ArrayExpression"); case _dictionaryLiteral: var node = startNode(); next(); var r = parseDictionary(); node.keys = r[0]; node.values = r[1]; return finishNode(node, "DictionaryLiteral"); case _braceL: return parseObj(); case _function: var node = startNode(); next(); return parseFunction(node, false); case _new: return parseNew(); case _selector: var node = startNode(); next(); expect(_parenL, "Expected '(' after '@selector'"); parseSelector(node, _parenR); expect(_parenR, "Expected closing ')' after selector"); return finishNode(node, "SelectorLiteralExpression"); case _protocol: var node = startNode(); next(); expect(_parenL, "Expected '(' after '@protocol'"); node.id = parseIdent(true); expect(_parenR, "Expected closing ')' after protocol name"); return finishNode(node, "ProtocolLiteralExpression"); case _ref: var node = startNode(); next(); expect(_parenL, "Expected '(' after '@ref'"); node.element = parseIdent(node, _parenR); expect(_parenR, "Expected closing ')' after ref"); return finishNode(node, "Reference"); case _deref: var node = startNode(); next(); expect(_parenL, "Expected '(' after '@deref'"); node.expr = parseExpression(true, true); expect(_parenR, "Expected closing ')' after deref"); return finishNode(node, "Dereference"); default: if (tokType.okAsIdent) return parseIdent(); unexpected(); } } function parseMessageSendExpression(node, firstExpr) { parseSelectorWithArguments(node, _bracketR); if (firstExpr.type === "Identifier" && firstExpr.name === "super") node.superObject = true; else node.object = firstExpr; return finishNode(node, "MessageSendExpression"); } function parseSelector(node, close) { var first = true, selectors = []; for (;;) { if (tokType !== _colon) { selectors.push(parseIdent(true).name); if (first && tokType === close) break; } expect(_colon, "Expected ':' in selector"); selectors.push(":"); if (tokType === close) break; first = false; } node.selector = selectors.join(""); } function parseSelectorWithArguments(node, close) { var first = true, selectors = [], args = [], parameters = []; node.selectors = selectors; node.arguments = args; for (;;) { if (tokType !== _colon) { selectors.push(parseIdent(true)); if (first && eat(close)) break; } else { selectors.push(null); } expect(_colon, "Expected ':' in selector"); args.push(parseExpression(true, true)); if (eat(close)) break; if (tokType === _comma) { node.parameters = []; while(eat(_comma)) { node.parameters.push(parseExpression(true, true)); } eat(close); break; } first = false; } } // New's precedence is slightly tricky. It must allow its argument // to be a `[]` or dot subscript expression, but not a call — at // least, not without wrapping it in parentheses. Thus, it uses the function parseNew() { var node = startNode(); next(); node.callee = parseSubscripts(parseExprAtom(false), true); if (eat(_parenL)) node.arguments = parseExprList(_parenR, tokType === _parenR ? null : parseExpression(true), false); else node.arguments = empty; return finishNode(node, "NewExpression"); } // Parse an object literal. function parseObj() { var node = startNode(), first = true, sawGetSet = false; node.properties = []; next(); while (!eat(_braceR)) { if (!first) { expect(_comma, "Expected ',' in object literal"); if (options.allowTrailingCommas && eat(_braceR)) break; } else first = false; var prop = {key: parsePropertyName()}, isGetSet = false, kind; if (eat(_colon)) { prop.value = parseExpression(true); kind = prop.kind = "init"; } else if (options.ecmaVersion >= 5 && prop.key.type === "Identifier" && (prop.key.name === "get" || prop.key.name === "set")) { isGetSet = sawGetSet = true; kind = prop.kind = prop.key.name; prop.key = parsePropertyName(); if (tokType !== _parenL) unexpected(); prop.value = parseFunction(startNode(), false); } else unexpected(); // getters and setters are not allowed to clash — either with // each other or with an init property — and in strict mode, // init properties are also not allowed to be repeated. if (prop.key.type === "Identifier" && (strict || sawGetSet)) { for (var i = 0; i < node.properties.length; ++i) { var other = node.properties[i]; if (other.key.name === prop.key.name) { var conflict = kind == other.kind || isGetSet && other.kind === "init" || kind === "init" && (other.kind === "get" || other.kind === "set"); if (conflict && !strict && kind === "init" && other.kind === "init") conflict = false; if (conflict) raise(prop.key.start, "Redefinition of property"); } } } node.properties.push(prop); } return finishNode(node, "ObjectExpression"); } function parsePropertyName() { if (tokType === _num || tokType === _string) return parseExprAtom(); return parseIdent(true); } // Parse a function declaration or literal (depending on the // `isStatement` parameter). function parseFunction(node, isStatement) { if (tokType === _name) node.id = parseIdent(); else if (isStatement) unexpected(); else node.id = null; node.params = []; var first = true; expect(_parenL, "Expected '(' before function parameters"); while (!eat(_parenR)) { if (!first) expect(_comma, "Expected ',' between function parameters"); else first = false; node.params.push(parseIdent()); } // Start a new scope with regard to labels and the `inFunction` // flag (restore them to their old value afterwards). var oldInFunc = inFunction, oldLabels = labels; inFunction = true; labels = []; node.body = parseBlock(true); inFunction = oldInFunc; labels = oldLabels; // If this is a strict mode function, verify that argument names // are not repeated, and it does not try to bind the words `eval` // or `arguments`. if (strict || node.body.body.length && isUseStrict(node.body.body[0])) { for (var i = node.id ? -1 : 0; i < node.params.length; ++i) { var id = i < 0 ? node.id : node.params[i]; if (isStrictReservedWord(id.name) || isStrictBadIdWord(id.name)) raise(id.start, "Defining '" + id.name + "' in strict mode"); if (i >= 0) for (var j = 0; j < i; ++j) if (id.name === node.params[j].name) raise(id.start, "Argument name clash in strict mode"); } } return finishNode(node, isStatement ? "FunctionDeclaration" : "FunctionExpression"); } // Parses a comma-separated list of expressions, and returns them as // an array. `close` is the token type that ends the list, and // `allowEmpty` can be turned on to allow subsequent commas with // nothing in between them to be parsed as `null` (which is needed // for array literals). // This function is modified so the first expression is passed as a // parameter. This is nessesary cause we need to check if it is a Objective-J // message send expression ([expr mySelector:param1 withSecondParam:param2]) function parseExprList(close, firstExpr, allowTrailingComma, allowEmpty) { if (firstExpr && eat(close)) return [firstExpr]; var elts = [], first = true; while (!eat(close)) { if (first) { first = false; if (allowEmpty && tokType === _comma && !firstExpr) elts.push(null); else elts.push(firstExpr); } else { expect(_comma, "Expected ',' between expressions"); if (allowTrailingComma && options.allowTrailingCommas && eat(close)) break; if (allowEmpty && tokType === _comma) elts.push(null); else elts.push(parseExpression(true)); } } return elts; } // Parses a comma-separated list of : pairs and returns them as // [arrayOfKeyExpressions, arrayOfValueExpressions]. function parseDictionary() { expect(_braceL, "Expected '{' before dictionary"); var keys = [], values = [], first = true; while (!eat(_braceR)) { if (!first) { expect(_comma, "Expected ',' between expressions"); if (options.allowTrailingCommas && eat(_braceR)) break; } keys.push(parseExpression(true, true)); expect(_colon, "Expected ':' between dictionary key and value"); values.push(parseExpression(true, true)); first = false; } return [keys, values]; } // Parse the next token as an identifier. If `liberal` is true (used // when parsing properties), it will also convert keywords into // identifiers. function parseIdent(liberal) { var node = startNode(); node.name = tokType === _name ? tokVal : (((liberal && !options.forbidReserved) || tokType.okAsIdent) && tokType.keyword) || unexpected(); tokRegexpAllowed = false; next(); return finishNode(node, "Identifier"); } function parseStringNumRegExpLiteral() { var node = startNode(); node.value = tokVal; node.raw = tokInput.slice(tokStart, tokEnd); next(); return finishNode(node, "Literal"); } // Parse the next token as an Objective-J typ. // It can be 'id' followed by a optional protocol '' // It can be 'void' or 'id' // It can be 'signed' or 'unsigned' followed by an optional 'char', 'byte', 'short', 'int' or 'long' // It can be 'char', 'byte', 'short', 'int' or 'long' // 'int' can be followed by an optinal 'long'. 'long' can be followed by an optional extra 'long' function parseObjectiveJType(startFrom) { var node = startFrom ? startNodeFrom(startFrom) : startNode(), allowProtocol = false; if (tokType === _name) { // It should be a class name node.name = tokVal; node.typeisclass = true; allowProtocol = true; next(); } else { node.typeisclass = false; node.name = tokType.keyword; // Do nothing more if it is 'void' if (!eat(_void)) { if (eat(_id)) { allowProtocol = true; } else { // Now check if it is some basic type or an approved combination of basic types var nextKeyWord; if (eat(_float) || eat(_boolean) || eat(_SEL) || eat(_double)) { nextKeyWord = tokType.keyword; } else { if (eat(_signed) || eat(_unsigned)) nextKeyWord = tokType.keyword || true; if (eat(_char) || eat(_byte) || eat(_short)) { if (nextKeyWord) node.name += " " + nextKeyWord; nextKeyWord = tokType.keyword || true; } else { if (eat(_int)) { if (nextKeyWord) node.name += " " + nextKeyWord; nextKeyWord = tokType.keyword || true; } if (eat(_long)) { if (nextKeyWord) node.name += " " + nextKeyWord; nextKeyWord = tokType.keyword || true; if (eat(_long)) { node.name += " " + nextKeyWord; } } } if (!nextKeyWord) { // It must be a class name if it was not a basic type. // FIXME: This is not true node.name = (!options.forbidReserved && tokType.keyword) || unexpected(); node.typeisclass = true; allowProtocol = true; next(); } } } } } if (allowProtocol) { // Is it 'id' or classname followed by a '<' then parse protocols. if (tokVal === '<') { var first = true, protocols = []; node.protocols = protocols; do { next(); if (first) first = false; else eat(_comma); protocols.push(parseIdent(true)); } while (tokVal !== '>'); next(); } } return finishNode(node, "ObjectiveJType"); } })(exports.acorn || (exports.acorn = {}), exports.acorn.walk || (exports.acorn.walk = (typeof acorn !== 'undefined') && acorn.walk) || (exports.acorn.walk = {}));