From 61d4fe2d30c8d4452994af43e426ba3db423b329 Mon Sep 17 00:00:00 2001 From: Eugene Yokota Date: Mon, 19 Sep 2022 01:33:16 -0400 Subject: [PATCH] Implement input task helper Def.input --- main-settings/src/main/scala/sbt/Def.scala | 3 + .../src/main/scala/sbt/InputTask.scala | 32 ++++++++- .../scala/sbt/std/InputTaskMacro.scala.scala | 66 +++++++++++++++++++ .../src/main/scala/sbt/std/SettingMacro.scala | 19 +++--- main/src/main/scala/sbt/Defaults.scala | 3 +- 5 files changed, 109 insertions(+), 14 deletions(-) diff --git a/main-settings/src/main/scala/sbt/Def.scala b/main-settings/src/main/scala/sbt/Def.scala index 98cf3b27b..4decefee3 100644 --- a/main-settings/src/main/scala/sbt/Def.scala +++ b/main-settings/src/main/scala/sbt/Def.scala @@ -234,6 +234,9 @@ object Def extends Init[Scope] with TaskMacroExtra with InitializeImplicits: // def settingDyn[T](t: Def.Initialize[T]): Def.Initialize[T] = macro settingDynMacroImpl[T] + inline def input[A1](inline p: State => Parser[A1]): ParserGen[A1] = + ${ SettingMacro.inputMacroImpl[A1]('p) } + inline def inputTask[A1](inline a: A1): Def.Initialize[InputTask[A1]] = ${ InputTaskMacro.inputTaskMacroImpl[A1]('a) } diff --git a/main-settings/src/main/scala/sbt/InputTask.scala b/main-settings/src/main/scala/sbt/InputTask.scala index 89623e2a8..720b31d1c 100644 --- a/main-settings/src/main/scala/sbt/InputTask.scala +++ b/main-settings/src/main/scala/sbt/InputTask.scala @@ -13,6 +13,7 @@ import std.TaskExtra._ import sbt.internal.util.{ ~>, AttributeKey, Types } import sbt.internal.util.Types._ import sbt.internal.util.Util._ +import sbt.util.Applicative /** Parses input and produces a task to run. Constructed using the companion object. */ final class InputTask[A1] private (val parser: State => Parser[Task[A1]]): @@ -86,13 +87,14 @@ object InputTask: )(action: Initialize[A1 => Task[A2]]): Initialize[InputTask[A2]] = p.zipWith(action)((parser, act) => free(parser)(act)) - /* /** Constructs an InputTask that accepts no user input. */ def createFree[T](action: Initialize[Task[T]]): Initialize[InputTask[T]] = action { tsk => free(emptyParser)(const(tsk)) } - */ + + def createFreeFromAction[A1](a: () => A1): InputTask[A1] = + free(emptyParser)(_ => Task.taskMonad.pure(a)) /** * Constructs an InputTask from: @@ -104,11 +106,11 @@ object InputTask: )(action: Initialize[Task[A1 => Initialize[Task[A2]]]]): Initialize[InputTask[A2]] = separate(p)(std.FullInstance.flattenFun[A1, A2](action)) - /* /** A dummy parser that consumes no input and produces nothing useful (unit). */ def emptyParser: State => Parser[Unit] = Types.const(sbt.internal.util.complete.DefaultParsers.success(())) + /* /** Implementation detail that is public because it is used by a macro. */ def parserAsInput[T](p: Parser[T]): Initialize[State => Parser[T]] = Def.valueStrict(Types.const(p)) @@ -181,4 +183,28 @@ object InputTask: f(task) } */ + + given inputTaskApplicative: Applicative[InputTask] with + type F[a] = InputTask[a] + override def pure[A1](a: () => A1): InputTask[A1] = InputTask.createFreeFromAction(a) + override def ap[A1, A2](ff: InputTask[A1 => A2])(in: InputTask[A1]): InputTask[A2] = + InputTask[A2]((s: State) => + (in.parser(s) ~ ff.parser(s)).map { case (ta1, tf) => + Task.taskMonad.ap(tf)(ta1) + } + ) + override def map[A1, A2](in: InputTask[A1])(f: A1 => A2): InputTask[A2] = + InputTask[A2]((s: State) => + in.parser(s).map { ta1 => + ta1.map(f) + } + ) end InputTask + +class ParserGen[A1](val p: Initialize[State => Parser[A1]]): + inline def mapTask[A2](inline action: A1 => A2): Initialize[InputTask[A2]] = + ${ std.InputTaskMacro.parserGenInputTaskMacroImpl[A1, A2]('this, 'action) } + + inline def flatMapTask[A2](inline action: A1 => Initialize[Task[A2]]): Initialize[InputTask[A2]] = + ${ std.InputTaskMacro.parserGenFlatMapTaskImpl[A1, A2]('this, 'action) } +end ParserGen diff --git a/main-settings/src/main/scala/sbt/std/InputTaskMacro.scala.scala b/main-settings/src/main/scala/sbt/std/InputTaskMacro.scala.scala index caa8dc123..138783e9b 100644 --- a/main-settings/src/main/scala/sbt/std/InputTaskMacro.scala.scala +++ b/main-settings/src/main/scala/sbt/std/InputTaskMacro.scala.scala @@ -195,4 +195,70 @@ object InputTaskMacro: } } */ + + def parserGenInputTaskMacroImpl[A1: Type, A2: Type]( + parserGen: Expr[ParserGen[A1]], + tree: Expr[A1 => A2] + )(using + qctx: Quotes + ): Expr[Def.Initialize[InputTask[A2]]] = + inputTaskMacro0[A2]('{ + val `arg$` = $parserGen.p.parsed + $tree(`arg$`) + }) + + def parserGenFlatMapTaskImpl[A1: Type, A2: Type]( + parserGen: Expr[ParserGen[A1]], + tree: Expr[A1 => Def.Initialize[Task[A2]]] + )(using + qctx: Quotes + ): Expr[Def.Initialize[InputTask[A2]]] = + import qctx.reflect.* + val convert1 = new FullConvert(qctx) // 1000 + import convert1.Converted + def mkInputTask(params: List[ValDef], body: Term): Expr[Def.Initialize[InputTask[A2]]] = + val lambdaTpe = + MethodType(params.map(_.name))( + _ => List(TypeRepr.of[A1]), + _ => TypeRepr.of[Def.Initialize[Task[A2]]] + ) + val lambda = Lambda( + owner = Symbol.spliceOwner, + tpe = lambdaTpe, + rhsFn = (sym, params) => { + val p0 = params.head.asInstanceOf[Ident] + val body2 = + convert1 + .contFlatMap[A2, TaskMacro.F, Id](body.asExprOf[TaskMacro.F[A2]], convert1.appExpr) + .asTerm + object refTransformer extends TreeMap: + override def transformTerm(tree: Term)(owner: Symbol): Term = + tree match + case Ident(name) if name == p0.name => Ref(p0.symbol) + case _ => super.transformTerm(tree)(owner) + end refTransformer + refTransformer.transformTerm(body2.changeOwner(sym))(sym) + } + ) + val action = lambda.asExprOf[A1 => Def.Initialize[Task[A2]]] + '{ + InputTask.createDyn[A1, A2](${ parserGen }.p)( + Def.valueStrict(TaskExtra.task[A1 => Def.Initialize[Task[A2]]]($action)) + ) + } + tree.asTerm match + case Lambda(params, body) => + mkInputTask(params, body) + case Inlined( + _, + _, + Lambda(params, body), + ) => + mkInputTask(params, body) + case Inlined( + _, + _, + Block(List(), Lambda(params, body)), + ) => + mkInputTask(params, body) end InputTaskMacro diff --git a/main-settings/src/main/scala/sbt/std/SettingMacro.scala b/main-settings/src/main/scala/sbt/std/SettingMacro.scala index 32c29bb35..0b286182f 100644 --- a/main-settings/src/main/scala/sbt/std/SettingMacro.scala +++ b/main-settings/src/main/scala/sbt/std/SettingMacro.scala @@ -18,6 +18,7 @@ import sbt.internal.util.appmacro.{ } import sbt.util.Applicative import scala.quoted.* +import sbt.internal.util.complete.Parser class InitializeConvert[C <: Quotes & scala.Singleton](override val qctx: C) extends Convert[C](qctx) @@ -48,14 +49,14 @@ object SettingMacro: val convert1 = InitializeConvert(qctx) convert1.contMapN[A1, F, Id](in, convert1.appExpr) -/* - def settingDynMacroImpl[T: c.WeakTypeTag]( - c: blackbox.Context - )(t: c.Expr[Initialize[T]]): c.Expr[Initialize[T]] = - Instance.contImpl[T, Id](c, InitializeInstance, InitializeConvert, MixedBuilder, EmptyLinter)( - Right(t), - Instance.idTransform[c.type] - ) - */ + def settingDynImpl[A1: Type](in: Expr[Initialize[A1]])(using qctx: Quotes): Expr[Initialize[A1]] = + val convert1 = InitializeConvert(qctx) + convert1.contFlatMap[A1, F, Id](in, convert1.appExpr) + def inputMacroImpl[A1: Type](in: Expr[State => Parser[A1]])(using + qctx: Quotes + ): Expr[ParserGen[A1]] = + val convert1 = InitializeConvert(qctx) + val init1 = convert1.contMapN[State => Parser[A1], F, Id](in, convert1.appExpr) + '{ ParserGen[A1]($init1) } end SettingMacro diff --git a/main/src/main/scala/sbt/Defaults.scala b/main/src/main/scala/sbt/Defaults.scala index 6f8836056..51af5c84e 100644 --- a/main/src/main/scala/sbt/Defaults.scala +++ b/main/src/main/scala/sbt/Defaults.scala @@ -1439,8 +1439,7 @@ object Defaults extends BuildCommon { private[this] lazy val inputTests0: Initialize[InputTask[Unit]] = { val parser = loadForParser(definedTestNames)((s, i) => testOnlyParser(s, i getOrElse Nil)) - Def.inputTaskDyn { - val (selected, frameworkOptions) = parser.parsed + ParserGen(parser).flatMapTask { case ((selected, frameworkOptions)) => val s = streams.value val filter = testFilter.value val config = testExecution.value