diff --git a/demo/kitchen-sink/require.js b/demo/kitchen-sink/require.js index 062516ac..9199e0fb 100644 --- a/demo/kitchen-sink/require.js +++ b/demo/kitchen-sink/require.js @@ -1951,12 +1951,13 @@ var requirejs, require, define; //This module may not have dependencies if (!isArray(deps)) { callback = deps; - deps = []; + deps = null; } //If no name, and callback is a function, then figure out if it a //CommonJS thing with dependencies. - if (!deps.length && isFunction(callback)) { + if (!deps && isFunction(callback)) { + deps = []; //Remove comments from the callback string, //look for require calls, and pull them into the dependencies, //but only if there are function args. diff --git a/lib/ace/mode/lua/luaparse.js b/lib/ace/mode/lua/luaparse.js index c1c780da..f961abc9 100644 --- a/lib/ace/mode/lua/luaparse.js +++ b/lib/ace/mode/lua/luaparse.js @@ -14,15 +14,20 @@ define(function(require, exports, module) { }(this, 'luaparse', function (exports) { 'use strict'; - exports.version = '0.0.1'; + exports.version = '0.0.11'; var input, options, length; + // Options can be set either globally on the parser object through + // defaultOptions, or during the parse call. var defaultOptions = exports.defaultOptions = { // Explicitly tell the parser when the input ends. wait: false // Store comments as an array in the chunk object. , comments: true + // Track identifier scopes by adding an isLocal attribute to each + // identifier-node. + , scope: false }; // The available tokens expressed as enum flags so they can be checked with @@ -30,7 +35,7 @@ define(function(require, exports, module) { var EOF = 1, StringLiteral = 2, Keyword = 4, Identifier = 8 , NumericLiteral = 16, Punctuator = 32, BooleanLiteral = 64 - , NilLiteral = 128; + , NilLiteral = 128, VarargLiteral = 256; // As this parser is a bit different from luas own, the error messages // will be different in some situations. @@ -82,6 +87,13 @@ define(function(require, exports, module) { , clauses: clauses }; } + , ifClause: function(condition, body) { + return { + type: 'IfClause' + , condition: condition + , body: body + }; + } , elseifClause: function(condition, body) { return { type: 'ElseifClause' @@ -142,12 +154,11 @@ define(function(require, exports, module) { }; } - , functionStatement: function(identifier, parameters, isVararg, isLocal, body) { + , functionStatement: function(identifier, parameters, isLocal, body) { return { type: 'FunctionDeclaration' , identifier: identifier - , vararg: isVararg - , local: isLocal + , isLocal: isLocal , parameters: parameters , body: body }; @@ -187,18 +198,19 @@ define(function(require, exports, module) { }; } - , literal: function(value, raw) { + , literal: function(type, value, raw) { + type = (type === StringLiteral) ? 'StringLiteral' + : (type === NumericLiteral) ? 'NumericLiteral' + : (type === BooleanLiteral) ? 'BooleanLiteral' + : (type === NilLiteral) ? 'NilLiteral' + : 'VarargLiteral'; + return { - type: 'Literal' + type: type , value: value , raw: raw }; } - , varargLiteral: function() { - return { - type: 'VarargLiteral' - }; - } , tableKey: function(key, value) { return { @@ -293,7 +305,14 @@ define(function(require, exports, module) { // ------- var slice = Array.prototype.slice - , toString = Object.prototype.toString; + , toString = Object.prototype.toString + // Simple indexOf implementation which only provides what's required. + , indexOf = Array.prototype.indexOf || function indexOf(element) { + for (var i = 0, length = this.length; i < length; i++) { + if (this[i] === element) return i; + } + return -1; + }; // A sprintf implementation using %index (beginning at 1) to input // arguments in the format string. @@ -306,7 +325,6 @@ define(function(require, exports, module) { function sprintf(format) { var args = slice.call(arguments, 1); format = format.replace(/%(\d)/g, function (match, index) { - match = ''; // jshint return '' + args[index - 1] || ''; }); return format; @@ -455,14 +473,14 @@ define(function(require, exports, module) { , range: [index, index] }; - var char = input.charCodeAt(index) + var character = input.charCodeAt(index) , next = input.charCodeAt(index + 1); // Memorize the range index where the token begins. tokenStart = index; - if (isIdentifierStart(char)) return scanIdentifierOrKeyword(); + if (isIdentifierStart(character)) return scanIdentifierOrKeyword(); - switch (char) { + switch (character) { case 39: case 34: // '" return scanStringLiteral(); @@ -475,7 +493,7 @@ define(function(require, exports, module) { // If the dot is followed by a digit it's a float. if (isDecDigit(next)) return scanNumericLiteral(); if (46 === next) { - if (46 === input.charCodeAt(index + 2)) return scanPunctuator('...'); + if (46 === input.charCodeAt(index + 2)) return scanVarargLiteral(); return scanPunctuator('..'); } return scanPunctuator('.'); @@ -520,10 +538,10 @@ define(function(require, exports, module) { function skipWhiteSpace() { while (index < length) { - var char = input.charCodeAt(index); - if (isWhiteSpace(char)) { + var character = input.charCodeAt(index); + if (isWhiteSpace(character)) { index++; - } else if (isLineTerminator(char)) { + } else if (isLineTerminator(character)) { line++; lineStart = ++index; } else { @@ -580,26 +598,39 @@ define(function(require, exports, module) { }; } + // A vararg literal consists of three dots. + + function scanVarargLiteral() { + index += 3; + return { + type: VarargLiteral + , value: '...' + , line: line + , lineStart: lineStart + , range: [tokenStart, index] + }; + } + // Find the string literal by matching the delimiter marks used. function scanStringLiteral() { var delimiter = input.charCodeAt(index++) , stringStart = index , string = '' - , char; + , character; while (index < length) { - char = input.charCodeAt(index++); - if (delimiter === char) break; - if (92 === char) { // \ + character = input.charCodeAt(index++); + if (delimiter === character) break; + if (92 === character) { // \ string += input.slice(stringStart, index - 1) + readEscapeSequence(); stringStart = index; } // EOF or `\n` terminates a string literal. If we haven't found the // ending delimiter by now, raise an exception. - else if (index >= length || isLineTerminator(char)) { + else if (index >= length || isLineTerminator(character)) { string += input.slice(stringStart, index - 1); - raise({}, errors.unfinishedString, string + String.fromCharCode(char)); + raise({}, errors.unfinishedString, string + String.fromCharCode(character)); } } string += input.slice(stringStart, index - 1); @@ -638,10 +669,10 @@ define(function(require, exports, module) { // If a hexadecimal number is encountered, it will be converted. function scanNumericLiteral() { - var char = input.charAt(index) + var character = input.charAt(index) , next = input.charAt(index + 1); - var value = ('0' === char && ~'xX'.indexOf(next || null)) ? + var value = ('0' === character && 'xX'.indexOf(next || null) >= 0) ? readHexLiteral() : readDecLiteral(); return { @@ -693,11 +724,11 @@ define(function(require, exports, module) { } // Binary exponents are optional - if (~'pP'.indexOf(input.charAt(index) || null)) { + if ('pP'.indexOf(input.charAt(index) || null) >= 0) { index++; // Sign part is optional and defaults to 1 (positive). - if (~'+-'.indexOf(input.charAt(index) || null)) + if ('+-'.indexOf(input.charAt(index) || null) >= 0) binarySign = ('+' === input.charAt(index++)) ? 1 : -1; exponentStart = index; @@ -729,10 +760,10 @@ define(function(require, exports, module) { while (isDecDigit(input.charCodeAt(index))) index++; } // Exponent part is optional. - if (~'eE'.indexOf(input.charAt(index) || null)) { + if ('eE'.indexOf(input.charAt(index) || null) >= 0) { index++; // Sign part is optional. - if (~'+-'.indexOf(input.charAt(index) || null)) index++; + if ('+-'.indexOf(input.charAt(index) || null) >= 0) index++; // An exponent is required to contain at least one decimal digit. if (!isDecDigit(input.charCodeAt(index))) raise({}, errors.malformedNumber, input.slice(tokenStart, index)); @@ -754,7 +785,7 @@ define(function(require, exports, module) { case 'n': index++; return '\n'; case 'r': index++; return '\r'; case 't': index++; return '\t'; - case 'v': index++; return '\v'; + case 'v': index++; return '\x0B'; case 'b': index++; return '\b'; case 'f': index++; return '\f'; // Skips the following span of white-space. @@ -790,19 +821,16 @@ define(function(require, exports, module) { tokenStart = index; index += 2; // -- - var char = input.charAt(index) + var character = input.charAt(index) , content = '' , isLong = false , commentStart = index; - if ('[' === char) { + if ('[' === character) { content = readLongString(); // This wasn't a multiline comment after all. - if (false === content) content = char; - else { - isLong = true; - index += 2; // Trailing -- - } + if (false === content) content = character; + else isLong = true; } // Scan until next line as long as it's not a multiline comment. if (!isLong) { @@ -829,7 +857,7 @@ define(function(require, exports, module) { var level = 0 , content = '' , terminator = false - , char, stringStart; + , character, stringStart; index++; // [ @@ -848,11 +876,11 @@ define(function(require, exports, module) { stringStart = index; while (index < length) { - char = input.charAt(index++); + character = input.charAt(index++); // We have to keep track of newlines as `skipWhiteSpace()` does not get // to scan this part. - if (isLineTerminator(char.charCodeAt(0))) { + if (isLineTerminator(character.charCodeAt(0))) { line++; lineStart = index; } @@ -860,7 +888,7 @@ define(function(require, exports, module) { // Once the delimiter is found, iterate through the depth count and see // if it matches. - if (']' === char) { + if (']' === character) { terminator = true; for (var i = 0; i < level; i++) { if ('=' !== input.charAt(index + i)) terminator = false; @@ -870,11 +898,6 @@ define(function(require, exports, module) { // We reached the end of the multiline string. Get out now. if (terminator) break; - - if ('\\' === char) { - content += input.slice(stringStart, index - 1) + readEscapeSequence(); - stringStart = index; - } } content += input.slice(stringStart, index - 1); index += level + 1; @@ -905,16 +928,6 @@ define(function(require, exports, module) { return false; } - // Check if the given expression exists and raise an exception if not. - // - // As expressions can return null due to the design of the parser, we often - // need this strict expression check as well. - - function expectExpression(expression) { - if (null == expression) raiseUnexpectedToken('', token); - else return expression; - } - // Expect the next token value to match. If not, throw an exception. function expect(value) { @@ -924,31 +937,31 @@ define(function(require, exports, module) { // ### Validation functions - function isWhiteSpace(char) { - return 9 === char || 32 === char || 0xB === char || 0xC === char; + function isWhiteSpace(character) { + return 9 === character || 32 === character || 0xB === character || 0xC === character; } - function isLineTerminator(char) { - return 10 === char || 13 === char; + function isLineTerminator(character) { + return 10 === character || 13 === character; } - function isDecDigit(char) { - return char >= 48 && char <= 57; + function isDecDigit(character) { + return character >= 48 && character <= 57; } - function isHexDigit(char) { - return (char >= 48 && char <= 57) || (char >= 97 && char <= 102) || (char >= 65 && char <= 70); + function isHexDigit(character) { + return (character >= 48 && character <= 57) || (character >= 97 && character <= 102) || (character >= 65 && character <= 70); } // From [Lua 5.2](http://www.lua.org/manual/5.2/manual.html#8.1) onwards // identifiers cannot use locale-dependet letters. - function isIdentifierStart(char) { - return (char >= 65 && char <= 90) || (char >= 97 && char <= 122) || 95 === char; + function isIdentifierStart(character) { + return (character >= 65 && character <= 90) || (character >= 97 && character <= 122) || 95 === character; } - function isIdentifierPart(char) { - return (char >= 65 && char <= 90) || (char >= 97 && char <= 122) || 95 === char || (char >= 48 && char <= 57); + function isIdentifierPart(character) { + return (character >= 65 && character <= 90) || (character >= 97 && character <= 122) || 95 === character || (character >= 48 && character <= 57); } // [3.1 Lexical Conventions](http://www.lua.org/manual/5.2/manual.html#3.1) @@ -974,7 +987,7 @@ define(function(require, exports, module) { } function isUnary(token) { - if (Punctuator === token.type) return ~'#-'.indexOf(token.value); + if (Punctuator === token.type) return '#-'.indexOf(token.value) >= 0; if (Keyword === token.type) return 'not' === token.value; return false; } @@ -1004,6 +1017,61 @@ define(function(require, exports, module) { } } + // Scope + // ----- + + // Store each block scope as a an array of identifier names. Each scope is + // stored in an FILO-array. + var scopes + // The current scope index + , scopeDepth + // A list of all global identifier nodes. + , globals + // A list of all global identifiers names used for faster lookup. + // @TODO benchmark, with exposing the globals this entire implementation + // should probably change. + , globalNames; + + // Create a new scope inheriting all declarations from the previous scope. + function createScope() { + scopes.push(Array.apply(null, scopes[scopeDepth++])); + } + + // Exit and remove the current scope. + function exitScope() { + scopes.pop(); + scopeDepth--; + } + + // Add identifier name to the current scope if it doesnt already exist. + function scopeIdentifierName(name) { + if (-1 !== indexOf.call(scopes[scopeDepth], name)) return; + scopes[scopeDepth].push(name); + } + + // Add identifier to the current scope + function scopeIdentifier(node) { + scopeIdentifierName(node.name); + attachScope(node, true); + } + + // Attach scope information to node. If the node is global, store it in the + // globals array so we can return the information to the user. + function attachScope(node, isLocal) { + if (!isLocal && -1 === indexOf.call(globalNames, node.name)) { + globalNames.push(node.name); + globals.push(node); + } + + node.isLocal = isLocal; + } + + // Is the identifier name available in this scope. + function scopeHasName(name) { + return (-1 !== indexOf.call(scopes[scopeDepth], name)); + } + + // Parse functions // --------------- @@ -1027,6 +1095,9 @@ define(function(require, exports, module) { var block = [] , statement; + // Each block creates a new scope. + if (options.scope) createScope(); + while (!isBlockFollow(token)) { // Return has to be the last statement in a block. if ('return' === token.value) { @@ -1038,6 +1109,8 @@ define(function(require, exports, module) { // ignore some statements, such as EmptyStatement. if (statement) block.push(statement); } + + if (options.scope) exitScope(); // Doesn't really need an ast node return block; } @@ -1081,7 +1154,14 @@ define(function(require, exports, module) { // label ::= '::' Name '::' function parseLabelStatement() { - var label = parseIdentifier(); + var name = token.value + , label = parseIdentifier(); + + if (options.scope) { + scopeIdentifierName('::' + name + '::'); + attachScope(label, true); + } + expect('::'); return ast.labelStatement(label); } @@ -1095,7 +1175,10 @@ define(function(require, exports, module) { // goto ::= 'goto' Name function parseGotoStatement() { - var label = parseIdentifier(); + var name = token.value + , label = parseIdentifier(); + + if (options.scope) label.isLabel = scopeHasName('::' + name + '::'); return ast.gotoStatement(label); } @@ -1110,7 +1193,7 @@ define(function(require, exports, module) { // while ::= 'while' exp 'do' block 'end' function parseWhileStatement() { - var condition = parseExpression(); + var condition = parseExpectedExpression(); expect('do'); var body = parseBlock(); expect('end'); @@ -1122,7 +1205,7 @@ define(function(require, exports, module) { function parseRepeatStatement() { var body = parseBlock(); expect('until'); - var condition = expectExpression(parseExpression()); + var condition = parseExpectedExpression(); return ast.repeatStatement(condition, body); } @@ -1135,7 +1218,7 @@ define(function(require, exports, module) { var expression = parseExpression(); if (null != expression) expressions.push(expression); while (consume(',')) { - expression = expectExpression(parseExpression()); + expression = parseExpectedExpression(); expressions.push(expression); } consume(';'); // grammar tells us ; is optional here. @@ -1151,12 +1234,17 @@ define(function(require, exports, module) { , condition , body; - do { - condition = parseExpression(); + condition = parseExpectedExpression(); + expect('then'); + body = parseBlock(); + clauses.push(ast.ifClause(condition, body)); + + while (consume('elseif')) { + condition = parseExpectedExpression(); expect('then'); body = parseBlock(); clauses.push(ast.elseifClause(condition, body)); - } while (consume('elseif')); + } if (consume('else')) { body = parseBlock(); @@ -1178,16 +1266,19 @@ define(function(require, exports, module) { var variable = parseIdentifier() , body; + // The start-identifier is local. + if (options.scope) scopeIdentifier(variable); + // If the first expression is followed by a `=` punctuator, this is a // Numeric For Statement. if (consume('=')) { // Start expression - var start = expectExpression(parseExpression()); + var start = parseExpectedExpression(); expect(','); // End expression - var end = expectExpression(parseExpression()); + var end = parseExpectedExpression(); // Optional step expression - var step = consume(',') ? expectExpression(parseExpression()) : null; + var step = consume(',') ? parseExpectedExpression() : null; expect('do'); body = parseBlock(); @@ -1199,13 +1290,18 @@ define(function(require, exports, module) { } else { // The namelist can contain one or more identifiers. var variables = [variable]; - while (consume(',')) variables.push(parseIdentifier()); + while (consume(',')) { + variable = parseIdentifier(); + // Each variable in the namelist is locally scoped. + if (options.scope) scopeIdentifier(variable); + variables.push(variable); + } expect('in'); var iterators = []; // One or more expressions in the explist. do { - var expression = expectExpression(parseExpression()); + var expression = parseExpectedExpression(); iterators.push(expression); } while (consume(',')); @@ -1228,26 +1324,41 @@ define(function(require, exports, module) { // | 'local' Name {',' Name} ['=' exp {',' exp} function parseLocalStatement() { + var name; + if (Identifier === token.type) { - var variables = []; - var init = []; + var variables = [] + , init = []; do { - variables.push(parseIdentifier()); + name = parseIdentifier(); + + variables.push(name); } while (consume(',')); if (consume('=')) { do { - var expression = expectExpression(parseExpression()); + var expression = parseExpectedExpression(); init.push(expression); } while (consume(',')); } + // Declarations doesn't exist before the statement has been evaluated. + // Therefore assignments can't use their declarator. And the identifiers + // shouldn't be added to the scope until the statement is complete. + if (options.scope) { + for (var i = 0, l = variables.length; i < l; i++) { + scopeIdentifier(variables[i]); + } + } + return ast.localStatement(variables, init); } if (consume('function')) { + name = parseIdentifier(); + if (options.scope) scopeIdentifier(name); + // MemberExpressions are not allowed in local function statements. - var name = parseIdentifier(); return parseFunctionDeclaration(name, true); } else { raiseUnexpectedToken('', token); @@ -1268,18 +1379,19 @@ define(function(require, exports, module) { , expression = parsePrefixExpression(); if (null == expression) return unexpected(token); - if (~',='.indexOf(token.value)) { + if (',='.indexOf(token.value) >= 0) { var variables = [expression] , init = [] , exp; while (consume(',')) { - exp = expectExpression(parsePrefixExpression()); + exp = parsePrefixExpression(); + if (null == exp) raiseUnexpectedToken('', token); variables.push(exp); } expect('='); do { - exp = expectExpression(parseExpression()); + exp = parseExpectedExpression(); init.push(exp); } while (consume(',')); return ast.assignmentStatement(variables, init); @@ -1306,7 +1418,6 @@ define(function(require, exports, module) { return ast.identifier(identifier); } - // Parse the functions parameters and body block. The name should already // have been parsed and passed to this declaration function. By separating // this we allow for anonymous functions in expressions. @@ -1318,28 +1429,39 @@ define(function(require, exports, module) { // parlist ::= Name {',' Name} | [',' '...'] | '...' function parseFunctionDeclaration(name, isLocal) { - var isVararg = false; var parameters = []; expect('('); - if (consume('...')) isVararg = true; - else if (Identifier === token.type) { - do { - if (consume('...')) { - isVararg = true; + // The declaration has arguments + if (!consume(')')) { + // Arguments are a comma separated list of identifiers, optionally ending + // with a vararg. + while (true) { + if (Identifier === token.type) { + var parameter = parseIdentifier(); + // Function parameters are local. + if (options.scope) scopeIdentifier(parameter); + + parameters.push(parameter); + + if (consume(',')) continue; + else if (consume(')')) break; + // No arguments are allowed after a vararg. + } else if (VarargLiteral === token.type) { + parameters.push(parsePrimaryExpression()); + expect(')'); break; + } else { + raiseUnexpectedToken(' or \'...\'', token); } - parameters.push(parseIdentifier()); - } while (consume(',')); + } } - if (isVararg) expect(')'); - else if (!consume(')')) raiseUnexpectedToken(' or \'...\'', token); var body = parseBlock(); expect('end'); isLocal = isLocal || false; - return ast.functionStatement(name, parameters, isVararg, isLocal, body); + return ast.functionStatement(name, parameters, isLocal, body); } // Parse the function name as identifiers and member expressions. @@ -1347,14 +1469,20 @@ define(function(require, exports, module) { // Name {'.' Name} [':' Name] function parseFunctionName() { - var base = parseIdentifier(); + var base = parseIdentifier() + , name; + if (options.scope) attachScope(base, false); while (consume('.')) { - base = ast.memberExpression(base, '.', parseIdentifier()); + name = parseIdentifier(); + if (options.scope) attachScope(name, false); + base = ast.memberExpression(base, '.', name); } if (consume(':')) { - base = ast.memberExpression(base, ':', parseIdentifier()); + name = parseIdentifier(); + if (options.scope) attachScope(name, false); + base = ast.memberExpression(base, ':', name); } return base; @@ -1372,15 +1500,15 @@ define(function(require, exports, module) { while (true) { if (Punctuator === token.type && consume('[')) { - key = parseExpression(); + key = parseExpectedExpression(); expect(']'); expect('='); - value = expectExpression(parseExpression()); + value = parseExpectedExpression(); fields.push(ast.tableKey(key, value)); } else if (Identifier === token.type) { - key = parseExpression(); + key = parseExpectedExpression(); if (consume('=')) { - value = parseExpression(); + value = parseExpectedExpression(); fields.push(ast.tableKeyString(key, value)); } else { fields.push(ast.tableValue(key)); @@ -1389,7 +1517,7 @@ define(function(require, exports, module) { if (null == (value = parseExpression())) break; fields.push(ast.tableValue(value)); } - if (~',;'.indexOf(token.value)) { + if (',;'.indexOf(token.value) >= 0) { next(); continue; } @@ -1402,7 +1530,8 @@ define(function(require, exports, module) { // Expression parser // ----------------- // - // Expressions are evaluated and always return a value. + // Expressions are evaluated and always return a value. If nothing is + // matched null will be returned. // // exp ::= (unop exp | primary | prefixexp ) { binop exp } // @@ -1418,6 +1547,15 @@ define(function(require, exports, module) { return expression; } + // Parse an expression expecting it to be valid. + + function parseExpectedExpression() { + var expression = parseExpression(); + if (null == expression) raiseUnexpectedToken('', token); + else return expression; + } + + // Return the precedence priority of the operator. // // As unary `-` can't be distinguished from binary `-`, unary precedence @@ -1427,23 +1565,23 @@ define(function(require, exports, module) { // the expensive CompareICStub which took ~8% of the parse time. function binaryPrecedence(operator) { - var char = operator.charCodeAt(0) + var character = operator.charCodeAt(0) , length = operator.length; if (1 === length) { - switch (char) { + switch (character) { case 94: return 10; // ^ case 42: case 47: case 37: return 7; // * / % case 43: case 45: return 6; // + - case 60: case 62: return 3; // < > } } else if (2 === length) { - switch (char) { + switch (character) { case 46: return 5; // .. case 60: case 62: case 61: case 126: return 3; // <= >= == ~= case 111: return 1; // or } - } else if (97 === char && 'and' === operator) return 2; + } else if (97 === character && 'and' === operator) return 2; return 0; } @@ -1464,7 +1602,8 @@ define(function(require, exports, module) { // UnaryExpression if (isUnary(token)) { next(); - var argument = expectExpression(parseSubExpression(8)); + var argument = parseSubExpression(8); + if (argument == null) raiseUnexpectedToken('', token); expression = ast.unaryExpression(operator, argument); } if (null == expression) { @@ -1490,7 +1629,8 @@ define(function(require, exports, module) { // Right-hand precedence operators if ('^' === operator || '..' === operator) precedence--; next(); - var right = expectExpression(parseSubExpression(precedence)); + var right = parseSubExpression(precedence); + if (null == right) raiseUnexpectedToken('', token); expression = ast.binaryExpression(operator, expression, right); } return expression; @@ -1503,14 +1643,20 @@ define(function(require, exports, module) { // args ::= '(' [explist] ')' | tableconstructor | String function parsePrefixExpression() { - var base; + var base, name + // Keep track of the scope, if a parent is local so are the children. + , isLocal; // The prefix if (Identifier === token.type) { + name = token.value; base = parseIdentifier(); + // Set the parent scope. + if (options.scope) attachScope(base, isLocal = scopeHasName(name)); } else if (consume('(')) { - base = parseExpression(); + base = parseExpectedExpression(); expect(')'); + if (options.scope) isLocal = base.isLocal; } else { return null; } @@ -1518,23 +1664,25 @@ define(function(require, exports, module) { // The suffix var expression, identifier; while (true) { - expectExpression(base); if (Punctuator === token.type) { switch (token.value) { case '[': next(); - expression = parseExpression(); + expression = parseExpectedExpression(); base = ast.indexExpression(base, expression); expect(']'); break; case '.': next(); identifier = parseIdentifier(); + // Inherit the scope + if (options.scope) attachScope(identifier, isLocal); base = ast.memberExpression(base, '.', identifier); break; case ':': next(); identifier = parseIdentifier(); + if (options.scope) attachScope(identifier, isLocal); base = ast.memberExpression(base, ':', identifier); // Once a : is found, this has to be a callexpression, otherwise // throw an error. @@ -1569,7 +1717,7 @@ define(function(require, exports, module) { var expression = parseExpression(); if (null != expression) expressions.push(expression); while (consume(',')) { - expression = expectExpression(parseExpression()); + expression = parseExpectedExpression(); expressions.push(expression); } @@ -1583,9 +1731,7 @@ define(function(require, exports, module) { } } else if (StringLiteral === token.type) { - var string = token.value; - next(); - return ast.stringCallExpression(base, string); + return ast.stringCallExpression(base, parsePrimaryExpression()); } raiseUnexpectedToken('function arguments', token); @@ -1595,21 +1741,19 @@ define(function(require, exports, module) { // | functiondef | tableconstructor | '...' function parsePrimaryExpression() { - var literals = StringLiteral | NumericLiteral | BooleanLiteral | NilLiteral - , value = token.value; + var literals = StringLiteral | NumericLiteral | BooleanLiteral | NilLiteral | VarargLiteral + , value = token.value + , type = token.type; - if (token.type & literals) { + if (type & literals) { var raw = input.slice(token.range[0], token.range[1]); next(); - return ast.literal(value, raw); - } else if (Keyword === token.type && 'function' === token.value) { + return ast.literal(type, value, raw); + } else if (Keyword === type && 'function' === value) { next(); return parseFunctionDeclaration(null); - } else if (Punctuator === token.type) { - // Semantically dotsliteral can only exist within a vararg functions. - if (consume('...')) return ast.varargLiteral(value); - if (consume('{')) return parseTableConstructor(); - } + } else if (consume('{')) + return parseTableConstructor(); } // Parser @@ -1618,6 +1762,8 @@ define(function(require, exports, module) { // Export the main parser. // // - `wait` Hold parsing until end() is called. Defaults to false + // - `comments` Store comments. Defaults to true. + // - `scope` Track identifier scope. Defaults to false. // // Example: // @@ -1641,6 +1787,11 @@ define(function(require, exports, module) { line = 1; lineStart = 0; length = input.length; + // When tracking identifier scope, initialize with an empty scope. + scopes = [[]]; + scopeDepth = 0; + globals = []; + globalNames = []; if (options.comments) comments = []; if (!options.wait) return end(); @@ -1668,6 +1819,7 @@ define(function(require, exports, module) { var chunk = parseChunk(); if (options.comments) chunk.comments = comments; + if (options.scope) chunk.globals = globals; return chunk; }