#!/bin/bash
# Read text from stdin, count words, print results to stdout
# sorted by count, highest count first.
# This bash wrapper is required because osascript closes stdin.
osascript <<EOF
        -- If there ever was a reason to run cat without arguments,
        -- this is it.
        set all_words to every word in "$(cat)"
        
        -- We count words in a list of records that consist of a word and a count.
        -- This is bound to be slow, but there is no map type.
        -- For real text processing out of AppleScript, you would
        -- of course just 'do shell script "perl -e ..."' :-)
        set word_count to {}
        repeat with one_word in all_words
                set one_word to text of one_word
                set found_count to false
                -- Look for an existing count, tirelessly examining all
                -- previously found words over and over again.
                repeat with existing_count in word_count
                        if the_word of existing_count equals one_word
                                set found_count to true
                                set the_count of existing_count to the_count of existing_count + 1
                        end if
                end repeat
                if not found_count
                        -- If it's a new word, create a new record for it
                        -- and initialise the count to 1.
                        set new_count to {the_word: one_word, the_count: 1}
                        set word_count to word_count & { new_count }
                end if
        end repeat

        -- AppleScript has no built-in sorting method. This is a straight-forward
        -- implementation of a popular sorting algorithm. AppleScript benefits
        -- greatly from some trivial optimisation, e.g. switching to a less
        -- expensive algorithm for small partitions, but this is not an
        -- optimisation contest.
        -- This implementation sorts in descending order. Order can be switched
        -- by switching the "greater than" and "less than" conditions in the
        -- two inner loops. Or you could "reverse" the result.
        to quicksort_words(the_words, left_end, right_end)
                set {partition_left, partition_right} to {left_end, right_end}
                -- Use the middle item as a pivot, not going to be fancy here.
                set pivot to item ((left_end + right_end) / 2) of the_words
                repeat while partition_right greater than partition_left
                        repeat while the_count of item partition_left of the_words greater than the_count of pivot
                                set partition_left to partition_left + 1
                        end repeat
                        repeat while the_count of item partition_right of the_words less than the_count of pivot
                                set partition_right to partition_right - 1
                        end repeat
                        if partition_left less than or equal to partition_right
                                -- Swap the words at the current partition boundary.
                                tell the_words to set {item partition_left, item partition_right} to {item partition_right, item partition_left}
                                set partition_left to partition_left + 1
                                set partition_right to partition_right - 1
                        end if
                end repeat
                if left_end less than partition_right
                        quicksort_words(the_words, left_end, partition_right)
                end if
                if right_end greater than partition_left
                        quicksort_words(the_words, partition_left, right_end)
                end if
        end quicksort_words
        
        quicksort_words(word_count, 1, count word_count)

        -- Build the output as one string.
        set output_text to ""
        repeat with the_count in word_count
                set output_text to output_text & the_word of the_count & ": " & the_count of the_count & "\n"
        end repeat

        -- osascript conveniently writes the last result to stdout.
        -- Stating output_text explicitly is not necessary as long
        -- as no other results are generated between the loop above
        -- and here.
        output_text
EOF
