diff options
Diffstat (limited to 'compiler/src/dotty/tools/dotc/config/Settings.scala')
-rw-r--r-- | compiler/src/dotty/tools/dotc/config/Settings.scala | 270 |
1 files changed, 270 insertions, 0 deletions
diff --git a/compiler/src/dotty/tools/dotc/config/Settings.scala b/compiler/src/dotty/tools/dotc/config/Settings.scala new file mode 100644 index 000000000..cffa047fe --- /dev/null +++ b/compiler/src/dotty/tools/dotc/config/Settings.scala @@ -0,0 +1,270 @@ +package dotty.tools.dotc +package config + +import collection.mutable.{ ArrayBuffer } +import scala.util.{ Try, Success, Failure } +import scala.reflect.internal.util.StringOps +import reflect.ClassTag +import core.Contexts._ +// import annotation.unchecked + // Dotty deviation: Imports take precedence over definitions in enclosing package + // (Note that @unchecked is in scala, not annotation, so annotation.unchecked gives + // us a package, which is not what was intended anyway). +import language.existentials + +object Settings { + + val BooleanTag = ClassTag.Boolean + val IntTag = ClassTag.Int + val StringTag = ClassTag(classOf[String]) + val ListTag = ClassTag(classOf[List[_]]) + val VersionTag = ClassTag(classOf[ScalaVersion]) + val OptionTag = ClassTag(classOf[Option[_]]) + + class SettingsState(initialValues: Seq[Any]) { + private var values = ArrayBuffer(initialValues: _*) + private var _wasRead: Boolean = false + + override def toString = s"SettingsState(values: ${values.toList})" + + def value(idx: Int): Any = { + _wasRead = true + values(idx) + } + + def update(idx: Int, x: Any): SettingsState = + if (_wasRead) + new SettingsState(values).update(idx, x) + else { + values(idx) = x + this + } + } + + case class ArgsSummary( + sstate: SettingsState, + arguments: List[String], + errors: List[String]) { + + def fail(msg: String) = + ArgsSummary(sstate, arguments, errors :+ msg) + } + + case class Setting[T: ClassTag] private[Settings] ( + name: String, + description: String, + default: T, + helpArg: String = "", + choices: Seq[T] = Nil, + prefix: String = "", + aliases: List[String] = Nil, + depends: List[(Setting[_], Any)] = Nil, + propertyClass: Option[Class[_]] = None)(private[Settings] val idx: Int) { + + def withAbbreviation(abbrv: String): Setting[T] = + copy(aliases = aliases :+ abbrv)(idx) + + def dependsOn[U](setting: Setting[U], value: U): Setting[T] = + copy(depends = depends :+ (setting, value))(idx) + + def valueIn(state: SettingsState): T = + state.value(idx).asInstanceOf[T] + + def updateIn(state: SettingsState, x: Any): SettingsState = x match { + case _: T => state.update(idx, x) + case _ => + // would like to do: + // throw new ClassCastException(s"illegal argument, found: $x of type ${x.getClass}, required: ${implicitly[ClassTag[T]]}") + // but this runs afoul of primitive types. Concretely: if T is Boolean, then x is a boxed Boolean and the test will fail. + // Maybe this is a bug in Scala 2.10? + state.update(idx, x.asInstanceOf[T]) + } + + def isDefaultIn(state: SettingsState) = valueIn(state) == default + + def legalChoices: String = + if (choices.isEmpty) "" + else choices match { + case r: Range => r.head + ".." + r.last + case xs: List[_] => xs.mkString(", ") + } + + def isLegal(arg: Any): Boolean = + if (choices.isEmpty) + arg match { + case _: T => true + case _ => false + } + else choices match { + case r: Range => + arg match { + case x: Int => r.head <= x && x <= r.last + case _ => false + } + case xs: List[_] => + xs contains arg + } + + def tryToSet(state: ArgsSummary): ArgsSummary = { + val ArgsSummary(sstate, arg :: args, errors) = state + def update(value: Any, args: List[String]) = + ArgsSummary(updateIn(sstate, value), args, errors) + def fail(msg: String, args: List[String]) = + ArgsSummary(sstate, args, errors :+ msg) + def missingArg = + fail(s"missing argument for option $name", args) + def doSet(argRest: String) = ((implicitly[ClassTag[T]], args): @unchecked) match { + case (BooleanTag, _) => + update(true, args) + case (OptionTag, _) => + update(Some(propertyClass.get.newInstance), args) + case (ListTag, _) => + if (argRest.isEmpty) missingArg + else update((argRest split ",").toList, args) + case (StringTag, _) if choices.nonEmpty => + if (argRest.isEmpty) missingArg + else if (!choices.contains(argRest)) + fail(s"$arg is not a valid choice for $name", args) + else update(argRest, args) + case (StringTag, arg2 :: args2) => + update(arg2, args2) + case (IntTag, arg2 :: args2) => + try { + val x = arg2.toInt + choices match { + case r: Range if x < r.head || r.last < x => + fail(s"$arg2 is out of legal range $legalChoices for $name", args2) + case _ => + update(x, args2) + } + } catch { + case _: NumberFormatException => + fail(s"$arg2 is not an integer argument for $name", args2) + } + case (VersionTag, _) => + ScalaVersion.parse(argRest) match { + case Success(v) => update(v, args) + case Failure(ex) => fail(ex.getMessage, args) + } + case (_, Nil) => + missingArg + } + + if (prefix != "" && arg.startsWith(prefix)) + doSet(arg drop prefix.length) + else if (prefix == "" && name == arg.takeWhile(_ != ':')) + doSet(arg.dropWhile(_ != ':').drop(1)) + else + state + } + } + + object Setting { + implicit class SettingDecorator[T](val setting: Setting[T]) extends AnyVal { + def value(implicit ctx: Context): T = setting.valueIn(ctx.sstate) + def update(x: T)(implicit ctx: Context): SettingsState = setting.updateIn(ctx.sstate, x) + def isDefault(implicit ctx: Context): Boolean = setting.isDefaultIn(ctx.sstate) + } + } + + class SettingGroup { + + val _allSettings = new ArrayBuffer[Setting[_]] + def allSettings: Seq[Setting[_]] = _allSettings + + def defaultState = new SettingsState(allSettings map (_.default)) + + def userSetSettings(state: SettingsState) = + allSettings filterNot (_.isDefaultIn(state)) + + def toConciseString(state: SettingsState) = + userSetSettings(state).mkString("(", " ", ")") + + private def checkDependencies(state: ArgsSummary): ArgsSummary = + (state /: userSetSettings(state.sstate))(checkDependenciesOfSetting) + + private def checkDependenciesOfSetting(state: ArgsSummary, setting: Setting[_]) = + (state /: setting.depends) { (s, dep) => + val (depSetting, reqValue) = dep + if (depSetting.valueIn(state.sstate) == reqValue) s + else s.fail(s"incomplete option ${setting.name} (requires ${depSetting.name})") + } + + /** Iterates over the arguments applying them to settings where applicable. + * Then verifies setting dependencies are met. + * + * This temporarily takes a boolean indicating whether to keep + * processing if an argument is seen which is not a command line option. + * This is an expedience for the moment so that you can say + * + * scalac -d /tmp foo.scala -optimise + * + * while also allowing + * + * scala Program opt opt + * + * to get their arguments. + */ + protected def processArguments(state: ArgsSummary, processAll: Boolean, skipped: List[String]): ArgsSummary = { + def stateWithArgs(args: List[String]) = ArgsSummary(state.sstate, args, state.errors) + state.arguments match { + case Nil => + checkDependencies(stateWithArgs(skipped)) + case "--" :: args => + checkDependencies(stateWithArgs(skipped ++ args)) + case x :: _ if x startsWith "-" => + def loop(settings: List[Setting[_]]): ArgsSummary = settings match { + case setting :: settings1 => + val state1 = setting.tryToSet(state) + if (state1 ne state) processArguments(state1, processAll, skipped) + else loop(settings1) + case Nil => + state.fail(s"bad option: '$x'") + } + loop(allSettings.toList) + case arg :: args => + if (processAll) processArguments(stateWithArgs(args), processAll, skipped :+ arg) + else state + } + } + + def processArguments(arguments: List[String], processAll: Boolean)(implicit ctx: Context): ArgsSummary = + processArguments(ArgsSummary(ctx.sstate, arguments, Nil), processAll, Nil) + + def publish[T](settingf: Int => Setting[T]): Setting[T] = { + val setting = settingf(_allSettings.length) + _allSettings += setting + setting + } + + def BooleanSetting(name: String, descr: String, initialValue: Boolean = false): Setting[Boolean] = + publish(Setting(name, descr, initialValue)) + + def StringSetting(name: String, helpArg: String, descr: String, default: String): Setting[String] = + publish(Setting(name, descr, default, helpArg)) + + def ChoiceSetting(name: String, helpArg: String, descr: String, choices: List[String], default: String): Setting[String] = + publish(Setting(name, descr, default, helpArg, choices)) + + def IntSetting(name: String, descr: String, default: Int, range: Seq[Int] = Nil): Setting[Int] = + publish(Setting(name, descr, default, choices = range)) + + def MultiStringSetting(name: String, helpArg: String, descr: String): Setting[List[String]] = + publish(Setting(name, descr, Nil, helpArg)) + + def PathSetting(name: String, descr: String, default: String): Setting[String] = + publish(Setting(name, descr, default)) + + def PhasesSetting(name: String, descr: String, default: String = ""): Setting[List[String]] = + publish(Setting(name, descr, if (default.isEmpty) Nil else List(default))) + + def PrefixSetting(name: String, pre: String, descr: String): Setting[List[String]] = + publish(Setting(name, descr, Nil, prefix = pre)) + + def VersionSetting(name: String, descr: String, default: ScalaVersion = NoScalaVersion): Setting[ScalaVersion] = + publish(Setting(name, descr, default)) + + def OptionSetting[T: ClassTag](name: String, descr: String): Setting[Option[T]] = + publish(Setting(name, descr, None, propertyClass = Some(implicitly[ClassTag[T]].runtimeClass))) + } +} |