All Downloads are FREE. Search and download functionalities are using the official Maven repository.

net.tixxit.delimited.parser.Input.scala Maven / Gradle / Ivy

The newest version!
package net.tixxit.delimited
package parser

/**
 * An immutable window into a stream of character data. The size of the window
 * is determined by 2 methods: `marked(pos)` and `append(chunk)`, which shrink
 * and expand the window respectively. All positions used with `Input` are
 * always relative to the entire stream, though only positions within the
 * current window are valid at any given point in time.
 *
 * The method `marked` returns a new `Input` which may drop all data before the
 * marked position. Accessing data before the marked position in the returned
 * `Input` is undefined and should be expected to fail in hilarious ways.
 *
 * The method `append` is used to append new chunks of data from the stream
 * onto the input. The `Input` returned by `append` will have all its data
 * up-to `mark` removed.
 *
 * @param offset the offset, in chars, of the start of `data` in the stream
 * @param data   the currently readable window into the stream
 * @param isLast true if this window covers the end of the stream
 * @param mark   the position in the stream we are able to truncate data to
 */
final class Input private (
  val offset: Long,
  val data: String,
  val isLast: Boolean,
  val mark: Long
) {
  private def check(i: Long): Int = if ((i < offset) || (i > (offset + data.length))) {
    throw new IndexOutOfBoundsException(i.toString)
  } else {
    (i - offset).toInt
  }

  /**
   * Returns the character data between `mark` and `limit`. This is equivalent
   * to calling `input.substring(input.mark, input.limit)`.
   */
  def window: String = substring(math.min(limit, mark), limit)

  /**
   * Returns the character at the given position. This method will do some
   * aggressive bounds checking.
   */
  def charAt(i: Long): Char = data.charAt(check(i))

  /**
   * Returns the index of the first position that cannot be read. If `isLast`
   * is `true`, then this is the length of the stream.
   */
  def limit: Long = offset + data.length

  /**
   * Returns the substring between `from` (inclusive) and `until` (exclusive).
   * It is expected that `mark <= from <= until <= limit`.
   */
  def substring(from: Long, until: Long): String =
    data.substring(check(from), check(until))

  /**
   * Returns an `Input` whose `mark` is at the given position.
   */
  def marked(pos: Long): Input = {
    if (pos < mark) {
      throw new IllegalArgumentException("mark cannot be smaller than current mark")
    }
    new Input(offset, data, isLast, pos)
  }

  private def trim: Input = {
    val next = math.min(mark - offset, data.length.toLong).toInt
    val tail = data.substring(next)
    val offset0 = offset + next
    new Input(offset0, tail, isLast, mark)
  }

  /**
   * Returns an `Input` with the chunk of data appended to the currently
   * readable window. If `last` is true, then the returned `Input`'s `isLast`
   * method returns `true`. Any data available in the current `Input`, but
   * prior to this `Input`'s `mark` will not be available in the returned
   * `Input`.
   */
  def append(chunk: String, last: Boolean = false): Input =
    if (mark > offset && data.length > 0) trim.append(chunk, last)
    else if (chunk.isEmpty) new Input(offset, data, last, mark)
    else new Input(offset, data + chunk, last, mark)

  /**
   * Returns a copy of this `Input` where `isLast` is true.
   */
  def finished: Input = new Input(offset, data, true, mark)
}

object Input {
  def init(str: String): Input =
    new Input(0, str, false, 0)

  def last(str: String): Input =
    new Input(0, str, true, 0)
}




© 2015 - 2025 Weber Informatics LLC | Privacy Policy