|
| 1 | +/* __ *\ |
| 2 | +** ________ ___ / / ___ Scala API ** |
| 3 | +** / __/ __// _ | / / / _ | (c) 2006-2013, LAMP/EPFL ** |
| 4 | +** __\ \/ /__/ __ |/ /__/ __ | http://scala-lang.org/ ** |
| 5 | +** /____/\___/_/ |_/____/_/ | | ** |
| 6 | +** |/ ** |
| 7 | +\* */ |
| 8 | + |
| 9 | + |
| 10 | + |
| 11 | +package scala |
| 12 | +package util.parsing.input |
| 13 | + |
| 14 | +import java.io.{File, FileReader, Reader => JReader} |
| 15 | +import scala.reflect.ClassTag |
| 16 | + |
| 17 | +/** The `PagedSeq` object defines a lazy implementations of |
| 18 | + * a random access sequence. |
| 19 | + * |
| 20 | + * Provides utility methods that return instances of `PagedSeq[Char]`. |
| 21 | + * `fromIterator` and `fromIterable` provide generalised instances of `PagedSeq` |
| 22 | + */ |
| 23 | +object PagedSeq { |
| 24 | + final val UndeterminedEnd = Int.MaxValue |
| 25 | + |
| 26 | + /** Constructs a paged sequence from an iterator */ |
| 27 | + def fromIterator[T: ClassTag](source: Iterator[T]): PagedSeq[T] = |
| 28 | + new PagedSeq[T]((data: Array[T], start: Int, len: Int) => { |
| 29 | + var i = 0 |
| 30 | + while (i < len && source.hasNext) { |
| 31 | + data(start + i) = source.next() |
| 32 | + i += 1 |
| 33 | + } |
| 34 | + if (i == 0) -1 else i |
| 35 | + }) |
| 36 | + |
| 37 | + /** Constructs a paged sequence from an iterable */ |
| 38 | + def fromIterable[T: ClassTag](source: Iterable[T]): PagedSeq[T] = |
| 39 | + fromIterator(source.iterator) |
| 40 | + |
| 41 | + /** Constructs a paged character sequence from a string iterator */ |
| 42 | + def fromStrings(source: Iterator[String]): PagedSeq[Char] = { |
| 43 | + var current: String = "" |
| 44 | + def more(data: Array[Char], start: Int, len: Int): Int = |
| 45 | + if (current.length != 0) { |
| 46 | + val cnt = current.length min len |
| 47 | + current.getChars(0, cnt, data, start) |
| 48 | + current = current.substring(cnt) |
| 49 | + if (cnt == len) cnt |
| 50 | + else (more(data, start + cnt, len - cnt) max 0) + cnt |
| 51 | + } else if (source.hasNext) { |
| 52 | + current = source.next() |
| 53 | + more(data, start, len) |
| 54 | + } else -1 |
| 55 | + new PagedSeq(more(_: Array[Char], _: Int, _: Int)) |
| 56 | + } |
| 57 | + |
| 58 | + /** Constructs a paged character sequence from a string iterable */ |
| 59 | + def fromStrings(source: Iterable[String]): PagedSeq[Char] = |
| 60 | + fromStrings(source.iterator) |
| 61 | + |
| 62 | + /** Constructs a paged character sequence from a line iterator |
| 63 | + * Lines do not contain trailing `\n` characters; The method inserts |
| 64 | + * a line separator `\n` between any two lines in the sequence. |
| 65 | + */ |
| 66 | + def fromLines(source: Iterator[String]): PagedSeq[Char] = { |
| 67 | + var isFirst = true |
| 68 | + fromStrings(source map { line => |
| 69 | + if (isFirst) { |
| 70 | + isFirst = false |
| 71 | + line |
| 72 | + } else "\n"+line |
| 73 | + }) |
| 74 | + } |
| 75 | + |
| 76 | + /** Constructs a paged character sequence from a line iterable |
| 77 | + * Lines do not contain trailing `\n` characters; The method inserts |
| 78 | + * a line separator `\n` between any two lines in the sequence. |
| 79 | + */ |
| 80 | + def fromLines(source: Iterable[String]): PagedSeq[Char] = |
| 81 | + fromLines(source.iterator) |
| 82 | + |
| 83 | + /** Constructs a paged character sequence from an input reader |
| 84 | + */ |
| 85 | + def fromReader(source: JReader): PagedSeq[Char] = |
| 86 | + new PagedSeq(source.read(_: Array[Char], _: Int, _: Int)) |
| 87 | + |
| 88 | + /** Constructs a paged character sequence from an input file |
| 89 | + */ |
| 90 | + def fromFile(source: File): PagedSeq[Char] = |
| 91 | + fromReader(new FileReader(source)) |
| 92 | + |
| 93 | + /** Constructs a paged character sequence from a file with given name |
| 94 | + */ |
| 95 | + def fromFile(source: String): PagedSeq[Char] = |
| 96 | + fromFile(new File(source)) |
| 97 | + |
| 98 | + /** Constructs a paged character sequence from a scala.io.Source value |
| 99 | + */ |
| 100 | + def fromSource(source: scala.io.Source) = |
| 101 | + fromLines(source.getLines()) |
| 102 | +} |
| 103 | + |
| 104 | + |
| 105 | +import PagedSeq._ |
| 106 | + |
| 107 | +/** An implementation of lazily computed sequences, where elements are stored |
| 108 | + * in "pages", i.e. arrays of fixed size. |
| 109 | + * |
| 110 | + * A paged sequence is constructed from a function that produces more elements when asked. |
| 111 | + * The producer function - `more`, is similar to the read method in java.io.Reader. |
| 112 | + * The `more` function takes three parameters: an array of elements, a start index, and an end index. |
| 113 | + * It should try to fill the array between start and end indices (excluding end index). |
| 114 | + * It returns the number of elements produced, or -1 if end of logical input stream was reached |
| 115 | + * before reading any element. |
| 116 | + * |
| 117 | + * @tparam T the type of the elements contained in this paged sequence, with an `ClassTag` context bound. |
| 118 | + * |
| 119 | + * @author Martin Odersky |
| 120 | + * @define Coll `PagedSeq` |
| 121 | + * @define coll paged sequence |
| 122 | + * @define mayNotTerminateInf |
| 123 | + * @define willNotTerminateInf |
| 124 | + */ |
| 125 | +class PagedSeq[T: ClassTag] protected( |
| 126 | + more: (Array[T], Int, Int) => Int, |
| 127 | + first1: Page[T], |
| 128 | + start: Int, |
| 129 | + end: Int) |
| 130 | +extends scala.collection.AbstractSeq[T] |
| 131 | + with scala.collection.IndexedSeq[T] |
| 132 | +{ |
| 133 | + def this(more: (Array[T], Int, Int) => Int) = this(more, new Page[T](0), 0, UndeterminedEnd) |
| 134 | + |
| 135 | + private var current: Page[T] = first1 |
| 136 | + |
| 137 | + private def latest = first1.latest |
| 138 | + |
| 139 | + private def addMore() = latest.addMore(more) |
| 140 | + |
| 141 | + private def page(absindex: Int) = { |
| 142 | + if (absindex < current.start) |
| 143 | + current = first1 |
| 144 | + while (absindex >= current.end && current.next != null) |
| 145 | + current = current.next |
| 146 | + while (absindex >= current.end && !current.isLast) { |
| 147 | + current = addMore() |
| 148 | + } |
| 149 | + current |
| 150 | + } |
| 151 | + |
| 152 | + /** The length of the paged sequence |
| 153 | + * @note Calling this method will force the entire sequence to be read. |
| 154 | + */ |
| 155 | + def length: Int = { |
| 156 | + while (!latest.isLast && latest.end < end) addMore() |
| 157 | + (latest.end min end) - start |
| 158 | + } |
| 159 | + |
| 160 | + /** The element at position `index`. |
| 161 | + */ |
| 162 | + def apply(index: Int) = |
| 163 | + if (isDefinedAt(index)) page(index + start)(index + start) |
| 164 | + else throw new IndexOutOfBoundsException(index.toString) |
| 165 | + |
| 166 | + /** Predicate method to check if an element is defined |
| 167 | + * at position `index` of the current sequence. |
| 168 | + * Unlike `length` this operation does not force reading |
| 169 | + * a lazy sequence to the end. |
| 170 | + */ |
| 171 | + override def isDefinedAt(index: Int) = |
| 172 | + index >= 0 && index < end - start && { |
| 173 | + val absidx = index + start |
| 174 | + absidx >= 0 && absidx < page(absidx).end |
| 175 | + } |
| 176 | + |
| 177 | + /** The subsequence from index `start` up to `end -1` if `end` |
| 178 | + * is lesser than the length of the current sequence and up to |
| 179 | + * length of the sequence otherwise. This is limited up to the length |
| 180 | + * of the current sequence if `end` is larger than its length. |
| 181 | + */ |
| 182 | + override def slice(_start: Int, _end: Int): PagedSeq[T] = { |
| 183 | + page(start) |
| 184 | + val s = start + _start |
| 185 | + val e = if (_end == UndeterminedEnd) _end else start + _end |
| 186 | + var f = first1 |
| 187 | + while (f.end <= s && !f.isLast) { |
| 188 | + if (f.next eq null) f = f.addMore(more) |
| 189 | + else f = f.next |
| 190 | + } |
| 191 | + // Warning -- not refining `more` means that slices can freely request and obtain |
| 192 | + // data outside of their slice. This is part of the design of PagedSeq |
| 193 | + // (to read pages!) but can be surprising. |
| 194 | + new PagedSeq(more, f, s, e) |
| 195 | + } |
| 196 | + |
| 197 | + /** The subsequence from index `start` up to |
| 198 | + * the length of the current sequence. |
| 199 | + */ |
| 200 | + def slice(start: Int): PagedSeq[T] = slice(start, UndeterminedEnd) |
| 201 | + |
| 202 | + /** Convert sequence to string */ |
| 203 | + override def toString = { |
| 204 | + val buf = new StringBuilder |
| 205 | + for (ch <- PagedSeq.this.iterator) buf append ch |
| 206 | + buf.toString |
| 207 | + } |
| 208 | +} |
| 209 | + |
| 210 | + |
| 211 | +/** Page containing up to PageSize characters of the input sequence. |
| 212 | + */ |
| 213 | +private class Page[T: ClassTag](val num: Int) { |
| 214 | + |
| 215 | + private final val PageSize = 4096 |
| 216 | + |
| 217 | + /** The next page in the sequence */ |
| 218 | + var next : Page[T] = null |
| 219 | + |
| 220 | + /** A later page in the sequence, serves a cache for pointing to last page */ |
| 221 | + var later : Page[T] = this |
| 222 | + |
| 223 | + /** The number of elements read into this page */ |
| 224 | + var filled: Int = 0 |
| 225 | + |
| 226 | + /** Set true if the current page is the last in the sequence or if |
| 227 | + * the `more` function returned -1 signalling end of input. */ |
| 228 | + var isLast: Boolean = false |
| 229 | + |
| 230 | + /** The element array */ |
| 231 | + final val data = new Array[T](PageSize) |
| 232 | + |
| 233 | + /** The index of the first element in this page relative to the whole sequence */ |
| 234 | + final def start = num * PageSize |
| 235 | + |
| 236 | + /** The index of the element following the last element in this page relative |
| 237 | + * to the whole sequence */ |
| 238 | + final def end = start + filled |
| 239 | + |
| 240 | + /** The last page as currently present in the sequence; This can change as more |
| 241 | + * elements get appended to the sequence. */ |
| 242 | + final def latest: Page[T] = { |
| 243 | + if (later.next != null) later = later.next.latest |
| 244 | + later |
| 245 | + } |
| 246 | + |
| 247 | + /** The element at the given sequence index. |
| 248 | + * That index is relative to the whole sequence, not the page. */ |
| 249 | + def apply(index: Int) = { |
| 250 | + if (index < start || index - start >= filled) throw new IndexOutOfBoundsException(index.toString) |
| 251 | + data(index - start) |
| 252 | + } |
| 253 | + |
| 254 | + /** Produces more elements by calling `more` and adds them on the current page, |
| 255 | + * or fills a subsequent page if current page is full. |
| 256 | + * @note If current page is full, it is the last one in the sequence. */ |
| 257 | + final def addMore(more: (Array[T], Int, Int) => Int): Page[T] = |
| 258 | + if (filled == PageSize) { |
| 259 | + next = new Page[T](num + 1) |
| 260 | + next.addMore(more) |
| 261 | + } else { |
| 262 | + val count = more(data, filled, PageSize - filled) |
| 263 | + if (count < 0) isLast = true |
| 264 | + else filled += count |
| 265 | + this |
| 266 | + } |
| 267 | +} |
0 commit comments