NIALL Rebol

From www.rebol.org, by Allen Kamp:

(Back to the main NIALL page, then)

REBOL [
    Title:  "RNILL - REBOL Non Intelligent Language Learner"
    Author: "Allen Kamp"
    Email:  allenk@powerup.com.au
    Date:   7-Aug-1999
    File:  %rnill.r
    Version: 1.0.0  
    Purpose: {
        RNILL a REBOL implementation of a non intelligent language learner
        inspired by NIALL (Non Intelligent Amos Language Learner by
        Mathew Peck 1990).
    }
    Comment: {
    RNILL is a fun chatter-bot:
    Just type what ever comes into your head, RNILL learns sentence structure
    from what you type. RNILL starts off knowing nothing (unlike Melissa
    or Eliza), so at first he may just repeat your sentences.
    Type in jokes, stories, obscure comments, whatever you like, or use the
    read command to read in texts.
    But be warned sometimes RNILL's replies will be spookily accurate.
    }
    Category: [games]   
]

Comment {RNILL - REBOL Non Intelligent Language Learner

     I spent hours chatting to the original NIALL on my Amiga. The concept 
     of a language learner fascinated me, always wanted a version for my PC,
     so I've made my own. RNILL is my implementation based on my memory and
     understanding of the concepts in NIALL. (It is nearly 9 years since I've
     seen NIALL or its AMOS source). Please don't write me and tell me it is
     different, I didn't set out to write a clone.
        
    How RNILL works...
    How RNILL Learns:
    RNILL Analyses each sentence in a 2 pass process
    Step 1 - Build Dictionary - Add new words or increment count for known
    words
    Step 2 - Build Relationships - Add new relations or increment count for
    known relations 
                
    How RNILL Replies:
    Begining with the start of sentence marker (always word 1 "-|"). RNILL
    chooses the next word from the relations list. (words that RNILL has learnt
    can follow the current word) The choice is weighted by how common that link
    has been. This recurses until an end of sentence is chosen ("-1")

    RNILL Tips.
    Multiple sentences may be typed in at a time. Don't worry about case.
    Punctuation - !.? are recognised for end of sentence punctuation. They
    are only needed to separate multiple sentences. Commas are stripped from
    sentences.
    
    RNILL stores its dictionary as RNILLData.txt, this will be created if it
    doesn't exist. You can fix typos and spelling errors in this file, however
    do not delete or add an entry, as the dictionary is a relational database. 

    RNILL Commands
     QUIT - RNILL saves on exit when you use the RNILL command Quit.
     ? - Shows current Dictionary Stats.
     READ - To read a text file for RNILL to learn from  

    To save duplication RNILL generally saves words as lowercase.
    When generating replies capitalises the first letter of a sentence.

    Preformated-Words
    Contains a list words that have a fixed case. 
    eg. "I" "I'd" in the English Language.
    RNILL will not make these lowercase.
}

;---Constants and Switches
begin-sentence: "|- "
data-file: %RNILLData.txt

;---Turn on or of welcome text and getting user name.
welcome-on: true 

Preformatted-Words: make hash! [
    "I" "I'll" "I'd" "I'm" "RNILL" "REBOL" "PC" "UNIX" "Amiga" "HTML"
    "Australia" "UK" "USA" "Japan"
]

;---End Constants and Switches


;---Init Tables
Dictionary: make hash! 1000
Stats: make block! 1000 
Relations: make block! 1000 

;---Load Tables
if exists? %RNILLData.txt [
    last-chat: modified? data-file
    Data: load %RNILLData.txt
    Dictionary: to-hash copy Data/1
    Stats: copy Data/2
    Relations: copy Data/3
    unset 'data
]

welcome-user: func [
    /local sentence
][
    ;---Welcoming sentences, also fed into RNILL learning process.
    user: ask {Hello my name RNILL, what is your name? }
    append preformatted-words user ; retain name formatting. 
    user-prompt: rejoin [user " > "] 
    sentence: rejoin ["Hello " user]
    print ["RNILL >" sentence]          
    ;---may as well learn this sentence too
    RNILL-learns sentence 

    either not exists? data-file [
        sentence: {Please tell me something about yourself}
    ][
        sentence: {It is good to talk to someone again}
    ]  

    print ["RNILL >" sentence]
    ;---may as well learn this sentence too
    RNILL-learns sentence ]

input-loop: func [] [

    while [true] [
            sentence: ask user-prompt
        switch/default sentence [
            "Quit" [
                ;Save RNILL Data on exit
                print [newline "Goodbye" User 
            {it has been nice chatting with you.}]
                RNILL-info/stats
                data: []
                insert/only tail data to-block dictionary
                insert/only tail data stats
                insert/only tail data relations 
                save data-file data 
                break ;quit out of loop/end program
            ]
            "?" [RNILL-info]
            "Read" [
                file: to-file ask {> Read Filename ? }
                try [either exists? file [sentence: read file]
            [Print "Read File Error..." sentence: ""]]
                if (sentence <> "") [
                Prin {Reading File...}
            RNILL-Learns sentence
            Print "Done"
                ] 
            ]
        ][
        ;---Default evaluate and build dictionary
            RNILL-Learns sentence 
            prin "RNILL > "
            RNILL-replies 1
            prin newline
        ]
    ]
    exit
]

;---Process Sentences

RNILL-Learns: func [
    paragraph [string!] {Sentence string to learn from}
    /local sentence words
][
    ;---Prepare Block of Ordered Words 
    ;---Paragraph -> Block of Sentences -> Block of words
    foreach sentence parse/all paragraph {.?!}[
        trim/with sentence "," ;remove comma
        trim sentence ; remove beginning or trailing spaces
        ;---Add Sentence Begin Marker
        sentence: join begin-sentence [sentence]
        words: parse sentence none
        ;--- Assess Word block
        build-dictionary words
        build-relationships words
    ]
    exit
]


build-dictionary: func [
sentence-blk
/local word-id counter found

] [

    foreach word sentence-blk [
        either found? found: find Dictionary word [
            ;---Update Known word, increment # times used 
            word-id: index? found
            counter: pick Stats word-id
            poke Stats word-id (counter + 1)
        ][
            ;---Add New Word
            append Dictionary format-word word
            append Stats 1 ; Word Usage Count
            ;---Insert empty series, to be 
        ;---filled later by build-relations
            insert/only tail Relations make block! []
        ] 
    ]
    exit
]


build-relationships: func [sentence][

    repeat i count: length? sentence [
        preword: pick sentence i
        postword: pick sentence (i + 1)
       ;---Use index as ID
        preword-ID: index? found: find dictionary preword
      ;---find relations entry block for preword
       relation-blk: pick relations preword-ID 

        either postword <> none [
        ;---Get PostWord-ID
            postword-ID: index? found: find dictionary postword 
        ][
            postword-ID: -1  ; End of Sentence ID
        ] 

     either found? relation-stat: select relation-blk postword-ID [
            ;---Increment Relation count 
            count: relation-stat/1
            change relation-stat (count + 1)
        ][
            ;---Add New Relation Entry
            append relation-blk Postword-ID ; relation entry id
            ;---Set Relation Count to 1
            insert/only tail relation-blk make block! [1] 
        ]
    ] 
    Exit
]

format-word: func [string [any-string!]] [
    /local str: to-string ""

    either found? str: find preformatted-words string [
        return (string: copy first str)
    ] [
        return (lowercase copy string)  
    ]
    exit
]
;---End Process Sentences


;--RNILL Reply Functions

RNILL-replies: func[
    {Creates and prints RNILL reply sentence}
    word-Id [integer!]
    /local word-used-count roll relation-blk loop-max
    weighting chosen-word i word-str relation-count
][
    word-used-count: pick stats word-id
    roll: random word-used-count
    relation-blk: pick relations word-id
    weighting: 0
    chosen-word: -1  ; End of Sentence as default.
    loop-max: length? relation-blk

    for i 1 loop-max 2 [
        relation-count: first pick relation-blk (i + 1)
        weighting: weighting + relation-count
        if (weighting >= roll) [
        chosen-word: pick relation-blk i
        break   
      ]
    ]
    if chosen-word <> -1 [
        word-str: pick dictionary chosen-word
        ;---Capitalise first-letter-first-word. 
        if word-id = 1 [word-str: uppercase/part copy word-str 1]
        prin join word-str [" "]
        RNILL-Replies chosen-word  ;recurse
    ]
    exit
]
;---End RNILL Repy Functions


;--RNILL Stat Functions 

count-words: func[
    {Returns the total number of words in RNILL's dictionary}
    /local count
][
    ;---Remove start sentence maker from count
    count: (length? stats) - 1
    if count < 0 [count: 0]
    return count
]

count-sentences: func[
    {Returns the total number of sentences RNILL has analysed}
    /local count
][
    count: pick stats 1
    if count = none [count: 0]
    return count
]

count-relationships: func[
    {Returns the total number of word relationships in RNILL's dictionary}
    /local count stat
][
    count: 0
    foreach stat stats [count: count + stat]
    return count
]

RNILL-info: func [{Shows RNILL Info}
/stats {Shows only Stats part of the info}
][
    ;---Header
    if not stats [
        
        ;prin "^(page)"
        print ""
        print {+------------------------------------------------------+}
        print {|  RNILL - The REBOL Non-Intelligent Language Learner  |}
        print {+------------------------------------------------------+}
    ]   
    
    ;---Stats
    print ""
    print {======================Statistics=======================+}
    print [{                Sentences |} count-sentences]
    print [{                    Words |} count-words]
    print [{            Relationships |} count-relationships]
    print {+------------------------------------------------------+}
    print ""
    
    ;---Footer
    if not stats [
        print {+------------------------------------------------------+}
        print {| To Exit: enter Quit     |     To View Stats: enter ? |}
        print {|                         |                            |}
        print {| To learn a text file: enter Read (and follow prompt) |}
        print {+------------------------------------------------------+}
    ]
]
;--End RNILL stat Functions

;---Begin---
random/seed now
prin "^(page)"
RNILL-info
either welcome-on [welcome-user] [user-prompt: {User > } user: ""]
input-loop