//
// Copyright (c) 2009, Brian Frank and Andy Frank
// Licensed under the Academic Free License version 3.0
//
// History:
// 08 Jun 15 Matthew Giannini Creation
//
using compiler
**
** Utility for working with JS closures. Shared by JsPod and JsClosureExpr
** for writing javascript related to closures.
**
** Every closure in JS requires that we create a 'fan.sys.Func'. The Func
** requires a 'ClosureFuncSpec$' object that represents the closure
** specification for the Func.This class analyzes all closures for the pod and creates
** a static field for each unique ClosureFuncSpec it identifies.Then, when the actual
** closure Func is created and called, it uses that static field instead of creating
** a new one every time the closure is called.
**
class JsPodClosures : JsNode
{
new make(JsCompilerSupport s) : super(s)
{
}
** Write the actual closure Func (JsClosureExpr)
Void writeClosure(ClosureExpr ce, JsWriter out)
{
loc := ce.loc
func := JsMethod(support, ce.doCall)
sig := func.sig(func.params)
out.w("fan.sys.Func.make\$closure(", loc).nl
out.indent
CType[] sigTypes := [,].addAll(ce.signature.params).add(ce.signature.ret)
isJs := sigTypes.all { JsType.checkJsSafety(it, support, loc) && !it.isForeign }
if (isJs)
{
// closure spec
out.w("${mapFuncSpec(ce)},", loc).nl
// func
out.w("function$sig", loc).nl
out.w("{").nl
out.indent
old := support.thisName
support.thisName = "\$this"
func.code?.write(out)
support.thisName = old
out.unindent
out.w("})")
}
else
{
// this closure uses non-JS types. Write a closure that documents this fact
out.w("new fan.sys.ClosureFuncSpec\$(fan.sys.Void.\$type, []),").nl
out.w("function() {").nl
out.w(" // Cannot write closure. Signature uses non-JS types: ${ce.signature}").nl
out.w(" throw fan.sys.UnsupportedErr.make('Closure uses non-JS types: ' + ${ce.signature.toStr.toCode});").nl
out.w("})")
}
out.unindent
}
** Write the unique closure specification fields for this pod (JsPod)
override Void write(JsWriter out)
{
varToFunc.each |JsMethod func, Str var|
{
loc := func.loc
out.w("${var} = new fan.sys.ClosureFuncSpec\$(", loc)
// return type
JsTypeLiteralExpr.writeType(func.ret, out)
out.w(",")
// raw parameters
out.w("[")
func.params.each |p,i|
{
if (i > 0) out.w(",")
out.w("\"${p.name}\",\"${p.paramType.sig}\",\"${p.hasDef}\"", loc)
}
out.w("]);").nl
}
}
** Creates a variable for the ClosureFuncSpec of this closure and
** returns the variable name. If we have already seen a closure with
** the EXACT same spec, then re-use that variable declaration and
** return the existing variable name.
private Str mapFuncSpec(ClosureExpr ce)
{
func := JsMethod(support, ce.doCall)
var := specKeyToVar.getOrAdd(specKey(func)) |->Str|
{
"${pod(ce)}.\$clos${support.unique}"
}
varToFunc[var] = func
return var
}
** Get the pod variable prefix for all the closure func specs
static private Str pod(ClosureExpr ce) { "fan.${ce.enclosingType.pod}" }
** Return the unique key for this function specification
static private Str specKey(JsMethod func)
{
buf := StrBuf()
func.params.each |p|
{
buf.add("${p.name}-${p.paramType.sig}-${p.hasDef},")
}
buf.add("${func.ret.sig}")
return buf.toStr
}
** Func spec key to field variable name
private Str:Str specKeyToVar := [:]
** Func spec field variable name to prototype function (for params and return type)
private Str:JsMethod varToFunc := [:] { ordered = true }
}