mirror of
https://github.com/cappuccino/cappuccino.git
synced 2026-08-25 21:17:03 +00:00
517 lines
15 KiB
JavaScript
517 lines
15 KiB
JavaScript
/*
|
|
* Executable.js
|
|
* Objective-J
|
|
*
|
|
* Created by Francisco Tolmasky.
|
|
* Copyright 2010, 280 North, Inc.
|
|
*
|
|
* 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
|
|
*/
|
|
|
|
|
|
var ExecutableUnloadedFileDependencies = 0,
|
|
ExecutableLoadingFileDependencies = 1,
|
|
ExecutableLoadedFileDependencies = 2,
|
|
AnonymousExecutableCount = 0;
|
|
|
|
function Executable(/*String*/ aCode, /*Array*/ fileDependencies, /*CFURL|String*/ aURL, /*Function*/ aFunction, /*ObjJCompiler*/aCompiler, /*Dictionary*/ aFilenameTranslateDictionary)
|
|
{
|
|
if (arguments.length === 0)
|
|
return this;
|
|
|
|
this._code = aCode;
|
|
this._function = aFunction || null;
|
|
this._URL = makeAbsoluteURL(aURL || new CFURL("(Anonymous" + (AnonymousExecutableCount++) + ")"));
|
|
|
|
this._compiler = aCompiler || null;
|
|
|
|
this._fileDependencies = fileDependencies;
|
|
this._filenameTranslateDictionary = aFilenameTranslateDictionary;
|
|
|
|
if (fileDependencies.length)
|
|
{
|
|
this._fileDependencyStatus = ExecutableUnloadedFileDependencies;
|
|
this._fileDependencyCallbacks = [];
|
|
}
|
|
else
|
|
this._fileDependencyStatus = ExecutableLoadedFileDependencies;
|
|
|
|
if (this._function)
|
|
return;
|
|
|
|
if (!aCompiler)
|
|
this.setCode(aCode);
|
|
}
|
|
|
|
exports.Executable = Executable;
|
|
|
|
Executable.prototype.path = function()
|
|
{
|
|
return this.URL().path();
|
|
};
|
|
|
|
Executable.prototype.URL = function()
|
|
{
|
|
return this._URL;
|
|
};
|
|
|
|
DISPLAY_NAME(Executable.prototype.URL);
|
|
|
|
Executable.prototype.functionParameters = function()
|
|
{
|
|
var functionParameters = ["global", "objj_executeFile", "objj_importFile"];
|
|
|
|
//exportedNames().concat("objj_executeFile", "objj_importFile");
|
|
|
|
#ifdef COMMONJS
|
|
functionParameters = functionParameters.concat("require", "exports", "module", "system", "print", "window");
|
|
#endif
|
|
|
|
return functionParameters;
|
|
};
|
|
|
|
DISPLAY_NAME(Executable.prototype.functionParameters);
|
|
|
|
Executable.prototype.functionArguments = function()
|
|
{
|
|
var functionArguments = [global, this.fileExecuter(), this.fileImporter()];
|
|
|
|
#ifdef COMMONJS
|
|
functionArguments = functionArguments.concat(Executable.commonJSArguments());
|
|
#endif
|
|
|
|
return functionArguments;
|
|
};
|
|
|
|
DISPLAY_NAME(Executable.prototype.functionArguments);
|
|
|
|
#ifdef COMMONJS
|
|
Executable.setCommonJSParameters = function()
|
|
{
|
|
this._commonJSParameters = Array.prototype.slice.call(arguments);
|
|
};
|
|
|
|
Executable.commonJSParameters = function()
|
|
{
|
|
return this._commonJSParameters || [];
|
|
};
|
|
|
|
Executable.setCommonJSArguments = function()
|
|
{
|
|
this._commonJSArguments = Array.prototype.slice.call(arguments);
|
|
};
|
|
|
|
Executable.commonJSArguments = function()
|
|
{
|
|
return this._commonJSArguments || [];
|
|
};
|
|
|
|
Executable.setFilenameTranslateDictionary = function(dict)
|
|
{
|
|
this._filenameTranslateDictionary = dict;
|
|
};
|
|
|
|
Executable.filenameTranslateDictionary = function()
|
|
{
|
|
return this._filenameTranslateDictionary || {};
|
|
};
|
|
|
|
|
|
Executable.prototype.toMarkedString = function()
|
|
{
|
|
var markedString = "@STATIC;1.0;",
|
|
dependencies = this.fileDependencies(),
|
|
index = 0,
|
|
count = dependencies.length;
|
|
|
|
for (; index < count; ++index)
|
|
markedString += dependencies[index].toMarkedString();
|
|
|
|
var code = this.code();
|
|
|
|
return markedString + MARKER_TEXT + ";" + code.length + ";" + code;
|
|
};
|
|
#endif
|
|
|
|
Executable.prototype.execute = function()
|
|
{
|
|
#if EXECUTION_LOGGING
|
|
CPLog("EXECUTION: " + this.URL());
|
|
#endif
|
|
|
|
if (this._compiler)
|
|
{
|
|
var fileDependencies = this.fileDependencies(),
|
|
index = 0,
|
|
count = fileDependencies.length;
|
|
|
|
for (; index < count; ++index)
|
|
{
|
|
var fileDependency = fileDependencies[index],
|
|
isQuoted = fileDependency.isLocal(),
|
|
URL = fileDependency.URL();
|
|
|
|
this.fileExecuter()(URL, isQuoted);
|
|
}
|
|
|
|
this.setCode(this._compiler.compilePass2());
|
|
this._compiler = null;
|
|
}
|
|
|
|
var oldContextBundle = CONTEXT_BUNDLE;
|
|
|
|
// FIXME: Should we have stored this?
|
|
CONTEXT_BUNDLE = CFBundle.bundleContainingURL(this.URL());
|
|
|
|
var result = this._function.apply(global, this.functionArguments());
|
|
|
|
CONTEXT_BUNDLE = oldContextBundle;
|
|
|
|
return result;
|
|
};
|
|
|
|
DISPLAY_NAME(Executable.prototype.execute);
|
|
|
|
Executable.prototype.code = function()
|
|
{
|
|
return this._code;
|
|
};
|
|
|
|
DISPLAY_NAME(Executable.prototype.code);
|
|
|
|
Executable.prototype.setCode = function(code)
|
|
{
|
|
this._code = code;
|
|
|
|
var parameters = this.functionParameters().join(",");
|
|
|
|
#if COMMONJS
|
|
if (typeof system !== "undefined" && system.engine === "rhino")
|
|
{
|
|
code = "function(" + parameters + "){" + code + "/**/\n}";
|
|
this._function = Packages.org.mozilla.javascript.Context.getCurrentContext().compileFunction(window, code, this.URL().absoluteString(), 0, NULL);
|
|
}
|
|
else
|
|
{
|
|
#endif
|
|
#if DEBUG
|
|
// "//@ sourceURL=" at the end lets us name our eval'd files for debuggers, etc.
|
|
// * WebKit: http://pmuellr.blogspot.com/2009/06/debugger-friendly.html
|
|
// * Firebug: http://blog.getfirebug.com/2009/08/11/give-your-eval-a-name-with-sourceurl/
|
|
//if (YES) {
|
|
var absoluteString = this.URL().absoluteString();
|
|
|
|
code += "/**/\n//@ sourceURL=" + absoluteString;
|
|
//} else {
|
|
// // Firebug only does it for "eval()", not "new Function()". Ugh. Slower.
|
|
// var functionText = "(function(){"+GET_CODE(aFragment)+"/**/\n})\n//@ sourceURL="+GET_FILE(aFragment).path;
|
|
// compiled = eval(functionText);
|
|
//}
|
|
#endif
|
|
this._function = new Function(parameters, code);
|
|
#if DEBUG
|
|
this._function.displayName = absoluteString;
|
|
#endif
|
|
#if COMMONJS
|
|
}
|
|
#endif
|
|
}
|
|
|
|
DISPLAY_NAME(Executable.prototype.setCode);
|
|
|
|
Executable.prototype.fileDependencies = function()
|
|
{
|
|
return this._fileDependencies;
|
|
}
|
|
|
|
DISPLAY_NAME(Executable.prototype.fileDependencies);
|
|
|
|
Executable.prototype.hasLoadedFileDependencies = function()
|
|
{
|
|
return this._fileDependencyStatus === ExecutableLoadedFileDependencies;
|
|
}
|
|
|
|
DISPLAY_NAME(Executable.prototype.hasLoadedFileDependencies);
|
|
|
|
var fileDependencyLoadCount = 0,
|
|
fileDependencyExecutables = [],
|
|
fileDependencyMarkers = { };
|
|
|
|
Executable.prototype.loadFileDependencies = function(aCallback)
|
|
{
|
|
var status = this._fileDependencyStatus;
|
|
|
|
if (aCallback)
|
|
{
|
|
if (status === ExecutableLoadedFileDependencies)
|
|
return aCallback();
|
|
|
|
this._fileDependencyCallbacks.push(aCallback);
|
|
}
|
|
|
|
if (status === ExecutableUnloadedFileDependencies)
|
|
{
|
|
if (fileDependencyLoadCount)
|
|
throw "Can't load";
|
|
|
|
loadFileDependenciesForExecutable(this);
|
|
}
|
|
}
|
|
|
|
DISPLAY_NAME(Executable.prototype.loadFileDependencies);
|
|
|
|
function loadFileDependenciesForExecutable(/*Executable*/ anExecutable)
|
|
{
|
|
fileDependencyExecutables.push(anExecutable);
|
|
anExecutable._fileDependencyStatus = ExecutableLoadingFileDependencies;
|
|
|
|
var fileDependencies = anExecutable.fileDependencies(),
|
|
index = 0,
|
|
count = fileDependencies.length,
|
|
referenceURL = anExecutable.referenceURL(),
|
|
referenceURLString = referenceURL.absoluteString(),
|
|
fileExecutableSearcher = anExecutable.fileExecutableSearcher();
|
|
|
|
fileDependencyLoadCount += count;
|
|
|
|
for (; index < count; ++index)
|
|
{
|
|
var fileDependency = fileDependencies[index],
|
|
isQuoted = fileDependency.isLocal(),
|
|
URL = fileDependency.URL(),
|
|
marker = (isQuoted && (referenceURLString + " ") || "") + URL;
|
|
|
|
if (fileDependencyMarkers[marker])
|
|
{
|
|
if (--fileDependencyLoadCount === 0)
|
|
fileExecutableDependencyLoadFinished();
|
|
|
|
continue;
|
|
}
|
|
|
|
fileDependencyMarkers[marker] = YES;
|
|
fileExecutableSearcher(URL, isQuoted, fileExecutableSearchFinished);
|
|
}
|
|
}
|
|
|
|
function fileExecutableSearchFinished(/*FileExecutable*/ aFileExecutable)
|
|
{
|
|
--fileDependencyLoadCount;
|
|
|
|
if (aFileExecutable._fileDependencyStatus === ExecutableUnloadedFileDependencies)
|
|
loadFileDependenciesForExecutable(aFileExecutable);
|
|
|
|
else if (fileDependencyLoadCount === 0)
|
|
fileExecutableDependencyLoadFinished();
|
|
}
|
|
|
|
function fileExecutableDependencyLoadFinished()
|
|
{
|
|
var executables = fileDependencyExecutables,
|
|
index = 0,
|
|
count = executables.length;
|
|
|
|
fileDependencyExecutables = [];
|
|
|
|
for (; index < count; ++index)
|
|
executables[index]._fileDependencyStatus = ExecutableLoadedFileDependencies;
|
|
|
|
for (index = 0; index < count; ++index)
|
|
{
|
|
var executable = executables[index],
|
|
callbacks = executable._fileDependencyCallbacks,
|
|
callbackIndex = 0,
|
|
callbackCount = callbacks.length;
|
|
|
|
for (; callbackIndex < callbackCount; ++callbackIndex)
|
|
callbacks[callbackIndex]();
|
|
|
|
executable._fileDependencyCallbacks = [];
|
|
}
|
|
}
|
|
|
|
Executable.prototype.referenceURL = function()
|
|
{
|
|
if (this._referenceURL === undefined)
|
|
this._referenceURL = new CFURL(".", this.URL());
|
|
|
|
return this._referenceURL;
|
|
}
|
|
|
|
DISPLAY_NAME(Executable.prototype.referenceURL);
|
|
|
|
Executable.prototype.fileImporter = function()
|
|
{
|
|
return Executable.fileImporterForURL(this.referenceURL());
|
|
}
|
|
|
|
DISPLAY_NAME(Executable.prototype.fileImporter);
|
|
|
|
Executable.prototype.fileExecuter = function()
|
|
{
|
|
return Executable.fileExecuterForURL(this.referenceURL());
|
|
}
|
|
|
|
DISPLAY_NAME(Executable.prototype.fileExecuter);
|
|
|
|
Executable.prototype.fileExecutableSearcher = function()
|
|
{
|
|
return Executable.fileExecutableSearcherForURL(this.referenceURL());
|
|
}
|
|
|
|
DISPLAY_NAME(Executable.prototype.fileExecutableSearcher);
|
|
|
|
var cachedFileExecuters = { };
|
|
|
|
Executable.fileExecuterForURL = function(/*CFURL|String*/ aURL)
|
|
{
|
|
var referenceURL = makeAbsoluteURL(aURL),
|
|
referenceURLString = referenceURL.absoluteString(),
|
|
cachedFileExecuter = cachedFileExecuters[referenceURLString];
|
|
|
|
if (!cachedFileExecuter)
|
|
{
|
|
cachedFileExecuter = function(/*CFURL*/ aURL, /*BOOL*/ isQuoted, /*BOOL*/ shouldForce)
|
|
{
|
|
Executable.fileExecutableSearcherForURL(referenceURL)(aURL, isQuoted,
|
|
function(/*FileExecutable*/ aFileExecutable)
|
|
{
|
|
if (!aFileExecutable.hasLoadedFileDependencies())
|
|
throw "No executable loaded for file at URL " + aURL;
|
|
|
|
aFileExecutable.execute(shouldForce);
|
|
});
|
|
}
|
|
|
|
cachedFileExecuters[referenceURLString] = cachedFileExecuter;
|
|
}
|
|
|
|
return cachedFileExecuter;
|
|
}
|
|
|
|
DISPLAY_NAME(Executable.fileExecuterForURL);
|
|
|
|
var cachedFileImporters = { };
|
|
|
|
Executable.fileImporterForURL = function(/*CFURL|String*/ aURL)
|
|
{
|
|
var referenceURL = makeAbsoluteURL(aURL),
|
|
referenceURLString = referenceURL.absoluteString(),
|
|
cachedFileImporter = cachedFileImporters[referenceURLString];
|
|
|
|
if (!cachedFileImporter)
|
|
{
|
|
cachedFileImporter = function(/*CFURL*/ aURL, /*BOOL*/ isQuoted, /*Function*/ aCallback)
|
|
{
|
|
// We make heavy use of URLs throughout this process, so cache them!
|
|
enableCFURLCaching();
|
|
|
|
Executable.fileExecutableSearcherForURL(referenceURL)(aURL, isQuoted,
|
|
function(/*FileExecutable*/ aFileExecutable)
|
|
{
|
|
aFileExecutable.loadFileDependencies(function()
|
|
{
|
|
aFileExecutable.execute();
|
|
|
|
// No more need to cache these.
|
|
disableCFURLCaching();
|
|
|
|
if (aCallback)
|
|
aCallback();
|
|
});
|
|
});
|
|
}
|
|
|
|
cachedFileImporters[referenceURLString] = cachedFileImporter;
|
|
}
|
|
|
|
return cachedFileImporter;
|
|
}
|
|
|
|
DISPLAY_NAME(Executable.fileImporterForURL);
|
|
|
|
var cachedFileExecutableSearchers = { },
|
|
cachedFileExecutableSearchResults = { };
|
|
|
|
function countProp(x) {
|
|
var count = 0;
|
|
for (var k in x) {
|
|
if (x.hasOwnProperty(k)) {
|
|
++count;
|
|
}
|
|
}
|
|
return count;
|
|
}
|
|
|
|
Executable.resetCachedFileExecutableSearchers = function()
|
|
{
|
|
cachedFileExecutableSearchers = { };
|
|
cachedFileExecutableSearchResults = { };
|
|
cachedFileImporters = { };
|
|
cachedFileExecuters = { };
|
|
fileDependencyMarkers = { };
|
|
}
|
|
|
|
Executable.fileExecutableSearcherForURL = function(/*CFURL*/ referenceURL)
|
|
{
|
|
var referenceURLString = referenceURL.absoluteString(),
|
|
cachedFileExecutableSearcher = cachedFileExecutableSearchers[referenceURLString],
|
|
aFilenameTranslateDictionary = Executable.filenameTranslateDictionary ? Executable.filenameTranslateDictionary() : null;
|
|
cachedSearchResults = { };
|
|
|
|
if (!cachedFileExecutableSearcher)
|
|
{
|
|
cachedFileExecutableSearcher = function(/*CFURL*/ aURL, /*BOOL*/ isQuoted, /*Function*/ success)
|
|
{
|
|
var cacheUID = (isQuoted && referenceURL || "") + aURL,
|
|
cachedResult = cachedFileExecutableSearchResults[cacheUID];
|
|
|
|
if (cachedResult)
|
|
return completed(cachedResult);
|
|
|
|
var isAbsoluteURL = (aURL instanceof CFURL) && aURL.scheme();
|
|
|
|
if (isQuoted || isAbsoluteURL)
|
|
{
|
|
if (!isAbsoluteURL)
|
|
aURL = new CFURL(aURL, referenceURL);
|
|
|
|
StaticResource.resolveResourceAtURL(aURL, NO, completed, aFilenameTranslateDictionary);
|
|
}
|
|
else
|
|
StaticResource.resolveResourceAtURLSearchingIncludeURLs(aURL, completed);
|
|
|
|
function completed(/*StaticResource*/ aStaticResource)
|
|
{
|
|
if (!aStaticResource)
|
|
{
|
|
var compilingFileUrl = ObjJAcornCompiler ? ObjJAcornCompiler.currentCompileFile : null;
|
|
throw new Error("Could not load file at " + aURL + (compilingFileUrl ? " when compiling " + compilingFileUrl : ""));
|
|
}
|
|
|
|
cachedFileExecutableSearchResults[cacheUID] = aStaticResource;
|
|
|
|
success(new FileExecutable(aStaticResource.URL(), aFilenameTranslateDictionary));
|
|
}
|
|
};
|
|
|
|
cachedFileExecutableSearchers[referenceURLString] = cachedFileExecutableSearcher;
|
|
}
|
|
|
|
return cachedFileExecutableSearcher;
|
|
}
|
|
|
|
DISPLAY_NAME(Executable.fileExecutableSearcherForURL);
|