From 75f75ecc162a8185b3f4c14bf845771c2a8e2f11 Mon Sep 17 00:00:00 2001 From: Antoine Gourlay Date: Tue, 24 Jan 2017 15:46:59 +0100 Subject: [PATCH] Move PagedSeq from scala-library to parser-combinators This moves `scala.collection.immutable.PagedSeq` to the `scala.util.parsing.input` package, after it was deprecated over there (see SI-9503). The only real bit of change doesn't show on the diff because it is a new file, so here is the diff between the scala-library version and this one: diff --git i/shared/src/main/scala/scala/util/parsing/input/PagedSeq.scala w/shared/src/main/scala/scala/util/parsing/input/PagedSeq.scala index 01854b1..ba12ec2 100644 --- i/shared/src/main/scala/scala/util/parsing/input/PagedSeq.scala +++ w/shared/src/main/scala/scala/util/parsing/input/PagedSeq.scala @@ -9,10 +9,9 @@ package scala -package collection -package immutable +package util.parsing.input -import java.io.{File, FileReader, Reader} +import java.io.{File, FileReader, Reader => JReader} import scala.reflect.ClassTag /** The `PagedSeq` object defines a lazy implementations of @@ -20,9 +19,7 @@ import scala.reflect.ClassTag * * Provides utility methods that return instances of `PagedSeq[Char]`. * `fromIterator` and `fromIterable` provide generalised instances of `PagedSeq` - * @since 2.7 */ -@deprecated("this object will be moved to the scala-parser-combinators module", "2.11.8") object PagedSeq { final val UndeterminedEnd = Int.MaxValue @@ -85,7 +82,7 @@ object PagedSeq { /** Constructs a paged character sequence from an input reader */ - def fromReader(source: Reader): PagedSeq[Char] = + def fromReader(source: JReader): PagedSeq[Char] = new PagedSeq(source.read(_: Array[Char], _: Int, _: Int)) /** Constructs a paged character sequence from an input file @@ -120,13 +117,11 @@ import PagedSeq._ * @tparam T the type of the elements contained in this paged sequence, with an `ClassTag` context bound. * * @author Martin Odersky - * @since 2.7 * @define Coll `PagedSeq` * @define coll paged sequence * @define mayNotTerminateInf * @define willNotTerminateInf */ -@deprecated("this class will be moved to the scala-parser-combinators module", "2.11.8") class PagedSeq[T: ClassTag] protected( more: (Array[T], Int, Int) => Int, first1: Page[T], Fixes #71 --- .../parsing/combinator/RegexParsers.scala | 1 - .../scala/util/parsing/input/PagedSeq.scala | 267 ++++++++++++++++++ .../util/parsing/input/PagedSeqReader.scala | 2 - .../util/parsing/input/StreamReader.scala | 1 - .../scala/util/parsing/combinator/gh45.scala | 1 - .../scala/util/parsing/combinator/t8879.scala | 1 - 6 files changed, 267 insertions(+), 6 deletions(-) create mode 100644 shared/src/main/scala/scala/util/parsing/input/PagedSeq.scala diff --git a/shared/src/main/scala/scala/util/parsing/combinator/RegexParsers.scala b/shared/src/main/scala/scala/util/parsing/combinator/RegexParsers.scala index 52e781d0..f18c0ae5 100644 --- a/shared/src/main/scala/scala/util/parsing/combinator/RegexParsers.scala +++ b/shared/src/main/scala/scala/util/parsing/combinator/RegexParsers.scala @@ -13,7 +13,6 @@ package util.parsing.combinator import java.util.regex.Pattern import scala.util.matching.Regex import scala.util.parsing.input._ -import scala.collection.immutable.PagedSeq import scala.language.implicitConversions /** The ''most important'' differences between `RegexParsers` and diff --git a/shared/src/main/scala/scala/util/parsing/input/PagedSeq.scala b/shared/src/main/scala/scala/util/parsing/input/PagedSeq.scala new file mode 100644 index 00000000..ba12ec28 --- /dev/null +++ b/shared/src/main/scala/scala/util/parsing/input/PagedSeq.scala @@ -0,0 +1,267 @@ +/* __ *\ +** ________ ___ / / ___ Scala API ** +** / __/ __// _ | / / / _ | (c) 2006-2013, LAMP/EPFL ** +** __\ \/ /__/ __ |/ /__/ __ | http://scala-lang.org/ ** +** /____/\___/_/ |_/____/_/ | | ** +** |/ ** +\* */ + + + +package scala +package util.parsing.input + +import java.io.{File, FileReader, Reader => JReader} +import scala.reflect.ClassTag + +/** The `PagedSeq` object defines a lazy implementations of + * a random access sequence. + * + * Provides utility methods that return instances of `PagedSeq[Char]`. + * `fromIterator` and `fromIterable` provide generalised instances of `PagedSeq` + */ +object PagedSeq { + final val UndeterminedEnd = Int.MaxValue + + /** Constructs a paged sequence from an iterator */ + def fromIterator[T: ClassTag](source: Iterator[T]): PagedSeq[T] = + new PagedSeq[T]((data: Array[T], start: Int, len: Int) => { + var i = 0 + while (i < len && source.hasNext) { + data(start + i) = source.next() + i += 1 + } + if (i == 0) -1 else i + }) + + /** Constructs a paged sequence from an iterable */ + def fromIterable[T: ClassTag](source: Iterable[T]): PagedSeq[T] = + fromIterator(source.iterator) + + /** Constructs a paged character sequence from a string iterator */ + def fromStrings(source: Iterator[String]): PagedSeq[Char] = { + var current: String = "" + def more(data: Array[Char], start: Int, len: Int): Int = + if (current.length != 0) { + val cnt = current.length min len + current.getChars(0, cnt, data, start) + current = current.substring(cnt) + if (cnt == len) cnt + else (more(data, start + cnt, len - cnt) max 0) + cnt + } else if (source.hasNext) { + current = source.next() + more(data, start, len) + } else -1 + new PagedSeq(more(_: Array[Char], _: Int, _: Int)) + } + + /** Constructs a paged character sequence from a string iterable */ + def fromStrings(source: Iterable[String]): PagedSeq[Char] = + fromStrings(source.iterator) + + /** Constructs a paged 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]): PagedSeq[Char] = { + var isFirst = true + fromStrings(source map { line => + if (isFirst) { + isFirst = false + line + } else "\n"+line + }) + } + + /** Constructs a paged 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]): PagedSeq[Char] = + fromLines(source.iterator) + + /** Constructs a paged character sequence from an input reader + */ + def fromReader(source: JReader): PagedSeq[Char] = + new PagedSeq(source.read(_: Array[Char], _: Int, _: Int)) + + /** Constructs a paged character sequence from an input file + */ + def fromFile(source: File): PagedSeq[Char] = + fromReader(new FileReader(source)) + + /** Constructs a paged character sequence from a file with given name + */ + def fromFile(source: String): PagedSeq[Char] = + fromFile(new File(source)) + + /** Constructs a paged character sequence from a scala.io.Source value + */ + def fromSource(source: scala.io.Source) = + fromLines(source.getLines()) +} + + +import PagedSeq._ + +/** An implementation of lazily computed sequences, where elements are stored + * in "pages", i.e. arrays of fixed size. + * + * A paged sequence is constructed from a function that produces more elements when asked. + * The producer function - `more`, is similar to the read method in java.io.Reader. + * The `more` function takes three parameters: an array of elements, a start index, and an end index. + * It should try to fill the array between start and end indices (excluding end index). + * It returns the number of elements produced, or -1 if end of logical input stream was reached + * before reading any element. + * + * @tparam T the type of the elements contained in this paged sequence, with an `ClassTag` context bound. + * + * @author Martin Odersky + * @define Coll `PagedSeq` + * @define coll paged sequence + * @define mayNotTerminateInf + * @define willNotTerminateInf + */ +class PagedSeq[T: ClassTag] protected( + more: (Array[T], Int, Int) => Int, + first1: Page[T], + start: Int, + end: Int) +extends scala.collection.AbstractSeq[T] + with scala.collection.IndexedSeq[T] +{ + def this(more: (Array[T], Int, Int) => Int) = this(more, new Page[T](0), 0, UndeterminedEnd) + + private var current: Page[T] = first1 + + private def latest = first1.latest + + private def addMore() = latest.addMore(more) + + private def page(absindex: Int) = { + if (absindex < current.start) + current = first1 + while (absindex >= current.end && current.next != null) + current = current.next + while (absindex >= current.end && !current.isLast) { + current = addMore() + } + current + } + + /** The length of the paged sequence + * @note Calling this method will force the entire sequence to be read. + */ + def length: Int = { + while (!latest.isLast && latest.end < end) addMore() + (latest.end min end) - start + } + + /** The element at position `index`. + */ + def apply(index: Int) = + if (isDefinedAt(index)) page(index + start)(index + start) + else throw new IndexOutOfBoundsException(index.toString) + + /** Predicate method to check if an element is defined + * at position `index` of the current sequence. + * 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 absidx = index + start + absidx >= 0 && absidx < page(absidx).end + } + + /** The subsequence from index `start` up to `end -1` if `end` + * is lesser than the length of the current sequence and up to + * length of the sequence otherwise. This is limited up to the length + * of the current sequence if `end` is larger than its length. + */ + override def slice(_start: Int, _end: Int): PagedSeq[T] = { + page(start) + val s = start + _start + val e = if (_end == UndeterminedEnd) _end else start + _end + var f = first1 + while (f.end <= s && !f.isLast) { + if (f.next eq null) f = f.addMore(more) + else f = f.next + } + // Warning -- not refining `more` means that slices can freely request and obtain + // data outside of their slice. This is part of the design of PagedSeq + // (to read pages!) but can be surprising. + new PagedSeq(more, f, s, e) + } + + /** The subsequence from index `start` up to + * the length of the current sequence. + */ + def slice(start: Int): PagedSeq[T] = slice(start, UndeterminedEnd) + + /** Convert sequence to string */ + override def toString = { + val buf = new StringBuilder + for (ch <- PagedSeq.this.iterator) buf append ch + buf.toString + } +} + + +/** Page containing up to PageSize characters of the input sequence. + */ +private class Page[T: ClassTag](val num: Int) { + + private final val PageSize = 4096 + + /** The next page in the sequence */ + var next : Page[T] = null + + /** A later page in the sequence, serves a cache for pointing to last page */ + var later : Page[T] = this + + /** The number of elements read into this page */ + var filled: Int = 0 + + /** Set true if the current page is the last in the sequence or if + * the `more` function returned -1 signalling end of input. */ + var isLast: Boolean = false + + /** The element array */ + final val data = new Array[T](PageSize) + + /** The index of the first element in this page relative to the whole sequence */ + final def start = num * PageSize + + /** The index of the element following the last element in this page relative + * to the whole sequence */ + final def end = start + filled + + /** The last page as currently present in the sequence; This can change as more + * elements get appended to the sequence. */ + final def latest: Page[T] = { + if (later.next != null) later = later.next.latest + later + } + + /** The element at the 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) + data(index - start) + } + + /** Produces more elements by calling `more` and adds them on the current page, + * or fills a subsequent page if current page is full. + * @note If current page is full, it is the last one in the sequence. */ + final def addMore(more: (Array[T], Int, Int) => Int): Page[T] = + if (filled == PageSize) { + next = new Page[T](num + 1) + next.addMore(more) + } else { + val count = more(data, filled, PageSize - filled) + if (count < 0) isLast = true + else filled += count + this + } +} diff --git a/shared/src/main/scala/scala/util/parsing/input/PagedSeqReader.scala b/shared/src/main/scala/scala/util/parsing/input/PagedSeqReader.scala index 51e66943..b4fefd0c 100644 --- a/shared/src/main/scala/scala/util/parsing/input/PagedSeqReader.scala +++ b/shared/src/main/scala/scala/util/parsing/input/PagedSeqReader.scala @@ -10,8 +10,6 @@ package scala package util.parsing.input -import scala.collection.immutable.PagedSeq - /** An object encapsulating basic character constants. * * @author Martin Odersky diff --git a/shared/src/main/scala/scala/util/parsing/input/StreamReader.scala b/shared/src/main/scala/scala/util/parsing/input/StreamReader.scala index 1883187d..11b84477 100644 --- a/shared/src/main/scala/scala/util/parsing/input/StreamReader.scala +++ b/shared/src/main/scala/scala/util/parsing/input/StreamReader.scala @@ -10,7 +10,6 @@ package scala package util.parsing.input import java.io.BufferedReader -import scala.collection.immutable.PagedSeq /** An object to create a `StreamReader` from a `java.io.Reader`. * diff --git a/shared/src/test/scala/scala/util/parsing/combinator/gh45.scala b/shared/src/test/scala/scala/util/parsing/combinator/gh45.scala index e184719a..ee685116 100644 --- a/shared/src/test/scala/scala/util/parsing/combinator/gh45.scala +++ b/shared/src/test/scala/scala/util/parsing/combinator/gh45.scala @@ -1,7 +1,6 @@ package scala.util.parsing.combinator import scala.util.parsing.input._ -import scala.collection.immutable.PagedSeq import org.junit.Test import org.junit.Assert.assertTrue diff --git a/shared/src/test/scala/scala/util/parsing/combinator/t8879.scala b/shared/src/test/scala/scala/util/parsing/combinator/t8879.scala index 2be5a563..09a7e22d 100644 --- a/shared/src/test/scala/scala/util/parsing/combinator/t8879.scala +++ b/shared/src/test/scala/scala/util/parsing/combinator/t8879.scala @@ -1,5 +1,4 @@ import scala.util.parsing.input._ -import scala.collection.immutable.PagedSeq import org.junit.Test import org.junit.Assert.fail