Skip to content

Commit fd8d60b

Browse files
Merge pull request #1 from alexarchambault/scala-2-support
Cross-compile for Scala 2
2 parents a52e5be + f237e2e commit fd8d60b

19 files changed

+998
-361
lines changed
Lines changed: 18 additions & 342 deletions
Original file line numberDiff line numberDiff line change
@@ -1,12 +1,6 @@
11
package scala.cli.directivehandler
22

3-
import com.virtuslab.using_directives.custom.model.{EmptyValue, Value}
4-
5-
import java.util.Locale
6-
73
import scala.cli.directivehandler.EitherSequence._
8-
import scala.deriving.*
9-
import scala.quoted.{_, given}
104

115
trait DirectiveHandler[+T] { self =>
126
def name: String
@@ -23,6 +17,23 @@ trait DirectiveHandler[+T] { self =>
2317
scopedDirective: ScopedDirective
2418
): Either[DirectiveException, ProcessedDirective[T]]
2519

20+
final def parse(
21+
input: String,
22+
path: Either[String, os.Path],
23+
scopePath: ScopePath
24+
): Either[DirectiveException, Seq[ProcessedDirective[T]]] = {
25+
26+
val directives = ExtractedDirectives.from(input.toCharArray, path)
27+
.fold(e => throw e, identity)
28+
.directives
29+
.map(dir => ScopedDirective(dir, path, scopePath))
30+
31+
directives
32+
.map(handleValues)
33+
.sequence
34+
.left.map(CompositeDirectiveException(_))
35+
}
36+
2637
def map[U](f: T => U): DirectiveHandler[U] =
2738
new DirectiveHandler[U] {
2839
def name = self.name
@@ -42,7 +53,7 @@ trait DirectiveHandler[+T] { self =>
4253

4354
}
4455

45-
object DirectiveHandler {
56+
object DirectiveHandler extends DirectiveHandlerMacros {
4657

4758
// from https://github.com/alexarchambault/case-app/blob/7ac9ae7cc6765df48eab27c4e35c66b00e4469a7/core/shared/src/main/scala/caseapp/core/util/CaseUtil.scala#L5-L22
4859
def pascalCaseSplit(s: List[Char]): List[String] =
@@ -69,339 +80,4 @@ object DirectiveHandler {
6980
(elems.head +: elems.tail.map(_.capitalize)).mkString
7081
}
7182

72-
private def fields[U](using
73-
q: Quotes,
74-
t: Type[U]
75-
): List[(q.reflect.Symbol, q.reflect.TypeRepr)] = {
76-
import quotes.reflect.*
77-
val tpe = TypeRepr.of[U]
78-
val sym = TypeRepr.of[U] match {
79-
case AppliedType(base, params) =>
80-
base.typeSymbol
81-
case _ =>
82-
TypeTree.of[U].symbol
83-
}
84-
85-
// Many things inspired by https://github.com/plokhotnyuk/jsoniter-scala/blob/8f39e1d45fde2a04984498f036cad93286344c30/jsoniter-scala-macros/shared/src/main/scala-3/com/github/plokhotnyuk/jsoniter_scala/macros/JsonCodecMaker.scala#L564-L613
86-
// and around, here
87-
88-
def typeArgs(tpe: TypeRepr): List[TypeRepr] = tpe match
89-
case AppliedType(_, typeArgs) => typeArgs.map(_.dealias)
90-
case _ => Nil
91-
92-
def resolveParentTypeArg(
93-
child: Symbol,
94-
fromNudeChildTarg: TypeRepr,
95-
parentTarg: TypeRepr,
96-
binding: Map[String, TypeRepr]
97-
): Map[String, TypeRepr] =
98-
if (fromNudeChildTarg.typeSymbol.isTypeParam) { // todo: check for paramRef instead ?
99-
val paramName = fromNudeChildTarg.typeSymbol.name
100-
binding.get(paramName) match
101-
case None => binding.updated(paramName, parentTarg)
102-
case Some(oldBinding) =>
103-
if (oldBinding =:= parentTarg) binding
104-
else sys.error(
105-
s"Type parameter $paramName in class ${child.name} appeared in the constructor of " +
106-
s"${tpe.show} two times differently, with ${oldBinding.show} and ${parentTarg.show}"
107-
)
108-
}
109-
else if (fromNudeChildTarg <:< parentTarg)
110-
binding // TODO: assupe parentTag is covariant, get covariance from tycon type parameters.
111-
else
112-
(fromNudeChildTarg, parentTarg) match
113-
case (AppliedType(ctycon, ctargs), AppliedType(ptycon, ptargs)) =>
114-
ctargs.zip(ptargs).foldLeft(resolveParentTypeArg(child, ctycon, ptycon, binding)) {
115-
(b, e) =>
116-
resolveParentTypeArg(child, e._1, e._2, b)
117-
}
118-
case _ =>
119-
sys.error(s"Failed unification of type parameters of ${tpe.show} from child $child - " +
120-
s"${fromNudeChildTarg.show} and ${parentTarg.show}")
121-
122-
def resolveParentTypeArgs(
123-
child: Symbol,
124-
nudeChildParentTags: List[TypeRepr],
125-
parentTags: List[TypeRepr],
126-
binding: Map[String, TypeRepr]
127-
): Map[String, TypeRepr] =
128-
nudeChildParentTags.zip(parentTags).foldLeft(binding)((s, e) =>
129-
resolveParentTypeArg(child, e._1, e._2, s)
130-
)
131-
132-
val nudeSubtype = TypeIdent(sym).tpe
133-
val baseConst = nudeSubtype.memberType(sym.primaryConstructor)
134-
val tpeArgsFromChild = typeArgs(tpe)
135-
val const = baseConst match {
136-
case MethodType(_, _, resTp) => resTp
137-
case PolyType(names, _, resPolyTp) =>
138-
val targs = typeArgs(tpe)
139-
val tpBinding = resolveParentTypeArgs(sym, tpeArgsFromChild, targs, Map.empty)
140-
val ctArgs = names.map { name =>
141-
tpBinding.get(name).getOrElse(sys.error(
142-
s"Type parameter $name of $sym can't be deduced from " +
143-
s"type arguments of ${tpe.show}. Please provide a custom implicitly accessible codec for it."
144-
))
145-
}
146-
val polyRes = resPolyTp match
147-
case MethodType(_, _, resTp) => resTp
148-
case other => other // hope we have no multiple typed param lists yet.
149-
if (ctArgs.isEmpty) polyRes
150-
else polyRes match
151-
case AppliedType(base, _) => base.appliedTo(ctArgs)
152-
case AnnotatedType(AppliedType(base, _), annot) =>
153-
AnnotatedType(base.appliedTo(ctArgs), annot)
154-
case _ => polyRes.appliedTo(ctArgs)
155-
case other =>
156-
sys.error(s"Primary constructor for ${tpe.show} is not MethodType or PolyType but $other")
157-
}
158-
sym.primaryConstructor
159-
.paramSymss
160-
.flatten
161-
.map(f => (f, f.tree))
162-
.collect {
163-
case (sym, v: ValDef) =>
164-
(sym, v.tpt.tpe)
165-
}
166-
}
167-
168-
def shortName[T](using Quotes, Type[T]): String = {
169-
val fullName = Type.show[T]
170-
// attempt at getting a simple name out of fullName (this is likely broken)
171-
fullName.takeWhile(_ != '[').split('.').last
172-
}
173-
174-
inline private def deriveParser[T]: DirectiveHandler[T] =
175-
${ deriveParserImpl[T] }
176-
private def deriveParserImpl[T](using q: Quotes, t: Type[T]): Expr[DirectiveHandler[T]] = {
177-
import quotes.reflect.*
178-
val tSym = TypeTree.of[T].symbol
179-
val origin = shortName[T]
180-
val fields0 = fields[T]
181-
182-
val defaultMap: Map[String, Expr[Any]] = {
183-
val comp =
184-
if (tSym.isClassDef && !tSym.companionClass.isNoSymbol) tSym.companionClass
185-
else tSym
186-
val bodyOpt = Some(comp)
187-
.filter(!_.isNoSymbol)
188-
.map(_.tree)
189-
.collect {
190-
case cd: ClassDef => cd.body
191-
}
192-
bodyOpt match {
193-
case Some(body) =>
194-
val names = fields0
195-
.map(_._1)
196-
.filter(_.flags.is(Flags.HasDefault))
197-
.map(_.name)
198-
val values = body.collect {
199-
case d @ DefDef(name, _, _, _) if name.startsWith("$lessinit$greater$default") =>
200-
Ref(d.symbol).asExpr
201-
}
202-
names.zip(values).toMap
203-
case None =>
204-
Map.empty
205-
}
206-
}
207-
208-
val nameValue = tSym.annotations
209-
.find(_.tpe =:= TypeRepr.of[DirectiveGroupName])
210-
.collect {
211-
case Apply(_, List(arg)) =>
212-
arg.asExprOf[String]
213-
}
214-
.getOrElse {
215-
Expr(shortName[T].stripSuffix("Directives"))
216-
}
217-
218-
val (usageValue, usageMdValue) = tSym.annotations
219-
.find(_.tpe =:= TypeRepr.of[DirectiveUsage])
220-
.collect {
221-
case Apply(_, List(arg)) =>
222-
(arg.asExprOf[String], Expr(""))
223-
case Apply(_, List(arg, argMd)) =>
224-
(arg.asExprOf[String], argMd.asExprOf[String])
225-
}
226-
.getOrElse {
227-
sys.error(s"Missing DirectiveUsage directive on ${Type.show[T]}")
228-
}
229-
230-
val (descriptionValue, descriptionMdValue) = tSym.annotations
231-
.find(_.tpe =:= TypeRepr.of[DirectiveDescription])
232-
.collect {
233-
case Apply(_, List(arg)) =>
234-
(arg.asExprOf[String], Expr(""))
235-
case Apply(_, List(arg, argMd)) =>
236-
(arg.asExprOf[String], argMd.asExprOf[String])
237-
}
238-
.getOrElse {
239-
sys.error(s"Missing DirectiveDescription directive on ${Type.show[T]}")
240-
}
241-
242-
val prefixValueOpt = tSym.annotations
243-
.find(_.tpe =:= TypeRepr.of[DirectivePrefix])
244-
.collect {
245-
case Apply(_, List(arg)) =>
246-
arg.asExprOf[String]
247-
}
248-
def withPrefix(name: Expr[String]): Expr[String] =
249-
prefixValueOpt match {
250-
case None => name
251-
case Some(prefixValue) => '{ $prefixValue + $name }
252-
}
253-
254-
val examplesValue = tSym.annotations
255-
.filter(_.tpe =:= TypeRepr.of[DirectiveExamples])
256-
.collect {
257-
case Apply(_, List(arg)) =>
258-
arg.asExprOf[String]
259-
}
260-
.reverse // not sure in what extent we can rely on the ordering here…
261-
262-
val tagsValue = tSym.annotations
263-
.filter(_.tpe =:= TypeRepr.of[DirectiveTag])
264-
.collect {
265-
case Apply(_, List(arg)) =>
266-
arg.asExprOf[String]
267-
}
268-
.reverse // not sure in what extent we can rely on the ordering here…
269-
270-
def namesFromAnnotations(sym: Symbol) = sym.annotations
271-
.filter(_.tpe =:= TypeRepr.of[DirectiveName])
272-
.collect {
273-
case Apply(_, List(arg)) =>
274-
withPrefix(arg.asExprOf[String])
275-
}
276-
277-
val keysValue = Expr.ofList {
278-
fields0.flatMap {
279-
case (sym, _) =>
280-
withPrefix(Expr(sym.name)) +: namesFromAnnotations(sym)
281-
}
282-
}
283-
284-
val elseCase: (
285-
Expr[ScopedDirective]
286-
) => Expr[Either[DirectiveException, ProcessedDirective[T]]] =
287-
scopedDirective =>
288-
'{
289-
Left(new UnexpectedDirectiveError($scopedDirective.directive.key))
290-
}
291-
292-
val handleValuesImpl = fields0.zipWithIndex.foldRight(elseCase) {
293-
case (((sym, tRepr), idx), elseCase0) =>
294-
val namesFromAnnotations0 = namesFromAnnotations(sym)
295-
296-
def typeArgs(tpe: TypeRepr): List[TypeRepr] = tpe match
297-
case AppliedType(_, typeArgs) => typeArgs.map(_.dealias)
298-
case _ => Nil
299-
300-
// from https://github.com/plokhotnyuk/jsoniter-scala/blob/1704a9cbb22b75a59f21ddf2a11427ba24df3212/jsoniter-scala-macros/shared/src/main/scala-3/com/github/plokhotnyuk/jsoniter_scala/macros/JsonCodecMaker.scala#L849-L854
301-
def genNew(argss: List[List[Term]]): Term =
302-
val constructorNoTypes = Select(New(Inferred(TypeRepr.of[T])), tSym.primaryConstructor)
303-
val constructor = typeArgs(TypeRepr.of[T]) match
304-
case Nil => constructorNoTypes
305-
case typeArgs => TypeApply(constructorNoTypes, typeArgs.map(Inferred(_)))
306-
argss.tail.foldLeft(Apply(constructor, argss.head))((acc, args) => Apply(acc, args))
307-
308-
val newArgs = fields0.map {
309-
case (sym, _) =>
310-
defaultMap.getOrElse(sym.name, sys.error(s"Field ${sym.name} has no default value"))
311-
}
312-
313-
tRepr.asType match {
314-
case '[t] =>
315-
val parser = Expr.summon[DirectiveValueParser[t]].getOrElse {
316-
sys.error(s"Cannot get implicit DirectiveValueParser[${Type.show[t]}]")
317-
}
318-
319-
val name = withPrefix(Expr(sym.name))
320-
321-
val cond: Expr[String] => Expr[Boolean] =
322-
if (namesFromAnnotations0.isEmpty)
323-
keyName => '{ DirectiveHandler.normalizeName($keyName) == $name }
324-
else {
325-
val names = Expr.ofList(name +: namesFromAnnotations0)
326-
keyName => '{ $names.contains(DirectiveHandler.normalizeName($keyName)) }
327-
}
328-
329-
scopedDirective =>
330-
'{
331-
if (${ cond('{ $scopedDirective.directive.key }) }) {
332-
val valuesByScope = $scopedDirective.directive.values.groupBy(_.getScope)
333-
.toVector
334-
.map {
335-
case (scopeOrNull, values) =>
336-
(Option(scopeOrNull), values)
337-
}
338-
.sortBy(_._1.getOrElse(""))
339-
valuesByScope
340-
.map {
341-
case (scopeOpt, values) =>
342-
$parser.parse(
343-
$scopedDirective.directive.values,
344-
$scopedDirective.cwd,
345-
$scopedDirective.maybePath
346-
).map { r =>
347-
scopeOpt -> ${
348-
genNew(List(newArgs.updated(idx, '{ r }).map(_.asTerm)))
349-
.asExprOf[T]
350-
}
351-
}
352-
}
353-
.sequence
354-
.left.map(CompositeDirectiveException(_))
355-
.map { v =>
356-
val mainOpt = v.collectFirst {
357-
case (None, t) => t
358-
}
359-
val scoped = v.collect {
360-
case (Some(scopeStr), t) =>
361-
// FIXME os.RelPath(…) might fail
362-
Scoped(
363-
$scopedDirective.cwd / os.RelPath(scopeStr),
364-
t
365-
)
366-
}
367-
ProcessedDirective(mainOpt, scoped)
368-
}
369-
}
370-
else
371-
${ elseCase0(scopedDirective) }
372-
}
373-
}
374-
}
375-
376-
'{
377-
new DirectiveHandler[T] {
378-
def name = $nameValue
379-
def usage = $usageValue
380-
override def usageMd =
381-
Some($usageMdValue).filter(_.nonEmpty).getOrElse(usage)
382-
def description = $descriptionValue
383-
override def descriptionMd =
384-
Some($descriptionMdValue).filter(_.nonEmpty).getOrElse(description)
385-
override def examples = ${ Expr.ofList(examplesValue) }
386-
override def tags = ${ Expr.ofList(tagsValue) }
387-
388-
lazy val keys = $keysValue
389-
.flatMap(key =>
390-
List(
391-
key,
392-
DirectiveHandler.pascalCaseSplit(key.toCharArray.toList)
393-
.map(_.toLowerCase(Locale.ROOT))
394-
.mkString("-")
395-
)
396-
)
397-
.distinct
398-
399-
def handleValues(scopedDirective: ScopedDirective) =
400-
${ handleValuesImpl('{ scopedDirective }) }
401-
}
402-
}
403-
}
404-
405-
inline given derive[T]: DirectiveHandler[T] =
406-
DirectiveHandler.deriveParser[T]
40783
}

0 commit comments

Comments
 (0)