{-# LANGUAGE OverloadedStrings #-} {-# LANGUAGE TemplateHaskell #-} {-# LANGUAGE QuasiQuotes #-} module Fay.Runtime where import Text.Shakespeare.Text import Data.Text.Lazy as T import Fay.Config -- | Get the default runtime source. getRuntimeSource :: Config -> String getRuntimeSource cfg = let ifTs :: T.Text -> T.Text ifTs a = if configTypeScript cfg then a else "" ifTsJs :: T.Text -> T.Text -> T.Text ifTsJs a b = if configTypeScript cfg then a else b in T.unpack [lt| /******************************************************************************* * Misc. */ #{ifTs "var Fay:{[key:string]: any;} = {};"} // Workaround for missing functionality in IE 8 and earlier. if( Object.create === undefined ) { Object.create = function( o ) { function F(){} F.prototype = o; return new F(); }; } // Insert properties of b in place into a. function Fay$$objConcat(a,b){ for (var p in b) if (b.hasOwnProperty(p)){ a[p] = b[p]; } return a; } /******************************************************************************* * Thunks. */ // Force a thunk (if it is a thunk) until WHNF. function Fay$$_(thunkish,nocache#{ifTs "?: boolean"}){ while (thunkish instanceof Fay$$$) { thunkish = thunkish.force(nocache); } return thunkish; } // Apply a function to arguments (see method2 in Fay.hs). function Fay$$__(){ var f = arguments[0]; for (var i = 1, len = arguments.length; i < len; i++) { f = (f instanceof Fay$$$? Fay$$_(f) : f)(arguments[i]); } return f; } // Thunk object. function Fay$$$(value){ this.forced = false; this.value = value; } // Force the thunk. Fay$$$.prototype.force = function(nocache) { return nocache ? this.value() : (this.forced ? this.value : (this.value = this.value(), this.forced = true, this.value)); }; function Fay$$seq(x) { return function(y) { Fay$$_(x,false); return y; } } function Fay$$seq$36$uncurried(x,y) { Fay$$_(x,false); return y; } /******************************************************************************* * Monad. */ function Fay$$Monad(value){ this.value = value; } // This is used directly from Fay, but can be rebound or shadowed. See primOps in Types.hs. // >> function Fay$$then(a){ return function(b){ return Fay$$bind(a)(function(_){ return b; }); }; } // This is used directly from Fay, but can be rebound or shadowed. See primOps in Types.hs. // >> function Fay$$then$36$uncurried(a,b){ return Fay$$bind$36$uncurried(a,function(_){ return b; }); } // >>= // This is used directly from Fay, but can be rebound or shadowed. See primOps in Types.hs. function Fay$$bind(m){ return function(f){ return new Fay$$$(function(){ var monad = Fay$$_(m,true); return Fay$$_(f)(monad.value); }); }; } // >>= // This is used directly from Fay, but can be rebound or shadowed. See primOps in Types.hs. function Fay$$bind$36$uncurried(m,f){ return new Fay$$$(function(){ var monad = Fay$$_(m,true); return Fay$$_(f)(monad.value); }); } // This is used directly from Fay, but can be rebound or shadowed. function Fay$$$_return(a){ return new Fay$$Monad(a); } // Allow the programmer to access thunk forcing directly. function Fay$$force(thunk){ return function(type){ return new Fay$$$(function(){ Fay$$_(thunk,type); return new Fay$$Monad(Fay$$unit); }) } } // This is used directly from Fay, but can be rebound or shadowed. function Fay$$return$36$uncurried(a){ return new Fay$$Monad(a); } // Unit: (). var Fay$$unit = null; /******************************************************************************* * Serialization. * Fay <-> JS. Should be bijective. */ // Serialize a Fay object to JS. function Fay$$fayToJs(type,fayObj){ var base = type[0]; var args = type[1]; var jsObj; if(base == "action") { // A nullary monadic action. Should become a nullary JS function. // Fay () -> function(){ return ... } return function(){ return Fay$$fayToJs(args[0],Fay$$_(fayObj,true).value); }; } else if(base == "function") { // A proper function. return function(){ var fayFunc = fayObj; var return_type = args[args.length-1]; var len = args.length; // If some arguments. if (len > 1) { // Apply to all the arguments. fayFunc = Fay$$_(fayFunc,true); // TODO: Perhaps we should throw an error when JS // passes more arguments than Haskell accepts. // Unserialize the JS values to Fay for the Fay callback. if (args == "automatic_function") { for (var i = 0; i < arguments.length; i++) { fayFunc = Fay$$_(fayFunc(Fay$$jsToFay(["automatic"],arguments[i])),true); } return Fay$$fayToJs(["automatic"], fayFunc); } for (var i = 0, len = len; i < len - 1 && fayFunc instanceof Function; i++) { fayFunc = Fay$$_(fayFunc(Fay$$jsToFay(args[i],arguments[i])),true); } // Finally, serialize the Fay return value back to JS. var return_base = return_type[0]; var return_args = return_type[1]; // If it's a monadic return value, get the value instead. if(return_base == "action") { return Fay$$fayToJs(return_args[0],fayFunc.value); } // Otherwise just serialize the value direct. else { return Fay$$fayToJs(return_type,fayFunc); } } else { throw new Error("Nullary function?"); } }; } else if(base == "string") { return Fay$$fayToJs_string(fayObj); } else if(base == "list") { // Serialize Fay list to JavaScript array. var arr = []; fayObj = Fay$$_(fayObj); while(fayObj instanceof Fay$$Cons) { arr.push(Fay$$fayToJs(args[0],fayObj.car)); fayObj = Fay$$_(fayObj.cdr); } return arr; } else if(base == "tuple") { // Serialize Fay tuple to JavaScript array. var arr = []; fayObj = Fay$$_(fayObj); var i = 0; while(fayObj instanceof Fay$$Cons) { arr.push(Fay$$fayToJs(args[i++],fayObj.car)); fayObj = Fay$$_(fayObj.cdr); } return arr; } else if(base == "defined") { fayObj = Fay$$_(fayObj); return fayObj instanceof Fay.FFI._Undefined ? undefined : Fay$$fayToJs(args[0],fayObj.slot1); } else if(base == "nullable") { fayObj = Fay$$_(fayObj); return fayObj instanceof Fay.FFI._Null ? null : Fay$$fayToJs(args[0],fayObj.slot1); } else if(base == "double" || base == "int" || base == "bool") { // Bools are unboxed. return Fay$$_(fayObj); } else if(base == "ptr") return fayObj; else if(base == "unknown") return Fay$$fayToJs(["automatic"], fayObj); else if(base == "automatic" && fayObj instanceof Function) { return Fay$$fayToJs(["function", "automatic_function"], fayObj); } else if(base == "automatic" || base == "user") { fayObj = Fay$$_(fayObj); if(fayObj instanceof Fay$$Cons || fayObj === null){ // Serialize Fay list to JavaScript array. var arr = []; while(fayObj instanceof Fay$$Cons) { arr.push(Fay$$fayToJs(["automatic"],fayObj.car)); fayObj = Fay$$_(fayObj.cdr); } return arr; } else { var fayToJsFun = fayObj && fayObj.instance && Fay$$fayToJsHash[fayObj.instance]; return fayToJsFun ? fayToJsFun(type,type[2],fayObj) : fayObj; } } throw new Error("Unhandled Fay->JS translation type: " + base); } // Stores the mappings from fay types to js objects. // This will be populated by compiled modules. var Fay$$fayToJsHash = {}; // Specialized serializer for string. function Fay$$fayToJs_string(fayObj){ // Serialize Fay string to JavaScript string. var str = ""; fayObj = Fay$$_(fayObj); while(fayObj instanceof Fay$$Cons) { str += Fay$$_(fayObj.car); fayObj = Fay$$_(fayObj.cdr); } return str; }; function Fay$$jsToFay_string(x){ return Fay$$list(x) }; // Special num/bool serializers. function Fay$$jsToFay_int(x){return x;} function Fay$$jsToFay_double(x){return x;} function Fay$$jsToFay_bool(x){return x;} function Fay$$fayToJs_int(x){return Fay$$_(x);} function Fay$$fayToJs_double(x){return Fay$$_(x);} function Fay$$fayToJs_bool(x){return Fay$$_(x);} // Unserialize an object from JS to Fay. function Fay$$jsToFay(type,jsObj){ var base = type[0]; var args = type[1]; var fayObj; if(base == "action") { // Unserialize a "monadic" JavaScript return value into a monadic value. return new Fay$$Monad(Fay$$jsToFay(args[0],jsObj)); } else if(base == "function") { // Unserialize a function from JavaScript to a function that Fay can call. // So // // var f = function(x,y,z){ … } // // becomes something like: // // function(x){ // return function(y){ // return function(z){ // return new Fay$$$(function(){ // return Fay$$jsToFay(f(Fay$$fayTojs(x), // Fay$$fayTojs(y), // Fay$$fayTojs(z)) // }}}}}; var returnType = args[args.length-1]; var funArgs = args.slice(0,-1); if (jsObj.length > 0) { var makePartial = function(args){ return function(arg){ var i = args.length; var fayArg = Fay$$fayToJs(funArgs[i],arg); var newArgs = args.concat([fayArg]); if(newArgs.length == funArgs.length) { return new Fay$$$(function(){ return Fay$$jsToFay(returnType,jsObj.apply(this,newArgs)); }); } else { return makePartial(newArgs); } }; }; return makePartial([]); } else return function (arg) { return Fay$$jsToFay(["automatic"], jsObj(Fay$$fayToJs(["automatic"], arg))); }; } else if(base == "string") { // Unserialize a JS string into Fay list (String). // This is a special case, when String is explicit in the type signature, // with `Automatic' a string would not be decoded. return Fay$$list(jsObj); } else if(base == "list") { // Unserialize a JS array into a Fay list ([a]). var serializedList = []; for (var i = 0, len = jsObj.length; i < len; i++) { // Unserialize each JS value into a Fay value, too. serializedList.push(Fay$$jsToFay(args[0],jsObj[i])); } // Pop it all in a Fay list. return Fay$$list(serializedList); } else if(base == "tuple") { // Unserialize a JS array into a Fay tuple ((a,b,c,...)). var serializedTuple = []; for (var i = 0, len = jsObj.length; i < len; i++) { // Unserialize each JS value into a Fay value, too. serializedTuple.push(Fay$$jsToFay(args[i],jsObj[i])); } // Pop it all in a Fay list. return Fay$$list(serializedTuple); } else if(base == "defined") { return jsObj === undefined ? new Fay.FFI._Undefined() : new Fay.FFI._Defined(Fay$$jsToFay(args[0],jsObj)); } else if(base == "nullable") { return jsObj === null ? new Fay.FFI._Null() : new Fay.FFI.Nullable(Fay$$jsToFay(args[0],jsObj)); } else if(base == "int") { // Int are unboxed, so there's no forcing to do. // But we can do validation that the int has no decimal places. // E.g. Math.round(x)!=x? throw "NOT AN INTEGER, GET OUT!" fayObj = Math.round(jsObj); if(fayObj!==jsObj) throw "Argument " + jsObj + " is not an integer!"; return fayObj; } else if (base == "double" || base == "bool" || base == "ptr") { return jsObj; } else if(base == "unknown") return Fay$$jsToFay(["automatic"], jsObj); else if(base == "automatic" && jsObj instanceof Function) { #{ifTsJs "let type: string[][]" "var type"} = [["automatic"]]; for (var i = 0; i < jsObj.length; i++) type.push(["automatic"]); return Fay$$jsToFay(["function", type], jsObj); } else if(base == "automatic" && jsObj instanceof Array) { var list = null; for (var i = jsObj.length - 1; i >= 0; i--) { list = new Fay$$Cons(Fay$$jsToFay([base], jsObj[i]), list); } return list; } else if(base == "automatic" || base == "user") { if (jsObj && jsObj['instance']) { var jsToFayFun = Fay$$jsToFayHash[jsObj["instance"]]; return jsToFayFun ? jsToFayFun(type,type[2],jsObj) : jsObj; } else return jsObj; } throw new Error("Unhandled JS->Fay translation type: " + base); } // Stores the mappings from js objects to fay types. // This will be populated by compiled modules. var Fay$$jsToFayHash = {}; /******************************************************************************* * Lists. */ // Cons object. function Fay$$Cons(car,cdr){ this.car = car; this.cdr = cdr; } // Make a list. function Fay$$list(xs){ var out = null; for(var i=xs.length-1; i>=0;i--) out = new Fay$$Cons(xs[i],out); return out; } // Built-in list cons. function Fay$$cons(x){ return function(y){ return new Fay$$Cons(x,y); }; } // List index. // `list' is already forced by the time it's passed to this function. // `list' cannot be null and `index' cannot be out of bounds. function Fay$$index(index,list){ for(var i = 0; i < index; i++) { list = Fay$$_(list.cdr); } return list.car; } // List length. // `list' is already forced by the time it's passed to this function. function Fay$$listLen(list,max){ for(var i = 0; list !== null && i < max + 1; i++) { list = Fay$$_(list.cdr); } return i == max; } /******************************************************************************* * Numbers. */ // Built-in *. function Fay$$mult(x){ return function(y){ return new Fay$$$(function(){ return Fay$$_(x) * Fay$$_(y); }); }; } function Fay$$mult$36$uncurried(x,y){ return new Fay$$$(function(){ return Fay$$_(x) * Fay$$_(y); }); } // Built-in +. function Fay$$add(x){ return function(y){ return new Fay$$$(function(){ return Fay$$_(x) + Fay$$_(y); }); }; } // Built-in +. function Fay$$add$36$uncurried(x,y){ return new Fay$$$(function(){ return Fay$$_(x) + Fay$$_(y); }); } // Built-in -. function Fay$$sub(x){ return function(y){ return new Fay$$$(function(){ return Fay$$_(x) - Fay$$_(y); }); }; } // Built-in -. function Fay$$sub$36$uncurried(x,y){ return new Fay$$$(function(){ return Fay$$_(x) - Fay$$_(y); }); } // Built-in /. function Fay$$divi(x){ return function(y){ return new Fay$$$(function(){ return Fay$$_(x) / Fay$$_(y); }); }; } // Built-in /. function Fay$$divi$36$uncurried(x,y){ return new Fay$$$(function(){ return Fay$$_(x) / Fay$$_(y); }); } /******************************************************************************* * Booleans. */ // Are two values equal? function Fay$$equal(lit1, lit2) { // Simple case lit1 = Fay$$_(lit1); lit2 = Fay$$_(lit2); if (lit1 === lit2) { return true; } // General case if (lit1 instanceof Array) { if (lit1.length != lit2.length) return false; for (var len = lit1.length, i = 0; i < len; i++) { if (!Fay$$equal(lit1[i], lit2[i])) return false; } return true; } else if (lit1 instanceof Fay$$Cons && lit2 instanceof Fay$$Cons) { do { if (!Fay$$equal(lit1.car,lit2.car)) return false; lit1 = Fay$$_(lit1.cdr), lit2 = Fay$$_(lit2.cdr); if (lit1 === null || lit2 === null) return lit1 === lit2; } while (true); } else if (typeof lit1 == 'object' && typeof lit2 == 'object' && lit1 && lit2 && lit1.instance === lit2.instance) { for(var x in lit1) { if(!Fay$$equal(lit1[x],lit2[x])) return false; } return true; } else { return false; } } // Built-in ==. function Fay$$eq(x){ return function(y){ return new Fay$$$(function(){ return Fay$$equal(x,y); }); }; } function Fay$$eq$36$uncurried(x,y){ return new Fay$$$(function(){ return Fay$$equal(x,y); }); } // Built-in /=. function Fay$$neq(x){ return function(y){ return new Fay$$$(function(){ return !(Fay$$equal(x,y)); }); }; } // Built-in /=. function Fay$$neq$36$uncurried(x,y){ return new Fay$$$(function(){ return !(Fay$$equal(x,y)); }); } // Built-in >. function Fay$$gt(x){ return function(y){ return new Fay$$$(function(){ return Fay$$_(x) > Fay$$_(y); }); }; } // Built-in >. function Fay$$gt$36$uncurried(x,y){ return new Fay$$$(function(){ return Fay$$_(x) > Fay$$_(y); }); } // Built-in <. function Fay$$lt(x){ return function(y){ return new Fay$$$(function(){ return Fay$$_(x) < Fay$$_(y); }); }; } // Built-in <. function Fay$$lt$36$uncurried(x,y){ return new Fay$$$(function(){ return Fay$$_(x) < Fay$$_(y); }); } // Built-in >=. function Fay$$gte(x){ return function(y){ return new Fay$$$(function(){ return Fay$$_(x) >= Fay$$_(y); }); }; } // Built-in >=. function Fay$$gte$36$uncurried(x,y){ return new Fay$$$(function(){ return Fay$$_(x) >= Fay$$_(y); }); } // Built-in <=. function Fay$$lte(x){ return function(y){ return new Fay$$$(function(){ return Fay$$_(x) <= Fay$$_(y); }); }; } // Built-in <=. function Fay$$lte$36$uncurried(x,y){ return new Fay$$$(function(){ return Fay$$_(x) <= Fay$$_(y); }); } // Built-in &&. function Fay$$and(x){ return function(y){ return new Fay$$$(function(){ return Fay$$_(x) && Fay$$_(y); }); }; } // Built-in &&. function Fay$$and$36$uncurried(x,y){ return new Fay$$$(function(){ return Fay$$_(x) && Fay$$_(y); }); ; } // Built-in ||. function Fay$$or(x){ return function(y){ return new Fay$$$(function(){ return Fay$$_(x) || Fay$$_(y); }); }; } // Built-in ||. function Fay$$or$36$uncurried(x,y){ return new Fay$$$(function(){ return Fay$$_(x) || Fay$$_(y); }); } /******************************************************************************* * Mutable references. */ // Make a new mutable reference. function Fay$$Ref(x){ this.value = x; } // Write to the ref. function Fay$$writeRef(ref,x){ ref.value = x; } // Get the value from the ref. function Fay$$readRef(ref){ return ref.value; } /******************************************************************************* * Dates. */ function Fay$$date(str){ return Date.parse(str); } /******************************************************************************* * Data.Var */ function Fay$$Ref2(val){ this.val = val; } function Fay$$Sig(){ this.handlers = []; } function Fay$$Var(val){ this.val = val; this.handlers = []; } // Helper used by Fay$$setValue and for merging function Fay$$broadcastInternal(self, val, force){ var handlers = self.handlers; var exceptions = []; for(#{ifTsJs "let" "var"} len = handlers.length, i = 0; i < len; i++) { try { force(handlers[i][1](val), true); } catch (e) { exceptions.push(e); } } // Rethrow the encountered exceptions. if (exceptions.length > 0) { console.error("Encountered " + exceptions.length + " exception(s) while broadcasing a change to ", self); for(#{ifTsJs "let len: number" "var len"} = exceptions.length, i = 0; i < len; i++) { (function(exception) { setTimeout(function() { throw exception; }, 0); })(exceptions[i]); } } } function Fay$$setValue(self, val, force){ if (self instanceof Fay$$Ref2) { self.val = val; } else if (self instanceof Fay$$Var) { self.val = val; Fay$$broadcastInternal(self, val, force); } else if (self instanceof Fay$$Sig) { Fay$$broadcastInternal(self, val, force); } else { throw "Fay$$setValue given something that's not a Ref2, Var, or Sig" } } function Fay$$subscribe(self, f){ var key = {}; self.handlers.push([key,f]); var searchStart = self.handlers.length - 1; return function(_){ for(var i = Math.min(searchStart, self.handlers.length - 1); i >= 0; i--) { if(self.handlers[i][0] == key) { self.handlers = self.handlers.slice(0,i).concat(self.handlers.slice(i+1)); return; } } return _; // This variable has to be used, otherwise Closure // strips it out and Fay serialization breaks. }; } /******************************************************************************* * Application code. */ |]