cor-lang
Version:
The Language of the Web
426 lines (360 loc) • 12.1 kB
JavaScript
(function(cor){
var
parseJson = typeof JSON !== 'undefined' ? JSON.parse : function parseJson(str) {
if(! /^([\s\[\{]*(?:"(?:\\.|[^"])+"|-?\d[\d\.]*(?:[Ee][+-]?\d+)?|null|true|false|)[\s\]\}]*(?:,|:|$))+$/.test(str)) {
throw 'Invalid characters in JSON';
}
return eval('(' + str + ')');
};
var fakeProgram = {
getExports: function() {
},
toJs: function() {
return {src: ''};
}
};
var
hasProp = Object.prototype.hasOwnProperty,
Class = cor.Class,
path = cor.path;
/*
## cor.Loader
This class is responsible for load modules required anywhere
in the source code. It resolve module dependencies
*/
var Loader = Class({
// the path of the .json file containig the environment variables
confPath : null,
// the modules are astored here once compiled
moduleCache : {},
// path ignored by the loader
ignoredPaths : {},
// a has map of paths to redirect the loader
// required/path => path/to/redirect
// the wilcard * redirect the rest of the paths
pathMap : {'*': ''},
// number of loaded modules
numModules : 0,
// number of rquired modules
numRequiredModules : 0,
// the path of the entry module
entryModulePath : null,
// plugins to extend the loader funcionality
plugins : {},
isReady: false,
init: function() {
var
me = this,
originalOnReadFileSuccess = this.onReadFileSuccess,
originalOnReadFileFailure = this.onReadFileFailure;
this.onReadFileSuccess = function onReadFileSuccess() {
originalOnReadFileSuccess.apply(me, arguments);
};
this.onReadFileFailure = function onReadFileFailure() {
originalOnReadFileFailure.apply(me, arguments);
};
},
error: function error(msg, from) {
return new Error((from ? from : '') + ': ' + msg);
},
/*
plugin parameter must be an Object
with the following format:
{
ext: 'file_extension',
// this function will find the required modules inside the source code
// the parameters passed are the source code and the route of the file
// returns an array
findRequired: function(src, from){
},
// this function should compile the source code to javascript
// the parammeters are source code and the route of the loaded file
// should return an object whith the following format:
// {
// prefix: '',
// src: '',
// suffix: ''
// }
toJs: function(src, from){
}
}
*/
addPlugin: function(plugin) {
if (plugin) {
if (typeof plugin.ext !== 'string') {
throw this.error("'ext' property must be string");
}
if (typeof plugin.findRequired !== 'function') {
throw this.error("'findRequired' function is not implemented in " + plugin.ext + "' plugin");
}
if (typeof plugin.toJs !== 'function') {
throw this.error("'toJs' function is not implemented in '" + plugin.ext + "' plugin");
}
this.plugins[plugin.ext] = plugin;
path.fileExts.push(plugin.ext);
}
else {
throw this.error("Invalid object for plugin" + ext + "'");
}
},
/*
readFile will read files using XHTTPRequest
*/
readFile: function(path, from, onLoad, onError) {
var
e, xhr,
sync = !onLoad;
xhr = (typeof ActiveXObject === 'function')
? new ActiveXObject('Microsoft.XMLHTTP')
: new XMLHttpRequest();
function everyThingOk() {
return (xhr.status >= 200 && xhr.status < 300 || xhr.status === 304 || xhr.status === 1223);
}
try {
xhr.open('GET', path, !sync);
if (sync) {
xhr.send(null);
if (everyThingOk()) {
return xhr.responseText;
}
else {
this.error('There is not file at ' + path, from);
}
}
else {
xhr.onreadystatechange = function() {
if (xhr.readyState === 4 ) {
if (everyThingOk()) {
onLoad(path, from, xhr.responseText);
}
else {
if (typeof onError === 'function') {
onError(path, from);
}
}
}
}
xhr.send(null);
}
}
catch (e) {
throw this.error('Could not use XMLHttpRequest for ' + path + '(' + e.toString() + ')', from);
}
},
// add paths to be ignored by the loader
// `path` parameter is an array
ignorePath: function(path) {
var i, len;
if (path instanceof Array) {
for (i = 0, len = path.length; i < len; i++) {
this.ignoredPaths[path[i]] = true;
}
}
else {
this.ignoredPaths[path] = true;
}
},
resolveMappedPath: function(srcPath) {
var
name, candidate, ret = '';
for (name in this.pathMap) {
if (hasProp.call(this.pathMap, name) && srcPath.indexOf(name) === 0) {
candidate = this.pathMap[name] + srcPath.substr(name.length);
}
}
if (candidate) {
ret = path.sanitize(candidate);
}
return ret;
},
registerModule: function(path, src, deps) {
var
p = new Program(path, src, deps, this);
this.moduleCache[path] = p;
this.numModules++;
},
onReadFileSuccess: function onReadFileSuccess(srcPath, from, txt) {
var
i, len, absolutePath,
requiredPath, required,
ext = path.ext(srcPath),
plugin = this.plugins[ext],
dependences = {};
if (plugin) {
required = plugin.findRequired(txt, from);
}
else {
throw this.error("Extension '" + ext + "' is not supported");
}
for (i = 0, len = required.length; i < len; i++) {
requiredPath = required[i];
if (this.ignoredPaths[requiredPath]) {
continue;
}
if (path.isAbsolute(requiredPath)) {
absolutePath = path.sanitize(requiredPath);
}
else {
absolutePath = this.resolveMappedPath(requiredPath);
if (! absolutePath) {
absolutePath = path.resolve(srcPath, requiredPath);
}
}
dependences[requiredPath] = absolutePath;
this.requireModule(absolutePath, srcPath);
}
this.registerModule(srcPath, txt, dependences);
if (this.numModules === this.numRequiredModules) {
this.onLoaderReady();
}
},
onReadFileFailure: function onReadFileFailure(srcPath, from) {
console.error('Unable to read ', srcPath, ' requested from ', from);
},
onLoaderReady: function() {
var module;
if (this.isReady) { return }
module = this.moduleCache[this.entryModulePath];
if (module) {
this.isReady = true;
return module.getExports();
}
else {
this.error('Entry module not found');
}
},
setPath: function(srcPaths, confPath) {
var name;
for (name in srcPaths) {
this.pathMap[name] = path.resolve(confPath, srcPaths[name]);
}
},
requireModule: function(srcPath, from) {
if (! (this.ignoredPaths[srcPath] || this.moduleCache[srcPath])) {
this.ignorePath(srcPath);
var ext = path.ext(srcPath);
if (ext === '') {
srcPath += path.ext(from);
}
if (! this.entryModulePath) {
this.entryModulePath = srcPath;
}
this.numRequiredModules++;
this.readFile(srcPath, from, this.onReadFileSuccess, this.onReadFileFailure);
}
},
setEntry: function(entryPath, confPath) {
var
me = this,
cwd = path.cwd();
if (entryPath) {
if (path.ext(confPath) === '.json') {
me.readFile(confPath, cwd, function onConfReady(confPath, from, txt) {
var
conf = parseJson(txt);
me.confPath = path.resolve(cwd, confPath);
if (conf) {
if (conf.ignore) {
me.ignorePath(conf.ignore);
}
if (conf.paths) {
me.setPath(conf.paths, me.confPath);
}
me.boot(entryPath);
}
},
me.onReadFileFailure);
}
else {
me.boot(entryPath);
}
}
},
boot: function(srcPath) {
var
resolved = path.resolve(path.cwd(), srcPath);
this.requireModule(resolved, path.cwd());
}
});
// Program Class
var Program = Class({
filename : null,
exports : null,
dependences : null,
src : '',
environment : null,
loader : null,
usesRuntime : false,
init: function(path, src, deps, loader) {
this.src = src;
this.filename = path;
this.dependences = deps;
this.loader = loader;
},
getExports: function() {
var path, prog, js;
if (this.environment === null) {
js = this.toJs();
prog = (new Function('return function(require,module,exports){' +
(js.prefix || '') +
(js.src || '') + '\n}' +
(js.suffix || '')
))();
if (typeof prog === 'function') {
this.environment = this.newModule();
prog(this.environment.require, this.environment, this.environment.exports);
}
else {
this.loader.error('Error while attemp to execute ' + path);
}
}
return this.environment.exports;
},
newModule: function() {
var
me = this,
newMod = {
exports: {},
require: function require(srcPath) {
var
cache = me.loader.moduleCache,
depPath = me.dependences[srcPath],
module;
if (path.ext(depPath) === '') {
module = cache[depPath + path.ext(me.filename)];
}
else {
module = cache[depPath];
}
if (module) {
return module.getExports();
}
else if (! me.loader.ignoredPaths[srcPath]) {
throw me.loader.error("Can not find module '" + srcPath + "'", me.filename);
}
}
};
return newMod;
},
toJs: function() {
var
js,
ext = path.ext(this.filename),
plugin = this.loader.plugins[ext];
if (plugin) {
js = plugin.toJs(this.src, this.filename)
if (typeof js === 'string') {
js = {src: js};
}
this.usesRuntime = js.usesRuntime;
return js;
}
else {
throw this.error('Can not translate to javascript files \'.' + ext + "'", this.filename);
}
}
});
// public to cor namespace
cor.Loader = Loader;
cor.Program = Program;
})(typeof cor === 'undefined' ? {} : cor);