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

polynote.data.Rope.scala Maven / Gradle / Ivy

The newest version!

/* This code was taken from Tekstlib - https://github.com/gnieh/tekstlib

 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 * http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */

package polynote.data

import io.circe.{Decoder, Encoder}
import polynote.messages.{ContentEdit, ContentEdits, ShortString}
import scodec.Codec

import scala.collection.immutable.Queue
import scala.collection.mutable.{ArrayBuffer, ListBuffer}

/** Rope data structure as a binary tree of character arrays.
  *  First described in `Ropes: an Alternative to Strings`
  *  by Hans-J. Boehm, Russ Atkinson and Michael Plass.
  *
  *  @author Lucas Satabin
  */
sealed abstract class Rope {

  def size: Int

  def depth: Int

  def charAt(idx: Int): Char

  def splitAt(idx: Int): (Rope, Rope)

  def withEdit(edit: ContentEdit): Rope = edit.applyTo(this)

  def withEdits(edits: ContentEdits): Rope = edits.edits.foldLeft(this)(_ withEdit _)

  def insertAt(idx: Int, that: Rope): Rope = {
    val (r1, r2) = splitAt(idx)
    r1 + that + r2
  }

  def delete(start: Int, length: Int): Rope = {
    val (r1, r2) = splitAt(start)
    val (_, r3) = r2.splitAt(length)
    r1 + r3
  }

  def +(that: Rope): Rope

  def +(s: String): Rope =
    this + RopeLeaf(s.toArray)

  def +(a: Array[Char]): Rope =
    this + RopeLeaf(a)

  def +(c: Char): Rope =
    this + RopeLeaf(Array(c))

  // should this match String's substring(beginIndex, endIndex) API?
  def substring(start: Int, length: Int): Rope = {
    val (_, r1) = splitAt(start)
    val (r2, _) = r1.splitAt(length)
    r2
  }

  def substring(start: Int): Rope = {
    val (_, r1) = splitAt(start)
    r1
  }

  def toString: String

  override def equals(obj: Any): Boolean = obj match {
    case r: Rope => this.size == r.size && this.toString == r.toString
    case s: String => s == this.toString
    case somethingElse => super.equals(somethingElse)
  }

  def map(f: Char => Char): Rope

  def flatMap(f: Char => Rope): Rope

  def filter(f: Char => Boolean): Rope

  def withFilter(f: Char => Boolean): Rope =
    filter(f)

  def foreach(f: Char => Unit): Unit

  def foldLeft[Acc](zero: Acc)(f: (Acc, Char) => Acc): Acc

  def foldRight[Acc](zero: Acc)(f: (Char, Acc) => Acc): Acc

  private[data] def isBalanced: Boolean

  private[data] def toList: List[Array[Char]]

}

object Rope {

  /** Creates a rope from a string */
  def apply(s: String): Rope =
    Rope(s.toArray)

  /** Creates a rope from a character array. */
  def apply(a: Array[Char]): Rope =
    if (a == null || a.isEmpty) {
      RopeEmpty
    } else if (a.length > thresh) {
      val (a1, a2) = a.splitAt(a.length / 2)
      Rope(a1) + Rope(a2)
    } else {
      RopeLeaf(a)
    }

  /** Creates a rope from a character. */
  def apply(c: Char): Rope =
    RopeLeaf(Array(c))

  private[data] def balance(r: Rope): Rope =
    if (r.isBalanced)
      r
    else
      fromList(r.toList)

  private def fromList(l: List[Array[Char]]): Rope =
    l match {
      case List(s1, s2) =>
        RopeConcat(RopeLeaf(s1), RopeLeaf(s2))
      case List(s) =>
        RopeLeaf(s)
      case Nil =>
        RopeEmpty
      case _ =>
        val (half1, half2) = l.splitAt(l.size / 2)
        RopeConcat(fromList(half1), fromList(half2))
    }

  implicit val encoder: Encoder[Rope] = Encoder.encodeString.contramap(_.toString)
  implicit val decoder: Decoder[Rope] = Decoder.decodeString.map(Rope.apply)
  implicit val codec: Codec[Rope] = scodec.codecs.implicits.implicitStringCodec.xmap(Rope.apply, _.toString)
  
  implicit def fromString(str: String): Rope = Rope(str)

  val thresh = 2048

}

private case object RopeEmpty extends Rope {

  def size: Int =
    0

  def depth: Int =
    0

  def charAt(idx: Int): Char =
    throw new StringIndexOutOfBoundsException(f"String index out of range: $idx")

  def splitAt(idx: Int): (Rope, Rope) =
    (RopeEmpty, RopeEmpty)

  def +(that: Rope): Rope =
    that

  override def toString: String =
    ""
  def map(f: Char => Char): Rope =
    this

  def flatMap(f: Char => Rope): Rope =
    this

  def filter(f: Char => Boolean): Rope =
    this

  def foreach(f: Char => Unit): Unit =
    ()

  def foldLeft[Acc](zero: Acc)(f: (Acc, Char) => Acc): Acc =
    zero

  def foldRight[Acc](zero: Acc)(f: (Char, Acc) => Acc): Acc =
    zero

  private[data] val isBalanced: Boolean =
    true

  private[data] def toList: List[Array[Char]] =
    Nil

}

private final case class RopeConcat(left: Rope, right: Rope) extends Rope {

  val size: Int =
    left.size + right.size

  val depth =
    1 + math.max(left.depth, right.depth)

  def charAt(idx: Int): Char =
    if (idx < 0 || idx >= size)
      throw new StringIndexOutOfBoundsException(f"String index out of range: $idx")
    else if (idx < left.size)
      left.charAt(idx)
    else
      right.charAt(idx - left.size)

  def splitAt(idx: Int): (Rope, Rope) =
    if (idx < 0) {
      (RopeEmpty, this)
    } else if (idx >= size) {
      (this, RopeEmpty)
    } else if (idx >= left.size) {
      val (r1, r2) = right.splitAt(idx - left.size)
      (left + r1, r2)
    } else {
      val (r1, r2) = left.splitAt(idx)
      (r1, r2 + right)
    }

  def +(that: Rope): Rope =
    (right, that) match {
      case (_, RopeEmpty) =>
        this
      case (RopeLeaf(rightValue), RopeLeaf(thatValue)) if rightValue.length + thatValue.length <= Rope.thresh =>
        Rope.balance(left + Rope(rightValue ++ thatValue))
      case _ =>
        Rope.balance(RopeConcat(this, that))
    }

  override def toString =
    left.toString + right.toString

  def map(f: Char => Char): Rope =
    left.map(f) + right.map(f)

  def flatMap(f: Char => Rope): Rope =
    left.flatMap(f) + right.flatMap(f)

  def filter(f: Char => Boolean): Rope =
    left.filter(f) + right.filter(f)

  def foreach(f: Char => Unit): Unit = {
    left.foreach(f)
    right.foreach(f)
  }

  def foldLeft[Acc](zero: Acc)(f: (Acc, Char) => Acc): Acc =
    right.foldLeft(left.foldLeft(zero)(f))(f)

  def foldRight[Acc](zero: Acc)(f: (Char, Acc) => Acc): Acc =
    left.foldRight(right.foldRight(zero)(f))(f)

  private[data] val isBalanced: Boolean =
    math.abs(left.depth - right.depth) < 4

  private[data] def toList: List[Array[Char]] =
    left.toList ++ right.toList

}

private final case class RopeLeaf(value: Array[Char]) extends Rope {

  val size =
    value.length

  val depth =
    0

  def charAt(idx: Int): Char =
    value(idx)

  def splitAt(idx: Int): (Rope, Rope) = {
    val (s1, s2) = value.splitAt(idx)
    (Rope(s1), Rope(s2))
  }

  def +(that: Rope): Rope =
    that match {
      case RopeEmpty =>
        this
      case RopeLeaf(thatValue) if this.value.length + thatValue.length <= Rope.thresh =>
        Rope(this.value ++ thatValue)
      case _ =>
        Rope.balance(RopeConcat(this, that))
    }

  override def toString =
    value.mkString

  def map(f: Char => Char): Rope =
    RopeLeaf(value.map(f))

  def flatMap(f: Char => Rope): Rope =
    value.foldLeft(Rope("")) { (acc, c) => acc + f(c) }

  def filter(f: Char => Boolean): Rope =
    Rope(value.filter(f))

  def foreach(f: Char => Unit): Unit =
    value.foreach(f)

  def foldLeft[Acc](zero: Acc)(f: (Acc, Char) => Acc): Acc =
    value.foldLeft(zero)(f)

  def foldRight[Acc](zero: Acc)(f: (Char, Acc) => Acc): Acc =
    value.foldRight(zero)(f)

  private[data] val isBalanced: Boolean =
    true

  private[data] def toList: List[Array[Char]] =
    List(value)

}




© 2015 - 2024 Weber Informatics LLC | Privacy Policy