Rebol [
    Title: "SimpleDiff"
    Date: 25-Aug-2016
    Author: "Christopher Ross-Gill"
    Type: 'module
    Home: https://github.com/paulgb/simplediff
    Name: 'diff-mod
    Exports: [diff]
    Version: 1.1.0
    Comment: {
        Based on Simple Diff for Python, CoffeeScript v0.1
        (C) Paul Butler 2008 <http://www.paulbutler.org/>
    }
    History: [
        22-May-2014 1.0.0 "Original Version"
        25-Aug-2016 1.1.0 "Tweaked to be compatible with Ren/C and Red"
    ]
    Usage: [
        probe diff probe [a b c d]   probe [b c d e]
        probe diff probe [a b c d]   probe [e f g h d]
        probe diff probe [a b c d]   probe [e f g h]
        probe diff probe [A B c D]   probe [a b c d]
        probe diff probe ["A" "B" "c" "D"]   probe ["a" "b" "c" "d"]
        probe diff probe [a b b b c] probe [a b b b b c]
        probe diff
            probe parse "you might say that, I couldn't possibly comment" none
            probe parse "You may wish to say that, couldn't possibly comment either way." none
    ]
]

diff: func [
    {
        Find the differences between two blocks. Returns a block of pairs, where the first value
        is in [+ - =] and represents an insertion, deletion, or no change for that list.
        The second value of the pair is the element.
    }
    before [block! string!] after [block! string!]
    /local items-before starts-before starts-after run this-run test tests limit
][
    assert [equal? type? before type? after]

    run: 0

    ; Build a block with elements from 'before as keys, and
    ; each position starting with each element as values.
    items-before: copy []
    forall before [
        append/only any [
            select/case items-before first before
            last repend items-before [first before copy []]
        ] before
    ]

    ; Find the largest subseries common to before and after
    forall after [
        if tests: select/case items-before first after [
            limit: length? after

            foreach test tests [
                repeat offset min limit length? test [
                    this-run: :offset
                    unless test/:offset == after/:offset [
                        this-run: offset - 1
                        break
                    ]
                ]

                if this-run > run [
                    run: :this-run
                    starts-before: :test
                    starts-after: :after
                ]
            ]
        ]
    ]

    collect [
        either zero? run [
            ; If no common subseries is found, assume that an
            ; insert and delete has taken place

            unless tail? before [keep reduce ['- before]]
            unless tail? after [keep reduce ['+ after]]
        ][
            ; Otherwise he common subseries is considered to have no change, and we
            ; recurse on the text before and after the substring

            keep diff copy/part before starts-before copy/part after starts-after
            keep reduce ['= copy/part starts-after run]
            keep diff copy skip starts-before run copy skip starts-after run
        ]
    ]
]