From 1a1d09ce961e318f06ed52dd74b250b11358d050 Mon Sep 17 00:00:00 2001 From: Adriaan Moors Date: Tue, 18 Jun 2013 18:57:05 -0700 Subject: Remove dependency on xml in sys.process. Added deprecated conversions in xml.Elem to preserve source compatibility. --- src/library/scala/sys/process/Process.scala | 17 ----------------- src/library/scala/sys/process/package.scala | 5 +---- src/library/scala/xml/Elem.scala | 23 +++++++++++++++++++++++ 3 files changed, 24 insertions(+), 21 deletions(-) diff --git a/src/library/scala/sys/process/Process.scala b/src/library/scala/sys/process/Process.scala index 402183a1f0..dcd06c89e9 100644 --- a/src/library/scala/sys/process/Process.scala +++ b/src/library/scala/sys/process/Process.scala @@ -127,15 +127,6 @@ trait ProcessCreation { */ def apply(url: URL): URLBuilder = new URLImpl(url) - /** Creates a [[scala.sys.process.ProcessBuilder]] from a Scala XML Element. - * This can be used as a way to template strings. - * - * @example {{{ - * apply( {dxPath.absolutePath} --dex --output={classesDexPath.absolutePath} {classesMinJarPath.absolutePath}) - * }}} - */ - def apply(command: scala.xml.Elem): ProcessBuilder = apply(command.text.trim) - /** Creates a [[scala.sys.process.ProcessBuilder]] from a `Boolean`. This can be * to force an exit value. */ @@ -220,14 +211,6 @@ trait ProcessImplicits { */ implicit def urlToProcess(url: URL): URLBuilder = apply(url) - /** Implicitly convert a [[scala.xml.Elem]] into a - * [[scala.sys.process.ProcessBuilder]]. This is done by obtaining the text - * elements of the element, trimming spaces, and then converting the result - * from string to a process. Importantly, tags are completely ignored, so - * they cannot be used to separate parameters. - */ - implicit def xmlToProcess(command: scala.xml.Elem): ProcessBuilder = apply(command) - /** Implicitly convert a `String` into a [[scala.sys.process.ProcessBuilder]]. */ implicit def stringToProcess(command: String): ProcessBuilder = apply(command) diff --git a/src/library/scala/sys/process/package.scala b/src/library/scala/sys/process/package.scala index 902543665f..d68cd004f8 100644 --- a/src/library/scala/sys/process/package.scala +++ b/src/library/scala/sys/process/package.scala @@ -80,10 +80,7 @@ package scala.sys { * spaces -- no escaping of spaces is possible -- or out of a * [[scala.collection.Seq]], where the first element represents the command * name, and the remaining elements are arguments to it. In this latter case, - * arguments may contain spaces. One can also implicitly convert - * [[scala.xml.Elem]] and `java.lang.ProcessBuilder` into a `ProcessBuilder`. - * In the introductory example, the strings were converted into - * `ProcessBuilder` implicitly. + * arguments may contain spaces. * * To further control what how the process will be run, such as specifying * the directory in which it will be run, see the factories on diff --git a/src/library/scala/xml/Elem.scala b/src/library/scala/xml/Elem.scala index 4200b7046a..484cf98744 100755 --- a/src/library/scala/xml/Elem.scala +++ b/src/library/scala/xml/Elem.scala @@ -35,8 +35,31 @@ object Elem { case _: SpecialNode | _: Group => None case _ => Some((n.prefix, n.label, n.attributes, n.scope, n.child)) } + + import scala.sys.process._ + /** Implicitly convert a [[scala.xml.Elem]] into a + * [[scala.sys.process.ProcessBuilder]]. This is done by obtaining the text + * elements of the element, trimming spaces, and then converting the result + * from string to a process. Importantly, tags are completely ignored, so + * they cannot be used to separate parameters. + */ + @deprecated("To create a scala.sys.process.Process from an xml.Elem, please use Process(elem.text.trim).", "2.11.0") + implicit def xmlToProcess(command: scala.xml.Elem): ProcessBuilder = Process(command.text.trim) + + @deprecated("To create a scala.sys.process.Process from an xml.Elem, please use Process(elem.text.trim).", "2.11.0") + implicit def processXml(p: Process.type) = new { + /** Creates a [[scala.sys.process.ProcessBuilder]] from a Scala XML Element. + * This can be used as a way to template strings. + * + * @example {{{ + * apply( {dxPath.absolutePath} --dex --output={classesDexPath.absolutePath} {classesMinJarPath.absolutePath}) + * }}} + */ + def apply(command: Elem): ProcessBuilder = Process(command.text.trim) + } } + /** The case class `Elem` extends the `Node` class, * providing an immutable data object representing an XML element. * -- cgit v1.2.3 From 739cc9d1ad8b7d1def468299c9cdc6e9ebb2d143 Mon Sep 17 00:00:00 2001 From: Adriaan Moors Date: Tue, 18 Jun 2013 19:09:40 -0700 Subject: Remove dependency on xml in ScalaRunTime. runtime.ScalaRunTime.stringOf's behavior has changed slightly: Call the object's `toString` for all classes in `scala.xml._`, instead of only for `Node` and `MetaData` (old behavior). --- src/library/scala/runtime/ScalaRunTime.scala | 7 +++---- 1 file changed, 3 insertions(+), 4 deletions(-) diff --git a/src/library/scala/runtime/ScalaRunTime.scala b/src/library/scala/runtime/ScalaRunTime.scala index 3a85207235..c049de3a28 100644 --- a/src/library/scala/runtime/ScalaRunTime.scala +++ b/src/library/scala/runtime/ScalaRunTime.scala @@ -15,7 +15,6 @@ import scala.collection.immutable.{ StringLike, NumericRange, List, Stream, Nil, import scala.collection.generic.{ Sorted } import scala.reflect.{ ClassTag, classTag } import scala.util.control.ControlThrowable -import scala.xml.{ Node, MetaData } import java.lang.{ Class => jClass } import java.lang.Double.doubleToLongBits @@ -268,11 +267,10 @@ object ScalaRunTime { } def isScalaClass(x: AnyRef) = packageOf(x) startsWith "scala." def isScalaCompilerClass(x: AnyRef) = packageOf(x) startsWith "scala.tools.nsc." + def isXmlClass(x: AnyRef) = packageOf(x) startsWith "scala.xml." // When doing our own iteration is dangerous def useOwnToString(x: Any) = x match { - // Node extends NodeSeq extends Seq[Node] and MetaData extends Iterable[MetaData] - case _: Node | _: MetaData => true // Range/NumericRange have a custom toString to avoid walking a gazillion elements case _: Range | _: NumericRange[_] => true // Sorted collections to the wrong thing (for us) on iteration - ticket #3493 @@ -281,10 +279,11 @@ object ScalaRunTime { case _: StringLike[_] => true // Don't want to evaluate any elements in a view case _: TraversableView[_, _] => true + // Node extends NodeSeq extends Seq[Node] and MetaData extends Iterable[MetaData] -> catch those and more by isXmlClass(x) // Don't want to a) traverse infinity or b) be overly helpful with peoples' custom // collections which may have useful toString methods - ticket #3710 // or c) print AbstractFiles which are somehow also Iterable[AbstractFile]s. - case x: Traversable[_] => !x.hasDefiniteSize || !isScalaClass(x) || isScalaCompilerClass(x) + case x: Traversable[_] => !x.hasDefiniteSize || !isScalaClass(x) || isScalaCompilerClass(x) || isXmlClass(x) // Otherwise, nothing could possibly go wrong case _ => false } -- cgit v1.2.3 From edee7dbfce89841cecdba3996492d3741f9e803e Mon Sep 17 00:00:00 2001 From: Adriaan Moors Date: Wed, 19 Jun 2013 17:02:27 -0700 Subject: Remove dependency on xml in plugin loading. --- src/compiler/scala/tools/nsc/plugins/Plugin.scala | 7 ++- .../tools/nsc/plugins/PluginDescription.scala | 63 ++++++++++------------ 2 files changed, 31 insertions(+), 39 deletions(-) diff --git a/src/compiler/scala/tools/nsc/plugins/Plugin.scala b/src/compiler/scala/tools/nsc/plugins/Plugin.scala index a584a4ed5d..4fd6ba7d9d 100644 --- a/src/compiler/scala/tools/nsc/plugins/Plugin.scala +++ b/src/compiler/scala/tools/nsc/plugins/Plugin.scala @@ -14,7 +14,6 @@ import java.util.zip.ZipException import scala.collection.mutable.ListBuffer import scala.util.{ Try, Success, Failure } -import scala.xml.XML /** Information about a plugin loaded from a jar file. * @@ -81,14 +80,14 @@ object Plugin { private def loadDescriptionFromJar(jarp: Path): Try[PluginDescription] = { // XXX Return to this once we have more ARM support def read(is: Option[InputStream]) = is match { - case None => throw new RuntimeException(s"Missing $PluginXML in $jarp") - case _ => PluginDescription fromXML (XML load is.get) + case None => throw new RuntimeException(s"Missing $PluginXML in $jarp") + case Some(is) => PluginDescription.fromXML(is) } Try(new Jar(jarp.jfile).withEntryStream(PluginXML)(read)) } private def loadDescriptionFromFile(f: Path): Try[PluginDescription] = - Try(XML loadFile f.jfile) map (PluginDescription fromXML _) + Try(PluginDescription.fromXML(new java.io.FileInputStream(f.jfile))) type AnyClass = Class[_] diff --git a/src/compiler/scala/tools/nsc/plugins/PluginDescription.scala b/src/compiler/scala/tools/nsc/plugins/PluginDescription.scala index 27693d1a45..bf78c93fcc 100644 --- a/src/compiler/scala/tools/nsc/plugins/PluginDescription.scala +++ b/src/compiler/scala/tools/nsc/plugins/PluginDescription.scala @@ -6,57 +6,50 @@ package scala.tools.nsc package plugins -import scala.xml.Node +import scala.reflect.internal.util.StringContextStripMarginOps /** A description of a compiler plugin, suitable for serialization * to XML for inclusion in the plugin's .jar file. * * @author Lex Spoon * @version 1.0, 2007-5-21 - * @param name A short name of the plugin, used to identify it in - * various contexts. The phase defined by the plugin - * should have the same name. - * @param classname The name of the main Plugin class. + * @author Adriaan Moors + * @version 2.0, 2013 + * @param name A short name of the plugin, used to identify it in + * various contexts. The phase defined by the plugin + * should have the same name. + * @param classname The name of the main Plugin class. */ case class PluginDescription(name: String, classname: String) { - - /** An XML representation of this description. It can be - * read back using `PluginDescription.fromXML`. + /** An XML representation of this description. * It should be stored inside the jar archive file. */ - def toXML: Node = { - - {name} - {classname} - - } + def toXML: String = + sm""" + | ${name} + | ${classname} + |""" } /** Utilities for the PluginDescription class. * - * @author Lex Spoon - * @version 1.0, 2007-5-21 + * @author Lex Spoon + * @version 1.0, 2007-5-21 + * @author Adriaan Moors + * @version 2.0, 2013 */ object PluginDescription { + private def text(ns: org.w3c.dom.NodeList): String = + if (ns.getLength == 1) ns.item(0).getTextContent.trim + else throw new RuntimeException("Bad plugin descriptor.") + + def fromXML(xml: java.io.InputStream): PluginDescription = { + import javax.xml.parsers.DocumentBuilderFactory + val root = DocumentBuilderFactory.newInstance.newDocumentBuilder.parse(xml).getDocumentElement + root.normalize() + if (root.getNodeName != "plugin") + throw new RuntimeException("Plugin descriptor root element must be .") - def fromXML(xml: Node): PluginDescription = { - // extract one field - def getField(field: String): Option[String] = { - val text = (xml \\ field).text.trim - if (text == "") None else Some(text) - } - def extracted = { - val name = "name" - val claas = "classname" - val vs = Map(name -> getField(name), claas -> getField(claas)) - if (vs.values exists (_.isEmpty)) fail() - else PluginDescription(name = vs(name).get, classname = vs(claas).get) - } - def fail() = throw new RuntimeException("Bad plugin descriptor.") - // check the top-level tag - xml match { - case {_*} => extracted - case _ => fail() - } + PluginDescription(text(root.getElementsByTagName("name")), text(root.getElementsByTagName("classname"))) } } -- cgit v1.2.3 From ef1264b2ff134771a2df4ee30e9a509fb7a78c49 Mon Sep 17 00:00:00 2001 From: Adriaan Moors Date: Thu, 20 Jun 2013 10:41:49 -0700 Subject: Remove dependency on xml in MarkupParsers Inlined TextBuffer. --- .../scala/tools/nsc/ast/parser/MarkupParsers.scala | 20 ++++++++++++++------ 1 file changed, 14 insertions(+), 6 deletions(-) diff --git a/src/compiler/scala/tools/nsc/ast/parser/MarkupParsers.scala b/src/compiler/scala/tools/nsc/ast/parser/MarkupParsers.scala index 832a9bf63e..70b9bf3168 100755 --- a/src/compiler/scala/tools/nsc/ast/parser/MarkupParsers.scala +++ b/src/compiler/scala/tools/nsc/ast/parser/MarkupParsers.scala @@ -10,7 +10,6 @@ import scala.collection.mutable import mutable.{ Buffer, ArrayBuffer, ListBuffer } import scala.util.control.ControlThrowable import scala.tools.nsc.util.CharArrayReader -import scala.xml.TextBuffer import scala.xml.parsing.MarkupParserCommon import scala.reflect.internal.Chars.{ SU, LF } @@ -172,12 +171,21 @@ trait MarkupParsers { xTakeUntil(handle.comment, () => r2p(start, start, curOffset), "-->") } - def appendText(pos: Position, ts: Buffer[Tree], txt: String) { - val toAppend = - if (preserveWS) Seq(txt) - else TextBuffer.fromString(txt).toText map (_.text) + def appendText(pos: Position, ts: Buffer[Tree], txt: String): Unit = { + def append(t: String) = ts append handle.text(pos, t) - toAppend foreach (t => ts append handle.text(pos, t)) + if (preserveWS) append(txt) + else { + val sb = new StringBuilder() + + txt foreach { c => + if (!isSpace(c)) sb append c + else if (sb.isEmpty || !isSpace(sb.last)) sb append ' ' + } + + val trimmed = sb.toString.trim + if (!trimmed.isEmpty) append(trimmed) + } } /** adds entity/character to ts as side-effect -- cgit v1.2.3 From 4e9b33ab24bb3bf922c37a05a79af364b7b32b84 Mon Sep 17 00:00:00 2001 From: Adriaan Moors Date: Thu, 20 Jun 2013 10:55:58 -0700 Subject: Remove dependency on xml in ast.parser Copied the following files from scala.xml to ast.parser.xml: - MarkupParsers differs from scala.xml.MarkupParsers only in the first 17 lines - Utility.scala was refactored and reduced (also includes TokenTests) --- .../scala/tools/nsc/ast/parser/MarkupParsers.scala | 4 +- .../scala/tools/nsc/ast/parser/Scanners.scala | 2 +- .../tools/nsc/ast/parser/SymbolicXMLBuilder.scala | 18 +- .../nsc/ast/parser/xml/MarkupParserCommon.scala | 255 +++++++++++++++++++++ .../scala/tools/nsc/ast/parser/xml/Utility.scala | 176 ++++++++++++++ 5 files changed, 441 insertions(+), 14 deletions(-) create mode 100644 src/compiler/scala/tools/nsc/ast/parser/xml/MarkupParserCommon.scala create mode 100755 src/compiler/scala/tools/nsc/ast/parser/xml/Utility.scala diff --git a/src/compiler/scala/tools/nsc/ast/parser/MarkupParsers.scala b/src/compiler/scala/tools/nsc/ast/parser/MarkupParsers.scala index 70b9bf3168..d3f495f280 100755 --- a/src/compiler/scala/tools/nsc/ast/parser/MarkupParsers.scala +++ b/src/compiler/scala/tools/nsc/ast/parser/MarkupParsers.scala @@ -10,7 +10,7 @@ import scala.collection.mutable import mutable.{ Buffer, ArrayBuffer, ListBuffer } import scala.util.control.ControlThrowable import scala.tools.nsc.util.CharArrayReader -import scala.xml.parsing.MarkupParserCommon +import scala.tools.nsc.ast.parser.xml.{MarkupParserCommon, Utility} import scala.reflect.internal.Chars.{ SU, LF } // XXX/Note: many/most of the functions in here are almost direct cut and pastes @@ -41,7 +41,7 @@ trait MarkupParsers { import global._ class MarkupParser(parser: SourceFileParser, final val preserveWS: Boolean) extends MarkupParserCommon { - + import Utility.{ isNameStart, isSpace } import Tokens.{ LBRACE, RBRACE } type PositionType = Position diff --git a/src/compiler/scala/tools/nsc/ast/parser/Scanners.scala b/src/compiler/scala/tools/nsc/ast/parser/Scanners.scala index 82a3144304..a8162a01bf 100644 --- a/src/compiler/scala/tools/nsc/ast/parser/Scanners.scala +++ b/src/compiler/scala/tools/nsc/ast/parser/Scanners.scala @@ -12,7 +12,7 @@ import Tokens._ import scala.annotation.{ switch, tailrec } import scala.collection.{ mutable, immutable } import mutable.{ ListBuffer, ArrayBuffer } -import scala.xml.Utility.{ isNameStart } +import scala.tools.nsc.ast.parser.xml.Utility.isNameStart import scala.language.postfixOps /** See Parsers.scala / ParsersCommon for some explanation of ScannersCommon. diff --git a/src/compiler/scala/tools/nsc/ast/parser/SymbolicXMLBuilder.scala b/src/compiler/scala/tools/nsc/ast/parser/SymbolicXMLBuilder.scala index f326212d5b..1abc0c860c 100755 --- a/src/compiler/scala/tools/nsc/ast/parser/SymbolicXMLBuilder.scala +++ b/src/compiler/scala/tools/nsc/ast/parser/SymbolicXMLBuilder.scala @@ -7,8 +7,6 @@ package scala.tools.nsc package ast.parser import scala.collection.{ mutable, immutable } -import scala.xml.{ EntityRef, Text } -import scala.xml.XML.{ xmlns } import symtab.Flags.MUTABLE import scala.reflect.internal.util.StringOps.splitWhere @@ -143,14 +141,12 @@ abstract class SymbolicXMLBuilder(p: Parsers#Parser, preserveWS: Boolean) { (buf map convertToTextPat).toList def parseAttribute(pos: Position, s: String): Tree = { - val ts = scala.xml.Utility.parseAttributeValue(s) map { - case Text(s) => text(pos, s) - case EntityRef(s) => entityRef(pos, s) - } - ts.length match { - case 0 => gen.mkNil - case 1 => ts.head - case _ => makeXMLseq(pos, ts.toList) + import xml.Utility.parseAttributeValue + + parseAttributeValue(s, text(pos, _), entityRef(pos, _)) match { + case Nil => gen.mkNil + case t :: Nil => t + case ts => makeXMLseq(pos, ts.toList) } } @@ -198,7 +194,7 @@ abstract class SymbolicXMLBuilder(p: Parsers#Parser, preserveWS: Boolean) { /* Extract all the namespaces from the attribute map. */ val namespaces: List[Tree] = - for (z <- attrMap.keys.toList ; if z startsWith xmlns) yield { + for (z <- attrMap.keys.toList ; if z startsWith "xmlns") yield { val ns = splitPrefix(z) match { case (Some(_), rest) => rest case _ => null diff --git a/src/compiler/scala/tools/nsc/ast/parser/xml/MarkupParserCommon.scala b/src/compiler/scala/tools/nsc/ast/parser/xml/MarkupParserCommon.scala new file mode 100644 index 0000000000..f6cfb64ed8 --- /dev/null +++ b/src/compiler/scala/tools/nsc/ast/parser/xml/MarkupParserCommon.scala @@ -0,0 +1,255 @@ +/* __ *\ +** ________ ___ / / ___ Scala API ** +** / __/ __// _ | / / / _ | (c) 2003-2013, LAMP/EPFL ** +** __\ \/ /__/ __ |/ /__/ __ | http://scala-lang.org/ ** +** /____/\___/_/ |_/____/_/ | | ** +** |/ ** +\* */ + +package scala.tools.nsc.ast.parser.xml + +/** This is not a public trait - it contains common code shared + * between the library level XML parser and the compiler's. + * All members should be accessed through those. + */ +private[scala] trait MarkupParserCommon { + import Utility._ + import scala.reflect.internal.Chars.SU + + protected def unreachable = scala.sys.error("Cannot be reached.") + + // type HandleType // MarkupHandler, SymbolicXMLBuilder + type InputType // Source, CharArrayReader + type PositionType // Int, Position + type ElementType // NodeSeq, Tree + type NamespaceType // NamespaceBinding, Any + type AttributesType // (MetaData, NamespaceBinding), mutable.Map[String, Tree] + + def mkAttributes(name: String, pscope: NamespaceType): AttributesType + def mkProcInstr(position: PositionType, name: String, text: String): ElementType + + /** parse a start or empty tag. + * [40] STag ::= '<' Name { S Attribute } [S] + * [44] EmptyElemTag ::= '<' Name { S Attribute } [S] + */ + protected def xTag(pscope: NamespaceType): (String, AttributesType) = { + val name = xName + xSpaceOpt() + + (name, mkAttributes(name, pscope)) + } + + /** '?' {Char})]'?>' + * + * see [15] + */ + def xProcInstr: ElementType = { + val n = xName + xSpaceOpt() + xTakeUntil(mkProcInstr(_, n, _), () => tmppos, "?>") + } + + /** attribute value, terminated by either `'` or `"`. value may not contain `<`. + @param endCh either `'` or `"` + */ + def xAttributeValue(endCh: Char): String = { + val buf = new StringBuilder + while (ch != endCh) { + // well-formedness constraint + if (ch == '<') return errorAndResult("'<' not allowed in attrib value", "") + else if (ch == SU) truncatedError("") + else buf append ch_returning_nextch + } + ch_returning_nextch + // @todo: normalize attribute value + buf.toString + } + + def xAttributeValue(): String = { + val str = xAttributeValue(ch_returning_nextch) + // well-formedness constraint + normalizeAttributeValue(str) + } + + private def takeUntilChar(it: Iterator[Char], end: Char): String = { + val buf = new StringBuilder + while (it.hasNext) it.next() match { + case `end` => return buf.toString + case ch => buf append ch + } + scala.sys.error("Expected '%s'".format(end)) + } + + /** [42] '<' xmlEndTag ::= '<' '/' Name S? '>' + */ + def xEndTag(startName: String) { + xToken('/') + if (xName != startName) + errorNoEnd(startName) + + xSpaceOpt() + xToken('>') + } + + /** 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 ':' + */ + def xName: String = { + if (ch == SU) + truncatedError("") + else if (!isNameStart(ch)) + return errorAndResult("name expected, but char '%s' cannot start a name" format ch, "") + + val buf = new StringBuilder + + do buf append ch_returning_nextch + while (isNameChar(ch)) + + if (buf.last == ':') { + reportSyntaxError( "name cannot end in ':'" ) + buf.toString dropRight 1 + } + else buf.toString + } + + private def attr_unescape(s: String) = s match { + case "lt" => "<" + case "gt" => ">" + case "amp" => "&" + case "apos" => "'" + case "quot" => "\"" + case "quote" => "\"" + case _ => "&" + s + ";" + } + + /** Replaces only character references right now. + * see spec 3.3.3 + */ + private def normalizeAttributeValue(attval: String): String = { + val buf = new StringBuilder + val it = attval.iterator.buffered + + while (it.hasNext) buf append (it.next() match { + case ' ' | '\t' | '\n' | '\r' => " " + case '&' if it.head == '#' => it.next() ; xCharRef(it) + case '&' => attr_unescape(takeUntilChar(it, ';')) + case c => c + }) + + buf.toString + } + + /** CharRef ::= "&#" '0'..'9' {'0'..'9'} ";" + * | "&#x" '0'..'9'|'A'..'F'|'a'..'f' { hexdigit } ";" + * + * see [66] + */ + def xCharRef(ch: () => Char, nextch: () => Unit): String = + Utility.parseCharRef(ch, nextch, reportSyntaxError _, truncatedError _) + + def xCharRef(it: Iterator[Char]): String = { + var c = it.next() + Utility.parseCharRef(() => c, () => { c = it.next() }, reportSyntaxError _, truncatedError _) + } + + def xCharRef: String = xCharRef(() => ch, () => nextch()) + + /** Create a lookahead reader which does not influence the input */ + def lookahead(): BufferedIterator[Char] + + /** The library and compiler parsers had the interesting distinction of + * different behavior for nextch (a function for which there are a total + * of two plausible behaviors, so we know the design space was fully + * explored.) One of them returned the value of nextch before the increment + * and one of them the new value. So to unify code we have to at least + * temporarily abstract over the nextchs. + */ + def ch: Char + def nextch(): Unit + protected def ch_returning_nextch: Char + def eof: Boolean + + // def handle: HandleType + var tmppos: PositionType + + def xHandleError(that: Char, msg: String): Unit + def reportSyntaxError(str: String): Unit + def reportSyntaxError(pos: Int, str: String): Unit + + def truncatedError(msg: String): Nothing + def errorNoEnd(tag: String): Nothing + + protected def errorAndResult[T](msg: String, x: T): T = { + reportSyntaxError(msg) + x + } + + def xToken(that: Char) { + if (ch == that) nextch() + else xHandleError(that, "'%s' expected instead of '%s'".format(that, ch)) + } + def xToken(that: Seq[Char]) { that foreach xToken } + + /** scan [S] '=' [S]*/ + def xEQ() = { xSpaceOpt(); xToken('='); xSpaceOpt() } + + /** skip optional space S? */ + def xSpaceOpt() = while (isSpace(ch) && !eof) nextch() + + /** scan [3] S ::= (#x20 | #x9 | #xD | #xA)+ */ + def xSpace() = + if (isSpace(ch)) { nextch(); xSpaceOpt() } + else xHandleError(ch, "whitespace expected") + + /** Apply a function and return the passed value */ + def returning[T](x: T)(f: T => Unit): T = { f(x); x } + + /** Execute body with a variable saved and restored after execution */ + def saving[A, B](getter: A, setter: A => Unit)(body: => B): B = { + val saved = getter + try body + finally setter(saved) + } + + /** Take characters from input stream until given String "until" + * is seen. Once seen, the accumulated characters are passed + * along with the current Position to the supplied handler function. + */ + protected def xTakeUntil[T]( + handler: (PositionType, String) => T, + positioner: () => PositionType, + until: String): T = + { + val sb = new StringBuilder + val head = until.head + val rest = until.tail + + while (true) { + if (ch == head && peek(rest)) + return handler(positioner(), sb.toString) + else if (ch == SU) + truncatedError("") // throws TruncatedXMLControl in compiler + + sb append ch + nextch() + } + unreachable + } + + /** Create a non-destructive lookahead reader and see if the head + * of the input would match the given String. If yes, return true + * and drop the entire String from input; if no, return false + * and leave input unchanged. + */ + private def peek(lookingFor: String): Boolean = + (lookahead() take lookingFor.length sameElements lookingFor.iterator) && { + // drop the chars from the real reader (all lookahead + orig) + (0 to lookingFor.length) foreach (_ => nextch()) + true + } +} diff --git a/src/compiler/scala/tools/nsc/ast/parser/xml/Utility.scala b/src/compiler/scala/tools/nsc/ast/parser/xml/Utility.scala new file mode 100755 index 0000000000..39e4831af2 --- /dev/null +++ b/src/compiler/scala/tools/nsc/ast/parser/xml/Utility.scala @@ -0,0 +1,176 @@ +/* __ *\ +** ________ ___ / / ___ Scala API ** +** / __/ __// _ | / / / _ | (c) 2003-2013, LAMP/EPFL ** +** __\ \/ /__/ __ |/ /__/ __ | http://scala-lang.org/ ** +** /____/\___/_/ |_/____/_/ | | ** +** |/ ** +\* */ + +package scala.tools.nsc.ast.parser.xml + +import scala.collection.mutable + + +/** + * The `Utility` object provides utility functions for processing instances + * of bound and not bound XML classes, as well as escaping text nodes. + * + * @author Burak Emir + */ +object Utility { + import scala.reflect.internal.Chars.SU + + private val unescMap = Map( + "lt" -> '<', + "gt" -> '>', + "amp" -> '&', + "quot" -> '"', + "apos" -> '\'' + ) + + /** + * Appends unescaped string to `s`, `amp` becomes `&`, + * `lt` becomes `<` etc.. + * + * @return `'''null'''` if `ref` was not a predefined entity. + */ + private final def unescape(ref: String, s: StringBuilder): StringBuilder = + ((unescMap get ref) map (s append _)).orNull + + def parseAttributeValue[T](value: String, text: String => T, entityRef: String => T): List[T] = { + val sb = new StringBuilder + var rfb: StringBuilder = null + val nb = new mutable.ListBuffer[T]() + + val it = value.iterator + while (it.hasNext) { + var c = it.next() + // entity! flush buffer into text node + if (c == '&') { + c = it.next() + if (c == '#') { + c = it.next() + val theChar = parseCharRef ({ ()=> c },{ () => c = it.next() },{s => throw new RuntimeException(s)}, {s => throw new RuntimeException(s)}) + sb.append(theChar) + } + else { + if (rfb eq null) rfb = new StringBuilder() + rfb append c + c = it.next() + while (c != ';') { + rfb.append(c) + c = it.next() + } + val ref = rfb.toString() + rfb.clear() + unescape(ref,sb) match { + case null => + if (!sb.isEmpty) { // flush buffer + nb += text(sb.toString()) + sb.clear() + } + nb += entityRef(ref) // add entityref + case _ => + } + } + } + else sb append c + } + + if(!sb.isEmpty) // flush buffer + nb += text(sb.toString()) + + nb.toList + } + + /** + * {{{ + * CharRef ::= "&#" '0'..'9' {'0'..'9'} ";" + * | "&#x" '0'..'9'|'A'..'F'|'a'..'f' { hexdigit } ";" + * }}} + * See [66] + */ + def parseCharRef(ch: () => Char, nextch: () => Unit, reportSyntaxError: String => Unit, reportTruncatedError: String => Unit): String = { + val hex = (ch() == 'x') && { nextch(); true } + val base = if (hex) 16 else 10 + var i = 0 + while (ch() != ';') { + ch() 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 => + reportTruncatedError("") + case _ => + reportSyntaxError("character '" + ch() + "' not allowed in char ref\n") + } + nextch() + } + new String(Array(i), 0, 1) + } + + /** {{{ + * (#x20 | #x9 | #xD | #xA) + * }}} */ + final def isSpace(ch: Char): Boolean = ch match { + case '\u0009' | '\u000A' | '\u000D' | '\u0020' => true + case _ => false + } + /** {{{ + * (#x20 | #x9 | #xD | #xA)+ + * }}} */ + final def isSpace(cs: Seq[Char]): Boolean = cs.nonEmpty && (cs forall isSpace) + + /** {{{ + * NameChar ::= Letter | Digit | '.' | '-' | '_' | ':' + * | CombiningChar | Extender + * }}} + * See [4] and Appendix B of XML 1.0 specification. + */ + def isNameChar(ch: Char) = { + import java.lang.Character._ + // The constants represent groups Mc, Me, Mn, Lm, and Nd. + + isNameStart(ch) || (getType(ch).toByte match { + case COMBINING_SPACING_MARK | + ENCLOSING_MARK | NON_SPACING_MARK | + MODIFIER_LETTER | DECIMAL_DIGIT_NUMBER => true + case _ => ".-:" contains ch + }) + } + + /** {{{ + * NameStart ::= ( Letter | '_' ) + * }}} + * where Letter means in one of the Unicode general + * categories `{ Ll, Lu, Lo, Lt, Nl }`. + * + * We do not allow a name to start with `:`. + * See [3] and Appendix B of XML 1.0 specification + */ + def isNameStart(ch: Char) = { + import java.lang.Character._ + + getType(ch).toByte match { + case LOWERCASE_LETTER | + UPPERCASE_LETTER | OTHER_LETTER | + TITLECASE_LETTER | LETTER_NUMBER => true + case _ => ch == '_' + } + } + + /** {{{ + * Name ::= ( Letter | '_' ) (NameChar)* + * }}} + * See [5] of XML 1.0 specification. + */ + def isName(s: String) = + s.nonEmpty && isNameStart(s.head) && (s.tail forall isNameChar) + +} -- cgit v1.2.3 From 9b72669a076206f1edbe34464717375c97997cea Mon Sep 17 00:00:00 2001 From: Jason Zaugg Date: Tue, 22 Jan 2013 00:33:18 +0100 Subject: Set scene for Predef.$scope's demise. When there's no Predef.$scope but xml is being used, the compiler aliases scala.xml.TopScope to $scope. There must be a scala.xml package when xml literals were parsed. For compatibility with the old library, which relied on $scope being in scope, synthesize a `import scala.xml.{TopScope => $scope}` when xml is needed, but there's no Predef.$scope and the old library is detected (scala.xml.TopScope exists). --- .../scala/tools/nsc/CompilationUnits.scala | 14 ++++++++++++ src/compiler/scala/tools/nsc/ast/TreeGen.scala | 19 +++++++++++----- .../scala/tools/nsc/ast/parser/Parsers.scala | 13 ++++++++--- .../scala/tools/nsc/typechecker/Contexts.scala | 26 ++++++++++++++++++++-- src/library/scala/Predef.scala | 8 ++++++- .../scala/reflect/internal/Definitions.scala | 6 ++++- src/reflect/scala/reflect/internal/Mirrors.scala | 3 +++ src/reflect/scala/reflect/internal/StdNames.scala | 2 ++ src/reflect/scala/reflect/internal/Trees.scala | 2 +- 9 files changed, 80 insertions(+), 13 deletions(-) diff --git a/src/compiler/scala/tools/nsc/CompilationUnits.scala b/src/compiler/scala/tools/nsc/CompilationUnits.scala index 15d365ab8c..b52e6fdf57 100644 --- a/src/compiler/scala/tools/nsc/CompilationUnits.scala +++ b/src/compiler/scala/tools/nsc/CompilationUnits.scala @@ -34,6 +34,20 @@ trait CompilationUnits { self: Global => /** the content of the compilation unit in tree form */ var body: Tree = EmptyTree + /** The position of the first xml literal encountered while parsing this compilation unit. + * NoPosition if there were none. Write-once. + */ + private[this] var _firstXmlPos: Position = NoPosition + + /** Record that we encountered XML. Should only be called once. */ + protected[nsc] def encounteredXml(pos: Position) = _firstXmlPos = pos + + /** Does this unit contain XML? */ + def hasXml = _firstXmlPos ne NoPosition + + /** Position of first XML literal in this unit. */ + def firstXmlPos = _firstXmlPos + def exists = source != NoSourceFile && source != null /** Note: depends now contains toplevel classes. diff --git a/src/compiler/scala/tools/nsc/ast/TreeGen.scala b/src/compiler/scala/tools/nsc/ast/TreeGen.scala index 692afbac66..c28a6ba337 100644 --- a/src/compiler/scala/tools/nsc/ast/TreeGen.scala +++ b/src/compiler/scala/tools/nsc/ast/TreeGen.scala @@ -19,11 +19,20 @@ abstract class TreeGen extends scala.reflect.internal.TreeGen with TreeDSL { import global._ import definitions._ - /** Builds a fully attributed wildcard import node. + /** Builds a fully attributed, synthetic wildcard import node. */ - def mkWildcardImport(pkg: Symbol): Import = { - assert(pkg ne null, this) - val qual = gen.mkAttributedStableRef(pkg) + def mkWildcardImport(pkg: Symbol): Import = + mkImportFromSelector(pkg, ImportSelector.wildList) + + /** Builds a fully attributed, synthetic import node. + * import `qualSym`.{`name` => `toName`} + */ + def mkImport(qualSym: Symbol, name: Name, toName: Name): Import = + mkImportFromSelector(qualSym, ImportSelector(name, 0, toName, 0) :: Nil) + + private def mkImportFromSelector(qualSym: Symbol, selector: List[ImportSelector]): Import = { + assert(qualSym ne null, this) + val qual = gen.mkAttributedStableRef(qualSym) val importSym = ( NoSymbol newImport NoPosition @@ -31,7 +40,7 @@ abstract class TreeGen extends scala.reflect.internal.TreeGen with TreeDSL { setInfo analyzer.ImportType(qual) ) val importTree = ( - Import(qual, ImportSelector.wildList) + Import(qual, selector) setSymbol importSym setType NoType ) diff --git a/src/compiler/scala/tools/nsc/ast/parser/Parsers.scala b/src/compiler/scala/tools/nsc/ast/parser/Parsers.scala index fc532f5d44..ef5872986c 100644 --- a/src/compiler/scala/tools/nsc/ast/parser/Parsers.scala +++ b/src/compiler/scala/tools/nsc/ast/parser/Parsers.scala @@ -166,13 +166,20 @@ self => def syntaxError(offset: Int, msg: String): Unit = throw new MalformedInput(offset, msg) def incompleteInputError(msg: String): Unit = throw new MalformedInput(source.content.length - 1, msg) - /** the markup parser */ - lazy val xmlp = new MarkupParser(this, preserveWS = true) - object symbXMLBuilder extends SymbolicXMLBuilder(this, preserveWS = true) { // DEBUG choices val global: self.global.type = self.global } + /** the markup parser + * The first time this lazy val is accessed, we assume we were trying to parse an xml literal. + * The current position is recorded for later error reporting if it turns out + * that we don't have the xml library on the compilation classpath. + */ + private[this] lazy val xmlp = { + currentUnit.encounteredXml(o2p(in.offset)) + new MarkupParser(this, preserveWS = true) + } + def xmlLiteral() : Tree = xmlp.xLiteral def xmlLiteralPattern() : Tree = xmlp.xLiteralPattern } diff --git a/src/compiler/scala/tools/nsc/typechecker/Contexts.scala b/src/compiler/scala/tools/nsc/typechecker/Contexts.scala index 61967d4cee..1f4ff7cc2d 100644 --- a/src/compiler/scala/tools/nsc/typechecker/Contexts.scala +++ b/src/compiler/scala/tools/nsc/typechecker/Contexts.scala @@ -16,7 +16,7 @@ import scala.reflect.internal.util.shortClassOfInstance */ trait Contexts { self: Analyzer => import global._ - import definitions.{ JavaLangPackage, ScalaPackage, PredefModule } + import definitions.{ JavaLangPackage, ScalaPackage, PredefModule, ScalaXmlTopScope, ScalaXmlPackage } import ContextMode._ object NoContext @@ -93,9 +93,31 @@ trait Contexts { self: Analyzer => else RootImports.completeList } + def rootContext(unit: CompilationUnit, tree: Tree = EmptyTree, erasedTypes: Boolean = false): Context = { val rootImportsContext = (startContext /: rootImports(unit))((c, sym) => c.make(gen.mkWildcardImport(sym))) - val c = rootImportsContext.make(tree, unit = unit) + + // there must be a scala.xml package when xml literals were parsed in this unit + if (unit.hasXml && ScalaXmlPackage == NoSymbol) + unit.error(unit.firstXmlPos, "XML literals may only be used if the package scala.xml is present in the compilation classpath.") + + // TODO: remove the def below and drop `|| predefDefinesDollarScope` in the condition for `contextWithXML` + // as soon as 2.11.0-M4 is released and used as STARR (and $scope is no longer defined in Predef) + // Until then, to allow compiling quick with pre-2.11.0-M4 STARR, + // which relied on Predef defining `val $scope`, we've left it in place. + // Since the new scheme also imports $scope (as an alias for scala.xml.TopScope), + // we must check whether it is still there and not import the alias to avoid ambiguity. + // (All of this is only necessary to compile the full quick stage with STARR. + // if using locker, Predef.$scope is no longer needed.) + def predefDefinesDollarScope = definitions.getMemberIfDefined(PredefModule, nme.dollarScope) != NoSymbol + + // hack for the old xml library (detected by looking for scala.xml.TopScope, which needs to be in scope as $scope) + // import scala.xml.{TopScope => $scope} + val contextWithXML = + if (!unit.hasXml || ScalaXmlTopScope == NoSymbol || predefDefinesDollarScope) rootImportsContext + else rootImportsContext.make(gen.mkImport(ScalaXmlPackage, nme.TopScope, nme.dollarScope)) + + val c = contextWithXML.make(tree, unit = unit) if (erasedTypes) c.setThrowErrors() else c.setReportErrors() c(EnrichmentEnabled | ImplicitsEnabled) = !erasedTypes c diff --git a/src/library/scala/Predef.scala b/src/library/scala/Predef.scala index 5ba38600b6..a188602543 100644 --- a/src/library/scala/Predef.scala +++ b/src/library/scala/Predef.scala @@ -134,7 +134,13 @@ object Predef extends LowPriorityImplicits with DeprecatedPredef { @inline def implicitly[T](implicit e: T) = e // for summoning implicit values from the nether world -- TODO: when dependent method types are on by default, give this result type `e.type`, so that inliner has better chance of knowing which method to inline in calls like `implicitly[MatchingStrategy[Option]].zero` @inline def locally[T](x: T): T = x // to communicate intent and avoid unmoored statements - // Apparently needed for the xml library + // TODO: remove `val $scope = ...` as soon as 2.11.0-M4 is released and used as STARR + // As it has a '$' in its name, we don't have to deprecate first. + // The compiler now aliases `scala.xml.TopScope` to `$scope` (unless Predef.$scope is still there). + // This definition left in place for older compilers and to compile quick with pre-2.11.0-M4 STARR. + // In principle we don't need it to compile library/reflect/compiler (there's no xml left there), + // so a new locker can be built without this definition, and locker can build quick + // (partest, scaladoc still require xml). val $scope = scala.xml.TopScope // errors and asserts ------------------------------------------------- diff --git a/src/reflect/scala/reflect/internal/Definitions.scala b/src/reflect/scala/reflect/internal/Definitions.scala index 4f2b7e2642..3470b05495 100644 --- a/src/reflect/scala/reflect/internal/Definitions.scala +++ b/src/reflect/scala/reflect/internal/Definitions.scala @@ -16,7 +16,7 @@ import scala.reflect.api.{Universe => ApiUniverse} trait Definitions extends api.StandardDefinitions { self: SymbolTable => - import rootMirror.{getModule, getPackage, getClassByName, getRequiredClass, getRequiredModule, getClassIfDefined, getModuleIfDefined, getPackageObject, getPackageObjectIfDefined, requiredClass, requiredModule} + import rootMirror.{getModule, getPackage, getClassByName, getRequiredClass, getRequiredModule, getClassIfDefined, getModuleIfDefined, getPackageObject, getPackageIfDefined, getPackageObjectIfDefined, requiredClass, requiredModule} object definitions extends DefinitionsClass @@ -471,6 +471,10 @@ trait Definitions extends api.StandardDefinitions { def methodCache_find = getMemberMethod(MethodCacheClass, nme.find_) def methodCache_add = getMemberMethod(MethodCacheClass, nme.add_) + // XML + lazy val ScalaXmlTopScope = getModuleIfDefined("scala.xml.TopScope") + lazy val ScalaXmlPackage = getPackageIfDefined("scala.xml") + // scala.reflect lazy val ReflectPackage = requiredModule[scala.reflect.`package`.type] lazy val ReflectApiPackage = getPackageObjectIfDefined("scala.reflect.api") // defined in scala-reflect.jar, so we need to be careful diff --git a/src/reflect/scala/reflect/internal/Mirrors.scala b/src/reflect/scala/reflect/internal/Mirrors.scala index bf38c3bf1e..6ed9de8e20 100644 --- a/src/reflect/scala/reflect/internal/Mirrors.scala +++ b/src/reflect/scala/reflect/internal/Mirrors.scala @@ -176,6 +176,9 @@ trait Mirrors extends api.Mirrors { def getPackage(fullname: TermName): ModuleSymbol = ensurePackageSymbol(fullname.toString, getModuleOrClass(fullname), allowModules = true) + def getPackageIfDefined(fullname: TermName): Symbol = + wrapMissing(getPackage(fullname)) + @deprecated("Use getPackage", "2.11.0") def getRequiredPackage(fullname: String): ModuleSymbol = getPackage(newTermNameCached(fullname)) diff --git a/src/reflect/scala/reflect/internal/StdNames.scala b/src/reflect/scala/reflect/internal/StdNames.scala index 81fffc833c..30aaaa1f3a 100644 --- a/src/reflect/scala/reflect/internal/StdNames.scala +++ b/src/reflect/scala/reflect/internal/StdNames.scala @@ -599,6 +599,7 @@ trait StdNames { val currentMirror: NameType = "currentMirror" val delayedInit: NameType = "delayedInit" val delayedInitArg: NameType = "delayedInit$body" + val dollarScope: NameType = "$scope" val drop: NameType = "drop" val elem: NameType = "elem" val emptyValDef: NameType = "emptyValDef" @@ -684,6 +685,7 @@ trait StdNames { val thisPrefix : NameType = "thisPrefix" val toArray: NameType = "toArray" val toObjectArray : NameType = "toObjectArray" + val TopScope: NameType = "TopScope" val toString_ : NameType = "toString" val toTypeConstructor: NameType = "toTypeConstructor" val tpe : NameType = "tpe" diff --git a/src/reflect/scala/reflect/internal/Trees.scala b/src/reflect/scala/reflect/internal/Trees.scala index 8781423a6d..ceb3b383d7 100644 --- a/src/reflect/scala/reflect/internal/Trees.scala +++ b/src/reflect/scala/reflect/internal/Trees.scala @@ -327,7 +327,7 @@ trait Trees extends api.Trees { self: SymbolTable => case class ImportSelector(name: Name, namePos: Int, rename: Name, renamePos: Int) extends ImportSelectorApi object ImportSelector extends ImportSelectorExtractor { val wild = ImportSelector(nme.WILDCARD, -1, null, -1) - val wildList = List(wild) + val wildList = List(wild) // OPT This list is shared for performance. } case class Import(expr: Tree, selectors: List[ImportSelector]) -- cgit v1.2.3