Added Nitfol and Frotz source code.
[projects/chimara/chimara.git] / interpreters / nitfol / tokenise.c
diff --git a/interpreters/nitfol/tokenise.c b/interpreters/nitfol/tokenise.c
new file mode 100644 (file)
index 0000000..7db9db6
--- /dev/null
@@ -0,0 +1,373 @@
+/*  Nitfol - z-machine interpreter using Glk for output.
+    Copyright (C) 1999  Evin Robertson
+
+    This program is free software; you can redistribute it and/or modify
+    it under the terms of the GNU General Public License as published by
+    the Free Software Foundation; either version 2 of the License, or
+    (at your option) any later version.
+
+    This program is distributed in the hope that it will be useful,
+    but WITHOUT ANY WARRANTY; without even the implied warranty of
+    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+    GNU General Public License for more details.
+
+    You should have received a copy of the GNU General Public License
+    along with this program; if not, write to the Free Software
+    Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111, USA.
+
+    The author can be reached at nitfol@deja.com
+*/
+#include "nitfol.h"
+
+static int tokenise(zword dictionarytable, const char *text, int length,
+                   zword *parse_dest, int maxwords,
+                   zword sepratortable, int numseparators,
+                   BOOL write_unrecognized);
+
+static void addparsed(zword *parse_dest, int word_num, int length, int off)
+{
+  if(zversion <= 4)
+    off+=1;
+  else
+    off+=2;
+
+  LOWORDwrite(*parse_dest, word_num);
+  *parse_dest+=ZWORD_SIZE;
+  LOBYTEwrite(*parse_dest, length);
+  *parse_dest+=1;
+  LOBYTEwrite(*parse_dest, off);
+  *parse_dest+=1;
+}
+
+static int dictentry_len;
+
+static int cmpdictentry(const void *a, const void *b)
+{
+  return n_memcmp(a, b, dictentry_len);
+}
+
+
+static zword find_word(zword dictionarytable, const char *word, int length)
+{
+  zbyte zsciibuffer[12];
+  int entry_length, word_length;
+  int num_entries;
+  void *p;
+
+  entry_length = LOBYTE(dictionarytable);
+  dictionarytable++;
+  num_entries = LOWORD(dictionarytable);
+  dictionarytable+=ZWORD_SIZE;
+
+  if(zversion <= 3)
+    word_length = 4;
+  else
+    word_length = 6;
+
+  encodezscii(zsciibuffer, word_length, word_length, word, length);
+
+  dictentry_len = word_length;
+  
+  if(is_neg(num_entries)) {  /* Unordered dictionary */
+    num_entries = neg(num_entries);
+    p = n_lfind(zsciibuffer, z_memory + dictionarytable, &num_entries,
+               entry_length, cmpdictentry);
+  } else {                   /* Ordered dictionary */
+    p = n_bsearch(zsciibuffer, z_memory + dictionarytable, num_entries,
+                 entry_length, cmpdictentry);
+  }
+  if(p)
+    return ((zbyte *) p) - z_memory;
+
+  return 0;
+}
+
+
+#ifdef SMART_TOKENISER
+
+struct Typocorrection {
+  struct Typocorrection *next;
+  struct Typocorrection *prev;
+  char original[13];
+  char changedto[13];
+};
+
+struct Typocorrection *recent_corrections;  /* Inform requests two parses of
+                                              each input; remember what
+                                              corrections we've made so
+                                              we don't print twice */
+void forget_corrections(void)
+{
+  LEdestroy(recent_corrections);
+}
+
+static zword smart_tokeniser(zword dictionarytable,
+                            const char *text, unsigned length, BOOL is_begin)
+{
+  zword word_num = 0;
+  unsigned tlength = (length < 12) ? length : 12;
+  char tbuffer[13];
+
+  /* Letter replacements are tried in this order - */
+  const char fixmeletters[] = "abcdefghijklmnopqrstuvwxyz";
+  /* char fixmeletters[] = "etaonrishdlfcmugpywbvkxjqz"; */
+
+  
+  word_num = find_word(dictionarytable, text, length);  
+
+  /* Some game files don't contain abbreviations for common commands */
+  if(!word_num && do_expand && length == 1 && is_begin) {
+    const char * const abbrevs[26] = {
+      "a",              "b",           "close",          "down",
+      "east",           "f",           "again",          "h",
+      "inventory",      "j",           "attack",         "look",
+      "m",              "north",       "oops",           "open",
+      "quit",           "drop",        "south",          "take",
+      "up",             "v",           "west",           "examine",
+      "yes",            "wait"
+    };
+    if('a' <= text[0] && text[0] <= 'z') {
+      strcpy(tbuffer, abbrevs[text[0] - 'a']);
+      tlength = strlen(tbuffer);
+      word_num = find_word(dictionarytable, tbuffer, tlength);
+    }
+  }
+
+  /* Check for various typing errors */
+
+  /* Don't attempt typo correction in very short words */
+  if(do_spell_correct && length >= 3) {
+
+    if(!word_num) {  /* Check for transposes */
+      /* To fix, try all possible transposes */
+      unsigned position;
+      for(position = 1; position < tlength; position++) {
+       unsigned s;
+       for(s = 0; s < tlength; s++)
+         tbuffer[s] = text[s];
+
+       tbuffer[position - 1] = text[position];
+       tbuffer[position]     = text[position - 1];
+
+       word_num = find_word(dictionarytable, tbuffer, tlength);
+       if(word_num)
+         break;
+      }
+    }
+
+    if(!word_num) {  /* Check for deletions */
+      /* To fix, try all possible insertions */
+      unsigned position;
+      for(position = 0; position <= tlength; position++) {
+       unsigned s;
+       for(s = 0; s < position; s++)    /* letters before the insertion */
+         tbuffer[s] = text[s];
+
+       for(s = position; s < tlength; s++)       /* after the insertion */
+         tbuffer[s + 1] = text[s];
+
+       /* try each letter */
+       for(s = 0; s < sizeof(fixmeletters); s++) {
+         tbuffer[position] = fixmeletters[s];
+         word_num = find_word(dictionarytable, tbuffer, tlength + 1);
+         if(word_num)
+           break;
+       }
+
+       if(word_num) {
+         tlength++;
+         break;
+       }
+      }
+    }
+
+    if(!word_num) {  /* Check for insertions */
+      /* To fix, try all possible deletions */
+      unsigned position;
+      for(position = 0; position < tlength; position++) {
+       unsigned s;
+       for(s = 0; s < position; s++)    /* letters before the deletion */
+         tbuffer[s] = text[s];
+
+       for(s = position + 1; s < tlength; s++)   /* after the deletion */
+         tbuffer[s - 1] = text[s];
+
+       word_num = find_word(dictionarytable, tbuffer, tlength - 1);
+
+       if(word_num) {
+         tlength--;
+         break;
+       }
+      }
+    }
+
+    if(!word_num) {  /* Check for substitutions */
+      /* To fix, try all possible substitutions */
+      unsigned position;
+      for(position = 0; position < tlength; position++) {
+      unsigned s;
+      for(s = 0; s < tlength; s++)
+       tbuffer[s] = text[s];
+
+      /* try each letter */
+      for(s = 0; s < sizeof(fixmeletters); s++) {
+       tbuffer[position] = fixmeletters[s];
+       word_num = find_word(dictionarytable, tbuffer, tlength);
+       if(word_num)
+         break;
+      }
+
+      if(word_num)
+       break;
+      }
+    }
+  }
+
+  /* Report any corrections made */
+  if(word_num) {
+    struct Typocorrection *p;
+    char original[13], changedto[13];
+    n_strncpy(original, text, 13);
+    n_strncpy(changedto, tbuffer, 13);
+    if(length < 13)
+      original[length] = 0;
+    if(tlength < 13)
+      changedto[tlength] = 0;
+
+    LEsearch(recent_corrections, p, ((n_strncmp(p->original, original, 13) == 0) &&
+                                    (n_strncmp(p->changedto, changedto, 13) == 0)));
+
+    /* Only print a correction if it hasn't yet been reported this turn */
+    if(!p) {
+      struct Typocorrection newcorrection;
+      n_strncpy(newcorrection.original, original, 13);
+      n_strncpy(newcorrection.changedto, changedto, 13);
+      LEadd(recent_corrections, newcorrection);
+
+      set_glk_stream_current();
+
+      if(allow_output) {
+       glk_put_char('[');
+       w_glk_put_buffer(text, length);
+       w_glk_put_string(" -> ");
+       w_glk_put_buffer(tbuffer, tlength);
+       glk_put_char(']');
+       glk_put_char(10);
+      }
+    }
+  }
+
+  return word_num;
+}
+
+#endif
+
+static void handle_word(zword dictionarytable, const char *text,
+                       zword word_start, int length,
+                       zword *parse_dest,
+                       BOOL write_unrecognized, int *parsed_words)
+{
+  
+  zword word_num;
+
+  word_num = find_word(dictionarytable, text + word_start, length);
+
+#ifdef SMART_TOKENISER
+  if(!word_num)
+    word_num = smart_tokeniser(dictionarytable, text + word_start, length,
+                              *parsed_words == 0);
+#endif
+
+  if(!word_num && !write_unrecognized)
+    *parse_dest += ZWORD_SIZE + 2;
+  else
+    addparsed(parse_dest, word_num, length, word_start);
+
+  (*parsed_words)++;
+}
+
+
+static int tokenise(zword dictionarytable, const char *text, int length,
+                   zword *parse_dest, int maxwords,
+                   zword separatortable, int numseparators,
+                   BOOL write_unrecognized)
+{
+  int i;
+  int parsed_words = 0;
+  int word_start = 0;
+  for(i = 0; i <= length && parsed_words < maxwords; i++) {
+    BOOL do_tokenise = FALSE;
+    BOOL do_add_separator = FALSE;
+    if((i == length) || text[i] == ' ') {  /* A space or at the end */
+      do_tokenise = TRUE;
+    } else {
+      int j;
+      for(j = 0; j < numseparators; j++) {
+       if(text[i] == (char) LOBYTE(separatortable + j)) {
+         do_tokenise = TRUE;
+         do_add_separator = TRUE;
+         break;
+       }
+      }
+    }
+
+    if(do_tokenise) {
+      int wordlength = i - word_start;
+      if(wordlength > 0) {
+       handle_word(dictionarytable, text, word_start, wordlength,
+                   parse_dest, write_unrecognized, &parsed_words);
+      }
+      word_start = i + 1;
+    }
+    if(do_add_separator && parsed_words < maxwords) {
+      handle_word(dictionarytable, text, i, 1,
+                 parse_dest, write_unrecognized, &parsed_words);
+
+    }
+  }
+  return parsed_words;
+}
+
+
+void z_tokenise(const char *text, int length, zword parse_dest,
+               zword dictionarytable, BOOL write_unrecognized)
+{
+  zword separatortable;
+  zword numparsedloc;
+  int numseparators;
+  int maxwords, parsed_words;
+
+  if(parse_dest > dynamic_size || parse_dest < 64) {
+    n_show_error(E_OUTPUT, "parse table in invalid location", parse_dest);
+    return;
+  }
+  
+  numseparators = LOBYTE(dictionarytable);
+  separatortable = dictionarytable + 1;
+  dictionarytable += numseparators + 1;
+
+  maxwords = LOBYTE(parse_dest);
+  numparsedloc = parse_dest + 1;
+  parse_dest+=2;
+
+  if(maxwords == 0)
+    n_show_warn(E_OUTPUT, "small parse size", maxwords);
+
+  parsed_words = tokenise(dictionarytable, text, length,
+                         &parse_dest, maxwords, separatortable, numseparators,
+                         write_unrecognized);
+  
+  LOBYTEwrite(numparsedloc, parsed_words);
+}
+
+
+void op_tokenise(void)
+{
+  if(numoperands < 3 || operand[2] == 0)
+    operand[2] = z_dictionary;
+  if(numoperands < 4)
+    operand[3] = 0;
+  z_tokenise((char *) z_memory + operand[0] + 2, LOBYTE(operand[0] + 1),
+            operand[1], operand[2], operand[3]==0);
+}
+