From 86ddfebfbd0ea938d01442b3fc7d65c5d5324be9 Mon Sep 17 00:00:00 2001 From: Martin Odersky Date: Mon, 17 Mar 2008 18:56:09 +0000 Subject: added CharSequence abstraction changed Regex class added Regex parsers changed thrown Errors in library to RuntimeExceptions added lazy_::, lazy_::: constructors and extractors for streams changed maximally supported function type arity to 22 --- .../scala/tools/nsc/symtab/Definitions.scala | 2 +- .../scala/tools/nsc/typechecker/DeVirtualize.txt | 56 ++++ src/library/scala/CharSequence.scala | 155 +++++++++++ src/library/scala/Enumeration.scala | 2 +- src/library/scala/LazyCharSequence.scala | 265 ++++++++++++++++++ src/library/scala/Predef.scala | 3 +- src/library/scala/Stream.scala | 13 +- src/library/scala/collection/jcl/Map.scala | 2 +- .../scala/collection/jcl/MutableIterable.scala | 2 +- src/library/scala/runtime/RichString.scala | 7 +- src/library/scala/runtime/StreamCons.scala | 17 ++ src/library/scala/util/matching/Regex.scala | 304 +++++++++++++-------- .../util/parsing/combinator/JavaTokenParsers.scala | 24 ++ .../util/parsing/combinator/RegexParsers.scala | 51 ++-- .../util/parsing/combinator/lexical/Scanners.scala | 4 +- .../parsing/combinatorold/lexical/Scanners.scala | 4 +- .../util/parsing/input/CharArrayPosition.scala | 4 +- .../scala/util/parsing/input/CharArrayReader.scala | 6 +- .../util/parsing/input/CharArraySequence.scala | 37 --- .../util/parsing/input/CharSequenceReader.scala | 28 +- .../scala/util/parsing/input/NoPosition.scala | 2 +- .../scala/util/parsing/input/OffsetPosition.scala | 4 +- .../scala/util/parsing/input/Position.scala | 4 +- src/library/scala/util/parsing/input/Reader.scala | 17 +- .../scala/util/parsing/input/StreamReader.scala | 49 ++-- 25 files changed, 825 insertions(+), 237 deletions(-) create mode 100755 src/compiler/scala/tools/nsc/typechecker/DeVirtualize.txt create mode 100755 src/library/scala/CharSequence.scala create mode 100644 src/library/scala/LazyCharSequence.scala create mode 100755 src/library/scala/runtime/StreamCons.scala create mode 100644 src/library/scala/util/parsing/combinator/JavaTokenParsers.scala delete mode 100755 src/library/scala/util/parsing/input/CharArraySequence.scala (limited to 'src') diff --git a/src/compiler/scala/tools/nsc/symtab/Definitions.scala b/src/compiler/scala/tools/nsc/symtab/Definitions.scala index e76be5a09d..aa5131a445 100644 --- a/src/compiler/scala/tools/nsc/symtab/Definitions.scala +++ b/src/compiler/scala/tools/nsc/symtab/Definitions.scala @@ -229,7 +229,7 @@ trait Definitions { }).normalize /* */ - val MaxFunctionArity = 9 + val MaxFunctionArity = 22 val FunctionClass: Array[Symbol] = new Array(MaxFunctionArity + 1) def functionApply(n: Int) = getMember(FunctionClass(n), nme.apply) def functionType(formals: List[Type], restpe: Type) = diff --git a/src/compiler/scala/tools/nsc/typechecker/DeVirtualize.txt b/src/compiler/scala/tools/nsc/typechecker/DeVirtualize.txt new file mode 100755 index 0000000000..4ca1317d5e --- /dev/null +++ b/src/compiler/scala/tools/nsc/typechecker/DeVirtualize.txt @@ -0,0 +1,56 @@ + /* + class A { + class C[X, Y](x: X) <: { var y = x ; def f(z: Y): X } + class D[Y](z) extends C[Int, Y](f(z)) { override def f(z:Int) = 3 } + } + class B extends A { + class C[X, Y](x: X) <: { def g = 2 } + } + */ + + class A { + type C[X, Y] <: CT[X, Y] + + trait CT { self: C => protected[this] val x: Int; val y = x; def f(z:Int) = z + 1 } + + type D <: C with DT + + trait DT extends { self: D => def f(z:Int) = z + 2 } + + trait preDT extends { self: D => val z: Int; val x = f(z) } + + def newC(x: Int): C + def newD(x: Int): D + + //type C = CT + //type D = C with DT + + class CC(_x:Int) extends { val x = _x } with CT + + def newC(x:Int): C = new CC(x).asInstanceOf[C] + + class DC(_z:Int) extends { val z = _z } with preDT with CT with DT { + override def f(z:Int) = super.f(z) + } + + def newD(z:Int):D = new DC(z).asInstanceOf[D] + } + + class B extends A { + type C <: CT with CT2 + + trait CT2 { self : C => def g = 2 } + + //type C = CT with CT2 + //type D = C with DT + + class CC2(_x:Int) extends { val x = _x } with CT with CT2 + + def newC(x:Int): C = new CC2(x).asInstanceOf[C] + + class DC2(_z:Int) extends { val z = _z } with preDT with CT with CT2 + with DT { override def f(z:Int) = super.f(z) } + + def newD(z:Int): D = new DC2(z).asInstanceOf[D] + } + diff --git a/src/library/scala/CharSequence.scala b/src/library/scala/CharSequence.scala new file mode 100755 index 0000000000..da776256a6 --- /dev/null +++ b/src/library/scala/CharSequence.scala @@ -0,0 +1,155 @@ +/* __ *\ +** ________ ___ / / ___ Scala API ** +** / __/ __// _ | / / / _ | (c) 2006-2007, LAMP/EPFL ** +** __\ \/ /__/ __ |/ /__/ __ | http://scala-lang.org/ ** +** /____/\___/_/ |_/____/_/ | | ** +** |/ ** +\* */ + +// $Id: + + +package scala + +import java.io._ +import collection._ +import util.matching.Regex + +/** A Scala abstraction of character sequences, similar to, but richer than, + * java.lang.CharSequence. Sequences can be fixed or lazy. A lazy + * character sequence may be constructed only while it is read. + * + * @author Martin Odersky + */ +trait CharSequence extends java.lang.CharSequence with Seq[Char] { + + /** The length of the character sequence + * Note: if char sequence is lazy, calling this method + * will force sequence to be read until the end. + */ + def length: Int + + /** The character at position `index'. + */ + def charAt(index: Int): Char + + /** Is character sequence defined at `index'? + * Unlike `length' this operation does not force reading + * a lazy sequence to the end. + * (the implementation of that method is inherited from Seq). + */ + def isDefinedAt(index: Int): Boolean + + /** Optionally the character at position `index'. None is not in range. + */ + def get(index: Int): Option[Char] = + if (isDefinedAt(index)) Some(charAt(index)) + else None + + /** The character at position `index'. (same as `charAt') + */ + def apply(index: Int): Char = charAt(index) + + /** the subsequence from index `start' up to and excluding + * the minimum of index `end' and the length of current sequence. + */ + def subSequence(start: Int, end: Int): CharSequence + + /** The subsequence from index `start' until the end of the current sequence + * If sequence is lazy, this operation does not force reading to the end. + */ + def subSequence(start: Int): CharSequence = + subSequence(start, length) + + /** Convert sequence to string */ + def toString: String + + /** the elements of this sequence */ + def elements: Iterator[Char] = new Iterator[Char] { + private var index = 0 + def hasNext: Boolean = isDefinedAt(index) + def next: Char = { + val ch = charAt(index) + index += 1 + ch + } + } + + /** Return all matches of given regexp in this character sequence as an iterator + */ + def findAll(regex: Regex): Regex.MatchIterator = regex findAllIn this + + /** Return first match of given regexp in this character sequence as an optional value + */ + def findFirst(regex: Regex): Option[String] = regex findFirstIn this + + /** Return first match of given regexp in this character sequence as an optional value + */ + def findFirstMatch(regex: Regex): Option[Regex.Match] = regex findFirstMatchIn this + + /** Return optionally string matching given regexp at the beginning of this + * character sequence, or None if regexp matches no prefix + * of the character sequence. + */ + def findPrefix(regex: Regex): Option[String] = regex findPrefixOf this + + /** Return optionally match for given regexp at the beginning of this + * character sequence, or None if regexp matches no prefix + * of the character sequence. + */ + def findPrefixMatch(regex: Regex): Option[Regex.Match] = regex findPrefixMatchOf this + + /** Replaces all matches of given regexp by a string. + * + * @param regex The regex to match with + * @param replacement The string that will replace each match + * @return The resulting string + */ + def replaceAll(regex: Regex, replacement: String): String = regex replaceAllIn (this, replacement) + + /** Replaces the first match of given regexp by a string. + * + * @param target The string to match + * @param replacement The string that will replace the match + * @return The resulting string + */ + def replaceFirst(regex: Regex, replacement: String): String = regex replaceFirstIn (this, replacement) +} + +/** The CharSequence object defines variance implementations of character sequences + */ +object CharSequence { + + def fromJava(source: java.lang.CharSequence): CharSequence = + new JavaWrapper(source) + + def fromArray(source: Array[Char]): CharSequence = + new AsArray(source, 0, source.length) + def fromArray(source: Array[Char], start: Int): CharSequence = + new AsArray(source, start, source.length) + def fromArray(source: Array[Char], start: Int, end: Int): CharSequence = + new AsArray(source, start, end) + + private class JavaWrapper(source: java.lang.CharSequence) extends CharSequence { + def length: Int = source.length + def charAt(index: Int): Char = source.charAt(index) + def subSequence(start: Int, end: Int): CharSequence = new JavaWrapper(source.subSequence(start, end)) + override def toString: String = source.toString + } + + private class AsArray(source: scala.Array[Char], start: Int, end: Int) extends CharSequence { + if (start < 0) throw new IndexOutOfBoundsException + if (end > source.length) throw new IndexOutOfBoundsException + + def charAt(index: Int) = + if (start + index < end) source(index + start) + else throw new IndexOutOfBoundsException + + def length: Int = if (end < start) 0 else end - start + + def subSequence(_start: Int, _end: Int) = + new AsArray(source, start + _start, start + _end) + + override def toString = new String(source, start, end - start) + } +} diff --git a/src/library/scala/Enumeration.scala b/src/library/scala/Enumeration.scala index 9c91996d23..93ed320f64 100644 --- a/src/library/scala/Enumeration.scala +++ b/src/library/scala/Enumeration.scala @@ -310,7 +310,7 @@ abstract class Enumeration(initial: Int, names: String*) { } } def contains(value : Value) = (underlying & value.mask32) != 0 - def |( set : Set32) = new Set32(underlying | set.underlying) + def |(set : Set32) = new Set32(underlying | set.underlying) def |(value : Value) = new Set32(underlying | value.mask32) def &~(value : Value) = new Set32(underlying & (~value.mask32)) def &(set : Set32) = new Set32(underlying & set.underlying) diff --git a/src/library/scala/LazyCharSequence.scala b/src/library/scala/LazyCharSequence.scala new file mode 100644 index 0000000000..6f773b817c --- /dev/null +++ b/src/library/scala/LazyCharSequence.scala @@ -0,0 +1,265 @@ +/* __ *\ +** ________ ___ / / ___ Scala API ** +** / __/ __// _ | / / / _ | (c) 2006-2007, LAMP/EPFL ** +** __\ \/ /__/ __ |/ /__/ __ | http://scala-lang.org/ ** +** /____/\___/_/ |_/____/_/ | | ** +** |/ ** +\* */ + +// $Id: + + +package scala + +import java.io._ +import util.matching.Regex + + +/** The CharSequence object defines variance implementations of character sequences + */ +object LazyCharSequence { + final val UndeterminedEnd = Math.MAX_INT + + /** Constructs a character sequence from a character iterator */ + def fromChars(source: Iterator[Char]): CharSequence = + new LazyCharSequence ((chars: Array[Char], start: Int, len: Int) => { + var i = 0 + while (i < len && source.hasNext) { + chars(start + i) = source.next + i += 1 + } + if (i == 0) -1 else i + }) + + /** Constructs a character sequence from a character iterable */ + def fromChars(source: Iterable[Char]): CharSequence = + fromChars(source.elements) + + /** Constructs a character sequence from a string iterator */ + def fromStrings(source: Iterator[String]): CharSequence = { + var current: String = "" + def more(chars: Array[Char], start: Int, len: Int): Int = + if (current.length != 0) { + val nchars = current.length min len + current.getChars(0, nchars, chars, start) + current = current.substring(nchars) + if (nchars == len) nchars + else (more(chars, start + nchars, len - nchars) max 0) + nchars + } else if (source.hasNext) { + current = source.next + more(chars, start, len) + } else -1 + new LazyCharSequence(more(_: Array[Char], _: Int, _: Int)) + } + + /** Constructs a character sequence from a string iterable */ + def fromStrings(source: Iterable[String]): CharSequence = + fromStrings(source.elements) + + /** Constructs a character sequence from a line iterator + * Lines do not contain trailing `\n' characters; The method inserts + * a line separator `\n' between any two lines in the sequence. + */ + def fromLines(source: Iterator[String]): CharSequence = { + var isFirst = true + fromStrings(source map { line => + if (isFirst) line + else { + isFirst = false + "\n"+line + } + }) + } + + /** Constructs a character sequence from a line iterable + * Lines do not contain trailing `\n' characters; The method inserts + * a line separator `\n' between any two lines in the sequence. + */ + def fromLines(source: Iterable[String]): CharSequence = + fromLines(source.elements) + + /** Constructs a character sequence from an input reader + */ + def fromReader(source: Reader): CharSequence = + new LazyCharSequence(source) + + /** Constructs a character sequence from an input file + */ + def fromFile(source: File) = + new LazyCharSequence(source) + + /** Constructs a character sequence from a file with given name + */ + def fromFile(source: String) = + new LazyCharSequence(source) + + /** Constructs a character sequence from a scala.io.Source value + */ + def fromSource(source: io.Source) = + fromLines(source.getLines) +} + + +import LazyCharSequence._ + +/** An implementation of lazily computed character sequences + * + * @author Martin Odersky + */ +class LazyCharSequence protected (more: (Array[Char], Int, Int) => Int, + first: Page, start: Int, end: Int) extends CharSequence { + + /** Constructs a character sequence from a method that produces more characters when asked. + * The producer method is analogous to the read method in java.io.Reader. + * It takes three parameters: an array of characters, a start index, and an end index. + * It should try to fill the array between start and end indices (not including end index). + * It returns the number of characters produced, or -1 if end of logical input stream was reached + * before any character was read. + */ + def this(more: (Array[Char], Int, Int) => Int) = this(more, new Page(0), 0, UndeterminedEnd) + + /** Constructs a character sequence from an input reader + */ + def this(source: Reader) = + this(source.read(_: Array[Char], _: Int, _: Int)) + + /** Constructs a character sequence from an input file + */ + def this(source: File) = + this(new FileReader(source)) + + /** Constructs a character sequence from a file with given name + */ + def this(source: String) = + this(new File(source)) + + private var current: Page = first + + private def latest = first.latest + + private def addMore() = latest.addMore(more) + + private def page(absindex: Int) = { + if (absindex < current.start) + current = first + while (absindex >= current.end && current.next != null) + current = current.next + while (absindex >= current.end && !current.isLast) { + current = addMore() + } + current + } + + /** The length of the character sequence + * Note: calling this method will force sequence to be read until the end. + */ + def length: Int = { + while (!latest.isLast) addMore() + (latest.end min end) - start + } + + /** The character at position `index'. + */ + def charAt(index: Int) = + if (isDefinedAt(index)) page(index + start)(index + start) + else throw new IndexOutOfBoundsException(index.toString) + + /** Is character sequence defined at `index'? + * Unlike `length' this operation does not force reading + * a lazy sequence to the end. + */ + override def isDefinedAt(index: Int) = + index >= 0 && index < end - start && { + val p = page(index + start); index + start < p.end + } + + /** Optionally the character at position `index'. None is not in range. + */ + override def get(index: Int) = + if (isDefinedAt(index)) Some(page(index + start)(index + start)) + else None + + /** the subsequence from index `start' up to and excluding + * the minimum of index `end' and the length of current sequence. + */ + def subSequence(_start: Int, _end: Int) = { + page(start) + val s = start + _start + val e = if (_end == UndeterminedEnd) _end else start + _end + var f = first + while (f.end <= s && !f.isLast) f = f.next + new LazyCharSequence(more, f, s, e) + } + + /** The subsequence from index `start' until the end of the current sequence + * This operation does not force reading to the end. + */ + override def subSequence(start: Int): CharSequence = + subSequence(start, UndeterminedEnd) + + /** Convert sequence to string */ + override def toString = { + val buf = new StringBuilder + for (ch <- elements) buf append ch + buf.toString + } +} + + +/** Page containing up to PageSize characters of the input sequence. + */ +private class Page(val num: Int) { + + private final val PageSize = 4096 + + /** The next page in the sequence */ + var next : Page = null + + /** A later page in the sequence, serves a cachae for pointing to last page */ + var later : Page = this + + /** The number of characters read into this page */ + var filled: Int = 0 + + /** Is this page the permamnently last one in the sequence? Only true once `more' + * method has returned -1 to signal end of input. */ + var isLast: Boolean = false + + /** The character array */ + final val chars = new Array[Char](PageSize) + + /** The index of the first character in this page relative to the whole sequence */ + final def start = num * PageSize + + /** The index of the character following the last charcater in this page relative + * to the whole sequence */ + final def end = start + filled + + /** The currently last page in the sequence; might change as more charcaters are appended */ + final def latest: Page = { + if (later.next != null) later = later.latest + later + } + + /** The character at given sequence index. + * That index is relative to the whole sequence, not the page. */ + def apply(index: Int) = { + if (index < start || index - start >= filled) throw new IndexOutOfBoundsException(index.toString) + chars(index - start) + } + + /** produces more characters by calling `more' and appends them on the current page, + * or fills a subsequent page if current page is full + * pre: if current page is full, it is the last one in the sequence. + */ + final def addMore(more: (Array[Char], Int, Int) => Int): Page = + if (filled == PageSize) { + next = new Page(num + 1) + next.addMore(more) + } else { + val count = more(chars, filled, PageSize - filled) + if (count < 0) isLast = true + else filled += count + this + } +} diff --git a/src/library/scala/Predef.scala b/src/library/scala/Predef.scala index 94f69f11d8..5525608cf5 100644 --- a/src/library/scala/Predef.scala +++ b/src/library/scala/Predef.scala @@ -73,7 +73,7 @@ object Predef { // errors and asserts ------------------------------------------------- - def error(message: String): Nothing = throw new Error(message) + def error(message: String): Nothing = throw new RuntimeException(message) def exit: Nothing = exit(0) @@ -324,6 +324,7 @@ object Predef { implicit def forceArrayProjection[A](x: Array.Projection[A]): Array[A] = x.force /** any random access character seq (including rich string can be converted into a string */ implicit def forceRandomAccessCharSeq(x: runtime.RichString): String = x.mkString + implicit def lazyStreamToConsable[A](xs: => Stream[A]) = new runtime.StreamCons(xs) def currentThread = java.lang.Thread.currentThread() diff --git a/src/library/scala/Stream.scala b/src/library/scala/Stream.scala index 957e14e361..1f9b233e4a 100644 --- a/src/library/scala/Stream.scala +++ b/src/library/scala/Stream.scala @@ -22,8 +22,18 @@ import Predef._ */ object Stream { + def apply[A](xs: A*) = (xs :\ (empty: Stream[A]))(cons(_, _)) + + def unapplySeq[A](xs: Stream[A]): Option[Seq[A]] = Some(xs) + + object lazy_:: { + def unapply[A](xs: Stream[A]): Option[(A, Stream[A])] = + if (xs.isEmpty) None + else Some(xs.head, xs.tail) + } + /** a stream with a definite size */ - trait Definite[+A] extends Stream[A] with Function0[Stream[A]] { + trait Definite[+A] extends Stream[A] with Function0[Stream[A]] { override def hasDefiniteSize = true override def apply = this override def toString = super[Stream].toString @@ -38,6 +48,7 @@ object Stream { } object cons { + /** A stream consisting of a given first element and remaining elements * @param hd The first element of the result stream * @param tl The remaining elements of the result stream diff --git a/src/library/scala/collection/jcl/Map.scala b/src/library/scala/collection/jcl/Map.scala index 2feed2ca82..8e921dcfc2 100644 --- a/src/library/scala/collection/jcl/Map.scala +++ b/src/library/scala/collection/jcl/Map.scala @@ -94,7 +94,7 @@ trait Map[K,E] extends MutableIterable[Tuple2[K,E]] with scala.collection.mutabl new MutableIterator[(K,E)] { def next = i.next def hasNext = i.hasNext - def remove : Unit = throw new Error + def remove : Unit = throw new NoSuchMethodException } } override def removeKey(key : K) = { diff --git a/src/library/scala/collection/jcl/MutableIterable.scala b/src/library/scala/collection/jcl/MutableIterable.scala index 0fed9954e5..3988ed8d93 100644 --- a/src/library/scala/collection/jcl/MutableIterable.scala +++ b/src/library/scala/collection/jcl/MutableIterable.scala @@ -91,7 +91,7 @@ trait MutableIterable[A] extends scala.Collection[A] { new MutableIterator[A] { def next = i.next def hasNext = i.hasNext - def remove : Unit = throw new Error + def remove : Unit = throw new NoSuchMethodException } } def size = size0; diff --git a/src/library/scala/runtime/RichString.scala b/src/library/scala/runtime/RichString.scala index bf14a652a5..9c7facd1a7 100644 --- a/src/library/scala/runtime/RichString.scala +++ b/src/library/scala/runtime/RichString.scala @@ -14,7 +14,7 @@ package scala.runtime import Predef._ import scala.util.matching.Regex -final class RichString(val self: String) extends Proxy with RandomAccessSeq[Char] with Ordered[String] { +final class RichString(val self: String) extends Proxy with CharSequence with RandomAccessSeq[Char] with Ordered[String] { import RichString._ override def apply(n: Int) = self charAt n override def length = self.length @@ -74,6 +74,11 @@ final class RichString(val self: String) extends Proxy with RandomAccessSeq[Char new RichString(buf.toString) } + def charAt(index: Int) = self.charAt(index) + + def subSequence(start: Int, end: Int): CharSequence = + new RichString(self.substring(start, end)) + /** return n times the current string */ def * (n: Int): String = { diff --git a/src/library/scala/runtime/StreamCons.scala b/src/library/scala/runtime/StreamCons.scala new file mode 100755 index 0000000000..2bcb0bf45b --- /dev/null +++ b/src/library/scala/runtime/StreamCons.scala @@ -0,0 +1,17 @@ +/* __ *\ +** ________ ___ / / ___ Scala API ** +** / __/ __// _ | / / / _ | (c) 2002-2008, LAMP/EPFL ** +** __\ \/ /__/ __ |/ /__/ __ | http://scala-lang.org/ ** +** /____/\___/_/ |_/____/_/ | | ** +** |/ ** +\* */ + +// $Id: RichString.scala 14350 2008-03-10 19:34:38Z odersky $ + + +package scala.runtime + +final class StreamCons[T](xs: => Stream[T]) { + def lazy_:: (x: T): Stream[T] = Stream.cons(x, xs) + def lazy_::: (ys: Stream[T]): Stream[T] = ys append xs +} diff --git a/src/library/scala/util/matching/Regex.scala b/src/library/scala/util/matching/Regex.scala index ae9fe6361b..947175f945 100644 --- a/src/library/scala/util/matching/Regex.scala +++ b/src/library/scala/util/matching/Regex.scala @@ -18,109 +18,72 @@ import java.util.regex.{Pattern, Matcher} * * @author Thibaud Hottelier * @author Philipp Haller + * @author Martin Odersky * @version 1.1, 29/01/2008 * * @param regex A string representing a regular expression * @param groupNames A mapping from names to indices in capture groups */ -class Regex(val regex: String, private var groupNames: Map[String, Int]) { - private def buildMap(res: Map[String, Int], groupNames: Seq[String], i: Int): Map[String, Int] = - if (i > groupNames.size) - res - else { - val p = (groupNames(i - 1), i) - buildMap(res + p, groupNames, i + 1) - } +class Regex(regex: String, groupNames: String*) { - /** Create a Regex from a string. - * - * @param s The regular expression - * @return A new Regex instance - */ - def this(s: String) = this(s, null: Map[String, Int]) + import Regex._ - /** Create a Regex from a string and - * a sequence of group names. - * - * @param s The regular expression - * @param groups The list of group names in the same order - * as the capture groups in the regular expression - * @return A new Regex instance - */ - def this(s: String, groups: String*) = { - this(s) - groupNames = buildMap(Map[String, Int](), groups, 1) - } - - /* Stores the compiled pattern at instantiation time */ + /** The compiled pattern */ val pattern = Pattern.compile(regex) - /* Builds a MatchData[String] from a Matcher */ - private def buildSeq(l: List[String], m: Matcher, i: Int): MatchData[String] = - if (i == -1) - new MatchData(l, groupNames) - else - buildSeq(m.group(i) :: l, m, i - 1) - - /* Builds a List[MatchData[String]] from a Matcher */ - private def doMatchAll(res: List[MatchData[String]], m: Matcher): List[MatchData[String]] = - if (m.find()) - doMatchAll(res ::: List(buildSeq(Nil, m, m.groupCount())), m) - else - res - - /* Builds a List[String] from a Matcher */ - private def unapplySeq0(target: String): Option[List[String]] = { - val m = pattern.matcher(target) - if (m.matches()) - Some(buildSeq(Nil, m, m.groupCount()).getGroups.tail) - else - None - } - /** Tries to match target (whole match) and returns * the matches. * * @param target The string to match * @return The matches */ - def unapplySeq(target: Any): Option[List[String]] = - if (target.isInstanceOf[String]) - unapplySeq0(target.asInstanceOf[String]) - else if (target.isInstanceOf[MatchData[_]]) - unapplySeq0(target.asInstanceOf[MatchData[String]]()) - else + def unapplySeq(target: Any): Option[List[String]] = target match { + case s: java.lang.CharSequence => + val m = pattern.matcher(s) + if (m.matches) Some((1 to m.groupCount).toList map m.group) + else None + case Match(s) => + unapplySeq(s) + case _ => None + } - /** Creates a MatchData[String] from a string. - * This is used in for-comprehensions to iterate over matches. - * - * @param s The string to match - * @return The MatchData[String] instance + /** Return all matches of this regexp in given character sequence as an iterator */ - def ~~(s: String) = matchAll(s) + def findAllIn(source: CharSequence) = new Regex.MatchIterator(source, this, groupNames) - /** Returns all matches of target string. - * - * @param target The string to match - * @return All matches in a list of MatchData[String] + /** Return optionally first matching string of this regexp in given character sequence, + * None if it does not exist. */ - def matchAll(target: String): List[MatchData[String]] = { - val m = pattern.matcher(target) - doMatchAll(Nil, m) + def findFirstIn(source: CharSequence): Option[String] = { + val m = pattern.matcher(source) + if (m.find) Some(m.group) else None } - /** Returns the first match of target string. - * - * @param target The string to match - * @return The first match as MatchData[String] + /** Return optionally first match of this regexp in given character sequence, + * None if it does not exist. */ - def matchFirst(target: String): Option[MatchData[String]] = { - val m = pattern.matcher(target) - if (!m.find()) - None - else - Some(buildSeq(Nil, m, m.groupCount())) + def findFirstMatchIn(source: CharSequence): Option[Match] = { + val m = pattern.matcher(source) + if (m.find) Some(new Match(source, m, groupNames)) else None + } + + /** Return optionally match of this regexp at the beginning of the + * given character sequence, or None if regexp matches no prefix + * of the character sequence. + */ + def findPrefixOf(source: CharSequence): Option[String] = { + val m = pattern.matcher(source) + if (m.lookingAt) Some(m.group) else None + } + + /** Return optionally match of this regexp at the beginning of the + * given character sequence, or None if regexp matches no prefix + * of the character sequence. + */ + def findPrefixMatchOf(source: CharSequence): Option[Match] = { + val m = pattern.matcher(source) + if (m.lookingAt) Some(new Match(source, m, groupNames)) else None } /** Replaces all matches by a string. @@ -129,7 +92,7 @@ class Regex(val regex: String, private var groupNames: Map[String, Int]) { * @param replacement The string that will replace each match * @return The resulting string */ - def replaceAll(target: String, replacement: String): String = { + def replaceAllIn(target: CharSequence, replacement: String): String = { val m = pattern.matcher(target) m.replaceAll(replacement) } @@ -140,43 +103,170 @@ class Regex(val regex: String, private var groupNames: Map[String, Int]) { * @param replacement The string that will replace the match * @return The resulting string */ - def replaceFirst(target: String, replacement: String): String = { + def replaceFirstIn(target: CharSequence, replacement: String): String = { val m = pattern.matcher(target) m.replaceFirst(replacement) } - /** Returns true if the whole string matches. - * - * @param target The string to match - * @return true iff the whole string matches + /** The string defining the regular expression */ + override def toString = regex +} + +/** This object defines inner classes that describe + * regex matches. The class hirrachy is as follows. + * + * MatchData + * | \ + * MatchIterator Match + */ +object Regex { + + /** This class provides methods to access + * the details of a match. */ - def matchesAll(target: String): Boolean = { - val m = pattern.matcher(target) - m.find() + trait MatchData { + + /** The source from where the match originated */ + val source: CharSequence + + /** The names of the groups, or some empty sequence if one defined */ + val groupNames: Seq[String] + + /** The index of the first matched character */ + def start: Int + + /** The index of the first matched character in group i */ + def start(i: Int): Int + + /** The index of the last matched character */ + def end: Int + + /** The number of subgroups */ + def groupCount: Int + + /** The index following the last matched character in group i */ + def end(i: Int): Int + + /** The matched string */ + def matched: String = source.subSequence(start, end).toString + + /** The matched string in group i */ + def group(i: Int): String = source.subSequence(start(i), end(i)).toString + + /** All matched subgroups, i.e. not including group(0) */ + def subgroups: List[String] = (1 to groupCount).toList map group + + /** The char sequence before first character of match */ + def before: CharSequence = source.subSequence(0, start) + + /** The char sequence before first character of match in group i */ + def before(i: Int): CharSequence = source.subSequence(0, start(i)) + + /** Returns char sequence after last character of match */ + def after: CharSequence = source.subSequence(end) + + /** The char sequence after last character of match in group i */ + def after(i: Int): CharSequence = source.subSequence(end(i)) + + private lazy val nameToIndex: Map[String, Int] = Map() ++ ("" :: groupNames.toList).zipWithIndex + + /** Returns the group with given name + * + * @param id The group name + * @return The requested group + * @throws NoSuchElementException if the requested + * group name is not defined + */ + def group(id: String): String = nameToIndex.get(id) match { + case None => throw new NoSuchElementException("group name "+id+" not defined") + case Some(index) => group(index) + } + + /** The matched string; equivalent to matched.toString */ + override def toString = matched + } - /** Returns true iff the string or one of its substrings match. - * - * @param target The string to match - * @return true iff the string matches + /** A case class for a succesful match. */ - def matches(target: String): Boolean = { - val m = pattern.matcher(target) - m.matches() + class Match(val source: CharSequence, + matcher: Matcher, + val groupNames: Seq[String]) extends MatchData { + + /** The index of the first matched character */ + val start = matcher.start + + /** The index following the last matched character */ + val end = matcher.end + + /** The number of subgroups */ + def groupCount = matcher.groupCount + + private lazy val starts: Array[Int] = + ((1 to groupCount) map matcher.start).toArray + private lazy val ends: Array[Int] = + ((1 to groupCount) map matcher.end).toArray + + /** The index of the first matched character in group i */ + def start(i: Int) = starts(i) + + /** The index following the last matched character in group i */ + def end(i: Int) = ends(i) + + /** The match itself with matcher-dependent lazy vals forced, + * so that match is valid even once matcher is advanced + */ + def force: this.type = { starts; ends; this } } - override def toString = """regex"""+".r" -} + /** An extractor object for Matches, yielding the matched string */ + object Match { + def unapply(m: Match): Some[String] = Some(m.matched) + } -/** Provides implicit conversions for regular expressions. - */ -object Regex { - /** Promotes a string to MatchableString so that =~ can be used */ - implicit def stringToMatchableString(s: String) = new MatchableString(s) + /** A class to step through a sequence of regex matches + */ + class MatchIterator(val source: CharSequence, val regex: Regex, val groupNames: Seq[String]) + extends Iterator[String] with MatchData { self => + + private val matcher = regex.pattern.matcher(source) + private var nextSeen = false + + /** Is there another match? */ + def hasNext: Boolean = { + if (!nextSeen) nextSeen = matcher.find() + nextSeen + } + + /** The next matched substring of `source' */ + def next: String = { + if (!hasNext) throw new NoSuchElementException + nextSeen = false + matcher.group + } + + /** The index of the first matched character */ + def start: Int = matcher.start + + /** The index of the first matched character in group i */ + def start(i: Int): Int = matcher.start(i) + + /** The index of the last matched character */ + def end: Int = matcher.end + + /** The index following the last matched character in group i */ + def end(i: Int): Int = matcher.end(i) - /** Allows treating an Option as a Boolean. */ - implicit def optionToBoolean(o: Option[MatchData[String]]) = o.isDefined + /** The number of subgroups */ + def groupCount = matcher.groupCount - /** Promotes a string to a Regex. */ - implicit def stringToRegex(s: String) = new Regex(s) + /** Convert to an iterator that yields MatchData elements instead of Strings */ + def matchData = new Iterator[Match] { + def hasNext = self.hasNext + def next = { self.next; new Match(source, matcher, groupNames).force } + } + } } + + + diff --git a/src/library/scala/util/parsing/combinator/JavaTokenParsers.scala b/src/library/scala/util/parsing/combinator/JavaTokenParsers.scala new file mode 100644 index 0000000000..6aceb5b3bc --- /dev/null +++ b/src/library/scala/util/parsing/combinator/JavaTokenParsers.scala @@ -0,0 +1,24 @@ +/* __ *\ +** ________ ___ / / ___ Scala API ** +** / __/ __// _ | / / / _ | (c) 2006-2007, LAMP/EPFL ** +** __\ \/ /__/ __ |/ /__/ __ | http://scala-lang.org/ ** +** /____/\___/_/ |_/____/_/ | | ** +** |/ ** +\* */ + +// $Id: Parsers.scala 12357 2007-07-18 21:55:08Z moors $ + +package scala.util.parsing.combinator + +trait JavaTokenParsers extends RegexParsers { + def ident: Parser[String] = + """[a-zA-Z_]\w*""".r + def wholeNumber: Parser[String] = + """-?\d+""".r + def decimalNumber: Parser[String] = + """(\d+(\.\d*)?|\d*.\d+)""".r + def stringLiteral: Parser[String] = + ("\""+"""([^"\p{Cntrl}\\]|\\[\\/bfnrt]|\\u[a-fA-F0-9]{4})*"""+"\"").r + def floatingPointNumber: Parser[String] = + """-?(\d+(\.\d*)?|\d*\.\d+)[eEfFdD]?([+-]?\d+)?""".r +} diff --git a/src/library/scala/util/parsing/combinator/RegexParsers.scala b/src/library/scala/util/parsing/combinator/RegexParsers.scala index 4b76a8ee79..18e7f4d8e4 100755 --- a/src/library/scala/util/parsing/combinator/RegexParsers.scala +++ b/src/library/scala/util/parsing/combinator/RegexParsers.scala @@ -12,7 +12,7 @@ package scala.util.parsing.combinator import java.util.regex.Pattern import scala.util.matching.Regex -import scala.util.parsing.input.CharSequenceReader +import scala.util.parsing.input._ trait RegexParsers extends Parsers { @@ -20,16 +20,16 @@ trait RegexParsers extends Parsers { var skipWhitespace = true - private val whiteSpacePat = Pattern compile """\s+""" + private val whiteSpace = """\s+""".r - private def handleWhiteSpace(source: CharSequence, offset: Int): Int = { - var start = offset - if (skipWhitespace) { - val wsm = whiteSpacePat.matcher(source.subSequence(offset, source.length)) - if (wsm.lookingAt) start += wsm.end - } - start - } + private def handleWhiteSpace(source: CharSequence, offset: Int): Int = + if (skipWhitespace) + (whiteSpace findPrefixMatchOf (source subSequence offset)) match { + case Some(matched) => offset + matched.end + case None => offset + } + else + offset /** A parser that matches a literal string */ implicit def literal(s: String): Parser[String] = new Parser[String] { @@ -39,14 +39,14 @@ trait RegexParsers extends Parsers { val start = handleWhiteSpace(source, offset) var i = 0 var j = start - while (i < s.length && j < source.length && s.charAt(i) == source.charAt(j)) { + while (i < s.length && source.isDefinedAt(j) && s.charAt(i) == source.charAt(j)) { i += 1 j += 1 } if (i == s.length) Success(source.subSequence(start, j).toString, in.drop(j - offset)) else - Failure("`"+s+"' expected", in.drop(start - offset)) + Failure("`"+s+"' expected but `"+in.first+"' found", in.drop(start - offset)) } } @@ -56,20 +56,29 @@ trait RegexParsers extends Parsers { val source = in.source val offset = in.offset val start = handleWhiteSpace(source, offset) - val pm = r.pattern.matcher(source.subSequence(start, source.length)) - if (pm.lookingAt) - Success(source.subSequence(start, start + pm.end).toString, - in.drop(start + pm.end - offset)) - else - Failure("string matching regex `"+r.regex+"' expected", in.drop(start - offset)) + (r findPrefixMatchOf (source subSequence start)) match { + case Some(matched) => + Success(source.subSequence(start, start + matched.end).toString, + in.drop(start + matched.end - offset)) + case None => + Failure("string matching regex `+r+' expected but `"+in.first+"' found", in.drop(start - offset)) + } } } /** Parse some prefix of character sequence `in' with parser `p' */ - def parse[T](p: Parser[T])(in: CharSequence): ParseResult[T] = + def parse[T](p: Parser[T], in: CharSequence): ParseResult[T] = p(new CharSequenceReader(in)) + /** Parse some prefix of reader `in' with parser `p' */ + def parse[T](p: Parser[T], in: Reader[Char]): ParseResult[T] = + p(in) + /** Parse all of character sequence `in' with parser `p' */ - def parseAll[T](p: Parser[T])(in: CharSequence): ParseResult[T] = - parse(phrase(p))(in) + def parseAll[T](p: Parser[T], in: CharSequence): ParseResult[T] = + parse(phrase(p), in) + + /** Parse all of reader `in' with parser `p' */ + def parseAll[T](p: Parser[T], in: Reader[Char]): ParseResult[T] = + parse(phrase(p), in) } diff --git a/src/library/scala/util/parsing/combinator/lexical/Scanners.scala b/src/library/scala/util/parsing/combinator/lexical/Scanners.scala index d4f3103a71..f1a930ecdf 100644 --- a/src/library/scala/util/parsing/combinator/lexical/Scanners.scala +++ b/src/library/scala/util/parsing/combinator/lexical/Scanners.scala @@ -66,8 +66,8 @@ trait Scanners extends Parsers { } private def skip(in: Reader[Char]) = if (in.atEnd) in else in.rest - def source: CharSequence = in.source - def offset: Int = in.offset + override def source: CharSequence = in.source + override def offset: Int = in.offset def first = tok def rest = new Scanner(rest2) def pos = rest1.pos diff --git a/src/library/scala/util/parsing/combinatorold/lexical/Scanners.scala b/src/library/scala/util/parsing/combinatorold/lexical/Scanners.scala index d4479d8045..15e9542bb5 100644 --- a/src/library/scala/util/parsing/combinatorold/lexical/Scanners.scala +++ b/src/library/scala/util/parsing/combinatorold/lexical/Scanners.scala @@ -66,8 +66,8 @@ trait Scanners extends Parsers { } private def skip(in: Reader[Char]) = if (in.atEnd) in else in.rest - def source: CharSequence = in.source - def offset: Int = in.offset + override def source: CharSequence = in.source + override def offset: Int = in.offset def first = tok def rest = new Scanner(rest2) def pos = rest1.pos diff --git a/src/library/scala/util/parsing/input/CharArrayPosition.scala b/src/library/scala/util/parsing/input/CharArrayPosition.scala index 8d63b12332..bf86e2e104 100644 --- a/src/library/scala/util/parsing/input/CharArrayPosition.scala +++ b/src/library/scala/util/parsing/input/CharArrayPosition.scala @@ -26,10 +26,10 @@ class CharArrayPosition(val source: Array[Char], val line: Int, val column: Int) // TODO: this could be implemented more high-level: // return the string representation of the sub-array of source that starts // after the (lnum-1)'ed '\n' up to (but not including) the (lnum)'ed '\n' - protected def lineContents(lnum: Int) = { + protected def lineContents = { var i = 0 var l = 1 - while (i < source.length && l < lnum) { + while (i < source.length && l < line) { while (i < source.length && source(i) != '\n') i += 1 i += 1 l += 1 diff --git a/src/library/scala/util/parsing/input/CharArrayReader.scala b/src/library/scala/util/parsing/input/CharArrayReader.scala index b7c576c631..44e1b6eb6e 100644 --- a/src/library/scala/util/parsing/input/CharArrayReader.scala +++ b/src/library/scala/util/parsing/input/CharArrayReader.scala @@ -16,7 +16,9 @@ package scala.util.parsing.input */ object CharArrayReader { final val EofCh = '\032' - final val CR = '\015' + + /** @deprecated This should probably be LF instead? */ + @deprecated final val CR = '\015' } /** A character array reader reads a stream of characters (keeping track of their positions) @@ -30,7 +32,7 @@ object CharArrayReader { * @author Martin Odersky, Adriaan Moors */ class CharArrayReader(chars: Array[Char], index: Int) -extends CharSequenceReader(new CharArraySequence(chars), index) { +extends CharSequenceReader(CharSequence.fromArray(chars), index) { def this(chars: Array[Char]) = this(chars, 0) diff --git a/src/library/scala/util/parsing/input/CharArraySequence.scala b/src/library/scala/util/parsing/input/CharArraySequence.scala deleted file mode 100755 index 9acca84df2..0000000000 --- a/src/library/scala/util/parsing/input/CharArraySequence.scala +++ /dev/null @@ -1,37 +0,0 @@ -/* __ *\ -** ________ ___ / / ___ Scala API ** -** / __/ __// _ | / / / _ | (c) 2006-2007, LAMP/EPFL ** -** __\ \/ /__/ __ |/ /__/ __ | http://scala-lang.org/ ** -** /____/\___/_/ |_/____/_/ | | ** -** |/ ** -\* */ - -// $Id: - - -package scala.util.parsing.input - -/** An implementation of java.lang.CharSequence over a character array - * - * @author Martin Odersky - */ -class CharArraySequence(source: Array[Char], start: Int, end: Int) extends CharSequence { - - def this(source: Array[Char], start: Int) = this(source, start, source.length) - - def this(source: Array[Char]) = this(source, 0) - - if (start < 0) throw new IndexOutOfBoundsException - if (end > source.length) throw new IndexOutOfBoundsException - - def charAt(index: Int) = - if (start + index < end) source(index + start) - else throw new IndexOutOfBoundsException - - def length: Int = if (end < start) 0 else end - start - - def subSequence(_start: Int, _end: Int) = - new CharArraySequence(source, start + _start, start + _end) - - override def toString = new String(source, start, end - start) -} diff --git a/src/library/scala/util/parsing/input/CharSequenceReader.scala b/src/library/scala/util/parsing/input/CharSequenceReader.scala index 453a808f08..adcd6b99f0 100755 --- a/src/library/scala/util/parsing/input/CharSequenceReader.scala +++ b/src/library/scala/util/parsing/input/CharSequenceReader.scala @@ -16,18 +16,18 @@ package scala.util.parsing.input */ object CharSequenceReader { final val EofCh = '\032' - final val CR = '\015' } /** A character array reader reads a stream of characters (keeping track of their positions) * from an array. * * @param source the source sequence - * @param index starting index. + * @param offset starting offset. * * @author Martin Odersky */ -class CharSequenceReader(val source: CharSequence, index: Int) extends Reader[Char] { +class CharSequenceReader(override val source: CharSequence, + override val offset: Int) extends Reader[Char] { import CharSequenceReader._ /** Construct a CharSequenceReader with its first element at @@ -35,22 +35,10 @@ class CharSequenceReader(val source: CharSequence, index: Int) extends Reader[Ch */ def this(source: CharSequence) = this(source, 0) - /** The effective offset into source - * This is the same as index, except that CR characters at the end of a line - * are always skipped. - */ - lazy val offset: Int = - if (index + 1 < source.length && - source.charAt(index) == CR && source.charAt(index + 1) == '\n') - index + 1 - else - index - /** Returns the first element of the reader, or EofCh if reader is at its end */ def first = - if (offset == source.length) EofCh - else source.charAt(offset) + if (source.isDefinedAt(offset)) source(offset) else EofCh /** Returns a CharSequenceReader consisting of all elements except the first * @@ -59,8 +47,8 @@ class CharSequenceReader(val source: CharSequence, index: Int) extends Reader[Ch * the rest of input. */ def rest: CharSequenceReader = - if (offset == source.length) this - else new CharSequenceReader(source, offset + 1) + if (source.isDefinedAt(offset)) new CharSequenceReader(source, offset + 1) + else this /** The position of the first element in the reader */ @@ -69,11 +57,11 @@ class CharSequenceReader(val source: CharSequence, index: Int) extends Reader[Ch /** true iff there are no more elements in this reader (except for trailing * EofCh's) */ - def atEnd = offset == source.length + def atEnd = !source.isDefinedAt(offset) /** Returns an abstract reader consisting of all elements except the first * n elements. */ override def drop(n: Int): CharSequenceReader = - new CharSequenceReader(source, (offset + n) min source.length) + new CharSequenceReader(source, offset + n) } diff --git a/src/library/scala/util/parsing/input/NoPosition.scala b/src/library/scala/util/parsing/input/NoPosition.scala index 7c11a7c510..1337164394 100644 --- a/src/library/scala/util/parsing/input/NoPosition.scala +++ b/src/library/scala/util/parsing/input/NoPosition.scala @@ -20,5 +20,5 @@ object NoPosition extends Position { def column = 0 override def toString = "" override def longString = toString - def lineContents(lnum: Int) = "" + def lineContents = "" } diff --git a/src/library/scala/util/parsing/input/OffsetPosition.scala b/src/library/scala/util/parsing/input/OffsetPosition.scala index a29e195455..6bbc6fdcac 100755 --- a/src/library/scala/util/parsing/input/OffsetPosition.scala +++ b/src/library/scala/util/parsing/input/OffsetPosition.scala @@ -50,8 +50,8 @@ case class OffsetPosition(source: CharSequence, offset: Int) extends Position { * @param lnum a 1-based integer index into the `document' * @return the line at `lnum' (not including a newline) */ - def lineContents(lnum: Int): String = - source.subSequence(index(lnum - 1), index(lnum)).toString + def lineContents: String = + source.subSequence(index(line - 1), index(line)).toString /** Returns a string representation of the `Position', of the form `line.column' */ override def toString = line+"."+column diff --git a/src/library/scala/util/parsing/input/Position.scala b/src/library/scala/util/parsing/input/Position.scala index 64d2ecf3a1..6f375b3529 100644 --- a/src/library/scala/util/parsing/input/Position.scala +++ b/src/library/scala/util/parsing/input/Position.scala @@ -38,7 +38,7 @@ trait Position { * @param lnum a 1-based integer index into the `document' * @return the line at `lnum' (not including a newline) */ - protected def lineContents(lnum: Int): String + protected def lineContents: String /** Returns a string representation of the `Position', of the form `line.column' */ override def toString = ""+line+"."+column @@ -53,7 +53,7 @@ trait Position { *
    List(this, is, a, line, from, the, document)
    *                  ^
*/ - def longString = lineContents(line)+"\n"+(" " * (column - 1))+"^" + def longString = lineContents+"\n"+(" " * (column - 1))+"^" /** Compare this position to another, by first comparing their line numbers, * and then -- if necessary -- using the columns to break a tie. diff --git a/src/library/scala/util/parsing/input/Reader.scala b/src/library/scala/util/parsing/input/Reader.scala index f9d7115113..51cf7eeb47 100644 --- a/src/library/scala/util/parsing/input/Reader.scala +++ b/src/library/scala/util/parsing/input/Reader.scala @@ -11,17 +11,22 @@ package scala.util.parsing.input + /** An interface for streams of values that have positions. * * @author Martin Odersky, Adriaan Moors */ abstract class Reader[+T] { - /** The source character sequence for this reader */ - def source: CharSequence + private[parsing] def source: CharSequence = this match { + case csr: CharSequenceReader => csr.source + case _ => throw new IllegalArgumentException("This kind of parser operates only on a CharSequenceReader") + } - /** The current index into source */ - def offset: Int + private[parsing] def offset: Int = this match { + case csr: CharSequenceReader => csr.offset + case _ => throw new IllegalArgumentException("This kind of parser operates only on a CharSequenceReader") + } /** Returns the first element of the reader */ @@ -41,7 +46,9 @@ abstract class Reader[+T] { def drop(n: Int): Reader[T] = { var r: Reader[T] = this var cnt = n - while (cnt > 0) r = r.rest + while (cnt > 0) { + r = r.rest; cnt -= 1 + } r } diff --git a/src/library/scala/util/parsing/input/StreamReader.scala b/src/library/scala/util/parsing/input/StreamReader.scala index bad859d4c0..f8835ace25 100755 --- a/src/library/scala/util/parsing/input/StreamReader.scala +++ b/src/library/scala/util/parsing/input/StreamReader.scala @@ -21,11 +21,9 @@ import java.io.BufferedReader */ object StreamReader { final val EofCh = '\032' - final val CR = '\015' def apply(in: java.io.Reader): StreamReader = { - val bin = new BufferedReader(in) - new StreamReader(bin, bin.readLine, 1, 1) + new StreamReader(new LazyCharSequence(in), 0, 1) } } @@ -46,34 +44,31 @@ object StreamReader { * * @author Miles Sabin */ -sealed class StreamReader private (bin: BufferedReader, sourceLine: String, ln: Int, col: Int) -extends Reader[Char] { +sealed class StreamReader(source: CharSequence, offset: Int, lnum: Int) extends CharSequenceReader(source, offset) { import StreamReader._ - def source: CharSequence = sourceLine - def offset: Int = col-1 + override def rest: CharSequenceReader = + if (offset == source.length) this + else if (source(offset) == '\n') new StreamReader(source.subSequence(offset + 1), 0, lnum + 1) + else new StreamReader(source, offset + 1, lnum) - def first = - if (sourceLine == null) - EofCh - else if (col > sourceLine.length) - CR - else - sourceLine(col-1) + private def nextEol = { + var i = offset + while (i < source.length && source(i) != '\n' && source(i) != EofCh) i += 1 + i + } - def rest: StreamReader = - if (sourceLine == null) - this - else if (col > sourceLine.length) - new StreamReader(bin, bin.readLine, ln+1, 1) + override def drop(n: Int): StreamReader = { + val eolPos = nextEol + if (eolPos < offset + n && eolPos < source.length) + new StreamReader(source.subSequence(eolPos + 1), 0, lnum + 1).drop(offset + n - (eolPos + 1)) else - new StreamReader(bin, sourceLine, ln, col+1) - - def pos: Position = new Position { - def line = ln - def column = col - def lineContents(lnum: Int) = sourceLine - } + new StreamReader(source, offset + n, lnum) + } - def atEnd = (sourceLine == null) + override def pos: Position = new Position { + def line = lnum + def column = offset + 1 + def lineContents = source.subSequence(0, nextEol).toString + } } -- cgit v1.2.3