11package scala .cli .directivehandler
22
3- import com .virtuslab .using_directives .custom .model .{EmptyValue , Value }
4-
5- import java .util .Locale
6-
73import scala .cli .directivehandler .EitherSequence ._
8- import scala .deriving .*
9- import scala .quoted .{_ , given }
104
115trait 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