1
1
package scala .cli .directivehandler
2
2
3
- import com .virtuslab .using_directives .custom .model .{EmptyValue , Value }
4
-
5
- import java .util .Locale
6
-
7
3
import scala .cli .directivehandler .EitherSequence ._
8
- import scala .deriving .*
9
- import scala .quoted .{_ , given }
10
4
11
5
trait DirectiveHandler [+ T ] { self =>
12
6
def name : String
@@ -23,6 +17,23 @@ trait DirectiveHandler[+T] { self =>
23
17
scopedDirective : ScopedDirective
24
18
): Either [DirectiveException , ProcessedDirective [T ]]
25
19
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
+
26
37
def map [U ](f : T => U ): DirectiveHandler [U ] =
27
38
new DirectiveHandler [U ] {
28
39
def name = self.name
@@ -42,7 +53,7 @@ trait DirectiveHandler[+T] { self =>
42
53
43
54
}
44
55
45
- object DirectiveHandler {
56
+ object DirectiveHandler extends DirectiveHandlerMacros {
46
57
47
58
// from https://github.com/alexarchambault/case-app/blob/7ac9ae7cc6765df48eab27c4e35c66b00e4469a7/core/shared/src/main/scala/caseapp/core/util/CaseUtil.scala#L5-L22
48
59
def pascalCaseSplit (s : List [Char ]): List [String ] =
@@ -69,339 +80,4 @@ object DirectiveHandler {
69
80
(elems.head +: elems.tail.map(_.capitalize)).mkString
70
81
}
71
82
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 ]
407
83
}
0 commit comments