Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Improving Command-Parsing in OPAL with Scallop-Library #226

Open
wants to merge 17 commits into
base: develop
Choose a base branch
from
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,7 @@ import java.util.Calendar
import com.typesafe.config.Config
import com.typesafe.config.ConfigFactory
import com.typesafe.config.ConfigValueFactory

import org.opalj.ai.Domain
import org.opalj.ai.domain.RecordDefUse
import org.opalj.ai.fpcf.properties.AIDomainFactoryKey
Expand Down Expand Up @@ -48,7 +49,7 @@ import org.opalj.br.fpcf.properties.cg.Callers
import org.opalj.br.fpcf.properties.cg.NoCallers
import org.opalj.bytecode.JRELibraryFolder
import org.opalj.collection.immutable.IntTrieSet
import org.opalj.commandlinebase.AnalysisCommand
import org.opalj.commandlinebase.AnalysisLevelCommand
import org.opalj.commandlinebase.AnalysisNameCommand
import org.opalj.commandlinebase.CallGraphCommand
import org.opalj.commandlinebase.ClassPathCommand
Expand All @@ -68,6 +69,7 @@ import org.opalj.commandlinebase.OpalConf
import org.opalj.commandlinebase.PackagesCommand
import org.opalj.commandlinebase.ProjectDirectoryCommand
import org.opalj.commandlinebase.RaterCommand
import org.opalj.commandlinebase.RunnerCommand
import org.opalj.commandlinebase.SchedulingStrategyCommand
import org.opalj.commandlinebase.ThreadsNumCommand
import org.opalj.fpcf.ComputationSpecification
Expand All @@ -77,7 +79,7 @@ import org.opalj.fpcf.PropertyStore
import org.opalj.fpcf.PropertyStoreContext
import org.opalj.fpcf.seq.PKESequentialPropertyStore
import org.opalj.log.LogContext
import org.opalj.support.parser.AnalysisCommandExternalParser
import org.opalj.support.parser.AnalysisCommandParser
import org.opalj.support.parser.CallGraphCommandExternalParser
import org.opalj.support.parser.ClassPathCommandExternalParser
import org.opalj.support.parser.DomainCommandExternalParser
Expand All @@ -104,6 +106,7 @@ import org.opalj.util.PerformanceEvaluation.time
import org.opalj.util.Seconds

import org.rogach.scallop.ScallopConf
import org.rogach.scallop.Subcommand

/**
* `PurityConf` is a configuration class for parsing and managing command-line arguments related to purity analysis
thanhtung24 marked this conversation as resolved.
Show resolved Hide resolved
Expand All @@ -113,11 +116,30 @@ import org.rogach.scallop.ScallopConf

class PurityConf(args: Array[String]) extends ScallopConf(args) with OpalConf {

private object analysis extends Subcommand("analysis") {
val runnerCommand = opt[String](
name = RunnerCommand.name,
descr = RunnerCommand.description,
argName = RunnerCommand.argName,
default = RunnerCommand.defaultValue,
noshort = RunnerCommand.noshort
)

val analysisLevelCommand = choice(
name = AnalysisLevelCommand.name,
descr = AnalysisLevelCommand.description,
argName = AnalysisLevelCommand.argName,
default = AnalysisLevelCommand.defaultValue,
noshort = AnalysisLevelCommand.noshort,
choices = AnalysisLevelCommand.choices
)
}
addSubcommand(analysis)

// Commands
private val classPathCommand = getPlainScallopOption(ClassPathCommand)
private val projectDirCommand = getPlainScallopOption(ProjectDirectoryCommand)
private val libDirCommand = getPlainScallopOption(LibraryDirectoryCommand)
private val analysisCommand = getChoiceScallopOption(AnalysisCommand)
private val fieldAssignabilityCommand = getChoiceScallopOption(FieldAssignabilityCommand)
private val escapeCommand = getChoiceScallopOption(EscapeCommand)
private val eagerCommand = getPlainScallopOption(EagerCommand)
Expand All @@ -142,12 +164,13 @@ class PurityConf(args: Array[String]) extends ScallopConf(args) with OpalConf {
val classPathFiles = parseCommandWithExternalParser(classPathCommand, ClassPathCommandExternalParser)
val projectDirectory = parseCommandWithInternalParser(projectDirCommand, ProjectDirectoryCommand)
val libraryDirectory = parseCommandWithInternalParser(libDirCommand, LibraryDirectoryCommand)
val analysisScheduler = parseCommandWithExternalParser(analysisCommand, AnalysisCommandExternalParser)
val analysisScheduler =
AnalysisCommandParser.parse(parseCommand(analysis.runnerCommand), parseCommand(analysis.analysisLevelCommand))
var support: Option[List[FPCFAnalysisScheduler]] = None

if (fieldAssignabilityCommand.isDefined && escapeCommand.isDefined && eagerCommand.isDefined && analysisScheduler != null)
support = Some(parseArgumentsForSupport(
analysisCommand.apply(),
analysis.analysisLevelCommand.apply(),
fieldAssignabilityCommand.apply(),
escapeCommand.apply(),
eagerCommand.apply(),
Expand Down Expand Up @@ -616,16 +639,12 @@ object Purity {

val purityConf = new PurityConf(args)

if (args.contains("--help") || args.contains("-h")) {
return
}

val begin = Calendar.getInstance()
Console.println(begin.getTime)

time {
if (purityConf.multiProjects.get) {
for (subp <- purityConf.classPathFiles.get.head.listFiles().filter(_.isDirectory)) {
for (subp <- purityConf.classPathFiles.get.flatMap(_.listFiles).filter(_.isDirectory)) {
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Instead of a flatMap, make this two iterations (see https://docs.scala-lang.org/tour/for-comprehensions.html for an example)

println(s"${subp.getName}: ${Calendar.getInstance().getTime}")
evaluate(
subp,
Expand Down Expand Up @@ -664,7 +683,7 @@ object Purity {
purityConf.individual.get,
purityConf.threadsNum.get,
purityConf.closedWorld.get,
purityConf.library.get || (purityConf.classPathFiles.head eq JRELibraryFolder),
purityConf.library.get || (purityConf.classPathFiles.get.head eq JRELibraryFolder),
purityConf.debug.get,
purityConf.evaluationDir,
purityConf.packages
Expand Down

This file was deleted.

Original file line number Diff line number Diff line change
@@ -0,0 +1,39 @@
/* BSD 2-Clause License - see OPAL/LICENSE for details. */
package org.opalj
package support
package parser

import org.opalj.br.fpcf.FPCFLazyAnalysisScheduler
import org.opalj.br.fpcf.analyses.LazyL0PurityAnalysis
import org.opalj.tac.fpcf.analyses.purity.LazyL1PurityAnalysis
import org.opalj.tac.fpcf.analyses.purity.LazyL2PurityAnalysis

/**
* The `AnalysisCommandParser` object is responsible for parsing the runner name and analysis level
* to return the corresponding analysis scheduler for purity analysis.
*
* This utility object matches the given runner name (such as "Purity") and analysis level
* (such as "L0", "L1", or "L2") to return the appropriate `FPCFLazyAnalysisScheduler` instance.
* If an invalid combination of runner name or analysis level is provided, an
* `IllegalArgumentException` is thrown.
*
* Example usage:
* {{{
* val scheduler = AnalysisCommandParser.parse("Purity", "L0")
* }}}
*
* @object AnalysisCommandParser
*/
object AnalysisCommandParser {
def parse(runnerName: Option[String], analysisLevel: Option[String]): Option[FPCFLazyAnalysisScheduler] =
runnerName.get match {
case "Purity" =>
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I don't think this is the right approach. One would have to change this class for every analysis that should be parsed as an analysis command. Instead, we would need a way to defer this parsing to an existing command, e.g. the FieldAssignabilityCommand, EscapeCommand, or, for the case that is now here, a PurityCommand.

analysisLevel.get match {
case "L0" => Some(LazyL0PurityAnalysis)
case "L1" => Some(LazyL1PurityAnalysis)
case null | "L2" => Some(LazyL2PurityAnalysis)
case _ => throw new IllegalArgumentException(s"Unknown analysis: $runnerName, $analysisLevel")
}
}

}
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@ package support
package parser

import java.io.File

import org.opalj.ba.CODE.logContext
import org.opalj.commandlinebase.OpalCommandExternalParser
import org.opalj.log.OPALLogger.error
Expand Down
Original file line number Diff line number Diff line change
@@ -1,9 +1,10 @@
/* BSD 2-Clause License - see OPAL/LICENSE for details. */
package org.opalj.commandlinebase
package org.opalj
package commandlinebase

object AnalysisCommand extends OpalChoiceCommand {
override var name: String = "analysis"
override var argName: String = "analysis"
object AnalysisLevelCommand extends OpalChoiceCommand {
override var name: String = "analysis level"
override var argName: String = "level"
override var description: String = "<L0|L1|L2> (Default: L2, the most precise analysis configuration)"
override var defaultValue: Option[String] = Some("L2")
override var noshort: Boolean = true
Expand Down
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
/* BSD 2-Clause License - see OPAL/LICENSE for details. */
package org.opalj.commandlinebase
package org.opalj
package commandlinebase

import java.util.Calendar

Expand Down
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
/* BSD 2-Clause License - see OPAL/LICENSE for details. */
package org.opalj.commandlinebase
package org.opalj
package commandlinebase

object CallGraphCommand extends OpalPlainCommand[String] {
override var name: String = "callGraph"
Expand Down
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
/* BSD 2-Clause License - see OPAL/LICENSE for details. */
package org.opalj.commandlinebase
package org.opalj
package commandlinebase

object ClassPathCommand extends OpalPlainCommand[String] {
override var name: String = "classPath"
Expand Down
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
/* BSD 2-Clause License - see OPAL/LICENSE for details. */
package org.opalj.commandlinebase
package org.opalj
package commandlinebase

object CloseWorldCommand extends OpalPlainCommand[Boolean] {
override var name: String = "closeWordAssumption"
Expand Down
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
/* BSD 2-Clause License - see OPAL/LICENSE for details. */
package org.opalj.commandlinebase
package org.opalj
package commandlinebase

object DebugCommand extends OpalPlainCommand[Boolean] {
override var name: String = "debug"
Expand Down
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
/* BSD 2-Clause License - see OPAL/LICENSE for details. */
package org.opalj.commandlinebase
package org.opalj
package commandlinebase

object DomainCommand extends OpalPlainCommand[String] {
override var name: String = "domain"
Expand Down
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
/* BSD 2-Clause License - see OPAL/LICENSE for details. */
package org.opalj.commandlinebase
package org.opalj
package commandlinebase

object EagerCommand extends OpalPlainCommand[Boolean] {
override var name: String = "eager"
Expand Down
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
/* BSD 2-Clause License - see OPAL/LICENSE for details. */
package org.opalj.commandlinebase
package org.opalj
package commandlinebase

object EscapeCommand extends OpalChoiceCommand {
override var name: String = "escape"
Expand Down
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
/* BSD 2-Clause License - see OPAL/LICENSE for details. */
package org.opalj.commandlinebase
package org.opalj
package commandlinebase

import java.io.File

Expand Down
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
/* BSD 2-Clause License - see OPAL/LICENSE for details. */
package org.opalj.commandlinebase
package org.opalj
package commandlinebase

object FieldAssignabilityCommand extends OpalChoiceCommand {
override var name: String = "fieldAssignability"
Expand Down
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
/* BSD 2-Clause License - see OPAL/LICENSE for details. */
package org.opalj.commandlinebase
package org.opalj
package commandlinebase

object IndividualCommand extends OpalPlainCommand[Boolean] {
override var name: String = "individual"
Expand Down
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
/* BSD 2-Clause License - see OPAL/LICENSE for details. */
package org.opalj.commandlinebase
package org.opalj
package commandlinebase

object JDKCommand extends OpalPlainCommand[Boolean] {
override var name: String = "noJDK"
Expand Down
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
/* BSD 2-Clause License - see OPAL/LICENSE for details. */
package org.opalj.commandlinebase
package org.opalj
package commandlinebase

object LibraryCommand extends OpalPlainCommand[Boolean] {
override var name: String = "library"
Expand Down
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
/* BSD 2-Clause License - see OPAL/LICENSE for details. */
package org.opalj.commandlinebase
package org.opalj
package commandlinebase

object LibraryDirectoryCommand extends OpalPlainCommand[String] {
override var name: String = "libDir"
Expand Down
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
/* BSD 2-Clause License - see OPAL/LICENSE for details. */
package org.opalj.commandlinebase
package org.opalj
package commandlinebase

object MultiProjectsCommand extends OpalPlainCommand[Boolean] {
override var name: String = "multiProjects"
Expand Down
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
/* BSD 2-Clause License - see OPAL/LICENSE for details. */
package org.opalj.commandlinebase
package org.opalj
package commandlinebase

trait OpalChoiceCommand extends OpalCommand {
var name: String
Expand Down
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
/* BSD 2-Clause License - see OPAL/LICENSE for details. */
package org.opalj.commandlinebase
package org.opalj
package commandlinebase

trait OpalCommand {
def parse[T](arg: T): Any
Expand Down
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
/* BSD 2-Clause License - see OPAL/LICENSE for details. */
package org.opalj.commandlinebase
package org.opalj
package commandlinebase

trait OpalCommandExternalParser[T, A] {
def parse(arg: T): Any
Expand Down
27 changes: 17 additions & 10 deletions OPAL/common/src/main/scala/org/opalj/commandlinebase/OpalConf.scala
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
/* BSD 2-Clause License - see OPAL/LICENSE for details. */
package org.opalj.commandlinebase
package org.opalj
package commandlinebase

import org.rogach.scallop.ScallopConf
import org.rogach.scallop.ScallopOption
Expand All @@ -24,14 +25,15 @@ trait OpalConf {
noshort = command.noshort
)

def getChoiceScallopOption(command: OpalChoiceCommand): ScallopOption[String] = choice(
name = command.name,
argName = command.argName,
descr = command.description,
default = command.defaultValue,
noshort = command.noshort,
choices = command.choices
)
def getChoiceScallopOption(command: OpalChoiceCommand): ScallopOption[String] =
choice(
name = command.name,
argName = command.argName,
descr = command.description,
default = command.defaultValue,
noshort = command.noshort,
choices = command.choices
)

def parseCommand[T](command: ScallopOption[T]): Option[T] = if (!command.isDefined) None else Some(command.apply())

Expand All @@ -44,7 +46,12 @@ trait OpalConf {
): Option[R] = if (!command.isDefined) None else Some(externalParser.parse(command.apply()).asInstanceOf[R])

override def onError(e: Throwable): Unit = e match {
case Help("") => printHelp()
case Help("") =>
printHelp()
sys.exit(0)
case Help(_) =>
subcommand.foreach(_.printHelp())
sys.exit(0)
case ScallopException(message) =>
println(s"Error: $message")
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

You should still use the OPALLogger facilities to print errors.

printHelp()
Expand Down
Loading