diff options
author | Sean McDirmid <sean.mcdirmid@gmail.com> | 2007-03-28 12:45:13 +0000 |
---|---|---|
committer | Sean McDirmid <sean.mcdirmid@gmail.com> | 2007-03-28 12:45:13 +0000 |
commit | 43185f20f4c71d9dce678f5d2c8253884fd61727 (patch) | |
tree | f3b97f235cb3cd29395a0712130ddf05fa58c707 /src | |
parent | 806238432588319e91805570ffbfc2f0ce5f409b (diff) | |
download | scala-43185f20f4c71d9dce678f5d2c8253884fd61727.tar.gz scala-43185f20f4c71d9dce678f5d2c8253884fd61727.tar.bz2 scala-43185f20f4c71d9dce678f5d2c8253884fd61727.zip |
New scala doc
Diffstat (limited to 'src')
-rw-r--r-- | src/compiler/scala/tools/nsc/doc/DocDriver.scala | 242 | ||||
-rw-r--r-- | src/compiler/scala/tools/nsc/doc/ModelAdditions.scala | 195 | ||||
-rw-r--r-- | src/compiler/scala/tools/nsc/doc/ModelExtractor.scala | 407 | ||||
-rw-r--r-- | src/compiler/scala/tools/nsc/doc/ModelFrames.scala | 325 | ||||
-rw-r--r-- | src/compiler/scala/tools/nsc/doc/ModelToXML.scala | 237 |
5 files changed, 1406 insertions, 0 deletions
diff --git a/src/compiler/scala/tools/nsc/doc/DocDriver.scala b/src/compiler/scala/tools/nsc/doc/DocDriver.scala new file mode 100644 index 0000000000..a70056f886 --- /dev/null +++ b/src/compiler/scala/tools/nsc/doc/DocDriver.scala @@ -0,0 +1,242 @@ +package scala.tools.nsc.doc; +import scala.collection.jcl; +import symtab.Flags._; +import scala.xml._; + +abstract class DocDriver extends ModelFrames with ModelToXML { + import global._; + + object additions extends jcl.LinkedHashSet[Symbol]; + object additions0 extends ModelAdditions(global) { + override def addition(sym : global.Symbol) = { + super.addition(sym); + sym match { + case sym : global.ClassSymbol => additions += sym.asInstanceOf[Symbol]; + case sym : global.ModuleSymbol => additions += sym.asInstanceOf[Symbol]; + case sym : global.TypeSymbol => additions += sym.asInstanceOf[Symbol]; + case _ => + } + } + def init : Unit = {} + } + + def process(units: Iterator[CompilationUnit]): Unit = { + assert(global.definitions != null); + + def g(pkg : Package, clazz : ClassOrObject) : Unit = { + allClasses(pkg) += clazz; + clazz.decls.map(._2).foreach{ + case clazz : ClassOrObject => + g(pkg, clazz); + case _ => + } + } + def f(pkg : Package, tree : Tree) : Unit = if (!tree.symbol.hasFlag(symtab.Flags.PRIVATE)) tree match { + case tree : PackageDef => + val pkg1 = new Package(tree.symbol.asInstanceOf[ModuleSymbol]); + tree.stats.foreach(stat => f(pkg1, stat)); + case tree : ClassDef => + assert(pkg != null); + g(pkg, new TopLevelClass(tree.symbol.asInstanceOf[ClassSymbol])); + case tree : ModuleDef => + assert(pkg != null); + g(pkg, new TopLevelObject(tree.symbol.asInstanceOf[ModuleSymbol])); + case _ => + } + units.foreach(unit => f(null, unit.body)); + + + for (val p <- allClasses; val d <- p._2) { + symbols += d.sym; + for (val pp <- d.sym.tpe.parents) subClasses(pp.symbol) += d; + } + additions0.init; + copyResources; + val packages0 = sort(allClasses.keySet); + new AllPackagesFrame with Frame { def packages = packages0; }; + new PackagesContentFrame with Frame { def packages = packages0; }; + new NavigationFrame with Frame { }; + new ListClassFrame with Frame { + def classes = { + for (val p <- allClasses; val d <- p._2) yield d; + } + object organized extends jcl.LinkedHashMap[(List[String],Boolean),List[ClassOrObject]] { + override def default(key : (List[String],Boolean)) = Nil; + classes.foreach(cls => { + val path = cls.path.map(.name); + this((path,cls.isInstanceOf[Clazz])) = cls :: this((path,cls.isInstanceOf[Clazz])); + }); + } + + def title = "List of all classes and objects" + def path = "all-classes" + def navLabel = null; // "root-page" + // override protected def navSuffix = ".html"; + override def optional(cls : ClassOrObject) : NodeSeq = { + val path = cls.path.map(.name); + val key = (cls.path.map(.name), cls.isInstanceOf[Clazz]); + assert(!organized(key).isEmpty); + (if (!organized(key).tail.isEmpty) Text(" (" +{ + //Console.println("CONFLICT: " + path + " " + organized(key)); + val str = cls.path(0).sym.owner.fullNameString('.'); + val idx = str.lastIndexOf('.'); + if (idx == -1) str; + else str.substring(idx + 1); + }+ ")"); + else NodeSeq.Empty) ++ super.optional(cls); + } + + } + for (val (pkg0,classes0) <- allClasses) { + new ListClassFrame with Frame { + def title = + "List of classes and objects in package " + pkg0.fullName('.') + def classes = classes0; + def path = pkg0.fullName('/') + NAME_SUFFIX_PACKAGE; + def navLabel = pkg0.fullName('.'); + } + new PackageContentFrame with Frame { + def classes = classes0; + def pkg = pkg0; + } + for (val clazz0 <- classes0) { + new ClassContentFrame with Frame { + def clazz = clazz0; + def title = + clazz0.kind + " " + clazz0.name + " in " + (clazz0.sym.owner.fullNameString('.')); + } + } + } + for (val sym <- additions) sym match { + case sym : ClassSymbol => + val add = new TopLevelClass(sym); + new ClassContentFrame with Frame { + def clazz = add; + def title = + add.kind + " " + add.name + " in package " + add.sym.owner.fullNameString('.') + } + case sym : TypeSymbol => + val add = new TopLevelClass(sym); + new ClassContentFrame with Frame { + def clazz = add; + def title = + add.kind + " " + add.name + " in package " + add.sym.owner.fullNameString('.') + } + case sym : ModuleSymbol => + val add = new TopLevelObject(sym); + new ClassContentFrame with Frame { + def clazz = add; + def title = + add.kind + " " + add.name + " in package " + add.sym.owner.fullNameString('.') + } + } + new RootFrame with Frame; + } + override def longList(entity : ClassOrObject,category : Category)(implicit from : Frame) : NodeSeq = category match { + case (Classes | Objects) => NodeSeq.Empty; + case _ => super.longList(entity,category); + } + trait Frame extends super.Frame { + def longHeader(entity : Entity) = DocDriver.this.longHeader(entity)(this); + def shortHeader(entity : Entity) = DocDriver.this.shortHeader(entity)(this); + } + import DocUtil._; + override def classBody(entity : ClassOrObject)(implicit from : Frame) : NodeSeq = + (subClasses.get(entity.sym) match { + case Some(symbols) => + <dl> + <dt style="margin:10px 0 0 20px;"><b>Direct Known Subclasses:</b></dt> + <dd>{symbols.mkXML("",", ","")(cls => { + aref(urlFor(cls.sym), cls.path.map(.name).mkString("",".","")); + })}</dd> + </dl><hr/>; + case None => NodeSeq.Empty; + })++super.classBody(entity); + protected def urlFor(sym : Symbol)(implicit frame : Frame) = frame.urlFor(sym); + + override protected def decodeTag(tag : String) : String = tag match { + case "exception" => "Throws" + case "ex" => "Examples" + case "param" => "Parameters" + case "pre" => "Precondition" + case "return" => "Returns" + case "note" => "Notes" + case "see" => "See Also" + case tag => super.decodeTag(tag); + } + override protected def decodeOption(tag : String, option : String) : NodeSeq = tag match { + case "throws" if additions0.exceptions.contains(option) => + val (sym, s) = additions0.exceptions(option); + val path = "../" //todo: fix path + val href = path + sym.fullNameString('/') + + (if (sym.isModule || sym.isModuleClass) NAME_SUFFIX_OBJECT else "") + + "#" + s + <a href={href}>{option}</a> ++ {Text(" - ")}; + case _ => super.decodeOption(tag,option); + } + object roots extends jcl.LinkedHashMap[String,String]; + roots("classes") = "http://java.sun.com/j2se/1.5.0/docs/api"; + roots("rt") = roots("classes"); + roots("scala-library") = "http://www.scala-lang.org/docu/files/api"; + + private def keyFor(file : java.util.zip.ZipFile) : String = { + var name = file.getName; + var idx = name.lastIndexOf(java.io.File.pathSeparator); + if (idx == -1) idx = name.lastIndexOf('/'); + if (idx != -1) name = name.substring(idx + 1); + if (name.endsWith(".jar")) return name.substring(0, name.length - (".jar").length); + else return null; + } + + // <code>{Text(string + " - ")}</code>; + override def hasLink0(sym : Symbol) : Boolean = { + if (sym == NoSymbol) return false; + val ret = super.hasLink0(sym) && (additions.contains(sym) || symbols.contains(sym)); + if (ret) return true; + if (sym.toplevelClass == NoSymbol) return false; + val clazz = sym.toplevelClass.asInstanceOf[ClassSymbol]; + import scala.tools.nsc.io._; + clazz.classFile match { + case file : ZipArchive#FileEntry => + val key = keyFor(file.archive); + if (key != null && roots.contains(key)) return true; + case null => + case _ => + } + return false; + } + def aref(href : String, label : String)(implicit frame : Frame) = + frame.aref(href, "_self", label); + protected def anchor(entity : Symbol)(implicit frame : Frame) : NodeSeq = + <a name={Text(frame.docName(entity))}></a> + + object symbols extends jcl.LinkedHashSet[Symbol]; + object allClasses extends jcl.LinkedHashMap[Package,jcl.LinkedHashSet[ClassOrObject]] { + override def default(pkg : Package) : jcl.LinkedHashSet[ClassOrObject] = { + object ret extends jcl.LinkedHashSet[ClassOrObject]; + this(pkg) = ret; ret; + } + } + object subClasses extends jcl.LinkedHashMap[Symbol,jcl.LinkedHashSet[ClassOrObject]] { + override def default(key : Symbol) = { + val ret = new jcl.LinkedHashSet[ClassOrObject]; + this(key) = ret; ret; + } + } + override def rootFor(sym : Symbol) : String = { + assert(sym != NoSymbol); + if (sym.toplevelClass == NoSymbol) return super.rootFor(sym); + if (symbols.contains(sym.toplevelClass)) return super.rootFor(sym); + val clazz = sym.toplevelClass.asInstanceOf[ClassSymbol]; + import scala.tools.nsc.io._; + clazz.classFile match { + case file : ZipArchive#FileEntry => + val key = keyFor(file.archive); + if (key != null && roots.contains(key)) { + return roots(key) + '/'; + } + case _ => ; + } + return super.rootFor(sym); + } +} diff --git a/src/compiler/scala/tools/nsc/doc/ModelAdditions.scala b/src/compiler/scala/tools/nsc/doc/ModelAdditions.scala new file mode 100644 index 0000000000..cba6de48b8 --- /dev/null +++ b/src/compiler/scala/tools/nsc/doc/ModelAdditions.scala @@ -0,0 +1,195 @@ +package scala.tools.nsc.doc; + +class ModelAdditions(val global : Global) { + import global._; + import definitions._; + def addition(sym : global.Symbol) : Unit = {} + + addition(AllClass); + comments(AllClass) = """ + /** <p> + * Class <code>Nothing</code> (previously named <code>All</code> in + * <a href="http://scala-lang.org" target="_top">Scala</a> 2.2.0 and + * older versions) is - together with class <a href="Null.html"> + * <code>Null</code></a> - at the bottom of the + * <a href="http://scala-lang.org" target="_top">Scala</a> type + * hierarchy. + * </p> + * <p> + * Type <code>Nothing</code> is a subtype of every other type + * (including <a href="Null.html"><code>Null</code></a>); there + * exist <em>no instances</em> of this type. Even though type + * <code>Nothing</code> is empty, it is nevertheless useful as a + * type parameter. For instance, the <a href="http://scala-lang.org" + * target="_top">Scala</a> library defines a value + * <a href="Nil$object.html"><code>Nil</code></a> of type + * <code><a href="List.html">List</a>[Nothing]</code>. Because lists + * are covariant in <a href="http://scala-lang.org" target="_top">Scala</a>, + * this makes <a href="Nil$object.html"><code>Nil</code></a> an + * instance of <code><a href="List.html">List</a>[T]</code>, for + * any element type <code>T</code>. + * </p> + */"""; + addition(AllRefClass); + comments(AllRefClass) = """ + /** <p> + * Class <code>Null</code> (previously named <code>AllRef</code> in + * <a href="http://scala-lang.org" target="_top">Scala</a> 2.2.0 and + * older versions) is - together with class <a href="Nothing.html"> + * <code>Nothing</code> - at the bottom of the + * <a href="http://scala-lang.org" target="_top">Scala</a> type + * hierarchy. + * </p> + * <p> + * Type <code>Null</code> is a subtype of all reference types; its + * only instance is the <code>null</code> reference. + * Since <code>Null</code> is not a subtype of value types, + * <code>null</code> is not a member of any such type. For instance, + * it is not possible to assign <code>null</code> to a variable of + * type <a href="Int.html"><code>Int</code></a>. + * </p> + */"""; + + addition(AnyClass); + comments(AnyClass) = """ + /** <p> + * Class <code>Any</code> is the root of the <a + * href="http://scala-lang.org/" + * target="_top">Scala</a> class hierarchy. Every class in a + * <a href="http://scala-lang.org/" target="_top">Scala</a> execution + * environment inherits directly or indirectly from this class. + * Class <code>Any</code> has two direct subclasses: + * <a href="AnyRef.html"><code>AnyRef</code></a> and + * <a href="AnyVal.html"><code>AnyVal</code></a>. + * </p> + */"""; + /****/ + addition(Object_isInstanceOf); + + comments(Object_isInstanceOf) = """ + /** <p> + * The method <code>isInstanceOf</code> is the pendant of the Java + * operator <code>instanceof</code>. + * </p> + * @see <ul><li>Java Language Specification (2<sup>nd</sup> Ed.): + * <a href="http://java.sun.com/docs/books/jls/second_edition/html/expressions.doc.html#80289" + * target="_top">Operator <code>instanceof</code></a>.</li></ul> + */"""; + /****/ + addition(Object_synchronized); + comments(Object_synchronized) = """ + /** <p> + * To make your programs thread-safe, you must first identify what + * data will be shared across threads. If you are writing data that + * may be read later by another thread, or reading data that may + * have been written by another thread, then that data is shared, + * and you must synchronize when accessing it. + * </p> + * @see <ul><li>The Java Tutorials: + * <a href="http://java.sun.com/docs/books/tutorial/essential/concurrency/sync.html" + * target="_top">Synchronization</a>.</li> + * <li> IBM developerWorks: + * <a href="http://www-128.ibm.com/developerworks/java/library/j-threads1.html" + * target="_top">Synchronization is not the enemy</a>.</li></ul> + */"""; + addition(AnyRefClass); + comments(AnyRefClass) = """ + /** <p> + * Class <code>AnyRef</code> is the root class of all + * <em>reference types</em>. + * </p> + */"""; + addition(AnyValClass); + comments(AnyValClass) = """ + /** <p> + * Class <code>AnyVal</code> is the root class of all + * <em>value types</em>. + * </p> + * <p> + * <code>AnyVal</code> has a fixed number subclasses, which + * describe values which are not implemented as objects in the + * underlying host system. + * </p> + * <p> + * Classes <a href="Double.html"><code>Double</code></a>, + * <a href="Float.html"><code>Float</code></a>, + * <a href="Long.html"><code>Long</code></a>, + * <a href="Int.html"><code>Int</code></a>, + * <a href="Char.html"><code>Char</code></a>, + * <a href="Short.html"><code>Short</code></a>, and + * <a href="Byte.html"><code>Byte</code></a> are together called + * <em>numeric value types</em>. + * Classes <a href="Byte.html"><code>Byte</code></a>, + * <a href="Short.html"><code>Short</code></a>, or + * <a href="Char.html"><code>Char</code></a> + * are called <em>subrange types</em>. Subrange types, as well as + * <a href="Int.html"><code>Int</code></a> and + * <a href="Long.html"><code>Long</code></a> are called + * <em>integer types</em>, whereas + * <a href="Float.html"><code>Float</code></a> and + * <a href="Double.html"><code>Double</code></a> are called + * <em>floating point types</em>. + * </p> + */"""; + addition(BooleanClass); + comments(BooleanClass) = {""" + /** <p> + * Class <code>Boolean</code> has only two values: <code>true</code> + * and <code>false</code>. + * </p> + */"""}; + def numericValDescr(sym: Symbol) = { + val maxValue = "MAX_" + sym.name.toString().toUpperCase() + val minValue = "MIN_" + sym.name.toString().toUpperCase(); + addition(sym); + comments(sym) = """ + /** <p> + * Class <code>""" + sym.name + """ </code> belongs to the value + * classes whose instances are not represented as objects by the + * underlying host system. All value classes inherit from class + * <a href="AnyVal.html"><code>AnyVal</code></a>. + * </p> + * <p> + * Values <code>""" + maxValue + """</code> and <code>""" + minValue + """</code> + * are in defined in object <a href="compat/Math$object.html">scala.compat.Math</a>. + * </p> + */"""; + } + (ByteClass :: CharClass :: DoubleClass :: LongClass :: + FloatClass :: IntClass :: ShortClass :: Nil).foreach(numericValDescr); + + addition(UnitClass); + comments(UnitClass) = {""" + /** <p> + * Class <code>Unit</code> has only one value: <code>()</code>. + * </p> + */"""}; + addition(UnitClass); + + def boxedValDescr(what : String) = { + val sym = definitions.getClass("scala.runtime.Boxed" + what); + addition(sym); + comments(sym) = """ + /** <p> + * Class <code>""" + sym.name + """</code> implements the + * boxing/unboxing from/to value types. + * </p> + * <p> + * Boxing and unboxing enable value types to be treated as objects; + * they provide a unified view of the type system wherein a value + * of any type can ultimately be treated as an object. + * </p> + */""" + }; + ("Float" :: "Long" :: "Number" :: "Int" :: Nil).foreach(boxedValDescr); + + object exceptions extends collection.jcl.TreeMap[String,(Symbol,String)] { + def f(name : String) = { + this("Predef." + name) = (definitions.PredefModule, name); + } + f("IndexOutOfBoundsException"); + f("NoSuchElementException"); + f("NullPointerException"); + f("UnsupportedOperationException"); + } +} diff --git a/src/compiler/scala/tools/nsc/doc/ModelExtractor.scala b/src/compiler/scala/tools/nsc/doc/ModelExtractor.scala new file mode 100644 index 0000000000..cd98915002 --- /dev/null +++ b/src/compiler/scala/tools/nsc/doc/ModelExtractor.scala @@ -0,0 +1,407 @@ +package scala.tools.nsc.doc; +import scala.collection.jcl; +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 assert(b : Boolean) { + if (!b) + throw new Error; + } + case class Tag(tag : String, option : String, body : String); + case class Comment(body : String, attributes : List[Tag]) { + def decodeAttributes = { + val map = new jcl.LinkedHashMap[String,List[(String,String)]] { + override def default(key : String) = Nil; + } + attributes.foreach(a => { + map(a.tag) = map(a.tag) ::: ((a.option,a.body) :: Nil); + }); + 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 = { + assert(comment0.startsWith("/**")); + assert(comment0.endsWith("*/")); + 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) return None; + var comment = comment0.get.trim; + Some(ModelExtractor.this.decodeComment(comment)); + } + 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 params : List[List[Param]] = 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.symbol) != definitions.AllClass => Some(lo); + case _ => None; + } + def hi : Option[Type] = sym.info match { + case TypeBounds(lo,hi) if decode(hi.symbol) != 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 Param(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 Param(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 Param(sym : Symbol) = new Param(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 params = { + 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 : ModuleSymbol) 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; + + 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; + } + return 0; + } + + trait ClassOrObject extends Entity { + def path : List[ClassOrObject] = this :: Nil; + override def listName = path.map(.name).mkString("",".",""); + + object freshParents extends jcl.LinkedHashSet[Type] { + this addAll sym.tpe.parents; + this.toList.foreach(e => this removeAll e.parents); + } + object constructorArgs extends jcl.LinkedHashMap[Symbol,Param] { + sym.constrParamAccessors.foreach(arg => { + val str = symtab.Flags.flagsToString(arg.flags); + assert(arg.hasFlag(symtab.Flags.PRIVATE) && arg.hasFlag(symtab.Flags.LOCAL)); + val argName = arg.name.toString.trim; + val actual = sym.tpe.decls.elements.find(e => { + val eName = e.name.toString.trim; + argName == eName && { + val str = symtab.Flags.flagsToString(e.flags); + !e.hasFlag(symtab.Flags.LOCAL); + } + }); + if (!actual.isEmpty) this(actual.get) = new ConstructorParam(actual.get); + else this(arg) = new ConstructorParam(arg); + }); + } + object decls extends jcl.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.pfilter(e => f(e)).valueSet; + def members(c : Category) : Iterable[Member] = members0(c.f); + + object inherited extends jcl.LinkedHashMap[Symbol,List[Member]]() { + override def default(tpe : Symbol) = Nil; + { + for (val m <- sym.tpe.members; !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 : jcl.LinkedHashSet[Symbol] = null; + for (val parent <- ClassOrObject.this.parents) { + val sym0 = sym.overriddenSymbol(parent.symbol); + if (sym0 != NoSymbol) { + if (ret == null) ret = new jcl.LinkedHashSet[Symbol]; + ret += sym0; + } + } + if (ret == null) Nil else ret.readOnly; + } + } + 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 params = methodArgumentNames.get(sym) match { + case Some(argss) if argss.length > 1 || (!argss.isEmpty && !argss(0).isEmpty) => + argss.map(.map(Param)); + case _ => + var i = 0; + val ret = for (val tpe <- sym.tpe.paramTypes) yield { + val ret = sym.newValueParameter(sym.pos, newTermName("arg" + i)); + ret.setInfo(tpe); + i = i + 1; + Param(ret); + } + if (ret.isEmpty) Nil; + else ret :: Nil; + } + override def kind = "def"; + } + case class Val(override val sym : TermSymbol) extends ValDef(sym) { + def resultType0 : Type = sym.tpe; + override def kind = { + import symtab.Flags._; + if (sym.hasFlag(ACCESSOR)) { + val setterName = nme.getterToSetter(sym.name); + val setter = sym.owner.info.decl(setterName); + 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; + 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 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; + return true; + } + def Member(sym : Symbol) : Option[Member] = { + import global._; + import symtab.Flags._; + if (!isVisible(sym)) return None; + if (sym.hasFlag(ACCESSOR)) { + if (sym.isSetter) return None; + assert(sym.isGetter); + return Some[Member](new Val(sym.asInstanceOf[TermSymbol])); + } else if (sym.isValue && !sym.isMethod && !sym.isModule) { + if (!sym.hasFlag(JAVA)) { + Console.println("SYM: " + sym + " " + sym.fullNameString('.')); + Console.println("FLA: " + symtab.Flags.flagsToString(sym.flags)); + } + assert(sym.hasFlag(JAVA)); + return Some[Member](new Val(sym.asInstanceOf[TermSymbol])); + } + if (sym.isValue && !sym.isModule) { + val str = symtab.Flags.flagsToString(sym.flags); + assert(sym.isMethod); + return new Some[Member](new Def(sym.asInstanceOf[TermSymbol])); + } + if (sym.isAliasType || sym.isAbstractType) return Some(new AbstractType(sym)); + if (sym.isClass) return Some(new NestedClass(sym.asInstanceOf[ClassSymbol])); + if (sym.isModule) return Some(new NestedObject(sym.asInstanceOf[ModuleSymbol])); + 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")(.isClass) { + 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 :: Objects :: Classes :: 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 jcl.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.getCanonicalName.compare(eB.getClass.getCanonicalName); + 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); + return diff0; + } + }}); + set addAll entities; + set; + } +} diff --git a/src/compiler/scala/tools/nsc/doc/ModelFrames.scala b/src/compiler/scala/tools/nsc/doc/ModelFrames.scala new file mode 100644 index 0000000000..917895e1ef --- /dev/null +++ b/src/compiler/scala/tools/nsc/doc/ModelFrames.scala @@ -0,0 +1,325 @@ +package scala.tools.nsc.doc; + +import compat.Platform.{EOL => LINE_SEPARATOR}; +import scala.xml._; +import java.io.{File,FileWriter}; +import scala.collection.jcl; + +/** This class provides HTML document framing functionality. + * + * @author Sean McDirmid, Stephane Micheloud + */ +trait ModelFrames extends ModelExtractor { + import DocUtil._; + def outdir: String; + def windowTitle: String; + def documentTitle: String; + def contentFrame = "contentFrame"; + def classesFrame = "classesFrame"; + def modulesFrame = "modulesFrame"; + protected val FILE_EXTENSION_HTML = ".html"; + protected val NAME_SUFFIX_OBJECT = "$object"; + protected val NAME_SUFFIX_PACKAGE = "$package"; + + + def rootTitle = <div class="page-title"> Scala 2<br/>API Specification</div>; + def rootDesc = <p>This document is the API specification for Scala 2.</p>; + + final def hasLink(sym : global.Symbol) : Boolean = { + if (sym == global.NoSymbol) false; + else if (hasLink0(sym)) true; + else { + hasLink(decode(sym.owner)); + } + } + def hasLink0(sym : global.Symbol) : Boolean = true; + + abstract class Frame extends UrlContext { + { // just save. + save(page(title, body, hasBody)); + } + def path: String // relative to outdir + def relative: String = { + assert(path ne null) + var idx = 0 + var ct = new StringBuilder + while (idx != -1) { + idx = path.indexOf('/', idx) + //System.err.println(path + " idx=" + idx) + ct.append(if (idx != -1) "../" else "") + idx = idx + (if (idx == -1) 0 else 1) + } + ct.toString + } + def save(nodes: NodeSeq) = { + val path0 = outdir + File.separator + path + FILE_EXTENSION_HTML + //if (settings.debug.value) inform("Writing XML nodes to " + path0) + val file = new File(path0) + val parent = file.getParentFile() + if (!parent.exists()) parent.mkdirs() + val writer = new FileWriter(file) + val str = dtype + LINE_SEPARATOR + nodes.toString() + writer.write(str, 0, str.length()) + writer.close() + } + protected def body: NodeSeq; + protected def title: String; + protected def hasBody = true; + + //def urlFor(entity : Entity, target : String) : NodeSeq; + def urlFor(entity : Entity) : String = { + val ret = this.urlFor(entity.sym); + assert(ret != null); ret; + } + def link(entity : Entity, target : String) = aref(urlFor(entity), target, entity.name); + protected def shortHeader(entity : Entity) : NodeSeq; + protected def longHeader(entity : Entity) : NodeSeq; + import global._; + import symtab.Flags; + + def urlFor(sym: Symbol): String = sym match { + case sym : TypeSymbol if sym == definitions.AnyRefClass => urlFor0(sym,sym) + FILE_EXTENSION_HTML; + case psym : ModuleSymbol if psym.isPackage => urlFor0(sym,sym) + FILE_EXTENSION_HTML; + case sym if !hasLink(sym) => null; + case msym: ModuleSymbol => urlFor0(sym, sym) + FILE_EXTENSION_HTML + case csym: ClassSymbol => urlFor0(sym, sym) + FILE_EXTENSION_HTML + case _ => + val cnt = urlFor(decode(sym.owner)); + if (cnt == null) null else cnt + "#" + docName(sym) + } + def docName(sym: Symbol): String = { + def javaParams(paramTypes: List[Type]): String = { + def javaName(pt: Type): String = { + val s = pt.toString + val matVal = patVal.matcher(s) + if (matVal.matches) matVal.group(1).toLowerCase + else s.replaceAll("\\$", ".") + } + paramTypes.map(pt => javaName(pt)).mkString("(", ",", ")") + } + def scalaParams(paramTypes: List[Type]): String = { + def scalaName(pt: Type): String = pt.toString.replaceAll(" ", "") + paramTypes.map(pt => scalaName(pt)).mkString("(", ",", ")") + } + java.net.URLEncoder.encode(sym.nameString + + (sym.tpe match { + case MethodType(paramTypes, _) => + if (sym hasFlag Flags.JAVA) javaParams(paramTypes) + else scalaParams(paramTypes) + case PolyType(_, MethodType(paramTypes, _)) => + if (sym hasFlag Flags.JAVA) javaParams(paramTypes) + else scalaParams(paramTypes) + case _ => "" + }), encoding) + } + def urlFor0(sym: Symbol, orig: Symbol): String = { + (if (sym == NoSymbol) "XXX" + else if (sym.owner.isPackageClass) { + rootFor(sym) + sym.fullNameString('/'); + } else urlFor0(decode(sym.owner), orig) + "." + Utility.escape(sym.nameString)) + + (sym match { + case msym: ModuleSymbol => + if (msym hasFlag Flags.PACKAGE) NAME_SUFFIX_PACKAGE + else NAME_SUFFIX_OBJECT + case csym: ClassSymbol => + if (csym.isModuleClass) { + if (csym hasFlag Flags.PACKAGE) NAME_SUFFIX_PACKAGE + else NAME_SUFFIX_OBJECT + } + else "" + case _ => "" + }) + } + } + protected def rootFor(sym : global.Symbol) = ""; + + private val doctitle: NodeSeq = + <div class="doctitle-larger">{load(documentTitle)}</div>; + + abstract class AllPackagesFrame extends Frame { + override val path = "modules"; + override val title = "List of all packages"; + def packages : Iterable[Package]; + override def body: NodeSeq = + <div> + {doctitle} + <a href="all-classes.html" target={classesFrame} onclick="resetKind();">{"All objects and classes"}</a> + </div> + <div class="kinds">Packages</div> + <ul class="list">{sort(packages).mkXML("","\n","")(pkg => { + <li><a href={urlFor(pkg)} target={classesFrame} onclick="resetKind();"> + {pkg.fullName('.')}</a></li> + })} + </ul>; + } + abstract class PackagesContentFrame extends Frame { + val path = "root-content"; + val title = "All Packages"; + def packages : Iterable[Package]; + //def modules: TreeMap[String, ModuleClassSymbol] + def body: NodeSeq = + {rootTitle} ++ {rootDesc} ++ <hr/> ++ + <table cellpadding="3" class="member" summary=""> + <tr><td colspan="2" class="title">Package Summary</td></tr> + {sort(packages).mkXML("","\n","")(pkg => <tr><td class="signature"> + <code>package + {aref(pkg.fullName('/') + "$content.html", "_self", pkg.fullName('.'))} + </code> + </td></tr>)} + </table>; + } + + val classFrameKinds = Objects :: Classes :: Nil; + abstract class ListClassFrame extends Frame { + def classes: Iterable[ClassOrObject] + def navLabel: String + private def navPath = { + val p = path; + (if (p endsWith NAME_SUFFIX_PACKAGE) + p.substring(0, p.length() - NAME_SUFFIX_PACKAGE.length()); + else p) + navSuffix; + } + protected def navSuffix = "$content.html"; + + def body: NodeSeq = { + val nav = if (navLabel == null) NodeSeq.Empty else + <table class="navigation" summary=""> + <tr><td valign="top" class="navigation-links"> + {aref(navPath, contentFrame, navLabel)} + </td></tr> + </table>; + val ids = new jcl.LinkedHashSet[String]; + def idFor(kind: Category, t: Entity)(seq : NodeSeq): NodeSeq = { + val ch = t.listName.charAt(0); + val id = kind.plural + "_" + ch; + if (ids contains id) <li>{seq}</li>; + else { + ids += id; + <li id={id}>{seq}</li> + }; + } + val body = <div>{classFrameKinds.mkXML("","\n","")(kind => { + val classes = sort(this.classes.filter(e => kind.f(e.sym))); + if (classes.isEmpty) NodeSeq.Empty; else + <div id={kind.plural} class="kinds">{Text(kind.plural)}</div> + <ul class="list"> + {classes.mkXML("","\n","")(cls => { + idFor(kind, cls)( + aref(urlFor(cls), contentFrame, cls.listName) ++ optional(cls) + ); + })} + </ul>; + })}</div>; + nav ++ body + } + def optional(cls : ClassOrObject) : NodeSeq = NodeSeq.Empty; + } + abstract class PackageContentFrame extends Frame { + override def path = pkg.fullName('/') + "$content"; + override def title = "All classes and objects in " + pkg.fullName('.'); + protected def pkg : Package; + protected def classes : Iterable[ClassOrObject]; + def body: NodeSeq = + {rootTitle} ++ {rootDesc} ++ {classFrameKinds.mkXML("","\n","")(kind => { + val classes = sort(this.classes.filter(e => kind.f(e.sym) && e.isInstanceOf[TopLevel])); + if (classes.isEmpty) NodeSeq.Empty else + <table cellpadding="3" class="member" summary=""> + <tr><td colspan="2" class="title">{kind.label} Summary</td></tr> + {classes.mkXML("","\n","")(shortHeader)} + </table> + })}; + } + abstract class ClassContentFrame extends Frame { + def clazz: ClassOrObject; + def body: NodeSeq = <span>{navigation}{header0}{longHeader(clazz)}</span>; + final def path = urlFor0(clazz.sym, clazz.sym) + private def navigation: NodeSeq = + <table class="navigation" summary=""> + <tr> + <td valign="top" class="navigation-links"> + <table><tr> + </tr></table> + </td> + <td align="right" valign="top" style="white-space:nowrap;" rowspan="2"> + {doctitle} + </td> + </tr> + <tr><td></td></tr> + </table>; + private def header0: NodeSeq = <span> + <div class="entity"> + {Text(clazz.kind)} + <span class="entity">{Text(clazz.name)}</span> in + {aref(urlFor(decode(clazz.sym.owner)), "_self", decode(clazz.sym.owner).fullNameString('.'))} + </div><hr/> + </span>; + } + def longComment(cmnt : Comment) : NodeSeq; + + val index = + <frameset cols="25%, 75%"> + <frameset rows="50%, 28, 50%"> + <frame src="modules.html" name={modulesFrame}></frame> + <frame src="nav-classes.html" name="navigationFrame"></frame> + <frame src="all-classes.html" name={classesFrame}></frame> + </frameset> + <frame src="root-content.html" name={contentFrame}></frame> + </frameset>; + + val root = <b></b>; + + abstract class RootFrame extends Frame { + def title = windowTitle + def body = index + def path = "index" + override def hasBody = false + } + + val indexChars = 'A' :: 'B' :: 'C' :: 'D' :: 'E' :: 'G' :: 'I' :: 'L' :: 'M' :: 'P' :: 'R' :: 'T' :: 'V' :: 'X' :: Nil; + + abstract class NavigationFrame extends Frame { + def title="navigation" + def path="nav-classes" + override def body0(hasBody: Boolean, nodes: NodeSeq): NodeSeq = + if (!hasBody) nodes + else <body style="margin:1px 0 0 1px; padding:1px 0 0 1px;">{nodes}</body>; + def body = + <form> + <select id="kinds" onchange="gotoKind()"> + <option value="#Classes" selected="selected">Classes</option> + <option value="#Objects">Objects</option> + </select> + <span id="alphabet" style="font-family:Courier;word-spacing:-8px;">{ + indexChars.mkXML("","\n","")(c => { + <a href={Unparsed("javascript:gotoName(\'" + c + "\')")}>{c}</a> + }); + } + </span> + </form> + } + + private val loader = getClass().getClassLoader() + def copyResources = { + import java.io._; + val rsrcdir = "scala/tools/nsc/doc/".replace('/', File.separatorChar) + for (val base <- List("style.css", "script.js")) { + try { + val in = loader.getResourceAsStream(rsrcdir + base) + val out = new FileOutputStream(new File(outdir + File.separator + base)) + val buf = new Array[byte](1024) + var len = 0 + while (len != -1) { + out.write(buf, 0, len) + len = in.read(buf) + } + in.close() + out.close() + } catch { + case _ => + error("Resource file '" + base + "' not found") + } + } + } + private val patVal = java.util.regex.Pattern.compile( + "scala\\.(Byte|Boolean|Char|Double|Float|Int|Long|Short)") +} diff --git a/src/compiler/scala/tools/nsc/doc/ModelToXML.scala b/src/compiler/scala/tools/nsc/doc/ModelToXML.scala new file mode 100644 index 0000000000..c43a215a22 --- /dev/null +++ b/src/compiler/scala/tools/nsc/doc/ModelToXML.scala @@ -0,0 +1,237 @@ +package scala.tools.nsc.doc; +import scala.xml._ + +/** This class has functionality to format source code models as XML blocks. + * + * @author Sean McDirmid, Stephane Micheloud + */ +trait ModelToXML extends ModelExtractor { + import global._; + import DocUtil._; + // decode entity into XML. + type Frame; + + protected def urlFor(sym : Symbol)(implicit frame : Frame) : String; + protected def anchor(sym : Symbol)(implicit frame : Frame) : NodeSeq; + def aref(href : String, label : String)(implicit frame : Frame) : NodeSeq; + def link(entity : Symbol)(implicit frame : Frame) : NodeSeq = { + val url = urlFor(entity); + // nothing to do but be verbose. + if (url == null) Text({ + entity.owner.fullNameString('.') + '.' + entity.nameString; + }) else aref(url, entity.nameString); + } + def link(tpe : Type)(implicit frame : Frame) : NodeSeq = { + if (!tpe.typeArgs.isEmpty) { + if (definitions.isFunctionType(tpe)) { + val (args,r) = tpe.typeArgs.splitAt(tpe.typeArgs.length - 1); + args.mkXML("(", ", ", ")")(link) ++ Text(" => ") ++ link(r.head); + } else if (tpe.symbol == definitions.RepeatedParamClass) { + assert(tpe.typeArgs.length == 1); + link(tpe.typeArgs(0)) ++ Text("*"); + } else if (tpe.symbol == definitions.ByNameParamClass) { + assert(tpe.typeArgs.length == 1); + Text("=> ") ++ link(tpe.typeArgs(0)); + } else if (tpe.symbol.name.toString.startsWith("Tuple") && + tpe.symbol.owner.name == nme.scala_.toTypeName) { + tpe.typeArgs.mkXML("(", ", ", ")")(link); + } else link(decode(tpe.symbol)) ++ tpe.typeArgs.surround("[", "]")(link); + } else tpe match { + case PolyType(tparams,result) => + link(result) ++ tparams.surround("[", "]")(link); + case RefinedType(parents,_) => + val parents1 = + if ((parents.length > 1) && + (parents.head.symbol eq definitions.ObjectClass)) parents.tail; + else parents; + parents1.mkXML(Text(""), <code> with </code>, Text(""))(link); + case _ => { + link(decode(tpe.symbol)); + } + } + } + private def printIf[T](what : Option[T], before : String, after : String)(f : T => NodeSeq) : NodeSeq = { + if (what.isEmpty) return Text(""); + var seq : NodeSeq = Text(before); + seq = seq ++ f(what.get); + seq = seq ++ Text(after); + seq; + } + def bodyFor(entity : Entity)(implicit frame : Frame) : NodeSeq = { + var seq = {entity.typeParams.surround("[", "]")(e => { + Text(e.variance) ++ <em>{e.name}</em> ++ + {printIf(e.hi, " <: ", "")(link)} ++ + {printIf(e.lo, " >: ", "")(link)} + })} ++ printIf(entity.hi, " <: ", "")(link) ++ + printIf(entity.lo, " >: ", "")(link); + {entity.params.foreach(xs => { + seq = seq ++ xs.mkXML("(", ", ", ")")(arg => { + var seq : NodeSeq = { + val str = arg.flagsString.trim; + if (str.length == 0) NodeSeq.Empty; + else <code>{Text(str)} </code>; + } + seq = seq ++ <em>{arg.name}</em>; + seq = seq ++ Text(" : ") ++ link(arg.resultType.get); + seq; + }); + seq; + })}; + seq ++ {printIf(entity.resultType, " : ", "")(tpe => link(tpe))} + } + def extendsFor(entity : Entity)(implicit frame : Frame) : NodeSeq = { + if (entity.parents.isEmpty) NodeSeq.Empty; + else <code> extends </code>++ + entity.parents.mkXML(Text(""), <code> with </code>, Text(""))(link); + } + def parse(str: String): NodeSeq = { + new SpecialNode { + def label = "#PCDATA" + def toString(sb: StringBuilder): StringBuilder = { + sb.append(str.trim) + sb + } + } + } + def longHeader(entity : Entity)(implicit from : Frame) : NodeSeq = Group({ + anchor(entity.sym) ++ <dl> + <dt> + {attrsFor(entity)} + <code>{Text(entity.flagsString)}</code> + <code>{Text(entity.kind)}</code> + <em>{entity.sym.nameString}</em>{bodyFor(entity)} + </dt> + <dd>{extendsFor(entity)}</dd> + </dl>; + } ++ { + val cmnt = entity.decodeComment; + if (cmnt.isEmpty) NodeSeq.Empty; + else longComment(cmnt.get); + } ++ (entity match { + case entity : ClassOrObject => {classBody(entity)}; + case _ => NodeSeq.Empty; + }) ++ { + val overridden = entity.overridden; + if (!overridden.isEmpty) { + var seq : NodeSeq = Text("Overrides "); + seq = seq ++ overridden.mkXML("",", ", "")(sym => link(decode(sym.owner)) ++ Text(".") ++ link(sym)); + seq; + } else NodeSeq.Empty; + } ++ <hr/>); + + def longComment(cmnt : Comment) : NodeSeq = { + val attrs = <dl>{ + var seq : NodeSeq = NodeSeq.Empty; + cmnt.decodeAttributes.foreach{ + case (tag,xs) => + seq = seq ++ <dt style="margin:10px 0 0 20px;"> + {decodeTag(tag)}</dt> ++ {xs.flatMap{ + case (option,body) => <dd>{ + if (option == null) NodeSeq.Empty; + else decodeOption(tag, option); + }{parse(body)}</dd> + }} + }; + seq; + }</dl>; + <span><dl><dd>{parse(cmnt.body)}</dd></dl>{attrs}</span> + } + + def classBody(entity : ClassOrObject)(implicit from : Frame) : NodeSeq = <span> + {categories.mkXML("","\n","")(c => shortList(entity, c)) : NodeSeq} + {categories.mkXML("","\n","")(c => longList(entity, c)) : NodeSeq} + </span>; + + def longList(entity : ClassOrObject,category : Category)(implicit from : Frame) : NodeSeq = { + val xs = entity.members(category); + if (!xs.elements.hasNext) return NodeSeq.Empty; + Group( + <table cellpadding="3" class="member-detail" summary=""> + <tr><td class="title">{Text(category.label)} Details</td></tr> + </table> + <div>{xs.mkXML("","\n","")(m => longHeader(m))}</div>); + } + + def shortList(entity : ClassOrObject, category : Category)(implicit from : Frame) : NodeSeq = { + val xs = entity.members(category); + var seq : NodeSeq = NodeSeq.Empty; + if (xs.elements.hasNext) { + // alphabetic + val set = new scala.collection.jcl.TreeSet[entity.Member]()(mA => new Ordered[entity.Member] { + def compare(mB : entity.Member) : Int = { + if (mA eq mB) return 0; + val diff = mA.name compare mB.name; + if (diff != 0) return diff; + val diff0 = mA.hashCode - mB.hashCode; + assert(diff0 != 0); + return diff0; + } + }); + set addAll xs; + seq = seq ++ <table cellpadding="3" class="member" summary=""> + <tr><td colspan="2" class="title">{Text(category.label + " Summary")}</td></tr> + {set.mkXML("","\n","")(mmbr => shortHeader(mmbr))} + </table> + } + // list inherited members...if any. + for (val (tpe,members) <- entity.inherited) { + val members0 = members.filter(m => category.f(m.sym)); + if (!members0.isEmpty) seq = seq ++ <table cellpadding="3" class="inherited" summary=""> + <tr><td colspan="2" class="title"> + {Text(category.plural + " inherited from ") ++ link(tpe)} + </td></tr> + <tr><td colspan="2" class="signature"> + {members0.mkXML((""), (", "), (""))(m => { + link(decode(m.sym)) ++ + (if (m.sym.hasFlag(symtab.Flags.ABSTRACT) || m.sym.hasFlag(symtab.Flags.DEFERRED)) { + Text(" (abstract)"); + } else NodeSeq.Empty); + })} + </td></tr> + </table> + } + seq; + } + + protected def decodeOption(tag : String, string : String) : NodeSeq = <code>{Text(string + " - ")}</code>; + protected def decodeTag(tag : String) : String = + "" + Character.toUpperCase(tag.charAt(0)) + tag.substring(1); + + def shortHeader(entity : Entity)(implicit from : Frame) : NodeSeq = { + <tr> + <td valign="top" class="modifiers"> + <code>{Text(entity.flagsString)} {Text(entity.kind)}</code> + </td> + <td class="signature"> + <em>{link(decode(entity.sym))}</em>{bodyFor(entity) ++ extendsFor(entity)} + { + val cmnt = entity.decodeComment; + if (cmnt.isEmpty) NodeSeq.Empty; + else <br>{parse(cmnt.get.body)}</br>; + } + </td> + </tr> + } + def attrsFor(entity : Entity)(implicit from : Frame) : NodeSeq = { + def attrFor(attr: AnnotationInfo[Constant]): Node = { + val buf = new StringBuilder + val AnnotationInfo(tpe, args, nvPairs) = attr + val name = link(decode(tpe.symbol)) + if (!args.isEmpty) + buf.append(args.map(.escapedStringValue).mkString("(", ",", ")")) + if (!nvPairs.isEmpty) + for (val ((name, value), index) <- nvPairs.zipWithIndex) { + if (index > 0) + buf.append(", ") + buf.append(name).append(" = ").append(value) + } + Group(name ++ Text(buf.toString)) + } + if (entity.sym.hasFlag(symtab.Flags.CASE)) NodeSeq.Empty; + else { + val sep = Text("@") + for (val attr <- entity.attributes) + yield Group({(sep ++ attrFor(attr) ++ <br/>)}) + } + } +} |