<commit_before>package org.ucombinator.jaam.main

import org.rogach.scallop._
import java.io._
import scala.collection.JavaConverters._
import org.ucombinator.jaam.util.Log

class MainConf(args : Seq[String]) extends ScallopConf(args = args) with JaamConf {
  shortSubcommandsHelp(true)
  // TODO: version: jvm version? Jaam-file header hash?

  banner("Usage: jaam-tools [subcommand] [options]")
  // TODO: short summary of each subcommand (w/ no options) in --help
  addSubcommand(Visualizer)
  addSubcommand(Interpreter)
  addSubcommand(Agent)
  addSubcommand(Cat)
  addSubcommand(App)
  addSubcommand(Coverage)
  addSubcommand(Coverage2)
  addSubcommand(Decompile)
  addSubcommand(FindMain)
  addSubcommand(Info)
  addSubcommand(ListItems)
  addSubcommand(Loop)
  addSubcommand(Loop2)
  addSubcommand(Loop3)
  addSubcommand(MissingReturns)
  addSubcommand(Print)
  addSubcommand(Taint)
  addSubcommand(Validate)
  addSubcommand(DecompileToFile)

  verify()
}

abstract class Main(name: String /* TODO: = Main.SubcommandName(getClass())*/) extends Subcommand(name) with JaamConf {
  // TODO: descr()
  def run(): Unit

  // Move to conf?
  val waitForUser = toggle(
    descrYes = "wait for user to press enter before starting (default: off)", // TODO: note: usefull for debugging and profiling
    noshort = true, prefix = "no-", default = Some(false))

  // Move to JaamConf
  val color = toggle(prefix = "no-", default = Some(true))

  val logLevel = enum[Log.Level](
    descr = "the level of logging verbosity",
    default = Some("warn"),
    argType = "log level",
    elems = scala.collection.immutable.ListMap(Log.levels.map(l => l.name -> l) :_*))
}

object Main {
  // TODO: support '-jar" with main-class
  def conf = _conf
  private var _conf: MainConf = _

  // short-subcommand help
  def main(args : Array[String]) {

    _conf = new MainConf(args)
    _conf.subcommand match {
      case None => println("ERROR: No subcommand specified")
      case Some(m : Main) =>
        if (m.waitForUser()) { // TODO: move to main.Main
          print("Press enter to start.")
          scala.io.StdIn.readLine()
        }

        Log.setLogging(m.logLevel()) // TODO: move to main.Main
        Log.color = m.color()


        m.run()
      case Some(other) => println("ERROR: Bad subcommand specified: " + other)
    }
  }

  def SubcommandName(o: Class[_]): String = {
    val name = o.getName().flatMap(c => if (c.isUpper) Seq('-', c.toLower) else Seq(c))
    name match {
      case s if s.startsWith("-") => s.stripPrefix("-")
      case s => s
    }
  }
}

/****************
 * Subcommands  *
 ****************/

object Visualizer extends Main("visualizer") {
  def run() {
    import javafx.application.Application

    Application.launch(classOf[org.ucombinator.jaam.visualizer.main.Main], Main.conf.args:_*)
  }
}

object Interpreter extends Main("interpreter") {
  // TODO: banner("")
  // TODO: how do we turn off sorting of options by name?
  import org.ucombinator.jaam.interpreter.AbstractState
  import scala.collection.immutable

  val classpath   = opt[String](required = true, short = 'P', descr = "the TODO class directory")
  val rtJar       = opt[String](required = true, short = 'J', descr = "the rt.jar file")
  val mainClass   = opt[String](required = true, short = 'c', descr = "the main class")
  val method      = opt[String](required = true, short = 'm', descr = "the main method", default = Some("main"))
  val libClasses  = opt[String](short = 'L', descr = "app's library classes")
  val _outfile     = opt[String](name = "outfile", short = 'o', descr = "the output file for the serialized data")
  def outfile() = _outfile.getOrElse(mainClass() + ".jaam") // TODO: extend scallop to do this for us

  val globalSnowflakeAddrLast = toggle(
    descrYes = "process states that read from the `GlobalSnowflakeAddr` last (default: on)",
    noshort = true, prefix = "no-", default = Some(true))

  val stateOrdering = enum[Boolean => Ordering[AbstractState]](
    default = Some("max"),
    argType = "ordering",
    elems = immutable.ListMap(
      "none" -> StateOrdering.none,
      "max" -> StateOrdering.max
        // TODO: state orderings: min insertion reverseInsertion
    ))

  val maxSteps = opt[Int](descr = "maximum number of interpretation steps")

  val stringTop = toggle(prefix = "no-", default = Some(true))

  val snowflakeLibrary = toggle(prefix = "no-", default = Some(true))

  val exceptions = toggle(prefix = "no-", default = Some(true))

  val initialStore = opt[java.io.File]()

  val analyzer = enum[Analyzer](
    descr = "which analyzer to use",
    default = Some("cha"),
    argType = "analyzer",
    elems = immutable.ListMap(
      "aam" -> AAM,
      "cha" -> CHA))
  sealed trait Analyzer {}
  final case object AAM extends Analyzer {}
  final case object CHA extends Analyzer {}

  object StateOrdering {
    def none(b: Boolean) = new Ordering[AbstractState] {
      override def compare(x: AbstractState, y: AbstractState): Int =
        0
    }

    def max(b: Boolean) = new Ordering[AbstractState] {
      override def compare(x: AbstractState, y: AbstractState): Int =
        // TODO: use `b`
        if (x.id < y.id) { -1 }
        else if (x.id == y.id) { 0 }
        else { +1 }
    }
  }

  def run() {
    analyzer() match { // TODO: as separate commands
      case AAM => org.ucombinator.jaam.interpreter.Main.aam()
      case CHA => org.ucombinator.jaam.interpreter.Main.cha()
    }
  }
}

object Agent extends Main("agent") {
  val arguments = trailArg[List[String]](default = Some(List()))

  def run() {
    var java = System.getProperty("java.home") + File.separator + "bin" + File.separator + "java"

    if (System.getProperty("os.name").toLowerCase().contains("win")) {
      java += ".exe"
    }

    val agentClass = classOf[org.ucombinator.jaam.agent.Main]
    val jar = agentClass.getProtectionDomain().getCodeSource().getLocation().toURI().getPath()

    val cmd = List(
      java,
      "-javaagent:" + jar,
      "-classpath", jar,
      agentClass.getCanonicalName()) ++ arguments()

    System.exit(new ProcessBuilder(cmd.asJava).inheritIO().start().waitFor())
  }
}

// TODO: agent is special because we have to launch a new process

object Cat extends Main("cat") {
  banner("Combine multile JAAM files into a single, cohesive file.")
  footer("")

  val input = inputOpt()
  val output = outputOpt()

  def run() {
    org.ucombinator.jaam.tools.cat.Cat.concatenateFiles(input(), output())
  }
}

object App extends Main("app") {
  banner("TODO")
  footer("")

  val input = opt[List[String]](descr = "class files, or directories (origin is auto-detected)", default = Some(List()))
  val app = opt[List[String]](short = 'a', descr = "application jars, class files, or directories", default = Some(List()))
  val lib = opt[List[String]](short = 'l', descr = "library jars, class files, or directories", default = Some(List()))
  val jvm = opt[List[String]](short = 'r', descr = "Java runtime jars, class files, or directories", default = Some(List()))
  val defaultJvm = toggle(prefix = "no-", default = Some(true))
<commit_msg>Cleanup comments in org.ucombinator.jaam.main.Main<commit_after>package org.ucombinator.jaam.main

import org.rogach.scallop._
import java.io._
import scala.collection.JavaConverters._
import org.ucombinator.jaam.util.Log

class MainConf(args : Seq[String]) extends ScallopConf(args = args) with JaamConf {
  shortSubcommandsHelp(true)
  // TODO: version: jvm version? Jaam-file header hash?

  banner("Usage: jaam-tools [subcommand] [options]")
  // TODO: short summary of each subcommand (w/ no options) in --help
  addSubcommand(Visualizer)
  addSubcommand(Interpreter)
  addSubcommand(Agent)
  addSubcommand(Cat)
  addSubcommand(App)
  addSubcommand(Coverage)
  addSubcommand(Coverage2)
  addSubcommand(Decompile)
  addSubcommand(FindMain)
  addSubcommand(Info)
  addSubcommand(ListItems)
  addSubcommand(Loop)
  addSubcommand(Loop2)
  addSubcommand(Loop3)
  addSubcommand(MissingReturns)
  addSubcommand(Print)
  addSubcommand(Taint)
  addSubcommand(Validate)
  addSubcommand(DecompileToFile)

  verify()
}

abstract class Main(name: String /* TODO: = Main.SubcommandName(getClass())*/) extends Subcommand(name) with JaamConf {
  // TODO: describe()
  def run(): Unit

  val waitForUser = toggle(
    descrYes = "wait for user to press enter before starting (default: off)", // TODO: note: usefull for debugging and profiling
    noshort = true, prefix = "no-", default = Some(false))

  val color = toggle(prefix = "no-", default = Some(true))

  val logLevel = enum[Log.Level](
    descr = "the level of logging verbosity",
    default = Some("warn"),
    argType = "log level",
    elems = scala.collection.immutable.ListMap(Log.levels.map(l => l.name -> l) :_*))
}

object Main {
  def conf = _conf
  private var _conf: MainConf = _

  // short-subcommand help
  def main(args : Array[String]) {

    _conf = new MainConf(args)
    _conf.subcommand match {
      case None => println("ERROR: No subcommand specified")
      case Some(m : Main) =>
        if (m.waitForUser()) {
          print("Press enter to start.")
          scala.io.StdIn.readLine()
        }

        Log.setLogging(m.logLevel())
        Log.color = m.color()

        m.run()
    }
  }

  def SubcommandName(o: Class[_]): String = {
    val name = o.getName().flatMap(c => if (c.isUpper) Seq('-', c.toLower) else Seq(c))
    name match {
      case s if s.startsWith("-") => s.stripPrefix("-")
      case s => s
    }
  }
}

/****************
 * Subcommands  *
 ****************/

object Visualizer extends Main("visualizer") {
  def run() {
    import javafx.application.Application

    Application.launch(classOf[org.ucombinator.jaam.visualizer.main.Main], Main.conf.args:_*)
  }
}

object Interpreter extends Main("interpreter") {
  // TODO: banner("")
  // TODO: how do we turn off sorting of options by name?
  import org.ucombinator.jaam.interpreter.AbstractState
  import scala.collection.immutable

  val classpath   = opt[String](required = true, short = 'P', descr = "the TODO class directory")
  val rtJar       = opt[String](required = true, short = 'J', descr = "the rt.jar file")
  val mainClass   = opt[String](required = true, short = 'c', descr = "the main class")
  val method      = opt[String](required = true, short = 'm', descr = "the main method", default = Some("main"))
  val libClasses  = opt[String](short = 'L', descr = "app's library classes")
  val _outfile     = opt[String](name = "outfile", short = 'o', descr = "the output file for the serialized data")
  def outfile() = _outfile.getOrElse(mainClass() + ".jaam") // TODO: extend scallop to do this for us

  val globalSnowflakeAddrLast = toggle(
    descrYes = "process states that read from the `GlobalSnowflakeAddr` last (default: on)",
    noshort = true, prefix = "no-", default = Some(true))

  val stateOrdering = enum[Boolean => Ordering[AbstractState]](
    default = Some("max"),
    argType = "ordering",
    elems = immutable.ListMap(
      "none" -> StateOrdering.none,
      "max" -> StateOrdering.max
        // TODO: state orderings: min insertion reverseInsertion
    ))

  val maxSteps = opt[Int](descr = "maximum number of interpretation steps")

  val stringTop = toggle(prefix = "no-", default = Some(true))

  val snowflakeLibrary = toggle(prefix = "no-", default = Some(true))

  val exceptions = toggle(prefix = "no-", default = Some(true))

  val initialStore = opt[java.io.File]()

  val analyzer = enum[Analyzer](
    descr = "which analyzer to use",
    default = Some("cha"),
    argType = "analyzer",
    elems = immutable.ListMap(
      "aam" -> AAM,
      "cha" -> CHA))
  sealed trait Analyzer {}
  final case object AAM extends Analyzer {}
  final case object CHA extends Analyzer {}

  object StateOrdering {
    def none(b: Boolean) = new Ordering[AbstractState] {
      override def compare(x: AbstractState, y: AbstractState): Int =
        0
    }

    def max(b: Boolean) = new Ordering[AbstractState] {
      override def compare(x: AbstractState, y: AbstractState): Int =
        // TODO: use `b`
        if (x.id < y.id) { -1 }
        else if (x.id == y.id) { 0 }
        else { +1 }
    }
  }

  def run() {
    analyzer() match { // TODO: as separate commands
      case AAM => org.ucombinator.jaam.interpreter.Main.aam()
      case CHA => org.ucombinator.jaam.interpreter.Main.cha()
    }
  }
}

object Agent extends Main("agent") {
  val arguments = trailArg[List[String]](default = Some(List()))

  def run() {
    // Agent launches a new process because we want to load only core JVM classes
    var java = System.getProperty("java.home") + File.separator + "bin" + File.separator + "java"

    if (System.getProperty("os.name").toLowerCase().contains("win")) {
      java += ".exe"
    }

    val agentClass = classOf[org.ucombinator.jaam.agent.Main]
    val jar = agentClass.getProtectionDomain().getCodeSource().getLocation().toURI().getPath()

    val cmd = List(
      java,
      "-javaagent:" + jar,
      "-classpath", jar,
      agentClass.getCanonicalName()) ++ arguments()

    System.exit(new ProcessBuilder(cmd.asJava).inheritIO().start().waitFor())
  }
}

object Cat extends Main("cat") {
  banner("Combine multile JAAM files into a single, cohesive file.")
  footer("")

  val input = inputOpt()
  val output = outputOpt()

  def run() {
    org.ucombinator.jaam.tools.cat.Cat.concatenateFiles(input(), output())
  }
}

object App extends Main("app") {
  banner("TODO")
  footer("")

  val input = opt[List[String]](descr = "class files, or directories (origin is auto-detected)", default = Some(List()))
  val app = opt[List[String]](short = 'a', descr = "application jars, class files, or directories", default = Some(List()))
  val lib = opt[List[String]](short = 'l', descr = "library jars, class files, or directories", default = Some(List()))
  val jvm = opt[List[String]](short = 'r', descr = "Java runtime jars, class files, or directories", default = Some(List()))
  val defaultJvm = toggle(prefix = "no-", default = Some(true))

{'fraction_non_alphanumeric': 0.14463981849120816, 'fraction_numerical': 0.0009926262053318207, 'mean_word_length': 3.4579646017699117, 'pattern_counts': {'":': 0, '<': 5, '<?xml version=': 0, '>': 32, 'https://': 0, 'lorem ipsum': 0, 'www.': 0, 'xml': 0}, 'pii_count': 0, 'substrings_counts': 0, 'word_list_counts': {'cursed_substrings.json': 0, 'profanity_word_list.json': 0, 'sexual_word_list.json': 0, 'zh_pornsignals.json': 0}}
{'dir': 'git-commits-cleaned', 'id': '5521369', 'n_tokens_mistral': 4622, 'n_tokens_neox': 4498, 'n_words': 1219}