Implement input task helper Def.input

This commit is contained in:
Eugene Yokota 2022-09-19 01:33:16 -04:00
parent 441f56bf6e
commit 61d4fe2d30
5 changed files with 109 additions and 14 deletions

View File

@ -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) }

View File

@ -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

View File

@ -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

View File

@ -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

View File

@ -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