diff --git a/Objective-J/Bootstrap.js b/Objective-J/Bootstrap.js index ddd7263bf..2810c76fd 100644 --- a/Objective-J/Bootstrap.js +++ b/Objective-J/Bootstrap.js @@ -49,17 +49,26 @@ if (DOMBaseElementsCount > 0) if (typeof OBJJ_COMPILER_FLAGS !== 'undefined') { - var flags = 0; + var flags = {}; for (var i = 0; i < OBJJ_COMPILER_FLAGS.length; i++) { - var flag = ObjJAcornCompiler.Flags[OBJJ_COMPILER_FLAGS[i]]; - - if (flag != null) + switch (OBJJ_COMPILER_FLAGS[i]) { - flags |= flag; + case "IncludeDebugSymbols": + flags.includeMethodFunctionNames = true; + break; + + case "IncludeTypeSignatures": + flags.includeIvarTypeSignatures = true; + flags.includeMethodArgumentTypeSignatures = true; + break; + + case "InlineMsgSend": + flags.inlineMsgSendFunctions = true; + break; } } - exports.setCurrentCompilerFlags(flags); + FileExecutable.setCurrentCompilerFlags(flags); } // Turn the main file into a URL. diff --git a/Objective-J/CFHTTPRequest.js b/Objective-J/CFHTTPRequest.js index 2c112e749..d07064696 100755 --- a/Objective-J/CFHTTPRequest.js +++ b/Objective-J/CFHTTPRequest.js @@ -371,7 +371,7 @@ function FileRequest(/*CFURL*/ aURL, onsuccess, onfailure, onprogress) { var aFilePath = aURL.toString().substring(5), OS = require("os"), - gccFlags = require("objective-j").currentGccCompilerFlags(), + gccFlags = require("objective-j").FileExecutable.currentGccCompilerFlags(), chunk, fileContents = ""; diff --git a/Objective-J/CommonJS/lib/objective-j.js b/Objective-J/CommonJS/lib/objective-j.js index 1d74d0f10..4d7feb707 100644 --- a/Objective-J/CommonJS/lib/objective-j.js +++ b/Objective-J/CommonJS/lib/objective-j.js @@ -1,6 +1,7 @@ var FILE = require("file"); var sprintf = require("printf").sprintf; +var OS = require("os"); var window = exports.window = require("browser/window"); @@ -80,6 +81,7 @@ exports.run = function(args) // copy the args since we're going to modify them var argv = args.slice(1); + var outputFormatInXML = false; if (argv[0] === "--version" || argv[0] === "-v") { @@ -120,25 +122,33 @@ exports.run = function(args) case "-x": case "--xml": argv.shift(); - exports.outputFormatInXML = true; + exports.messageOutputFormatInXML = true; + outputFormatInXML = true; break; case "-g": case "--include-debug-symbols": argv.shift(); - (OBJJ_COMPILER_FLAGS || (OBJJ_COMPILER_FLAGS = [])).push("IncludeDebugSymbols"); + var flags = ObjectiveJ.FileExecutable.currentCompilerFlags(); + flags.includeMethodFunctionNames = true; + ObjectiveJ.FileExecutable.setCurrentCompilerFlags(flags); break; case "-T": case "--dont-include-type-signatures": argv.shift(); - (OBJJ_COMPILER_FLAGS || (OBJJ_COMPILER_FLAGS = [])).push("IncludeTypeSignatures"); + var flags = ObjectiveJ.FileExecutable.currentCompilerFlags(); + flags.includeIvarTypeSignatures = true; + flags.includeMethodArgumentTypeSignatures = true; + ObjectiveJ.FileExecutable.setCurrentCompilerFlags(flags); break; case "-O2": case "--inline-msg-send": argv.shift(); - (OBJJ_COMPILER_FLAGS || (OBJJ_COMPILER_FLAGS = [])).push("InlineMsgSend"); + var flags = ObjectiveJ.FileExecutable.currentCompilerFlags(); + flags.inlineMsgSendFunctions = true; + ObjectiveJ.FileExecutable.setCurrentCompilerFlags(flags); break; } } @@ -160,19 +170,7 @@ exports.run = function(args) } catch(e) { - if (exports.outputFormatInXML) - { - var dict = new CFMutableDictionary(); - dict.addValueForKey('line', e.line ? e.line : 0); - dict.addValueForKey('sourcePath', e.path ? e.path : mainFilePath); - dict.addValueForKey('message', e.message); - - errors.push(dict); - } - else - { - errors.push("\n" + e); - } + errors.push(e); } if (typeof main === "function") @@ -194,10 +192,8 @@ exports.run = function(args) if (errors.length) { - if (exports.outputFormatInXML) - throw CFPropertyListCreateXMLData(errors, kCFPropertyListXMLFormat_v1_0).rawString(); - else - throw errors; + // Make sure we get exit will failiure + OS.exit(1); } } else @@ -271,7 +267,6 @@ function getPackage() { exports.version = function() { return getPackage()["version"]; } exports.revision = function() { return getPackage()["cappuccino-revision"]; } exports.timestamp = function() { return new Date(getPackage()["cappuccino-timestamp"]); } -exports.outputFormatInXML = false; exports.fullVersionString = function() { return sprintf("objective-j %s (%04d-%02d-%02d %s)", diff --git a/Objective-J/CommonJS/lib/objective-j/compiler.js b/Objective-J/CommonJS/lib/objective-j/compiler.js index 6f761e3a5..681c00116 100644 --- a/Objective-J/CommonJS/lib/objective-j/compiler.js +++ b/Objective-J/CommonJS/lib/objective-j/compiler.js @@ -6,10 +6,6 @@ var FILE = require("file"), require("objective-j/rhino/regexp-rhino-patch"); -ObjectiveJ.ObjJAcornCompiler.Flags.Preprocess = 1 << 10; -ObjectiveJ.ObjJAcornCompiler.Flags.Compress = 1 << 11; -ObjectiveJ.ObjJAcornCompiler.Flags.CheckSyntax = 1 << 12; - var compressors = { ss : { id : "minify/shrinksafe" } //,yui : { id : "minify/yuicompressor" } @@ -37,9 +33,8 @@ function compressor(code) function compileWithResolvedFlags(aFilePath, objjcFlags, gccFlags, asPlainJavascript) { - var shouldObjjPreprocess = objjcFlags & ObjectiveJ.ObjJAcornCompiler.Flags.Preprocess, - shouldCheckSyntax = objjcFlags & ObjectiveJ.ObjJAcornCompiler.Flags.CheckSyntax, - shouldCompress = objjcFlags & ObjectiveJ.ObjJAcornCompiler.Flags.Compress, + var shouldObjjPreprocess = true, + shouldCompress = objjcFlags.compress, fileContents = "", executable, code; @@ -118,7 +113,7 @@ function compileWithResolvedFlags(aFilePath, objjcFlags, gccFlags, asPlainJavasc } } - ObjectiveJ.setCurrentCompilerFlags(objjcFlags); + ObjectiveJ.FileExecutable.setCurrentCompilerFlags(objjcFlags); ObjectiveJ.make_narwhal_factory(absolutePath, basePath, translateFilenameToPath)(require, {}, module, system, print); executable = new ObjectiveJ.FileExecutable(FILE.basename(aFilePath)); @@ -153,7 +148,7 @@ function resolveFlags(args) count = args.length, gccFlags = [], - objjcFlags = ObjectiveJ.ObjJAcornCompiler.Flags.Preprocess | ObjectiveJ.ObjJAcornCompiler.Flags.CheckSyntax; + objjcFlags = {}; for (; index < count; ++index) { @@ -180,27 +175,23 @@ function resolveFlags(args) } } - else if (argument.indexOf("-E") === 0) - objjcFlags &= ~ObjectiveJ.ObjJAcornCompiler.Flags.Preprocess; - - else if (argument.indexOf("-S") === 0) - objjcFlags &= ~ObjectiveJ.ObjJAcornCompiler.Flags.CheckSyntax; - else if (argument.indexOf("-T") === 0) - objjcFlags &= ~ObjectiveJ.ObjJAcornCompiler.Flags.IncludeTypeSignatures; - + { + objjcFlags.includeIvarTypeSignatures = false; + objjcFlags.includeMethodArgumentTypeSignatures = false; + } else if (argument.indexOf("-g") === 0) - objjcFlags |= ObjectiveJ.ObjJAcornCompiler.Flags.IncludeDebugSymbols; + objjcFlags.includeMethodFunctionNames = true; else if (argument.indexOf("-O") === 0) { - objjcFlags |= ObjectiveJ.ObjJAcornCompiler.Flags.Compress; + objjcFlags.compress = true; // FIXME: currently we are sending in '-O2' when we want InlineMsgSend. Here we only check if we it is '-O...' if (argument.length > 2) - objjcFlags |= ObjectiveJ.ObjJAcornCompiler.Flags.InlineMsgSend; + objjcFlags.inlineMsgSendFunctions = true; } else if (argument.indexOf("-G") === 0) - objjcFlags |= ObjectiveJ.ObjJAcornCompiler.Flags.Generate; + objjcFlags.generate = true; else filePaths.push(argument); @@ -223,7 +214,7 @@ exports.main = function(args) { var shouldPrintOutput = false, asPlainJavascript = false, - objjcFlags = 0; + objjcFlags = {}; var argv = args.slice(1); @@ -251,7 +242,8 @@ exports.main = function(args) if (argv[0] === "-T" || argv[0] === "--includeTypeSignatures") { - objjcFlags |= ObjectiveJ.ObjJAcornCompiler.Flags.IncludeTypeSignatures; + objjcFlags.includeIvarTypeSignatures = true; + objjcFlags.includeMethodArgumentTypeSignatures = true; argv.shift(); continue; } @@ -280,9 +272,19 @@ exports.main = function(args) var resolved = resolveFlags(argv), outputFilePaths = resolved.outputFilePaths, - gccFlags = resolved.gccFlags; + gccFlags = resolved.gccFlags, + resolvedObjjcFlags = resolved.objjcFlags; + + // Merge resolved keys into objjcFlags + for (var key in resolvedObjjcFlags) + { + if (resolvedObjjcFlags.hasOwnProperty(key)) + { + if (resolvedObjjcFlags[key]) + objjcFlags[key] = resolvedObjjcFlags[key]; + } + } - objjcFlags |= resolved.objjcFlags; resolved.filePaths.forEach(function(filePath, index) { if (!shouldPrintOutput) diff --git a/Objective-J/CommonJS/lib/objective-j/jake/bundletask.js b/Objective-J/CommonJS/lib/objective-j/jake/bundletask.js index 613d9b254..136a8388e 100644 --- a/Objective-J/CommonJS/lib/objective-j/jake/bundletask.js +++ b/Objective-J/CommonJS/lib/objective-j/jake/bundletask.js @@ -907,7 +907,7 @@ BundleTask.prototype.defineSourceTasks = function() basePath = absolutePath.substring(0, absolutePath.length - theTranslatedFilename.length); // Here we set the current compiler flags so the load system will know what compiler flags to use - ObjectiveJ.setCurrentGccCompilerFlags(environmentCompilerFlags); + ObjectiveJ.FileExecutable.setCurrentGccCompilerFlags(environmentCompilerFlags); // Here we tell the CFBundle to load frameworks for the current build enviroment and not the enviroment that is running CFBundle.environments = function() {return [anEnvironment.name(), "ObjJ"]}; ObjectiveJ.make_narwhal_factory(absolutePath, basePath, translateFilenameToPath)(require, e, module, system, print); diff --git a/Objective-J/Executable.js b/Objective-J/Executable.js index 227b890a4..8da88e457 100644 --- a/Objective-J/Executable.js +++ b/Objective-J/Executable.js @@ -169,6 +169,10 @@ Executable.prototype.execute = function() this._compiler.popImport(); this.setCode(this._compiler.compilePass2()); + + if (FileExecutable.printWarningsAndErrors(this._compiler, exports.messageOutputFormatInXML)) + throw "Compilation error"; + this._compiler = null; } @@ -499,7 +503,7 @@ Executable.fileExecutableSearcherForURL = function(/*CFURL*/ referenceURL) { if (!aStaticResource) { - var compilingFileUrl = ObjJAcornCompiler ? ObjJAcornCompiler.currentCompileFile : null; + var compilingFileUrl = exports.ObjJCompiler ? exports.ObjJCompiler.currentCompileFile : null; throw new Error("Could not load file at " + aURL + (compilingFileUrl ? " when compiling " + compilingFileUrl : "")); } diff --git a/Objective-J/FileExecutable.js b/Objective-J/FileExecutable.js index a3b471a7e..d6e72e655 100644 --- a/Objective-J/FileExecutable.js +++ b/Objective-J/FileExecutable.js @@ -22,6 +22,9 @@ var FileExecutablesForURLStrings = { }; +var currentCompilerFlags = {}; +var currentGccCompilerFlags = ""; + function FileExecutable(/*CFURL|String*/ aURL, /*Dictionary*/ aFilenameTranslateDictionary) { aURL = makeAbsoluteURL(aURL); @@ -41,7 +44,17 @@ function FileExecutable(/*CFURL|String*/ aURL, /*Dictionary*/ aFilenameTranslate if (fileContents.match(/^@STATIC;/)) executable = decompile(fileContents, aURL); else if ((extension === "j" || !extension) && !fileContents.match(/^{/)) - executable = exports.ObjJAcornCompiler.compileFileDependencies(fileContents, aURL, exports.currentCompilerFlags()); + { + var compiler = exports.ObjJCompiler.compileFileDependencies(fileContents, aURL, currentCompilerFlags || {}); + + if (FileExecutable.printWarningsAndErrors(compiler, exports.messageOutputFormatInXML)) + throw "Compilation error"; + + var fileDependencies = compiler.dependencies.map(function (aFileDep) { + return new FileDependency(new CFURL(aFileDep.url), aFileDep.isLocal); + }); + executable = new Executable(compiler.jsBuffer ? compiler.jsBuffer.toString() : null, fileDependencies, compiler.URL, null, compiler); + } else executable = new Executable(fileContents, [], aURL); @@ -139,3 +152,110 @@ FileExecutable._lookupCachedFunction = function(/*CFURL|String*/ aURL) aURL = typeof aURL === "string" ? aURL : aURL.absoluteString(); return FunctionCache[aURL]; } + +FileExecutable.setCurrentGccCompilerFlags = function(/*String*/ compilerFlags) +{ + if (currentGccCompilerFlags === compilerFlags) return; + + currentGccCompilerFlags = compilerFlags; + + var args = compilerFlags.split(" "), + count = args.length, + objjcFlags = {}; + + for (var index = 0; index < count; ++index) + { + var argument = args[index]; + + if (argument.indexOf("-g") === 0) + objjcFlags.includeMethodFunctionNames = true; + else if (argument.indexOf("-O") === 0) { + objjcFlags.inlineMsgSendFunctions = true; + // FIXME: currently we are sending in '-O2' when we want InlineMsgSend. Here we only check if it is '-O...'. + // Maybe we should have some other option for this + if (argument.length > 2) + objjcFlags.inlineMsgSendFunctions = true; + } + //else if (argument.indexOf("-G") === 0) + //objjcFlags |= ObjJAcornCompiler.Flags.Generate; + else if (argument.indexOf("-T") === 0) { + objjcFlags.includeIvarTypeSignatures = false; + objjcFlags.includeMethodArgumentTypeSignatures = false; + } + } + + FileExecutable.setCurrentCompilerFlags(objjcFlags); +} + +FileExecutable.currentGccCompilerFlags = function(/*String*/ compilerFlags) +{ + return currentGccCompilerFlags; +} + +FileExecutable.setCurrentCompilerFlags = function(/*JSObject*/ compilerFlags) +{ + currentCompilerFlags = compilerFlags; + // Here we set the default flags if they are not included. We do this as the default values + // in the compiler might not be what we want. + if (currentCompilerFlags.transformNamedFunctionDeclarationToAssignment == null) + currentCompilerFlags.transformNamedFunctionDeclarationToAssignment = true; + if (currentCompilerFlags.sourceMap == null) + currentCompilerFlags.sourceMap = false; + if (currentCompilerFlags.inlineMsgSendFunctions == null) + currentCompilerFlags.inlineMsgSendFunctions = false; +} + +FileExecutable.currentCompilerFlags = function(/*JSObject*/ compilerFlags) +{ + return currentCompilerFlags; +} + +/*! + This funtion prints all errors and warnings for the provieded compiler. It returns true if there + are any errors in the list. it will print it in xml format if printXML is 'true' + */ +FileExecutable.printWarningsAndErrors = function(/*ObjJCompiler*/ compiler, /*BOOL*/ printXML) +{ + var warnings = [], + anyErrors = false; + + for (var i = 0; i < compiler.warningsAndErrors.length; i++) + { + var warning = compiler.warningsAndErrors[i], + message = compiler.prettifyMessage(warning); + + // Set anyErrors to 'true' if there are any errors in the list + anyErrors = anyErrors || warning.messageType === "ERROR"; +#ifdef BROWSER + console.log(message); +#else + if (printXML) + { + var dict = new CFMutableDictionary(); + if (warning.messageOnLine != null) dict.addValueForKey('line', warning.messageOnLine) + if (warning.path != null) dict.addValueForKey('sourcePath', new CFURL(warning.path).path()) + if (message != null) dict.addValueForKey('message', message) + + warnings.push(dict); + } + else + { + print(message); + } +#endif + } + +#ifndef BROWSER + if (warnings.length && printXML) + try { + print(CFPropertyListCreateXMLData(warnings, kCFPropertyListXMLFormat_v1_0).rawString()); + } catch (e) { + print ("XML encode error: " + e); + } +#endif + + return anyErrors; +} + +// Set the compiler flags to empty dictionary so the default values are correct. +FileExecutable.setCurrentCompilerFlags({}); diff --git a/Objective-J/ObjJAcornCompiler.js b/Objective-J/ObjJAcornCompiler.js index fb53b2481..961c3f06d 100644 --- a/Objective-J/ObjJAcornCompiler.js +++ b/Objective-J/ObjJAcornCompiler.js @@ -1,31 +1,55 @@ -/* - * ObjJAcornCompiler.js - * Objective-J - * - * Created by Martin Carlberg. - * Copyright 2013, Martin Carlberg. - * - * This library is free software; you can redistribute it and/or - * modify it under the terms of the GNU Lesser General Public - * License as published by the Free Software Foundation; either - * version 2.1 of the License, or (at your option) any later version. - * - * This library is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU - * Lesser General Public License for more details. - * - * You should have received a copy of the GNU Lesser General Public - * License along with this library; if not, write to the Free Software - * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA - */ +// ObjJAcornCompiler was written by Martin Carlberg and released under +// an MIT license. +// +// Git repositories for ObjJAcornCompiler are available at +// +// https://github.com/mrcarlberg/ObjJAcornCompiler.git +// +// Please use the [github bug tracker][ghbt] to report issues. +// +// [ghbt]: https://github.com/mrcarlberg/ObjJAcornCompiler/issues +// +// This file defines the main compiler interface. +// +// Copyright 2013, 2014, 2015, 2016, Martin Carlberg. + + +(function(mod) +{ + //print("Compiler INIT! exports: " + typeof exports + ", module: " + typeof module + ", define: " + typeof define); + mod(exports.ObjJCompiler || (exports.ObjJCompiler = {}), exports.acorn, exports.acorn.walk/*, sourceMap*/); // Plain browser env +})(function(exports, acorn, walk, sourceMap) +{ +"use strict"; + +exports.version = "0.3.7"; +//exports.acorn = acorn; var Scope = function(prev, base) { this.vars = Object.create(null); + if (base) for (var key in base) this[key] = base[key]; this.prev = prev; - if (prev) this.compiler = prev.compiler; + + if (prev) + { + this.compiler = prev.compiler; + this.nodeStack = prev.nodeStack.slice(0); + this.nodePriorStack = prev.nodePriorStack.slice(0); + this.nodeStackOverrideType = prev.nodeStackOverrideType.slice(0); + } + else + { + this.nodeStack = []; + this.nodePriorStack = []; + this.nodeStackOverrideType = []; + } +} + +Scope.prototype.toString = function() +{ + return this.ivars ? "ivars: " + JSON.stringify(this.ivars) : ""; } Scope.prototype.compiler = function() @@ -107,9 +131,20 @@ Scope.prototype.copyAddedSelfToIvarsToParent = function() Scope.prototype.addMaybeWarning = function(warning) { - var rootScope = this.rootScope(); + var rootScope = this.rootScope(), + maybeWarnings = rootScope._maybeWarnings; - (rootScope._maybeWarnings || (rootScope._maybeWarnings = [])).push(warning); + if (!maybeWarnings) + rootScope._maybeWarnings = maybeWarnings = [warning]; + else + { + var lastWarning = maybeWarnings[maybeWarnings.length - 1]; + + // MessageSendExpression (and maybe others) will walk some expressions multible times and + // possible generate warnings multible times. Here we check if this warning is already added + if (!lastWarning.isEqualTo(warning)) + maybeWarnings.push(warning); + } } Scope.prototype.maybeWarnings = function() @@ -117,6 +152,113 @@ Scope.prototype.maybeWarnings = function() return this.rootScope()._maybeWarnings; } +Scope.prototype.pushNode = function(node, overrideType) +{ + // Here we push 3 things to a stack. The node, override type and an array that can keep track of prior nodes on this level. + // The current node is also pushed to the last prior array. + // Special case when node is the same as the parent node. This happends when using an override type when walking the AST + // The same prior list is then used instead of a new empty one. + var nodePriorStack = this.nodePriorStack, + length = nodePriorStack.length, + lastPriorList = length ? nodePriorStack[length - 1] : null, + lastNode = length ? this.nodeStack[length - 1] : null; + // First add this node to parent list of nodes, if it has one + if (lastPriorList) { + if (lastNode !== node) { + // If not the same node push the node + lastPriorList.push(node); + } + } + // Use the last prior list if it is the same node + nodePriorStack.push(lastNode === node ? lastPriorList : []); + this.nodeStack.push(node); + this.nodeStackOverrideType.push(overrideType); +} + +Scope.prototype.popNode = function() +{ + this.nodeStackOverrideType.pop(); + this.nodePriorStack.pop(); + return this.nodeStack.pop(); +} + +Scope.prototype.currentNode = function() +{ + var nodeStack = this.nodeStack; + return nodeStack[nodeStack.length - 1]; +} + +Scope.prototype.currentOverrideType = function() +{ + var nodeStackOverrideType = this.nodeStackOverrideType; + return nodeStackOverrideType[nodeStackOverrideType.length - 1]; +} + +Scope.prototype.priorNode = function() +{ + var nodePriorStack = this.nodePriorStack, + length = nodePriorStack.length; + + if (length > 1) { + var parent = nodePriorStack[length - 2], + l = parent.length; + return parent[l - 2] || null; + } + return null; +} + +Scope.prototype.formatDescription = function(index, formatDescription, useOverrideForNode) +{ + var nodeStack = this.nodeStack, + length = nodeStack.length; + + index = index || 0; + if (index >= length) + return null; + + // Get the nodes backwards from the stack + var i = length - index - 1; + var currentNode = nodeStack[i]; + var currentFormatDescription = formatDescription || this.compiler.formatDescription; + // Get the parent descriptions except if no formatDescription was provided, then it is the root description + var parentFormatDescriptions = formatDescription ? formatDescription.parent : currentFormatDescription; + + var nextFormatDescription; + if (parentFormatDescriptions) { + var nodeType = useOverrideForNode === currentNode ? this.nodeStackOverrideType[i] : currentNode.type; + //console.log("nodeType: " + nodeType + ", (useOverrideForNode === currentNode):" + + !!(useOverrideForNode === currentNode)); + nextFormatDescription = parentFormatDescriptions[nodeType]; + if (useOverrideForNode === currentNode && !nextFormatDescription) { + //console.log("Stop"); + return null; + } + } + + //console.log("index: " + index + ", currentNode: " + JSON.stringify(currentNode) + ", currentFormatDescription: " + JSON.stringify(currentFormatDescription) + ", nextFormatDescription: " + JSON.stringify(nextFormatDescription)); + + if (nextFormatDescription) { + // Check for more 'parent' attributes or return nextFormatDescription + return this.formatDescription(index + 1, nextFormatDescription); + } else { + // Check for a virtual node one step up in the stack + nextFormatDescription = this.formatDescription(index + 1, formatDescription, currentNode); + if (nextFormatDescription) + return nextFormatDescription; + else { + // Ok, we have found a format description (currentFormatDescription). + // Lets check if we have any other descriptions dependent on the prior node. + var priorFormatDescriptions = currentFormatDescription.prior; + if (priorFormatDescriptions) { + var priorNode = this.priorNode(), + priorFormatDescription = priorFormatDescriptions[priorNode ? priorNode.type : "None"]; + if (priorFormatDescription) + return priorFormatDescription; + } + return currentFormatDescription; + } + } +} + var GlobalVariableMaybeWarning = function(/* String */ aMessage, /* SpiderMonkey AST node */ node, /* String */ code) { this.message = createMessage(aMessage, node, code); @@ -126,29 +268,129 @@ var GlobalVariableMaybeWarning = function(/* String */ aMessage, /* SpiderMonkey GlobalVariableMaybeWarning.prototype.checkIfWarning = function(/* Scope */ st) { var identifier = this.node.name; - return !st.getLvar(identifier) && typeof global[identifier] === "undefined" && typeof window[identifier] === "undefined" && !st.compiler.getClassDef(identifier); + return !st.getLvar(identifier) && typeof global[identifier] === "undefined" && (typeof window === 'undefined' || typeof window[identifier] === "undefined") && !st.compiler.getClassDef(identifier); } -function StringBuffer() +GlobalVariableMaybeWarning.prototype.isEqualTo = function(/* GlobalVariableMaybeWarning */ aWarning) { - this.atoms = []; + if (this.message.message !== aWarning.message.message) return false; + if (this.node.start !== aWarning.node.start) return false; + if (this.node.end !== aWarning.node.end) return false; + + return true; } -StringBuffer.prototype.toString = function() +function StringBuffer(useSourceNode, file) +{ + if (useSourceNode) { + this.rootNode = new sourceMap.SourceNode(); + this.concat = this.concatSourceNode; + this.toString = this.toStringSourceNode; + this.isEmpty = this.isEmptySourceNode; + this.appendStringBuffer = this.appendStringBufferSourceNode; + this.length = this.lengthSourceNode; + if (file) + this.file = file.toString(); + } else { + this.atoms = []; + this.concat = this.concatString; + this.toString = this.toStringString; + this.isEmpty = this.isEmptyString; + this.appendStringBuffer = this.appendStringBufferString; + this.length = this.lengthString; + } +} + +StringBuffer.prototype.toStringString = function() { return this.atoms.join(""); } -StringBuffer.prototype.concat = function(aString) +StringBuffer.prototype.toStringSourceNode = function() +{ + return this.rootNode.toStringWithSourceMap({file: this.file}); +} + +StringBuffer.prototype.concatString = function(aString) { this.atoms.push(aString); } -StringBuffer.prototype.isEmpty = function() +StringBuffer.prototype.concatSourceNode = function(aString, node) +{ + if (node) { + //console.log("Snippet: " + aString + ", line: " + node.loc.start.line + ", column: " + node.loc.start.column + ", source: " + node.loc.source); + this.rootNode.add(new sourceMap.SourceNode(node.loc.start.line, node.loc.start.column, node.loc.source, aString)); + } else + this.rootNode.add(aString); + if (!this.notEmpty) + this.notEmpty = true; +} + +// '\n' will indent. '\n\0' will not indent. '\n\1' will indent one more then the current indent level. +// '\n\-1' will indent one less then the current indent level. Numbers from 0-9 can me used. +StringBuffer.prototype.concatFormat = function(aString) +{ + if (!aString) return; + var lines = aString.split("\n"), + size = lines.length; + if (size > 1) { + this.concat(lines[0]); + for (var i = 1; i < size; i++) { + var line = lines[i]; + this.concat("\n"); + if (line.slice(0, 1) === "\\") { + var numberLength = 1; + var indent = line.slice(1, 1 + numberLength); + if (indent === '-') { + numberLength = 2; + indent = line.slice(1, 1 + numberLength); + } + var indentationNumber = parseInt(indent); + if (indentationNumber) { + this.concat(indentationNumber > 0 ? indentation + Array(indentationNumber * indentationSpaces + 1).join(indentType) : indentation.substring(indentationSize * -indentationNumber)); + } + line = line.slice(1 + numberLength); + } else if (line || i === size - 1) { + // Ident if there is something between line breaks or the last linebreak + this.concat(indentation); + } + if (line) this.concat(line); + } + } else + this.concat(aString); +} + +StringBuffer.prototype.isEmptyString = function() { return this.atoms.length !== 0; } +StringBuffer.prototype.isEmptySourceNode = function() +{ + return this.notEmpty; +} + +StringBuffer.prototype.appendStringBufferString = function(stringBuffer) +{ + this.atoms.push.apply(this.atoms, stringBuffer.atoms); +} + +StringBuffer.prototype.appendStringBufferSourceNode = function(stringBuffer) +{ + this.rootNode.add(stringBuffer.rootNode); +} + +StringBuffer.prototype.lengthString = function() +{ + return this.atoms.length; +} + +StringBuffer.prototype.lengthSourceNode = function() +{ + return this.rootNode.children.length; +} + // Both the ClassDef and ProtocolDef conforms to a 'protocol' (That we can't declare in Javascript). // Both Objects have the attribute 'protocols': Array of ProtocolDef that they conform to // Both also have the functions: addInstanceMethod, addClassMethod, getInstanceMethod and getClassMethod @@ -188,21 +430,19 @@ ClassDef.prototype.listOfNotImplementedMethodsForProtocols = function(protocolDe protocolClassMethods = protocolDef.requiredClassMethods, inheritFromProtocols = protocolDef.protocols; - if (protocolInstanceMethods) - for (var methodName in protocolInstanceMethods) { - var methodDef = protocolInstanceMethods[methodName]; + if (protocolInstanceMethods) for (var methodName in protocolInstanceMethods) { + var methodDef = protocolInstanceMethods[methodName]; - if (!instanceMethods[methodName]) - resultList.push({"methodDef": methodDef, "protocolDef": protocolDef}); - } + if (!instanceMethods[methodName]) + resultList.push({"methodDef": methodDef, "protocolDef": protocolDef}); + } - if (protocolClassMethods) - for (var methodName in protocolClassMethods) { - var methodDef = protocolClassMethods[methodName]; + if (protocolClassMethods) for (var methodName in protocolClassMethods) { + var methodDef = protocolClassMethods[methodName]; - if (!classMethods[methodName]) - resultList.push({"methodDef": methodDef, "protocolDef": protocolDef}); - } + if (!classMethods[methodName]) + resultList.push({"methodDef": methodDef, "protocolDef": protocolDef}); + } if (inheritFromProtocols) resultList = resultList.concat(this.listOfNotImplementedMethodsForProtocols(inheritFromProtocols)); @@ -365,184 +605,227 @@ var MethodDef = function(name, types) this.types = types; } -var reservedIdentifiers = exports.acorn.makePredicate("self _cmd undefined localStorage arguments"); +var reservedIdentifiers = acorn.makePredicate("self _cmd undefined localStorage arguments"); -var wordPrefixOperators = exports.acorn.makePredicate("delete in instanceof new typeof void"); +var wordPrefixOperators = acorn.makePredicate("delete in instanceof new typeof void"); -var isLogicalBinary = exports.acorn.makePredicate("LogicalExpression BinaryExpression"); -var isInInstanceof = exports.acorn.makePredicate("in instanceof"); +var isLogicalBinary = acorn.makePredicate("LogicalExpression BinaryExpression"); +var isInInstanceof = acorn.makePredicate("in instanceof"); -var ObjJAcornCompiler = function(/*String*/ aString, /*CFURL*/ aURL, /*unsigned*/ flags, /*unsigned*/ pass, /* Dictionary */ classDefs, /* Dictionary */ protocolDefs, /* Dictionary */ typeDefs) + // A optional argument can be given to further configure + // the compiler. These options are recognized: + + var defaultOptions = { + // Acorn options. For more information check objj-acorn. + // We have a function here to create a new object every time we copy + // the default options. + acornOptions: function() { return Object.create(null) }, + + // Turn on `sourceMap` generate a source map for the compiler file. + sourceMap: false, + + // The compiler can do different passes. + // 1: Parse and walk AST tree to collect file dependencies. + // 2: Parse and walk to generate code. + // Pass one is only for when the Objective-J load and runtime. + pass: 2, + + // Pass in class definitions. New class definitions in source file will be added here when compiling. + classDefs: function() { return Object.create(null) }, + + // Pass in protocol definitions. New protocol definitions in source file will be added here when compiling. + protocolDefs: function() { return Object.create(null) }, + + // Pass in typeDef definitions. New typeDef definitions in source file will be added here when compiling. + typeDefs: function() { return Object.create(null) }, + + // Turn off `generate` to make the compile copy the code from the source file (and replace needed parts) + // instead of generate it from the AST tree. The preprocessor does not work if this is turn off as it alters + // the AST tree and not the original source. We should deprecate this in the future. + generate: true, + + // Turn on `generateObjJ` to generate Objecitve-J code instead of Javascript code. This can be used to beautify + // the code. + generateObjJ: false, + + // Format description for generated code. For more information look at the readme file in the format folder. + formatDescription: null, + + // How many spaces for indentation when generation code. + indentationSpaces: 4, + + // The type of indentation. Default is space. Can be changed to tab or any other string. + indentationType: " ", + + // Include comments when generating code. This option will turn on the acorn options trackComments and trackCommentsIncludeLineBreak. + includeComments: false, + + // There is a bug in Safari 2.0 that can't handle a named function declaration. See http://kangax.github.io/nfe/#safari-bug + // Turn on `transformNamedFunctionDeclarationToAssignment` to make the compiler transform these. + // We support this here as the old Objective-J compiler (Not a real compiler, Preprocessor.js) transformed + // named function declarations to assignments. + // Example: 'function f(x) { return x }' transforms to: 'f = function(x) { return x }' + transformNamedFunctionDeclarationToAssignment: false, + + // Turn off `includeMethodFunctionNames` to remove function names on methods. + includeMethodFunctionNames: true, + + // Turn off `includeMethodArgumentTypeSignatures` to remove type information on method arguments. + includeMethodArgumentTypeSignatures: true, + + // Turn off `includeIvarTypeSignatures` to remove type information on ivars. + includeIvarTypeSignatures: true, + + // Turn off `inlineMsgSendFunctions` to use message send functions. Needed to use message send decorators. + inlineMsgSendFunctions: true, + }; + + // We copy the options to a new object as we don't want to mess up incoming options when we start compiling. + function setupOptions(opts) { + var options = Object.create(null); + for (var opt in defaultOptions) { + if (opts && Object.prototype.hasOwnProperty.call(opts, opt)) { + var incomingOpt = opts[opt]; + options[opt] = typeof incomingOpt === 'function' ? incomingOpt() : incomingOpt; + } else if (defaultOptions.hasOwnProperty(opt)) { + var defaultOpt = defaultOptions[opt]; + options[opt] = typeof defaultOpt === 'function' ? defaultOpt() : defaultOpt; + } + } + return options; + } + +var ObjJAcornCompiler = function(/*String*/ aString, /*CFURL*/ aURL, options) { this.source = aString; - this.URL = new CFURL(aURL); - this.pass = pass; - this.jsBuffer = new StringBuffer(); + this.URL = aURL && aURL.toString(); + options = setupOptions(options); + this.options = options; + this.pass = options.pass; + this.classDefs = options.classDefs; + this.protocolDefs = options.protocolDefs; + this.typeDefs = options.typeDefs; + this.generate = options.generate; + this.createSourceMap = options.sourceMap; + this.formatDescription = options.formatDescription; + this.includeComments = options.includeComments; + this.transformNamedFunctionDeclarationToAssignment = options.transformNamedFunctionDeclarationToAssignment; + this.jsBuffer = new StringBuffer(this.createSourceMap, aURL); this.imBuffer = null; this.cmBuffer = null; - this.warnings = []; + this.dependencies = []; + this.warningsAndErrors = []; + this.lastPos = 0; + + //this.formatDescription = { + // Identifier: {before:"", after:"", parent: {ReturnStatement: {after:"", before:""}, Statement: {after:"", before:""}}}, + // BlockStatement: {before:" ", after:"", afterLeftBrace: "\n", beforeRightBrace: "/* Before Brace */"}, + // Statement: {before:"", after:"/*Statement after*/;\n"} + //}; + + var acornOptions = options.acornOptions; + + if (acornOptions) + { + if (!acornOptions.sourceFile && this.URL) + acornOptions.sourceFile = this.URL.substr(this.URL.lastIndexOf('/') + 1); + if (options.sourceMap && !acornOptions.locations) + acornOptions.locations = true; + } + else + { + acornOptions = options.acornOptions = this.URL && {sourceFile: this.URL.substr(this.URL.lastIndexOf('/') + 1)}; + if (options.sourceMap) + acornOptions.locations = true; + } try { - this.tokens = exports.acorn.parse(aString); + this.tokens = acorn.parse(aString, options.acornOptions); + (this.pass === 2 && (options.includeComments || options.formatDescription) ? compileWithFormat : compile)(this.tokens, new Scope(null ,{ compiler: this }), this.pass === 2 ? pass2 : pass1); } catch (e) { if (e.lineStart != null) { - var message = this.prettifyMessage(e, "ERROR"); -#ifdef BROWSER - console.log(message); -#else - if (exports.outputFormatInXML) - { - var dict = new CFMutableDictionary(); - dict.addValueForKey('line', e.line); - dict.addValueForKey('sourcePath', this.URL.path()); - dict.addValueForKey('message', message); - - print(CFPropertyListCreateXMLData([dict], kCFPropertyListXMLFormat_v1_0).rawString()); - } - else - { - print(message); - } -#endif + e.messageForLine = aString.substring(e.lineStart, e.lineEnd); } - - throw e; + this.addWarning(e); + return; } - this.dependencies = []; - this.flags = flags & (ObjJAcornCompiler.Flags.IncludeDebugSymbols | ObjJAcornCompiler.Flags.InlineMsgSend | ObjJAcornCompiler.Flags.IncludeTypeSignatures); - this.classDefs = classDefs ? classDefs : Object.create(null); - this.protocolDefs = protocolDefs ? protocolDefs : Object.create(null); - this.typeDefs = typeDefs ? typeDefs : Object.create(null); - this.lastPos = 0; - this.generate = true; // Before there was an option to generate the code or copy & paste it from the source. Today we always generate the code. - - compile(this.tokens, new Scope(null ,{ compiler: this }), pass === 2 ? pass2 : pass1); + this.setCompiledCode(this.jsBuffer); } -ObjJAcornCompiler.Flags = { }; - -ObjJAcornCompiler.Flags.IncludeDebugSymbols = 1 << 0; -ObjJAcornCompiler.Flags.IncludeTypeSignatures = 1 << 1; -ObjJAcornCompiler.Flags.Generate = 1 << 2; -ObjJAcornCompiler.Flags.InlineMsgSend = 1 << 3; - -var currentCompilerFlags = ObjJAcornCompiler.Flags.IncludeTypeSignatures; -var currentGccCompilerFlags = ""; - -exports.ObjJAcornCompiler = ObjJAcornCompiler; - -exports.ObjJAcornCompiler.compileToExecutable = function(/*String*/ aString, /*CFURL*/ aURL, /*unsigned*/ flags) +ObjJAcornCompiler.prototype.setCompiledCode = function(stringBuffer) { - ObjJAcornCompiler.currentCompileFile = aURL; - return new ObjJAcornCompiler(aString, aURL, flags, 2).executable(); + if (this.createSourceMap) + { + var s = stringBuffer.toString(); + this.compiledCode = s.code; + this.sourceMap = s.map; + } + else + { + this.compiledCode = stringBuffer.toString(); + } } -exports.ObjJAcornCompiler.compileToIMBuffer = function(/*String*/ aString, /*CFURL*/ aURL, /*unsigned*/ flags, classDefs, protocolDefs, typeDefs) +// This might not be used +exports.compileToExecutable = function(/*String*/ aString, /*CFURL*/ aURL, options) { - return new ObjJAcornCompiler(aString, aURL, flags, 2, classDefs, protocolDefs, typeDefs).IMBuffer(); + exports.currentCompileFile = aURL; + return new ObjJAcornCompiler(aString, aURL, options).executable(); } -exports.ObjJAcornCompiler.compileFileDependencies = function(/*String*/ aString, /*CFURL*/ aURL, /*unsigned*/ flags) +exports.compileToIMBuffer = function(/*String*/ aString, /*CFURL*/ aURL, options) { - ObjJAcornCompiler.currentCompileFile = aURL; - return new ObjJAcornCompiler(aString, aURL, flags, 1).executable(); + return new ObjJAcornCompiler(aString, aURL, options).IMBuffer(); +} + +exports.compile = function(/*String*/ aString, /*CFURL*/ aURL, options) +{ + return new ObjJAcornCompiler(aString, aURL, options); +} + +exports.compileFileDependencies = function(/*String*/ aString, /*CFURL*/ aURL, options) +{ + exports.currentCompileFile = aURL; + (options || (options = {})).pass = 1; + return new ObjJAcornCompiler(aString, aURL, options); } ObjJAcornCompiler.prototype.compilePass2 = function() { - var warnings = []; + exports.currentCompileFile = this.URL; + this.pass = this.options.pass = 2; + this.jsBuffer = new StringBuffer(this.createSourceMap, this.URL); - ObjJAcornCompiler.currentCompileFile = this.URL; - this.pass = 2; - this.jsBuffer = new StringBuffer(); - this.warnings = []; - //print(this.URL + ": Compiling"); - compile(this.tokens, new Scope(null ,{ compiler: this }), pass2); - for (var i = 0; i < this.warnings.length; i++) - { - var warning = this.warnings[i], - type = "WARNING"; + // To get the source mapping correct when the new Function construtor is used we add a + // new line as first thing in the code. + if (this.createSourceMap) + this.jsBuffer.concat("\n"); - var message = this.prettifyMessage(warning, type); -#ifdef BROWSER - console.log(message); -#else - if (exports.outputFormatInXML) - { - var dict = new CFMutableDictionary(); - dict.addValueForKey('line', warning.line) - dict.addValueForKey('sourcePath', this.URL.path()) - dict.addValueForKey('message', message) - - warnings.push(dict); - } - else - { - print(message); - } -#endif + this.warningsAndErrors = []; + try { + compile(this.tokens, new Scope(null ,{ compiler: this }), pass2); + } catch (e) { + this.addWarning(e); + return null; } - if (warnings.length && exports.outputFormatInXML) - print(CFPropertyListCreateXMLData(warnings, kCFPropertyListXMLFormat_v1_0).rawString()); + this.setCompiledCode(this.jsBuffer); - //print(this.URL + ": " + this.jsBuffer.toString()); - return this.jsBuffer.toString(); -} - -exports.setCurrentGccCompilerFlags = function(/*String*/ compilerFlags) -{ - if (currentGccCompilerFlags === compilerFlags) return; - - currentGccCompilerFlags = compilerFlags; - - var args = compilerFlags.split(" "), - count = args.length, - objjcFlags = ObjJAcornCompiler.Flags.IncludeTypeSignatures; - - for (var index = 0; index < count; ++index) - { - var argument = args[index]; - - if (argument.indexOf("-g") === 0) - objjcFlags |= ObjJAcornCompiler.Flags.IncludeDebugSymbols; - else if (argument.indexOf("-O") === 0) { - objjcFlags |= ObjJAcornCompiler.Flags.Compress; - // FIXME: currently we are sending in '-O2' when we want InlineMsgSend. Here we only check if it is '-O...'. - // Maybe we should have some other option for this - if (argument.length > 2) - objjcFlags |= ObjJAcornCompiler.Flags.InlineMsgSend; - } - else if (argument.indexOf("-G") === 0) - objjcFlags |= ObjJAcornCompiler.Flags.Generate; - else if (argument.indexOf("-T") === 0) - objjcFlags &= ~ObjJAcornCompiler.Flags.IncludeTypeSignatures; - } - - currentCompilerFlags = objjcFlags; -} - -exports.currentGccCompilerFlags = function(/*String*/ compilerFlags) -{ - return currentGccCompilerFlags; -} - -exports.setCurrentCompilerFlags = function(/*String*/ compilerFlags) -{ - currentCompilerFlags = compilerFlags; -} - -exports.currentCompilerFlags = function(/*String*/ compilerFlags) -{ - return currentCompilerFlags; + return this.compiledCode; } +/*! + Add warning or error to the list + */ ObjJAcornCompiler.prototype.addWarning = function(/* Warning */ aWarning) { - this.warnings.push(aWarning); + if (aWarning.path == null) + aWarning.path = this.URL; + + this.warningsAndErrors.push(aWarning); } ObjJAcornCompiler.prototype.getIvarForClass = function(/* String */ ivarName, /* Scope */ scope) @@ -569,13 +852,11 @@ ObjJAcornCompiler.prototype.getIvarForClass = function(/* String */ ivarName, /* ObjJAcornCompiler.prototype.getClassDef = function(/* String */ aClassName) { - if (!aClassName) - return null; + if (!aClassName) return null; var c = this.classDefs[aClassName]; - if (c) - return c; + if (c) return c; if (typeof objj_getClass === 'function') { @@ -619,13 +900,11 @@ ObjJAcornCompiler.prototype.getClassDef = function(/* String */ aClassName) ObjJAcornCompiler.prototype.getProtocolDef = function(/* String */ aProtocolName) { - if (!aProtocolName) - return null; + if (!aProtocolName) return null; var p = this.protocolDefs[aProtocolName]; - if (p) - return p; + if (p) return p; if (typeof objj_getProtocol === 'function') { @@ -696,6 +975,7 @@ ObjJAcornCompiler.methodDefsFromMethodList = function(/* Array */ methodList) return myMethods; } +//FIXME: Does not work anymore ObjJAcornCompiler.prototype.executable = function() { if (!this._executable) @@ -708,31 +988,44 @@ ObjJAcornCompiler.prototype.IMBuffer = function() return this.imBuffer; } -ObjJAcornCompiler.prototype.JSBuffer = function() +ObjJAcornCompiler.prototype.code = function() { - return this.jsBuffer; + return this.compiledCode; } -ObjJAcornCompiler.prototype.prettifyMessage = function(/* Message */ aMessage, /* String */ messageType) +ObjJAcornCompiler.prototype.ast = function() { - var line = this.source.substring(aMessage.lineStart, aMessage.lineEnd), + return JSON.stringify(this.tokens, null, indentationSpaces); +} + +ObjJAcornCompiler.prototype.map = function() +{ + return JSON.stringify(this.sourceMap); +} + +ObjJAcornCompiler.prototype.prettifyMessage = function(/* Message */ aMessage) +{ + var line = aMessage.messageForLine, message = "\n" + line; - message += (new Array(aMessage.column + 1)).join(" "); + message += (new Array(aMessage.messageOnColumn + 1)).join(" "); message += (new Array(Math.min(1, line.length) + 1)).join("^") + "\n"; - message += messageType + " line " + aMessage.line + " in " + this.URL + ": " + aMessage.message; + message += aMessage.messageType + " line " + aMessage.messageOnLine + " in " + this.URL + ": " + aMessage.message; return message; } ObjJAcornCompiler.prototype.error_message = function(errorMessage, node) { - var pos = exports.acorn.getLineInfo(this.source, node.start), - syntaxErrorData = {message: errorMessage, line: pos.line, column: pos.column, lineStart: pos.lineStart, lineEnd: pos.lineEnd}, - syntaxError = new SyntaxError(this.prettifyMessage(syntaxErrorData, "ERROR")); + var pos = acorn.getLineInfo(this.source, node.start), + syntaxError = new SyntaxError(errorMessage); - syntaxError.line = pos.line; - syntaxError.path = this.URL.path(); + syntaxError.messageOnLine = pos.line; + syntaxError.messageOnColumn = pos.column; + syntaxError.path = this.URL; + syntaxError.messageForNode = node; + syntaxError.messageType = "ERROR"; + syntaxError.messageForLine = this.source.substring(pos.lineStart, pos.lineEnd); return syntaxError; } @@ -751,21 +1044,59 @@ ObjJAcornCompiler.prototype.popImport = function() function createMessage(/* String */ aMessage, /* SpiderMonkey AST node */ node, /* String */ code) { - var message = exports.acorn.getLineInfo(code, node.start); + var message = acorn.getLineInfo(code, node.start); message.message = aMessage; + // As a SyntaxError object can't change the property 'line' we also set the property 'messageOnLine' + message.messageOnLine = message.line; + message.messageOnColumn = message.column; + message.messageForNode = node; + message.messageType = "WARNING"; + message.messageForLine = code.substring(message.lineStart, message.lineEnd); return message; } function compile(node, state, visitor) { function c(node, st, override) { - //print("c: " + (override ? override + ", " : "") + node.type + ", " + exports.acorn.getLineInfo(st.compiler.source, node.start).line); visitor[override || node.type](node, st, c); - //print("cc: " + (override ? override + ", " : "") + node.type + ", " + exports.acorn.getLineInfo(st.compiler.source, node.end).line); } c(node, state); -}; +} + +function compileWithFormat(node, state, visitor) { + var lastNode, lastComment; + function c(node, st, override) { + var compiler = st.compiler, + includeComments = compiler.includeComments, + parentNode = st.currentNode(), + localLastNode = lastNode, + sameNode = localLastNode === node; + //console.log(override || node.type); + lastNode = node; + if (includeComments && !sameNode && node.commentsBefore && node.commentsBefore !== lastComment) { + for (var i = 0; i < node.commentsBefore.length; i++) + compiler.jsBuffer.concat(node.commentsBefore[i]); + } + st.pushNode(node, override); + var formatDescription = st.formatDescription(); + //console.log("formatDescription: " + JSON.stringify(formatDescription) + ", node.type: " + node.type + ", override: " + override); + if (!sameNode && formatDescription && formatDescription.before) + compiler.jsBuffer.concatFormat(formatDescription.before); + visitor[override || node.type](node, st, c, formatDescription); + if (!sameNode && formatDescription && formatDescription.after) + compiler.jsBuffer.concatFormat(formatDescription.after); + st.popNode(); + if (includeComments && !sameNode && node.commentsAfter) { + for (var i = 0; i < node.commentsAfter.length; i++) + compiler.jsBuffer.concat(node.commentsAfter[i]); + lastComment = node.commentsAfter; + } else { + lastComment = null; + } + } + c(node, state); +} function isIdempotentExpression(node) { switch (node.type) { @@ -843,9 +1174,9 @@ function checkCanDereference(st, node) { // Surround expression with parentheses function surroundExpression(c) { - return function(node, st, override) { + return function(node, st, override, format) { st.compiler.jsBuffer.concat("("); - c(node, st, override); + c(node, st, override, format); st.compiler.jsBuffer.concat(")"); } } @@ -898,23 +1229,32 @@ function nodePrecedence(node, subNode, right) { return nodePrecedence < subNodePrecedence || (nodePrecedence === subNodePrecedence && isLogicalBinary(nodeType) && ((nodeOperatorPrecedence = operatorPrecedence[node.operator]) < (subNodeOperatorPrecedence = operatorPrecedence[subNode.operator]) || (right && nodeOperatorPrecedence === subNodeOperatorPrecedence))); } -var pass1 = exports.acorn.walk.make({ +var pass1 = walk.make({ ImportStatement: function(node, st, c) { var urlString = node.filename.value; - st.compiler.dependencies.push(new FileDependency(new CFURL(urlString), node.localfilepath)); + st.compiler.dependencies.push({url: urlString, isLocal: node.localfilepath}); + //st.compiler.dependencies.push(typeof FileDependency !== 'undefined' ? new FileDependency(typeof CFURL !== 'undefined' ? new CFURL(urlString) : urlString, node.localfilepath) : urlString); } }); +var indentType = " "; var indentationSpaces = 4; -var indentStep = Array(indentationSpaces + 1).join(" "); +var indentationSize = indentationSpaces * indentType.length; +var indentStep = Array(indentationSpaces + 1).join(indentType); var indentation = ""; -var pass2 = exports.acorn.walk.make({ +var pass2 = walk.make({ Program: function(node, st, c) { var compiler = st.compiler, generate = compiler.generate; + + indentType = compiler.options.indentationType; + indentationSpaces = compiler.options.indentationSpaces; + indentationSize = indentationSpaces * indentType.length; + indentStep = Array(indentationSpaces + 1).join(indentType); indentation = ""; + for (var i = 0; i < node.body.length; ++i) { c(node.body[i], st, "Statement"); } @@ -929,7 +1269,7 @@ Program: function(node, st, c) { } } }, -BlockStatement: function(node, st, c) { +BlockStatement: function(node, st, c, format) { var compiler = st.compiler, generate = compiler.generate, endOfScopeBody = st.endOfScopeBody, @@ -939,10 +1279,18 @@ BlockStatement: function(node, st, c) { delete st.endOfScopeBody; if (generate) { - st.indentBlockLevel = typeof st.indentBlockLevel === "undefined" ? 0 : st.indentBlockLevel + 1; + var skipIndentation = st.skipIndentation; buffer = compiler.jsBuffer; - buffer.concat(indentation.substring(indentationSpaces)); - buffer.concat("{\n"); + if (format) { + buffer.concat("{", node); + buffer.concatFormat(format.afterLeftBrace); + } else { + if (skipIndentation) + delete st.skipIndentation; + else + buffer.concat(indentation.substring(indentationSize)); + buffer.concat("{\n", node); + } } for (var i = 0; i < node.body.length; ++i) { c(node.body[i], st, "Statement"); @@ -960,46 +1308,73 @@ BlockStatement: function(node, st, c) { buffer.concat(";\n"); } - buffer.concat(indentation.substring(indentationSpaces)); - buffer.concat("}"); - if (st.isDecl || st.indentBlockLevel > 0) - buffer.concat("\n"); - st.indentBlockLevel--; + //Simulate a node for the last curly bracket + var endNode = node.loc && { loc: { start: { line : node.loc.end.line, column: node.loc.end.column-1}}, source: node.loc.source}; + if (format) { + buffer.concatFormat(format.beforeRightBrace); + buffer.concat("}", endNode); + } else { + buffer.concat(indentation.substring(indentationSize)); + buffer.concat("}", endNode); + if (!skipIndentation && st.isDecl !== false) + buffer.concat("\n"); + st.indentBlockLevel--; + } } }, -ExpressionStatement: function(node, st, c) { +ExpressionStatement: function(node, st, c, format) { var compiler = st.compiler, - generate = compiler.generate; + generate = compiler.generate && !format; if (generate) compiler.jsBuffer.concat(indentation); c(node.expression, st, "Expression"); if (generate) compiler.jsBuffer.concat(";\n"); }, -IfStatement: function(node, st, c) { +IfStatement: function(node, st, c, format) { var compiler = st.compiler, generate = compiler.generate, buffer; if (generate) { buffer = compiler.jsBuffer; - if (!st.superNodeIsElse) - buffer.concat(indentation); - else - delete st.superNodeIsElse; - buffer.concat("if ("); + if (format) { + buffer.concat("if", node); + buffer.concatFormat(format.beforeLeftParenthesis); + buffer.concat("("); + } else { + // Keep the 'else' and 'if' on the same line if it is an 'else if' + if (!st.superNodeIsElse) + buffer.concat(indentation); + else + delete st.superNodeIsElse; + buffer.concat("if (", node); + } } c(node.test, st, "Expression"); - // We don't want EmptyStatements to generate an extra parenthesis except when it is in a while, for, ... - if (generate) buffer.concat(node.consequent.type === "EmptyStatement" ? ");\n" : ")\n"); + if (generate) { + if (format) { + buffer.concat(")"); + buffer.concatFormat(format.afterRightParenthesis); + } else { + // We don't want EmptyStatements to generate an extra parenthesis except when it is in a while, for, ... + buffer.concat(node.consequent.type === "EmptyStatement" ? ");\n" : ")\n"); + } + } indentation += indentStep; c(node.consequent, st, "Statement"); - indentation = indentation.substring(indentationSpaces); + indentation = indentation.substring(indentationSize); var alternate = node.alternate; if (alternate) { var alternateNotIf = alternate.type !== "IfStatement"; if (generate) { - var emptyStatement = alternate.type === "EmptyStatement"; - buffer.concat(indentation); - // We don't want EmptyStatements to generate an extra parenthesis except when it is in a while, for, ... - buffer.concat(alternateNotIf ? emptyStatement ? "else;\n" : "else\n" : "else "); + if (format) { + buffer.concatFormat(format.beforeElse); // Do we need this? + buffer.concat("else"); + buffer.concatFormat(format.afterElse); + } else { + var emptyStatement = alternate.type === "EmptyStatement"; + buffer.concat(indentation); + // We don't want EmptyStatements to generate an extra parenthesis except when it is in a while, for, ... + buffer.concat(alternateNotIf ? emptyStatement ? "else;\n" : "else\n" : "else "); + } } if (alternateNotIf) indentation += indentStep; @@ -1007,229 +1382,384 @@ IfStatement: function(node, st, c) { st.superNodeIsElse = true; c(alternate, st, "Statement"); - if (alternateNotIf) indentation = indentation.substring(indentationSpaces); + if (alternateNotIf) indentation = indentation.substring(indentationSize); } }, -LabeledStatement: function(node, st, c) { +LabeledStatement: function(node, st, c, format) { var compiler = st.compiler; if (compiler.generate) { var buffer = compiler.jsBuffer; - buffer.concat(indentation); - buffer.concat(node.label.name); - buffer.concat(": "); + if (!format) buffer.concat(indentation); + c(node.label, st, "IdentifierName"); + if (format) { + buffer.concat(":"); + buffer.concatFormat(format.afterColon); + } else { + buffer.concat(": "); + } } c(node.body, st, "Statement"); }, -BreakStatement: function(node, st, c) { +BreakStatement: function(node, st, c, format) { var compiler = st.compiler; if (compiler.generate) { - compiler.jsBuffer.concat(indentation); - if (node.label) { - compiler.jsBuffer.concat("break "); - compiler.jsBuffer.concat(node.label.name); - compiler.jsBuffer.concat(";\n"); + var label = node.label, + buffer = compiler.jsBuffer; + if (!format) buffer.concat(indentation); + if (label) { + if (format) { + buffer.concat("break", node); + buffer.concatFormat(format.beforeLabel); + } else { + buffer.concat("break ", node); + } + c(label, st, "IdentifierName"); + if (!format) buffer.concat(";\n"); } else - compiler.jsBuffer.concat("break;\n"); + buffer.concat(format ? "break" : "break;\n", node); } }, -ContinueStatement: function(node, st, c) { +ContinueStatement: function(node, st, c, format) { var compiler = st.compiler; if (compiler.generate) { - var buffer = compiler.jsBuffer; - buffer.concat(indentation); - if (node.label) { - buffer.concat("continue "); - buffer.concat(node.label.name); - buffer.concat(";\n"); + var label = node.label, + buffer = compiler.jsBuffer; + if (!format) buffer.concat(indentation); + if (label) { + if (format) { + buffer.concat("continue", node); + buffer.concatFormat(format.beforeLabel); + } else { + buffer.concat("continue ", node); + } + c(label, st, "IdentifierName"); + if (!format) buffer.concat(";\n"); } else - buffer.concat("continue;\n"); + buffer.concat(format ? "continue" : "continue;\n", node); } }, -WithStatement: function(node, st, c) { +WithStatement: function(node, st, c, format) { var compiler = st.compiler, generate = compiler.generate, buffer; if (generate) { buffer = compiler.jsBuffer; - buffer.concat(indentation); - buffer.concat("with("); + if (format) { + buffer.concat("with", node); + buffer.concatFormat(format.beforeLeftParenthesis); + buffer.concat("("); + } else { + buffer.concat(indentation); + buffer.concat("with(", node); + } } c(node.object, st, "Expression"); - if (generate) buffer.concat(")\n"); + if (generate) + if (format) { + buffer.concat(")"); + buffer.concatFormat(format.afterRightParenthesis); + } else { + buffer.concat(")\n"); + } indentation += indentStep; c(node.body, st, "Statement"); - indentation = indentation.substring(indentationSpaces); + indentation = indentation.substring(indentationSize); }, -SwitchStatement: function(node, st, c) { +SwitchStatement: function(node, st, c, format) { var compiler = st.compiler, generate = compiler.generate, buffer; if (generate) { buffer = compiler.jsBuffer; - buffer.concat(indentation); - buffer.concat("switch("); + if (format) { + buffer.concat("switch", node); + buffer.concatFormat(format.beforeLeftParenthesis); + buffer.concat("(", node); + } else { + buffer.concat(indentation); + buffer.concat("switch(", node); + } } c(node.discriminant, st, "Expression"); - if (generate) buffer.concat(") {\n"); + if (generate) + if (format) { + buffer.concat(")"); + buffer.concatFormat(format.afterRightParenthesis); + buffer.concat("{"); + buffer.concatFormat(format.afterLeftBrace); + } else { + buffer.concat(") {\n"); + } + indentation += indentStep; for (var i = 0; i < node.cases.length; ++i) { var cs = node.cases[i]; if (cs.test) { if (generate) { - buffer.concat(indentation); - buffer.concat("case "); + if (format) { + buffer.concatFormat(format.beforeCase); + buffer.concat("case", node); + buffer.concatFormat(format.afterCase); + } else { + buffer.concat(indentation); + buffer.concat("case "); + } } c(cs.test, st, "Expression"); - if (generate) buffer.concat(":\n"); + if (generate) + if (format) { + buffer.concat(":"); + buffer.concatFormat(format.afterColon); + } else { + buffer.concat(":\n"); + } } else - if (generate) buffer.concat("default:\n"); + if (generate) + if (format) { + buffer.concatFormat(format.beforeCase); + buffer.concat("default"); + buffer.concatFormat(format.afterCase); + buffer.concat(":"); + buffer.concatFormat(format.afterColon); + } else { + buffer.concat("default:\n"); + } indentation += indentStep; for (var j = 0; j < cs.consequent.length; ++j) c(cs.consequent[j], st, "Statement"); - indentation = indentation.substring(indentationSpaces); + indentation = indentation.substring(indentationSize); } + indentation = indentation.substring(indentationSize); if (generate) { - buffer.concat(indentation); - buffer.concat("}\n"); + if (format) { + buffer.concatFormat(format.beforeRightBrace); + buffer.concat("}"); + } else { + buffer.concat(indentation); + buffer.concat("}\n"); + } } }, -ReturnStatement: function(node, st, c) { +ReturnStatement: function(node, st, c, format) { var compiler = st.compiler, generate = compiler.generate, buffer; if (generate) { buffer = compiler.jsBuffer; - buffer.concat(indentation); - buffer.concat("return"); + if (!format) buffer.concat(indentation); + buffer.concat("return", node); } if (node.argument) { - if (generate) buffer.concat(" "); + if (generate) buffer.concatFormat(format ? format.beforeExpression : " "); c(node.argument, st, "Expression"); } - if (generate) buffer.concat(";\n"); + if (generate && !format) buffer.concat(";\n"); }, -ThrowStatement: function(node, st, c) { +ThrowStatement: function(node, st, c, format) { var compiler = st.compiler, generate = compiler.generate, buffer; if (generate) { buffer = compiler.jsBuffer; - buffer.concat(indentation); - buffer.concat("throw "); + if (!format) buffer.concat(indentation); + buffer.concat("throw", node); + buffer.concatFormat(format ? format.beforeExpression : " "); } c(node.argument, st, "Expression"); - if (generate) buffer.concat(";\n"); + if (generate && !format) buffer.concat(";\n"); }, -TryStatement: function(node, st, c) { +TryStatement: function(node, st, c, format) { var compiler = st.compiler, generate = compiler.generate, buffer; if (generate) { buffer = compiler.jsBuffer; - buffer.concat(indentation); - buffer.concat("try"); + if (!format) buffer.concat(indentation); + buffer.concat("try", node); + buffer.concatFormat(format ? format.beforeStatement : " "); } indentation += indentStep; + if (!format) st.skipIndentation = true; c(node.block, st, "Statement"); - indentation = indentation.substring(indentationSpaces); - for (var i = 0; i < node.handlers.length; ++i) { - var handler = node.handlers[i], inner = new Scope(st), + indentation = indentation.substring(indentationSize); + if (node.handler) { + var handler = node.handler, + inner = new Scope(st), param = handler.param, name = param.name; inner.vars[name] = {type: "catch clause", node: param}; if (generate) { - buffer.concat(indentation); - buffer.concat("catch("); - buffer.concat(name); - buffer.concat(") "); + if (format) { + buffer.concatFormat(format.beforeCatch); + buffer.concat("catch"); + buffer.concatFormat(format.afterCatch); + buffer.concat("("); + c(param, st, "IdentifierName"); + buffer.concat(")"); + buffer.concatFormat(format.beforeCatchStatement); + } else { + buffer.concat("\n"); + buffer.concat(indentation); + buffer.concat("catch("); + buffer.concat(name); + buffer.concat(") "); + } } indentation += indentStep; + inner.skipIndentation = true; inner.endOfScopeBody = true; c(handler.body, inner, "ScopeBody"); - indentation = indentation.substring(indentationSpaces); + indentation = indentation.substring(indentationSize); inner.copyAddedSelfToIvarsToParent(); } if (node.finalizer) { if (generate) { - buffer.concat(indentation); - buffer.concat("finally "); + if (format) { + buffer.concatFormat(format.beforeCatch); + buffer.concat("finally"); + buffer.concatFormat(format.beforeCatchStatement); + } else { + buffer.concat("\n"); + buffer.concat(indentation); + buffer.concat("finally "); + } } indentation += indentStep; + st.skipIndentation = true; c(node.finalizer, st, "Statement"); - indentation = indentation.substring(indentationSpaces); + indentation = indentation.substring(indentationSize); } + if (generate && !format) + buffer.concat("\n"); }, -WhileStatement: function(node, st, c) { +WhileStatement: function(node, st, c, format) { var compiler = st.compiler, generate = compiler.generate, body = node.body, buffer; if (generate) { buffer = compiler.jsBuffer; - buffer.concat(indentation); - buffer.concat("while ("); + if (format) { + buffer.concat("while", node); + buffer.concatFormat(format.beforeLeftParenthesis); + buffer.concat("("); + } else { + buffer.concat(indentation); + buffer.concat("while (", node); + } } c(node.test, st, "Expression"); - if (generate) buffer.concat(body.type === "EmptyStatement" ? ");\n" : ")\n"); + if (generate) + if (format) { + buffer.concat(")"); + buffer.concatFormat(format.afterRightParenthesis); + } else { + // We don't want EmptyStatements to generate an extra parenthesis except when it is in a while, for, ... + buffer.concat(body.type === "EmptyStatement" ? ");\n" : ")\n"); + } indentation += indentStep; c(body, st, "Statement"); - indentation = indentation.substring(indentationSpaces); + indentation = indentation.substring(indentationSize); }, -DoWhileStatement: function(node, st, c) { +DoWhileStatement: function(node, st, c, format) { var compiler = st.compiler, generate = compiler.generate, buffer; if (generate) { buffer = compiler.jsBuffer; - buffer.concat(indentation); - buffer.concat("do\n"); + if (format) { + buffer.concat("do", node); + buffer.concatFormat(format.beforeStatement); + } else { + buffer.concat(indentation); + buffer.concat("do\n", node); + } } indentation += indentStep; c(node.body, st, "Statement"); - indentation = indentation.substring(indentationSpaces); + indentation = indentation.substring(indentationSize); if (generate) { - buffer.concat(indentation); - buffer.concat("while ("); + if (format) { + buffer.concat("while"); + buffer.concatFormat(format.beforeLeftParenthesis); + buffer.concat("("); + } else { + buffer.concat(indentation); + buffer.concat("while ("); + } } c(node.test, st, "Expression"); - if (generate) buffer.concat(");\n"); + if (generate) buffer.concatFormat(format ? ")" : ");\n"); }, -ForStatement: function(node, st, c) { +ForStatement: function(node, st, c, format) { var compiler = st.compiler, generate = compiler.generate, body = node.body, buffer; if (generate) { buffer = compiler.jsBuffer; - buffer.concat(indentation); - buffer.concat("for ("); + if (format) { + buffer.concat("for", node); + buffer.concatFormat(format.beforeLeftParenthesis); + buffer.concat("("); + } else { + buffer.concat(indentation); + buffer.concat("for (", node); + } } if (node.init) c(node.init, st, "ForInit"); - if (generate) buffer.concat("; "); + if (generate) buffer.concat(format ? ";" : "; "); if (node.test) c(node.test, st, "Expression"); - if (generate) buffer.concat("; "); + if (generate) buffer.concat(format ? ";" : "; "); if (node.update) c(node.update, st, "Expression"); - if (generate) buffer.concat(body.type === "EmptyStatement" ? ");\n" : ")\n"); + if (generate) + if (format) { + buffer.concat(")"); + buffer.concatFormat(format.afterRightParenthesis); + } else { + // We don't want EmptyStatements to generate an extra parenthesis except when it is in a while, for, ... + buffer.concat(body.type === "EmptyStatement" ? ");\n" : ")\n"); + } indentation += indentStep; c(body, st, "Statement"); - indentation = indentation.substring(indentationSpaces); + indentation = indentation.substring(indentationSize); }, -ForInStatement: function(node, st, c) { +ForInStatement: function(node, st, c, format) { var compiler = st.compiler, generate = compiler.generate, body = node.body, buffer; if (generate) { buffer = compiler.jsBuffer; - buffer.concat(indentation); - buffer.concat("for ("); + if (format) { + buffer.concat("for", node); + buffer.concatFormat(format.beforeLeftParenthesis); + buffer.concat("("); + } else { + buffer.concat(indentation); + buffer.concat("for (", node); + } } c(node.left, st, "ForInit"); - if (generate) buffer.concat(" in "); + if (generate) + if (format) { + buffer.concatFormat(format.beforeIn); + buffer.concat("in"); + buffer.concatFormat(format.afterIn); + } else { + buffer.concat(" in "); + } c(node.right, st, "Expression"); - if (generate) buffer.concat(body.type === "EmptyStatement" ? ");\n" : ")\n"); + if (generate) + if (format) { + buffer.concat(")"); + buffer.concatFormat(format.afterRightParenthesis); + } else { + // We don't want EmptyStatements to generate an extra parenthesis except when it is in a while, for, ... + buffer.concat(body.type === "EmptyStatement" ? ");\n" : ")\n"); + } indentation += indentStep; c(body, st, "Statement"); - indentation = indentation.substring(indentationSpaces); + indentation = indentation.substring(indentationSize); }, ForInit: function(node, st, c) { var compiler = st.compiler, @@ -1241,160 +1771,231 @@ ForInit: function(node, st, c) { } else c(node, st, "Expression"); }, -DebuggerStatement: function(node, st, c) { +DebuggerStatement: function(node, st, c, format) { var compiler = st.compiler; if (compiler.generate) { var buffer = compiler.jsBuffer; - buffer.concat(indentation); - buffer.concat("debugger;\n"); + if (format) { + buffer.concat("debugger", node); + } else { + buffer.concat(indentation); + buffer.concat("debugger;\n", node); + } } }, -Function: function(node, st, c) { +Function: function(node, st, c, format) { var compiler = st.compiler, generate = compiler.generate, - buffer = compiler.jsBuffer; + buffer = compiler.jsBuffer, inner = new Scope(st), - decl = node.type == "FunctionDeclaration"; + decl = node.type == "FunctionDeclaration", + id = node.id; - inner.isDecl = decl; + inner.isDecl = decl; for (var i = 0; i < node.params.length; ++i) inner.vars[node.params[i].name] = {type: "argument", node: node.params[i]}; - if (node.id) { - (decl ? st : inner).vars[node.id.name] = - {type: decl ? "function" : "function name", node: node.id}; - if (generate) { - buffer.concat(node.id.name); - buffer.concat(" = "); - } else { - buffer.concat(compiler.source.substring(compiler.lastPos, node.start)); - buffer.concat(node.id.name); - buffer.concat(" = function"); - compiler.lastPos = node.id.end; + if (generate && !format) + buffer.concat(indentation); + if (id) { + var name = id.name; + (decl ? st : inner).vars[name] = {type: decl ? "function" : "function name", node: id}; + if (compiler.transformNamedFunctionDeclarationToAssignment) { + if (generate) { + buffer.concat(name); + buffer.concat(" = "); + } else { + buffer.concat(compiler.source.substring(compiler.lastPos, node.start)); + buffer.concat(name); + buffer.concat(" = function"); + compiler.lastPos = id.end; + } } } if (generate) { - buffer.concat("function("); + buffer.concat("function", node); + if (!compiler.transformNamedFunctionDeclarationToAssignment && id) + { + if (!format) buffer.concat(" "); + c(id, st, "IdentifierName"); + } + if (format) buffer.concatFormat(format.beforeLeftParenthesis); + buffer.concat("("); for (var i = 0; i < node.params.length; ++i) { if (i) - buffer.concat(", "); - buffer.concat(node.params[i].name); + buffer.concat(format ? "," : ", "); + c(node.params[i], st, "IdentifierName"); + } + if (format) { + buffer.concat(")"); + buffer.concatFormat(format.afterRightParenthesis); + } else { + buffer.concat(")\n"); } - buffer.concat(")\n"); } indentation += indentStep; inner.endOfScopeBody = true; c(node.body, inner, "ScopeBody"); - indentation = indentation.substring(indentationSpaces); + indentation = indentation.substring(indentationSize); inner.copyAddedSelfToIvarsToParent(); }, -VariableDeclaration: function(node, st, c) { +VariableDeclaration: function(node, st, c, format) { var compiler = st.compiler, generate = compiler.generate, buffer; if (generate) { buffer = compiler.jsBuffer; - if (!st.isFor) buffer.concat(indentation); - buffer.concat("var "); + if (!st.isFor && !format) buffer.concat(indentation); + buffer.concat(format ? "var" : "var ", node); } for (var i = 0; i < node.declarations.length; ++i) { var decl = node.declarations[i], identifier = decl.id.name; if (i) if (generate) { - if (st.isFor) - buffer.concat(", "); - else { - buffer.concat(",\n"); - buffer.concat(indentation); - buffer.concat(" "); + if (format) { + buffer.concat(","); + } else { + if (st.isFor) + buffer.concat(", "); + else { + buffer.concat(",\n"); + buffer.concat(indentation); + buffer.concat(" "); + } } } st.vars[identifier] = {type: "var", node: decl.id}; - if (generate) buffer.concat(identifier); + c(decl.id, st, "IdentifierName"); if (decl.init) { - if (generate) buffer.concat(" = "); + if (generate) { + if (format) { + buffer.concatFormat(format.beforeEqual); + buffer.concat("="); + buffer.concatFormat(format.afterEqual); + } else { + buffer.concat(" = "); + } + } c(decl.init, st, "Expression"); } // FIXME: Extract to function + // Here we check back if a ivar with the same name exists and if we have prefixed 'self.' on previous uses. + // If this is the case we have to remove the prefixes and issue a warning that the variable hides the ivar. if (st.addedSelfToIvars) { var addedSelfToIvar = st.addedSelfToIvars[identifier]; if (addedSelfToIvar) { - var buffer = st.compiler.jsBuffer.atoms; - for (var i = 0; i < addedSelfToIvar.length; i++) { + var atoms = st.compiler.jsBuffer.atoms; + for (var i = 0, size = addedSelfToIvar.length; i < size; i++) { var dict = addedSelfToIvar[i]; - buffer[dict.index] = ""; + atoms[dict.index] = ""; compiler.addWarning(createMessage("Local declaration of '" + identifier + "' hides instance variable", dict.node, compiler.source)); } st.addedSelfToIvars[identifier] = []; } } } - if (generate && !st.isFor) compiler.jsBuffer.concat(";\n"); // Don't add ';' if this is a for statement but do it if this is a statement + if (generate && !format && !st.isFor) buffer.concat(";\n"); // Don't add ';' if this is a for statement but do it if this is a statement }, ThisExpression: function(node, st, c) { var compiler = st.compiler; - if (compiler.generate) compiler.jsBuffer.concat("this"); -}, -ArrayExpression: function(node, st, c) { - var compiler = st.compiler, - generate = compiler.generate; - if (generate) compiler.jsBuffer.concat("["); - for (var i = 0; i < node.elements.length; ++i) { - var elt = node.elements[i]; - if (i !== 0) - if (generate) compiler.jsBuffer.concat(", "); - if (elt) c(elt, st, "Expression"); - } - if (generate) compiler.jsBuffer.concat("]"); + if (compiler.generate) compiler.jsBuffer.concat("this", node); }, -ObjectExpression: function(node, st, c) { +ArrayExpression: function(node, st, c, format) { var compiler = st.compiler, - generate = compiler.generate; - if (generate) compiler.jsBuffer.concat("{"); - for (var i = 0; i < node.properties.length; ++i) + generate = compiler.generate, + buffer; + + if (generate) { + buffer = compiler.jsBuffer; + buffer.concat("[", node); + } + + for (var i = 0; i < node.elements.length; ++i) { + var elt = node.elements[i]; + + if (generate && i !== 0) + if (format) { + buffer.concatFormat(format.beforeComma); + buffer.concat(","); + buffer.concatFormat(format.afterComma); + } else + buffer.concat(", "); + + if (elt) c(elt, st, "Expression"); + } + if (generate) buffer.concat("]"); +}, +ObjectExpression: function(node, st, c, format) { + var compiler = st.compiler, + generate = compiler.generate, + properties = node.properties, + buffer = compiler.jsBuffer; + if (generate) buffer.concat("{", node); + for (var i = 0, size = properties.length; i < size; ++i) { - var prop = node.properties[i]; + var prop = properties[i]; if (generate) { if (i) - compiler.jsBuffer.concat(", "); + if (format) { + buffer.concatFormat(format.beforeComma); + buffer.concat(","); + buffer.concatFormat(format.afterComma); + } else + buffer.concat(", "); st.isPropertyKey = true; c(prop.key, st, "Expression"); delete st.isPropertyKey; - compiler.jsBuffer.concat(": "); + if (format) { + buffer.concatFormat(format.beforeColon); + buffer.concat(":"); + buffer.concatFormat(format.afterColon); + } else { + buffer.concat(": "); + } } else if (prop.key.raw && prop.key.raw.charAt(0) === "@") { - compiler.jsBuffer.concat(compiler.source.substring(compiler.lastPos, prop.key.start)); + buffer.concat(compiler.source.substring(compiler.lastPos, prop.key.start)); compiler.lastPos = prop.key.start + 1; } c(prop.value, st, "Expression"); } - if (generate) compiler.jsBuffer.concat("}"); + if (generate) buffer.concat("}"); }, -SequenceExpression: function(node, st, c) { +SequenceExpression: function(node, st, c, format) { var compiler = st.compiler, - generate = compiler.generate; - if (generate) compiler.jsBuffer.concat("("); + generate = compiler.generate, + buffer; + if (generate) { + buffer = compiler.jsBuffer; + buffer.concat("("); + } for (var i = 0; i < node.expressions.length; ++i) { if (generate && i !== 0) - compiler.jsBuffer.concat(", "); + if (format) { + buffer.concatFormat(format.beforeComma); + buffer.concat(","); + buffer.concatFormat(format.afterComma); + } else + buffer.concat(", "); c(node.expressions[i], st, "Expression"); } - if (generate) compiler.jsBuffer.concat(")"); + if (generate) buffer.concat(")"); }, UnaryExpression: function(node, st, c) { var compiler = st.compiler, generate = compiler.generate, argument = node.argument; if (generate) { + var buffer = compiler.jsBuffer; if (node.prefix) { - compiler.jsBuffer.concat(node.operator); + buffer.concat(node.operator, node); if (wordPrefixOperators(node.operator)) - compiler.jsBuffer.concat(" "); + buffer.concat(" "); (nodePrecedence(node, argument) ? surroundExpression(c) : c)(argument, st, "Expression"); } else { (nodePrecedence(node, argument) ? surroundExpression(c) : c)(argument, st, "Expression"); - compiler.jsBuffer.concat(node.operator); + buffer.concat(node.operator); } } else { c(argument, st, "Expression"); @@ -1402,26 +2003,27 @@ UnaryExpression: function(node, st, c) { }, UpdateExpression: function(node, st, c) { var compiler = st.compiler, - generate = compiler.generate; + generate = compiler.generate, + buffer = compiler.jsBuffer; if (node.argument.type === "Dereference") { checkCanDereference(st, node.argument); // @deref(x)++ and ++@deref(x) require special handling. - if (!generate) compiler.jsBuffer.concat(compiler.source.substring(compiler.lastPos, node.start)); + if (!generate) buffer.concat(compiler.source.substring(compiler.lastPos, node.start)); // Output the dereference function, "(...)(z)" - compiler.jsBuffer.concat((node.prefix ? "" : "(") + "("); + buffer.concat((node.prefix ? "" : "(") + "("); // The thing being dereferenced. if (!generate) compiler.lastPos = node.argument.expr.start; c(node.argument.expr, st, "Expression"); - if (!generate) compiler.jsBuffer.concat(compiler.source.substring(compiler.lastPos, node.argument.expr.end)); - compiler.jsBuffer.concat(")("); + if (!generate) buffer.concat(compiler.source.substring(compiler.lastPos, node.argument.expr.end)); + buffer.concat(")("); if (!generate) compiler.lastPos = node.argument.start; c(node.argument, st, "Expression"); - if (!generate) compiler.jsBuffer.concat(compiler.source.substring(compiler.lastPos, node.argument.end)); - compiler.jsBuffer.concat(" " + node.operator.substring(0, 1) + " 1)" + (node.prefix ? "" : node.operator == '++' ? " - 1)" : " + 1)")); + if (!generate) buffer.concat(compiler.source.substring(compiler.lastPos, node.argument.end)); + buffer.concat(" " + node.operator.substring(0, 1) + " 1)" + (node.prefix ? "" : node.operator == '++' ? " - 1)" : " + 1)")); if (!generate) compiler.lastPos = node.end; return; @@ -1429,42 +2031,42 @@ UpdateExpression: function(node, st, c) { if (node.prefix) { if (generate) { - compiler.jsBuffer.concat(node.operator); + buffer.concat(node.operator, node); if (wordPrefixOperators(node.operator)) - compiler.jsBuffer.concat(" "); + buffer.concat(" "); } (generate && nodePrecedence(node, node.argument) ? surroundExpression(c) : c)(node.argument, st, "Expression"); } else { (generate && nodePrecedence(node, node.argument) ? surroundExpression(c) : c)(node.argument, st, "Expression"); - if (generate) compiler.jsBuffer.concat(node.operator); + if (generate) buffer.concat(node.operator); } }, -BinaryExpression: function(node, st, c) { +BinaryExpression: function(node, st, c, format) { var compiler = st.compiler, generate = compiler.generate, operatorType = isInInstanceof(node.operator); (generate && nodePrecedence(node, node.left) ? surroundExpression(c) : c)(node.left, st, "Expression"); if (generate) { var buffer = compiler.jsBuffer; - buffer.concat(" "); + buffer.concatFormat(format ? format.beforeOperator : " "); buffer.concat(node.operator); - buffer.concat(" "); + buffer.concatFormat(format ? format.afterOperator : " "); } (generate && nodePrecedence(node, node.right, true) ? surroundExpression(c) : c)(node.right, st, "Expression"); }, -LogicalExpression: function(node, st, c) { +LogicalExpression: function(node, st, c, format) { var compiler = st.compiler, generate = compiler.generate; (generate && nodePrecedence(node, node.left) ? surroundExpression(c) : c)(node.left, st, "Expression"); if (generate) { var buffer = compiler.jsBuffer; - buffer.concat(" "); + buffer.concatFormat(format ? format.beforeOperator : " "); buffer.concat(node.operator); - buffer.concat(" "); + buffer.concatFormat(format ? format.afterOperator : " "); } (generate && nodePrecedence(node, node.right, true) ? surroundExpression(c) : c)(node.right, st, "Expression"); }, -AssignmentExpression: function(node, st, c) { +AssignmentExpression: function(node, st, c, format) { var compiler = st.compiler, generate = compiler.generate, saveAssignment = st.assignment, @@ -1505,6 +2107,7 @@ AssignmentExpression: function(node, st, c) { var saveAssignment = st.assignment, nodeLeft = node.left; + st.assignment = true; if (nodeLeft.type === "Identifier" && nodeLeft.name === "self") { var lVar = st.getLvar("self", true); @@ -1516,44 +2119,67 @@ AssignmentExpression: function(node, st, c) { } (generate && nodePrecedence(node, nodeLeft) ? surroundExpression(c) : c)(nodeLeft, st, "Expression"); if (generate) { - buffer.concat(" "); + buffer.concatFormat(format ? format.beforeOperator : " "); buffer.concat(node.operator); - buffer.concat(" "); + buffer.concatFormat(format ? format.afterOperator : " "); } st.assignment = saveAssignment; (generate && nodePrecedence(node, node.right, true) ? surroundExpression(c) : c)(node.right, st, "Expression"); if (st.isRootScope() && nodeLeft.type === "Identifier" && !st.getLvar(nodeLeft.name)) st.vars[nodeLeft.name] = {type: "global", node: nodeLeft}; }, -ConditionalExpression: function(node, st, c) { - var compiler = st.compiler, - generate = compiler.generate; - (generate && nodePrecedence(node, node.test) ? surroundExpression(c) : c)(node.test, st, "Expression"); - if (generate) - compiler.jsBuffer.concat(" ? "); - c(node.consequent, st, "Expression"); - if (generate) compiler.jsBuffer.concat(" : "); - c(node.alternate, st, "Expression"); -}, -NewExpression: function(node, st, c) { - var compiler = st.compiler, - generate = compiler.generate; - if (generate) compiler.jsBuffer.concat("new "); - (generate && nodePrecedence(node, node.callee) ? surroundExpression(c) : c)(node.callee, st, "Expression"); - if (generate) compiler.jsBuffer.concat("("); - if (node.arguments) { - for (var i = 0; i < node.arguments.length; ++i) { - if (generate && i) - compiler.jsBuffer.concat(", "); - c(node.arguments[i], st, "Expression"); - } - } - if (generate) compiler.jsBuffer.concat(")"); -}, -CallExpression: function(node, st, c) { +ConditionalExpression: function(node, st, c, format) { var compiler = st.compiler, generate = compiler.generate, - callee = node.callee; + buffer; + (generate && nodePrecedence(node, node.test) ? surroundExpression(c) : c)(node.test, st, "Expression"); + if (generate) { + buffer = compiler.jsBuffer; + if (format) { + buffer.concatFormat(format.beforeOperator); + buffer.concat("?"); + buffer.concatFormat(format.afterOperator); + } else { + buffer.concat(" ? "); + } + } + c(node.consequent, st, "Expression"); + if (generate) + if (format) { + buffer.concatFormat(format.beforeOperator); + buffer.concat(":"); + buffer.concatFormat(format.afterOperator); + } else { + buffer.concat(" : "); + } + c(node.alternate, st, "Expression"); +}, +NewExpression: function(node, st, c, format) { + var compiler = st.compiler, + nodeArguments = node.arguments, + generate = compiler.generate, + buffer; + if (generate) { + buffer = compiler.jsBuffer; + buffer.concat("new ", node); + } + (generate && nodePrecedence(node, node.callee) ? surroundExpression(c) : c)(node.callee, st, "Expression"); + if (generate) buffer.concat("("); + if (nodeArguments) { + for (var i = 0, size = nodeArguments.length; i < size; ++i) { + if (i && generate) + buffer.concatFormat(format ? "," : ", "); + c(nodeArguments[i], st, "Expression"); + } + } + if (generate) buffer.concat(")"); +}, +CallExpression: function(node, st, c, format) { + var compiler = st.compiler, + nodeArguments = node.arguments, + generate = compiler.generate, + callee = node.callee, + buffer; // If call to function 'eval' we assume that 'self' can be altered and from this point // we check if 'self' is null before 'objj_msgSend' is called with 'self' as receiver. @@ -1568,27 +2194,26 @@ CallExpression: function(node, st, c) { } (generate && nodePrecedence(node, callee) ? surroundExpression(c) : c)(callee, st, "Expression"); - if (generate) compiler.jsBuffer.concat("("); - if (node.arguments) { - for (var i = 0; i < node.arguments.length; ++i) { - if (generate && i) - compiler.jsBuffer.concat(", "); - c(node.arguments[i], st, "Expression"); + if (generate) { + buffer = compiler.jsBuffer; + buffer.concat("("); + } + if (nodeArguments) { + for (var i = 0, size = nodeArguments.length; i < size; ++i) { + if (i && generate) + buffer.concat(format ? "," : ", "); + c(nodeArguments[i], st, "Expression"); } } - if (generate) compiler.jsBuffer.concat(")"); + if (generate) buffer.concat(")"); }, MemberExpression: function(node, st, c) { var compiler = st.compiler, generate = compiler.generate, computed = node.computed; (generate && nodePrecedence(node, node.object) ? surroundExpression(c) : c)(node.object, st, "Expression"); - if (generate) { - if (computed) - compiler.jsBuffer.concat("["); - else - compiler.jsBuffer.concat("."); - } + if (generate) + compiler.jsBuffer.concat(computed ? "[" : ".", node); st.secondMemberExpression = !computed; // No parentheses when it is computed, '[' amd ']' are the same thing. (generate && !computed && nodePrecedence(node, node.property) ? surroundExpression(c) : c)(node.property, st, "Expression"); @@ -1619,12 +2244,12 @@ Identifier: function(node, st, c) { } while (compiler.source.substr(nodeStart++, 1) === "(") // Save the index in where the "self." string is stored and the node. // These will be used if we find a variable declaration that is hoisting this identifier. - ((st.addedSelfToIvars || (st.addedSelfToIvars = Object.create(null)))[identifier] || (st.addedSelfToIvars[identifier] = [])).push({node: node, index: compiler.jsBuffer.atoms.length}); - compiler.jsBuffer.concat("self."); + ((st.addedSelfToIvars || (st.addedSelfToIvars = Object.create(null)))[identifier] || (st.addedSelfToIvars[identifier] = [])).push({node: node, index: compiler.jsBuffer.length()}); + compiler.jsBuffer.concat("self.", node); } } else if (!reservedIdentifiers(identifier)) { // Don't check for warnings if it is a reserved word like self, localStorage, _cmd, etc... var message, - classOrGlobal = typeof global[identifier] !== "undefined" || typeof window[identifier] !== "undefined" || compiler.getClassDef(identifier), + classOrGlobal = typeof global[identifier] !== "undefined" || (typeof window !== 'undefined' && typeof window[identifier] !== "undefined") || compiler.getClassDef(identifier), globalVar = st.getLvar(identifier); if (classOrGlobal && (!globalVar || globalVar.type !== "class")) { // It can't be declared with a @class statement. /* Turned off this warning as there are many many warnings when compiling the Cappuccino frameworks - Martin @@ -1644,16 +2269,31 @@ Identifier: function(node, st, c) { st.addMaybeWarning(message); } } - if (generate) compiler.jsBuffer.concat(identifier); + if (generate) compiler.jsBuffer.concat(identifier, node); +}, +// Use this when there should not be a look up to issue warnings or add 'self.' before ivars +IdentifierName: function(node, st, c) { + var compiler = st.compiler; + if (compiler.generate) + compiler.jsBuffer.concat(node.name, node); }, Literal: function(node, st, c) { var compiler = st.compiler, generate = compiler.generate; if (generate) { - if (node.raw && node.raw.charAt(0) === "@") - compiler.jsBuffer.concat(node.raw.substring(1)); - else - compiler.jsBuffer.concat(node.raw); + if (node.raw) + if (node.raw.charAt(0) === "@") + compiler.jsBuffer.concat(node.raw.substring(1), node); + else + compiler.jsBuffer.concat(node.raw, node); + else { + var value = node.value, + doubleQuote = value.indexOf('"') !== -1; + compiler.jsBuffer.concat(doubleQuote ? "'" : '"', node); + compiler.jsBuffer.concat(value); + compiler.jsBuffer.concat(doubleQuote ? "'" : '"'); + } + } else if (node.raw.charAt(0) === "@") { compiler.jsBuffer.concat(compiler.source.substring(compiler.lastPos, node.start)); compiler.lastPos = node.start + 1; @@ -1662,8 +2302,9 @@ Literal: function(node, st, c) { ArrayLiteral: function(node, st, c) { var compiler = st.compiler, generate = compiler.generate, - buffer = compiler.jsBuffer; - + buffer = compiler.jsBuffer, + generateObjJ = compiler.options.generateObjJ, + elementLength = node.elements.length; if (!generate) { buffer.concat(compiler.source.substring(compiler.lastPos, node.start)); compiler.lastPos = node.start; @@ -1671,8 +2312,10 @@ ArrayLiteral: function(node, st, c) { if (!generate) buffer.concat(" "); // Add an extra space if it looks something like this: "return()". No space between return and expression. if (!st.receiverLevel) st.receiverLevel = 0; - if (!node.elements.length) { - if (compiler.flags & ObjJAcornCompiler.Flags.InlineMsgSend) { + if (generateObjJ) { + buffer.concat("@["); + } else if (!elementLength) { + if (compiler.options.inlineMsgSendFunctions) { buffer.concat("(___r"); buffer.concat(++st.receiverLevel + ""); buffer.concat(" = (CPArray.isa.method_msgSend[\"alloc\"] || _objj_forward)(CPArray, \"alloc\"), ___r"); @@ -1681,7 +2324,7 @@ ArrayLiteral: function(node, st, c) { buffer.concat(st.receiverLevel + ""); buffer.concat(".isa.method_msgSend[\"init\"] || _objj_forward)(___r"); buffer.concat(st.receiverLevel + ""); - buffer.concat(", \"init\"))"); + buffer.concat(", \"init\"))"); } else { buffer.concat("(___r"); buffer.concat(++st.receiverLevel + ""); @@ -1697,7 +2340,7 @@ ArrayLiteral: function(node, st, c) { if (!(st.maxReceiverLevel >= st.receiverLevel)) st.maxReceiverLevel = st.receiverLevel; } else { - if (compiler.flags & ObjJAcornCompiler.Flags.InlineMsgSend) { + if (compiler.options.inlineMsgSendFunctions) { buffer.concat("(___r"); buffer.concat(++st.receiverLevel + ""); buffer.concat(" = (CPArray.isa.method_msgSend[\"alloc\"] || _objj_forward)(CPArray, \"alloc\"), ___r"); @@ -1721,8 +2364,9 @@ ArrayLiteral: function(node, st, c) { if (!(st.maxReceiverLevel >= st.receiverLevel)) st.maxReceiverLevel = st.receiverLevel; - - for (var i = 0; i < node.elements.length; i++) { + } + if (elementLength) { + for (var i = 0; i < elementLength; i++) { var elt = node.elements[i]; if (i) @@ -1732,18 +2376,22 @@ ArrayLiteral: function(node, st, c) { c(elt, st, "Expression"); if (!generate) buffer.concat(compiler.source.substring(compiler.lastPos, elt.end)); } - buffer.concat("], " + node.elements.length + "))"); + if (!generateObjJ) buffer.concat("], " + elementLength + "))"); } - st.receiverLevel--; + if (generateObjJ) + buffer.concat("]"); + else + st.receiverLevel--; + if (!generate) compiler.lastPos = node.end; }, DictionaryLiteral: function(node, st, c) { var compiler = st.compiler, generate = compiler.generate, buffer = compiler.jsBuffer, - noOfKeys = node.keys.length; - + generateObjJ = compiler.options.generateObjJ, + keyLength = node.keys.length; if (!generate) { buffer.concat(compiler.source.substring(compiler.lastPos, node.start)); compiler.lastPos = node.start; @@ -1751,8 +2399,17 @@ DictionaryLiteral: function(node, st, c) { if (!generate) buffer.concat(" "); // Add an extra space if it looks something like this: "return()". No space between return and expression. if (!st.receiverLevel) st.receiverLevel = 0; - if (!noOfKeys) { - if (compiler.flags & ObjJAcornCompiler.Flags.InlineMsgSend) { + if (generateObjJ) { + buffer.concat("@{"); + for (var i = 0; i < keyLength; i++) { + if (i !== 0) buffer.concat(","); + c(node.keys[i], st, "Expression"); + buffer.concat(":"); + c(node.values[i], st, "Expression"); + } + buffer.concat("}"); + } else if (!keyLength) { + if (compiler.options.inlineMsgSendFunctions) { buffer.concat("(___r"); buffer.concat(++st.receiverLevel + ""); buffer.concat(" = (CPDictionary.isa.method_msgSend[\"alloc\"] || _objj_forward)(CPDictionary, \"alloc\"), ___r"); @@ -1777,7 +2434,7 @@ DictionaryLiteral: function(node, st, c) { if (!(st.maxReceiverLevel >= st.receiverLevel)) st.maxReceiverLevel = st.receiverLevel; } else { - if (compiler.flags & ObjJAcornCompiler.Flags.InlineMsgSend) { + if (compiler.options.inlineMsgSendFunctions) { buffer.concat("(___r"); buffer.concat(++st.receiverLevel + ""); buffer.concat(" = (CPDictionary.isa.method_msgSend[\"alloc\"] || _objj_forward)(CPDictionary, \"alloc\"), ___r"); @@ -1802,7 +2459,7 @@ DictionaryLiteral: function(node, st, c) { if (!(st.maxReceiverLevel >= st.receiverLevel)) st.maxReceiverLevel = st.receiverLevel; - for (var i = 0; i < noOfKeys; i++) { + for (var i = 0; i < keyLength; i++) { var value = node.values[i]; if (i) buffer.concat(", "); @@ -1813,10 +2470,11 @@ DictionaryLiteral: function(node, st, c) { buffer.concat("], ["); - for (var i = 0; i < noOfKeys; i++) { + for (var i = 0; i < keyLength; i++) { var key = node.keys[i]; if (i) buffer.concat(", "); + if (!generate) compiler.lastPos = key.start; c(key, st, "Expression"); if (!generate) buffer.concat(compiler.source.substring(compiler.lastPos, key.end)); @@ -1824,21 +2482,31 @@ DictionaryLiteral: function(node, st, c) { buffer.concat("]))"); } - st.receiverLevel--; + if (!generateObjJ) + st.receiverLevel--; if (!generate) compiler.lastPos = node.end; }, ImportStatement: function(node, st, c) { var compiler = st.compiler, generate = compiler.generate, - buffer = compiler.jsBuffer; + buffer = compiler.jsBuffer, + localfilepath = node.localfilepath, + generateObjJ = compiler.options.generateObjJ; if (!generate) buffer.concat(compiler.source.substring(compiler.lastPos, node.start)); - buffer.concat("objj_executeFile(\""); - buffer.concat(node.filename.value); - buffer.concat(node.localfilepath ? "\", YES);" : "\", NO);"); + if (generateObjJ) { + buffer.concat("@import "); + buffer.concat(localfilepath ? "\"" : "<"); + buffer.concat(node.filename.value); + buffer.concat(localfilepath ? "\"" : ">"); + } else { + buffer.concat("objj_executeFile(\"", node); + buffer.concat(node.filename.value); + buffer.concat(localfilepath ? "\", YES);" : "\", NO);"); + } if (!generate) compiler.lastPos = node.end; }, -ClassDeclarationStatement: function(node, st, c) { +ClassDeclarationStatement: function(node, st, c, format) { var compiler = st.compiler, generate = compiler.generate, saveJSBuffer = compiler.jsBuffer, @@ -1846,15 +2514,17 @@ ClassDeclarationStatement: function(node, st, c) { classDef = compiler.getClassDef(className), classScope = new Scope(st), isInterfaceDeclaration = node.type === "InterfaceDeclarationStatement", - protocols = node.protocols; + protocols = node.protocols, + generateObjJ = compiler.options.generateObjJ; - compiler.imBuffer = new StringBuffer(); - compiler.cmBuffer = new StringBuffer(); - compiler.classBodyBuffer = new StringBuffer(); // TODO: Check if this is needed + compiler.imBuffer = new StringBuffer(compiler.createSourceMap, compiler.URL); + compiler.cmBuffer = new StringBuffer(compiler.createSourceMap), compiler.URL; + compiler.classBodyBuffer = new StringBuffer(compiler.createSourceMap, compiler.URL); // TODO: Check if this is needed if (compiler.getTypeDef(className)) throw compiler.error_message(className + " is already declared as a type", node.classname); + if (!generate) saveJSBuffer.concat(compiler.source.substring(compiler.lastPos, node.start)); // First we declare the class @@ -1875,14 +2545,14 @@ ClassDeclarationStatement: function(node, st, c) { if (!superClassDef) { var errorMessage = "Can't find superclass " + node.superclassname.name; - for (var i = ObjJAcornCompiler.importStack.length; --i >= 0;) + if (ObjJAcornCompiler.importStack) for (var i = ObjJAcornCompiler.importStack.length; --i >= 0;) errorMessage += "\n" + Array((ObjJAcornCompiler.importStack.length - i) * 2 + 1).join(" ") + "Imported by: " + ObjJAcornCompiler.importStack[i]; throw compiler.error_message(errorMessage, node.superclassname); } classDef = new ClassDef(!isInterfaceDeclaration, className, superClassDef, Object.create(null)); - saveJSBuffer.concat("{var the_class = objj_allocateClassPair(" + node.superclassname.name + ", \"" + className + "\"),\nmeta_class = the_class.isa;"); + if (!generateObjJ) saveJSBuffer.concat("\n{var the_class = objj_allocateClassPair(" + node.superclassname.name + ", \"" + className + "\"),\nmeta_class = the_class.isa;", node); } else if (node.categoryname) { @@ -1890,25 +2560,50 @@ ClassDeclarationStatement: function(node, st, c) { if (!classDef) throw compiler.error_message("Class " + className + " not found ", node.classname); - saveJSBuffer.concat("{\nvar the_class = objj_getClass(\"" + className + "\")\n"); - saveJSBuffer.concat("if(!the_class) throw new SyntaxError(\"*** Could not find definition for class \\\"" + className + "\\\"\");\n"); - saveJSBuffer.concat("var meta_class = the_class.isa;"); + if (!generateObjJ) { + saveJSBuffer.concat("{\nvar the_class = objj_getClass(\"" + className + "\")\n", node); + saveJSBuffer.concat("if(!the_class) throw new SyntaxError(\"*** Could not find definition for class \\\"" + className + "\\\"\");\n"); + saveJSBuffer.concat("var meta_class = the_class.isa;"); + } } else { classDef = new ClassDef(!isInterfaceDeclaration, className, null, Object.create(null)); - saveJSBuffer.concat("{var the_class = objj_allocateClassPair(Nil, \"" + className + "\"),\nmeta_class = the_class.isa;"); + if (!generateObjJ) + saveJSBuffer.concat("{var the_class = objj_allocateClassPair(Nil, \"" + className + "\"),\nmeta_class = the_class.isa;", node); } - if (protocols) - for (var i = 0, size = protocols.length; i < size; i++) - { - saveJSBuffer.concat("\nvar aProtocol = objj_getProtocol(\"" + protocols[i].name + "\");"); + if (generateObjJ) { + saveJSBuffer.concat(isInterfaceDeclaration ? "@interface " : "@implementation "); + saveJSBuffer.concat(className); + if (node.superclassname) { + saveJSBuffer.concat(" : "); + c(node.superclassname, st, "IdentifierName"); + } else if (node.categoryname) { + saveJSBuffer.concat(" ("); + c(node.categoryname, st, "IdentifierName"); + saveJSBuffer.concat(")"); + } + } + + if (protocols) for (var i = 0, size = protocols.length; i < size; i++) + { + if (generateObjJ) { + if (i) + saveJSBuffer.concat(", "); + else + saveJSBuffer.concat(" <"); + c(protocols[i], st, "IdentifierName"); + if (i === size - 1) + saveJSBuffer.concat(">"); + } else { + saveJSBuffer.concat("\nvar aProtocol = objj_getProtocol(\"" + protocols[i].name + "\");", protocols[i]); saveJSBuffer.concat("\nif (!aProtocol) throw new SyntaxError(\"*** Could not find definition for protocol \\\"" + protocols[i].name + "\\\"\");"); saveJSBuffer.concat("\nclass_addProtocol(the_class, aProtocol);"); } - /* + } +/* if (isInterfaceDeclaration) classDef.interfaceDeclaration = true; */ @@ -1922,13 +2617,19 @@ ClassDeclarationStatement: function(node, st, c) { hasAccessors = false; // Then we add all ivars - if (node.ivardeclarations) + if (node.ivardeclarations) { + if (generateObjJ) { + saveJSBuffer.concat("{"); + indentation += indentStep; + } + for (var i = 0; i < node.ivardeclarations.length; ++i) { var ivarDecl = node.ivardeclarations[i], ivarType = ivarDecl.ivartype ? ivarDecl.ivartype.name : null, ivarTypeIsClass = ivarDecl.ivartype ? ivarDecl.ivartype.typeisclass : false, - ivarName = ivarDecl.id.name, + ivarIdentifier = ivarDecl.id, + ivarName = ivarIdentifier.name, ivar = {"type": ivarType, "name": ivarName}, accessors = ivarDecl.accessors; @@ -1946,20 +2647,24 @@ ClassDeclarationStatement: function(node, st, c) { || compiler.getClassDef(ivarType) || compiler.getTypeDef(ivarType) || ivarType == classDef.name; if (!isTypeDefined) - compiler.addWarning(createMessage("Unknown type '" + ivarType + "' for ivar '" + ivarName + "'", ivarDecl.id, compiler.source)); + compiler.addWarning(createMessage("Unknown type '" + ivarType + "' for ivar '" + ivarName + "'", ivarDecl.ivartype, compiler.source)); - if (firstIvarDeclaration) - { - firstIvarDeclaration = false; - saveJSBuffer.concat("class_addIvars(the_class, ["); + if (generateObjJ) { + c(ivarDecl, st, "IvarDeclaration"); + } else { + if (firstIvarDeclaration) + { + firstIvarDeclaration = false; + saveJSBuffer.concat("class_addIvars(the_class, ["); + } + else + saveJSBuffer.concat(", "); + + if (compiler.options.includeIvarTypeSignatures) + saveJSBuffer.concat("new objj_ivar(\"" + ivarName + "\", \"" + ivarType + "\")", node); + else + saveJSBuffer.concat("new objj_ivar(\"" + ivarName + "\")", node); } - else - saveJSBuffer.concat(", "); - - if (compiler.flags & ObjJAcornCompiler.Flags.IncludeTypeSignatures) - saveJSBuffer.concat("new objj_ivar(\"" + ivarName + "\", \"" + ivarType + "\")"); - else - saveJSBuffer.concat("new objj_ivar(\"" + ivarName + "\")"); if (ivarDecl.outlet) ivar.outlet = true; @@ -1969,18 +2674,17 @@ ClassDeclarationStatement: function(node, st, c) { if (!classScope.ivars) classScope.ivars = Object.create(null); - classScope.ivars[ivarName] = {type: "ivar", name: ivarName, node: ivarDecl.id, ivar: ivar}; + classScope.ivars[ivarName] = {type: "ivar", name: ivarName, node: ivarIdentifier, ivar: ivar}; - if (accessors) - { + if (accessors) { + // Declare the accessor methods in the class definition. // TODO: This next couple of lines for getting getterName and setterName are duplicated from below. Create functions for this. var property = (accessors.property && accessors.property.name) || ivarName, getterName = (accessors.getter && accessors.getter.name) || property; classDef.addInstanceMethod(new MethodDef(getterName, [ivarType])); - if (!accessors.readonly) - { + if (!accessors.readonly) { var setterName = accessors.setter ? accessors.setter.name : null; if (!setterName) @@ -1994,14 +2698,17 @@ ClassDeclarationStatement: function(node, st, c) { hasAccessors = true; } } - - if (!firstIvarDeclaration) + } + if (generateObjJ) { + indentation = indentation.substring(indentationSize); + saveJSBuffer.concatFormat("\n}"); + } else if (!firstIvarDeclaration) saveJSBuffer.concat("]);"); // If we have accessors add get and set methods for them - if (!isInterfaceDeclaration && hasAccessors) + if (!generateObjJ && !isInterfaceDeclaration && hasAccessors) { - var getterSetterBuffer = new StringBuffer(); + var getterSetterBuffer = new StringBuffer(compiler.createSourceMap, compiler.URL); // Add the class declaration to compile accessors correctly // Remove all protocols from class declaration @@ -2050,7 +2757,7 @@ ClassDeclarationStatement: function(node, st, c) { // Remove all @accessors or we will get a recursive loop in infinity var b = getterSetterBuffer.toString().replace(/@accessors(\(.*\))?/g, ""); - var imBuffer = ObjJAcornCompiler.compileToIMBuffer(b, "Accessors", compiler.flags, compiler.classDefs, compiler.protocolDefs, compiler.typeDefs); + var imBuffer = exports.compileToIMBuffer(b, "Accessors", compiler.options); // Add the accessors methods first to instance method buffer. // This will allow manually added set and get methods to override the compiler generated @@ -2074,45 +2781,46 @@ ClassDeclarationStatement: function(node, st, c) { if (bodyLength > 0) { - if (!generate) - compiler.lastPos = bodies[0].start; + if (!generate) compiler.lastPos = bodies[0].start; // And last add methods and other statements for (var i = 0; i < bodyLength; ++i) { var body = bodies[i]; c(body, classScope, "Statement"); } - if (!generate) - saveJSBuffer.concat(compiler.source.substring(compiler.lastPos, body.end)); + if (!generate) saveJSBuffer.concat(compiler.source.substring(compiler.lastPos, body.end)); } + // We must make a new class object for our class definition if it's not a category - if (!isInterfaceDeclaration && !node.categoryname) { + if (!generateObjJ && !isInterfaceDeclaration && !node.categoryname) { saveJSBuffer.concat("objj_registerClassPair(the_class);\n"); } // Add instance methods - if (compiler.imBuffer.isEmpty()) + if (!generateObjJ && compiler.imBuffer.isEmpty()) { saveJSBuffer.concat("class_addMethods(the_class, ["); - saveJSBuffer.atoms.push.apply(saveJSBuffer.atoms, compiler.imBuffer.atoms); // FIXME: Move this append to StringBuffer + saveJSBuffer.appendStringBuffer(compiler.imBuffer); saveJSBuffer.concat("]);\n"); } // Add class methods - if (compiler.cmBuffer.isEmpty()) + if (!generateObjJ && compiler.cmBuffer.isEmpty()) { saveJSBuffer.concat("class_addMethods(meta_class, ["); - saveJSBuffer.atoms.push.apply(saveJSBuffer.atoms, compiler.cmBuffer.atoms); // FIXME: Move this append to StringBuffer + saveJSBuffer.appendStringBuffer(compiler.cmBuffer); saveJSBuffer.concat("]);\n"); } - saveJSBuffer.concat("}"); + if (!generateObjJ) saveJSBuffer.concat("}\n"); compiler.jsBuffer = saveJSBuffer; // Skip the "@end" - if (!generate) - compiler.lastPos = node.end; + if (!generate) compiler.lastPos = node.end; + + if (generateObjJ) + saveJSBuffer.concat("\n@end"); // If the class conforms to protocols check that all required methods are implemented if (protocols) @@ -2133,8 +2841,8 @@ ClassDeclarationStatement: function(node, st, c) { var unimplementedMethods = classDef.listOfNotImplementedMethodsForProtocols(protocolDefs); if (unimplementedMethods && unimplementedMethods.length > 0) - for (var i = 0, size = unimplementedMethods.length; i < size; i++) { - var unimplementedMethod = unimplementedMethods[i], + for (var j = 0, unimpSize = unimplementedMethods.length; j < unimpSize; j++) { + var unimplementedMethod = unimplementedMethods[j], methodDef = unimplementedMethod.methodDef, protocolDef = unimplementedMethod.protocolDef; @@ -2150,35 +2858,55 @@ ProtocolDeclarationStatement: function(node, st, c) { protocolDef = compiler.getProtocolDef(protocolName), protocols = node.protocols, protocolScope = new Scope(st), - inheritFromProtocols = []; + inheritFromProtocols = [], + generateObjJ = compiler.options.generateObjJ; if (protocolDef) throw compiler.error_message("Duplicate protocol " + protocolName, node.protocolname); - compiler.imBuffer = new StringBuffer(); - compiler.cmBuffer = new StringBuffer(); + compiler.imBuffer = new StringBuffer(compiler.createSourceMap, compiler.URL); + compiler.cmBuffer = new StringBuffer(compiler.createSourceMap), compiler.URL; - if (!generate) - buffer.concat(compiler.source.substring(compiler.lastPos, node.start)); + if (!generate) buffer.concat(compiler.source.substring(compiler.lastPos, node.start)); - buffer.concat("{var the_protocol = objj_allocateProtocol(\"" + protocolName + "\");"); + if (generateObjJ) { + buffer.concat("@protocol "); + c(node.protocolname, st, "IdentifierName"); + } else { + buffer.concat("{var the_protocol = objj_allocateProtocol(\"" + protocolName + "\");", node); + } + + if (protocols) { + if (generateObjJ) + buffer.concat(" <"); - if (protocols) for (var i = 0, size = protocols.length; i < size; i++) { var protocol = protocols[i], - inheritFromProtocolName = protocol.name; + inheritFromProtocolName = protocol.name, inheritProtocolDef = compiler.getProtocolDef(inheritFromProtocolName); if (!inheritProtocolDef) throw compiler.error_message("Can't find protocol " + inheritFromProtocolName, protocol); - buffer.concat("\nvar aProtocol = objj_getProtocol(\"" + inheritFromProtocolName + "\");"); - buffer.concat("\nif (!aProtocol) throw new SyntaxError(\"*** Could not find definition for protocol \\\"" + protocolName + "\\\"\");"); - buffer.concat("\nprotocol_addProtocol(the_protocol, aProtocol);"); + if (generateObjJ) { + if (i) + buffer.concat(", "); + + c(protocol, st, "IdentifierName"); + } else { + buffer.concat("\nvar aProtocol = objj_getProtocol(\"" + inheritFromProtocolName + "\");", node); + buffer.concat("\nif (!aProtocol) throw new SyntaxError(\"*** Could not find definition for protocol \\\"" + protocolName + "\\\"\");", node); + buffer.concat("\nprotocol_addProtocol(the_protocol, aProtocol);", node); + } + inheritFromProtocols.push(inheritProtocolDef); } + if (generateObjJ) + buffer.concat(">"); + } + protocolDef = new ProtocolDef(protocolName, inheritFromProtocols); compiler.protocolDefs[protocolName] = protocolDef; protocolScope.protocolDef = protocolDef; @@ -2191,109 +2919,168 @@ ProtocolDeclarationStatement: function(node, st, c) { if (requiredLength > 0) { // We only add the required methods - for (var i = 0; i < requiredLength; ++i) - { + for (var i = 0; i < requiredLength; ++i) { var required = someRequired[i]; - if (!generate) - compiler.lastPos = required.start; + if (!generate) compiler.lastPos = required.start; c(required, protocolScope, "Statement"); } - if (!generate) - buffer.concat(compiler.source.substring(compiler.lastPos, required.end)); + if (!generate) buffer.concat(compiler.source.substring(compiler.lastPos, required.end)); } } - buffer.concat("\nobjj_registerProtocol(the_protocol);\n"); + if (generateObjJ) { + buffer.concatFormat("\n@end"); + } else { + buffer.concat("\nobjj_registerProtocol(the_protocol);\n"); - // Add instance methods - if (compiler.imBuffer.isEmpty()) - { - buffer.concat("protocol_addMethodDescriptions(the_protocol, ["); - buffer.atoms.push.apply(buffer.atoms, compiler.imBuffer.atoms); // FIXME: Move this append to StringBuffer - buffer.concat("], true, true);\n"); + // Add instance methods + if (compiler.imBuffer.isEmpty()) + { + buffer.concat("protocol_addMethodDescriptions(the_protocol, ["); + buffer.appendStringBuffer(compiler.imBuffer); + buffer.concat("], true, true);\n"); + } + + // Add class methods + if (compiler.cmBuffer.isEmpty()) + { + buffer.concat("protocol_addMethodDescriptions(the_protocol, ["); + buffer.appendStringBuffer(compiler.cmBuffer); + buffer.concat("], true, false);\n"); + } + + buffer.concat("}"); } - // Add class methods - if (compiler.cmBuffer.isEmpty()) - { - buffer.concat("protocol_addMethodDescriptions(the_protocol, ["); - buffer.atoms.push.apply(buffer.atoms, compiler.cmBuffer.atoms); // FIXME: Move this append to StringBuffer - buffer.concat("], true, false);\n"); - } - - buffer.concat("}"); - compiler.jsBuffer = buffer; // Skip the "@end" - if (!generate) - compiler.lastPos = node.end; + if (!generate) compiler.lastPos = node.end; +}, +IvarDeclaration: function(node, st, c, format) { + var compiler = st.compiler, + buffer = compiler.jsBuffer; + + if (node.outlet) + buffer.concat("@outlet "); + c(node.ivartype, st, "IdentifierName"); + buffer.concat(" "); + c(node.id, st, "IdentifierName"); + if (node.accessors) + buffer.concat(" @accessors"); }, MethodDeclarationStatement: function(node, st, c) { var compiler = st.compiler, generate = compiler.generate, saveJSBuffer = compiler.jsBuffer, methodScope = new Scope(st), - isInstanceMethodType = node.methodtype === '-'; + isInstanceMethodType = node.methodtype === '-', selectors = node.selectors, nodeArguments = node.arguments, returnType = node.returntype, - types = [returnType ? returnType.name : (node.action ? "void" : "id")], - returnTypeProtocols = returnType ? returnType.protocols : null; - selector = selectors[0].name; // There is always at least one selector + types = [returnType ? returnType.name : (node.action ? "void" : "id")], // Return type is 'id' as default except if it is an action declared method, then it's 'void' + returnTypeProtocols = returnType ? returnType.protocols : null, + selector = selectors[0].name, // There is always at least one selector + generateObjJ = compiler.options.generateObjJ; - if (returnTypeProtocols) - for (var i = 0, size = returnTypeProtocols.length; i < size; i++) { - var returnTypeProtocol = returnTypeProtocols[i]; - if (!compiler.getProtocolDef(returnTypeProtocol.name)) { - compiler.addWarning(createMessage("Cannot find protocol declaration for '" + returnTypeProtocol.name + "'", returnTypeProtocol, compiler.source)); - } + if (returnTypeProtocols) for (var i = 0, size = returnTypeProtocols.length; i < size; i++) { + var returnTypeProtocol = returnTypeProtocols[i]; + if (!compiler.getProtocolDef(returnTypeProtocol.name)) { + compiler.addWarning(createMessage("Cannot find protocol declaration for '" + returnTypeProtocol.name + "'", returnTypeProtocol, compiler.source)); } - - if (!generate) - saveJSBuffer.concat(compiler.source.substring(compiler.lastPos, node.start)); - - compiler.jsBuffer = isInstanceMethodType ? compiler.imBuffer : compiler.cmBuffer; - - // Put together the selector. Maybe this should be done in the parser... - for (var i = 0; i < nodeArguments.length; i++) { - var argument = nodeArguments[i], - argumentType = argument.type, - argumentTypeName = argumentType ? argumentType.name : "id", - argumentProtocols = argumentType ? argumentType.protocols : null; - - types.push(argumentType ? argumentType.name : "id"); - - if (argumentProtocols) for (var j = 0, size = argumentProtocols.length; j < size; j++) - { - var argumentProtocol = argumentProtocols[j]; - if (!compiler.getProtocolDef(argumentProtocol.name)) - compiler.addWarning(createMessage("Cannot find protocol declaration for '" + argumentProtocol.name + "'", argumentProtocol, compiler.source)); - } - - if (i === 0) - selector += ":"; - else - selector += (selectors[i] ? selectors[i].name : "") + ":"; } - if (compiler.jsBuffer.isEmpty()) // Add comma separator if this is not first method in this buffer - compiler.jsBuffer.concat(", "); + if (!generate) saveJSBuffer.concat(compiler.source.substring(compiler.lastPos, node.start)); - compiler.jsBuffer.concat("new objj_method(sel_getUid(\""); - compiler.jsBuffer.concat(selector); - compiler.jsBuffer.concat("\"), "); + // If we are generating objective-J code write everything directly to the regular buffer + // Otherwise we have one for instance methods and one for class methods. + if (generateObjJ) { + compiler.jsBuffer.concat(isInstanceMethodType ? "- (" : "+ ("); + compiler.jsBuffer.concat(types[0]); + compiler.jsBuffer.concat(")"); + } else { + compiler.jsBuffer = isInstanceMethodType ? compiler.imBuffer : compiler.cmBuffer; + } - if (node.body) - { - compiler.jsBuffer.concat("function"); + // Put together the selector. Maybe this should be done in the parser... + // Or maybe we should do it here as when genereting Objective-J code it's kind of handy + var size = nodeArguments.length; + if (size > 0) { + for (var i = 0; i < nodeArguments.length; i++) { + var argument = nodeArguments[i], + argumentType = argument.type, + argumentTypeName = argumentType ? argumentType.name : "id", + argumentProtocols = argumentType ? argumentType.protocols : null; - if (compiler.flags & ObjJAcornCompiler.Flags.IncludeDebugSymbols) - { - compiler.jsBuffer.concat(" $" + st.currentClassName() + "__" + selector.replace(/:/g, "_")); + types.push(argumentTypeName); + + if (i === 0) + selector += ":"; + else + selector += (selectors[i] ? selectors[i].name : "") + ":"; + + if (argumentProtocols) for (var j = 0, size = argumentProtocols.length; j < size; j++) { + var argumentProtocol = argumentProtocols[j]; + if (!compiler.getProtocolDef(argumentProtocol.name)) { + compiler.addWarning(createMessage("Cannot find protocol declaration for '" + argumentProtocol.name + "'", argumentProtocol, compiler.source)); + } + } + + if (generateObjJ) { + var aSelector = selectors[i]; + + if (i) + compiler.jsBuffer.concat(" "); + + compiler.jsBuffer.concat((aSelector ? aSelector.name : "") + ":"); + compiler.jsBuffer.concat("("); + compiler.jsBuffer.concat(argumentTypeName); + if (argumentProtocols) { + compiler.jsBuffer.concat(" <"); + for (var j = 0, size = argumentProtocols.length; j < size; j++) { + var argumentProtocol = argumentProtocols[j]; + + if (j) + compiler.jsBuffer.concat(", "); + + compiler.jsBuffer.concat(argumentProtocol.name); + } + + compiler.jsBuffer.concat(">"); + } + compiler.jsBuffer.concat(")"); + c(argument.identifier, st, "IdentifierName"); + } } + } else if (generateObjJ) { + var selectorNode = selectors[0]; + compiler.jsBuffer.concat(selectorNode.name, selectorNode); + } - compiler.jsBuffer.concat("(self, _cmd"); + if (generateObjJ) { + if (node.parameters) { + compiler.jsBuffer.concat(", ..."); + } + } else { + if (compiler.jsBuffer.isEmpty()) // Add comma separator if this is not first method in this buffer + compiler.jsBuffer.concat(", "); + + compiler.jsBuffer.concat("new objj_method(sel_getUid(\"", node); + compiler.jsBuffer.concat(selector); + compiler.jsBuffer.concat("\"), "); + } + + if (node.body) { + if (!generateObjJ) { + compiler.jsBuffer.concat("function"); + + if (compiler.options.includeMethodFunctionNames) + { + compiler.jsBuffer.concat(" $" + st.currentClassName() + "__" + selector.replace(/:/g, "_")); + } + + compiler.jsBuffer.concat("(self, _cmd"); + } methodScope.methodType = node.methodtype; methodScope.vars["self"] = {type: "method base", scope: methodScope}; @@ -2304,35 +3091,40 @@ MethodDeclarationStatement: function(node, st, c) { var argument = nodeArguments[i], argumentName = argument.identifier.name; - compiler.jsBuffer.concat(", "); - compiler.jsBuffer.concat(argumentName); + if (!generateObjJ) { + compiler.jsBuffer.concat(", "); + compiler.jsBuffer.concat(argumentName, argument.identifier); + } methodScope.vars[argumentName] = {type: "method argument", node: argument}; } - compiler.jsBuffer.concat(")\n"); + if (!generateObjJ) + compiler.jsBuffer.concat(")\n"); - if (!generate) - compiler.lastPos = node.startOfBody; + if (!generate) compiler.lastPos = node.startOfBody; indentation += indentStep; methodScope.endOfScopeBody = true; c(node.body, methodScope, "Statement"); - indentation = indentation.substring(indentationSpaces); - if (!generate) - compiler.jsBuffer.concat(compiler.source.substring(compiler.lastPos, node.body.end)); + indentation = indentation.substring(indentationSize); + if (!generate) compiler.jsBuffer.concat(compiler.source.substring(compiler.lastPos, node.body.end)); - compiler.jsBuffer.concat("\n"); + if (!generateObjJ) + compiler.jsBuffer.concat("\n"); } else { // It is a interface or protocol declatartion and we don't have a method implementation - compiler.jsBuffer.concat("Nil\n"); + if (generateObjJ) + compiler.jsBuffer.concat(";"); + else + compiler.jsBuffer.concat("Nil\n"); } - if (compiler.flags & ObjJAcornCompiler.Flags.IncludeTypeSignatures) - compiler.jsBuffer.concat(","+JSON.stringify(types)); + if (!generateObjJ) { + if (compiler.options.includeMethodArgumentTypeSignatures) + compiler.jsBuffer.concat(","+JSON.stringify(types)); + compiler.jsBuffer.concat(")"); + compiler.jsBuffer = saveJSBuffer; + } - compiler.jsBuffer.concat(")"); - compiler.jsBuffer = saveJSBuffer; - - if (!generate) - compiler.lastPos = node.end; + if (!generate) compiler.lastPos = node.end; // Add the method to the class or protocol definition var def = st.classDef, @@ -2345,7 +3137,7 @@ MethodDeclarationStatement: function(node, st, c) { def = st.protocolDef; if (!def) - throw "InternalError: MethodDeclaration without ClassDeclaration or ProtocolDeclaration at line: " + exports.acorn.getLineInfo(compiler.source, node.start).line; + throw "InternalError: MethodDeclaration without ClassDeclaration or ProtocolDeclaration at line: " + acorn.getLineInfo(compiler.source, node.start).line; // Create warnings if types does not corresponds to method declaration in superclass or interface declarations // If we don't find the method in superclass or interface declarations above or if it is a protocol @@ -2353,14 +3145,13 @@ MethodDeclarationStatement: function(node, st, c) { if (!alreadyDeclared) { var protocols = def.protocols; - if (protocols) - for (var i = 0, size = protocols.length; i < size; i++) { - var protocol = protocols[i], - alreadyDeclared = isInstanceMethodType ? protocol.getInstanceMethod(selector) : protocol.getClassMethod(selector); + if (protocols) for (var i = 0, size = protocols.length; i < size; i++) { + var protocol = protocols[i], + alreadyDeclared = isInstanceMethodType ? protocol.getInstanceMethod(selector) : protocol.getClassMethod(selector); - if (alreadyDeclared) - break; - } + if (alreadyDeclared) + break; + } } if (alreadyDeclared) { @@ -2398,21 +3189,26 @@ MethodDeclarationStatement: function(node, st, c) { MessageSendExpression: function(node, st, c) { var compiler = st.compiler, generate = compiler.generate, - inlineMsgSend = compiler.flags & ObjJAcornCompiler.Flags.InlineMsgSend, + inlineMsgSend = compiler.options.inlineMsgSendFunctions, buffer = compiler.jsBuffer, nodeObject = node.object, selectors = node.selectors, - arguments = node.arguments, - argumentsLength = arguments.length, + nodeArguments = node.arguments, + argumentsLength = nodeArguments.length, firstSelector = selectors[0], - selector = firstSelector ? firstSelector.name : ""; // There is always at least one selector + selector = firstSelector ? firstSelector.name : "", // There is always at least one selector + parameters = node.parameters, + generateObjJ = compiler.options.generateObjJ; // Put together the selector. Maybe this should be done in the parser... - for (var i = 0; i < argumentsLength; i++) - if (i === 0) - selector += ":"; - else - selector += (selectors[i] ? selectors[i].name : "") + ":"; + for (var i = 0; i < argumentsLength; i++) { + if (i !== 0) { + var nextSelector = selectors[i]; + if (nextSelector) + selector += nextSelector.name; + } + selector += ":"; + } if (!generate) { buffer.concat(compiler.source.substring(compiler.lastPos, node.start)); @@ -2421,24 +3217,28 @@ MessageSendExpression: function(node, st, c) { // Find out the total number of arguments so we can choose appropriate msgSend function. Only needed if call the function and not inline it var totalNoOfParameters = argumentsLength; - if (node.parameters) - totalNoOfParameters += node.parameters.length; + if (parameters) + totalNoOfParameters += parameters.length; } if (node.superObject) { if (!generate) buffer.concat(" "); // Add an extra space if it looks something like this: "return()". No space between return and expression. - if (inlineMsgSend) { - buffer.concat("("); - buffer.concat(st.currentMethodType() === "+" ? compiler.currentSuperMetaClass : compiler.currentSuperClass); - buffer.concat(".method_dtable[\""); - buffer.concat(selector); - buffer.concat("\"] || _objj_forward)(self"); + if (generateObjJ) { + buffer.concat("[super "); } else { - buffer.concat("objj_msgSendSuper"); - if (totalNoOfParameters < 4) { - buffer.concat("" + totalNoOfParameters); + if (inlineMsgSend) { + buffer.concat("("); + buffer.concat(st.currentMethodType() === "+" ? compiler.currentSuperMetaClass : compiler.currentSuperClass); + buffer.concat(".method_dtable[\""); + buffer.concat(selector); + buffer.concat("\"] || _objj_forward)(self"); + } else { + buffer.concat("objj_msgSendSuper"); + if (totalNoOfParameters < 4) { + buffer.concat("" + totalNoOfParameters); + } + buffer.concat("({ receiver:self, super_class:" + (st.currentMethodType() === "+" ? compiler.currentSuperMetaClass : compiler.currentSuperClass ) + " }"); } - buffer.concat("({ receiver:self, super_class:" + (st.currentMethodType() === "+" ? compiler.currentSuperMetaClass : compiler.currentSuperClass ) + " }"); } } else @@ -2498,129 +3298,182 @@ MessageSendExpression: function(node, st, c) { } } - if (generate && !node.superObject) { - if (!inlineMsgSend) { - if (totalNoOfParameters < 4) { - buffer.concat("" + totalNoOfParameters); + if (generateObjJ) { + for (var i = 0; i < argumentsLength || (argumentsLength === 0 && i === 0); i++) { + var selector = selectors[i]; + + buffer.concat(" "); + buffer.concat(selector ? selector.name : ""); + + if (argumentsLength > 0) { + var argument = nodeArguments[i]; + + buffer.concat(":"); + c(argument, st, "Expression"); } } - if (receiverIsIdentifier) { - buffer.concat("("); - c(nodeObject, st, "Expression"); - } else { - buffer.concat("(___r"); - buffer.concat(st.receiverLevel + ""); + if (parameters) for (var i = 0, size = parameters.length; i < size; ++i) + { + var parameter = parameters[i]; + + buffer.concat(", "); + c(parameter, st, "Expression"); } - } + buffer.concat("]"); + } else { + if (generate && !node.superObject) { + if (!inlineMsgSend) { + if (totalNoOfParameters < 4) { + buffer.concat("" + totalNoOfParameters); + } + } - buffer.concat(", \""); - buffer.concat(selector); // FIXME: sel_getUid(selector + "") ? This FIXME is from the old preprocessor compiler - buffer.concat("\""); - - if (node.arguments) for (var i = 0; i < node.arguments.length; i++) - { - var argument = node.arguments[i]; - - buffer.concat(", "); - if (!generate) - compiler.lastPos = argument.start; - c(argument, st, "Expression"); - if (!generate) { - buffer.concat(compiler.source.substring(compiler.lastPos, argument.end)); - compiler.lastPos = argument.end; + if (receiverIsIdentifier) { + buffer.concat("("); + c(nodeObject, st, "Expression"); + } else { + buffer.concat("(___r"); + buffer.concat(st.receiverLevel + ""); + } } - } - // TODO: Move this 'if' with body up inside the node.argument 'if' - if (node.parameters) for (var i = 0; i < node.parameters.length; ++i) - { - var parameter = node.parameters[i]; + buffer.concat(", \""); + buffer.concat(selector); // FIXME: sel_getUid(selector + "") ? This FIXME is from the old preprocessor compiler + buffer.concat("\""); - buffer.concat(", "); - if (!generate) - compiler.lastPos = parameter.start; - c(parameter, st, "Expression"); - if (!generate) { - buffer.concat(compiler.source.substring(compiler.lastPos, parameter.end)); - compiler.lastPos = parameter.end; + if (nodeArguments) for (var i = 0; i < nodeArguments.length; i++) + { + var argument = nodeArguments[i]; + + buffer.concat(", "); + if (!generate) + compiler.lastPos = argument.start; + c(argument, st, "Expression"); + if (!generate) { + buffer.concat(compiler.source.substring(compiler.lastPos, argument.end)); + compiler.lastPos = argument.end; + } } + + if (parameters) for (var i = 0; i < parameters.length; ++i) + { + var parameter = parameters[i]; + + buffer.concat(", "); + if (!generate) + compiler.lastPos = parameter.start; + c(parameter, st, "Expression"); + if (!generate) { + buffer.concat(compiler.source.substring(compiler.lastPos, parameter.end)); + compiler.lastPos = parameter.end; + } + } + + if (generate && !node.superObject) { + if (receiverIsNotSelf) + buffer.concat(")"); + if (!receiverIsIdentifier) + st.receiverLevel--; + } + + buffer.concat(")"); } - if (generate && !node.superObject) { - if (receiverIsNotSelf) - buffer.concat(")"); - if (!receiverIsIdentifier) - st.receiverLevel--; - } - - buffer.concat(")"); if (!generate) compiler.lastPos = node.end; }, SelectorLiteralExpression: function(node, st, c) { var compiler = st.compiler, buffer = compiler.jsBuffer, - generate = compiler.generate; + generate = compiler.generate, + generateObjJ = compiler.options.generateObjJ; + if (!generate) { buffer.concat(compiler.source.substring(compiler.lastPos, node.start)); buffer.concat(" "); // Add an extra space if it looks something like this: "return(@selector(a:))". No space between return and expression. } - buffer.concat("sel_getUid(\""); + + buffer.concat(generateObjJ ? "@selector(" : "sel_getUid(\"", node); buffer.concat(node.selector); - buffer.concat("\")"); + buffer.concat(generateObjJ ? ")" : "\")"); + if (!generate) compiler.lastPos = node.end; }, ProtocolLiteralExpression: function(node, st, c) { var compiler = st.compiler, buffer = compiler.jsBuffer, - generate = compiler.generate; + generate = compiler.generate, + generateObjJ = compiler.options.generateObjJ; + if (!generate) { buffer.concat(compiler.source.substring(compiler.lastPos, node.start)); buffer.concat(" "); // Add an extra space if it looks something like this: "return(@protocol(a))". No space between return and expression. } - buffer.concat("objj_getProtocol(\""); - buffer.concat(node.id.name); - buffer.concat("\")"); + buffer.concat(generateObjJ ? "@protocol(" : "objj_getProtocol(\"", node); + c(node.id, st, "IdentifierName"); + buffer.concat(generateObjJ ? ")" : "\")"); if (!generate) compiler.lastPos = node.end; }, Reference: function(node, st, c) { var compiler = st.compiler, buffer = compiler.jsBuffer, - generate = compiler.generate; + generate = compiler.generate, + generateObjJ = compiler.options.generateObjJ; + if (!generate) { buffer.concat(compiler.source.substring(compiler.lastPos, node.start)); buffer.concat(" "); // Add an extra space if it looks something like this: "return()". No space between return and expression. } - buffer.concat("function(__input) { if (arguments.length) return "); - c(node.element, st, "Expression"); - buffer.concat(" = __input; return "); - c(node.element, st, "Expression"); - buffer.concat("; }"); + if (generateObjJ) { + buffer.concat("@ref(", node); + buffer.concat(node.element.name, node.element); + buffer.concat(")", node); + } else { + buffer.concat("function(__input) { if (arguments.length) return ", node); + c(node.element, st, "Expression"); + buffer.concat(" = __input; return "); + c(node.element, st, "Expression"); + buffer.concat("; }"); + } + if (!generate) compiler.lastPos = node.end; }, Dereference: function(node, st, c) { var compiler = st.compiler, - generate = compiler.generate; + buffer = compiler.jsBuffer, + generate = compiler.generate, + generateObjJ = compiler.options.generateObjJ; checkCanDereference(st, node.expr); // @deref(y) -> y() // @deref(@deref(y)) -> y()() if (!generate) { - compiler.jsBuffer.concat(compiler.source.substring(compiler.lastPos, node.start)); + buffer.concat(compiler.source.substring(compiler.lastPos, node.start)); compiler.lastPos = node.expr.start; } + if (generateObjJ) + buffer.concat("@deref("); c(node.expr, st, "Expression"); - if (!generate) compiler.jsBuffer.concat(compiler.source.substring(compiler.lastPos, node.expr.end)); - compiler.jsBuffer.concat("()"); + if (!generate) buffer.concat(compiler.source.substring(compiler.lastPos, node.expr.end)); + if (generateObjJ) + buffer.concat(")"); + else + buffer.concat("()"); if (!generate) compiler.lastPos = node.end; }, ClassStatement: function(node, st, c) { - var compiler = st.compiler; + var compiler = st.compiler, + buffer = compiler.jsBuffer, + generateObjJ = compiler.options.generateObjJ; if (!compiler.generate) { - compiler.jsBuffer.concat(compiler.source.substring(compiler.lastPos, node.start)); + buffer.concat(compiler.source.substring(compiler.lastPos, node.start)); compiler.lastPos = node.start; - compiler.jsBuffer.concat("//"); + buffer.concat("//"); + } + if (generateObjJ) { + buffer.concat("@class "); + c(node.id, st, "IdentifierName"); } var className = node.id.name; @@ -2628,17 +3481,22 @@ ClassStatement: function(node, st, c) { throw compiler.error_message(className + " is already declared as a type", node.id); if (!compiler.getClassDef(className)) { - classDef = new ClassDef(false, className); - compiler.classDefs[className] = classDef; + compiler.classDefs[className] = new ClassDef(false, className); } st.vars[node.id.name] = {type: "class", node: node.id}; }, GlobalStatement: function(node, st, c) { - var compiler = st.compiler; + var compiler = st.compiler, + buffer = compiler.jsBuffer, + generateObjJ = compiler.options.generateObjJ; if (!compiler.generate) { - compiler.jsBuffer.concat(compiler.source.substring(compiler.lastPos, node.start)); + buffer.concat(compiler.source.substring(compiler.lastPos, node.start)); compiler.lastPos = node.start; - compiler.jsBuffer.concat("//"); + buffer.concat("//"); + } + if (generateObjJ) { + buffer.concat("@global "); + c(node.id, st, "IdentifierName"); } st.rootScope().vars[node.id.name] = {type: "global", node: node.id}; }, @@ -2651,7 +3509,6 @@ PreprocessStatement: function(node, st, c) { } }, TypeDefStatement: function(node, st, c) { - var compiler = st.compiler, generate = compiler.generate, buffer = compiler.jsBuffer, @@ -2665,9 +3522,6 @@ TypeDefStatement: function(node, st, c) { if (compiler.getClassDef(typeDefName)) throw compiler.error_message(typeDefName + " is already declared as class", node.typedefname); - compiler.imBuffer = new StringBuffer(); - compiler.cmBuffer = new StringBuffer(); - if (!generate) buffer.concat(compiler.source.substring(compiler.lastPos, node.start)); @@ -2681,10 +3535,10 @@ TypeDefStatement: function(node, st, c) { buffer.concat("}"); - compiler.jsBuffer = buffer; - - // Skip the "@end" + // Skip to the end if (!generate) compiler.lastPos = node.end; } }); + +}); diff --git a/Objective-J/acorn.js b/Objective-J/acorn.js index 594111ee0..550d06d49 100644 --- a/Objective-J/acorn.js +++ b/Objective-J/acorn.js @@ -13,28 +13,28 @@ // // [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 -// -// Objective-J extensions made by Martin Carlberg -// -// Git repositories for Acorn with Objective-J extension is available at -// -// https://github.com/mrcarlberg/acorn.git if (typeof exports != "undefined" && !exports.acorn) { exports.acorn = {}; exports.acorn.walk = {}; } -(function(exports) { +(function(exports, walk) { "use strict"; - exports.version = "0.1.01"; + 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 @@ -49,6 +49,9 @@ if (typeof exports != "undefined" && !exports.acorn) { 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); }; @@ -78,6 +81,10 @@ if (typeof exports != "undefined" && !exports.acorn) { // 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 @@ -90,6 +97,16 @@ if (typeof exports != "undefined" && !exports.acorn) { // 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 @@ -125,12 +142,20 @@ if (typeof exports != "undefined" && !exports.acorn) { // Preprocess undefine macro function. To delete a macro preprocessUndefineMacro: defaultUndefineMacro, // Preprocess is macro function - preprocessIsMacro: defaultIsMacro + 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 }; function setOptions(opts) { options = opts || {}; - for (var opt in defaultOptions) if (!options.hasOwnProperty(opt)) + for (var opt in defaultOptions) if (!Object.prototype.hasOwnProperty.call(options, opt)) options[opt] = defaultOptions[opt]; sourceFile = options.sourceFile || null; } @@ -138,6 +163,14 @@ if (typeof exports != "undefined" && !exports.acorn) { 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)}, + __BROWSER__: function() {return macrosMakeBuiltin("__BROWSER__", typeof(window) !== "undefined" ? "1" : null, tokPos)}, + __LINE__: function() {return macrosMakeBuiltin("__LINE__", String(options.locations ? tokCurLine : getLineInfo(input, tokPos).line), tokPos)}, + } + function defaultAddMacro(macro) { macros[macro.identifier] = macro; macrosIsPredicate = null; @@ -153,8 +186,40 @@ if (typeof exports != "undefined" && !exports.acorn) { } function defaultIsMacro(macroIdentifier) { - var x = Object.keys(macros).join(" "); - return (macrosIsPredicate || (macrosIsPredicate = makePredicate(x)))(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 @@ -186,6 +251,7 @@ if (typeof exports != "undefined" && !exports.acorn) { input = String(inpt); inputLen = input.length; setOptions(opts); initTokenState(); + initPreprocessorState(); var t = {}; function getToken(forceRegexp) { @@ -198,14 +264,14 @@ if (typeof exports != "undefined" && !exports.acorn) { getToken.jumpTo = function(pos, reAllowed) { tokPos = pos; if (options.locations) { - tokCurLine = tokLineStart = lineBreak.lastIndex = 0; + tokCurLine = 1; + tokLineStart = lineBreak.lastIndex = 0; var match; while ((match = lineBreak.exec(input)) && match.index < pos) { ++tokCurLine; tokLineStart = match.index + match[0].length; } } - var ch = input.charAt(pos - 1); tokRegexpAllowed = reAllowed; skipSpace(); }; @@ -220,8 +286,13 @@ if (typeof exports != "undefined" && !exports.acorn) { 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 tokStart, tokEnd; + var tokFirstStart, tokStart, tokEnd, tokMacroOffset, tokPosMacroOffset, lastTokMacroOffset; // When `options.locations` is true, these hold objects // containing the tokens start and end line/column pairs. @@ -261,27 +332,21 @@ if (typeof exports != "undefined" && !exports.acorn) { // track of the current line, and know when a new line has been // entered. - var tokCurLine, tokLineStart, tokLineStartNext; + 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; + var tokInput, preTokInput, tokFirstInput; // 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. `afterImport` is used - // to make the part between '<' and '>' to be one token if it comes after - // a @import token. - - var tokAfterImport; - // 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 + // not really a subscript. var nodeMessageSendObjectExpression; @@ -296,8 +361,24 @@ if (typeof exports != "undefined" && !exports.acorn) { var preTokPos, preTokType, preTokVal, preTokStart, preTokEnd; var preLastStart, preLastEnd; - var preprocessStack = []; - var preprocessMacroParamterListMode = false; + var preprocessStack; + var preprocessStackLastItem; + var preprocessOnlyTransformArgumentsForLastToken; + var preprocessMacroParameterListMode; + var preprocessIsParsingPreprocess; + var preprocessParameterScope; + var preTokParameterScope; + var preprocessOverrideTokEndLoc; + + // 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 @@ -307,9 +388,11 @@ if (typeof exports != "undefined" && !exports.acorn) { 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.line = pos.line; - syntaxError.column = pos.column; + syntaxError.messageOnLine = pos.line; + syntaxError.messageOnColumn = pos.column; syntaxError.lineStart = pos.lineStart; syntaxError.lineEnd = pos.lineEnd; syntaxError.fileName = sourceFile; @@ -317,6 +400,10 @@ if (typeof exports != "undefined" && !exports.acorn) { 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 @@ -369,7 +456,7 @@ if (typeof exports != "undefined" && !exports.acorn) { // Objective-J @ keywords var _implementation = {keyword: "implementation"}, _outlet = {keyword: "outlet"}, _accessors = {keyword: "accessors"}; - var _end = {keyword: "end"}, _import = {keyword: "import", afterImport: true}; + 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"}; @@ -396,11 +483,15 @@ if (typeof exports != "undefined" && !exports.acorn) { 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"} // Map keyword names to token types. @@ -430,9 +521,9 @@ if (typeof exports != "undefined" && !exports.acorn) { // Map Preprocessor keyword names to token types. - var keywordTypesPreprocess = {"define": _preDefine, "pragma": _prePragma, "ifdef": _preIfdef, "ifndef": _preIfndef, + var keywordTypesPreprocessor = {"define": _preDefine, "pragma": _prePragma, "ifdef": _preIfdef, "ifndef": _preIfndef, "undef": _preUndef, "if": _preIf, "endif": _preEndif, "else": _preElse, "elif": _preElseIf, - "defined": _preDefined}; + "defined": _preDefined, "warning": _preWarning, "error": _preError}; // Punctuation token types. Again, the `type` property is purely for debugging. @@ -463,7 +554,7 @@ if (typeof exports != "undefined" && !exports.acorn) { 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}; + 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}; @@ -477,7 +568,7 @@ if (typeof exports != "undefined" && !exports.acorn) { 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]; + 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 @@ -555,7 +646,7 @@ if (typeof exports != "undefined" && !exports.acorn) { // The preprocessor keywords. - var isKeywordPreprocess = makePredicate("define pragma if ifdef ifndef else elif endif defined"); + var isKeywordPreprocessor = makePredicate("define undef pragma if ifdef ifndef else elif endif defined error warning"); // ## Character categories @@ -567,7 +658,7 @@ if (typeof exports != "undefined" && !exports.acorn) { 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 = "\u0371-\u0374\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 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 + "]"); @@ -582,17 +673,17 @@ if (typeof exports != "undefined" && !exports.acorn) { // Test whether a given character code starts an identifier. - function isIdentifierStart(code) { + 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. - function isIdentifierChar(code) { + var isIdentifierChar = exports.isIdentifierChar = function(code) { if (code < 48) return code === 36; if (code < 58) return true; if (code < 65) return false; @@ -600,7 +691,7 @@ if (typeof exports != "undefined" && !exports.acorn) { if (code < 97) return code === 95; if (code < 123)return true; return code >= 0xaa && nonASCIIidentifier.test(String.fromCharCode(code)); - } + }; // ## Tokenizer @@ -610,73 +701,119 @@ if (typeof exports != "undefined" && !exports.acorn) { 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 === 0 ? macroCurrentLineStart : 0); + } + } + } + + function PositionOffset(line, column) { + this.line = line; + 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() { - macros = Object.create(null); tokCurLine = 1; - tokPos = tokLineStart = 0; + 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; + 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. -var preprocessTokens = [_preIf, _preIfdef, _preIfndef, _preElse, _preElseIf, _preEndif]; - - function finishToken(type, val) { - // If we get any of these preprocess tokens skip it and read next - if (type in preprocessTokens) return readToken(); - tokEnd = tokPos; - if (options.locations) tokEndLoc = new line_loc_t; + function finishToken(type, val, overrideTokEnd) { + if (overrideTokEnd) { + tokEnd = overrideTokEnd; + if (options.locations) tokEndLoc = preprocessOverrideTokLoc; + } else { + tokEnd = tokPos; + if (options.locations) tokEndLoc = new line_loc_t; + } tokType = type; skipSpace(); if (options.preprocess && input.charCodeAt(tokPos) === 35 && input.charCodeAt(tokPos + 1) === 35) { // '##' - var val1 = type === _name ? val : type.keyword; + var val1 = val != null ? val : type.keyword || type.type; tokPos += 2; - if (val1) { + 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(); - readToken(); - var val2 = tokType === _name ? tokVal : tokType.keyword; - if (val2) { - var concat = "" + val1 + val2, - code = concat.charCodeAt(0), - tok; - if (isIdentifierStart(code)) - tok = readWord(concat) !== false; - - // We might got a word token from the concatenation - if (tok) return tok; - // FIXME: Is not using the concatenated token - tok = getTokenFromCode(code, finishToken); - if (tok === false) { - unexpected(); - } - // We have now got another type of token from the concatenation - return tok; - } else { - // FIXME: Second token was not of right type. Save second token and return the first. When readToken is called again return the second. + 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; - tokAfterImport = type.afterImport; } - function skipBlockComment() { + 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"); @@ -689,26 +826,30 @@ var preprocessTokens = [_preIf, _preIfdef, _preIfndef, _preElse, _preElseIf, _pr tokLineStart = match.index + match[0].length; } } - 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(start, end)); + 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() { + 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 !== 8329) { + while (tokPos < inputLen && ch !== 10 && ch !== 13 && ch !== 8232 && ch !== 8233) { ++tokPos; ch = input.charCodeAt(tokPos); } - 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(start, 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() { @@ -717,11 +858,15 @@ var preprocessTokens = [_preIf, _preIfdef, _preIfndef, _preElse, _preElseIf, _pr // 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 !== 8329) || last === 92)) { // White space and '\' + 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); } + if (options.locations) { + ++tokCurLine; + tokLineStart = tokPos; + } } // Called at the start of the parse and after every token. Skips @@ -733,53 +878,90 @@ var preprocessTokens = [_preIf, _preIfdef, _preIfndef, _preElse, _preElseIf, _pr function skipSpace() { tokComments = null; tokSpaces = null; - var spaceStart = tokPos; + onlySkipSpace(); + } + + function onlySkipSpace(dontSkipEOL, dontSkipMacroBoundary, dontSkipComments) { + var spaceStart = tokPos, + lastIsNewlinePos; for(;;) { var ch = input.charCodeAt(tokPos); if (ch === 32) { // ' ' ++tokPos; - } else if(ch === 13) { + } else if (ch === 13 && !dontSkipEOL) { + lastIsNewlinePos = tokPos; ++tokPos; var next = input.charCodeAt(tokPos); - if(next === 10) { + if (next === 10) { ++tokPos; } - if(options.locations) { + if (options.locations) { ++tokCurLine; tokLineStart = tokPos; } - } else if (ch === 10) { + } else if (ch === 10 && !dontSkipEOL) { + lastIsNewlinePos = tokPos; ++tokPos; - ++tokCurLine; - tokLineStart = tokPos; - } else if(ch < 14 && ch > 8) { + if (options.locations) { + ++tokCurLine; + tokLineStart = tokPos; + } + } else if (ch === 9) { ++tokPos; - } else if (ch === 47) { // '/' + } else if (ch === 47 && !dontSkipComments) { // '/' var next = input.charCodeAt(tokPos+1); if (next === 42) { // '*' if (options.trackSpaces) (tokSpaces || (tokSpaces = [])).push(input.slice(spaceStart, tokPos)); - skipBlockComment(); + skipBlockComment(lastIsNewlinePos); spaceStart = tokPos; } else if (next === 47) { // '/' if (options.trackSpaces) (tokSpaces || (tokSpaces = [])).push(input.slice(spaceStart, tokPos)); - skipLineComment(); + skipLineComment(lastIsNewlinePos); spaceStart = tokPos; } else break; - } else if (ch === 160) { // '\xa0' - ++tokPos; - } else if (ch >= 5760 && nonASCIIwhitespace.test(String.fromCharCode(ch))) { + } 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 && preprocessStack.length) { + if (options.preprocess) { + if (dontSkipMacroBoundary) return true; + if (!preprocessStack.length) break; // 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; - lastEnd = lastItem.lastEnd; - lastStart = lastItem.lastStart; + tokCurLine = lastItem.currentLine; + tokLineStart = lastItem.currentLineStart; + /*tokStart = *///tokFirstStart = lastItem.tokStart; + //lastEnd = lastItem.lastEnd; + //lastStart = lastItem.lastStart; + preprocessOnlyTransformArgumentsForLastToken = lastItem.onlyTransformArgumentsForLastToken; + preprocessParameterScope = lastItem.parameterScope; + tokPosMacroOffset = lastItem.macroOffset; + // Set the last item + var lastIndex = preprocessStack.length; + preprocessStackLastItem = lastIndex ? preprocessStack[lastIndex - 1] : null; + onlySkipSpace(dontSkipEOL); + } else { + break; + } + } else if (ch === 92 && options.preprocess) { // '\' + // 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; } @@ -801,202 +983,216 @@ var preprocessTokens = [_preIf, _preIfdef, _preIfndef, _preElse, _preElseIf, _pr // The `forceRegexp` parameter is used in the one case where the // `tokRegexpAllowed` trick does not work. See `parseStatement`. - function readToken_dot(code, finishToken) { + function readToken_dot(code, finisher) { var next = input.charCodeAt(tokPos+1); - if (next >= 48 && next <= 57) return readNumber(String.fromCharCode(code), finishToken); + if (next >= 48 && next <= 57) return readNumber(String.fromCharCode(code), finisher); if (next === 46 && options.objj && input.charCodeAt(tokPos+2) === 46) { //'.' tokPos += 3; - return finishToken(_dotdotdot); + return finisher(_dotdotdot); } ++tokPos; - return finishToken(_dot); + return finisher(_dot); } - function readToken_slash(finishToken) { // '/' + function readToken_slash(finisher) { // '/' var next = input.charCodeAt(tokPos+1); if (tokRegexpAllowed) {++tokPos; return readRegexp();} - if (next === 61) return finishOp(_assign, 2, finishToken); - return finishOp(_slash, 1, finishToken); + if (next === 61) return finishOp(_assign, 2, finisher); + return finishOp(_slash, 1, finisher); } - function readToken_mult_modulo(finishToken) { // '%*' + function readToken_mult_modulo(finisher) { // '%*' var next = input.charCodeAt(tokPos+1); - if (next === 61) return finishOp(_assign, 2, finishToken); - return finishOp(_bin10, 1, finishToken); + if (next === 61) return finishOp(_assign, 2, finisher); + return finishOp(_bin10, 1, finisher); } - function readToken_pipe_amp(code, finishToken) { // '|&' + function readToken_pipe_amp(code, finisher) { // '|&' var next = input.charCodeAt(tokPos+1); - if (next === code) return finishOp(code === 124 ? _bin1 : _bin2, 2, finishToken); - if (next === 61) return finishOp(_assign, 2, finishToken); - return finishOp(code === 124 ? _bin3 : _bin5, 1, finishToken); + 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(finishToken) { // '^' + function readToken_caret(finisher) { // '^' var next = input.charCodeAt(tokPos+1); - if (next === 61) return finishOp(_assign, 2, finishToken); - return finishOp(_bin4, 1, finishToken); + if (next === 61) return finishOp(_assign, 2, finisher); + return finishOp(_bin4, 1, finisher); } - function readToken_plus_min(code, finishToken) { // '+-' + function readToken_plus_min(code, finisher) { // '+-' var next = input.charCodeAt(tokPos+1); - if (next === code) return finishOp(_incdec, 2, finishToken); - if (next === 61) return finishOp(_assign, 2, finishToken); - return finishOp(_plusmin, 1, finishToken); + 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, finishToken) { // '<>' - if (tokAfterImport && options.objj && code === 60) { // '<' - var str = []; - for (;;) { - if (tokPos >= inputLen) raise(tokStart, "Unterminated import statement"); + function readToken_lt_gt(code, finisher) { // '<>' + if (tokType === _import && options.objj && code === 60) { // '<' + for (var start = tokPos + 1;;) { var ch = input.charCodeAt(++tokPos); - if (ch === 62) { // '>' - ++tokPos; - return finishToken(_filename, String.fromCharCode.apply(null, str)); - } - str.push(ch); + 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, finishToken); - return finishOp(_bin8, size, finishToken); + 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, finishToken); + return finishOp(_bin7, size, finisher); } - function readToken_eq_excl(code, finishToken) { // '=!' + 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, finishToken); - return finishOp(code === 61 ? _eq : _prefix, 1, finishToken); + 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, finishToken) { // '@' + function readToken_at(code, finisher) { // '@' var next = input.charCodeAt(++tokPos); if (next === 34 || next === 39) // Read string if "'" or '"' - return readString(next, finishToken); + return readString(next, finisher); if (next === 123) // Read dictionary literal if "{" - return finishToken(_dictionaryLiteral); - if (next === 91) // Ready array literal if "[" - return finishToken(_arrayLiteral); + return finisher(_dictionaryLiteral); + if (next === 91) // Read array literal if "[" + return finisher(_arrayLiteral); var word = readWord1(), token = objJAtKeywordTypes[word]; - if (!token) raise(tokPos, "Unrecognized Objective-J keyword '@" + word + "'"); - return finishToken(token); + if (!token) raise(tokStart, "Unrecognized Objective-J keyword '@" + word + "'"); + return finisher(token); } -// True if we are skipping token when finding #else or #endif after and #if - -var preNotSkipping = true; -var preIfLevel = 0; - - function readToken_preprocess(finishTokenFunction) { // '#' + function readToken_preprocess(finisher) { // '#' ++tokPos; - preprocessReadToken(); + preprocessSkipSpace(); + preprocessReadToken(false, true); // Dont track and it is a preprocessToken switch (preTokType) { case _preDefine: - preprocessReadToken(); - var macroIdentifierEnd = preTokEnd; - 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 first = true; - while (!preprocessEat(_parenR)) { - if (!first) preprocessExpect(_comma, "Expected ',' between macro parameters"); else first = false; - parameters.push(preprocessGetIdent()); - } + if (preNotSkipping) { + preprocessParseDefine(); + } else { + return finisher(_preDefine); } - var start = tokPos = preTokStart; - preprocesSkipRestOfLine(); - var macroString = input.slice(start, tokPos); - macroString = macroString.replace(/\\/g, " "); - options.preprocessAddMacro(new Macro(macroIdentifier, macroString, parameters)); break; case _preUndef: preprocessReadToken(); options.preprocessUndefineMacro(preprocessGetIdent()); - preprocesSkipRestOfLine(); break; case _preIf: if (preNotSkipping) { - preIfLevel++; - preprocessReadToken(); - var expr = preprocessParseExpression(); + // 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(!test); + if (!test) { + preNotSkipping = false; + preprocessSkipToElseOrEndif(); + } + tokRegexpAllowed = saveTokRegexpAllowed; } else { - return finishTokenFunction(_preIf); + return finisher(_preIf); } break; case _preIfdef: if (preNotSkipping) { - preIfLevel++; + preIfLevel.push(_preIf); preprocessReadToken(); var ident = preprocessGetIdent(); - var test = options.preprocessGetMacro(ident); - if (!test) + var test = options.preprocessIsMacro(ident); + if (!test) { preNotSkipping = false - //preprocessExpect(_eol); - preprocessSkipToElseOrEndif(!test); + preprocessSkipToElseOrEndif(); + } } else { - //preprocesSkipRestOfLine(); - return finishTokenFunction(_preIfdef); + return finisher(_preIfdef); } break; case _preIfndef: if (preNotSkipping) { - preIfLevel++; + preIfLevel.push(_preIf); preprocessReadToken(); var ident = preprocessGetIdent(); - var test = options.preprocessGetMacro(ident); - if (test) + var test = options.preprocessIsMacro(ident); + if (test) { preNotSkipping = false - //preprocessExpect(_eol); - preprocessSkipToElseOrEndif(test); + preprocessSkipToElseOrEndif(); + } } else { //preprocesSkipRestOfLine(); - return finishTokenFunction(_preIfndef); + return finisher(_preIfndef); } break; case _preElse: - if (preIfLevel) { + if (preIfLevel.length) { if (preNotSkipping) { - preNotSkipping = false; - finishTokenFunction(_preElse); - preprocessReadToken(); - preprocessSkipToElseOrEndif(true, true); // no else + 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 { - return finishTokenFunction(_preElse); + preIfLevel[preIfLevel.length - 1] = _preElse; + return finisher(_preElse); } } else raise(preTokStart, "#else without #if"); break; - case _preEndif: - if (preIfLevel) { + case _preElseIf: + if (preIfLevel.length) { if (preNotSkipping) { - preIfLevel--; + 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 finishTokenFunction(_preEndif); + return finisher(_preEndif); break; case _prePragma: @@ -1007,20 +1203,88 @@ var preIfLevel = 0; 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; + 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 finishTokenFunction(_preprocess); + return finisher(_preprocess); //raise(tokPos, "Invalid preprocessing directive '" + (preTokType.keyword || preTokVal) + "' " + input.slice(tokStart, tokPos)); } - // Drop this token and read next non preprocess token - finishToken(_preprocess); + // 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(_preprocess, null, null, true); // skipEOL return readToken(); } + function preprocessParseDefine() { + preprocessIsParsingPreprocess = true; + preprocessReadToken(); + var macroIdentifierEnd = preTokEnd; + 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; + } + } + var start = preTokStart; + var positionOffset = options.locations && new PositionOffset(tokCurLine, tokLineStart); + while(preTokType !== _eol && preTokType !== _eof) + preprocessReadToken(); + + var macroString = input.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 exports.walk.recursive(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) { @@ -1038,65 +1302,97 @@ var preIfLevel = 0; return c(left, st) < c(right, st); case ">": return c(left, st) > c(right, st); - case "=": + 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) { - var name = node.name, - macro = options.preprocessGetMacro(name); - return (macro && parseInt(macro.macro)) || 0; + // If it is not macro expanded it should be counted as a zero + return 0; }, DefinedExpression: function(node, st, c) { - return !!options.preprocessGetMacro(node.id.name); + 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, finishToken, allowEndOfLineToken) { + 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, finishToken); + return readToken_dot(code, finisher); // Punctuation tokens. - case 40: ++tokPos; return finishToken(_parenL); - case 41: ++tokPos; return finishToken(_parenR); - case 59: ++tokPos; return finishToken(_semi); - case 44: ++tokPos; return finishToken(_comma); - case 91: ++tokPos; return finishToken(_bracketL); - case 93: ++tokPos; return finishToken(_bracketR); - case 123: ++tokPos; return finishToken(_braceL); - case 125: ++tokPos; return finishToken(_braceR); - case 58: ++tokPos; return finishToken(_colon); - case 63: ++tokPos; return finishToken(_question); + 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(finishToken); + 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, finishToken); + return readNumber(false, finisher); // Quotes produce strings. case 34: case 39: // '"', "'" - return readString(code, finishToken); + return readString(code, finisher); // Operators are parsed inline in tiny state machines. '=' (61) is // often referred to. `finishOp` simply skips the amount of @@ -1104,116 +1400,203 @@ var preIfLevel = 0; // of the type given by its first argument. case 47: // '/' - return readToken_slash(finishToken); + return readToken_slash(finisher); case 37: case 42: // '%*' - return readToken_mult_modulo(finishToken); + return readToken_mult_modulo(finisher); case 124: case 38: // '|&' - return readToken_pipe_amp(code, finishToken); + return readToken_pipe_amp(code, finisher); case 94: // '^' - return readToken_caret(finishToken); + return readToken_caret(finisher); case 43: case 45: // '+-' - return readToken_plus_min(code, finishToken); + return readToken_plus_min(code, finisher); case 60: case 62: // '<>' - return readToken_lt_gt(code, finishToken, finishToken); + return readToken_lt_gt(code, finisher); case 61: case 33: // '=!' - return readToken_eq_excl(code, finishToken); + return readToken_eq_excl(code, finisher); case 126: // '~' - return finishOp(_prefix, 1, finishToken); + return finishOp(_prefix, 1, finisher); case 64: // '@' if (options.objj) - return readToken_at(code, finishToken); + return readToken_at(code, finisher); return false; case 35: // '#' if (options.preprocess) { - return readToken_preprocess(finishToken); + 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(lastEnd, tokPos)); + if (lastEnd !== 0 && lastEnd !== tokPos && !match) { + 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, finishToken); + return finishOp(_preBackslash, 1, finisher); } return false; } - if (allowEndOfLineToken && newline.test(String.fromCharCode(code))) { - return finishOp(_eol, 1, finishToken); + if (allowEndOfLineToken) { + var r; + if (code === 13) { + r = finishOp(_eol, input.charCodeAt(tokPos+1) === 10 ? 2 : 1, finisher); + } else if (code === 10 || code === 8232 || code === 8233) { + r = finishOp(_eol, 1, finisher); + } else { + return false; + } + if (options.locations) { + ++tokCurLine; + tokLineStart = tokPos; + } + return r; } return false; -} + } -// Returns true if it stops at a line break + // Stringify next token and return with it as a literal string. - function preprocessSkipSpace() { - while (tokPos < inputLen) { - var ch = input.charCodeAt(tokPos); - if (ch === 32 || ch === 9 || ch === 160 || (ch >= 5760 && nonASCIIwhitespaceNoNewLine.test(String.fromCharCode(ch)))) { - ++tokPos; - } 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 = 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; - } else { - return false; - } + 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 { - lineBreak.lastIndex = 0; - var match = lineBreak.exec(input.slice(tokPos, tokPos + 2)); - return match && match.index === 0; + 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); } - function preprocessSkipToElseOrEndif(test, skipElse) { - if (test) { - var ifLevel = 0; - while (ifLevel > 0 || (preTokType != _preEndif && (preTokType != _preElse || skipElse))) { - switch (preTokType) { - case _preIf: - case _preIfdef: - case _preIfndef: - ifLevel++; - break; + // Escape characters in stringify string. - case _preEndif: - ifLevel--; - break; - - case _eof: - preNotSkipping = true; - raise(preTokStart, "Missing #endif"); - } - preprocessReadToken(); + function preprocessStringifyEscape(aString) { + for (var escaped = "", pos = 0, ch = aString.charAt(pos); ch !== ""; ch = aString.charAt(++pos)) { + switch (ch) { + case '"': escaped += '\\\\\\"'; break; + case "\n": escaped += "\\\\n"; break; + case "\r": escaped += "\\\\r"; break; + case "\t": escaped += "\\\\t"; break; + case "\b": escaped += "\\\\b"; break; + case "\v": escaped += "\\\\v"; break; + case "\u00A0": escaped += "\\\\u00A0"; break; + case "\u2028": escaped += "\\\\u2028"; break; + case "\u2029": escaped += "\\\\u2029"; break; + case "\\": escaped += "\\\\"; break; + default: escaped += ch; break; } - preNotSkipping = true; - if (preTokType === _preEndif) - preIfLevel--; } + return escaped; } - function preprocessReadToken() { + // Skip whitespaces sometimes without line breaks + // Returns true if it stops at a line break. + + function preprocessSkipSpace(skipComments, skipEOL) { + onlySkipSpace(!skipEOL); + lineBreak.lastIndex = 0; + var match = lineBreak.exec(input.slice(tokPos, tokPos + 2)); + return match && match.index === 0; + } + + 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) { preTokStart = tokPos; preTokInput = input; - if (tokPos >= inputLen) return _eof; + preTokParameterScope = preprocessParameterScope; + if (tokPos >= inputLen) return preprocessFinishToken(_eof); var code = input.charCodeAt(tokPos); - if (preprocessMacroParamterListMode && code !== 41 && code !== 44) { // ')', ',' + if (!preprocessToken && !preNotSkipping && code !== 35) { // '#' + // If we are skipping take the whole line if the token does not start with '#' (preprocess tokens) + preprocesSkipRestOfLine(); + return preprocessFinishToken(_preprocessSkipLine, input.slice(preTokStart, tokPos++)); + } 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))) { // ')', ',' @@ -1221,47 +1604,76 @@ var preIfLevel = 0; 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(); - if (getTokenFromCode(code, preprocessFinishToken, true) === false) { + 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(); + if (ch === "\\" || nonASCIIidentifierStart.test(ch)) return preprocessReadWord(processMacros); raise(tokPos, "Unexpected character '" + ch + "'"); } } - function preprocessReadWord() { + function preprocessReadWord(processMacros) { var word = readWord1(); - preprocessFinishToken(isKeywordPreprocess(word) ? keywordTypesPreprocess[word] : _name, word); + var type = _name; + if (processMacros && options.preprocess) { + var readMacroWordReturn = readMacroWord(word, preprocessNext); + if (readMacroWordReturn === true) + return true; + } + + if (!containsEsc && isKeywordPreprocessor(word)) type = keywordTypesPreprocessor[word]; + preprocessFinishToken(type, word, readMacroWordReturn); // If readMacroWord returns anything except 'true' it is the real tokEndPos } - function preprocessFinishToken(type, val) { + function preprocessFinishToken(type, val, overrideTokEnd, skipEOL) { + preTokType = type; + preTokVal = val; + preTokEnd = overrideTokEnd || tokPos; + //tokRegexpAllowed = type.beforeExpr; + preprocessSkipSpace(false, skipEOL); // Dont skip comments + } + +// FIXME: Find out if this is really used? + function preprocessFinishTokenSkipComments(type, val) { preTokType = type; preTokVal = val; preTokEnd = tokPos; - preprocessSkipSpace(); + preprocessSkipSpace(true); // 'true' for skip comments } // Continue to the next token. - function preprocessNext() { - preLastStart = tokStart; - preLastEnd = tokEnd; - //lastEndLoc = tokEndLoc; - return preprocessReadToken(); + function preprocessNext(stealth, onlyTransformArguments, forceRegexp, processMacros) { + if (!stealth) { + preLastStart = tokStart; + preLastEnd = tokEnd; + } + return preprocessReadToken(false, false, processMacros); } // Predicate that tests whether the next token is of the given // type, and if yes, consumes it as a side effect. - function preprocessEat(type) { + function preprocessEat(type, processMacros) { if (preTokType === type) { - preprocessNext(); + preprocessNext(false, false, null, processMacros); return true; } } @@ -1269,35 +1681,40 @@ var preIfLevel = 0; // Expect a token of a given type. If found, consume it, otherwise, // raise with errorMessage or an unexpected token error. - function preprocessExpect(type, errorMessage) { - if (preTokType === type) preprocessReadToken(); + function preprocessExpect(type, errorMessage, processMacros) { + if (preTokType === type) preprocessReadToken(processMacros); else raise(preTokStart, errorMessage || "Unexpected token"); } - function preprocessGetIdent() { - var ident = preTokType === _name ? preTokVal : ((!options.forbidReserved || preTokType.okAsIdent) && preTokType.keyword) || raise(preTokStart, "Expected Macro identifier"); - preprocessNext(); + 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() { + function preprocessParseIdent(processMacros) { var node = startNode(); - node.name = preprocessGetIdent(); + 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() { - return preprocessParseExprOps(); + function preprocessParseExpression(processMacros) { + return preprocessParseExprOps(processMacros); } // Start the precedence parser. - function preprocessParseExprOps() { - return preprocessParseExprOp(preprocessParseMaybeUnary(), -1); + function preprocessParseExprOps(processMacros) { + return preprocessParseExprOp(preprocessParseMaybeUnary(processMacros), -1, processMacros); } // Parse binary operators with the operator precedence parsing @@ -1306,7 +1723,7 @@ var preIfLevel = 0; // 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) { + function preprocessParseExprOp(left, minPrec, processMacros) { var prec = preTokType.binop; if (prec) { if (!preTokType.preprocess) raise(preTokStart, "Unsupported macro operator"); @@ -1314,10 +1731,10 @@ var preIfLevel = 0; var node = startNodeFrom(left); node.left = left; node.operator = preTokVal; - preprocessNext(); - node.right = preprocessParseExprOp(preprocessParseMaybeUnary(), prec); - var node = preprocessFinishNode(node, /*/&&|\|\|/.test(node.operator) ? "LogicalExpression" : */"BinaryExpression"); - return preprocessParseExprOp(node, minPrec); + 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; @@ -1325,43 +1742,43 @@ var preIfLevel = 0; // Parse an unary expression if possible - function preprocessParseMaybeUnary() { + function preprocessParseMaybeUnary(processMacros) { if (preTokType.preprocess && preTokType.prefix) { var node = startNode(); - node.operator = tokVal; + node.operator = preTokVal; node.prefix = true; - preprocessNext(); - node.argument = preprocessParseMaybeUnary(); + preprocessNext(false, false, null, processMacros); + node.argument = preprocessParseMaybeUnary(processMacros); return preprocessFinishNode(node, "UnaryExpression"); } - return preprocessParseExprAtom(); + 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() { + function preprocessParseExprAtom(processMacros) { switch (preTokType) { case _name: - return preprocessParseIdent(); + return preprocessParseIdent(processMacros); case _num: case _string: - return preprocessParseStringNumLiteral(); + return preprocessParseStringNumLiteral(processMacros); case _parenL: var tokStart1 = preTokStart; - preprocessNext(); - var val = preprocessParseExpression(); + preprocessNext(false, false, null, processMacros); + var val = preprocessParseExpression(processMacros); val.start = tokStart1; val.end = preTokEnd; - preprocessExpect(_parenR, "Expected closing ')' in macro expression"); + preprocessExpect(_parenR, "Expected closing ')' in macro expression", processMacros); return val; case _preDefined: var node = startNode(); - preprocessNext(); - node.id = preprocessParseIdent(); + preprocessNext(false, false, null, processMacros); + node.object = preprocessParseDefinedExpression(processMacros); return preprocessFinishNode(node, "DefinedExpression"); default: @@ -1369,11 +1786,36 @@ var preIfLevel = 0; } } - function preprocessParseStringNumLiteral() { + // 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(); + preprocessNext(false, false, null, processMacros); return preprocessFinishNode(node, "Literal"); } @@ -1383,20 +1825,26 @@ var preIfLevel = 0; return node; } - function readToken(forceRegexp) { - tokStart = tokPos; - tokInput = input; - if (options.locations) tokStartLoc = new line_loc_t; + 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); + 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(); + if (isIdentifierStart(code) || code === 92 /* '\' */) return readWord(null, onlyTransformMacroArguments, forceRegexp); var tok = getTokenFromCode(code, finishToken); @@ -1404,16 +1852,16 @@ var preIfLevel = 0; // 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(); + if (ch === "\\" || nonASCIIidentifierStart.test(ch)) return readWord(null, onlyTransformMacroArguments, forceRegexp); raise(tokPos, "Unexpected character '" + ch + "'"); } return tok; } - function finishOp(type, size, finishToken) { + function finishOp(type, size, finisher) { var str = input.slice(tokPos, tokPos + size); tokPos += size; - finishToken(type, str); + finisher(type, str); } // Parse a regular expression. Some context-awareness is necessary, @@ -1463,17 +1911,17 @@ var preIfLevel = 0; return total; } - function readHexNumber(finishToken) { + 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 finishToken(_num, val); + return finisher(_num, val); } // Read an integer, octal integer, or floating-point number. - function readNumber(startsWithDot, finishToken) { + 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) { @@ -1485,7 +1933,7 @@ var preIfLevel = 0; if (next === 69 || next === 101) { // 'eE' next = input.charCodeAt(++tokPos); if (next === 43 || next === 45) ++tokPos; // '+-' - if (readInt(10) === null) raise(start, "Invalid number") + if (readInt(10) === null) raise(start, "Invalid number"); isFloat = true; } if (isIdentifierStart(input.charCodeAt(tokPos))) raise(tokPos, "Identifier directly after number"); @@ -1495,22 +1943,20 @@ var preIfLevel = 0; 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 finishToken(_num, val); + return finisher(_num, val); } // Read a string value, interpreting backslash-escapes. - var rs_str = []; - - function readString(quote, finishToken) { + function readString(quote, finisher) { tokPos++; - rs_str.length = 0; + var out = ""; for (;;) { if (tokPos >= inputLen) raise(tokStart, "Unterminated string constant"); var ch = input.charCodeAt(tokPos); if (ch === quote) { ++tokPos; - return finishToken(_string, String.fromCharCode.apply(null, rs_str)); + return finisher(_string, out); } if (ch === 92) { // '\' ch = input.charCodeAt(++tokPos); @@ -1521,30 +1967,30 @@ var preIfLevel = 0; ++tokPos; if (octal) { if (strict) raise(tokPos - 2, "Octal literal in strict mode"); - rs_str.push(parseInt(octal, 8)); + out += String.fromCharCode(parseInt(octal, 8)); tokPos += octal.length - 1; } else { switch (ch) { - case 110: rs_str.push(10); break; // 'n' -> '\n' - case 114: rs_str.push(13); break; // 'r' -> '\r' - case 120: rs_str.push(readHexChar(2)); break; // 'x' - case 117: rs_str.push(readHexChar(4)); break; // 'u' - case 85: rs_str.push(readHexChar(8)); break; // 'U' - case 116: rs_str.push(9); break; // 't' -> '\t' - case 98: rs_str.push(8); break; // 'b' -> '\b' - case 118: rs_str.push(11); break; // 'v' -> '\u000b' - case 102: rs_str.push(12); break; // 'f' -> '\f' - case 48: rs_str.push(0); break; // 0 -> '\0' + 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: rs_str.push(ch); break; + default: out += String.fromCharCode(ch); break; } } } else { - if (ch === 13 || ch === 10 || ch === 8232 || ch === 8329) raise(tokStart, "Unterminated string constant"); - rs_str.push(ch); // '\' + if (ch === 13 || ch === 10 || ch === 8232 || ch === 8233) raise(tokStart, "Unterminated string constant"); + out += String.fromCharCode(ch); // '\' ++tokPos; } } @@ -1602,63 +2048,13 @@ var preIfLevel = 0; // words when necessary. Argument preReadWord is used to concatenate // The word is then passed in from caller. - function readWord(preReadWord) { + function readWord(preReadWord, onlyTransformMacroArguments, forceRegexp) { var word = preReadWord || readWord1(); var type = _name; - var reservedError; if (options.preprocess) { - var macro; - var i = preprocessStack.length; - if (i > 0) { - var lastItem = preprocessStack[i - 1]; - // If the current macro has parameters check if this word is one of them and should be translated - if (lastItem.parameterDict && lastItem.macro.isParameterFunction()(word)) { - macro = lastItem.parameterDict[word]; - } - } - // Does the word match agains any of the know macro names - if (!macro && options.preprocessIsMacro(word)) - macro = options.preprocessGetMacro(word); - if (macro) { - var macroStart = tokStart; - var parameters; - var hasParameters = macro.parameters; - var nextIsParenL; - if (hasParameters) - nextIsParenL = tokPos < inputLen && input.charCodeAt(tokPos) === 40; // '(' - if (!hasParameters || nextIsParenL) { - // Now we know that we have a matching macro. Get parameters if needed - var macroString = macro.macro; - var lastTokPos = tokPos; - if (nextIsParenL) { - var first = true; - var noParams = 0; - parameters = Object.create(null); - preprocessReadToken(); - preprocessMacroParamterListMode = true; - preprocessExpect(_parenL); - lastTokPos = tokPos; - while (!preprocessEat(_parenR)) { - if (!first) preprocessExpect(_comma, "Expected ',' between macro parameters"); else first = false; - var ident = hasParameters[noParams++]; - var val = preTokVal; - preprocessExpect(_preprocessParamItem); - parameters[ident] = new Macro(ident, val); - lastTokPos = tokPos; - } - preprocessMacroParamterListMode = false; - } - // If the macro defines anything add it to the preprocess input stack - if (macroString) { - preprocessStack.push({macro: macro, parameterDict: parameters, start: macroStart, end:lastTokPos, input: input, inputLen: inputLen, lastStart: tokStart, lastEnd: lastTokPos}); - input = macroString; - inputLen = macroString.length; - tokPos = 0; - } - // Now read the next token - return next(); - } - } + var readMacroWordReturn = readMacroWord(word, next, onlyTransformMacroArguments, forceRegexp); + if (readMacroWordReturn === true) + return true; } if (!containsEsc) { @@ -1669,18 +2065,273 @@ var preIfLevel = 0; strict && isStrictReservedWord(word)) raise(tokStart, "The keyword '" + word + "' is reserved"); } - return finishToken(type, word); + return finishToken(type, word, readMacroWordReturn); // If readMacroWord returns anything except 'true' it is the real tokEndPos } - function Macro(ident, macro, parameters) { + // 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)) { // 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 macroStart = tokStart; + 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) && 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 + var macroString = macro.macro; + //var lastTokPos = tokPos; + 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.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) { + preprocessStackLastItem = {macro: macro, macroOffset: macroOffset, parameterDict: parameters, /*start: macroStart,*/ end:end, inputLen: inputLen, tokStart: tokStart, onlyTransformArgumentsForLastToken: preprocessOnlyTransformArgumentsForLastToken, currentLine: tokCurLine, currentLineStart: tokLineStart/*, lastStart: lastStart, lastEnd: lastEnd*/}; + if (parameterScope) preprocessStackLastItem.parameterScope = parameterScope; + preprocessStackLastItem.input = input; + preprocessStack.push(preprocessStackLastItem); + preprocessOnlyTransformArgumentsForLastToken = onlyTransformArguments; + input = macroString; + inputLen = macroString.length; + tokPosMacroOffset = macro.start; + tokPos = 0; + tokCurLine = 0; + tokLineStart = 0; + } else if (preConcatenating) { + // If we are concatenating or stringifying and the macro is empty just make an empty string. + finishToken(_name, ""); + return true; + } + // Now read the next token + onlySkipSpace(); + nextFinisher(true, onlyTransformArguments, forceRegexp, true); // Stealth and Preprocess macros + return true; + } + + // 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) { this.identifier = ident; - if (macro) this.macro = macro; + 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; } Macro.prototype.isParameterFunction = function() { - var y = (this.parameters || []).join(" "); - return this.isParameterFunctionVar || (this.isParameterFunctionVar = makePredicate(y)); + return this.isParameterFunctionVar || (this.isParameterFunctionVar = makePredicate((this.parameters || []).join(" "))); } // ## Parser @@ -1706,13 +2357,18 @@ var preIfLevel = 0; // ### 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() { - lastStart = tokStart; - lastEnd = tokEnd; - lastEndLoc = tokEndLoc; + function next(stealth, onlyTransformArguments, forceRegexp) { + if (!stealth) { + lastStart = tokStart; + lastEnd = tokEnd; + lastEndLoc = tokEndLoc; + lastTokMacroOffset = tokMacroOffset; + } nodeMessageSendObjectExpression = null; - return readToken(); + readToken(forceRegexp, onlyTransformArguments, stealth); } // Enter strict mode. Re-reads the next token to please pedantic @@ -1721,6 +2377,10 @@ var preIfLevel = 0; function setStrict(strct) { strict = strct; tokPos = lastEnd; + while (tokPos < tokLineStart) { + tokLineStart = input.lastIndexOf("\n", tokLineStart - 2) + 1; + --tokCurLine; + } skipSpace(); readToken(); } @@ -1730,7 +2390,7 @@ var preIfLevel = 0; function node_t() { this.type = null; - this.start = tokStart; + this.start = tokStart + tokMacroOffset; this.end = null; } @@ -1795,12 +2455,13 @@ var preIfLevel = 0; var lastFinishedNode; function finishNode(node, type) { + var nodeEnd = lastEnd + lastTokMacroOffset; node.type = type; - node.end = lastEnd; + node.end = nodeEnd; if (options.trackComments) { if (lastTokCommentsAfter) { node.commentsAfter = lastTokCommentsAfter; - tokCommentsAfter = null; + lastTokCommentsAfter = null; } else if (lastFinishedNode && lastFinishedNode.end === lastEnd && lastFinishedNode.commentsAfter) { node.commentsAfter = lastFinishedNode.commentsAfter; @@ -1823,7 +2484,7 @@ var preIfLevel = 0; if (options.locations) node.loc.end = lastEndLoc; if (options.ranges) - node.range[1] = lastEnd; + node.range[1] = nodeEnd; return node; } @@ -1848,7 +2509,7 @@ var preIfLevel = 0; function canInsertSemicolon() { return !options.strictSemicolons && - (tokType === _eof || tokType === _braceR || newline.test(tokInput.slice(lastEnd, tokStart)) || + (tokType === _eof || tokType === _braceR || newline.test(tokFirstInput.slice(lastEnd, tokFirstStart)) || (nodeMessageSendObjectExpression && options.objj)); } @@ -1918,7 +2579,7 @@ var preIfLevel = 0; // does not help. function parseStatement() { - if (tokType === _slash) + if (tokType === _slash || tokType === _assign && tokVal == "/=") readToken(true); var starttype = tokType, node = startNode(); @@ -2065,8 +2726,8 @@ var preIfLevel = 0; case _try: next(); node.block = parseBlock(); - node.handlers = []; - while (tokType === _catch) { + node.handler = null; + if (tokType === _catch) { var clause = startNode(); next(); expect(_parenL, "Expected '(' after 'catch'"); @@ -2076,10 +2737,11 @@ var preIfLevel = 0; expect(_parenR, "Expected closing ')' after catch"); clause.guard = null; clause.body = parseBlock(); - node.handlers.push(finishNode(clause, "CatchClause")); + node.handler = finishNode(clause, "CatchClause"); } + node.guardedHandlers = empty; node.finalizer = eat(_finally) ? parseBlock() : null; - if (!node.handlers.length && !node.finalizer) + if (!node.handler && !node.finalizer) raise(node.start, "Missing catch or finally clause"); return finishNode(node, "TryStatement"); @@ -2111,7 +2773,7 @@ var preIfLevel = 0; next(); return finishNode(node, "EmptyStatement"); - // This is a Objective-J statement + // Objective-J case _interface: if (options.objj) { next(); @@ -2152,7 +2814,7 @@ var preIfLevel = 0; } break; - // This is a Objective-J statement + // Objective-J case _implementation: if (options.objj) { next(); @@ -2176,19 +2838,6 @@ var preIfLevel = 0; } next(); } - 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 (;;) { @@ -2206,9 +2855,9 @@ var preIfLevel = 0; } break; - // This is a Objective-J statement + // Objective-J case _protocol: - // If next token is a left parenthesis it is a ProtocolLiternal expression so bail out + // 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); @@ -2240,7 +2889,7 @@ var preIfLevel = 0; } break; - // This is a Objective-J statement + // Objective-J case _import: if (options.objj) { next(); @@ -2256,7 +2905,7 @@ var preIfLevel = 0; } break; - // This is a Objective-J statement + // Objective-J case _preprocess: if (options.objj) { next(); @@ -2264,7 +2913,7 @@ var preIfLevel = 0; } break; - // This is a Objective-J statement + // Objective-J case _class: if (options.objj) { next(); @@ -2273,7 +2922,7 @@ var preIfLevel = 0; } break; - // This is a Objective-J statement + // Objective-J case _global: if (options.objj) { next(); @@ -2290,7 +2939,6 @@ var preIfLevel = 0; return finishNode(node, "TypeDefStatement"); } break; - } // The indentation is one step to the right here to make sure it @@ -2331,7 +2979,6 @@ var preIfLevel = 0; if (outlet) decl.outlet = outlet; decl.ivartype = type; - // print("keyword: " + type.name + " is class " + type.typeisclass) decl.id = parseIdent(); if (strict && isStrictBadIdWord(decl.id.name)) raise(decl.id.start, "Binding " + decl.id.name + " in strict mode"); @@ -2470,11 +3117,11 @@ var preIfLevel = 0; while (!eat(_braceR)) { var stmt = parseStatement(); node.body.push(stmt); - if (first && isUseStrict(stmt)) { + if (first && allowStrict && isUseStrict(stmt)) { oldStrict = strict; setStrict(strict = true); } - first = false + first = false; } if (strict && !oldStrict) setStrict(false); return finishNode(node, "BlockStatement"); @@ -2582,7 +3229,7 @@ var preIfLevel = 0; // Start the precedence parser. function parseExprOps(noIn) { - return parseExprOp(parseMaybeUnary(noIn), -1, noIn); + return parseExprOp(parseMaybeUnary(), -1, noIn); } // Parse binary operators with the operator precedence parsing @@ -2599,7 +3246,7 @@ var preIfLevel = 0; node.left = left; node.operator = tokVal; next(); - node.right = parseExprOp(parseMaybeUnary(noIn), prec, noIn); + node.right = parseExprOp(parseMaybeUnary(), prec, noIn); var node = finishNode(node, /&&|\|\|/.test(node.operator) ? "LogicalExpression" : "BinaryExpression"); return parseExprOp(node, minPrec, noIn); } @@ -2609,13 +3256,14 @@ var preIfLevel = 0; // Parse unary operators, both prefix and postfix. - function parseMaybeUnary(noIn) { + function parseMaybeUnary() { if (tokType.prefix) { var node = startNode(), update = tokType.isUpdate; node.operator = tokVal; node.prefix = true; + tokRegexpAllowed = true; next(); - node.argument = parseMaybeUnary(noIn); + node.argument = parseMaybeUnary(); if (update) checkLVal(node.argument); else if (strict && node.operator === "delete" && node.argument.type === "Identifier") @@ -2697,17 +3345,17 @@ var preIfLevel = 0; return finishNode(node, "Literal"); case _parenL: - var tokStartLoc1 = tokStartLoc, tokStart1 = tokStart; + var tokStartLoc1 = tokStartLoc, macroOffset = tokMacroOffset, tokStart1 = tokStart + macroOffset; next(); var val = parseExpression(); val.start = tokStart1; - val.end = tokEnd; + val.end = tokEnd + macroOffset; if (options.locations) { val.loc.start = tokStartLoc1; val.loc.end = tokEndLoc; } if (options.ranges) - val.range = [tokStart1, tokEnd]; + val.range = [tokStart1, tokEnd + lastTokMacroOffset]; expect(_parenR, "Expected closing ')' in expression"); return val; @@ -2789,7 +3437,7 @@ var preIfLevel = 0; return finishNode(node, "Dereference"); default: - if(tokType.okAsIdent) + if (tokType.okAsIdent) return parseIdent(); unexpected(); @@ -2862,7 +3510,7 @@ var preIfLevel = 0; node.callee = parseSubscripts(parseExprAtom(false), true); if (eat(_parenL)) node.arguments = parseExprList(_parenR, tokType === _parenR ? null : parseExpression(true), false); - else node.arguments = []; + else node.arguments = empty; return finishNode(node, "NewExpression"); } @@ -3009,6 +3657,7 @@ var preIfLevel = 0; 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"); } @@ -3029,11 +3678,12 @@ var preIfLevel = 0; // '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(); + 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; @@ -3041,59 +3691,65 @@ var preIfLevel = 0; // Do nothing more if it is 'void' if (!eat(_void)) { if (eat(_id)) { - // Is it 'id' followed by a '<' parse protocols. Do nothing more if it is only 'id' - 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(); - } + 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; + { + 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; - next(); - } + 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(); + } } } } } - return finishNode(node, "ObjectiveJType"); + 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"); } -})(typeof exports === "undefined" ? (self.acorn = {}) : exports.acorn); +})(exports.acorn, exports.acorn.walk); diff --git a/Objective-J/acornwalk.js b/Objective-J/acornwalk.js index 8d5666529..456fec8a4 100644 --- a/Objective-J/acornwalk.js +++ b/Objective-J/acornwalk.js @@ -101,8 +101,7 @@ if (!exports.acorn) { }; exports.TryStatement = function(node, st, c) { c(node.block, st, "Statement"); - for (var i = 0; i < node.handlers.length; ++i) - c(node.handlers[i].body, st, "ScopeBody"); + if (node.handler) c(node.handler.body, st, "ScopeBody"); if (node.finalizer) c(node.finalizer, st, "Statement"); }; exports.WhileStatement = function(node, st, c) { @@ -267,10 +266,10 @@ if (!exports.acorn) { }, TryStatement: function(node, scope, c) { c(node.block, scope, "Statement"); - for (var i = 0; i < node.handlers.length; ++i) { - var handler = node.handlers[i], inner = makeScope(scope); - inner.vars[handler.param.name] = {type: "catch clause", node: handler.param}; - c(handler.body, inner, "ScopeBody"); + if (node.handler) { + var inner = makeScope(scope); + inner.vars[node.handler.param.name] = {type: "catch clause", node: node.handler.param}; + c(node.handler.body, inner, "ScopeBody"); } if (node.finalizer) c(node.finalizer, scope, "Statement"); }, diff --git a/Tests/Objective-J/Preprocessor/OutputTests/Messages/super-selector-inlined.js b/Tests/Objective-J/Preprocessor/OutputTests/Messages/super-selector-inlined.js new file mode 100644 index 000000000..502214f75 --- /dev/null +++ b/Tests/Objective-J/Preprocessor/OutputTests/Messages/super-selector-inlined.js @@ -0,0 +1,7 @@ +var the_class=objj_allocateClassPair(Nil,"TheSuperClass"),meta_class=the_class.isa; +objj_registerClassPair(the_class); +var the_class=objj_allocateClassPair(Nil,"TheClass"),meta_class=the_class.isa; +objj_registerClassPair(the_class); +class_addMethods(the_class,[new objj_method(sel_getUid("xxxx:"),function $TheClass__xxxx_(_1,_2,_3){ +(objj_getClass("TheClass").super_class.method_dtable["xxxx:"]||_objj_forward)(_1,"xxxx:",_3); +},["void","id"])]); \ No newline at end of file diff --git a/Tests/Objective-J/Preprocessor/OutputTests/Messages/super-selector.j b/Tests/Objective-J/Preprocessor/OutputTests/Messages/super-selector.j new file mode 100644 index 000000000..3e7035be5 --- /dev/null +++ b/Tests/Objective-J/Preprocessor/OutputTests/Messages/super-selector.j @@ -0,0 +1,11 @@ +@implementation TheSuperClass +@end + +@implementation TheClass + +- (void)xxxx:(id)argument +{ + [super xxxx:argument]; +} + +@end \ No newline at end of file diff --git a/Tests/Objective-J/Preprocessor/OutputTests/Messages/super-selector.js b/Tests/Objective-J/Preprocessor/OutputTests/Messages/super-selector.js new file mode 100644 index 000000000..e2c2376b6 --- /dev/null +++ b/Tests/Objective-J/Preprocessor/OutputTests/Messages/super-selector.js @@ -0,0 +1,7 @@ +var the_class=objj_allocateClassPair(Nil,"TheSuperClass"),meta_class=the_class.isa; +objj_registerClassPair(the_class); +var the_class=objj_allocateClassPair(Nil,"TheClass"),meta_class=the_class.isa; +objj_registerClassPair(the_class); +class_addMethods(the_class,[new objj_method(sel_getUid("xxxx:"),function $TheClass__xxxx_(_1,_2,_3){ +objj_msgSendSuper1({receiver:_1,super_class:objj_getClass("TheClass").super_class},"xxxx:",_3); +},["void","id"])]); diff --git a/Tests/Objective-J/Preprocessor/OutputTests/OutputTest.j b/Tests/Objective-J/Preprocessor/OutputTests/OutputTest.j index 34150d7f2..f622dd8d8 100644 --- a/Tests/Objective-J/Preprocessor/OutputTests/OutputTest.j +++ b/Tests/Objective-J/Preprocessor/OutputTests/OutputTest.j @@ -16,6 +16,7 @@ var FILENAMES = [ "Messages/colon-selector", "Messages/self-as-receiver", "Messages/complex-receiver", + "Messages/super-selector", "Misc/parenthesis-return", "Misc/preprocess-if-directives", @@ -53,12 +54,12 @@ var FILENAMES = [ correctInlined = FILE.exists(p) ? FILE.read(p) : correct; // Get inlined version if it exists. Otherwise use the regular one. [self assertNoThrow:function() { - preprocessed = ObjectiveJ.ObjJAcornCompiler.compileToExecutable(unpreprocessed, nil, ObjectiveJ.ObjJAcornCompiler.Flags.IncludeDebugSymbols | ObjectiveJ.ObjJAcornCompiler.Flags.IncludeTypeSignatures).code(); + preprocessed = ObjectiveJ.ObjJCompiler.compileToExecutable(unpreprocessed, nil, {includeMethodFunctionNames: true, includeMethodArgumentTypeSignatures: true, includeIvarTypeSignatures: true, inlineMsgSendFunctions: false, transformNamedFunctionDeclarationToAssignment: true}).code(); preprocessed = compressor.compress(preprocessed, { charset : "UTF-8", useServer : true }); correct = compressor.compress(correct, { charset : "UTF-8", useServer : true }); // Get an Inlined version - preprocessedInlined = ObjectiveJ.ObjJAcornCompiler.compileToExecutable(unpreprocessed, nil, ObjectiveJ.ObjJAcornCompiler.Flags.IncludeDebugSymbols | ObjectiveJ.ObjJAcornCompiler.Flags.InlineMsgSend | ObjectiveJ.ObjJAcornCompiler.Flags.IncludeTypeSignatures).code(); + preprocessedInlined = ObjectiveJ.ObjJCompiler.compileToExecutable(unpreprocessed, nil, {includeMethodFunctionNames: true, includeMethodArgumentTypeSignatures: true, includeIvarTypeSignatures: true, inlineMsgSendFunctions: true, transformNamedFunctionDeclarationToAssignment:true}).code(); preprocessedInlined = compressor.compress(preprocessedInlined, { charset : "UTF-8", useServer : true }); correctInlined = compressor.compress(correctInlined, { charset : "UTF-8", useServer : true }); }]; diff --git a/Tests/Tools/ToolsTest.j b/Tests/Tools/ToolsTest.j index 67bb7e06c..ab0df8e1f 100644 --- a/Tests/Tools/ToolsTest.j +++ b/Tests/Tools/ToolsTest.j @@ -58,10 +58,11 @@ function cleanup() { \nERROR line 1 in file:" + rootDirectory + "/objjErrorTestFile.j: Can't find superclass CPObject\n" equals:p.stdout.read() message:"objj failed"]; var p = OS.popen(["objj", "-x", "objjWarningTestFile.j"]); + var pp = p.stdout.read(); [self assert:0 equals:p.wait() message:"objj failed"]; [self assert:"\nline1sourcePath" + rootDirectory + "/objjWarningTestFile.jmessage\n@import <Foundation/Foundation.j>@implementation AppController : CPObject{CPWindow theWindow;}@end\ -\n ^\ -\nWARNING line 1 in file:" + rootDirectory + "/objjWarningTestFile.j: Unknown type 'CPWindow' for ivar 'theWindow'\n" equals:p.stdout.read() message:"objj failed"]; +\n ^\ +\nWARNING line 1 in file:" + rootDirectory + "/objjWarningTestFile.j: Unknown type 'CPWindow' for ivar 'theWindow'\n" equals:pp message:"objj failed"]; status = OS.system(["objj", "-m", "ToolsTestApp/AppController.j", "ToolsTestApp/AppController.j"]); [self assert:0 equals:status message:"objj failed with several files"];