using afPlastic::PlasticCompilationErr
using afPlastic::PlasticClassModel
using afPlastic::PlasticCompiler
using afPlastic::SrcCodeSnippet
using afConcurrent::LocalRef
** Compiles an efan template into a method on a Fantom type.
**
** [Plastic]`pod:afPlastic` is used to generate and compile the Fantom source.
const class EfanCompiler {
private const PlasticCompiler plasticCompiler
private const EfanParser efanParser
** The name of created render methods.
const Str renderMethodName := "_efanRender"
** The name given to the 'ctx' variable in the render method.
const Str ctxName := "ctx"
** Generates the type name given to compiled efan template instances.
const |Type[]->Str| templateTypeNameFn := |Type[] viewHelpers->Str| { (viewHelpers.first?.name ?: "") + "EfanTemplate" }
** Standard it-block ctor for setting fields.
new make(|This|? in := null) {
this.plasticCompiler = PlasticCompiler { it.podBaseName = "afEfanTemplate" }
this.efanParser = EfanParser()
in?.call(this)
}
private new ctorForIoc(EfanParser efanParser, PlasticCompiler plasticCompiler, |This| in) {
this.efanParser = efanParser
this.plasticCompiler = plasticCompiler
in(this)
}
** Compiles and instantiates a new 'EfanMeta' instance from the given efan template.
** The compiled template extends the given view helper mixins.
**
** This method compiles a new Fantom Type so use judiciously to avoid memory leaks.
**
** Note that 'templateLoc' is only used for reporting Err msgs.
EfanMeta compile(Uri templateLoc, Str templateSrc, Type? ctxType := null, Type[] viewHelpers := Type#.emptyList) {
viewHelpers.each {
if (!it.isPublic)
throw EfanErr(ErrMsgs.viewHelperMixinIsNotPublic(it))
}
// the important efan parsing
parseResult := efanParser.parse(templateLoc, templateSrc)
isConst := viewHelpers.first?.isConst ?: true // const by default
model := PlasticClassModel(templateTypeNameFn(viewHelpers), isConst)
parseResult.usings.each { model.usingStr(it) }
model.usingType(EfanMeta#)
viewHelpers.each { model.extend(it) }
viewHelpers.first?.methods?.findAll { it.isCtor }?.each {
model.overrideCtor(it, "")
}
// we need the special syntax of "_efan_output = XXXX" so we don't have to close any brackets with eval expressions
fieldName := efanParser.fieldName
model.addField(LocalRef#, fieldName + "_ref").withInitValue("${LocalRef#.qname}(\"${fieldName}\") |->Obj| { StrBuf() }")
model.addField(Obj?#, fieldName, """((StrBuf) ${fieldName}_ref.val).toStr""", """((StrBuf) ${fieldName}_ref.val).add(it)""")
// check the ctx type here so it also works from renderEfan()
ctxTypeSig := (ctxType == null) ? "Obj?" : ctxType.signature
renderCode := ""
if (ctxType != null && !ctxType.isNullable) {
renderCode += "if (_ctx == null)\n"
renderCode += " throw ${EfanErr#.qname}(\"${ErrMsgs.compiler_ctxIsNull} ${ctxType.typeof.signature}\")\n"
}
if (ctxType != null) {
renderCode += "if (_ctx != null && !_ctx.typeof.fits(${ctxType.qname}#))\n"
renderCode += " throw ${EfanErr#.qname}(\"ctx \${_ctx.typeof.signature} ${ErrMsgs.compiler_ctxNoFit(ctxType)}\")\n"
}
renderCode += "${ctxTypeSig} ${ctxName} := _ctx\n"
renderCode += "\n"
renderCode += parseResult.fantomCode
renderCode += "\t\t${fieldName} := this.${fieldName}\n"
renderCode += "\t\t${fieldName}_ref.cleanUp\n"
renderCode += "\t\treturn ${fieldName}\n"
model.addMethod(Str#, renderMethodName, "Obj? _ctx", renderCode)
efanType := compileModel(model, templateSrc, templateLoc)
return EfanMeta {
it.type = efanType
it.typeSrc = model.toFantomCode
it.templateLoc = templateLoc
it.templateSrc = templateSrc
it.srcCodePadding = plasticCompiler.srcCodePadding
it.ctxType = ctxType
it.ctxName = this.ctxName
it.renderMethod = efanType.method(renderMethodName, true)
}
}
** Compiles the given Plastic model, converting any compilation errors to 'EfanCompilationErrs'
** that shows where in the efan template the error occurred.
@NoDoc
Type compileModel(PlasticClassModel model, Str templateSrc, Uri templateLoc) {
try {
return plasticCompiler.compileModel(model)
} catch (PlasticCompilationErr cause) {
templateLineNo := EfanMeta.findTemplateLineNo(model.toFantomCode, cause.errLineNo) ?: throw cause
srcCodeSnippet := SrcCodeSnippet(templateLoc, templateSrc)
throw EfanCompilationErr(srcCodeSnippet, templateLineNo, cause.msg, cause.linesOfPadding, cause)
}
}
}