1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
|
/* NSC -- new Scala compiler
* Copyright 2007-2009 LAMP/EPFL
* @author Sean McDirmid
*/
// $Id$
package scala.tools.nsc.doc
import scala.collection.mutable
import compat.Platform.{EOL => LINE_SEPARATOR}
/** This class attempts to reverse engineer source code intent from compiler
* symbol objects.
*
* @author Sean McDirmid
*/
trait ModelExtractor {
val global: Global
import global._
def settings: doc.Settings
def assert(b: Boolean) {
if (!b) throw new Error
}
def assert(b: Boolean, message: Any) {
if (!b) throw new Error(message.toString)
}
case class Tag(tag: String, option: String, body: String)
case class Comment(body: String, attributes: List[Tag]) {
def decodeAttributes = {
val map = new mutable.LinkedHashMap[String, List[(String, String)]] {
override def default(key: String) = Nil
}
attributes.foreach(a => {
map(a.tag) = map(a.tag) ::: List((a.option, a.body))
});
map
}
}
protected def decode(sym: Symbol) =
if (sym == definitions.ScalaObjectClass || sym == definitions.ObjectClass)
definitions.AnyRefClass
else sym match {
case sym: ModuleClassSymbol => sym.sourceModule
case sym => sym
}
protected def decodeComment(comment0: String): Comment = {
val comment = comment0 // .substring("/**".length, comment0.length - "*/".length)
val tok = new java.util.StringTokenizer(comment, LINE_SEPARATOR)
val buf = new StringBuilder
type AttrDescr = (String, String, StringBuilder)
val attributes = new collection.mutable.ListBuffer[AttrDescr]
var attr: AttrDescr = null
while (tok.hasMoreTokens) {
val s = tok.nextToken.replaceFirst("\\p{Space}?\\*", "")
val mat1 = pat1.matcher(s)
if (mat1.matches) {
attr = (mat1.group(1), null, new StringBuilder(mat1.group(2)))
//if (kind != CONSTRUCTOR)
attributes += attr
} else {
val mat2 = pat2.matcher(s)
if (mat2.matches) {
attr = (mat2.group(1), mat2.group(2), new StringBuilder(mat2.group(3)))
//if (kind != CLASS)
attributes += attr
} else if (attr ne null)
attr._3.append(s + LINE_SEPARATOR)
else
buf.append(s + LINE_SEPARATOR)
}
}
Comment(buf.toString, attributes.toList.map({x => Tag(x._1,x._2,x._3.toString)}))
}
sealed abstract class Entity(val sym: Symbol) {
private[ModelExtractor] def sym0 = sym
override def toString = sym.toString
def comment: Option[String] = global.comments.get(sym)
// comments decoded, now what?
def attributes = sym.attributes
def decodeComment: Option[Comment] = {
val comment0 = this.comment
if (comment0.isEmpty) None
else Some(ModelExtractor.this.decodeComment(comment0.get.trim))
}
protected def accessQualified(core: String, qual: String) = core match {
case "public" => "" // assert(qual == null); "";
case core => core + (if (qual == null) "" else "[" + qual + "]")
}
def flagsString = {
import symtab.Flags
//val isLocal = sym.hasFlag(Flags.LOCAL)
val x =
if (sym hasFlag Flags.PRIVATE) "private"
else if (sym hasFlag Flags.PROTECTED) "protected"
else "public"
var string = accessQualified(x,
if (sym hasFlag Flags.LOCAL) "this"
else if (sym.privateWithin != null && sym.privateWithin != NoSymbol)
sym.privateWithin.nameString
else null
)
def f(flag: Int, str: String) {
if (sym hasFlag flag) string = string + " " + str
}
f(Flags.IMPLICIT, "implicit")
f(Flags.SEALED, "sealed")
f(Flags.OVERRIDE, "override")
f(Flags.CASE, "case")
if (!sym.isTrait) f(Flags.ABSTRACT, "abstract")
if (!sym.isModule) f(Flags.FINAL, "final")
if (!sym.isTrait) f(Flags.DEFERRED, "abstract")
string.trim
}
def listName = name
def name = sym.nameString
def fullName(sep: Char) = sym.fullNameString(sep)
def kind: String
def header { }
def typeParams: List[TypeParam] = Nil
def valueParams: List[List[ValueParam]] = Nil
def resultType: Option[Type] = None
def parents: Iterable[Type] = Nil
def lo: Option[Type] = sym.info match {
case TypeBounds(lo, hi) if decode(lo.typeSymbol) != definitions.NothingClass => Some(lo)
case _ => None
}
def hi: Option[Type] = sym.info match {
case TypeBounds(lo, hi) if decode(hi.typeSymbol) != definitions.AnyClass => Some(hi)
case _ => None
}
def variance = {
import symtab.Flags._
if (sym hasFlag COVARIANT) "+"
else if (sym hasFlag CONTRAVARIANT) "-"
else ""
}
def overridden: Iterable[Symbol] = Nil
}
class ValueParam(sym: Symbol) extends Entity(sym) {
override def resultType = Some(sym.tpe)
//def kind = if (sym.isPublic) "val" else "";
def kind = ""
}
class ConstructorParam(sym: Symbol) extends ValueParam(sym) {
override protected def accessQualified(core: String, qual: String) = core match {
case "public" => "val"
case "protected" => super.accessQualified(core,qual) + " val"
case "private" if qual == "this" => ""
case core => super.accessQualified(core, qual)
}
}
def ValueParam(sym: Symbol) = new ValueParam(sym)
class TypeParam(sym: Symbol) extends Entity(sym) {
def kind = ""
}
def TypeParam(sym: Symbol) = new TypeParam(sym)
trait Clazz extends ClassOrObject {
private def csym = sym.asInstanceOf[TypeSymbol]
override def typeParams = csym.typeParams.map(TypeParam)
override def valueParams = {
if (constructorArgs.isEmpty) Nil
else constructorArgs.values.toList :: Nil
}
def isTrait = csym.isTrait
override def kind = if (sym.isTrait) "trait" else "class"
}
trait Object extends ClassOrObject {
override def kind = "object"
}
case class Package(override val sym: Symbol) extends Entity(sym) {
override def kind = "package"
override def name = fullName('.')
}
trait TopLevel extends ClassOrObject
class TopLevelClass (sym: Symbol) extends Entity(sym) with TopLevel with Clazz
class TopLevelObject(sym: Symbol) extends Entity(sym) with TopLevel with Object {
override def attributes = sym.moduleClass.attributes
}
def compare(pathA: List[ClassOrObject], pathB: List[ClassOrObject]): Int = {
var pA = pathA
var pB = pathB
while (true) {
if (pA.isEmpty) return -1
if (pB.isEmpty) return +1
val diff = pA.head.name compare pB.head.name
if (diff != 0) return diff
pA = pA.tail
pB = pB.tail
}
0
}
def isAccessible(sym: Symbol): Boolean = {
import symtab.Flags._
settings.memberaccess.value match {
case "private" => sym.isPublic || (sym hasFlag PROTECTED) || (sym hasFlag PRIVATE)
case "protected" => sym.isPublic || (sym hasFlag PROTECTED)
case "public" => sym.isPublic
case _ => false
}
}
trait ClassOrObject extends Entity {
def path: List[ClassOrObject] = this :: Nil
override def listName = path map (_.name) mkString "."
object freshParents extends mutable.LinkedHashSet[Type] {
this ++= sym.tpe.parents
this.toList foreach (this --= _.parents)
}
object constructorArgs extends mutable.LinkedHashMap[Symbol, ValueParam] {
import symtab.Flags._
sym.constrParamAccessors.filter(arg => ! (arg hasFlag SYNTHETIC)).foreach(arg => {
val str = flagsToString(arg.flags)
assert((arg hasFlag PRIVATE) && (arg hasFlag LOCAL), arg)
val argName = arg.name.toString.trim
val actual = sym.tpe.decls.elements.find(e => {
val eName = e.name.toString.trim;
argName == eName && {
val str = flagsToString(e.flags);
!e.hasFlag(LOCAL);
}
});
val param = actual getOrElse arg
this(param) = new ConstructorParam(param)
});
}
object decls extends mutable.LinkedHashMap[Symbol, Member] {
sym.tpe.decls.elements.foreach(e => {
if (!constructorArgs.contains(e)) {
val m = Member(e)
if (!m.isEmpty && !this.contains(e)) this.put(e, m.get)
}
});
}
def members0(f: Symbol => Boolean) = decls.filterKeys(f).values.toList
def members(c: Category): Iterable[Member] = members0(c.f)
object inherited extends mutable.LinkedHashMap[Symbol, List[Member]]() {
override def default(tpe: Symbol) = Nil
for (m <- sym.tpe.members if !sym.tpe.decls.elements.contains(m) &&
(Values.f(m) || Methods.f(m))) {
val o = m.overridingSymbol(sym)
if (o == NoSymbol) {
val parent = decode(m.enclClass)
val mo = Member(m)
if (!mo.isEmpty) {
this(parent) = mo.get :: this(parent)
}
}
}
}
override def parents = freshParents
abstract class Member(sym: Symbol) extends Entity(sym) {
private def overriding = sym.allOverriddenSymbols
override def comment = super.comment match {
case ret @ Some(comment) =>
ret
case None =>
val o = overriding.find(comments.contains)
o.map(comments.apply)
}
}
abstract class ValDef(sym: Symbol) extends Member(sym) {
override def resultType = Some(resultType0)
protected def resultType0: Type
override def overridden: Iterable[Symbol] = {
var ret: mutable.LinkedHashSet[Symbol] = null
for (parent <- ClassOrObject.this.parents) {
val sym0 = sym.overriddenSymbol(parent.typeSymbol)
if (sym0 != NoSymbol) {
if (ret == null) ret = new mutable.LinkedHashSet[Symbol];
ret += sym0
}
}
if (ret == null) Nil else ret
}
}
case class Def(override val sym : TermSymbol) extends ValDef(sym) {
override def resultType0 = sym.tpe.finalResultType
override def typeParams = sym.tpe.typeParams.map(TypeParam)
override def valueParams = methodArgumentNames.get(sym) match {
case Some(argss) if argss.length > 1 || (!argss.isEmpty && !argss(0).isEmpty) =>
argss map (_.map(ValueParam))
case _ =>
var i = 0
val ret = for (tpe <- sym.tpe.paramTypes) yield {
val ret = sym.newValueParameter(sym.pos, newTermName("arg" + i));
ret setInfo tpe
i += 1
ValueParam(ret)
}
if (ret.isEmpty) Nil
else ret :: Nil
}
override def kind = "def"
}
case class Val(override val sym: TermSymbol) extends ValDef(sym) {
import symtab.Flags._
def resultType0: Type = sym.tpe
override def kind: String =
if (sym hasFlag ACCESSOR) {
val setterName = nme.getterToSetter(sym.name)
val setter = sym.owner.info.decl(setterName)
val lazyMod = if (sym hasFlag LAZY) "lazy " else ""
lazyMod + (if (setter == NoSymbol) "val" else "var")
} else {
assert(sym hasFlag JAVA)
if (sym hasFlag FINAL) "val" else "var"
}
}
case class AbstractType(override val sym: Symbol) extends Member(sym) {
override def kind = "type"
}
abstract class NestedClassOrObject(override val sym: Symbol) extends Member(sym) with ClassOrObject {
override def path: List[ClassOrObject] = ClassOrObject.this.path ::: super.path
}
case class NestedClass(override val sym: ClassSymbol) extends NestedClassOrObject(sym) with Clazz
case class NestedObject(override val sym: ModuleSymbol) extends NestedClassOrObject(sym) with Object {
override def attributes = sym.moduleClass.attributes
}
def isVisible(sym: Symbol): Boolean = {
import symtab.Flags._
if (sym.isLocalClass) return false
if (sym.isLocal) return false
if (sym.isPrivateLocal) return false
if (sym hasFlag PRIVATE) return !inIDE //false
if (sym hasFlag SYNTHETIC) return false
if (sym hasFlag BRIDGE) return false
if ((sym.nameString indexOf "$") != -1) return false
if ((sym hasFlag CASE) && sym.isMethod) return false
true
}
def Member(sym: Symbol): Option[Member] = {
import global._
import symtab.Flags
if (!isVisible(sym))
None
else if (!isAccessible(sym))
None
else if (sym hasFlag Flags.ACCESSOR) {
if (sym.isSetter) return None;
assert(sym.isGetter);
Some[Member](new Val(sym.asInstanceOf[TermSymbol]))
}
else if (sym.isValue && !sym.isMethod && !sym.isModule) {
if (!sym.hasFlag(Flags.JAVA)) {
Console.println("SYM: " + sym + " " + sym.fullNameString('.'))
Console.println("FLA: " + Flags.flagsToString(sym.flags))
}
assert(sym hasFlag Flags.JAVA)
Some[Member](new Val(sym.asInstanceOf[TermSymbol]))
}
else if (sym.isValue && !sym.isModule) {
val str = Flags.flagsToString(sym.flags)
assert(sym.isMethod)
Some[Member](new Def(sym.asInstanceOf[TermSymbol]))
}
else if (sym.isAliasType || sym.isAbstractType)
Some(new AbstractType(sym))
else if (sym.isClass)
Some(new NestedClass(sym.asInstanceOf[ClassSymbol]))
else if (sym.isModule)
Some(new NestedObject(sym.asInstanceOf[ModuleSymbol]))
else
None
}
}
case class Category(label: String)(g: Symbol => Boolean) {
val f = g
def plural = label + "s"
}
val Constructors = new Category("Additional Constructor")(e => e.isConstructor && !e.isPrimaryConstructor) {
// override def plural = "Additional Constructors";
}
val Objects = Category("Object")(_.isModule);
val Classes = new Category("Class")(sym => sym.isClass || (sym == definitions.AnyRefClass)) {
override def plural = "Classes"
}
val Values = new Category("Value")(e => e.isValue && e.hasFlag(symtab.Flags.ACCESSOR)) {
override def plural = "Values and Variables"
}
val Methods = Category("Method")(e => e.isValue && e.isMethod && !e.isConstructor && !e.hasFlag(symtab.Flags.ACCESSOR));
val Types = Category("Type")(e => e.isAliasType || e.isAbstractType);
val categories = Constructors :: Types :: Values :: Methods :: Classes :: Objects :: Nil;
import java.util.regex.Pattern
// patterns for standard tags with 1 and 2 arguments
private val pat1 = Pattern.compile(
"[ \t]*@(author|deprecated|pre|return|see|since|todo|version|ex|note)[ \t]*(.*)")
private val pat2 = Pattern.compile(
"[ \t]*@(exception|param|throws)[ \t]+(\\p{Graph}*)[ \t]*(.*)")
def sort[E <: Entity](entities: Iterable[E]): Iterable[E] = {
val set = new collection.immutable.TreeSet[E]()({eA: E => new Ordered[E] {
def compare(eB: E): Int = {
if (eA eq eB) return 0;
(eA, eB) match {
case (eA: ClassOrObject, eB: ClassOrObject) =>
val diff = ModelExtractor.this.compare(eA.path, eB.path)
if (diff!= 0) return diff
case _ =>
}
if (eA.getClass != eB.getClass) {
val diff = eA.getClass.getName.compare(eB.getClass.getName)
assert(diff != 0)
return diff
}
if (!eA.sym0.isPackage) {
val diff = eA.sym0.nameString compare eB.sym0.nameString
if (diff != 0) return diff
}
val diff0 = eA.sym0.fullNameString compare eB.sym0.fullNameString
assert(diff0 != 0)
diff0
}
override def equals(other: Any) : Boolean =
eA.equals(other) || (other match { case that: AnyRef => this.eq(that)
case _ => false })
}})
set ++ entities
}
}
|