summaryrefslogtreecommitdiff
path: root/src/compiler/scala
diff options
context:
space:
mode:
authorGilles Dubochet <gilles.dubochet@epfl.ch>2010-03-24 16:59:22 +0000
committerGilles Dubochet <gilles.dubochet@epfl.ch>2010-03-24 16:59:22 +0000
commitd43ccc679de41aca085072d96a61e363e5e23e34 (patch)
tree803b33d6e9e7c7116a97687d2963c666571a2bd9 /src/compiler/scala
parentc7c8981b43a6df71e088b444dacf53d609a21ffc (diff)
downloadscala-d43ccc679de41aca085072d96a61e363e5e23e34.tar.gz
scala-d43ccc679de41aca085072d96a61e363e5e23e34.tar.bz2
scala-d43ccc679de41aca085072d96a61e363e5e23e34.zip
[scaladoc] Improved Scaladoc comment syntax, co...
[scaladoc] Improved Scaladoc comment syntax, contributed by Pedro Furlanetto. - Wiki syntax supports nested, numbered and unnumbered lists; - Wiki syntax supports links (entity links currently require fully qualified names); - Stars no longer are mandatory to start comment lines. Already reviewed by dubochet; no review.
Diffstat (limited to 'src/compiler/scala')
-rw-r--r--src/compiler/scala/tools/nsc/doc/DocFactory.scala2
-rw-r--r--src/compiler/scala/tools/nsc/doc/Universe.scala8
-rw-r--r--src/compiler/scala/tools/nsc/doc/html/HtmlFactory.scala10
-rw-r--r--src/compiler/scala/tools/nsc/doc/html/HtmlPage.scala14
-rw-r--r--src/compiler/scala/tools/nsc/doc/html/page/Index.scala10
-rw-r--r--src/compiler/scala/tools/nsc/doc/html/resource/lib/template.css27
-rw-r--r--src/compiler/scala/tools/nsc/doc/html/resource/lib/template.js2
-rw-r--r--src/compiler/scala/tools/nsc/doc/model/Entity.scala3
-rw-r--r--src/compiler/scala/tools/nsc/doc/model/ModelFactory.scala66
-rw-r--r--src/compiler/scala/tools/nsc/doc/model/comment/Body.scala5
-rw-r--r--src/compiler/scala/tools/nsc/doc/model/comment/Comment.scala3
-rw-r--r--src/compiler/scala/tools/nsc/doc/model/comment/CommentFactory.scala428
12 files changed, 337 insertions, 241 deletions
diff --git a/src/compiler/scala/tools/nsc/doc/DocFactory.scala b/src/compiler/scala/tools/nsc/doc/DocFactory.scala
index b70d8c10ec..c4d231d750 100644
--- a/src/compiler/scala/tools/nsc/doc/DocFactory.scala
+++ b/src/compiler/scala/tools/nsc/doc/DocFactory.scala
@@ -53,8 +53,8 @@ class DocFactory(val reporter: Reporter, val settings: doc.Settings) { processor
assert(settings.docformat.value == "html")
if (!reporter.hasErrors) {
val modelFactory = (new model.ModelFactory(compiler, settings))
- val htmlFactory = (new html.HtmlFactory(reporter, settings))
val docModel = modelFactory.makeModel
+ val htmlFactory = (new html.HtmlFactory(docModel))
println("model contains " + modelFactory.templatesCount + " documentable templates")
htmlFactory generate docModel
}
diff --git a/src/compiler/scala/tools/nsc/doc/Universe.scala b/src/compiler/scala/tools/nsc/doc/Universe.scala
new file mode 100644
index 0000000000..666a06dc4b
--- /dev/null
+++ b/src/compiler/scala/tools/nsc/doc/Universe.scala
@@ -0,0 +1,8 @@
+package scala.tools.nsc.doc
+
+/**
+ * Class to hold common dependencies across Scaladoc classes.
+ * @author Pedro Furlanetto
+ * @author Gilles Dubochet
+ */
+class Universe(val settings: Settings, val rootPackage: model.Package)
diff --git a/src/compiler/scala/tools/nsc/doc/html/HtmlFactory.scala b/src/compiler/scala/tools/nsc/doc/html/HtmlFactory.scala
index a78c122798..f01984052f 100644
--- a/src/compiler/scala/tools/nsc/doc/html/HtmlFactory.scala
+++ b/src/compiler/scala/tools/nsc/doc/html/HtmlFactory.scala
@@ -16,19 +16,19 @@ import scala.collection._
/** A class that can generate Scaladoc sites to some fixed root folder.
* @author David Bernard
* @author Gilles Dubochet */
-class HtmlFactory(val reporter: Reporter, val settings: Settings) {
+class HtmlFactory(val universe: Universe) {
/** The character encoding to be used for generated Scaladoc sites. This value is currently always UTF-8. */
def encoding: String = "UTF-8"
/** The character encoding to be used for generated Scaladoc sites. This value is defined by the generator's
* settings. */
- def siteRoot: File = new File(settings.outdir.value)
+ def siteRoot: File = new File(universe.settings.outdir.value)
/** Generates the Scaladoc site for a model into the site toot. A scaladoc site is a set of HTML and related files
* that document a model extracted from a compiler run.
* @param model The model to generate in the form of a sequence of packages. */
- def generate(modelRoot: Package): Unit = {
+ def generate(universe: Universe): Unit = {
def copyResource(subPath: String) {
val buf = new Array[Byte](1024)
@@ -67,7 +67,7 @@ class HtmlFactory(val reporter: Reporter, val settings: Settings) {
copyResource("lib/filter_box_right.png")
copyResource("lib/remove.png")
- new page.Index(modelRoot) writeFor this
+ new page.Index(universe) writeFor this
val written = mutable.HashSet.empty[DocTemplateEntity]
@@ -77,7 +77,7 @@ class HtmlFactory(val reporter: Reporter, val settings: Settings) {
tpl.templates filter { t => !(written contains t) } map (writeTemplate(_))
}
- writeTemplate(modelRoot)
+ writeTemplate(universe.rootPackage)
}
diff --git a/src/compiler/scala/tools/nsc/doc/html/HtmlPage.scala b/src/compiler/scala/tools/nsc/doc/html/HtmlPage.scala
index 520bcb9a36..cea8d04caa 100644
--- a/src/compiler/scala/tools/nsc/doc/html/HtmlPage.scala
+++ b/src/compiler/scala/tools/nsc/doc/html/HtmlPage.scala
@@ -133,8 +133,8 @@ abstract class HtmlPage { thisPage =>
case Code(data) => <pre>{ Unparsed(data) }</pre>
case UnorderedList(items) =>
<ul>{ listItemsToHtml(items) }</ul>
- case OrderedList(items) =>
- <ol>{ listItemsToHtml(items) }</ol>
+ case OrderedList(items, listStyle) =>
+ <ol class={ listStyle }>{ listItemsToHtml(items) }</ol>
case DefinitionList(items) =>
<dl>{items map { case (t, d) => <dt>{ inlineToHtml(t) }</dt><dd>{ blockToHtml(d) }</dd> } }</dl>
case HorizontalRule() =>
@@ -144,7 +144,7 @@ abstract class HtmlPage { thisPage =>
def listItemsToHtml(items: Seq[Block]) =
items.foldLeft(xml.NodeSeq.Empty){ (xmlList, item) =>
item match {
- case OrderedList(_) | UnorderedList(_) => // html requires sub ULs to be put into the last LI
+ case OrderedList(_, _) | UnorderedList(_) => // html requires sub ULs to be put into the last LI
xmlList.init ++ <li>{ xmlList.last.child ++ blockToHtml(item) }</li>
case Paragraph(inline) =>
xmlList :+ <li>{ inlineToHtml(inline) }</li> // LIs are blocks, no need to use Ps
@@ -154,14 +154,14 @@ abstract class HtmlPage { thisPage =>
}
def inlineToHtml(inl: Inline): NodeSeq = inl match {
- //case URLLink(url, text) => <a href={url}>{if(text.isEmpty)url else inlineSeqsToXml(text)}</a>
case Chain(items) => items flatMap (inlineToHtml(_))
case Italic(in) => <i>{ inlineToHtml(in) }</i>
case Bold(in) => <b>{ inlineToHtml(in) }</b>
case Underline(in) => <u>{ inlineToHtml(in) }</u>
case Superscript(in) => <sup>{ inlineToHtml(in) }</sup>
case Subscript(in) => <sub>{ inlineToHtml(in) }</sub>
- case Link(raw,title) => <a href={ raw }>{ title.getOrElse(raw) }</a> // TODO link to target
+ case Link(raw, title) => <a href={ raw }>{ inlineToHtml(title) }</a>
+ case EntityLink(entity) => templateToHtml(entity)
case Monospace(text) => <code>{ Unparsed(text) }</code>
case Text(text) => Unparsed(text)
}
@@ -183,7 +183,7 @@ abstract class HtmlPage { thisPage =>
val (tpl, width) = tpe.refEntity(inPos)
(tpl match {
case dtpl:DocTemplateEntity if hasLinks =>
- <a href={ relativeLinkTo(tpl) } class="extype" name={ dtpl.qualifiedName }>{
+ <a href={ relativeLinkTo(dtpl) } class="extype" name={ dtpl.qualifiedName }>{
string.slice(inPos, inPos + width)
}</a>
case tpl =>
@@ -199,7 +199,7 @@ abstract class HtmlPage { thisPage =>
/** Returns the HTML code that represents the template in `tpl` as a hyperlinked name. */
def templateToHtml(tpl: TemplateEntity) = tpl match {
case dTpl: DocTemplateEntity =>
- <a href={ relativeLinkTo(dTpl) }>{ dTpl.name }</a>
+ <a href={ relativeLinkTo(dTpl) } class="extype" name={ dTpl.qualifiedName }>{ dTpl.name }</a>
case ndTpl: NoDocTemplate =>
xml.Text(ndTpl.name)
}
diff --git a/src/compiler/scala/tools/nsc/doc/html/page/Index.scala b/src/compiler/scala/tools/nsc/doc/html/page/Index.scala
index cdc040c15d..7bbd8ef821 100644
--- a/src/compiler/scala/tools/nsc/doc/html/page/Index.scala
+++ b/src/compiler/scala/tools/nsc/doc/html/page/Index.scala
@@ -13,11 +13,15 @@ import model._
import scala.collection._
import scala.xml._
-class Index(modelRoot: Package) extends HtmlPage {
+class Index(universe: Universe) extends HtmlPage {
def path = List("index.html")
- def title = "Scaladoc: all classes and objects"
+ def title = {
+ val s = universe.settings
+ ( if (!s.doctitle.isDefault) s.doctitle.value else "" ) +
+ ( if (!s.docversion.isDefault) (" " + s.docversion.value) else "" )
+ }
def headers =
<xml:group>
@@ -74,7 +78,7 @@ class Index(modelRoot: Package) extends HtmlPage {
}</ol>
</xml:group>
}
- packageElem(modelRoot)
+ packageElem(universe.rootPackage)
}</div>
</div>
<div id="content">
diff --git a/src/compiler/scala/tools/nsc/doc/html/resource/lib/template.css b/src/compiler/scala/tools/nsc/doc/html/resource/lib/template.css
index 64c3e0ad8a..a23c8b6402 100644
--- a/src/compiler/scala/tools/nsc/doc/html/resource/lib/template.css
+++ b/src/compiler/scala/tools/nsc/doc/html/resource/lib/template.css
@@ -176,12 +176,37 @@ div.members > ol > li {
margin: 2px 0 2px 0;
}
-.cmt ul, .cmt ol {
+.cmt ul {
display: block;
list-style: circle;
padding-left:20px;
}
+.cmt ol {
+ display: block;
+ padding-left:20px;
+}
+
+.cmt ol.decimal {
+ list-style: decimal;
+}
+
+.cmt ol.lowerAlpha {
+ list-style: lower-alpha;
+}
+
+.cmt ol.upperAlpha {
+ list-style: upper-alpha;
+}
+
+.cmt ol.lowerRoman {
+ list-style: lower-roman;
+}
+
+.cmt ol.upperRoman {
+ list-style: upper-roman;
+}
+
.cmt li {
display:list-item;
}
diff --git a/src/compiler/scala/tools/nsc/doc/html/resource/lib/template.js b/src/compiler/scala/tools/nsc/doc/html/resource/lib/template.js
index 4ff278e80b..cb159a97c0 100644
--- a/src/compiler/scala/tools/nsc/doc/html/resource/lib/template.js
+++ b/src/compiler/scala/tools/nsc/doc/html/resource/lib/template.js
@@ -33,7 +33,7 @@ $(document).ready(function(){
filterInherit();
});
//http://flowplayer.org/tools/tooltip.html
- $(".signature .symbol .extype").tooltip({
+ $(".extype").tooltip({
tip: "#tooltip",
position:"top center",
onBeforeShow: function(ev) {
diff --git a/src/compiler/scala/tools/nsc/doc/model/Entity.scala b/src/compiler/scala/tools/nsc/doc/model/Entity.scala
index 2362016152..f3cf0518f1 100644
--- a/src/compiler/scala/tools/nsc/doc/model/Entity.scala
+++ b/src/compiler/scala/tools/nsc/doc/model/Entity.scala
@@ -111,6 +111,9 @@ trait Package extends Object {
def packages: List[Package]
}
+/** A package represent the root of Entities hierarchy */
+trait RootPackage extends Package
+
trait NonTemplateMemberEntity extends MemberEntity {
def isUseCase: Boolean
}
diff --git a/src/compiler/scala/tools/nsc/doc/model/ModelFactory.scala b/src/compiler/scala/tools/nsc/doc/model/ModelFactory.scala
index 44b542db0a..f7fe5678fe 100644
--- a/src/compiler/scala/tools/nsc/doc/model/ModelFactory.scala
+++ b/src/compiler/scala/tools/nsc/doc/model/ModelFactory.scala
@@ -10,8 +10,10 @@ import scala.collection._
import symtab.Flags
+import model.{ RootPackage => RootPackageEntity }
+
/** This trait extracts all required information for documentation from compilation units */
-class ModelFactory(val global: Global, val settings: doc.Settings) { extractor =>
+class ModelFactory(val global: Global, val settings: doc.Settings) extends CommentFactory { thisFactory =>
import global._
import definitions.{ ObjectClass, ScalaObjectClass, RootPackage, EmptyPackage }
@@ -19,41 +21,26 @@ class ModelFactory(val global: Global, val settings: doc.Settings) { extractor =
private var droppedPackages = 0
def templatesCount = templatesCache.size - droppedPackages
- /** */
- def makeModel: Package =
- makePackage(RootPackage, null) getOrElse abort("no documentable class found in compilation units")
-
- object commentator {
-
- val factory = new CommentFactory(reporter)
-
- private val commentCache = mutable.HashMap.empty[(Symbol, TemplateImpl), Comment]
-
- def registeredUseCase(sym: Symbol, inTpl: => TemplateImpl, docStr: String, docPos: Position): Symbol = {
- commentCache += (sym, inTpl) -> factory.parse(docStr, docPos)
- sym
- }
-
- def comment(sym: Symbol, inTpl: => DocTemplateImpl): Option[Comment] = {
- val key = (sym, inTpl)
- if (commentCache isDefinedAt key)
- Some(commentCache(key))
- else { // not reached for use-case comments
- val rawComment = expandedDocComment(sym, inTpl.sym)
- if (rawComment == "") None else {
- val c = factory.parse(rawComment, docCommentPos(sym))
- commentCache += (sym, inTpl) -> c
- Some(c)
- }
- }
- }
+ private var modelFinished = false
+ /** */
+ def makeModel: Universe = {
+ val rootPackage =
+ makeRootPackage getOrElse { throw new Error("no documentable class found in compilation units") }
+ val universe = new Universe(settings, rootPackage)
+ modelFinished = true
+ universe
}
/** */
protected val templatesCache =
new mutable.LinkedHashMap[(Symbol, TemplateImpl), DocTemplateImpl]
+ def findTemplate(query: String): Option[DocTemplateImpl] = {
+ if (!modelFinished) throw new Error("cannot find template in unfinished universe")
+ templatesCache.values find { tpl => tpl.qualifiedName == query && !tpl.isObject }
+ }
+
def optimize(str: String): String =
if (str.length < 16) str.intern else str
@@ -87,13 +74,13 @@ class ModelFactory(val global: Global, val settings: doc.Settings) { extractor =
/** Provides a default implementation for instances of the `MemberEntity` type. It must be instantiated as a
* `SymbolicEntity` to access the compiler symbol that underlies the entity. */
abstract class MemberImpl(sym: Symbol, inTpl: => DocTemplateImpl) extends EntityImpl(sym, inTpl) with MemberEntity {
- val comment =
- if (inTpl == null) None else commentator.comment(sym, inTpl)
+ lazy val comment =
+ if (inTpl == null) None else thisFactory.comment(sym, inTpl)
override def inTemplate = inTpl
override def toRoot: List[MemberImpl] = this :: inTpl.toRoot
def inDefinitionTemplates =
if (inTpl == null)
- makePackage(RootPackage, null).toList
+ makeRootPackage.toList
else
makeTemplate(sym.owner) :: (sym.allOverriddenSymbols map { inhSym => makeTemplate(inhSym.owner) })
def visibility = {
@@ -121,7 +108,7 @@ class ModelFactory(val global: Global, val settings: doc.Settings) { extractor =
}
def deprecation =
if (sym.isDeprecated && sym.deprecationMessage.isDefined)
- Some(commentator.factory.parseWiki(sym.deprecationMessage.get, NoPosition))
+ Some(parseWiki(sym.deprecationMessage.get, NoPosition))
else if (sym.isDeprecated)
Some(Body(Nil))
else if (comment.isDefined)
@@ -209,6 +196,8 @@ class ModelFactory(val global: Global, val settings: doc.Settings) { extractor =
val packages = members partialMap { case p: Package => p }
}
+ abstract class RootPackageImpl(sym: Symbol) extends PackageImpl(sym, null) with RootPackageEntity
+
abstract class NonTemplateMemberImpl(sym: Symbol, inTpl: => DocTemplateImpl) extends MemberImpl(sym, inTpl) with NonTemplateMemberEntity {
override def qualifiedName = optimize(inTemplate.qualifiedName + "#" + name)
override def definitionName = optimize(inDefinitionTemplates.head.qualifiedName + "#" + name)
@@ -232,6 +221,9 @@ class ModelFactory(val global: Global, val settings: doc.Settings) { extractor =
aSym
}
+ def makeRootPackage: Option[PackageImpl] =
+ makePackage(RootPackage, null)
+
/** Creates a package entity for the given symbol or returns `None` if the symbol does not denote a package that
* contains at least one ''documentable'' class, trait or object. Creating a package entity */
def makePackage(aSym: Symbol, inTpl: => PackageImpl): Option[PackageImpl] = {
@@ -241,7 +233,7 @@ class ModelFactory(val global: Global, val settings: doc.Settings) { extractor =
else {
val pack =
if (bSym == RootPackage)
- new PackageImpl(bSym, null) {
+ new RootPackageImpl(bSym) {
override val name = "root"
override def inTemplate = this
override def toRoot = this :: Nil
@@ -278,7 +270,7 @@ class ModelFactory(val global: Global, val settings: doc.Settings) { extractor =
val bSym = normalizeTemplate(aSym)
if (bSym.isPackage) inTpl match {
case inPkg: PackageImpl => makePackage(bSym, inPkg) getOrElse (new NoDocTemplateImpl(bSym, inPkg))
- case _ => abort("'" + bSym + "' must be in a package")
+ case _ => throw new Error("'" + bSym + "' must be in a package")
}
else if ((bSym.sourceFile != null) && bSym.isPublic && !bSym.isLocal) inTpl match {
case inDTpl: DocTemplateImpl => makeDocTemplate(bSym, inDTpl)
@@ -317,7 +309,7 @@ class ModelFactory(val global: Global, val settings: doc.Settings) { extractor =
def isCaseClass = sym.isClass && sym.hasFlag(Flags.CASE)
}
else
- abort("'" + bSym + "' that isn't a class, trait or object cannot be built as a documentable template")
+ throw new Error("'" + bSym + "' that isn't a class, trait or object cannot be built as a documentable template")
}
/** */
@@ -385,7 +377,7 @@ class ModelFactory(val global: Global, val settings: doc.Settings) { extractor =
Nil
else {
val allSyms = useCases(aSym, inTpl.sym) map { case (bSym, bComment, bPos) =>
- commentator.registeredUseCase(bSym, inTpl, bComment, bPos)
+ addCommentBody(bSym, inTpl, bComment, bPos)
}
(allSyms ::: List(aSym)) flatMap (makeMember0(_))
}
diff --git a/src/compiler/scala/tools/nsc/doc/model/comment/Body.scala b/src/compiler/scala/tools/nsc/doc/model/comment/Body.scala
index c87ecb7d4d..7d2aee2d98 100644
--- a/src/compiler/scala/tools/nsc/doc/model/comment/Body.scala
+++ b/src/compiler/scala/tools/nsc/doc/model/comment/Body.scala
@@ -19,7 +19,7 @@ final case class Title(text: Inline, level: Int) extends Block
final case class Paragraph(text: Inline) extends Block
final case class Code(data: String) extends Block
final case class UnorderedList(items: Seq[Block]) extends Block
-final case class OrderedList(items: Seq[Block]) extends Block
+final case class OrderedList(items: Seq[Block], style: String) extends Block
final case class DefinitionList(items: SortedMap[Inline, Block]) extends Block
final case class HorizontalRule() extends Block
@@ -32,6 +32,7 @@ final case class Bold(text: Inline) extends Inline
final case class Underline(text: Inline) extends Inline
final case class Superscript(text: Inline) extends Inline
final case class Subscript(text: Inline) extends Inline
-final case class Link(target: String, title: Option[String]) extends Inline
+final case class Link(target: String, title: Inline) extends Inline
+final case class EntityLink(target: TemplateEntity) extends Inline
final case class Monospace(text: String) extends Inline
final case class Text(text: String) extends Inline
diff --git a/src/compiler/scala/tools/nsc/doc/model/comment/Comment.scala b/src/compiler/scala/tools/nsc/doc/model/comment/Comment.scala
index 762ff843cf..7fe2e58991 100644
--- a/src/compiler/scala/tools/nsc/doc/model/comment/Comment.scala
+++ b/src/compiler/scala/tools/nsc/doc/model/comment/Comment.scala
@@ -65,7 +65,8 @@ abstract class Comment {
override def toString =
body.toString + "\n" +
(authors map ("@author " + _.toString)).mkString("\n") +
- (result map ("@return " + _.toString)).mkString
+ (result map ("@return " + _.toString)).mkString("\n") +
+ (version map ("@version " + _.toString)).mkString
}
diff --git a/src/compiler/scala/tools/nsc/doc/model/comment/CommentFactory.scala b/src/compiler/scala/tools/nsc/doc/model/comment/CommentFactory.scala
index e39942e029..5c5b320c34 100644
--- a/src/compiler/scala/tools/nsc/doc/model/comment/CommentFactory.scala
+++ b/src/compiler/scala/tools/nsc/doc/model/comment/CommentFactory.scala
@@ -19,28 +19,42 @@ import scala.annotation.switch
*
* @author Manohar Jonnalagedda
* @author Gilles Dubochet */
-final class CommentFactory(val reporter: Reporter) { parser =>
+trait CommentFactory { thisFactory: ModelFactory with CommentFactory =>
- val endOfText = '\u0003'
- val endOfLine = '\u000A'
+ val global: Global
+ import global.reporter
- /** Something that should not have happened, happened, and Scaladoc should exit. */
- protected def oops(msg: String): Nothing =
- throw FatalError("program logic: " + msg)
+ private val commentCache = mutable.HashMap.empty[(global.Symbol, TemplateImpl), Comment]
- protected val CleanHtml =
- new Regex("""</?(p|h\d|pre|dl|dt|dd|ol|ul|li|blockquote|div|hr|br|br).*/?>""")
+ def addCommentBody(sym: global.Symbol, inTpl: => TemplateImpl, docStr: String, docPos: global.Position): global.Symbol = {
+ commentCache += (sym, inTpl) -> parse(docStr, docPos)
+ sym
+ }
- protected val ShortLineEnd =
- new Regex("""\.|</?.*>""")
+ def comment(sym: global.Symbol, inTpl: => DocTemplateImpl): Option[Comment] = {
+ val key = (sym, inTpl)
+ if (commentCache isDefinedAt key)
+ Some(commentCache(key))
+ else { // not reached for use-case comments
+ val rawComment = global.expandedDocComment(sym, inTpl.sym).trim
+ if (rawComment == "") None else {
+ val c = parse(rawComment, global.docCommentPos(sym))
+ commentCache += (sym, inTpl) -> c
+ Some(c)
+ }
+ }
+ }
+
+ protected val endOfText = '\u0003'
+ protected val endOfLine = '\u000A'
- /** The body of a comment, dropping start and end markers. */
- protected val CleanComment =
- new Regex("""(?s)\s*/\*\*((?:[^\*]\*)*)\*/\s*""")
+ /** Something that should not have happened, happened, and Scaladoc should exit. */
+ protected def oops(msg: String): Nothing =
+ throw FatalError("program logic: " + msg)
- /** The body of a line, dropping the start star-marker, one leading whitespace and all trailing whitespace. */
+ /** The body of a line, dropping the (optional) start star-marker, one leading whitespace and all trailing whitespace. */
protected val CleanCommentLine =
- new Regex("""\*\s?(.*)""")
+ new Regex("""(?:\s*\*\s?)?(.*)""")
/** A Scaladoc tag not linked to a symbol. Returns the name of the tag, and the rest of the line. */
protected val SimpleTag =
@@ -71,155 +85,157 @@ final class CommentFactory(val reporter: Reporter) { parser =>
/** Parses a raw comment string into a `Comment` object.
* @param comment The raw comment string (including start and end markers) to be parsed.
* @param pos The position of the comment in source. */
- def parse(comment: String, pos: Position): Comment = {
+ protected def parse(comment: String, pos: Position): Comment = {
/** The cleaned raw comment as a list of lines. Cleaning removes comment start and end markers, line start markers
* and unnecessary whitespace. */
val cleaned: List[String] = {
- def cleanLine(line: String): Option[String] = {
- line.trim match {
- case CleanCommentLine(ctl) => Some(ctl)
- case "" =>
- None
+ def cleanLine(line: String): String = {
+ //replaceAll removes trailing whitespaces
+ line.replaceAll("""\s+$""", "") match {
+ case "" => "" // Empty lines are require to keep paragraphs
+ case CleanCommentLine(ctl) => ctl
case tl =>
- reporter.warning(pos, "Comment has no start-of-line marker ('*')")
- Some(tl)
+ reporter.warning(pos, "Please re-check this line of the comment")
+ tl
+ }
}
- }
- comment.trim.stripPrefix("/*").stripSuffix("*/").lines.toList flatMap (cleanLine(_))
- }
-
- /** Parses a comment (in the form of a list of lines) to a Comment instance, recursively on lines. To do so, it
- * splits the whole comment into main body and tag bodies, then runs the `WikiParser` on each body before creating
- * the comment instance.
- *
- * @param body The body of the comment parsed until now.
- * @param tags All tags parsed until now.
- * @param lastTagKey The last parsed tag, or `None` if the tag section hasn't started. Lines that are not tagged
- * are part of the previous tag or, if none exists, of the body.
- * @param remaining The lines that must still recursively be parsed.
- * @param inCodeBlock Whether the next line is part of a code block (in which no tags must be read). */
- def parse0(docBody: String, tags: Map[TagKey, List[String]], lastTagKey: Option[TagKey], remaining: List[String], inCodeBlock: Boolean): Comment = {
- remaining match {
-
- case CodeBlockStart(before, after) :: ls if (!inCodeBlock) =>
- if (before.trim != "")
- parse0(docBody, tags, lastTagKey, before :: ("{{{" + after) :: ls, false)
- else if (after.trim != "")
- parse0(docBody, tags, lastTagKey, after :: ls, true)
- else
- parse0(docBody, tags, lastTagKey, ls, true)
+ comment.trim.stripPrefix("/*").stripSuffix("*/").lines.toList map (cleanLine(_))
+ }
+
+ /** Parses a comment (in the form of a list of lines) to a Comment instance, recursively on lines. To do so, it
+ * splits the whole comment into main body and tag bodies, then runs the `WikiParser` on each body before creating
+ * the comment instance.
+ *
+ * @param body The body of the comment parsed until now.
+ * @param tags All tags parsed until now.
+ * @param lastTagKey The last parsed tag, or `None` if the tag section hasn't started. Lines that are not tagged
+ * are part of the previous tag or, if none exists, of the body.
+ * @param remaining The lines that must still recursively be parsed.
+ * @param inCodeBlock Whether the next line is part of a code block (in which no tags must be read). */
+ def parse0(docBody: String, tags: Map[TagKey, List[String]], lastTagKey: Option[TagKey], remaining: List[String], inCodeBlock: Boolean): Comment = {
+ remaining match {
+
+ case CodeBlockStart(before, after) :: ls if (!inCodeBlock) =>
+ if (before.trim != "")
+ parse0(docBody, tags, lastTagKey, before :: ("{{{" + after) :: ls, false)
+ else if (after.trim != "")
+ parse0(docBody, tags, lastTagKey, after :: ls, true)
+ else
+ parse0(docBody, tags, lastTagKey, ls, true)
+
+ case CodeBlockEnd(before, after) :: ls =>
+ if (before.trim != "")
+ parse0(docBody, tags, lastTagKey, before :: ("}}}" + after) :: ls, true)
+ else if (after.trim != "")
+ parse0(docBody, tags, lastTagKey, after :: ls, false)
+ else
+ parse0(docBody, tags, lastTagKey, ls, false)
+
+ case SymbolTag(name, sym, body) :: ls if (!inCodeBlock) =>
+ val key = SymbolTagKey(name, sym)
+ val value = body :: tags.getOrElse(key, Nil)
+ parse0(docBody, tags + (key -> value), Some(key), ls, inCodeBlock)
+
+ case SimpleTag(name, body) :: ls if (!inCodeBlock) =>
+ val key = SimpleTagKey(name)
+ val value = body :: tags.getOrElse(key, Nil)
+ parse0(docBody, tags + (key -> value), Some(key), ls, inCodeBlock)
+
+ case line :: ls if (lastTagKey.isDefined) =>
+ val key = lastTagKey.get
+ val value =
+ ((tags get key): @unchecked) match {
+ case Some(b :: bs) => (b + endOfLine + line) :: bs
+ case None => oops("lastTagKey set when no tag exists for key")
+ }
+ parse0(docBody, tags + (key -> value), lastTagKey, ls, inCodeBlock)
- case CodeBlockEnd(before, after) :: ls =>
- if (before.trim != "")
- parse0(docBody, tags, lastTagKey, before :: ("}}}" + after) :: ls, true)
- else if (after.trim != "")
- parse0(docBody, tags, lastTagKey, after :: ls, false)
- else
- parse0(docBody, tags, lastTagKey, ls, false)
-
- case SymbolTag(name, sym, body) :: ls if (!inCodeBlock) =>
- val key = SymbolTagKey(name, sym)
- val value = body :: tags.getOrElse(key, Nil)
- parse0(docBody, tags + (key -> value), Some(key), ls, inCodeBlock)
-
- case SimpleTag(name, body) :: ls if (!inCodeBlock) =>
- val key = SimpleTagKey(name)
- val value = body :: tags.getOrElse(key, Nil)
- parse0(docBody, tags + (key -> value), Some(key), ls, inCodeBlock)
-
- case line :: ls if (lastTagKey.isDefined) =>
- val key = lastTagKey.get
- val value =
- ((tags get key): @unchecked) match {
- case Some(b :: bs) => (b + endOfLine + line) :: bs
- case None => oops("lastTagKey set when no tag exists for key")
- }
- parse0(docBody, tags + (key -> value), lastTagKey, ls, inCodeBlock)
+ case line :: ls =>
+ val newBody = if (docBody == "") line else docBody + endOfLine + line
+ parse0(newBody, tags, lastTagKey, ls, inCodeBlock)
- case line :: ls =>
- val newBody = if (docBody == "") line else docBody + endOfLine + line
- parse0(newBody, tags, lastTagKey, ls, inCodeBlock)
+ case Nil =>
- case Nil =>
+ val bodyTags: mutable.Map[TagKey, List[Body]] =
+ mutable.Map(tags mapValues (_ map (parseWiki(_, pos))) toSeq: _*)
- val bodyTags: mutable.Map[TagKey, List[Body]] =
- mutable.Map(tags mapValues (_ map (parseWiki(_, pos))) toSeq: _*)
+ def oneTag(key: SimpleTagKey): Option[Body] =
+ ((bodyTags remove key): @unchecked) match {
+ case Some(r :: rs) =>
+ if (!rs.isEmpty) reporter.warning(pos, "Only one '@" + key.name + "' tag is allowed")
+ Some(r)
+ case None => None
+ }
- def oneTag(key: SimpleTagKey): Option[Body] =
- ((bodyTags remove key): @unchecked) match {
- case Some(r :: rs) =>
- if (!rs.isEmpty) reporter.warning(pos, "Only one '@" + key.name + "' tag is allowed")
- Some(r)
- case None => None
+ def allTags(key: SimpleTagKey): List[Body] =
+ (bodyTags remove key) getOrElse Nil
+
+ def allSymsOneTag(key: TagKey): Map[String, Body] = {
+ val keys: Seq[SymbolTagKey] =
+ bodyTags.keys.toSeq flatMap {
+ case stk: SymbolTagKey if (stk.name == key.name) => Some(stk)
+ case stk: SimpleTagKey if (stk.name == key.name) =>
+ reporter.warning(pos, "Tag '@" + stk.name + "' must be followed by a symbol name")
+ None
+ case _ => None
+ }
+ val pairs: Seq[(String, Body)] =
+ for (key <- keys) yield {
+ val bs = (bodyTags remove key).get
+ if (bs.length > 1)
+ reporter.warning(pos, "Only one '@" + key.name + "' tag for symbol " + key.symbol + " is allowed")
+ (key.symbol, bs.head)
+ }
+ Map.empty[String, Body] ++ pairs
}
- def allTags(key: SimpleTagKey): List[Body] =
- (bodyTags remove key) getOrElse Nil
-
- def allSymsOneTag(key: TagKey): Map[String, Body] = {
- val keys: Seq[SymbolTagKey] =
- bodyTags.keys.toSeq flatMap {
- case stk: SymbolTagKey if (stk.name == key.name) => Some(stk)
- case stk: SimpleTagKey if (stk.name == key.name) =>
- reporter.warning(pos, "Tag '@" + stk.name + "' must be followed by a symbol name")
- None
- case _ => None
- }
- val pairs: Seq[(String, Body)] =
- for (key <- keys) yield {
- val bs = (bodyTags remove key).get
- if (bs.length > 1)
- reporter.warning(pos, "Only one '@" + key.name + "' tag for symbol " + key.symbol + " is allowed")
- (key.symbol, bs.head)
- }
- Map.empty[String, Body] ++ pairs
- }
-
- val com = new Comment {
- val body = parseWiki(docBody, pos)
- val authors = allTags(SimpleTagKey("author"))
- val see = allTags(SimpleTagKey("see"))
- val result = oneTag(SimpleTagKey("return"))
- val throws = allSymsOneTag(SimpleTagKey("throws"))
- val valueParams = allSymsOneTag(SimpleTagKey("param"))
- val typeParams = allSymsOneTag(SimpleTagKey("tparam"))
- val version = oneTag(SimpleTagKey("version"))
- val since = oneTag(SimpleTagKey("since"))
- val todo = allTags(SimpleTagKey("todo"))
- val deprecated = oneTag(SimpleTagKey("deprecated"))
- val note = allTags(SimpleTagKey("note"))
- val example = allTags(SimpleTagKey("example"))
- val short = {
- val shortText = ShortLineEnd.findFirstMatchIn(docBody) match {
- case None => docBody
- case Some(m) => docBody.take(m.start)
- }
- val safeText = CleanHtml.replaceAllIn(shortText, "") // get rid of all layout-busting html tags
- parseWiki(safeText, pos) match {
- case Body(Paragraph(inl) :: _) => inl
- case _ =>
- if (safeText != "")
- reporter.warning(pos, "Comment must start with a sentence")
- Text("")
+ val com = new Comment {
+ val body = parseWiki(docBody, pos)
+ val authors = allTags(SimpleTagKey("author"))
+ val see = allTags(SimpleTagKey("see"))
+ val result = oneTag(SimpleTagKey("return"))
+ val throws = allSymsOneTag(SimpleTagKey("throws"))
+ val valueParams = allSymsOneTag(SimpleTagKey("param"))
+ val typeParams = allSymsOneTag(SimpleTagKey("tparam"))
+ val version = oneTag(SimpleTagKey("version"))
+ val since = oneTag(SimpleTagKey("since"))
+ val todo = allTags(SimpleTagKey("todo"))
+ val deprecated = oneTag(SimpleTagKey("deprecated"))
+ val note = allTags(SimpleTagKey("note"))
+ val example = allTags(SimpleTagKey("example"))
+ val short = {
+ def findShort(blocks: Iterable[Block]): Inline =
+ if (blocks.isEmpty) Text("")
+ else blocks.head match {
+ case Title(text, _) => text
+ case Paragraph(text) => text
+ case Code(data) => Monospace(data.lines.next)
+ case UnorderedList(items) => findShort(items)
+ case OrderedList(items, _) => findShort(items)
+ case DefinitionList(items) => findShort(items.values)
+ case HorizontalRule() => findShort(blocks.tail)
+ }
+ findShort(body.blocks)
}
}
- }
- for ((key, _) <- bodyTags)
- reporter.warning(pos, "Tag '@" + key.name + "' is not recognised")
+ for ((key, _) <- bodyTags)
+ reporter.warning(pos, "Tag '@" + key.name + "' is not recognised")
- com
+ com
}
}
+
parse0("", Map.empty, None, cleaned, false)
+
}
/** Parses a string containing wiki syntax into a `Comment` object. Note that the string is assumed to be clean:
- * * Removed Scaladoc start and end markers.
- * * Removed start-of-line star and one whitespace afterwards (if present).
- * * Removed all end-of-line whitespace.
- * * Only `endOfLine` is used to mark line endings. */
+ * - Removed Scaladoc start and end markers.
+ * - Removed start-of-line star and one whitespace afterwards (if present).
+ * - Removed all end-of-line whitespace.
+ * - Only `endOfLine` is used to mark line endings. */
def parseWiki(string: String, pos: Position): Body =
new WikiParser(string.toArray, pos).document()
@@ -230,6 +246,17 @@ final class CommentFactory(val reporter: Reporter) { parser =>
* @author Gilles Dubochet */
protected final class WikiParser(val buffer: Array[Char], pos: Position) extends CharReader(buffer) { wiki =>
+ /** listStyle ::= '-' spc | '1.' spc | 'I.' spc | 'i.' spc | 'A.' spc | 'a.' spc
+ * Characters used to build lists and their contructors */
+ protected val listStyles = Map[String, (Seq[Block] => Block)]( // TODO Should this be defined at some list companion?
+ "- " -> ( UnorderedList(_) ),
+ "1. " -> ( OrderedList(_,"decimal") ),
+ "I. " -> ( OrderedList(_,"upperRoman") ),
+ "i. " -> ( OrderedList(_,"lowerRoman") ),
+ "A. " -> ( OrderedList(_,"upperAlpha") ),
+ "a. " -> ( OrderedList(_,"lowerAlpha") )
+ )
+
def document(): Body = {
nextChar()
val blocks = new mutable.ListBuffer[Block]
@@ -248,47 +275,59 @@ final class CommentFactory(val reporter: Reporter) { parser =>
title()
else if (check("----"))
hrule()
- else if (check(" - "))
- listBlock(countWhitespace, '-', UnorderedList)
- else if (check(" 1 "))
- listBlock(countWhitespace, '1', OrderedList)
+ else if (checkList)
+ listBlock
else {
para()
}
}
- /**
- * {{{
- * nListBlock ::= nLine { mListBlock }
- * nLine ::= nSpc '*' para '\n'
- * }}}
- * Where n and m stand for the number of spaces. When m > n, a new list is nested. */
- def listBlock(indentation: Int, marker: Char, constructor: (Seq[Block] => Block)): Block = {
- var count = indentation
- val start = " " * count + marker + " "
- var chk = check(start)
- var line = listLine(indentation, marker)
- val blocks = mutable.ListBuffer.empty[Block]
- while (chk) {
- blocks += line
- count = countWhitespace
- if (count > indentation) { // nesting-in
- blocks += listBlock(count, marker, constructor) // TODO is tailrec really needed here?
+ /** Checks if the current line is formed with more than one space and one the listStyles */
+ def checkList =
+ countWhitespace > 0 && listStyles.keysIterator.indexWhere(checkSkipWhitespace(_)) >= 0
+
+ /** {{{
+ * nListBlock ::= nLine { mListBlock }
+ * nLine ::= nSpc listStyle para '\n'
+ * }}}
+ * Where n and m stand for the number of spaces. When m > n, a new list is nested. */
+ def listBlock: Block = {
+ /** consumes one line of a list block */
+ def listLine(indentedListStyle: String): Block = {
+ // deals with mixed lists in the same nesting level by skipping it
+ if(!jump(indentedListStyle)) { // TODO show warning when jump is false
+ nextChar();
+ nextChar()
}
- chk = check(start)
- if (chk) { line = listLine(indentation, marker) }
+ val p = Paragraph(inline(check(Array(endOfLine))))
+ blockEnded("end of list line ")
+ p
}
- constructor(blocks)
- }
+ def listLevel(leftSide: String, listStyle: String, constructor: (Seq[Block] => Block)): Block = {
+ val blocks = mutable.ListBuffer.empty[Block]
+ val length = leftSide.length
+ val indentedListStyle = leftSide + listStyle
+
+ var index = 1
+ var line = listLine(indentedListStyle)
+
+ while (index > -1) {
+ blocks += line
+ if (countWhitespace > length) { // nesting-in
+ blocks += listBlock // TODO is tailrec really needed here?
+ }
+ index = listStyles.keysIterator.indexWhere(x => check(leftSide))
+ if (index > -1) { line = listLine(indentedListStyle) }
+ }
- def listLine(indentation: Int, marker: Char): Block = {
- jump(" " * indentation + marker + " ")
- val p = Paragraph(inline(check(Array(endOfLine))))
- blockEnded("end of list line ")
- p
+ constructor(blocks)
+ }
+ val indentation = countWhitespace
+ val indentStr = " " * indentation
+ val style = listStyles.keysIterator.find( x => check(indentStr + x) )
+ val constructor = listStyles(style.get)
+ listLevel(indentStr, style.get, constructor)
}
-
- /** {{{ code ::= "{{{" { char } '}' "}}" '\n' }}} */
def code(): Block = {
jump("{{{")
readUntil("}}}")
@@ -421,20 +460,39 @@ final class CommentFactory(val reporter: Reporter) { parser =>
Subscript(i)
}
+ protected val SchemeUri =
+ new Regex("""([^:]+:.*)""")
+
+ def entityLink(query: String): Inline = findTemplate(query) match {
+ case Some(tpl) =>
+ EntityLink(tpl)
+ case None =>
+ Text(query)
+ }
+
def link(isInlineEnd: => Boolean, isBlockEnd: => Boolean): Inline = {
jump("[[")
- readUntil { check("]]") }
+ readUntil { check("]]") || check(" ") }
+ val target = getRead()
+ val title =
+ if (!check("]]")) Some({
+ jump(" ")
+ inline(check("]]"), isBlockEnd)
+ })
+ else None
jump("]]")
- val read = getRead()
- val (target, title) = {
- val index = read.indexOf(' ');
- val split = read.splitAt( if (index > -1) index else 0 )
- if (split._1 == "")
- (split._2, None)
- else
- (split._1, Some(split._2.trim))
+ (target, title) match {
+ case (SchemeUri(uri), Some(title)) =>
+ Link(uri, title)
+ case (SchemeUri(uri), None) =>
+ Link(uri, Text(uri))
+ case (qualName, None) =>
+ entityLink(qualName)
+ case (qualName, Some(text)) =>
+ reportError(pos, "entity link to " + qualName + " cannot have a custom title'" + text + "'")
+ entityLink(qualName)
}
- Link(target, title)
+
}
/* UTILITY */
@@ -516,6 +574,10 @@ final class CommentFactory(val reporter: Reporter) { parser =>
/* JUMPERS */
+ /** jumps all the characters in chars
+ * @return true only if the correct characters have been jumped
+ * consumes any matching characters
+ */
final def jump(chars: Array[Char]): Boolean = {
var index = 0
while (index < chars.length && char == chars(index) && char != endOfText) {