# From: "Dat Nguyen" <thucdat@hotmail.com>
# Subject: Module rule-engine
# Date: Wed, 19 May 2004 01:16:37 -0400
#
# Animal Taxonomy is a favorite subject of specialized rule-based programming 
# languages. With some creative construction, C-Kermit can solve that problem 
# in the same manner. The cheetah.kr script exposes the same look and feel of 
# many rule-based programs. Many configuration problems in computer 
# administration and network should be solvable similarly, provided the rule 
# set can be identified.
#
# This demonstration comes in two modules: rule-engine and cheetah.
# To run the demonstration, tell Kermit to TAKE the cheetah module.
#
# Here is rule-engine:

define say {
    echo \%1
    return 1
}

define askContent {
    local \%v
    ask \%v {[\%1]: }
    _asg \%1 \%v
    return \%1
}

define askYesNo {
    local ans
    while true {
        ask ans {\%1 (y|n): }
        if \find(y,\m(ans)) return 1
        if \find(n,\m(ans)) return 0
    }
}

define askANumber {
    local \%n
    while true {
        echo Enter a Value
        ask \%n {[\%1]: }
        if numeric \%n return \%n
    }
}

define disableRule {
    local \%i
    for \%i 1 \v(argc)-1 1 {
        asg RuleDatabase \m(RuleDatabase)\&_[\%i]|
        incr RuleNumber
    }
}

define enableRule {
    local \%i
    for \%i 1 \v(argc)-1 1 {
        asg RuleDatabase \freplace(\m(RuleDatabase),|\&_[\%i]|,|)
        decr RuleNumber
    }
}

define availableRule {
    if \find(\%1,\m(RuleDatabase)) return 0
    disableRule {\%1}
    return 1
}

define eligibleRule {
    (! \find(\%1,\m(RuleDatabase)))
}

define clearRuleDatabase {
    asg RuleDatabase |
    (setq  RuleNumber 0)
}

define addFact {
    local \%i
    for \%i 1 \v(argc)-1 1 {
        if \find(\&_[\%i],\m(FactDatabase)) continue
        asg FactDatabase \m(FactDatabase)\&_[\%i]|
        incr FactNumber
    }
    # showFact
}

define addATrueFact {
# \%1 Name of the Boolean fact
# \%2 Value of Boolean fact
# Overwrite previous value
    local \%s \%w \%f
    asg \%s \fword(\%1,1)_\fword(\%1,2)
    asg \%w \freplace(\%1,{\fword(\%1,1) \fword(\%1,2) })
    _asg \%s |\%w|
    addFact {\%s}
    return 1
}

define addAlsoTrueFact {
    local \%s \%w \%f
    asg \%s \fword(\%1,1)_\fword(\%1,2)_also
    asg \%w \freplace(\%1,{\fword(\%1,1) \fword(\%1,2) })
    _asg \%s \m(\%s)|\%w|
    addFact {\%s}
    return 1
}

define addTrueFact {
# add and initialize with True
    _asg \%1 1
    addFact {\%1}
}

define addFalseFact {
    _asg \%1 0
    # add and initialize with False
    addFact {\%1}
}

define addValueFact {
    if \find(\%1,\m(FactDatabase)) return \m(\%1)
    _asg \%1 \fsexpr(askANumber '(\%1))
    addFact {\%1}
    return \m(\%1)
}

define addcontentFact {
    if \find(\%1,\m(FactDatabase)) return \m(\%1)
    _asg \%1 \fsexpr(askContent '(\%1))
    addFact {\%1}
    return \m(\%1)
}

define removeFact {
    local \%i
    for \%i 1 \v(argc)-1 1 {
        asg FactDatabase \freplace(\m(FactDatabase),|\&_[\%i]|,|)
        decr FactNumber
    }
#   showFact
}

define isItTrueThat {
# Use for fact that can have only one of two possible value.
# a buble can be either ON of OFF
    local \%s \%w \%f
    asg \%s \fword(\%1,1)_\fword(\%1,2)
    asg \%w \freplace(\%1,{\fword(\%1,1) \fword(\%1,2) })
    asg \%f \find(\%s,\m(FactDatabase))
    if \%f {
        # term in factdatabase
        if \find(|\%w|,\m(\%s)) return 1
        return 0
    } else {
        # term not in factdatabase
        local \%a
        asg \%a \fsexpr(askYesNo '(\%1))
        if \%a {
            _asg \%s |\%w|
            # save fact only when not in database before
            if not \%f addFact {\%s}
        }
        return \%a
    }
}

define isTrueFact {
# This responds with the content of the fact (True or False)
    if \find(\%1,\m(FactDatabase)) return \m(\%1)
    _asg \%1 \fsexpr(askYesNo '(\%1))
    addFact {\%1}
    return \m(\%1)
}

define gotFact {
# This gives user a chance to create a state fact with y(yes) or n(no)
    if \find(\%1,\m(FactDatabase)) return 1
    echo \%1
    local \%a
    asg \%a \fexec(askYesNo)
    if \%a {	# yes, create fact
        addFact {\%1}
    }
    return \%a
}

define hasFact {
# This does not expect user to respond
    (\find(\%1,\m(FactDatabase)))
}

define anyFact {
    (FactNumber)
}

define removeAllFact {
    asg FactDatabase |
    (setq FactNumber 0)
}

define showFact {
    show mac FactDatabase
    show mac FactNumber
}

define addGoal {
    local \%i
    for \%i 1 \v(argc)-1 1 {
        asg GoalDatabase \m(GoalDatabase)\&_[\%i]|
        incr GoalNumber
    }
#   showGoal
}

define removeGoal {
    local \%i
    for \%i 1 \v(argc)-1 1 {
        asg GoalDatabase \freplace(\m(GoalDatabase),|\&_[\%i]|,|)
        decr GoalNumber
    }
#   showGoal
}

define allGoalDone {
    (! GoalNumber)
}

define hasGoal {
    (\find(\%1,\m(GoalDatabase)))
}

define removeAllGoal {
    asg GoalDatabase |
    (setq GoalNumber 0)
}

define showGoal {
    show mac GoalDatabase
    show mac GoalNumber
}

define fireRule {
    local \&e[] \%n \%i \%f
    asg \%n \fsplit(\%1,&e,{ })
    while true {
        for \%i 1 \%n 1 {
            if define \m(\&e[\%i]) {
                asg \%f \fexec(\&e[\%i])
                # if rule was fired, reevaluate all rules 
                if > \%f 0 {
                    asg firedRules \m(firedRules)|\&e[\%i]
                    echo \&e[\%i] was fired
                    if = 2 \%f END
                    break
                }
            }
        }
        if not \%f break			# no rule was fired
        if = 1 \fexec(allGoalDone) break	# all goals achieved
    }
    echo
    echo {That's all there is}
    echo
}

define rule_sample {
    (if (hasGoal 'Goal_xxx)
      (if (hasFact 'Fact_yyy)
        (if (not (hasGoal 'Goal_zzz))
          (.
           (removeGoal 'Goal_xxx)	# Goal achieved
           (removeFact 'Fact_yyy)       # Fact changed
           (addGoal 'Fact_www)          # New goal
           (addFact 'Fact_yyy)          # New fact
           (1)
           )
         (0)
         )
       (0)
       )
     (0)
     )
}
define mutual_exclude {
    local \%i \%s
    asg \%s +
    for \%i 1 \v(argc)-1 1 {
        asg \%s \%s\&_[\%i]+
    }
    _asg \v(macro)_term \m(\v(macro)_term)|\%s
}

define addThisFact {
    asg allTheFacts \m(allTheFacts)|\%1
    registerThisFact {\%1} 1 0
}

define excludeThisFact {
    asg allTheFacts \freplace(\m(allTheFacts),|\%1|,|)
    registerThisFact {\%1} 0 1
}

define showAllFacts {
    local \%i \&w[]
    for \%i 1 \fsplit(\m(allTheFacts),&w,|) 1 {
        echo \&w[\%i]
    }
}

define clearAllFacts {
    asg allTheFacts 
}

define registerThisFact {
    local \%i \%k \%n \&w[] \%f
    asg \%n \fsplit(\m(mutual_exclude_term),&w,|)
    if \%n {
        local \%m \&e[]
        # There is mutual exclusive fact
        asg \%f \farraylook(^+\%1+*,&w)
        if > \%f 0 {
            asg \%m \fsplit(\&w[\%f],&e,{+})
            # This is a mutual exclusive fact
            if \%2 {
                # This is a true fact, turn off all others
                for \%i 1 \%m 1 {
                    _asg \&e[\%i] \%3
                }				
            } else {
                # This is a false fact, turn off only for boolean fact
                if = 2 \%m {
                    # This is a boolean fact
                    for \%i 1 \%m 1 {
                        _asg \&e[\%i] \%3
                    }				
                }
            }
        }
    }
    _asg \%1 \%2
}

define consultUser {
    asg consult_user 1
}

define donotConsultUser {
    asg consult_user 0
}
donotConsultUser

define isThisFactTrue {
    if define \m(\%1) return \m(\%1)
    local \%a
    if == \m(consult_user) 1 {
        asg \%a \fsexpr(askYesNo '(\%1))
    } else {
        asg \%a 0
    }
    if \find(+\%1+,\m(mutual_exclude_term)) {
        # term with mutual exclusion
        if = 1 \%a {
            addThisFact {\%1}
        } else {
            excludeThisFact {\%1}
        }
    } else {
        # term withOUT mutual exclusion
        _asg \%1 \%a
    }
    return \%a
}

# \farraylook(oofa*,&a)
