id |
---|
scala-backend |
The Scala backend takes the Morphir IR as the input and returns an in-memory representation of files generated - FileMap The consumer is responsible for getting the input IR and saving the output to the file-system.
The transformation from the Morphir IR to the FileMap is based on the Scala AST.\
1. Reading the Input IR
2. Scala Code Generation
3. Writing Output to File System
The IR is saved on disk as JSON formatted file, morphir-ir.json. The IR (as Json) and command line option is passed to Elm through port:
worker.ports.generate.send([options, ir])
The file is received in the CLI.elm and decoded into a Decode Value.
targetOption =
Decode.decodeValue (field "target" string) optionsJson
optionsResult =
Decode.decodeValue (decodeOptions targetOption) optionsJson
packageDistroResult =
Decode.decodeValue DistributionCodec.decodeVersionedDistribution packageDistJson
The packageDistroResult is a Morphir.IR.Distribution type which is an in memory representation of the IR.
The distribution is passed to the Scala backend to generate a FileMap
fileMap =
mapDistribution options enrichedDistro
The code generation phase consists of functions that transform the distribution into a FileMap
\
The code generation consists of a number of mapping functions that map the Morphir IR types to Scala Types.
This is the entry point for the Scala backend. This function take Morphir IR (as a Distribution type) and generates the FileMap of Scala Source codes. A FileMap is a Morphir type and is a dictionary of File path and file content.
\
This function takes the Distribution, Package path and Package definition
and returns a FileMap.
This function maps through the modules in the package definition and for each module, it
generate a compilation unit for each module by calling the PrettyPrinter.mapCompilationUnit
which returns a compilation unit.
A compilation unit is a record type with the following fields
\
type alias CompilationUnit =
{ dirPath : List String
, fileName : String
, packageDecl : PackageDecl
, imports : List ImportDecl
, typeDecls : List (Documented (Annotated TypeDecl))
}
\
Takes a Morphir IR fully-qualified name and maps it to tuple of Scala path and name. A fully qualified name consists of packagPath, modulePath and localName.
\
Maps a Morphir IR fully-qualified name to Scala type reference. It extracts the path and name from the fully qualified name and uses the Scala.TypeRef constructor to create a Scala Reference type.
mapFQNameToTypeRef : FQName -> Scala.Type
mapFQNameToTypeRef fQName =
let
( path, name ) =
mapFQNameToPathAndName fQName
in
Scala.TypeRef path (name |> Name.toTitleCase)
\
This function maps a type declaration in Morphir to a Scala member declaration.
\
This function maps a module definition to a list of Scala compilation units.
\
Maps a custom type to a List of Scala member declaration
\
Maps a Morphir IR Type to a Scala type
\
Maps an IR value defintion to a Scala value.
\
Maps and IR Value type to a Scala value.
\
Maps an IR Pattern type to a Scala Pattern type
\
Maps an IR value name (List String) to a Scala value (String)
A set of Scala keywords that cannot be used as a variable name.
\
We cannot use any method names in java.lang.Object
because values are represented as functions/values in a Scala
object which implicitly inherits those methods which can result in name collisions.
\
\
The Scala backend returns a FileMap to the Typescript CLI. The fileMap returned from the backend is encoded into Json and send through the generateResult port.
...
...
fileMap =
mapDistribution options enrichedDistro
in
( model, fileMap
|> Ok
|> encodeResult Encode.string encodeFileMap
|> generateResult )
The generated FileMap is received in the JavaScript and parsed into a string.
const fileMap = await generate(opts, JSON.parse(morphirIrJson.toString()))
Finally, the returned files are written to disk. The complete gen() function is given below:
async function gen(input, outputPath, options) {
await mkdir(outputPath, {
recursive: true
})
const morphirIrJson = await readFile(path.resolve(input))
const opts = options
opts.limitToModules = options.modulesToInclude ? options.modulesToInclude.split(',') : null
const fileMap = await generate(opts, JSON.parse(morphirIrJson.toString()))
const writePromises =
fileMap.map(async ([
[dirPath, fileName], content
]) => {
const fileDir = dirPath.reduce((accum, next) => path.join(accum, next), outputPath)
const filePath = path.join(fileDir, fileName)
if (await fileExist(filePath)) {
console.log(`UPDATE - ${filePath}`)
} else {
await mkdir(fileDir, {
recursive: true
})
console.log(`INSERT - ${filePath}`)
}
if (options.target == 'TypeScript') {
return fsWriteFile(filePath, prettier.format(content, { parser: "typescript" }))
} else {
return fsWriteFile(filePath, content)
}
})
const filesToDelete = await findFilesToDelete(outputPath, fileMap)
const deletePromises =
filesToDelete.map(async (fileToDelete) => {
console.log(`DELETE - ${fileToDelete}`)
return fs.unlinkSync(fileToDelete)
})
copyRedistributables(options, outputPath)
return Promise.all(writePromises.concat(deletePromises))
}
Note: Generated files overwrite existing directory and contents