jjg@1409: /*
jjg@1529: * Copyright (c) 2012, 2013, Oracle and/or its affiliates. All rights reserved.
jjg@1409: * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
jjg@1409: *
jjg@1409: * This code is free software; you can redistribute it and/or modify it
jjg@1409: * under the terms of the GNU General Public License version 2 only, as
jjg@1409: * published by the Free Software Foundation. Oracle designates this
jjg@1409: * particular file as subject to the "Classpath" exception as provided
jjg@1409: * by Oracle in the LICENSE file that accompanied this code.
jjg@1409: *
jjg@1409: * This code is distributed in the hope that it will be useful, but WITHOUT
jjg@1409: * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
jjg@1409: * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
jjg@1409: * version 2 for more details (a copy is included in the LICENSE file that
jjg@1409: * accompanied this code).
jjg@1409: *
jjg@1409: * You should have received a copy of the GNU General Public License version
jjg@1409: * 2 along with this work; if not, write to the Free Software Foundation,
jjg@1409: * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
jjg@1409: *
jjg@1409: * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
jjg@1409: * or visit www.oracle.com if you need additional information or have any
jjg@1409: * questions.
jjg@1409: */
jjg@1409:
jjg@1409: package com.sun.tools.javac.parser;
jjg@1409:
jjg@1409: import java.text.BreakIterator;
jjg@1409: import java.util.Arrays;
jjg@1409: import java.util.HashMap;
jjg@1409: import java.util.HashSet;
jjg@1409: import java.util.Locale;
jjg@1409: import java.util.Map;
jjg@1409: import java.util.Set;
jjg@1409:
jjg@1409: import com.sun.source.doctree.AttributeTree.ValueKind;
jjg@1409: import com.sun.tools.javac.parser.DocCommentParser.TagParser.Kind;
jjg@1409: import com.sun.tools.javac.parser.Tokens.Comment;
jjg@1409: import com.sun.tools.javac.parser.Tokens.TokenKind;
jjg@1409: import com.sun.tools.javac.tree.DCTree;
jjg@1409: import com.sun.tools.javac.tree.DCTree.DCAttribute;
jjg@1409: import com.sun.tools.javac.tree.DCTree.DCDocComment;
jjg@1409: import com.sun.tools.javac.tree.DCTree.DCEndElement;
jlahoda@1704: import com.sun.tools.javac.tree.DCTree.DCEndPosTree;
jjg@1409: import com.sun.tools.javac.tree.DCTree.DCErroneous;
jjg@1409: import com.sun.tools.javac.tree.DCTree.DCIdentifier;
jjg@1409: import com.sun.tools.javac.tree.DCTree.DCReference;
jjg@1409: import com.sun.tools.javac.tree.DCTree.DCStartElement;
jjg@1409: import com.sun.tools.javac.tree.DCTree.DCText;
jjg@1409: import com.sun.tools.javac.tree.DocTreeMaker;
jjg@1409: import com.sun.tools.javac.tree.JCTree;
jjg@1409: import com.sun.tools.javac.util.DiagnosticSource;
jjg@1409: import com.sun.tools.javac.util.List;
jjg@1409: import com.sun.tools.javac.util.ListBuffer;
jjg@1409: import com.sun.tools.javac.util.Log;
jjg@1409: import com.sun.tools.javac.util.Name;
jjg@1409: import com.sun.tools.javac.util.Names;
jjg@1409: import com.sun.tools.javac.util.Options;
jjg@1409: import com.sun.tools.javac.util.Position;
jjg@1409: import static com.sun.tools.javac.util.LayoutCharacters.*;
jjg@1409:
jjg@1409: /**
jjg@1409: *
jjg@1409: *
This is NOT part of any supported API.
jjg@1409: * If you write code that depends on this, you do so at your own risk.
jjg@1409: * This code and its internal interfaces are subject to change or
jjg@1409: * deletion without notice.
jjg@1409: */
jjg@1409: public class DocCommentParser {
jjg@1409: static class ParseException extends Exception {
jjg@1409: private static final long serialVersionUID = 0;
jjg@1409: ParseException(String key) {
jjg@1409: super(key);
jjg@1409: }
jjg@1409: }
jjg@1409:
jjg@1409: final ParserFactory fac;
jjg@1409: final DiagnosticSource diagSource;
jjg@1409: final Comment comment;
jjg@1409: final DocTreeMaker m;
jjg@1409: final Names names;
jjg@1409:
jjg@1409: BreakIterator sentenceBreaker;
jjg@1409:
jjg@1409: /** The input buffer, index of most recent character read,
jjg@1409: * index of one past last character in buffer.
jjg@1409: */
jjg@1409: protected char[] buf;
jjg@1409: protected int bp;
jjg@1409: protected int buflen;
jjg@1409:
jjg@1409: /** The current character.
jjg@1409: */
jjg@1409: protected char ch;
jjg@1409:
jjg@1409: int textStart = -1;
jjg@1409: int lastNonWhite = -1;
jjg@1409: boolean newline = true;
jjg@1409:
jjg@1409: Map tagParsers;
jjg@1409:
jjg@1409: DocCommentParser(ParserFactory fac, DiagnosticSource diagSource, Comment comment) {
jjg@1409: this.fac = fac;
jjg@1409: this.diagSource = diagSource;
jjg@1409: this.comment = comment;
jjg@1409: names = fac.names;
jjg@1409: m = fac.docTreeMaker;
jjg@1409:
jjg@1409: Locale locale = (fac.locale == null) ? Locale.getDefault() : fac.locale;
jjg@1409:
jjg@1409: Options options = fac.options;
jjg@1409: boolean useBreakIterator = options.isSet("breakIterator");
jjg@1409: if (useBreakIterator || !locale.getLanguage().equals(Locale.ENGLISH.getLanguage()))
jjg@1409: sentenceBreaker = BreakIterator.getSentenceInstance(locale);
jjg@1409:
jjg@1409: initTagParsers();
jjg@1409: }
jjg@1409:
jjg@1409: DCDocComment parse() {
jjg@1409: String c = comment.getText();
jjg@1409: buf = new char[c.length() + 1];
jjg@1409: c.getChars(0, c.length(), buf, 0);
jjg@1409: buf[buf.length - 1] = EOI;
jjg@1409: buflen = buf.length - 1;
jjg@1409: bp = -1;
jjg@1409: nextChar();
jjg@1409:
jjg@1409: List body = blockContent();
jjg@1409: List tags = blockTags();
jjg@1409:
jjg@1409: // split body into first sentence and body
jjg@1409: ListBuffer fs = new ListBuffer();
jjg@1409: loop:
jjg@1409: for (; body.nonEmpty(); body = body.tail) {
jjg@1409: DCTree t = body.head;
jjg@1409: switch (t.getKind()) {
jjg@1409: case TEXT:
jjg@1409: String s = ((DCText) t).getBody();
jjg@1409: int i = getSentenceBreak(s);
jjg@1409: if (i > 0) {
jjg@1409: int i0 = i;
jjg@1409: while (i0 > 0 && isWhitespace(s.charAt(i0 - 1)))
jjg@1409: i0--;
jjg@1409: fs.add(m.at(t.pos).Text(s.substring(0, i0)));
jjg@1409: int i1 = i;
jjg@1409: while (i1 < s.length() && isWhitespace(s.charAt(i1)))
jjg@1409: i1++;
jjg@1409: body = body.tail;
jjg@1409: if (i1 < s.length())
jjg@1409: body = body.prepend(m.at(t.pos + i1).Text(s.substring(i1)));
jjg@1409: break loop;
jjg@1409: } else if (body.tail.nonEmpty()) {
jjg@1409: if (isSentenceBreak(body.tail.head)) {
jjg@1409: int i0 = s.length() - 1;
jjg@1409: while (i0 > 0 && isWhitespace(s.charAt(i0)))
jjg@1409: i0--;
jjg@1409: fs.add(m.at(t.pos).Text(s.substring(0, i0 + 1)));
jjg@1409: body = body.tail;
jjg@1409: break loop;
jjg@1409: }
jjg@1409: }
jjg@1409: break;
jjg@1409:
jjg@1409: case START_ELEMENT:
jjg@1409: case END_ELEMENT:
jjg@1409: if (isSentenceBreak(t))
jjg@1409: break loop;
jjg@1409: break;
jjg@1409: }
jjg@1409: fs.add(t);
jjg@1409: }
jjg@1409:
jjg@1409: @SuppressWarnings("unchecked")
jjg@1409: DCTree first = getFirst(fs.toList(), body, tags);
jjg@1409: int pos = (first == null) ? Position.NOPOS : first.pos;
jjg@1409:
jjg@1409: DCDocComment dc = m.at(pos).DocComment(comment, fs.toList(), body, tags);
jjg@1409: return dc;
jjg@1409: }
jjg@1409:
jjg@1409: void nextChar() {
jjg@1409: ch = buf[bp < buflen ? ++bp : buflen];
jjg@1409: switch (ch) {
jjg@1409: case '\f': case '\n': case '\r':
jjg@1409: newline = true;
jjg@1409: }
jjg@1409: }
jjg@1409:
jjg@1409: /**
jjg@1409: * Read block content, consisting of text, html and inline tags.
jjg@1409: * Terminated by the end of input, or the beginning of the next block tag:
jjg@1409: * i.e. @ as the first non-whitespace character on a line.
jjg@1409: */
jjg@1409: @SuppressWarnings("fallthrough")
jjg@1409: protected List blockContent() {
jjg@1409: ListBuffer trees = new ListBuffer();
jjg@1409: textStart = -1;
jjg@1409:
jjg@1409: loop:
jjg@1409: while (bp < buflen) {
jjg@1409: switch (ch) {
jjg@1409: case '\n': case '\r': case '\f':
jjg@1409: newline = true;
jjg@1409: // fallthrough
jjg@1409:
jjg@1409: case ' ': case '\t':
jjg@1409: nextChar();
jjg@1409: break;
jjg@1409:
jjg@1409: case '&':
jjg@1409: entity(trees);
jjg@1409: break;
jjg@1409:
jjg@1409: case '<':
jjg@1409: newline = false;
jjg@1409: addPendingText(trees, bp - 1);
jjg@1409: trees.add(html());
jjg@1409: if (textStart == -1) {
jjg@1409: textStart = bp;
jjg@1409: lastNonWhite = -1;
jjg@1409: }
jjg@1409: break;
jjg@1409:
jjg@1409: case '>':
jjg@1409: newline = false;
jjg@1409: addPendingText(trees, bp - 1);
jjg@1409: trees.add(m.at(bp).Erroneous(newString(bp, bp+1), diagSource, "dc.bad.gt"));
jjg@1409: nextChar();
jjg@1409: if (textStart == -1) {
jjg@1409: textStart = bp;
jjg@1409: lastNonWhite = -1;
jjg@1409: }
jjg@1409: break;
jjg@1409:
jjg@1409: case '{':
jjg@1409: inlineTag(trees);
jjg@1409: break;
jjg@1409:
jjg@1409: case '@':
jjg@1409: if (newline) {
jjg@1409: addPendingText(trees, lastNonWhite);
jjg@1409: break loop;
jjg@1409: }
jjg@1409: // fallthrough
jjg@1409:
jjg@1409: default:
jjg@1409: newline = false;
jjg@1409: if (textStart == -1)
jjg@1409: textStart = bp;
jjg@1409: lastNonWhite = bp;
jjg@1409: nextChar();
jjg@1409: }
jjg@1409: }
jjg@1409:
jjg@1409: if (lastNonWhite != -1)
jjg@1409: addPendingText(trees, lastNonWhite);
jjg@1409:
jjg@1409: return trees.toList();
jjg@1409: }
jjg@1409:
jjg@1409: /**
jjg@1409: * Read a series of block tags, including their content.
jjg@1409: * Standard tags parse their content appropriately.
jjg@1409: * Non-standard tags are represented by {@link UnknownBlockTag}.
jjg@1409: */
jjg@1409: protected List blockTags() {
jjg@1409: ListBuffer tags = new ListBuffer();
jjg@1409: while (ch == '@')
jjg@1409: tags.add(blockTag());
jjg@1409: return tags.toList();
jjg@1409: }
jjg@1409:
jjg@1409: /**
jjg@1409: * Read a single block tag, including its content.
jjg@1409: * Standard tags parse their content appropriately.
jjg@1409: * Non-standard tags are represented by {@link UnknownBlockTag}.
jjg@1409: */
jjg@1409: protected DCTree blockTag() {
jjg@1409: int p = bp;
jjg@1409: try {
jjg@1409: nextChar();
jjg@1409: if (isIdentifierStart(ch)) {
jjg@2204: Name name = readTagName();
jjg@1409: TagParser tp = tagParsers.get(name);
jjg@1409: if (tp == null) {
jjg@1409: List content = blockContent();
jjg@1409: return m.at(p).UnknownBlockTag(name, content);
jjg@1409: } else {
jjg@1409: switch (tp.getKind()) {
jjg@1409: case BLOCK:
jjg@1409: return tp.parse(p);
jjg@1409: case INLINE:
jjg@1409: return erroneous("dc.bad.inline.tag", p);
jjg@1409: }
jjg@1409: }
jjg@1409: }
jjg@1409: blockContent();
jjg@1409:
jjg@1409: return erroneous("dc.no.tag.name", p);
jjg@1409: } catch (ParseException e) {
jjg@1409: blockContent();
jjg@1409: return erroneous(e.getMessage(), p);
jjg@1409: }
jjg@1409: }
jjg@1409:
jjg@1409: protected void inlineTag(ListBuffer list) {
jjg@1409: newline = false;
jjg@1409: nextChar();
jjg@1409: if (ch == '@') {
jjg@1409: addPendingText(list, bp - 2);
jjg@1409: list.add(inlineTag());
jjg@1409: textStart = bp;
jjg@1409: lastNonWhite = -1;
jjg@1409: } else {
jjg@1409: if (textStart == -1)
jjg@1409: textStart = bp - 1;
jjg@1409: lastNonWhite = bp;
jjg@1409: }
jjg@1409: }
jjg@1409:
jjg@1409: /**
jjg@1409: * Read a single inline tag, including its content.
jjg@1409: * Standard tags parse their content appropriately.
jjg@1409: * Non-standard tags are represented by {@link UnknownBlockTag}.
jjg@1409: * Malformed tags may be returned as {@link Erroneous}.
jjg@1409: */
jjg@1409: protected DCTree inlineTag() {
jjg@1409: int p = bp - 1;
jjg@1409: try {
jjg@1409: nextChar();
jjg@1409: if (isIdentifierStart(ch)) {
jjg@2204: Name name = readTagName();
jjg@1409: skipWhitespace();
jjg@1409:
jjg@1409: TagParser tp = tagParsers.get(name);
jjg@1409: if (tp == null) {
jjg@1409: DCTree text = inlineText();
jjg@1409: if (text != null) {
jjg@1409: nextChar();
jlahoda@1704: return m.at(p).UnknownInlineTag(name, List.of(text)).setEndPos(bp);
jjg@1409: }
jjg@1409: } else if (tp.getKind() == TagParser.Kind.INLINE) {
jlahoda@1704: DCEndPosTree> tree = (DCEndPosTree>) tp.parse(p);
jjg@1409: if (tree != null) {
jlahoda@1704: return tree.setEndPos(bp);
jjg@1409: }
jjg@1409: } else {
jjg@1409: inlineText(); // skip content
jjg@1409: nextChar();
jjg@1409: }
jjg@1409: }
jjg@1409: return erroneous("dc.no.tag.name", p);
jjg@1409: } catch (ParseException e) {
jjg@1409: return erroneous(e.getMessage(), p);
jjg@1409: }
jjg@1409: }
jjg@1409:
jjg@1409: /**
jjg@1409: * Read plain text content of an inline tag.
jjg@1409: * Matching pairs of { } are skipped; the text is terminated by the first
jjg@1409: * unmatched }. It is an error if the beginning of the next tag is detected.
jjg@1409: */
jjg@1409: protected DCTree inlineText() throws ParseException {
jjg@1409: skipWhitespace();
jjg@1409: int pos = bp;
jjg@1409: int depth = 1;
jjg@1409:
jjg@1409: loop:
jjg@1409: while (bp < buflen) {
jjg@1409: switch (ch) {
jjg@1409: case '\n': case '\r': case '\f':
jjg@1409: newline = true;
jjg@1409: break;
jjg@1409:
jjg@1409: case ' ': case '\t':
jjg@1409: break;
jjg@1409:
jjg@1409: case '{':
jjg@1409: newline = false;
jjg@1409: lastNonWhite = bp;
jjg@1409: depth++;
jjg@1409: break;
jjg@1409:
jjg@1409: case '}':
jjg@1409: if (--depth == 0) {
jjg@1409: return m.at(pos).Text(newString(pos, bp));
jjg@1409: }
jjg@1409: newline = false;
jjg@1409: lastNonWhite = bp;
jjg@1409: break;
jjg@1409:
jjg@1409: case '@':
jjg@1409: if (newline)
jjg@1409: break loop;
jjg@1409: newline = false;
jjg@1409: lastNonWhite = bp;
jjg@1409: break;
jjg@1409:
jjg@1409: default:
jjg@1409: newline = false;
jjg@1409: lastNonWhite = bp;
jjg@1409: break;
jjg@1409: }
jjg@1409: nextChar();
jjg@1409: }
jjg@1409: throw new ParseException("dc.unterminated.inline.tag");
jjg@1409: }
jjg@1409:
jjg@1409: /**
jjg@1409: * Read Java class name, possibly followed by member
jjg@1409: * Matching pairs of < > are skipped. The text is terminated by the first
jjg@1409: * unmatched }. It is an error if the beginning of the next tag is detected.
jjg@1409: */
jjg@1409: // TODO: boolean allowMember should be enum FORBID, ALLOW, REQUIRE
jjg@1409: // TODO: improve quality of parse to forbid bad constructions.
jjg@1409: @SuppressWarnings("fallthrough")
jjg@1409: protected DCReference reference(boolean allowMember) throws ParseException {
jjg@1409: int pos = bp;
jjg@1409: int depth = 0;
jjg@1409:
jjg@1409: // scan to find the end of the signature, by looking for the first
jjg@1409: // whitespace not enclosed in () or <>, or the end of the tag
jjg@1409: loop:
jjg@1409: while (bp < buflen) {
jjg@1409: switch (ch) {
jjg@1409: case '\n': case '\r': case '\f':
jjg@1409: newline = true;
jjg@1409: // fallthrough
jjg@1409:
jjg@1409: case ' ': case '\t':
jjg@1409: if (depth == 0)
jjg@1409: break loop;
jjg@1409: break;
jjg@1409:
jjg@1409: case '(':
jjg@1409: case '<':
jjg@1409: newline = false;
jjg@1409: depth++;
jjg@1409: break;
jjg@1409:
jjg@1409: case ')':
jjg@1409: case '>':
jjg@1409: newline = false;
jjg@1409: --depth;
jjg@1409: break;
jjg@1409:
jjg@1409: case '}':
jjg@1409: if (bp == pos)
jjg@1409: return null;
jjg@1409: newline = false;
jjg@1409: break loop;
jjg@1409:
jjg@1409: case '@':
jjg@1409: if (newline)
jjg@1409: break loop;
jjg@1409: // fallthrough
jjg@1409:
jjg@1409: default:
jjg@1409: newline = false;
jjg@1409:
jjg@1409: }
jjg@1409: nextChar();
jjg@1409: }
jjg@1409:
jjg@1409: if (depth != 0)
jjg@1409: throw new ParseException("dc.unterminated.signature");
jjg@1409:
jjg@1409: String sig = newString(pos, bp);
jjg@1409:
jjg@1409: // Break sig apart into qualifiedExpr member paramTypes.
jjg@1409: JCTree qualExpr;
jjg@1409: Name member;
jjg@1409: List paramTypes;
jjg@1409:
jjg@1409: Log.DeferredDiagnosticHandler deferredDiagnosticHandler
jjg@1409: = new Log.DeferredDiagnosticHandler(fac.log);
jjg@1409:
jjg@1409: try {
jjg@1409: int hash = sig.indexOf("#");
jjg@1409: int lparen = sig.indexOf("(", hash + 1);
jjg@1409: if (hash == -1) {
jjg@1409: if (lparen == -1) {
jjg@1409: qualExpr = parseType(sig);
jjg@1409: member = null;
jjg@1409: } else {
jjg@1409: qualExpr = null;
jjg@1409: member = parseMember(sig.substring(0, lparen));
jjg@1409: }
jjg@1409: } else {
jjg@1409: qualExpr = (hash == 0) ? null : parseType(sig.substring(0, hash));
jjg@1409: if (lparen == -1)
jjg@1409: member = parseMember(sig.substring(hash + 1));
jjg@1409: else
jjg@1409: member = parseMember(sig.substring(hash + 1, lparen));
jjg@1409: }
jjg@1409:
jjg@1409: if (lparen < 0) {
jjg@1409: paramTypes = null;
jjg@1409: } else {
jjg@1409: int rparen = sig.indexOf(")", lparen);
jjg@1409: if (rparen != sig.length() - 1)
jjg@1409: throw new ParseException("dc.ref.bad.parens");
jjg@1409: paramTypes = parseParams(sig.substring(lparen + 1, rparen));
jjg@1409: }
jjg@1409:
jjg@1409: if (!deferredDiagnosticHandler.getDiagnostics().isEmpty())
jjg@1409: throw new ParseException("dc.ref.syntax.error");
jjg@1409:
jjg@1409: } finally {
jjg@1409: fac.log.popDiagnosticHandler(deferredDiagnosticHandler);
jjg@1409: }
jjg@1409:
jlahoda@1704: return m.at(pos).Reference(sig, qualExpr, member, paramTypes).setEndPos(bp);
jjg@1409: }
jjg@1409:
jjg@1409: JCTree parseType(String s) throws ParseException {
jjg@1409: JavacParser p = fac.newParser(s, false, false, false);
jjg@1409: JCTree tree = p.parseType();
jjg@1409: if (p.token().kind != TokenKind.EOF)
jjg@1409: throw new ParseException("dc.ref.unexpected.input");
jjg@1409: return tree;
jjg@1409: }
jjg@1409:
jjg@1409: Name parseMember(String s) throws ParseException {
jjg@1409: JavacParser p = fac.newParser(s, false, false, false);
jjg@1409: Name name = p.ident();
jjg@1409: if (p.token().kind != TokenKind.EOF)
jjg@1409: throw new ParseException("dc.ref.unexpected.input");
jjg@1409: return name;
jjg@1409: }
jjg@1409:
jjg@1409: List parseParams(String s) throws ParseException {
jjg@1409: if (s.trim().isEmpty())
jjg@1409: return List.nil();
jjg@1409:
jjg@1409: JavacParser p = fac.newParser(s.replace("...", "[]"), false, false, false);
jjg@1409: ListBuffer paramTypes = new ListBuffer();
jjg@1409: paramTypes.add(p.parseType());
jjg@1409:
jjg@1409: if (p.token().kind == TokenKind.IDENTIFIER)
jjg@1409: p.nextToken();
jjg@1409:
jjg@1409: while (p.token().kind == TokenKind.COMMA) {
jjg@1409: p.nextToken();
jjg@1409: paramTypes.add(p.parseType());
jjg@1409:
jjg@1409: if (p.token().kind == TokenKind.IDENTIFIER)
jjg@1409: p.nextToken();
jjg@1409: }
jjg@1409:
jjg@1409: if (p.token().kind != TokenKind.EOF)
jjg@1409: throw new ParseException("dc.ref.unexpected.input");
jjg@1409:
jjg@1409: return paramTypes.toList();
jjg@1409: }
jjg@1409:
jjg@1409: /**
jjg@1409: * Read Java identifier
jjg@1409: * Matching pairs of { } are skipped; the text is terminated by the first
jjg@1409: * unmatched }. It is an error if the beginning of the next tag is detected.
jjg@1409: */
jjg@1409: @SuppressWarnings("fallthrough")
jjg@1409: protected DCIdentifier identifier() throws ParseException {
jjg@1409: skipWhitespace();
jjg@1409: int pos = bp;
jjg@1409:
jjg@1409: if (isJavaIdentifierStart(ch)) {
jjg@1529: Name name = readJavaIdentifier();
jjg@1529: return m.at(pos).Identifier(name);
jjg@1409: }
jjg@1409:
jjg@1409: throw new ParseException("dc.identifier.expected");
jjg@1409: }
jjg@1409:
jjg@1409: /**
jjg@1409: * Read a quoted string.
jjg@1409: * It is an error if the beginning of the next tag is detected.
jjg@1409: */
jjg@1409: @SuppressWarnings("fallthrough")
jjg@1409: protected DCText quotedString() {
jjg@1409: int pos = bp;
jjg@1409: nextChar();
jjg@1409:
jjg@1409: loop:
jjg@1409: while (bp < buflen) {
jjg@1409: switch (ch) {
jjg@1409: case '\n': case '\r': case '\f':
jjg@1409: newline = true;
jjg@1409: break;
jjg@1409:
jjg@1409: case ' ': case '\t':
jjg@1409: break;
jjg@1409:
jjg@1409: case '"':
jjg@1409: nextChar();
jjg@1409: // trim trailing white-space?
jjg@1409: return m.at(pos).Text(newString(pos, bp));
jjg@1409:
jjg@1409: case '@':
jjg@1409: if (newline)
jjg@1409: break loop;
jjg@1409:
jjg@1409: }
jjg@1409: nextChar();
jjg@1409: }
jjg@1409: return null;
jjg@1409: }
jjg@1409:
jjg@1409: /**
jjg@1409: * Read general text content of an inline tag, including HTML entities and elements.
jjg@1409: * Matching pairs of { } are skipped; the text is terminated by the first
jjg@1409: * unmatched }. It is an error if the beginning of the next tag is detected.
jjg@1409: */
jjg@1409: @SuppressWarnings("fallthrough")
jjg@1409: protected List inlineContent() {
jjg@1409: ListBuffer trees = new ListBuffer();
jjg@1409:
jjg@1409: skipWhitespace();
jjg@1409: int pos = bp;
jjg@1409: int depth = 1;
jjg@1409: textStart = -1;
jjg@1409:
jjg@1409: loop:
jjg@1409: while (bp < buflen) {
jjg@1409:
jjg@1409: switch (ch) {
jjg@1409: case '\n': case '\r': case '\f':
jjg@1409: newline = true;
jjg@1409: // fall through
jjg@1409:
jjg@1409: case ' ': case '\t':
jjg@1409: nextChar();
jjg@1409: break;
jjg@1409:
jjg@1409: case '&':
jjg@1409: entity(trees);
jjg@1409: break;
jjg@1409:
jjg@1409: case '<':
jjg@1409: newline = false;
jjg@1409: addPendingText(trees, bp - 1);
jjg@1409: trees.add(html());
jjg@1409: break;
jjg@1409:
jjg@1409: case '{':
jjg@1409: newline = false;
jjg@1409: depth++;
jjg@1409: nextChar();
jjg@1409: break;
jjg@1409:
jjg@1409: case '}':
jjg@1409: newline = false;
jjg@1409: if (--depth == 0) {
jjg@1409: addPendingText(trees, bp - 1);
jjg@1409: nextChar();
jjg@1409: return trees.toList();
jjg@1409: }
jjg@1409: nextChar();
jjg@1409: break;
jjg@1409:
jjg@1409: case '@':
jjg@1409: if (newline)
jjg@1409: break loop;
jjg@1409: // fallthrough
jjg@1409:
jjg@1409: default:
jjg@1409: if (textStart == -1)
jjg@1409: textStart = bp;
jjg@1409: nextChar();
jjg@1409: break;
jjg@1409: }
jjg@1409: }
jjg@1409:
jjg@1409: return List.of(erroneous("dc.unterminated.inline.tag", pos));
jjg@1409: }
jjg@1409:
jjg@1409: protected void entity(ListBuffer list) {
jjg@1409: newline = false;
jjg@1409: addPendingText(list, bp - 1);
jjg@1409: list.add(entity());
jjg@1409: if (textStart == -1) {
jjg@1409: textStart = bp;
jjg@1409: lastNonWhite = -1;
jjg@1409: }
jjg@1409: }
jjg@1409:
jjg@1409: /**
jjg@1409: * Read an HTML entity.
jjg@1409: * {@literal &identifier; } or {@literal digits; } or {@literal hex-digits; }
jjg@1409: */
jjg@1409: protected DCTree entity() {
jjg@1409: int p = bp;
jjg@1409: nextChar();
jjg@1529: Name name = null;
jjg@1409: boolean checkSemi = false;
jjg@1409: if (ch == '#') {
jjg@1529: int namep = bp;
jjg@1409: nextChar();
jjg@1409: if (isDecimalDigit(ch)) {
jjg@1409: nextChar();
jjg@1409: while (isDecimalDigit(ch))
jjg@1409: nextChar();
jjg@1529: name = names.fromChars(buf, namep, bp - namep);
jjg@1409: } else if (ch == 'x' || ch == 'X') {
jjg@1409: nextChar();
jjg@1409: if (isHexDigit(ch)) {
jjg@1409: nextChar();
jjg@1409: while (isHexDigit(ch))
jjg@1409: nextChar();
jjg@1529: name = names.fromChars(buf, namep, bp - namep);
jjg@1409: }
jjg@1409: }
jjg@1409: } else if (isIdentifierStart(ch)) {
jjg@1529: name = readIdentifier();
jjg@1409: }
jjg@1409:
jjg@1529: if (name == null)
jjg@1529: return erroneous("dc.bad.entity", p);
jjg@1529: else {
jjg@1529: if (ch != ';')
jjg@1529: return erroneous("dc.missing.semicolon", p);
jjg@1409: nextChar();
jjg@1529: return m.at(p).Entity(name);
jjg@1409: }
jjg@1409: }
jjg@1409:
jjg@1409: /**
jjg@1409: * Read the start or end of an HTML tag, or an HTML comment
jjg@1409: * {@literal } or {@literal }
jjg@1409: */
jjg@1409: protected DCTree html() {
jjg@1409: int p = bp;
jjg@1409: nextChar();
jjg@1409: if (isIdentifierStart(ch)) {
jjg@1529: Name name = readIdentifier();
jjg@1409: List attrs = htmlAttrs();
jjg@1409: if (attrs != null) {
jjg@1409: boolean selfClosing = false;
jjg@1409: if (ch == '/') {
jjg@1409: nextChar();
jjg@1409: selfClosing = true;
jjg@1409: }
jjg@1409: if (ch == '>') {
jjg@1409: nextChar();
jlahoda@1704: return m.at(p).StartElement(name, attrs, selfClosing).setEndPos(bp);
jjg@1409: }
jjg@1409: }
jjg@1409: } else if (ch == '/') {
jjg@1409: nextChar();
jjg@1409: if (isIdentifierStart(ch)) {
jjg@1529: Name name = readIdentifier();
jjg@1409: skipWhitespace();
jjg@1409: if (ch == '>') {
jjg@1409: nextChar();
jjg@1409: return m.at(p).EndElement(name);
jjg@1409: }
jjg@1409: }
jjg@1409: } else if (ch == '!') {
jjg@1409: nextChar();
jjg@1409: if (ch == '-') {
jjg@1409: nextChar();
jjg@1409: if (ch == '-') {
jjg@1409: nextChar();
jjg@1409: while (bp < buflen) {
jjg@1409: int dash = 0;
jjg@1409: while (ch == '-') {
jjg@1409: dash++;
jjg@1409: nextChar();
jjg@1409: }
jjg@1409: // strictly speaking, a comment should not contain "--"
jjg@1409: // so dash > 2 is an error, dash == 2 implies ch == '>'
jjg@1409: if (dash >= 2 && ch == '>') {
jjg@1409: nextChar();
jjg@1409: return m.at(p).Comment(newString(p, bp));
jjg@1409: }
jjg@1409:
jjg@1409: nextChar();
jjg@1409: }
jjg@1409: }
jjg@1409: }
jjg@1409: }
jjg@1409:
jjg@1409: bp = p + 1;
jjg@1409: ch = buf[bp];
jjg@1409: return erroneous("dc.malformed.html", p);
jjg@1409: }
jjg@1409:
jjg@1409: /**
jjg@1409: * Read a series of HTML attributes, terminated by {@literal > }.
jjg@1409: * Each attribute is of the form {@literal identifier[=value] }.
jjg@1409: * "value" may be unquoted, single-quoted, or double-quoted.
jjg@1409: */
jjg@1409: protected List htmlAttrs() {
jjg@1409: ListBuffer attrs = new ListBuffer();
jjg@1409: skipWhitespace();
jjg@1409:
jjg@1409: loop:
jjg@1409: while (isIdentifierStart(ch)) {
jjg@1409: int namePos = bp;
jjg@1529: Name name = readIdentifier();
jjg@1409: skipWhitespace();
jjg@1409: List value = null;
jjg@1409: ValueKind vkind = ValueKind.EMPTY;
jjg@1409: if (ch == '=') {
jjg@1409: ListBuffer v = new ListBuffer();
jjg@1409: nextChar();
jjg@1409: skipWhitespace();
jjg@1409: if (ch == '\'' || ch == '"') {
jjg@1409: vkind = (ch == '\'') ? ValueKind.SINGLE : ValueKind.DOUBLE;
jjg@1409: char quote = ch;
jjg@1409: nextChar();
jjg@1409: textStart = bp;
jjg@1409: while (bp < buflen && ch != quote) {
jjg@1409: if (newline && ch == '@') {
jjg@1409: attrs.add(erroneous("dc.unterminated.string", namePos));
jjg@1409: // No point trying to read more.
jjg@1409: // In fact, all attrs get discarded by the caller
jjg@1409: // and superseded by a malformed.html node because
jjg@1409: // the html tag itself is not terminated correctly.
jjg@1409: break loop;
jjg@1409: }
jjg@1409: attrValueChar(v);
jjg@1409: }
jjg@1409: addPendingText(v, bp - 1);
jjg@1409: nextChar();
jjg@1409: } else {
jjg@1409: vkind = ValueKind.UNQUOTED;
jjg@1409: textStart = bp;
jjg@1409: while (bp < buflen && !isUnquotedAttrValueTerminator(ch)) {
jjg@1409: attrValueChar(v);
jjg@1409: }
jjg@1409: addPendingText(v, bp - 1);
jjg@1409: }
jjg@1409: skipWhitespace();
jjg@1409: value = v.toList();
jjg@1409: }
jjg@1409: DCAttribute attr = m.at(namePos).Attribute(name, vkind, value);
jjg@1409: attrs.add(attr);
jjg@1409: }
jjg@1409:
jjg@1409: return attrs.toList();
jjg@1409: }
jjg@1409:
jjg@1409: protected void attrValueChar(ListBuffer list) {
jjg@1409: switch (ch) {
jjg@1409: case '&':
jjg@1409: entity(list);
jjg@1409: break;
jjg@1409:
jjg@1409: case '{':
jjg@1409: inlineTag(list);
jjg@1409: break;
jjg@1409:
jjg@1409: default:
jjg@1409: nextChar();
jjg@1409: }
jjg@1409: }
jjg@1409:
jjg@1409: protected void addPendingText(ListBuffer list, int textEnd) {
jjg@1455: if (textStart != -1) {
jjg@1455: if (textStart <= textEnd) {
jjg@1455: list.add(m.at(textStart).Text(newString(textStart, textEnd + 1)));
jjg@1455: }
jjg@1409: textStart = -1;
jjg@1409: }
jjg@1409: }
jjg@1409:
jjg@1409: protected DCErroneous erroneous(String code, int pos) {
jjg@1409: int i = bp - 1;
jjg@1409: loop:
jjg@1529: while (i > pos) {
jjg@1409: switch (buf[i]) {
jjg@1409: case '\f': case '\n': case '\r':
jjg@1409: newline = true;
jjg@1409: break;
jjg@1409: case '\t': case ' ':
jjg@1409: break;
jjg@1409: default:
jjg@1409: break loop;
jjg@1409: }
jjg@1409: i--;
jjg@1409: }
jjg@1409: textStart = -1;
jjg@1409: return m.at(pos).Erroneous(newString(pos, i + 1), diagSource, code);
jjg@1409: }
jjg@1409:
jjg@1409: @SuppressWarnings("unchecked")
jjg@1409: T getFirst(List... lists) {
jjg@1409: for (List list: lists) {
jjg@1409: if (list.nonEmpty())
jjg@1409: return list.head;
jjg@1409: }
jjg@1409: return null;
jjg@1409: }
jjg@1409:
jjg@1409: protected boolean isIdentifierStart(char ch) {
jjg@1409: return Character.isUnicodeIdentifierStart(ch);
jjg@1409: }
jjg@1409:
jjg@1529: protected Name readIdentifier() {
jjg@1529: int start = bp;
jjg@1529: nextChar();
jjg@1529: while (bp < buflen && Character.isUnicodeIdentifierPart(ch))
jjg@1529: nextChar();
jjg@1529: return names.fromChars(buf, start, bp - start);
jjg@1409: }
jjg@1409:
jjg@2204: protected Name readTagName() {
jjg@2204: int start = bp;
jjg@2204: nextChar();
jjg@2204: while (bp < buflen && (Character.isUnicodeIdentifierPart(ch) || ch == '.'))
jjg@2204: nextChar();
jjg@2204: return names.fromChars(buf, start, bp - start);
jjg@2204: }
jjg@2204:
jjg@1409: protected boolean isJavaIdentifierStart(char ch) {
jjg@1409: return Character.isJavaIdentifierStart(ch);
jjg@1409: }
jjg@1409:
jjg@1529: protected Name readJavaIdentifier() {
jjg@1529: int start = bp;
jjg@1529: nextChar();
jjg@1529: while (bp < buflen && Character.isJavaIdentifierPart(ch))
jjg@1529: nextChar();
jjg@1529: return names.fromChars(buf, start, bp - start);
jjg@1409: }
jjg@1409:
jjg@1409: protected boolean isDecimalDigit(char ch) {
jjg@1409: return ('0' <= ch && ch <= '9');
jjg@1409: }
jjg@1409:
jjg@1409: protected boolean isHexDigit(char ch) {
jjg@1409: return ('0' <= ch && ch <= '9')
jjg@1409: || ('a' <= ch && ch <= 'f')
jjg@1409: || ('A' <= ch && ch <= 'F');
jjg@1409: }
jjg@1409:
jjg@1409: protected boolean isUnquotedAttrValueTerminator(char ch) {
jjg@1409: switch (ch) {
jjg@1409: case '\f': case '\n': case '\r': case '\t':
jjg@1409: case ' ':
jjg@1409: case '"': case '\'': case '`':
jjg@1409: case '=': case '<': case '>':
jjg@1409: return true;
jjg@1409: default:
jjg@1409: return false;
jjg@1409: }
jjg@1409: }
jjg@1409:
jjg@1409: protected boolean isWhitespace(char ch) {
jjg@1409: return Character.isWhitespace(ch);
jjg@1409: }
jjg@1409:
jjg@1409: protected void skipWhitespace() {
jjg@1409: while (isWhitespace(ch))
jjg@1409: nextChar();
jjg@1409: }
jjg@1409:
jjg@1409: protected int getSentenceBreak(String s) {
jjg@1409: if (sentenceBreaker != null) {
jjg@1409: sentenceBreaker.setText(s);
jjg@1409: int i = sentenceBreaker.next();
jjg@1409: return (i == s.length()) ? -1 : i;
jjg@1409: }
jjg@1409:
jjg@1409: // scan for period followed by whitespace
jjg@1409: boolean period = false;
jjg@1409: for (int i = 0; i < s.length(); i++) {
jjg@1409: switch (s.charAt(i)) {
jjg@1409: case '.':
jjg@1409: period = true;
jjg@1409: break;
jjg@1409:
jjg@1409: case ' ':
jjg@1409: case '\f':
jjg@1409: case '\n':
jjg@1409: case '\r':
jjg@1409: case '\t':
jjg@1409: if (period)
jjg@1409: return i;
jjg@1409: break;
jjg@1409:
jjg@1409: default:
jjg@1409: period = false;
jjg@1409: break;
jjg@1409: }
jjg@1409: }
jjg@1409: return -1;
jjg@1409: }
jjg@1409:
jjg@1409:
jjg@1409: Set htmlBlockTags = new HashSet(Arrays.asList(
jjg@1409: "h1", "h2", "h3", "h4", "h5", "h6", "p", "pre"));
jjg@1409:
jjg@1409: protected boolean isSentenceBreak(Name n) {
jjg@1409: return htmlBlockTags.contains(n.toString().toLowerCase());
jjg@1409: }
jjg@1409:
jjg@1409: protected boolean isSentenceBreak(DCTree t) {
jjg@1409: switch (t.getKind()) {
jjg@1409: case START_ELEMENT:
jjg@1409: return isSentenceBreak(((DCStartElement) t).getName());
jjg@1409:
jjg@1409: case END_ELEMENT:
jjg@1409: return isSentenceBreak(((DCEndElement) t).getName());
jjg@1409: }
jjg@1409: return false;
jjg@1409: }
jjg@1409:
jjg@1409: /**
jjg@1409: * @param start position of first character of string
jjg@1409: * @param end position of character beyond last character to be included
jjg@1409: */
jjg@1409: String newString(int start, int end) {
jjg@1409: return new String(buf, start, end - start);
jjg@1409: }
jjg@1409:
jjg@1409: static abstract class TagParser {
jjg@1409: enum Kind { INLINE, BLOCK }
jjg@1409:
jjg@1409: Kind kind;
jjg@1409: DCTree.Kind treeKind;
jjg@1409:
jjg@1409: TagParser(Kind k, DCTree.Kind tk) {
jjg@1409: kind = k;
jjg@1409: treeKind = tk;
jjg@1409: }
jjg@1409:
jjg@1409: Kind getKind() {
jjg@1409: return kind;
jjg@1409: }
jjg@1409:
jjg@1409: DCTree.Kind getTreeKind() {
jjg@1409: return treeKind;
jjg@1409: }
jjg@1409:
jjg@1409: abstract DCTree parse(int pos) throws ParseException;
jjg@1409: }
jjg@1409:
jjg@1409: /**
jjg@1409: * @see Javadoc Tags
jjg@1409: */
jjg@1409: private void initTagParsers() {
jjg@1409: TagParser[] parsers = {
jjg@1409: // @author name-text
jjg@1409: new TagParser(Kind.BLOCK, DCTree.Kind.AUTHOR) {
jjg@1409: public DCTree parse(int pos) {
jjg@1409: List name = blockContent();
jjg@1409: return m.at(pos).Author(name);
jjg@1409: }
jjg@1409: },
jjg@1409:
jjg@1409: // {@code text}
jjg@1409: new TagParser(Kind.INLINE, DCTree.Kind.CODE) {
jjg@1409: public DCTree parse(int pos) throws ParseException {
jjg@1409: DCTree text = inlineText();
jjg@1409: nextChar();
jjg@1409: return m.at(pos).Code((DCText) text);
jjg@1409: }
jjg@1409: },
jjg@1409:
jjg@1409: // @deprecated deprecated-text
jjg@1409: new TagParser(Kind.BLOCK, DCTree.Kind.DEPRECATED) {
jjg@1409: public DCTree parse(int pos) {
jjg@1409: List reason = blockContent();
jjg@1409: return m.at(pos).Deprecated(reason);
jjg@1409: }
jjg@1409: },
jjg@1409:
jjg@1409: // {@docRoot}
jjg@1409: new TagParser(Kind.INLINE, DCTree.Kind.DOC_ROOT) {
jjg@1409: public DCTree parse(int pos) throws ParseException {
jjg@1409: if (ch == '}') {
jjg@1409: nextChar();
jjg@1409: return m.at(pos).DocRoot();
jjg@1409: }
jjg@1409: inlineText(); // skip unexpected content
jjg@1409: nextChar();
jjg@1409: throw new ParseException("dc.unexpected.content");
jjg@1409: }
jjg@1409: },
jjg@1409:
jjg@1409: // @exception class-name description
jjg@1409: new TagParser(Kind.BLOCK, DCTree.Kind.EXCEPTION) {
jjg@1409: public DCTree parse(int pos) throws ParseException {
jjg@1409: skipWhitespace();
jjg@1409: DCReference ref = reference(false);
jjg@1409: List description = blockContent();
jjg@1409: return m.at(pos).Exception(ref, description);
jjg@1409: }
jjg@1409: },
jjg@1409:
jjg@1409: // {@inheritDoc}
jjg@1409: new TagParser(Kind.INLINE, DCTree.Kind.INHERIT_DOC) {
jjg@1409: public DCTree parse(int pos) throws ParseException {
jjg@1409: if (ch == '}') {
jjg@1409: nextChar();
jjg@1409: return m.at(pos).InheritDoc();
jjg@1409: }
jjg@1409: inlineText(); // skip unexpected content
jjg@1409: nextChar();
jjg@1409: throw new ParseException("dc.unexpected.content");
jjg@1409: }
jjg@1409: },
jjg@1409:
jjg@1409: // {@link package.class#member label}
jjg@1409: new TagParser(Kind.INLINE, DCTree.Kind.LINK) {
jjg@1409: public DCTree parse(int pos) throws ParseException {
jjg@1409: DCReference ref = reference(true);
jjg@1409: List label = inlineContent();
jjg@1409: return m.at(pos).Link(ref, label);
jjg@1409: }
jjg@1409: },
jjg@1409:
jjg@1409: // {@linkplain package.class#member label}
jjg@1409: new TagParser(Kind.INLINE, DCTree.Kind.LINK_PLAIN) {
jjg@1409: public DCTree parse(int pos) throws ParseException {
jjg@1409: DCReference ref = reference(true);
jjg@1409: List label = inlineContent();
jjg@1409: return m.at(pos).LinkPlain(ref, label);
jjg@1409: }
jjg@1409: },
jjg@1409:
jjg@1409: // {@literal text}
jjg@1409: new TagParser(Kind.INLINE, DCTree.Kind.LITERAL) {
jjg@1409: public DCTree parse(int pos) throws ParseException {
jjg@1409: DCTree text = inlineText();
jjg@1409: nextChar();
jjg@1409: return m.at(pos).Literal((DCText) text);
jjg@1409: }
jjg@1409: },
jjg@1409:
jjg@1409: // @param parameter-name description
jjg@1409: new TagParser(Kind.BLOCK, DCTree.Kind.PARAM) {
jjg@1409: public DCTree parse(int pos) throws ParseException {
jjg@1409: skipWhitespace();
jjg@1409:
jjg@1409: boolean typaram = false;
jjg@1409: if (ch == '<') {
jjg@1409: typaram = true;
jjg@1409: nextChar();
jjg@1409: }
jjg@1409:
jjg@1409: DCIdentifier id = identifier();
jjg@1409:
jjg@1409: if (typaram) {
jjg@1409: if (ch != '>')
jjg@1409: throw new ParseException("dc.gt.expected");
jjg@1409: nextChar();
jjg@1409: }
jjg@1409:
jjg@1409: skipWhitespace();
jjg@1409: List desc = blockContent();
jjg@1409: return m.at(pos).Param(typaram, id, desc);
jjg@1409: }
jjg@1409: },
jjg@1409:
jjg@1409: // @return description
jjg@1409: new TagParser(Kind.BLOCK, DCTree.Kind.RETURN) {
jjg@1409: public DCTree parse(int pos) {
jjg@1409: List description = blockContent();
jjg@1409: return m.at(pos).Return(description);
jjg@1409: }
jjg@1409: },
jjg@1409:
jjg@1409: // @see reference | quoted-string | HTML
jjg@1409: new TagParser(Kind.BLOCK, DCTree.Kind.SEE) {
jjg@1409: public DCTree parse(int pos) throws ParseException {
jjg@1409: skipWhitespace();
jjg@1409: switch (ch) {
jjg@1409: case '"':
jjg@1409: DCText string = quotedString();
jjg@1409: if (string != null) {
jjg@1409: skipWhitespace();
jjg@1409: if (ch == '@')
jjg@1409: return m.at(pos).See(List.of(string));
jjg@1409: }
jjg@1409: break;
jjg@1409:
jjg@1409: case '<':
jjg@1409: List html = blockContent();
jjg@1409: if (html != null)
jjg@1409: return m.at(pos).See(html);
jjg@1409: break;
jjg@1409:
jjg@1455: case '@':
jjg@1455: if (newline)
jjg@1455: throw new ParseException("dc.no.content");
jjg@1455: break;
jjg@1455:
jjg@1455: case EOI:
jjg@1455: if (bp == buf.length - 1)
jjg@1455: throw new ParseException("dc.no.content");
jjg@1455: break;
jjg@1455:
jjg@1409: default:
jjg@1409: if (isJavaIdentifierStart(ch) || ch == '#') {
jjg@1409: DCReference ref = reference(true);
jjg@1409: List description = blockContent();
jjg@1409: return m.at(pos).See(description.prepend(ref));
jjg@1409: }
jjg@1409: }
jjg@1409: throw new ParseException("dc.unexpected.content");
jjg@1409: }
jjg@1409: },
jjg@1409:
jjg@1409: // @serialData data-description
jjg@1409: new TagParser(Kind.BLOCK, DCTree.Kind.SERIAL_DATA) {
jjg@1409: public DCTree parse(int pos) {
jjg@1409: List description = blockContent();
jjg@1409: return m.at(pos).SerialData(description);
jjg@1409: }
jjg@1409: },
jjg@1409:
jjg@1409: // @serialField field-name field-type description
jjg@1409: new TagParser(Kind.BLOCK, DCTree.Kind.SERIAL_FIELD) {
jjg@1409: public DCTree parse(int pos) throws ParseException {
jjg@1409: skipWhitespace();
jjg@1409: DCIdentifier name = identifier();
jjg@1409: skipWhitespace();
jjg@1409: DCReference type = reference(false);
jjg@1409: List description = null;
jjg@1409: if (isWhitespace(ch)) {
jjg@1409: skipWhitespace();
jjg@1409: description = blockContent();
jjg@1409: }
jjg@1409: return m.at(pos).SerialField(name, type, description);
jjg@1409: }
jjg@1409: },
jjg@1409:
jjg@1409: // @serial field-description | include | exclude
jjg@1409: new TagParser(Kind.BLOCK, DCTree.Kind.SERIAL) {
jjg@1409: public DCTree parse(int pos) {
jjg@1409: List description = blockContent();
jjg@1409: return m.at(pos).Serial(description);
jjg@1409: }
jjg@1409: },
jjg@1409:
jjg@1409: // @since since-text
jjg@1409: new TagParser(Kind.BLOCK, DCTree.Kind.SINCE) {
jjg@1409: public DCTree parse(int pos) {
jjg@1409: List description = blockContent();
jjg@1409: return m.at(pos).Since(description);
jjg@1409: }
jjg@1409: },
jjg@1409:
jjg@1409: // @throws class-name description
jjg@1409: new TagParser(Kind.BLOCK, DCTree.Kind.THROWS) {
jjg@1409: public DCTree parse(int pos) throws ParseException {
jjg@1409: skipWhitespace();
jjg@1409: DCReference ref = reference(false);
jjg@1409: List description = blockContent();
jjg@1409: return m.at(pos).Throws(ref, description);
jjg@1409: }
jjg@1409: },
jjg@1409:
jjg@1409: // {@value package.class#field}
jjg@1409: new TagParser(Kind.INLINE, DCTree.Kind.VALUE) {
jjg@1409: public DCTree parse(int pos) throws ParseException {
jjg@1409: DCReference ref = reference(true);
jjg@1409: skipWhitespace();
jjg@1409: if (ch == '}') {
jjg@1409: nextChar();
jjg@1409: return m.at(pos).Value(ref);
jjg@1409: }
jjg@1409: nextChar();
jjg@1409: throw new ParseException("dc.unexpected.content");
jjg@1409: }
jjg@1409: },
jjg@1409:
jjg@1409: // @version version-text
jjg@1409: new TagParser(Kind.BLOCK, DCTree.Kind.VERSION) {
jjg@1409: public DCTree parse(int pos) {
jjg@1409: List description = blockContent();
jjg@1409: return m.at(pos).Version(description);
jjg@1409: }
jjg@1409: },
jjg@1409: };
jjg@1409:
jjg@1409: tagParsers = new HashMap();
jjg@1409: for (TagParser p: parsers)
jjg@1409: tagParsers.put(names.fromString(p.getTreeKind().tagName), p);
jjg@1409:
jjg@1409: }
jjg@1409: }