Home

Awesome

Immutable Ropes for Common Lisp

Have you ever wanted to make yet another Emacs clone? Well now you can use ropes to do it so you can tell everyone on Twitter and Hackernews that your editor is blazingly fast and super serious and uses fancy text structures.

None of the exported symbols mutate ropes, they return new ones. This lets us reuse as many leaves and branches as possible to reduce memory usage, as well as enable a functional style of programming.

Installation

With Ultralisp installed:

(ql:quickload :rope)

Usage

Convert a string to a rope with make-rope, and turn it back into a string with write-rope passing nil as the output.

You can also pass an input stream or pathname to make-rope to efficiently read files.

(let* ((rope (rope:make-rope "Immutable Ropes for Common Lisp"))
       (super (rope:insert-rope rope 10 "Super "))
       (superer (rope:kill-rope super 0 10)))
  (list
   (rope:write-rope rope nil)
   (rope:write-rope super nil)
   (rope:write-rope superer nil)))

Split a rope at an index:

(let ((rope (rope:make-rope "Immutable Super Ropes for Common Lisp")))
  (multiple-value-bind (ante post) (rope:split-rope rope 16)
    (list ante post)))

Here is what the split ropes look like graphed with :cl-dot. I decreased rope::*long-leaf* to 8 to better visualize such a small string.

Split Rope

Concatenate ropes together:

(let ((rope-a (rope:make-rope "Immutable Super Ropes"))
      (rope-b (rope:make-rope " for Common Lisp")))
  (rope:concat-rope rope-a rope-b))

Concatenated Rope

Kill a segment of a rope:

(let ((rope (rope:make-rope "Immutable Ropes for Common Lisp")))
  (rope:write-rope (rope:kill-rope rope 20 27) t))

Get chars or strings at a position:

(let ((rope (rope:make-rope "Immutable Ropes for Common Lisp")))
  (print (rope:index-rope rope 2))
  (print (rope:substr-rope rope 10 15)))

Performance

Time to insert is a good measure since it splits and concatenates. This graph demonstrates O(log(n)) performance:

Insert Benchmark

Inserting into a gigabyte rope takes approximately 26 microseconds on my i5-6200U.

Dev Utils

If you want to generate graphs as shown above, you will need to install :cl-dot (not in Quicklisp), and load :rope/dev. Then you can make a rope and run this:

(defparameter rope::*long-leaf* 24)
(defparameter rope::*short-leaf* 8)

(let ((rope (rope::make-rope rope/dev::*string*)))
  (multiple-value-bind (ante post) (rope::split-rope rope 30)
    (rope/dev:graph-ropes (list rope ante post))))

If you are using Sly in Emacs, it will automatically open up a PNG buffer.