git.fiddlerwoaroof.com
ot-edit.lisp
44e4061c
 (defpackage :fwoar.lisp-sandbox.ot-edit
   (:use :cl )
   (:export
    #:edit
    #:insert
    #:editable-string
    #:apply-edits))
 (in-package :fwoar.lisp-sandbox.ot-edit)
 
 (defgeneric transform-for-op (op))
 (defgeneric edit (base operation))
 (defgeneric apply-edits (base operations))
 
76b82aba
 (fw.lu:defclass+ editable-string ()
   ((%string :initarg :string :accessor string-to-edit)
    (%transform :accessor transform :initform 'identity)))
44e4061c
 
4898e7f0
 (defclass op ()
   ((%epoch :reader epoch :initform (let ((c (load-time-value (vector 0)))) (incf (elt c 0))))))
44e4061c
 
76b82aba
 (defmethod apply-edits ((base string) (operations sequence))
   (string-to-edit
    (reduce 'edit
            (sort operations '< :key 'epoch)
            :initial-value (editable-string base))))
 
cfdc28be
 (fw.lu:defclass+ insert (op)
44e4061c
   ((%point :initarg :point :accessor point)
    (%value :initarg :value :reader value)))
cfdc28be
 (defmethod print-object ((o insert) s)
   (print-unreadable-object (o s :type t :identity t)
     (format s "~d/~d" (point o) (epoch o))))
44e4061c
 (defmethod transform-for-op ((op insert))
   (let ((point (point op))
         (insert-length (length (value op))))
     (lambda (new-point)
       (if (< new-point point)
           new-point
           (+ new-point insert-length)))))
 (defun do-insert (base new point)
   (let ((begin (subseq base 0 point))
         (end (subseq base point)))
     (concatenate 'string begin new end)))
 (defmethod edit ((base editable-string) (operation insert))
f1131399
   (let* ((current-transform (transform base))
          (op-point (point operation))
          (string-to-edit (string-to-edit base))
          (transformed-op (insert (funcall current-transform op-point)
                                  (value operation))))
     (setf (string-to-edit base) (do-insert string-to-edit (value transformed-op) (point transformed-op))
           (transform base) (alexandria:compose (transform-for-op transformed-op) current-transform))
44e4061c
     base))
d71eb841
 
 (fw.lu:defclass+ replace-char (op)
   ((%point :initarg :point :accessor point)
    (%value :initarg :value :reader value)))
 (defmethod print-object ((o replace-char) s)
   (print-unreadable-object (o s :type t :identity t)
     (format s "~d/~d" (point o) (epoch o))))
 (defmethod transform-for-op ((op replace-char))
   (let ((point (point op))
         (insert-length (length (value op))))
     (lambda (new-point)
       (if (<= new-point point)
           new-point
           (+ new-point -1 insert-length)))))
44e4061c
 (defun do-replace-char (base new point)
   (let ((begin (subseq base 0 point))
         (end (subseq base (1+ point))))
     (concatenate 'string begin new end)))
 (defmethod edit ((base editable-string) (operation replace-char))
f1131399
   (let* ((current-transform (transform base))
          (op-point (point operation))
          (string-to-edit (string-to-edit base))
          (transformed-op (replace-char (funcall current-transform op-point)
                                        (value operation))))
     (setf (string-to-edit base) (do-replace-char string-to-edit (value transformed-op) (point transformed-op))
           (transform base) (alexandria:compose (transform-for-op transformed-op) current-transform))
44e4061c
     base))
 
 
 (defun sample ()
b7f9ee2a
   (assert (equal (apply-edits "ac" (list (insert 0 "z")
                                          (insert 1 "b")
                                          (insert 2 "d")))
f1131399
                  "zabcd"))
   (assert
    (let ((ops (list (insert 1 "fff")
                     (insert 1 "ggg")
                     (insert 1 "hhh"))))
      (list (equal (apply-edits #1="abcd" ops)
                   (apply-edits #1# (reverse ops)))
            (equal (apply-edits #1# (reverse ops))
                   (apply-edits #1# (serapeum:reshuffle ops)))
            (equal (apply-edits #1# (reverse ops))
                   "afffggghhhbcd"))))
   (let ((ops (lambda ()
                (list (insert 2 "fff")
                      (insert 1 "bbb")
                      (insert 1 "ddd")))))
     (:printv (funcall ops))
     (assert (equal (:printv (apply-edits "ac" (:printv (serapeum:reshuffle (funcall ops)))))
                    (:printv (apply-edits "ac" (:printv (serapeum:reshuffle (funcall ops)))))))
     (assert (equal (:printv (apply-edits "ace" (serapeum:reshuffle (funcall ops))))
                    (:printv (apply-edits "ace" (serapeum:reshuffle (funcall ops))))))))