** Models a Fantom class.
**
** All types are generated with a standard serialisation ctor:
**
** new make(|This|? f := null) { f?.call(this) }
**
** All added fields and methods will be public. As you will never compile against the generated
** code, this should not be problematic.
class PlasticClassModel {
** Set to 'true' if this class is 'const'
const Bool isConst
** The name of the class.
const Str className
** The superclass type.
Type superClass := Obj# { private set }
** A list of mixin types the model extends.
Type[] mixins := [,] { private set } // for user info only
private Pod[] usingPods := [,]
private Type[] usingTypes := [,]
private Type[] extends := [,]
private PlasticFieldModel[] fields := [,]
private PlasticMethodModel[] methods := [,]
** Creates a class model with the given name.
new make(Str className, Bool isConst) {
this.isConst = isConst
this.className = className
extends.add(superClass)
}
** 'use' the given pod.
This usingPod(Pod pod) {
usingPods.add(pod)
return this
}
** 'use' the given type.
This usingType(Type type) {
usingTypes.add(type)
return this
}
** Sets the given type as the superclass.
** If this model is const, then the given type must be const also.
** This method may only be called once.
** The superclass must be an acutal 'class' (not a mixin) and be public.
This extendClass(Type classType) {
if (isConst && !classType.isConst)
throw PlasticErr(PlasticMsgs.constTypeCannotSubclassNonConstType(className, classType))
if (!isConst && classType.isConst)
throw PlasticErr(PlasticMsgs.nonConstTypeCannotSubclassConstType(className, classType))
if (superClass != Obj#)
throw PlasticErr(PlasticMsgs.canOnlyExtendOneClass(className, superClass, classType))
if (!classType.isClass)
throw PlasticErr(PlasticMsgs.canOnlyExtendClass(classType))
if (classType.isInternal)
throw PlasticErr(PlasticMsgs.superTypesMustBePublic(className, classType))
extends = extends.exclude { it == superClass}.add(classType)
superClass = classType
return this
}
** Extend the given mixin.
** The mixin must be public.
This extendMixin(Type mixinType) {
if (isConst && !mixinType.isConst)
throw PlasticErr(PlasticMsgs.constTypeCannotSubclassNonConstType(className, mixinType))
if (!isConst && mixinType.isConst)
throw PlasticErr(PlasticMsgs.nonConstTypeCannotSubclassConstType(className, mixinType))
if (!mixinType.isMixin)
throw PlasticErr(PlasticMsgs.canOnlyExtendMixins(mixinType))
if (mixinType.isInternal)
throw PlasticErr(PlasticMsgs.superTypesMustBePublic(className, mixinType))
mixins.add(mixinType)
extends.add(mixinType)
return this
}
** Add a field.
** 'getBody' and 'setBody' are code blocks to be used in the 'get' and 'set' accessors.
This addField(Type fieldType, Str fieldName, Str? getBody := null, Str? setBody := null, Type[] facets := Type#.emptyList) {
// synthetic fields may be non-const - how do we check if field is synthetic?
// if (isConst && !fieldType.isConst)
// throw PlasticErr(PlasticMsgs.constTypesMustHaveConstFields(className, fieldType, fieldName))
fieldModel := PlasticFieldModel(false, PlasticVisibility.visPublic, fieldType.isConst, fieldType, fieldName, getBody, setBody, facets)
fields.add(fieldModel)
return this
}
** Override a field.
** The given field must exist in a super class / mixin.
** 'getBody' and 'setBody' are code blocks to be used in the 'get' and 'set' accessors.
This overrideField(Field field, Str? getBody := null, Str? setBody := null) {
if (!extends.any { it.fits(field.parent) })
throw PlasticErr(PlasticMsgs.overrideFieldDoesNotBelongToSuperType(field, extends))
if (field.isPrivate || field.isInternal)
throw PlasticErr(PlasticMsgs.overrideFieldHasWrongScope(field))
fields.add(PlasticFieldModel(true, PlasticVisibility.visPublic, field.isConst, field.type, field.name, getBody, setBody, Facet#.emptyList))
return this
}
** Add a method.
** 'signature' does not include (brackets).
** 'body' does not include {braces}
This addMethod(Type returnType, Str methodName, Str signature, Str body) {
methods.add(PlasticMethodModel(false, PlasticVisibility.visPublic, returnType, methodName, signature, body))
return this
}
** Add a method.
** The given method must exist in a super class / mixin.
** 'body' does not include {braces}
This overrideMethod(Method method, Str body) {
if (!extends.any { it.fits(method.parent) })
throw PlasticErr(PlasticMsgs.overrideMethodDoesNotBelongToSuperType(method, extends))
if (method.isPrivate || method.isInternal)
throw PlasticErr(PlasticMsgs.overrideMethodHasWrongScope(method))
if (!method.isVirtual)
throw PlasticErr(PlasticMsgs.overrideMethodsMustBeVirtual(method))
if (method.params.any { it.hasDefault })
throw PlasticErr(PlasticMsgs.overrideMethodsCanNotHaveDefaultValues(method))
methods.add(PlasticMethodModel(true, PlasticVisibility.visPublic, method.returns, method.name, method.params.join(", "), body))
return this
}
** Converts the model into Fantom source code.
**
** All types are generated with a standard serialisation ctor:
**
** new make(|This|? f := null) { f?.call(this) }
Str toFantomCode() {
code := ""
usingPods.unique.each { code += "using ${it.name}\n" }
usingTypes.unique.each { code += "using ${it.qname}\n" }
code += "\n"
constKeyword := isConst ? "const " : ""
extendsKeyword := extends.exclude { it == Obj#}.isEmpty ? "" : " : " + extends.exclude { it == Obj#}.map { it.qname }.join(", ")
code += "${constKeyword}class ${className}${extendsKeyword} {\n\n"
fields.each { code += it.toFantomCode }
code += "\n"
code += " new make(|This|? f := null) {
f?.call(this)
}\n"
code += "\n"
methods.each { code += it.toFantomCode }
code += "}\n"
return code
}
}
** Models a Fantom field.
class PlasticFieldModel {
Bool isOverride
PlasticVisibility visibility
Bool isConst
Type type
Str name
Str? getBody
Str? setBody
Type[] facetTypes
internal new make(Bool isOverride, PlasticVisibility visibility, Bool isConst, Type type, Str name, Str? getBody, Str? setBody, Type[] facetTypes) {
this.isOverride = isOverride
this.visibility = visibility
this.isConst = isConst
this.type = type
this.name = name
this.getBody = getBody
this.setBody = setBody
this.facetTypes = facetTypes
}
** Converts the model into Fantom source code.
Str toFantomCode() {
field := ""
facetTypes.each { field += " @${it.qname}\n" }
overrideKeyword := isOverride ? "override " : ""
constKeyword := isConst ? "const " : ""
field +=
" ${overrideKeyword}${visibility.keyword}${constKeyword}${type.signature} ${name}"
if (getBody != null || setBody != null) {
field += " {\n"
if (getBody != null)
field += " get { ${getBody} }\n"
if (setBody != null)
field += " set { ${setBody} }\n"
field += " }\n"
}
field += "\n"
return field
}
}
** Models a Fantom method.
class PlasticMethodModel {
Bool isOverride
PlasticVisibility visibility
Type returnType
Str name
Str signature
Str body
internal new make(Bool isOverride, PlasticVisibility visibility, Type returnType, Str name, Str signature, Str body) {
this.isOverride = isOverride
this.visibility = visibility
this.returnType = returnType
this.name = name
this.signature = signature
this.body = body
}
** Converts the model into Fantom source code.
Str toFantomCode() {
overrideKeyword := isOverride ? "override " : ""
return
" ${overrideKeyword}${visibility.keyword}${returnType.signature} ${name}(${signature}) {
${indentBody}
}\n"
}
private Str indentBody() {
body.splitLines.join("\n\t\t")
}
}
** A list of Fantom visibilities.
enum class PlasticVisibility {
** Private scope.
visPrivate ("private "),
** Internal scope.
visInternal ("internal "),
** Protected scope.
visProtected("protected "),
** Public scope.
visPublic ("");
** The keyword to be used in Fantom source code.
const Str keyword
private new make(Str keyword) {
this.keyword = keyword
}
** Returns the visibility of the given field / method.
static PlasticVisibility fromSlot(Slot slot) {
if (slot.isPrivate)
return visPrivate
if (slot.isInternal)
return visInternal
if (slot.isProtected)
return visProtected
if (slot.isPublic)
return visPublic
throw Err("What visibility is ${slot.signature}???")
}
}