sourceafPlastic::PlasticCompiler.fan

using concurrent::AtomicInt
using concurrent::AtomicRef
using compiler

** (Service) - 
** Compiles Fantom source code and afPlastic models into usable Fantom code.
** 
** Note: This class is available as a service in IoC v3 under the 'root' scope with an ID of 'afPlastic::PlasticCompiler'.
const class PlasticCompiler {
    
    ** static because pods are shared throughout the JVM, not just the IoC 
    private static const AtomicInt podIndex := AtomicInt(1)

    ** When generating code snippets to report compilation Errs, this is the number of lines of src 
    ** code the erroneous line should be padded with. 
    ** 
    ** Value is mutable. Defaults to '5'.  
    Int srcCodePadding {
        get { _srcCodePadding.val }
        set { _srcCodePadding.val = it }
    }
    private const AtomicInt _srcCodePadding := AtomicInt(5)
    
    ** The base name used to generate pod names. 
    ** This name may overridden when compiling src.
    ** 
    ** Defaults to 'afPlastic' so pods will be named 'afPlasticXXX'.
    Str podBaseName {
        get { _podBaseName.val }
        set { _podBaseName.val = it }       
    }   
    private const AtomicRef _podBaseName    := AtomicRef("afPlastic")

    ** Creates a 'PlasticCompiler'.
    new make(|This|? in := null) { in?.call(this) }
    
    ** Compiles the given class model into a pod and returns the associated Fantom type.
    ** If no pod name is given, a unique one will be generated.
    Type compileModel(PlasticClassModel model, Str? podName := null) {
        podName = podName ?: generatePodName
        pod     := compileCode(model.toFantomCode, podName)
        type    := pod.type(model.className)
        return type
    }

    Type[] compileModels(PlasticClassModel[] models, Str? podName := null) {
        if (models.isEmpty) return Type[,]
        
        podName = podName ?: generatePodName
        
        podCode := models.first
        models.eachRange(1..-1) { podCode.usings.addAll(it.usings); it.usings.clear }
        fanCode := models.map { it.toFantomCode }.join("\n")
        
        pod     := compileCode(fanCode, podName)
        types   := models.map { pod.type(it.className) }
        return types
    }
    
    ** Compiles the given Fantom code into a pod. 
    ** If no pod name is given, a unique one will be generated.
    ** 
    ** 'srcCodeLocation' is just used to report errors. If not given, the 'podName' is used.
    Pod compileCode(Str fantomPodCode, Str? podName := null, Uri? srcCodeLocation := null ) {
        input           := CompilerInput()
        input.output    = CompilerOutputMode.transientPod
        return compile(input, fantomPodCode, podName, srcCodeLocation).transientPod
    }
    
    ** Compiles the given Fantom code into a pod file. 
    ** If no pod name is given, a unique one will be generated.
    ** 
    ** 'srcCodeLocation' is just used to report errors. If not given, the 'podName' is used.
    ** 
    ** Note the returned file is deemed to be transient, so is deleted on exit.
    ** 
    ** Note: Fantom 1.0.68 needs [this patch]`http://fantom.org/forum/topic/2536` in order to work.
    File compileCodeToPodFile(Str fantomPodCode, Str? podName := null, Uri? srcCodeLocation := null ) {
        input               := CompilerInput()
        input.output        = CompilerOutputMode.podFile
        input.outDir        = Env.cur.tempDir
        // yes please! Generate .apidocs for us!
        input.includeDoc    = true
        // if true, then actual src FILES are looked for
        input.includeSrc    = false
        // just needs to be not-null - compiler::WritePod.writePodDocs() looks for .fandoc files
        input.baseDir       = Env.cur.tempDir
        return compile(input, fantomPodCode, podName, srcCodeLocation).podFile.deleteOnExit
    }
    
    ** Different pod names prevents "sys::Err: Duplicate pod name: <podName>".
    ** We internalise podName so we can guarantee no duplicate pod names
    Str generatePodName() {
        index := podIndex.getAndIncrement.toStr.padl(3, '0')        
        return "${podBaseName}${index}"
    }
    
    private CompilerOutput compile(CompilerInput input, Str fantomPodCode, Str? podName, Uri? srcCodeLocation) {
        podName = podName ?: generatePodName

        if (Pod.of(this).log.isDebug)
            Pod.of(this).log.debug("Compiling code for pod: ${podName}\n${fantomPodCode}")

        try {
            input.podName   = podName
            input.summary   = "Dynamic Pod compiled by Alien-Factory Plastic"
            input.version   = Version.defVal
            input.log.level = LogLevel.silent   // we'll raise our own Errs - less noise to std.out
            input.isScript  = true
            input.mode      = CompilerInputMode.str
            input.srcStrLoc = Loc(podName)
            input.srcStr    = fantomPodCode

            compiler        := Compiler(input)
            output          := compiler.compile
            return output

        } catch (CompilerErr err) {
            srcCode := SrcCodeSnippet(srcCodeLocation ?: podName.toUri, fantomPodCode)
            throw PlasticCompilationErr(srcCode, err.line ?: 1, err.msg, srcCodePadding)
        }
    }
}