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