I managed to (almost) compile your auto-generated sources. The only what can defeat a code generator is another code generator :)
This code is auto-generated and so it's not possible to split it up into multiple methods.
I guess this is not completely true. Just it's better to split the code automatically.
I used Scalameta. Your method visit_1_1_0
of class M_SEMANT
has many branching if (...) ... else ...
(as well as the rest of your auto-generated code). I transform a branch if ($condExpr) $thenExpr else $elseExpr
into
def $condName(): Boolean = $condExpr
def $thenName() = $thenExpr
def $elseName() = $elseExpr
if ($condName()) $thenName() else $elseName()
so that if
, then
, and else
become nested methods. Upon compilation, nested methods are moved to the class level
The cost of nested methods
You can apply this transformation recursively, for all if-else
(then you should uncomment lines in ScalametaTransformer
) but it turned out that it was enough to re-write the outermost if-else
in visit_1_1_0
. Obtained methods seem to be under JVM limitation.
This is the result of sbt transformed/clean transform transformed/compile
matches M_SEMANT
matches template
matches visit_1_1_0
matches if-else
[error] /media/data/Projects1/cool-aps-semant1/cool-aps-semant/transformed/target/scala-2.13/src_managed/main/cool-semant.scala:31:15: type mismatch;
[error] found : Unit
[error] required: Int
[error] if (!cond) {
[error] ^
[error] /media/data/Projects1/cool-aps-semant1/cool-aps-semant/transformed/target/scala-2.13/src_managed/main/cool-semant.scala:25:11: type mismatch;
[error] found : Unit
[error] required: Int
[error] if (!cond) {
[error] ^
[error] two errors found
[error] (transformed / Compile / compileIncremental) Compilation failed
Since those two places are the same in your original code, I guess those compile errors were present there. But we don't have Method too large
any more.
Sources before transform are in src/main/scala
, sources after transform are in transformed/target/scala-2.13/src_managed/main
.
project/build.sbt
libraryDependencies ++= Seq(
"org.scalameta" %% "scalameta" % "4.7.7"
)
build.sbt
ThisBuild / scalaVersion := "2.13.10"
lazy val root = (project in file("."))
.settings(
name := "aps-cool"
)
lazy val transformed = project
.settings(
Compile / unmanagedSourceDirectories += (Compile / sourceManaged).value
)
lazy val transform = taskKey[Unit]("Transform sources")
transform := {
val inputDir = (root / Compile / scalaSource).value
val outputDir = (transformed / Compile / sourceManaged).value
Generator.gen(inputDir, outputDir, Seq("cool-semant.scala"), Map("cool-semant.scala" -> Seq(960)))
}
project/Generator.scala
import sbt.*
object Generator {
def gen(inputDir: File, outputDir: File, filesToTransform: Seq[String] = Seq(), emptyLineIndices: Map[String, Seq[Int]] = Map()): Unit = {
val finder: PathFinder = inputDir ** "*.scala"
for (inputFile <- finder.get) yield {
val inputStr = IO.read(inputFile)
val outputFile = outputDir / inputFile.relativeTo(inputDir).get.toString
val outputStr =
if (filesToTransform.isEmpty || filesToTransform.contains(inputFile.name))
ScalametaTransformer.transform(inputStr, emptyLineIndices.getOrElse(inputFile.name, Seq()))
else inputStr
IO.write(outputFile, outputStr)
}
}
}
project/ScalametaTransformer.scala
import scala.meta.*
import scala.util.Properties
object ScalametaTransformer {
private val ifElseTransformer = new Transformer {
override def apply(tree: Tree): Tree = tree match {
case q"if ($condExpr) $thenExpr else $elseExpr" =>
println(s"matches if-else")
val condName = Term.fresh("cond")
val thenName = Term.fresh("then")
val elseName = Term.fresh("else")
q"""
def $condName(): Boolean = $condExpr
def $thenName() = $thenExpr
def $elseName() = $elseExpr
if ($condName()) $thenName() else $elseName()
"""
// apply recursively:
// val condExpr1 = super.apply(condExpr).asInstanceOf[Term]
// val thenExpr1 = super.apply(thenExpr).asInstanceOf[Term]
// val elseExpr1 = super.apply(elseExpr).asInstanceOf[Term]
// q"""
// def $condName() = $condExpr1
// def $thenName() = $thenExpr1
// def $elseName() = $elseExpr1
// if ($condName()) $thenName() else $elseName()
// """
case _ => super.apply(tree)
}
}
private val classMethodTransformer = new Transformer {
override def apply(tree: Tree): Tree = tree match {
case q"..$mods class M_SEMANT[..$tparams] ..$ctorMods (...$paramss) $template" =>
println("matches M_SEMANT")
val template1 = template match {
case template"{ ..$earlyStats } with ..$inits { $self => ..$stats }" =>
println("matches template")
val stats1 = stats.map {
case q"..$mods def visit_1_1_0[..$tparams](...$paramss): $tpeopt = $expr" =>
println("matches visit_1_1_0")
val expr1 = ifElseTransformer(expr).asInstanceOf[Term]
q"..$mods def visit_1_1_0[..$tparams](...$paramss): $tpeopt = $expr1"
case t => t
}
template"{ ..$earlyStats } with ..$inits { $self => ..$stats1 }"
}
q"..$mods class M_SEMANT[..$tparams] ..$ctorMods (...$paramss) $template1"
case _ => super.apply(tree)
}
}
def transform(str: String, emptyLineIndices: Seq[Int] = Seq()): String = {
val origTree = str.parse[Source].get
val newTree = classMethodTransformer(origTree)
insertEmptyLines(newTree.toString, emptyLineIndices)
}
private def insertEmptyLines(str: String, indices: Seq[Int]): String =
indices.foldLeft(str)(insertEmptyLine)
private def insertEmptyLine(str: String, index: Int): String =
str.linesIterator.patch(index, Iterator.fill(1)(""), 0).mkString(Properties.lineSeparator)
}
https://github.com/amir734jj/cool-aps-semant/pull/1
Inserting an empty line is a workaround for https://github.com/scalameta/scalameta/issues/2046