diff options
Diffstat (limited to 'src/compiler/scala/tools/nsc/ast/parser/MarkupParsers1.scala')
-rw-r--r-- | src/compiler/scala/tools/nsc/ast/parser/MarkupParsers1.scala | 705 |
1 files changed, 705 insertions, 0 deletions
diff --git a/src/compiler/scala/tools/nsc/ast/parser/MarkupParsers1.scala b/src/compiler/scala/tools/nsc/ast/parser/MarkupParsers1.scala new file mode 100644 index 0000000000..bba76c748c --- /dev/null +++ b/src/compiler/scala/tools/nsc/ast/parser/MarkupParsers1.scala @@ -0,0 +1,705 @@ +/* NSC -- new Scala compiler + * Copyright 2005-2009 LAMP/EPFL + * @author Burak Emir + */ +// $Id: MarkupParsers.scala 17315 2009-03-16 17:46:58Z extempore $ + +package scala.tools.nsc.ast.parser + +import scala.collection.mutable +import scala.tools.nsc.util.{Position,NoPosition,SourceFile,CharArrayReader1} +import scala.xml.{Text, TextBuffer} +import SourceFile.{SU,LF} +import scala.annotation.switch + +/** This trait ... + * + * @author Burak Emir + * @version 1.0 + */ +trait MarkupParsers1 {self: Parsers1 => + case object MissingEndTagException extends RuntimeException { + override def getMessage = "start tag was here: " + } + + case object ConfusedAboutBracesException extends RuntimeException { + override def getMessage = " I encountered a '}' where I didn't expect one, maybe this tag isn't closed <" + } + + case object TruncatedXML extends RuntimeException { + override def getMessage = "input ended while parsing XML" + } + + import global._ + //import posAssigner.atPos + + class MarkupParser(parser: UnitParser, presWS: Boolean) /*with scala.xml.parsing.MarkupParser[Tree,Tree] */{ + + import Tokens.{EMPTY, LBRACE, RBRACE} + + final val preserveWS = presWS + var input : CharArrayReader1 = _ + + import parser.{symbXMLBuilder => handle, i2p} + + def pos : Int = input.charOffset + var tmppos : Position = NoPosition + def ch = input.ch + /** this method assign the next character to ch and advances in input */ + def nextch = { val result = input.ch; input.nextChar(); result } // { s.in.next; /*s.xNext;*/ ch = s.in.ch ; pos = s.in.cpos } + + var xEmbeddedBlock = false + + /** munch expected XML token, report syntax error for unexpected. + * + * @param that ... + */ + /*[Duplicate]*/ def xToken(that: Char) { + if (ch == that) nextch + else if (ch == SU) + throw TruncatedXML + else reportSyntaxError("'" + that + "' expected instead of '" + ch + "'") + } + + var debugLastStartElement = new mutable.Stack[(Int, String)] + + /** checks whether next character starts a Scala block, if yes, skip it. + * @return true if next character starts a scala block + */ + /*[Duplicate]*/ def xCheckEmbeddedBlock: Boolean = { + // attentions, side-effect, used in xText + xEmbeddedBlock = (ch == '{') && { nextch; (ch != '{') } + //Console.println("pos = "+pos+" xEmbeddedBlock returns "+xEmbeddedBlock) + xEmbeddedBlock + } + + /** parse attribute and add it to listmap + * [41] Attributes ::= { S Name Eq AttValue } + * AttValue ::= `'` { _ } `'` + * | `"` { _ } `"` + * | `{` scalablock `}` + */ + /*[Duplicate]*/ def xAttributes = { + var aMap = new mutable.HashMap[String, Tree]() + while (xml.Parsing.isNameStart(ch)) { + val key = xName + xEQ + val delim = ch + val pos1 = pos + val value: /* AttribValue[*/Tree/*]*/ = ch match { + case '"' | '\'' => + nextch + val tmp = xAttributeValue(delim) + nextch + try { + handle.parseAttribute(pos1, tmp) + } catch { + case e => + reportSyntaxError("error parsing attribute value") + parser.errorTermTree + } + + case '{' => + nextch + xEmbeddedExpr + case SU => + throw TruncatedXML + case _ => + reportSyntaxError("' or \" delimited attribute value" + + " or '{' scala-expr '}' expected" ) + Literal(Constant("<syntax-error>")) + } + // well-formedness constraint: unique attribute names + if (aMap.contains(key)) { + reportSyntaxError( "attribute "+key+" may only be defined once" ) + } + aMap.update(key, value) + if ((ch != '/') && (ch != '>')) { + xSpace + } + } + aMap + } + + /** attribute value, terminated by either ' or ". value may not contain <. + * @param endch either ' or " + */ + /*[Duplicate]*/ def xAttributeValue(endCh: Char): String = { + val buf = new StringBuilder + while (ch != endCh) { + if (ch == SU) + throw TruncatedXML + buf append ch + nextch + } + val str = buf.toString() + + // @todo: normalize attribute value + // well-formedness constraint + if (str.indexOf('<') != -1) { + reportSyntaxError( "'<' not allowed in attrib value" ); "" + } else { + str + } + } + + /** parse a start or empty tag. + * [40] STag ::= '<' Name { S Attribute } [S] + * [44] EmptyElemTag ::= '<' Name { S Attribute } [S] + */ + /*[Duplicate]*/ def xTag: (String, mutable.Map[String, Tree]) = { + val elemName = xName + xSpaceOpt + val aMap = + if (xml.Parsing.isNameStart(ch)) xAttributes + else new mutable.HashMap[String, Tree]() + (elemName, aMap) + } + + /** [42] '<' xmlEndTag ::= '<' '/' Name S? '>' + */ + /*[Duplicate]*/ def xEndTag(startName: String) { + xToken('/') + val endName = xName + if (endName != startName) { + reportSyntaxError("expected closing tag of " + startName) + throw MissingEndTagException + } + xSpaceOpt + xToken('>') + } + + /** '<! CharData ::= [CDATA[ ( {char} - {char}"]]>"{char} ) ']]>' + * + * see [15] + */ + /*[Duplicate]*/ def xCharData: Tree = { + xToken('[') + xToken('C') + xToken('D') + xToken('A') + xToken('T') + xToken('A') + xToken('[') + val pos1 = pos + val sb: StringBuilder = new StringBuilder() + while (true) { + if (ch==']' && + { sb.append(ch); nextch; ch == ']' } && + { sb.append(ch); nextch; ch == '>' }) { + sb.length = sb.length - 2 + nextch + return handle.charData(pos1, sb.toString()) + } else if (ch == SU) + throw TruncatedXML + else + sb.append(ch) + nextch + } + Predef.error("this cannot happen") + } + + def xUnparsed: Tree = { + val pos1 = pos + val sb: StringBuilder = new StringBuilder() + while (true) { + if (ch=='<' && + { sb.append(ch); nextch; ch == '/' } && + { sb.append(ch); nextch; ch == 'x' } && + { sb.append(ch); nextch; ch == 'm' } && + { sb.append(ch); nextch; ch == 'l' } && + { sb.append(ch); nextch; ch == ':' } && + { sb.append(ch); nextch; ch == 'u' } && + { sb.append(ch); nextch; ch == 'n' } && + { sb.append(ch); nextch; ch == 'p' } && + { sb.append(ch); nextch; ch == 'a' } && + { sb.append(ch); nextch; ch == 'r' } && + { sb.append(ch); nextch; ch == 's' } && + { sb.append(ch); nextch; ch == 'e' } && + { sb.append(ch); nextch; ch == 'd' } && + { sb.append(ch); nextch; ch == '>' }) { + sb.length = sb.length - "</xml:unparsed".length + nextch + return handle.unparsed(pos1, sb.toString()) + } else if (ch == SU) { + throw TruncatedXML + } else sb.append(ch) + nextch + } + Predef.error("this cannot happen") + } + + /** CharRef ::= "&#" '0'..'9' {'0'..'9'} ";" + * | "&#x" '0'..'9'|'A'..'F'|'a'..'f' { hexdigit } ";" + * + * see [66] + */ + /*[Duplicate]*/ def xCharRef: String = { + val hex = (ch == 'x') && { nextch; true } + val base = if (hex) 16 else 10 + var i = 0 + while (ch != ';') { + (ch: @switch) match { + case '0' | '1' | '2' | '3' | '4' | '5' | '6' | '7' | '8' | '9' => + i = i * base + ch.asDigit + case 'a' | 'b' | 'c' | 'd' | 'e' | 'f' + | 'A' | 'B' | 'C' | 'D' | 'E' | 'F' => + if (!hex) + reportSyntaxError("hex char not allowed in decimal char ref\n" + +"Did you mean to write &#x ?"); + else + i = i * base + ch.asDigit + case SU => + throw TruncatedXML + case _ => + reportSyntaxError("character '"+ch+"' not allowed in char ref") + } + nextch + } + new String(Array(i.asInstanceOf[Char])) + } + + /** Comment ::= '<!--' ((Char - '-') | ('-' (Char - '-')))* '-->' + * + * see [15] + */ + /*[Duplicate]*/ def xComment: Tree = { + val sb: StringBuilder = new StringBuilder() + xToken('-') + xToken('-') + while (true) { + if (ch=='-' && { sb.append(ch); nextch; ch == '-' }) { + sb.length = sb.length - 1 + nextch + xToken('>') + return handle.comment(pos, sb.toString()) + } else if (ch == SU) { + throw TruncatedXML + } else sb.append(ch) + nextch + } + Predef.error("this cannot happen") + } + + /** + * @param pos ... + * @param ts ... + * @param txt ... + */ + /*[Duplicate]*/ def appendText(pos: Position, ts: mutable.Buffer[Tree], + txt: String) { + if (!preserveWS) { + for (t <- TextBuffer.fromString(txt).toText) { + ts.append(handle.text(pos, t.text)) + } + } + else + ts.append( handle.text(pos, txt)) + } + + /** adds entity/character to to ts as side-effect + * @precond ch == '&' + */ + def content_AMP(ts: mutable.ArrayBuffer[Tree]) { + nextch + ch match { + case '#' => // CharacterRef + nextch + val theChar = handle.text(tmppos, xCharRef) + xToken(';') + ts.append(theChar) + case _ => // EntityRef + val n = xName + xToken(';') + ts.append(handle.entityRef(tmppos, n)) + } + } + + /** + * @precond ch == '{' + * @postcond: xEmbeddedBlock == false! + */ + def content_BRACE(p: Position, ts:mutable.ArrayBuffer[Tree]) { + if (xCheckEmbeddedBlock) + ts.append(xEmbeddedExpr) + else { + appendText(p, ts, xText)/* + val str = new StringBuilder("{") + str.append(xText) + nextch + appendText(p, ts, str.toString())*/ + } + } + + /** Returns true if it encounters an end tag (without consuming it), + * appends trees to ts as side-effect. + * + * @param ts ... + * @return ... + */ + private def content_LT(ts: mutable.ArrayBuffer[Tree]): Boolean = { + ch match { + case '/' => + return true // end tag + case '!' => + nextch // CDATA or Comment + ts.append(if ('[' == ch) xCharData else xComment) + case '?' => // PI + nextch + ts.append(xProcInstr) + case _ => + ts.append(element) // child node + } + false + } + + /*[Duplicate]*/ def content: mutable.Buffer[Tree] = { + var ts = new mutable.ArrayBuffer[Tree] + var exit = false + while (!exit) { + if (xEmbeddedBlock) + ts.append(xEmbeddedExpr) + else { + tmppos = pos + ch match { + case '<' => // end tag, cdata, comment, pi or child node + nextch + exit = content_LT(ts) + case '{' => // either the character '{' or an embedded scala block + content_BRACE(tmppos, ts) + case '&' => // EntityRef or CharRef + content_AMP(ts) + case SU => + exit = true + case _ => // text content + appendText(tmppos, ts, xText) + // here xEmbeddedBlock might be true + } + } + } + ts + } + + /** '<' element ::= xmlTag1 '>' { xmlExpr | '{' simpleExpr '}' } ETag + * | xmlTag1 '/' '>' + */ + /*[Duplicate]*/ def element: Tree = { + val pos1 = pos + val (qname, attrMap) = xTag + if (ch == '/') { // empty element + xToken('/') + xToken('>') + handle.element(pos1, qname, attrMap, new mutable.ListBuffer[Tree]) + } + else { // handle content + xToken('>') + if (qname == "xml:unparsed") + return xUnparsed + + debugLastStartElement.push((pos1, qname)) + val ts = content + xEndTag(qname) + debugLastStartElement.pop + qname match { + case "xml:group" => handle.group(pos1, ts) + case _ => handle.element(pos1, qname, attrMap, ts) + } + } + } + + /** actually, Name ::= (Letter | '_' | ':') (NameChar)* but starting with ':' cannot happen + * Name ::= (Letter | '_') (NameChar)* + * + * see [5] of XML 1.0 specification + * + * pre-condition: ch != ':' // assured by definition of XMLSTART token + * post-condition: name does neither start, nor end in ':' + */ + /*[Duplicate]*/ def xName: String = { + if (ch == SU) { + throw TruncatedXML + } else if ( !xml.Parsing.isNameStart(ch)) { + reportSyntaxError("name expected, but char '"+ch+"' cannot start a name") + return "" + } + val buf = new StringBuilder + do { + buf append ch; nextch + } while (xml.Parsing.isNameChar(ch)) + if (':' == buf.last) { + reportSyntaxError( "name cannot end in ':'" ) + buf.setLength(buf.length - 1) + } + val n = buf.toString().intern() + //cbuf.length = 0 + n + } + + /** scan [S] '=' [S]*/ + /*[Duplicate]*/ def xEQ = { xSpaceOpt; xToken('='); xSpaceOpt } + + /** skip optional space S? */ + /*[Duplicate]*/ def xSpaceOpt = { while (xml.Parsing.isSpace(ch)) { nextch }} + + /** scan [3] S ::= (#x20 | #x9 | #xD | #xA)+ */ + /*[Duplicate]*/ def xSpace = + if (xml.Parsing.isSpace(ch)) { nextch; xSpaceOpt } + else if (ch == SU) + throw TruncatedXML + else reportSyntaxError("whitespace expected") + + /** '<?' ProcInstr ::= Name [S ({Char} - ({Char}'>?' {Char})]'?>' + * + * see [15] + */ + /*[Duplicate]*/ def xProcInstr: Tree = { + val sb: StringBuilder = new StringBuilder() + val n = xName + if (xml.Parsing.isSpace(ch)) { + xSpace + while (true) { + if (ch == '?' && { sb.append(ch); nextch; ch == '>' }) { + sb.length = sb.length - 1 + nextch + return handle.procInstr(tmppos, n, sb.toString) + } else + sb.append(ch); + nextch + } + } + xToken('?') + xToken('>') + handle.procInstr(tmppos, n, sb.toString) + } + + /** parse character data. + * precondition: xEmbeddedBlock == false (we are not in a scala block) + */ + /*[Duplicate]*/ def xText: String = { + if (xEmbeddedBlock) Predef.error("internal error: encountered embedded block"); // assert + //Console.println("xText ch now "+ch) + //if( xCheckEmbeddedBlock ) { + // return "" + //} else { + var exit = false + val buf = new StringBuilder + while (!exit && (ch!=SU)) { + buf append ch + val expectRBRACE = ch == '}' + // TODO check for "}}" + nextch + if (expectRBRACE) { + if (ch == '}') + nextch + else { + reportSyntaxError("in XML content, please use '}}' to express '}'") + throw ConfusedAboutBracesException + } + } + exit = xCheckEmbeddedBlock ||(ch == '<') || (ch == '&') + } + val str = buf.toString() + //cbuf.length = 0 + str + //} + } + + //val cbuf = new StringBuilder() + + /** append Unicode character to name buffer*/ + //private def putChar(c: char) = cbuf.append(c) + + /** xLiteral = element { element } + * @return Scala representation of this xml literal + * precondition: s.xStartsXML == true + */ + def xLiteral: Tree = try { + input = parser.in + handle.isPattern = false + + //val pos = s.currentPos + var tree:Tree = null + val ts = new mutable.ArrayBuffer[Tree]() + tmppos = (pos) // Iuli: added this line, as it seems content_LT uses tmppos when creating trees +// assert(ch == '<') +// nextch + content_LT(ts) + //Console.println("xLiteral:ts = "+ts.toList) + //lastend = s.in.bp + //lastch = s.in.ch + //if (settings.debug.value) { + // Console.println("DEBUG 1: I am getting char '"+ch+"' at lastend "+lastend+" pos = "+pos); // DEBUG + //} + input = input.lookaheadReader + xSpaceOpt + // parse more XML ? + if (ch == '<') { + input = parser.in + xSpaceOpt + while (ch == '<') { + nextch + ts.append(element) + xSpaceOpt + } + tree = handle.makeXMLseq((pos), ts) + } else { + input = parser.in + assert(ts.length == 1) + tree = ts(0) + } + tree + } catch { + case c @ TruncatedXML => + parser.incompleteInputError(c.getMessage) + EmptyTree + + case c @ (MissingEndTagException | ConfusedAboutBracesException) => + parser.syntaxError((debugLastStartElement.top._1):Int, + c.getMessage + debugLastStartElement.top._2+">") + EmptyTree + + case _:ArrayIndexOutOfBoundsException => + parser.syntaxError((debugLastStartElement.top._1), + "missing end tag in XML literal for <" + +debugLastStartElement.top._2+">"); + EmptyTree + } finally { + parser.in.resume(Tokens.XMLSTART) + } + + /** @see xmlPattern. resynchronizes after successful parse + * @return this xml pattern + * precondition: s.xStartsXML == true + */ + def xLiteralPattern: Tree = try { + input = parser.in + val oldMode = handle.isPattern; + handle.isPattern = true +// assert(ch == '<') +// nextch + var tree = xPattern; xSpaceOpt; + handle.isPattern = oldMode; + tree + } catch { + case c @ TruncatedXML => + parser.syntaxError(pos - 1, c.getMessage) + EmptyTree + + case c @ (MissingEndTagException | ConfusedAboutBracesException) => + parser.syntaxError((debugLastStartElement.top._1), + c.getMessage + debugLastStartElement.top._2+">") + EmptyTree + + case _:ArrayIndexOutOfBoundsException => + parser.syntaxError((debugLastStartElement.top._1), + "missing end tag in XML literal for <" + +debugLastStartElement.top._2+">") + EmptyTree + } finally { + parser.in.resume(Tokens.XMLSTART) + } + + def escapeToScala[A](op: => A, kind: String) = { + xEmbeddedBlock = false + val savedSepRegions = parser.in.sepRegions + parser.in.resume(LBRACE) + try { + op //p.expr(true,false); + } finally { + parser.in.sepRegions = savedSepRegions // parser.in.sepRegions.tail + if (parser.in.token != RBRACE) { + reportSyntaxError(" expected end of Scala "+kind) + } + } + } + + def xEmbeddedExpr: Tree = escapeToScala(parser.block(), "block") + + /** xScalaPatterns ::= patterns + */ + def xScalaPatterns: List[Tree] = escapeToScala(parser.patterns(true), "pattern") + + var scannerState: List[List[Int]] = Nil + +/* + private def pushScannerState { + scannerState = s.sepRegions :: scannerState + s.sepRegions = Nil + } + private def popScannerState { + s.sepRegions = scannerState.head + scannerState = scannerState.tail + } + */ +/* + private def init { + ch = s.in.ch + pos = s.in.cpos + } + */ + + def reportSyntaxError(str: String) = { + parser.syntaxError(pos - 1, "in XML literal: " + str) + nextch + } +/* + private def sync { + xEmbeddedBlock = false + s.xSync + } +*/ + /** '<' xPattern ::= Name [S] { xmlPattern | '{' pattern3 '}' } ETag + * | Name [S] '/' '>' + */ + def xPattern: Tree = { + val pos1 = pos + val qname = xName + debugLastStartElement.push((pos1, qname)) + xSpaceOpt + if (ch == '/') { // empty tag + nextch + xToken('>') + return handle.makeXMLpat(pos1, qname, new mutable.ArrayBuffer[Tree]()) + } + + // else: tag with content + xToken('>') + var ts = new mutable.ArrayBuffer[Tree] + var exit = false + while (! exit) { + val pos2 = pos + if (xEmbeddedBlock) { + ts ++= xScalaPatterns + } else + ch match { + case '<' => // tag + nextch + if (ch != '/') { //child + ts.append(xPattern) + } else { + exit = true + } + case '{' => // embedded Scala patterns + while (ch == '{') { + nextch + ts ++= xScalaPatterns + } + // postcond: xEmbeddedBlock = false; + if (xEmbeddedBlock) Predef.error("problem with embedded block"); // assert + + case SU => + throw TruncatedXML + + case _ => // teMaxt + appendText(pos2, ts, xText) + // here xEmbeddedBlock might be true; + //if( xEmbeddedBlock ) throw new ApplicationError("after:"+text); // assert + } + } + xEndTag(qname) + debugLastStartElement.pop + handle.makeXMLpat(pos1, qname, ts) + } + + } /* class MarkupParser */ +} |