« Games from the days of 1200 baud modems | Main | What is the What? »

October 17, 2007

Eliza in Apex Code

Every time I do a new project I like to rebuild Eliza in a programming language. Here is Eliza with a Visual Force page and Apex code as the underlying implementation. It doesn't support a lot of different sentence types but there is enough to work now.
The display is done as a Visual Force Page: <apex:page controller=\"ElizaController\" showHeader=\"false\">

<p>Hello Welcome to the Apex Question and Answer Page </p>

<apex:form id=\"questionForm\">

<apex:inputText id=\"questionText\" value=\"{!QuestionText}\"></apex:inputText>

<!-- apex:commandButton value=\"Click Here\"><apex:param name=\"question\" value=\"{!QuestionText}\"/></apex:commandButton -->

<apex:commandButton value=\"Click Here\"></apex:commandButton>

</apex:form>

<p>Your Question: {!questionText} </p><b> Your Answer: {!answer}</b></p>

</apex:page>

With a custom controller:

public class ElizaController {

    private String QuestionText;

    public void setQuestionText(String val) {
        questionText = val;
    }

    public String getQuestionText() {
      return questionText;
    }


   public String getAnswer() { 
      return Eliza.converse(questionText);
   }
}

Most of the work is done in the Eliza Util class:

public class ElizaUtil {

    private Map variables = new Map();
    private static String[] wwords = new String[]{'when','why','where','how'};

    public String getValue(String var) {
        return variables.get(var);
    }

    public void setValue(String var,String val) {
        variables.put(var,val);
    }

    public static String wWord() {
       Integer value = Math.round(Math.random()*3.0);
       return wwords[value];

    }

    public boolean atom(String s) {
       if (s == null) return true;
       return s.split(' ',2).size() == 1;
    }

    public String car(String s) {
       if (s == null) return null;
       String[] words = s.split(' ',2);
       if (words.size() == 0) return null;
       return words[0];
    }

    public String cdr(String s) {
       if (s == null) return null;
       String[]words = s.split(' ',30);
       if (words.size() < 2) return null;
       String result = words[1];
       for(Integer i=2;i < words.size();i++){ 
          result = result + ' ' + words[i];
       } 
       return result;
    }

    private static String variableName(String s) {
       if (s.length() < 2) return 'default';
       else return s.substring(1,2);
    }

    public Boolean match(String p, String s) {
        variables.clear();
        return submatch(p,s);
    } 

    public Boolean submatch (String p, String s) {
       if (p == null) { 
           return (s == null);
       } else if (s != null && (car(p) == car (s)) && submatch(cdr(p),cdr(s))) { 
           return true;
       } else if (s != null && car(p).startsWith('?')) {
           if (match(cdr(p),cdr(s))) {
               setValue(variableName(car(p)),car(s));
               return true;
           }
           else return false;
       } else if (s != null && car(p).startsWith('*')) {
           if (s != null && submatch(cdr(p),cdr(s))) {
               setValue(variableName(car(p)),car(s));
               return true;
           } else if (match(cdr(p),s)) {
               setValue(variableName(car(p)),null);
               return true;
           } else if (s!=null && submatch(p,cdr(s))) {
               String currentValue = getValue(variableName(car(p)));
               if (currentValue == null) currentValue ='';
               setValue(variableName(car(p)),car(s)+' '+currentValue);
               return true;
           }
           return false;
       }
       return false;
      
    }

   static testMethod void testMatchPositive() {
       ElizaUtil eu = new ElizaUtil();
       System.assertEquals(eu.match('Hello','Hello'),true);
       System.assertEquals(eu.match('Hello to you','Hello to you'),true); 
       System.assertEquals(eu.match('Another test','Another test'),true);
       System.assertEquals(eu.match(null,null),true);
    } 

    static testMethod void testMatchNegative() {
       ElizaUtil eu = new ElizaUtil();

       System.assertEquals(eu.match('Hello to you','Hello to me'),false);
       System.assertEquals(eu.match('a',null),false);
       System.assertEquals(eu.match(null,'a'),false);
       System.assertEquals(eu.match('me','you'),false);
    }

    static testMethod void testMatchAdvanced() {
       ElizaUtil eu = new ElizaUtil();
       System.assertEquals(eu.match('*X to you','Hello to me and to you'),true);
       System.debug(eu.getValue('X'));
       System.assertEquals(eu.getValue('X'),'Hello to me and');
       System.assertEquals(eu.match('?X to you','Hello to you'),true);
       System.assertEquals(eu.getValue('X'),'Hello');
       System.debug(eu.getValue('X'));

       System.assertEquals(eu.match('Hello to ?X *Y','Hello to Chris and friends'),true);
       System.debug('X->'+eu.getValue('X')+' Y->'+eu.getValue('Y'));
       System.assertEquals(eu.getValue('X'),'Chris');
       System.assertEquals(eu.getValue('Y'),'and friends');
       System.assertEquals(eu.match('*X to you','Hello to me'),false);
       System.assertEquals(eu.match('?X to you','Hello to me'),false);  
    }

    static testMethod void testCarCdr() {
       ElizaUtil eu = new ElizaUtil();
       System.assertEquals(eu.car('Hello'),'Hello');
       System.assertEquals(eu.car('Hello to you'),'Hello');
       System.assertEquals(eu.cdr('Hello to you'),'to you');
       System.assertEquals(eu.cdr('Hello'),null);
       System.assertEquals(eu.cdr(null),null);

    }
}

Posted by Chris at October 17, 2007 04:30 PM