*** empty log message ***
[m17n/m17n-lib-js.git] / xex.js
diff --git a/xex.js b/xex.js
index 0024512..e386132 100644 (file)
--- a/xex.js
+++ b/xex.js
@@ -1,35 +1,18 @@
 // -* coding: utf-8; -*
 
-var Xex = {};
-
-Xex.Alist = function ()
-{
-  this.count = 0;
-}
-
-Xex.Alist.prototype.put = function (key, val)
-{
-  this.count++;
-  return (this[key] = val);
-}
-Xex.Alist.prototype.clone = function ()
-{
-  var alist = new Xex.Alist ();
-  for (key in this)
-    alist[key] = this[key];
-  return alist;
-}
-Xex.Alist.prototype.toString = function ()
-{
-  var str = 'alist:';
-  for (key in this)
-    str += '"' + key + '"';
-  return str;
-}
-
-// Xex.alist = new Xex.Alist ();
-// Xex.alist.put ('abc', "ABC");
-// alert (Xex.alist['abc']);
+var Xex = {
+  LogNode: null,
+  Log: function (arg, indent)
+  {
+    if (! Xex.LogNode)
+      return;
+    var str = '';
+    if (indent != undefined)
+      for (var i = 0; i <= indent; i++)
+       str += '  ';
+    Xex.LogNode.value = str + arg + "\n" + Xex.LogNode.value;
+  }
+};
 
 Xex.Error = {
   UnknownError: "unknown-error",
@@ -45,7 +28,8 @@ Xex.Error = {
 
   UnknownFunction: "unknown-function",
   MacroExpansionError: "macro-expansion-error",
-  NoVariableName: "no-variable-anme",
+  NoVariableName: "no-variable-name",
+  NoFunctionName: "no-funcion-name",
 
   // Run time errors.
   ArithmeticError: "arithmetic-error",
@@ -56,35 +40,43 @@ Xex.Error = {
   UncaughtThrow: "uncaught-throw"
 };
 
-Xex.Variable = function (domain, name, val)
+Xex.Variable = function (domain, name, desc, val, range)
 {
   this.domain = domain;
   this.name = name;
+  this.desc = desc;
   this.val = val;
+  this.range = range;
 }
 
-Xex.Variable.prototype.clone = function () {
-  return new Xex.Variable (this.domain, this.name, this.value);
+Xex.Variable.prototype.clone = function ()
+{
+  return new Xex.Variable (this.domain, this.name, this.desc,
+                          this.val, this.range);
 }
     
-Xex.Variable.prototype.Equals = function (obj) {
+Xex.Variable.prototype.Equals = function (obj)
+{
   return ((obj instanceof Xex.Variable)
          && obj.name == this.name);
 }
 
-Xex.Variable.prototype.SetValue = function (term) {
+Xex.Variable.prototype.SetValue = function (term)
+{
   this.val = term;
   return term;
 }
 
-Xex.Function = function (name, with_var, min_args, max_args) {
+Xex.Function = function (name, with_var, min_args, max_args)
+{
   this.name = name;
   this.with_var = with_var;
   this.min_args = min_args;
   this.max_args = max_args;
 };  
 
-Xex.Subrountine = function (builtin, name, with_var, min_args, max_args) {
+Xex.Subrountine = function (builtin, name, with_var, min_args, max_args)
+{
   this.name = name;
   this.with_var = with_var;
   this.min_args = min_args;
@@ -94,7 +86,7 @@ Xex.Subrountine = function (builtin, name, with_var, min_args, max_args) {
 
 Xex.Subrountine.prototype.Call = function (domain, vari, args)
 {
-  newargs = new Array ();
+  var newargs = new Array ();
   for (var i = 0; i < args.length; i++)
     {
       newargs[i] = args[i].Eval (domain);
@@ -121,7 +113,6 @@ Xex.SpecialForm.prototype.Call = function (domain, vari, args)
 Xex.Lambda = function (name, min_args, max_args, args, body)
 {
   this.name = name;
-  this.with_var = with_var;
   this.min_args = min_args;
   this.max_args = max_args;
   this.args = args;
@@ -175,7 +166,6 @@ Xex.Lambda.prototype.Call = function (domain, vari, args)
 Xex.Macro = function (name, min_args, max_args, args, body)
 {
   this.name = name;
-  this.with_var = with_var;
   this.min_args = min_args;
   this.max_args = max_args;
   this.args = args;
@@ -193,9 +183,9 @@ Xex.Macro.prototype.Call = function (domain, vari, args)
       domain.Bind (this.args[i], args[i]);
     try {
       domain.Catch (Xex.CatchTag.Return);
-      for (var term in body)
+      for (var i in this.body)
         {
-         result = term.Eval (domain);
+         result = this.body[i].Eval (domain);
          if (domain.Thrown ())
            break;
        }
@@ -352,7 +342,7 @@ Xex.Domain.prototype = {
     this.functions[name] =  new Xex.Lambda (name, min_args, max_args,
                                            args, body);
   },
-  DefunByFunc: function (func) { this.functions[func.Name] = func; },
+  DefunByFunc: function (func) { this.functions[func.name] = func; },
   Defmacro: function (name, min_args, max_args, args, body)
   {
     this.functions[name] = new Xex.Macro (name, min_args, max_args,
@@ -362,30 +352,20 @@ Xex.Domain.prototype = {
   {
     var func = this.functions[fname];
 
+    if (! func)
+      throw new Xex.ErrTerm (Xex.Error.UnknownFunction, fname);
     if (this.termtypes[alias])
       throw new Xex.ErrTerm (Xex.Error.FunctionConflict,
                           "Already defined as a term type: " + alias);
     if (this.functions[alias])
       throw new Xex.ErrTerm (Xex.Error.FunctionConflict,
                           "Already defined as a function: " + alias);
-    if (! func)
-      throw new Xex.ErrTerm (Xex.Error.UnknownFunction, fname);
     this.functions[alias] = func;
   },
-  Defvar: function (name)
+  Defvar: function (name, desc, val, range)
   {
-    var vari = this.variables[name];
-    if (vari)
-      {
-       if (vari.Typed)
-         throw new Xex.ErrTerm (Xex.Error.VariableTypeConflict,
-                              "Not a non-typed variable: " + name);
-      }
-    else
-      {
-       vari = new Xex.Variable (this, name, Xex.Zero);
-       this.variables[name] = vari;
-      }
+    var vari = new Xex.Variable (this, name, desc, val, range);
+    this.variables[name] = vari;
     return vari;
   },
   GetFunc: function (name)
@@ -393,7 +373,7 @@ Xex.Domain.prototype = {
     var func = this.functions[name];
     if (! func)
       throw new Xex.ErrTerm (Xex.Error.UnknownFunction,
-                            "Unknown function: " + this + ':' + name);
+                            "Unknown function: " + name);
     return func;
   },
   CopyFunc: function (domain, name)
@@ -411,7 +391,8 @@ Xex.Domain.prototype = {
   {
     var vari = this.variables[name];
     if (! vari)
-      vari = this.variables[name] = new Xex.Variable (this, name, Xex.Zero);
+      vari = this.variables[name] = new Xex.Variable (this, name, null,
+                                                     Xex.Zero, null);
     return vari;
   },
   GetVar: function (name) { return this.variables[name]; },
@@ -450,52 +431,182 @@ Xex.Term.prototype = {
     if (this.val != undefined)
       return '<' + this.type + '>' + this.val + '</' + this.type + '>';
     return '<' + this.type + '/>';
-  }
+  },
+  Intval: function () { throw new Xex.ErrTerm (Xex.Error.WrongType,
+                                              "Not an integer"); },
+  Strval: function () { throw new Xex.ErrTerm (Xex.Error.WrongType,
+                                              "Not a string"); }
 };
 
-Xex.ParseTerm = function (domain, node)
+Node.prototype.firstElement = function ()
 {
-  var name = node.nodeName;
-  var parser = domain.termtypes[name];
-
-  if (parser)
-    return parser (domain, node);
-  if (name == 'defun' || name == 'defmacro')
-    {
-      name = parse_defun_head (domain, node);
-      parse_defun_body (domain, node);
-      return new Xex.StrTerm (name);
-    }
-  if (name == 'defvar')
-    {
-      name = parse_defvar (doamin, node);
-      return new Xex.StrTerm (nanme);
-    }
-
-  return new Xex.Funcall.prototype.Parser (domain, node);
+  for (var n = this.firstChild; n; n = n.nextSibling)
+    if (n.nodeType == 1)
+      return n;
+  return null;
 }
 
-Xex.ParseTermList = function (domain, node)
+Node.prototype.nextElement = function ()
 {
-  for (var n = node; n; n = n.nextSibling)
+  for (var n = this.nextSibling; n; n = n.nextSibling)
     if (n.nodeType == 1)
+      return n;
+  return null;
+};
+
+(function () {
+  function parse_defvar (domain, node)
+  {
+    var name = node.attributes['vname'].nodeValue;
+    if (! name)
+      throw new Xex.ErrTerm (Xex.Error.NoVariableName, node, '');
+    var vari = domain.variables[name];
+    var desc, val, range;
+    if (vari)
       {
-       if (n.nodeName == 'defun' || n.nodeName == 'defmacro')
-         Xex.parse_defun_head (domain, n);
+       desc = vari.description;
+       val = vari.val;
+       range = vari.range;
       }
-  var terms = new Array ();
-  for (var n = node; n; n = n.nextSibling)
-    if (n.nodeType == 1)
+    node = node.firstElement ();
+    if (node && node.nodeName == 'description')
+      {
+       desc = node.firstChild.nodeValue;
+       node = node.nextElement ();
+      }
+    if (node)
+      {
+       val = Xex.Term.Parse (domain, node);
+       node = node.nextElement ();
+       if (node && node.nodeName == 'possible-values')
+         for (node = node.firstElement (); node; node = node.nextElement ())
+           {
+             var pval;
+             if (node.nodeName == 'range')
+               {
+                 if (! val.IsInt)
+                   throw new Xex.ErrTerm (Xex.Error.TermTypeInvalid,
+                                          'Range not allowed for ' + name);
+                 pval = new Array ();
+                 for (var n = node.firstElement (); n; n = n.nextElement ())
+                   {
+                     var v = Xex.Term.Parse (domain, n);
+                     if (! v.IsInt)
+                       throw new Xex.ErrTerm (Xex.Error.TermTypeInvalid,
+                                              'Invalid range value: ' + val);
+                     pval.push (v);
+                   }
+                 }
+             else
+               {
+                 pval = Xex.Term.Parse (domain, node);
+                 if (val.type != pval.type)
+                   throw new Xex.ErrTerm (Xex.Error.TermTypeInvalid,
+                                          'Invalid possible value: ' + pval);
+               }
+             if (! range)
+               range = new Array ();
+             range.push (pval);
+         }
+      }
+    if (! val)
+      val = Xex.Zero;
+    domain.Defvar (name, desc, val, range);
+    return name;
+  }
+
+  function parse_defun_head (domain, node)
+  {
+    var name = node.attributes['fname'].nodeValue;
+    if (! name)
+      throw new Xex.ErrTerm (Xex.Error.NoFunctionName, node, '');
+    var args = new Array ();
+    var nfixed = 0, noptional = 0, nrest = 0;
+
+    node = node.firstElement ();
+    if (node && node.nodeName == 'args')
+      {
+       var n;
+       for (n = n.firstElement (); n; n = n.nextElement ())
+         {
+           if (n.nodeName == 'fixed')
+             nfixed++;
+           else if (n.nodeName == 'optional')
+             noptional++;
+           else if (n.nodeName == 'rest')
+             nrest++;
+           else
+             throw new Xex.ErrTerm (Xex.Error.WrongType, n, n.nodeName);
+         }
+       if (nrest > 1)
+         throw new Xex.ErrTerm (Xex.Error.WrongType, n, 'Too many <rest>');
+       for (n = node.firstElement (); n; n = n.nextElement ())
+         args.push (domain.DefVar (n.attributes['vname'].nodeValue));
+      }
+    args.min_args = nfixed;
+    args.max_args = nrest == 0 ? nfixed + noptional : -1;
+
+    if (node.nodeName == 'defun')
+      domain.Defun (name, args, null);
+    else
+      domain.Defmacro (name, args, null);
+    return name;
+  }
+
+  function parse_defun_body (domain, node)
+  {
+    var name = node.attributes['fname'].nodeValue;
+    var func = domain.GetFunc (name);
+    var body;
+    for (node = node.firstElement (); node; node = node.nextElement ())
+      if (node.nodeName != 'description' && node.nodeName != 'args')
+       break;
+    body = Xex.Term.Parse (domain, node, null);
+    func.body = body;
+  }
+
+  Xex.Term.Parse = function (domain, node, stop)
+  {
+    if (arguments.length == 2)
+      {
+       var name = node.nodeName;
+       var parser = domain.termtypes[name];
+
+       if (parser)
+         return parser (domain, node);
+       if (name == 'defun' || name == 'defmacro')
+         {
+           name = parse_defun_head (domain, node);
+           parse_defun_body (domain, node);
+           return new Xex.StrTerm (name);
+         }
+       if (name == 'defvar')
+         {
+           name = parse_defvar (domain, node);
+           return new Xex.StrTerm (name);
+         }
+       return new Xex.Funcall.prototype.Parser (domain, node);
+      }
+    for (var n = node; n && n != stop; n = n.nextElement ())
+      if (n.nodeName == 'defun' || n.nodeName == 'defmacro')
+       parse_defun_head (domain, n);
+    var terms = null;
+    for (var n = node; n && n != stop; n = n.nextElement ())
       {
        if (n.nodeName == 'defun' || n.nodeName == 'defmacro')
-         Xex.parse_defun_body (domain, n);
+         parse_defun_body (domain, n);
        else if (n.nodeName == 'defvar')
-         Xex.parse_defvar (domain, n);
+         parse_defvar (domain, n);
        else
-         terms.push (Xex.ParseTerm (domain, n));
+         {
+           if (! terms)
+             terms = new Array ();
+           terms.push (Xex.Term.Parse (domain, n));
+         }
       }
-  return terms;
-}
+    return terms;
+  }
+}) ();
 
 Xex.Varref = function (vname)
 {
@@ -510,6 +621,7 @@ Xex.Varref = function (vname)
   {
     if (! this.vari || this.vari.domain != domain)
       this.vari = domain.GetVarCreate (this.val);
+    Xex.Log (this.ToString () + '=>' + this.vari.val, domain.depth);
     return this.vari.val;
   }
 
@@ -518,6 +630,11 @@ Xex.Varref = function (vname)
     return new Xex.Varref (node.attributes['vname'].nodeValue);
   }
 
+  proto.ToString = function ()
+  {
+    return '<varref vname="' + this.val + '"/>';
+  }
+
   Xex.Varref.prototype = proto;
 }) ();
 
@@ -539,12 +656,12 @@ Xex.Funcall = function (func, vari, args)
     var attr;
 
     if (fname == 'funcall')
-      fname = node.attributes['fname']
+      fname = node.attributes['fname'].nodeValue;
     var func = domain.GetFunc (fname);
     var vari;
     attr = node.attributes['vname'];
     vari = attr != undefined ? domain.GetVarCreate (attr.nodeValue) : false;
-    var args = Xex.ParseTermList (domain, node.firstChild);
+    var args = Xex.Term.Parse (domain, node.firstElement (), null);
     return new Xex.Funcall (func, vari, args);
   }
 
@@ -560,7 +677,16 @@ Xex.Funcall = function (func, vari, args)
 
   proto.Eval = function (domain)
   {
-    return this.func.Call (domain, this.vari, this.args);
+    if (! (this.func instanceof Xex.Subrountine))
+      Xex.Log (this, domain.depth);
+    domain.depth++;
+    var result;
+    try {
+      result = this.func.Call (domain, this.vari, this.args);
+    } finally {
+      Xex.Log (this + ' => ' + result, --domain.depth);
+    }
+    return result;
   }
 
   proto.Clone = function ()
@@ -580,11 +706,18 @@ Xex.Funcall = function (func, vari, args)
   {
     var arglist = ''
     var len = this.args.length;
+    var str = '<' + this.func.name;
+    if (this.vari)
+      str += ' vname="' + this.vari.name + '"';
     if (len == 0)
-      return '<' + this.func.name + '/>';
-    for (var i = 0; i < len; i++)
-      arglist += this.args[i].toString ();
-    return '<' + this.func.name + '>' + arglist + '</' + this.func.name + '>';
+      return str + '/>';
+    if (this.func instanceof Xex.Subrountine)
+      for (var i = 0; i < len; i++)
+       arglist += this.args[i].toString ();
+    else
+      for (var i = 0; i < len; i++)
+       arglist += '.';
+    return str + '>' + arglist + '</' + this.func.name + '>';
   }
 
   Xex.Funcall.prototype = proto;
@@ -642,6 +775,7 @@ Xex.IntTerm = function (num) { this.val = num; };
 (function () {
   var proto = new Xex.Term ('integer');
   proto.IsInt = true;
+  proto.Intval = function () { return this.val; };
   proto.IsTrue = function () { return this.val != 0; }
   proto.Clone = function () { return new Xex.IntTerm (this.val); }
   proto.Parser = function (domain, node)
@@ -659,6 +793,7 @@ Xex.StrTerm = function (str) { this.val = str; };
 (function () {
   var proto = new Xex.Term ('string');
   proto.IsStr = true;
+  proto.Strval = function () { return this.val; };
   proto.IsTrue = function () { return this.val.length > 0; }
   proto.Clone = function () { return new Xex.StrTerm (this.val); }
   proto.Parser = function (domain, node)
@@ -701,7 +836,7 @@ Xex.LstTerm = function (list) { this.val = list; };
 
   proto.Parser = function (domain, node)
   {
-    var list = Xex.ParseTermList (domain, node.firstChild);
+    var list = Xex.Term.Parse (domain, node.firstElement (), null);
     return new Xex.LstTerm (list);
   }
 
@@ -724,24 +859,87 @@ Xex.LstTerm = function (list) { this.val = list; };
 
   function Fset (domain, vari, args)
   {
-    return vari.SetValue (args[0]);
+    if (! vari)
+      throw new Xex.ErrTerm (Xex.Error.NoVariableName,
+                            'No variable name to set');
+    vari.SetValue (args[0]);
+    return args[0];
   }
 
   function maybe_set_intvar (vari, n)
   {
     var term = new Xex.IntTerm (n);
-    if (vari != null)
+    if (vari)
       vari.SetValue (term);
     return term;
   }
 
   function Fadd (domain, vari, args)
   {
-    var n = vari ? vari.val.val : 0;
+    var n = vari ? vari.val.Intval () : 0;
     var len = args.length;
 
     for (var i = 0; i < len; i++)
-      n += args[i].val;
+      n += args[i].Intval ();
+    return maybe_set_intvar (vari, n);
+  }
+
+  function Fmul (domain, vari, args)
+  {
+    var n = vari ? vari.val.Intval () : 1;
+    for (var i = 0; i < args.length; i++)
+      n *= arg.Intval ();
+    return maybe_set_intvar (vari, n);
+  }
+
+  function Fsub (domain, vari, args)
+  {
+    var n, i;
+
+    if (! vari)
+      {
+       n = args[0].Intval ();
+       i = 1;
+      }
+    else
+      {
+       n = vari.val.Intval ();
+       i = 0;
+      }
+    while (i < args.length)
+      n -= args[i++].Intval ();
+    return maybe_set_intvar (vari, n);
+  }
+
+  function Fdiv (domain, vari, args)
+  {
+    var n, i;
+
+    if (! vari == null)
+      {
+       n = args[0].Intval ();
+       i = 1;
+      }
+    else
+      {
+       n = vari.val.Intval ();
+       i = 0;
+      }
+    while (i < args.length)
+      n /= args[i++].Intval ();
+    return maybe_set_intvar (vari, n);
+  }
+
+  function Fmod (domain, vari, args)
+  {
+    return maybe_set_intvar (vari, args[0].Intval () % args[1].Intval ());
+  }
+
+  function Flogior (domain, vari, args)
+  {
+    var n = vari == null ? 0 : vari.val;
+    for (var i = 0; i < args.length; i++)
+      n |= args[i].val;
     return maybe_set_intvar (vari, n);
   }
 
@@ -753,7 +951,7 @@ Xex.LstTerm = function (list) { this.val = list; };
       var result = args[i].Eval (domain);
       if (domain.Thrown ())
        return result;
-      if (! result.IsTrue)
+      if (! result.IsTrue ())
        return Xex.Zero;
     }
     return Xex.One;
@@ -767,12 +965,73 @@ Xex.LstTerm = function (list) { this.val = list; };
       var result = args[i].Eval (domain);
       if (domain.Thrown ())
        return result;
-      if (result.IsTrue)
+      if (result.IsTrue ())
        return Xex.One;
     }
     return Xex.Zero;
   }
 
+  function Feq (domain, vari, args)
+  {
+    for (var i = 1; i < args.length; i++)
+      if (! args[i - 1].Equals (args[i]))
+       return Xex.Zero;
+    return Xex.One;
+  }
+
+  function Flt (domain, vari, args)
+  {
+    var n = args[0].Intval;
+
+    for (var i = 1; i < args.length; i++)
+      {
+       var n1 = args[i].Intval;
+       if (n >= n1)
+         return Xex.Zero;
+       n = n1;
+      }
+    return Xex.One;
+  }
+
+  function Fle (domain, vari, args)
+  {
+    var n = args[0].Intval;
+    for (var i = 1; i < args.length; i++)
+      {
+       var n1 = args[i].Intval;
+       if (n > n1)
+         return Xex.Zero;
+       n = n1;
+      }
+    return Xex.One;
+  }
+
+  function Fgt (domain, vari, args)
+  {
+    var n = args[0].Intval;
+    for (var i = 1; i < args.length; i++)
+      {
+       var n1 = args[i].Intval;
+       if (n <= n1)
+         return Xex.Zero;
+       n = n1;
+      }
+    return Xex.One;
+  }
+
+  function Fge (domain, vari, args)
+  {
+    var n = args[0].Intval;
+    for (var i = 1; i < args.Length; i++)
+      {
+       var n1 = args[i].Intval;
+       if (n < n1)
+         return Xex.Zero;
+       n = n1;
+      }
+    return Xex.One;
+  }
+
   function Fprogn (domain, vari, args)
   {
     var result = Xex.One;
@@ -793,13 +1052,35 @@ Xex.LstTerm = function (list) { this.val = list; };
 
     if (domain.Thrown ())
       return result;
-    if (result.IsTrue)
+    if (result.IsTrue ())
       return args[1].Eval (domain);
-    if (args.Length == 2)
+    if (args.length == 2)
       return Zero;
     return args[2].Eval (domain);
   }
 
+  function Fcond (domain, vari, args)
+  {
+    for (var i = 0; i < args.length; i++)
+      {
+       var list = args[i];
+       var result = list.val[0].Eval (domain);
+       if (result.IsTrue ())
+         {
+           for (var j = 1; j < list.val.length; j++)
+             {
+               domain.depth++;
+               result = list.val[j].Eval (domain);
+               domain.depth--;
+               if (domain.Thrown ())
+                 return result;
+               }
+           return result;
+         }
+      }
+    return Xex.Zero;
+  }
+
   function eval_terms (domain, terms, idx)
   {
     var result = Xex.Zero;
@@ -871,14 +1152,65 @@ Xex.LstTerm = function (list) { this.val = list; };
   Xex.BasicDomain = basic;
 
   basic.DefSubr (Fset, "set", true, 1, 1);
+  basic.DefAlias ("=", "set");
+  //basic.DefSubr (Fnot, "not", false, 1, 1);
+  //basic.DefAlias ("!", "not");
   basic.DefSubr (Fadd, "add", true, 1, -1);
+  basic.DefSubr (Fmul, "mul", true, 1, -1);
+  basic.DefAlias ("*", "mul");
+  basic.DefSubr (Fsub, "sub", true, 1, -1);
+  basic.DefAlias ("-", "sub");
+  basic.DefSubr (Fdiv, "div", true, 1, -1);
+  basic.DefAlias ("/", "div");
+  basic.DefSubr (Fmod, "mod", true, 1, 2);
+  basic.DefAlias ("%", "mod");
+  basic.DefSubr (Flogior, "logior", true, 1, -1);
+  basic.DefAlias ('|', "logior");
+  //basic.DefSubr (Flogand, "logand", true, 1, -1);
+  //basic.DefAlias ("&", "logand");
+  //basic.DefSubr (Flsh, "lsh", true, 1, 2);
+  //basic.DefAlias ("<<", "lsh");
+  //basic.DefSubr (Frsh, "rsh", true, 1, 2);
+  //basic.DefAlias (">>", "rsh");
+  basic.DefSubr (Feq, "eq", false, 2, -1);
+  basic.DefAlias ("==", "eq");
+  //basic.DefSubr (Fnoteq, "noteq", false, 2, 2);
+  //basic.DefAlias ("!=", "noteq");
+  basic.DefSubr (Flt, "lt", false, 2, -1);
+  basic.DefAlias ("<", "lt");
+  basic.DefSubr (Fle, "le", false, 2, -1);
+  basic.DefAlias ("<=", "le");
+  basic.DefSubr (Fgt, "gt", false, 2, -1);
+  basic.DefAlias (">", "gt");
+  basic.DefSubr (Fge, "ge", false, 2, -1);
+  basic.DefAlias (">=", "ge");
   basic.DefSubr (Fthrow, "throw", false, 1, 2);
 
+  //basic.DefSubr (Fappend, "append", true, 0, -1);
+  //basic.DefSubr (Fconcat, "concat", true, 0, -1);
+  //basic.DefSubr (Fnth, "nth", false, 2, 2);
+  //basic.DefSubr (Fcopy, "copy", false, 1, 1);
+  //basic.DefSubr (Fins, "ins", true, 2, 2);
+  //basic.DefSubr (Fdel, "del", true, 2, 2);
+  //basic.DefSubr (Feval, "eval", false, 1, 1);
+  //basic.DefSubr (Fbreak, "break", false, 0, 1);
+  //basic.DefSubr (Freturn, "return", false, 0, 1);
+  //basic.DefSubr (Fthrow, "throw", false, 1, 2);
+
   basic.DefSpecial (Fand, "and", false, 1, -1);
+  basic.DefAlias ("&&", "and");
   basic.DefSpecial (For, "or", false, 1, -1);
-  basic.DefAlias ("=", "set");
+  basic.DefAlias ("||", "or");
   basic.DefSpecial (Fprogn, "progn", false, 1, -1);
+  basic.DefAlias ("expr", "progn");
   basic.DefSpecial (Fif, "if", false, 2, 3);
+  //basic.DefSpecial (Fwhen, "when", false, 1, -1);
+  //basic.DefSpecial (Floop, "loop", false, 1, -1);
+  //basic.DefSpecial (Fwhile, "while", false, 1, -1);
+  basic.DefSpecial (Fcond, "cond", false, 1, -1);
+  //basic.DefSpecial (Fforeach, "foreach", true, 2, -1);
+  //basic.DefSpecial (Fquote, "quote", false, 1, 1);
+  //basic.DefSpecial (Ftype, "type", false, 1, 1);
   basic.DefSpecial (Fcatch, "catch", true, 2, -1);
 
   basic.DefType (Xex.Funcall.prototype);
@@ -893,6 +1225,7 @@ Xex.LstTerm = function (list) { this.val = list; };
 
 Xex.Zero = new Xex.IntTerm (0);
 Xex.One = new Xex.IntTerm (1);
+Xex.nil = new Xex.SymTerm ('nil');
 
 Xex.Load = function (server, file)
 {
@@ -911,8 +1244,10 @@ var MIM = {
   enabled: true,
   // Boolean flag to tell if MIM is running in debug mode or not.
   debug: false,
-  // List of registered input methods.
-  im_list: {},
+  // List of main input methods.
+  imlist: {},
+  // List of extra input methods;
+  imextra: {},
   // Global input method data
   im_global: null,
   // Currently selected input method.
@@ -1065,7 +1400,7 @@ var MIM = {
     for (node = node.firstChild; node; node = node.nextSibling)
       if (node.nodeType == 1)
        {
-         var term = Xex.ParseTerm (domain, node);
+         var term = Xex.Term.Parse (domain, node);
          return new MIM.KeySeq (term);
        }
     throw new Xex.ErrTerm (MIM.Error.ParseError, "Invalid keyseq");
@@ -1097,24 +1432,16 @@ var MIM = {
   MIM.Marker.prototype.CharAt = function (ic)
   {
     var p = this.Position (ic);
-    if (p < 0)
-      return ic.GetSurroundingChar (p);
-    else if (pos >= ic.preedit.length)
-      return ic.GetSurroundingChar (p - ic.preedit.length);
+    if (p < 0 || p >= ic.preedit.length)
+      return 0;
     return ic.preedit.charCodeAt (p);
   }
 
-  MIM.NamedMarker = function (name) { this.val = name; }
-  MIM.NamedMarker.prototype = new MIM.Marker ();
-  MIM.NamedMarker.prototype.Position = function (ic)
-  {
-    var p = ic.marker_positions[this.val];
-    return (p == undefined ? 0 : p);
-  }
-  MIM.NamedMarker.prototype.Mark = function (ic)
-  {
-    ic.marker_positions[this.val] = ic.cursor_pos;
-  }
+  MIM.FloatingMarker = function (name) { this.val = name; };
+  var proto = new MIM.Marker ();
+  MIM.FloatingMarker.prototype = proto;
+  proto.Position = function (ic) { return ic.marker_positions[this.val]; };
+  proto.Mark = function (ic) { ic.marker_positions[this.val] = ic.cursor_pos; };
 
   MIM.PredefinedMarker = function (name) { this.val = name; }
   MIM.PredefinedMarker.prototype = new MIM.Marker ();
@@ -1165,7 +1492,7 @@ var MIM = {
   MIM.SurroundMarker = function (name)
   {
     this.val = name;
-    this.distance = parseInt (name.slice (2));
+    this.distance = parseInt (name.slice (1));
     if (isNaN (this.distance))
       throw new Xex.ErrTerm (MIM.Error.ParseError, "Invalid marker: " + name);
   }
@@ -1174,6 +1501,17 @@ var MIM = {
   {
     return ic.cursor_pos + this.distance;
   }
+  MIM.SurroundMarker.prototype.CharAt = function (ic)
+  {
+    if (this.val == '@-0')
+      return -1;
+    var p = this.Position (ic);
+    if (p < 0)
+      return ic.GetSurroundingChar (p);
+    else if (p >= ic.preedit.length)
+      return ic.GetSurroundingChar (p - ic.preedit.length);
+    return ic.preedit.charCodeAt (p);
+  }
 
   MIM.Marker.prototype.Parser = function (domain, node)
   {
@@ -1188,7 +1526,7 @@ var MIM = {
        throw new Xex.ErrTerm (MIM.Error.ParseError,
                               "Invalid marker: " + name);
       }
-    return new MIM.NamedMarker (name);
+    return new MIM.FloatingMarker (name);;
   }
 }) ();
 
@@ -1197,6 +1535,7 @@ MIM.Selector = function (name)
   this.val = name;
 }
 MIM.Selector.prototype = new Xex.Term ('selector');
+
 (function () {
   var selectors = {};
   selectors["@<"] = selectors["@first"] = new MIM.Selector ('@<');
@@ -1232,10 +1571,10 @@ MIM.Rule.prototype.Parser = function (domain, node)
   for (n = node.firstChild; n && n.nodeType != 1; n = n.nextSibling);
   if (! n)
     throw new Xex.ErrTerm (MIM.Error.ParseError, "invalid rule:" + node);
-  var keyseq = Xex.ParseTerm (domain, n);
+  var keyseq = Xex.Term.Parse (domain, n);
   if (keyseq.type != 'keyseq')
     throw new Xex.ErrTerm (MIM.Error.ParseError, "invalid rule:" + node);
-  var actions = Xex.ParseTermList (domain, n.nextSibling);
+  var actions = Xex.Term.Parse (domain, n.nextElement (), null);
   return new MIM.Rule (keyseq, actions);
 }
 MIM.Rule.prototype.toString = function ()
@@ -1248,6 +1587,7 @@ MIM.Map = function (name)
   this.name = name;
   this.rules = new Array ();
 };
+
 (function () {
   var proto = new Xex.Term ('map');
 
@@ -1259,7 +1599,7 @@ MIM.Map = function (name)
     var map = new MIM.Map (name);
     for (var n = node.firstChild; n; n = n.nextSibling)
       if (n.nodeType == 1)
-       map.rules.push (Xex.ParseTerm (domain, n));
+       map.rules.push (Xex.Term.Parse (domain, n));
     return map;
   }
 
@@ -1299,22 +1639,25 @@ MIM.Action.prototype.Run = function (domain)
 
 MIM.Keymap = function ()
 {
+  this.name = 'TOP';
   this.submaps = null;
-  this.actions = null;
 };
+
 (function () {
   var proto = {};
 
-  function add_rule (keymap, rule)
+  function add_rule (keymap, rule, branch_actions)
   {
     var keyseq = rule.keyseq;
     var len = keyseq.val.length;
+    var name = '';
 
     for (var i = 0; i < len; i++)
       {
        var key = keyseq.val[i];
-       var sub;
+       var sub = false;
 
+       name += key.key;
        if (! keymap.submaps)
          keymap.submaps = {};
        else
@@ -1322,17 +1665,19 @@ MIM.Keymap = function ()
        if (! sub)
          keymap.submaps[key.key] = sub = new MIM.Keymap ();
        keymap = sub;
+       keymap.name = name;
       }
-    keymap.actions = rule.actions;
+    keymap.map_actions = rule.actions;
+    keymap.branch_actions = branch_actions;
   }
 
-  proto.Add = function (map)
+  proto.Add = function (map, branch_actions)
   {
     var rules = map.rules;
     var len = rules.length;
 
     for (var i = 0; i < len; i++)
-      add_rule (this, rules[i]);
+      add_rule (this, rules[i], branch_actions);
   }
   proto.Lookup = function (keys, index)
   {
@@ -1355,6 +1700,7 @@ MIM.State = function (name)
   this.name = name;
   this.keymap = new MIM.Keymap ();
 };
+
 (function () {
   var proto = new Xex.Term ('state');
 
@@ -1365,21 +1711,21 @@ MIM.State = function (name)
     if (! name)
       throw new Xex.ErrTerm (MIM.Error.ParseError, "invalid map");
     var state = new MIM.State (name);
-    for (node = node.firstChild; node; node = node.nextSibling)
+    for (node = node.firstElement (); node; node = node.nextElement ())
       {
-       if (node.nodeType != 1)
-         continue;
-       if (node.nodeName == 'branch')
+       if (node.nodeName == 'title')
+         state.title = node.firstChild.nodeValue;
+       else
          {
-           state.keymap.Add (map_list[node.attributes['mname'].nodeValue]);
-           state.keymap.actions = Xex.ParseTermList (domain, node.firstChild);
+           var n = node.firstElement ();
+           if (node.nodeName == 'branch')
+             state.keymap.Add (map_list[node.attributes['mname'].nodeValue],
+                               Xex.Term.Parse (domain, n, null));
+           else if (node.nodeName == 'state-hook')
+             state.enter_actions = Xex.Term.Parse (domain, n, null);
+           else if (node.nodeName == 'catch-all-branch')
+             state.fallback_actions = Xex.Term.Parse (domain, n, null);
          }
-       else if (node.nodeName == 'state-hook')
-         state.enter_actions = Xex.ParseTermList (domain, node.firstChild);
-       else if (node.nodeName == 'catch-all-branch')
-         state.fallback_actions = Xex.ParseTermList (domain, node.firstChild);
-       else if (node.nodeName == 'title')
-         state.title = node.firstChild.nodeValue;
       }
     return state;
   }
@@ -1392,70 +1738,506 @@ MIM.State = function (name)
   MIM.State.prototype = proto;
 }) ();
 
-MIM.im_domain = new Xex.Domain ('input-method', null, null);
-MIM.im_domain.DefType (MIM.KeySeq.prototype);
-MIM.im_domain.DefType (MIM.Marker.prototype);
-MIM.im_domain.DefType (MIM.Selector.prototype);
-MIM.im_domain.DefType (MIM.Rule.prototype);
-MIM.im_domain.DefType (MIM.Map.prototype);
-MIM.im_domain.DefType (MIM.State.prototype);
-
 (function () {
-  var im_domain = MIM.im_domain;
-
-  function Finsert (domain, vari, args)
+  function Block (index, term)
   {
-    domain.context.insert (args[0].val, null);
+    this.Index = index;
+    if (term.IsStr)
+      this.Data = term.val;
+    else if (term.IsList)
+      {
+       this.Data = new Array ();
+       for (var i = 0; i < term.val.length; i++)
+         this.Data.push (term.val[i].val);
+      }
   }
 
-  im_domain.DefSubr (Finsert, "insert", false, 1, 1);
-}) ();
+  Block.prototype.Count = function () { return this.Data.length; }
+  Block.prototype.get = function (i)
+  {
+    return (this.Data instanceof Array ? this.Data[i] : this.Data.charAt (i));
+  }
 
-(function () {
-  var parsers = { };
-  parsers['description'] = function (node)
+  MIM.Candidates = function (candidates, column)
   {
-    this.description = node.firstChild.nodeValue;
+    this.column = column;
+    this.row = 0;
+    this.index = 0;
+    this.total = 0;
+    this.blocks = new Array ();
+
+    for (var i = 0; i < candidates.length; i++)
+      {
+       var block = new Block (this.total, candidates[i]);
+       this.blocks.push (block);
+       this.total += block.Count ();
+      }
   }
-  parsers['title'] = function (node)
+
+  function get_col ()
   {
-    this.title = node.firstChild.nodeValue;
+    return (this.column > 0 ? this.index % this.column
+           : this.index - this.blocks[this.row].Index);
   }
-  parsers['map-list'] = function (node)
+
+  function prev_group ()
   {
-    for (node = node.firstChild; node; node = node.nextSibling)
+    var col = get_col.call (this);
+    var nitems;
+    if (this.column > 0)
+      {
+       this.index -= this.column;
+       if (this.index >= 0)
+         nitems = this.column;
+       else
+         {
+           var lastcol = (this.total - 1) % this.column;
+           this.index = (col < lastcol ? this.total - lastcol + col
+                         : this.total - 1);
+           this.row = this.blocks.length - 1;
+           nitems = lastcol + 1;
+         }
+       while (this.blocks[this.row].Index > this.index)
+         this.row--;
+      }
+    else
       {
-       if (node.nodeType != 1 || node.nodeName != 'map')
-         continue;
-       var map = Xex.ParseTerm (this.domain, node);
-       this.map_list[map.name] = map;
+       this.row = this.row > 0 ? this.row - 1 : this.blocks.length - 1;
+       nitems = this.blocks[this.row].Count ();
+       this.index = (this.blocks[this.row].Index
+                     + (col < nitems ? col : nitems - 1));
       }
+    return nitems;
   }
-  parsers['state-list'] = function (node)
+
+  function next_group ()
   {
-    this.domain.map_list = this.map_list;
-    for (node = node.firstChild; node; node = node.nextSibling)
+    var col = get_col.call (this);
+    var nitems;
+    if (this.column > 0)
+      {
+       this.index += this.column - col;
+       if (this.index < this.total)
+         {
+           if (this.index + col >= this.total)
+             {
+               nitems = this.total - this.index;
+               this.index = this.total - 1;
+             }
+           else
+             {
+               nitems = this.column;
+               this.index += col;
+             }
+         }
+       else
+         {
+           this.index = col;
+           this.row = 0;
+         }
+       while (this.blocks[this.row].Index > this.index)
+         this.row++;
+      }
+    else
       {
-       if (node.nodeType != 1 || node.nodeName != 'state')
-         continue;
-       var state = Xex.ParseTerm (this.domain, node);
-       if (! state.title)
-         state.title = this.title;
-       if (! this.initial_state)
-         this.initial_state = state;
-       this.state_list[state.name] = state;
+       this.row = this.row < this.blocks.length - 1 ? this.row + 1 : 0;
+       nitems = this.blocks[this.row].Count ();
+       this.index = (this.blocks[this.row].Index
+                     + (col < nitems ? col : nitems - 1));
       }
-    delete this.domain.map_list;
+    return nitems;
   }
 
-  MIM.IM = function (lang, name, extra_id, file)
+  function prev ()
   {
-    this.lang = lang;
-    this.name = name;
-    this.extra_id = extra_id;
+    if (this.index == 0)
+      {
+       this.index = this.total - 1;
+       this.row = this.blocks.length - 1;
+      }
+    else
+      {
+       this.index--;
+       if (this.blocks[this.row].Index > this.index)
+         this.row--;
+      }
+    }
+
+  function next ()
+  {
+    this.index++;
+    if (this.index == this.total)
+      {
+       this.index = 0;
+       this.row = 0;
+      }
+    else
+      {
+       var b = this.blocks[this.row];
+       if (this.index == b.Index + b.Count ())
+         this.row++;
+      }
+  }
+
+  function first ()
+  {
+    this.index -= get_col.call (this);
+    while (this.blocks[this.row].Index > this.index)
+      this.row--;
+  }
+
+  function last ()
+  {
+    var b = this.blocks[this.row];
+    if (this.column > 0)
+      {
+       if (this.index + 1 < this.total)
+         {
+           this.index += this.column - get_col.call (this) + 1;
+           while (b.Index + b.Count () <= this.index)
+             b = this.blocks[++this.row];
+         }
+      }
+    else
+      this.index = b.Index + b.Count () - 1;
+  }
+
+  MIM.Candidates.prototype.Current = function ()
+  {
+    var b = this.blocks[this.row];
+    return b.get (this.index - b.Index);
+  }
+
+  MIM.Candidates.prototype.Select = function (selector)
+  {
+    if (selector.type == 'selector')
+      {
+       switch (selector.val)
+         {
+         case '@<': first.call (this); break;
+         case '@>': last.call (this); break;
+         case '@-': prev.call (this); break;
+         case '@+': next.call (this); break;
+         case '@[': prev_group.call (this); break;
+         case '@]': next_group.cal (this); break;
+         default: break;
+         }
+       return this.Current ();
+      }
+    var col, start, end
+    if (this.column > 0)
+      {
+       col = this.index % this.column;
+       start = this.index - col;
+       end = start + this.column;
+      }
+    else
+      {
+       start = this.blocks[this.row].Index;
+       col = this.index - start;
+       end = start + this.blocks[this.row].Count;
+      }
+    if (end > this.total)
+      end = this.total;
+    this.index += selector.val - col;
+    if (this.index >= end)
+      this.index = end - 1;
+    if (this.column > 0)
+      {
+       if (selector.val > col)
+         while (this.blocks[this.row].Index + this.blocks[this.row].Count
+                < this.index)
+           this.row++;
+       else
+         while (this.blocks[this.row].Index > this.index)
+           this.row--;
+      }
+    return this.Current ();
+  }
+}) ();
+
+MIM.im_domain = new Xex.Domain ('input-method', null, null);
+MIM.im_domain.DefType (MIM.KeySeq.prototype);
+MIM.im_domain.DefType (MIM.Marker.prototype);
+MIM.im_domain.DefType (MIM.Selector.prototype);
+MIM.im_domain.DefType (MIM.Rule.prototype);
+MIM.im_domain.DefType (MIM.Map.prototype);
+MIM.im_domain.DefType (MIM.State.prototype);
+
+(function () {
+  var im_domain = MIM.im_domain;
+
+  function Finsert (domain, vari, args)
+  {
+    var text;
+    if (args[0].type == 'integer')
+      text = String.fromCharCode (args[0].val);
+    else
+      text = args[0].val;
+    domain.context.ins (text, null);
+    return args[0];
+  }
+
+  function Finsert_candidates (domain, vari, args)
+  {
+    var ic = domain.context;
+    var gsize = domain.variables['candidates_group_size'];
+    var candidates = new MIM.Candidates (args, gsize ? gsize.Intval : 0);
+    ic.ins (candidates.Current (), candidates);
+    return args[0];
+  }
+
+  function Fdelete (domain, vari, args)
+  {
+    var ic = domain.context;
+    var pos = args[0].IsInt ? args[0].Intval : args[0].Position (ic);
+    return new Xex.IntTerm (ic.del (pos));
+  }
+
+  function Fselect (domain, vari, args)
+  {
+    var ic = domain.context;
+    var can = ic.candidates;
+
+    if (can)
+      {
+       var old_text = can.Current ();
+       var new_text = can.Select (args[0]);
+       ic.rep (old_text, new_text, can);
+      }
+    else
+      Xex.Log ('no candidates at ' + ic.cursor_pos + ' of ' + ic.candidate_table.table.length);
+    return args[0];
+  }
+
+  function Fchar_at (domain, vari, args)
+  {
+    return new Xex.IntTerm (args[0].CharAt (domain.context));
+  }
+
+  function Fmove (domain, vari, args)
+  {
+    var ic = domain.context;
+    var pos = args[0].IsInt ? args[0].val : args[0].Position (ic);
+    ic.move (pos);
+    return new Xex.IntTerm (pos);
+  }
+
+  function Fmark (domain, vari, args)
+  {
+    args[0].Mark (domain.context);
+    return args[0];
+  }
+
+  function Fpushback (domain, vari, args)
+  {
+    var a = (args[0].IsInt ? args[0].Intval ()
+            : args[0].IsStr ? new KeySeq (args[0])
+            : args[0]);
+    domain.context.pushback (a);
+    return args[0];
+  }
+
+  function Fundo  (domain, vari, args)
+  {
+    var ic = domain.context;
+    var n = args.length == 0 ? -2 : args[0].val;
+    if (n < 0)
+      ic.keys.val.splice (ic.keys.length + n, -n);
+    else
+      ic.keys.val.splice (n, ic.keys.length);
+    ic.reset ();
+    return Xex.nil;
+  }
+
+  function Fcommit (domain, vari, args)
+  {
+    domain.context.commit ();
+    return Xex.nil;
+  }
+
+  function Funhandle (domain, vari, args)
+    {
+      domain.context.commit ();
+      return Xex.Fthrow (domain, vari, Xex.CatchTag._mimtag);
+    }
+
+  function Fshift (domain, vari, args)
+  {
+    var ic = domain.context;
+    var state_name = args[0].val;
+    var state = ic.im.state_list[state_name];
+    if (! state)
+      throw ("Unknown state: " + state_name);
+      ic.shift (state);
+    return args[0];
+  }
+
+  function Fsurrounding_flag (domain, vari, args)
+  {
+    return new Xex.IntTerm (-1);
+  }
+
+  im_domain.DefSubr (Finsert, "insert", false, 1, 1);
+  im_domain.DefSubr (Finsert_candidates, "insert-candidates", false, 1, 1);
+  im_domain.DefSubr (Fdelete, "delete", false, 1, 1);
+  im_domain.DefSubr (Fselect, "select", false, 1, 1);
+  //im_domain.DefSubr (Fshow, "show-candidates", false, 0, 0);
+  //im_domain.DefSubr (Fhide, "hide-candidates", false, 0, 0);
+  im_domain.DefSubr (Fmove, "move", false, 1, 1);
+  im_domain.DefSubr (Fmark, "mark", false, 1, 1);
+  im_domain.DefSubr (Fpushback, "pushback", false, 1, 1);
+  //im_domain.DefSubr (Fpop, "pop", false, 0, 0);
+  im_domain.DefSubr (Fundo, "undo", false, 0, 1);
+  im_domain.DefSubr (Fcommit, "commit", false, 0, 0);
+  im_domain.DefSubr (Funhandle, "unhandle", false, 0, 0);
+  im_domain.DefSubr (Fshift, "shift", false, 1, 1);
+  //im_domain.DefSubr (Fshiftback, "shiftback", false, 0, 0);
+  im_domain.DefSubr (Fchar_at, "char-at", false, 1, 1);
+  //im_domain.DefSubr (Fkey_count, "key-count", false, 0, 0);
+  im_domain.DefSubr (Fsurrounding_flag, "surrounding-text-flag", false, 0, 0);
+}) ();
+
+
+(function () {
+  function get_global_var (vname)
+  {
+    if (MIM.im_global.load_status == MIM.LoadStatus.NotLoaded)
+      MIM.im_global.Load ()
+    return MIM.im_global.domain.variables[vname];
+  }
+
+  function include (node)
+  {
+    node = node.firstElement ();
+    if (node.nodeName != 'tags')
+      return null;
+    
+    var lang = null, name = null, extra = null;
+    for (node = node.firstElement (); node; node = node.nextElement ())
+      {
+       if (node.nodeName == 'language')
+         lang = node.firstChild.nodeValue;
+       else if (node.nodeName == 'name')
+         name = node.firstChild.nodeValue;
+       else if (node.nodeName == 'extra-id')
+         extra = node.firstChild.nodeValue;
+      }
+    if (! lang || ! MIM.imlist[lang])
+      return null;
+    if (! extra)
+      {
+       if (! name || ! (im = MIM.imlist[lang][name]))
+         return null;
+      }
+    else
+      {
+       if (! (im = MIM.imextra[lang][extra]))
+         return null;
+      }
+    if (im.load_status != MIM.LoadStatus.Loaded
+       && (im.load_status != MIM.LoadStatus.NotLoaded || ! im.Load ()))
+      return null;
+    return im;
+  }
+
+  var parsers = { };
+
+  parsers['description'] = function (node)
+  {
+    this.description = node.firstChild.nodeValue;
+  }
+  parsers['variable-list'] = function (node)
+  {
+    for (node = node.firstElement (); node; node = node.nextElement ())
+      {
+       var vname = node.attributes['vname'].nodeValue;
+       if (this != MIM.im_global)
+         {
+           var vari = get_global_var (vname);
+           if (vari != null)
+             this.domain.Defvar (vname);
+         }
+       vname = Xex.Term.Parse (this.domain, node)
+      }
+  }
+  parsers['command-list'] = function (node)
+  {
+  }
+  parsers['macro-list'] = function (node)
+  {
+    for (var n = node.firstElement (); n; n = n.nextElement ())
+      if (n.nodeName == 'xi:include')
+       {
+         var im = include (n);
+         if (! im)
+           alert ('inclusion fail');
+         else
+           for (var macro in im.domain.functions)
+             {
+               var func = im.domain.functions[macro];
+               if (func instanceof Xex.Macro)
+                 im.domain.CopyFunc (this.domain, macro);
+             }
+         n = n.previousSibling;
+         node.removeChild (n.nextSibling);
+       }
+    Xex.Term.Parse (this.domain, node.firstElement (), null);
+  }
+  parsers['title'] = function (node)
+  {
+    this.title = node.firstChild.nodeValue;
+  }
+  parsers['map-list'] = function (node)
+  {
+    for (node = node.firstElement (); node; node = node.nextElement ())
+      {
+       if (node.nodeName == 'xi:include')
+         {
+           var im = include (node);
+           if (! im)
+             {
+               alert ('inclusion fail');
+               continue;
+             }
+           for (var mapname in im.map_list)
+             this.map_list[mapname] = im.map_list[mapname];
+         }
+       else
+         {
+           var map = Xex.Term.Parse (this.domain, node);
+           this.map_list[map.name] = map;
+         }
+      }
+  }
+  parsers['state-list'] = function (node)
+  {
+    this.domain.map_list = this.map_list;
+    for (node = node.firstElement (); node; node = node.nextElement ())
+      {
+       if (node.nodeName == 'state')
+         {
+           var state = Xex.Term.Parse (this.domain, node);
+           if (! state.title)
+             state.title = this.title;
+           if (! this.initial_state)
+             this.initial_state = state;
+           this.state_list[state.name] = state;
+         }
+      }
+    delete this.domain.map_list;
+  }
+
+  MIM.IM = function (lang, name, extra_id, file)
+  {
+    this.lang = lang;
+    this.name = name;
+    this.extra_id = extra_id;
     this.file = file;
     this.load_status = MIM.LoadStatus.NotLoaded;
-    this.domain = new Xex.Domain (this.lang + '-' + this.name,
+    this.domain = new Xex.Domain (this.lang + '-'
+                                 + (this.name != 'nil'
+                                    ? this.name : this.extra_id),
                                  MIM.im_domain, null);
   }
 
@@ -1471,10 +2253,8 @@ MIM.im_domain.DefType (MIM.State.prototype);
       this.map_list = {};
       this.initial_state = null;
       this.state_list = {};
-      for (node = node.firstChild; node; node = node.nextSibling)
+      for (node = node.firstElement (); node; node = node.nextElement ())
         {
-         if (node.nodeType != 1)
-           continue;
          var name = node.nodeName;
          var parser = parsers[name];
          if (parser)
@@ -1487,15 +2267,23 @@ MIM.im_domain.DefType (MIM.State.prototype);
 
   MIM.IM.prototype = proto;
 
-  MIM.IC = function (im)
+  MIM.IC = function (im, target)
   {
     if (im.load_status == MIM.LoadStatus.NotLoaded)
       im.Load ();
     if (im.load_status != MIM.LoadStatus.Loaded)
       alert ('im:' + im.name + ' error:' + im.load_status);
     this.im = im;
+    this.target = target;
     this.domain = new Xex.Domain ('context', im.domain, this);
     this.active = true;
+    this.range = new Array ();
+    this.range[0] = this.range[1] = 0;
+    this.state = null;
+    this.initial_state = this.im.initial_state;
+    this.keys = new MIM.KeySeq ();
+    this.marker_positions = new Array ();
+    this.candidate_table = new MIM.CandidateTable ();
     this.reset ();
   }
 
@@ -1504,12 +2292,12 @@ MIM.im_domain.DefType (MIM.State.prototype);
     this.table = new Array ();
   }
 
-  MIM.CandidateTable.prototype.get = function (from)
+  MIM.CandidateTable.prototype.get = function (pos)
   {
     for (var i = 0; i < this.table.length; i++)
       {
        var elt = this.table[i];
-       if (elt.from <= from && elt.to > from)
+       if (elt.from < pos && pos <= elt.to)
          return elt.val;
       }
   }
@@ -1519,8 +2307,7 @@ MIM.im_domain.DefType (MIM.State.prototype);
     for (var i = 0; i < this.table.length; i++)
       {
        var elt = this.table[i];
-       if (elt.from >= from && elt.from < to
-           || elt.to >= from && elt.to < to)
+       if (elt.from < to && elt.to > from)
          {
            elt.from = from;
            elt.to = to;
@@ -1534,6 +2321,8 @@ MIM.im_domain.DefType (MIM.State.prototype);
   MIM.CandidateTable.prototype.adjust = function (from, to, inserted)
   {
     var diff = inserted - (to - from);
+    if (diff == 0)
+      return;
     for (var i = 0; i < this.table.length; i++)
       {
        var elt = this.table[i];
@@ -1550,13 +2339,20 @@ MIM.im_domain.DefType (MIM.State.prototype);
     this.table.length = 0;
   }
 
+  function detach_candidates (ic)
+  {
+    ic.candidate_table.clear ();
+    ic.candidates = null;
+    ic.changed |= (MIM.ChangedStatus.Preedit | MIM.ChangedStatus.CursorPos
+                  | ChangedStatus.CandidateList
+                  | ChangedStatus.CandidateIndex
+                  | ChangedStatus.CandidateShow);
+  }
+
   function set_cursor (prefix, pos)
   {
     this.cursor_pos = pos;
-    if (pos > 0)
-      this.candidates = this.candidate_table.get (pos - 1);
-    else
-      this.candidates = null;
+    this.candidates = this.candidate_table.get (pos);
   }
 
   function save_state ()
@@ -1579,28 +2375,36 @@ MIM.im_domain.DefType (MIM.State.prototype);
     var out = this.keymap.Lookup (this.keys, this.key_head);
     var sub = out.map;
 
-    alert ('handling ' + this.keys.val[this.key_head]);
+    Xex.Log ('handling ' + this.keys.val[this.key_head]
+            + ' in ' + this.state.name + ':' + this.keymap.name);
     this.key_head = out.index;
     if (sub != this.keymap)
       {
+
        restore_state.call (this);
        this.keymap = sub;
-       alert ('submap found, taking map actions:' + sub.actions);
-       if (this.keymap.actions != null)
+       Xex.Log ('submap found');
+       if (this.keymap.map_actions)
          {
-           if (! this.take_actions (this.keymap.actions))
+           Xex.Log ('taking map actions:');
+           if (! this.take_actions (this.keymap.map_actions))
              return false;
          }
-       else if (this.keymap.submaps != null)
+       else if (this.keymap.submaps)
          {
+           Xex.Log ('no map actions');
            for (var i = this.state_key_head; i < this.key_head; i++)
-             this.preedit_replace (this.cursor_pos, this.cursor_pos,
-                                   this.keys.val[i].key, null);
+             {
+               Xex.Log ('inserting key:' + this.keys.val[i].key);
+               this.ins (this.keys.val[i].key, null);
+             }
          }
-       if (this.keymap.submaps == null)
+       if (! this.keymap.submaps)
          {
+           Xex.Log ('terminal:');
            if (this.keymap.branch_actions != null)
              {
+               Xex.Log ('branch actions:');
                if (! this.take_actions (this.keymap.branch_actions))
                  return false;
              }
@@ -1610,21 +2414,29 @@ MIM.im_domain.DefType (MIM.State.prototype);
       }
     else
       {
+       Xex.Log ('no submap');
        var current_state = this.state;
+       var map = this.keymap;
 
-       if (this.keymap.branch_actions != null)
+       if (map.branch_actions)
          {
-           if (! this.take_actions (this.keymap.branch_actions))
+           Xex.Log ('branch actions');
+           if (! this.take_actions (map.branch_actions))
              return false;
          }
-       if (this.state == current_state)
+
+       if (map == this.keymap)
          {
-           if (this.state == this.initial_state
+           Xex.Log ('no state change');
+           if (map == this.initial_state.keymap
                && this.key_head < this.keys.val.length)
-             return false;
-           if (this.keymap != this.state.keymap)
-             this.shift (this.state);
-           else if (this.keymap.branch_actions == null)
+             {
+               Xex.Log ('unhandled');
+               return false;
+             }
+           if (this.keymap != current_state.keymap)
+             this.shift (current_state);
+           else if (this.keymap.actions == null)
              this.shift (this.initial_state);
          }
       }
@@ -1632,42 +2444,31 @@ MIM.im_domain.DefType (MIM.State.prototype);
   }
 
   proto = {
-    init: function ()
+    reset: function ()
     {
-      this.produced = null;
-      this.preedit = '';
       this.cursor_pos = 0;
-      this.marker_positions = {};
-      this.candidates = null;
       this.candidate_show = false;
-      this.state = null;
       this.prev_state = null;
-      this.initial_state = this.im.initial_state;
       this.title = this.initial_state.title;
       this.state_preedit = '';
       this.state_key_head = 0;
       this.state_var_values = {};
       this.state_pos = 0;
-      this.keymap = null;
-      this.keys = new MIM.KeySeq ();
       this.key_head = 0;
+      this.keys.val.length = 0;
       this.key_unhandled = false;
       this.unhandled_key = null;
       this.changed = MIM.ChangedStatus.None;
       this.error_message = '';
       this.title = this.initial_state.title;
-      this.produced = null;
+      this.produced = '';
       this.preedit = '';
-      this.marker_positions = {};
-      this.candidate_table = new MIM.CandidateTable ();
+      this.preedit_saved = '';
+      this.candidate_table.clear ();
       this.candidates = null;
       this.candidate_show = false;
-    },
-
-    reset: function ()
-    {
-      this.init ();
-      this.state_var_values = {};
+      for (var elt in this.marker_positions)
+       this.marker_positions[elt] = 0;
       this.shift (this.initial_state);
     },
 
@@ -1685,19 +2486,66 @@ MIM.im_domain.DefType (MIM.State.prototype);
 
     GetSurroundingChar: function (pos)
     {
-      if (pos < 0 ? this.caret_pos < - pos : this.target.value.length < pos)
-       return 0;
-      return this.target.value.charCodeAt (this.caret_pos + pos);
+      if (pos < 0)
+       {
+         pos += this.range[0];
+         if (pos < 0)
+           return 0;
+       }
+      else
+       {
+         pos += this.range[1];
+         if (pos >= this.target.value.length)
+           return 0;
+       }
+      return this.target.value.charCodeAt (pos);
     },
     
+    DelSurroundText: function (pos)
+    {
+      var text;
+      if (pos < 0)
+       {
+         pos += this.range[0];
+         if (pos <= 0)
+           {
+             pos = 0; text = '';
+           }
+         else
+           text = this.target.value.substring (0, pos);
+         if (this.range[0] < this.target.value.length)
+           text += this.target.value.substring (this.range[0]);
+         this.target.value = text;
+         this.range[1] -= this.range[0] - pos;
+         this.range[0] = pos;
+       }
+      else
+       {
+         pos += this.range[1];
+         text = this.target.value.substring (0, this.range[1]);
+         if (pos >= this.target.value.length)
+           pos = this.target.value.length;
+         else
+           text += this.target.value.substring (pos);
+         this.target.value = text;
+       }
+    },
+
     adjust_markers: function (from, to, inserted)
     {
       var diff = inserted - (to - from);
 
-      for (var m in this.marker_positions)
-       if (this.marker_positions[m] > from)
-         this.marker_positions[m] = (this.marker_positions[m] >= to
-                                     ? pos + diff : from);
+      for (var name in this.marker_positions)
+       {
+         var pos = this.marker_positions[name];
+         if (pos > from)
+           {
+             if (pos >= to)
+               this.marker_positions[name] += diff;
+             else if (pos > from)
+               this.marker_positions[name] = from;
+           }
+       }
       if (this.cursor_pos >= to)
        set_cursor.call (this, 'adjust', this.cursor_pos + diff);
       else if (this.cursor_pos > from)
@@ -1714,30 +2562,47 @@ MIM.im_domain.DefType (MIM.State.prototype);
        this.candidate_table.put (from, from + text.length, candidates)
     },
 
-    insert: function (text, candidates)
+    ins: function (text, candidates)
     {
       this.preedit_replace (this.cursor_pos, this.cursor_pos, text, candidates);
       this.changed = MIM.ChangedStatus.Preedit | MIM.ChangedStatus.CursorPos;
     },
 
+    rep: function (old_text, new_text, candidates)
+    {
+      this.preedit_replace (this.cursor_pos - old_text.length,
+                           this.cursor_pos, new_text, candidates);
+      this.changed = MIM.ChangedStatus.Preedit | MIM.ChangedStatus.CursorPos;
+    },
+
     del: function (pos)
     {
-      if (pos < 0)
+      var deleted = pos - this.cursor_pos;
+      if (pos < this.cursor_pos)
        {
-         this.DelSurroundText (pos);
-         pos = 0;
+         if (pos < 0)
+           {
+             this.DelSurroundText (pos);
+             deleted = - this.cursor_pos;
+             pos = 0;
+           }
+         if (pos < this.cursor_pos)
+           this.preedit_replace (pos, this.cursor_pos, '', null);
        }
-      else if (pos > this.preedit.length)
+      else
        {
-         this.DelSurroundText (pos - this.preedit.length);
-         pos = this.preedit.length;
+         if (pos > this.preedit.length)
+           {
+             this.DelSurroundText (pos - this.preedit.length);
+             deleted = this.preedit.length - this.cursor_pos;
+             pos = this.preedit.length;
+           }
+         if (pos > this.cursor_pos)
+           this.preedit_replace (this.cursor_pos, pos, '', null);
        }
-      if  (pos < this.cursor_pos)
-       this.preedit = (this.predit.substring (0, pos)
-                       + this.preedit.substring (this.cursor_pos));
-      else
-       this.preedit = (this.preedit.substring (0, this.cursor_pos)
-                       + this.predit.substring (pos));
+      if (deleted != 0)
+       this.changed = MIM.ChangedStatus.Preedit | MIM.ChangedStatus.CursorPos;
+      return deleted;
     },
 
     show: function ()
@@ -1806,43 +2671,42 @@ MIM.im_domain.DefType (MIM.State.prototype);
       {
        this.candidate_table.clear ();
        this.produced += this.preedit;
-       this.preedit_replace.call (this, 0, this.preedit.Length, '', null);
+       this.preedit_replace.call (this, 0, this.preedit.length, '', null);
       }
     },
 
     shift: function (state)
     {
       if (state == null)
-      {
-       if (this.prev_state == null)
-         return;
-       state = this.prev_state;
-      }
+        {
+         if (this.prev_state == null)
+           return;
+         state = this.prev_state;
+       }
 
       if (state == this.initial_state)
         {
-         this.commit ();
-         this.keys.val.splice (0, this.key_head);
-         this.key_head = 0;
-         if (state != this.state)
+         if (this.state)
            {
-             this.domain.RestoreValues (this.state_initial_var_values);
-             if (state.enter_actions != null)
-               take_actions.call (state.enter_actions);
+             this.commit ();
+             this.keys.val.splice (0, this.key_head);
+             this.key_head = 0;
+             this.prev_state = null;
            }
-         this.prev_state = null;
        }
       else
-        {
-         if (state != this.state && state.enter_actions != null)
-           take_actions.call (state.enter_actions);
-         this.prev_state = this.state;
+       {
+         if (state != this.state)
+           this.prev_state = this.state;
        }
-      save_state.call (this);
+      if (state != this.state && state.enter_actions)
+       this.take_actions (state.enter_actions);
       if (! this.state || this.state.title != state.title)
        this.changed |= MIM.ChangedStatus.StateTitle;
       this.state = state;
       this.keymap = state.keymap;
+      this.state_key_head = this.key_head;
+      save_state.call (this);
     },
 
     Filter: function (key)
@@ -1864,16 +2728,29 @@ MIM.im_domain.DefType (MIM.State.prototype);
        {
          if (! handle_key.call (this))
            {
-             this.unhandled_key = this.keys.val[this.key_head++];
+             if (this.key_head < this.keys.val.length)
+               {
+                 this.unhandled_key = this.keys.val[this.key_head];
+                 this.keys.val.splice (this.key_head, this.key_head + 1);
+               }
              this.key_unhandled = true;
              break;
            }
          if (++count == 10)
-           break;
+           {
+             this.reset ();
+             this.key_unhandled = true;
+             break;
+           }
        }
-      this.keys.val.splice (0, this.key_head);
-      this.key_head = 0;
-      return (! this.key_unhandled && this.produced.length == 0);
+      if (this.key_unhandled)
+       {
+         this.keys.val.length = 0;
+         this.key_head = this.state_key_head = this.commit_key_head = 0;
+       }
+      return (! this.key_unhandled
+             && this.produced.length == 0
+             && this.preedit.length == 0);
     }
   }
 
@@ -1883,7 +2760,7 @@ MIM.im_domain.DefType (MIM.State.prototype);
   for (node = node.firstChild; node; node = node.nextSibling)
     if (node.nodeName == 'input-method')
       {
-       var lang, name, extra_id, file;
+       var lang = null, name = null, extra_id = null, file = null;
 
        for (var n = node.firstChild; n; n = n.nextSibling)
          {
@@ -1896,47 +2773,480 @@ MIM.im_domain.DefType (MIM.State.prototype);
            else if (n.nodeName == 'filename')
              file = n.firstChild.nodeValue;
          }
-       if (! MIM.im_list[lang])
-         MIM.im_list[lang] = {};
-       MIM.im_list[lang][name] = new MIM.IM (lang, name, extra_id, file);
+       if (name && name != 'nil')
+         {
+           if (! MIM.imlist[lang])
+             MIM.imlist[lang] = {};
+           MIM.imlist[lang][name] = new MIM.IM (lang, name, extra_id, file);
+         }
+       else if (extra_id && extra_id != 'nil')
+         {
+           if (! MIM.imextra[lang])
+             MIM.imextra[lang] = {};
+           MIM.imextra[lang][extra_id] = new MIM.IM (lang, name, extra_id, file);
+         }
       }
+  if (MIM.imextra.t && MIM.imextra.t.global)
+    MIM.im_global = MIM.imextra.t.global;
+  else
+    {
+      MIM.im_global = new MIM.IM ('t', 'nil', 'global', null);
+      MIM.im_global.load_status = MIM.LoadStatus.Error;
+    }
   node = undefined;
 }) ();
 
 (function () {
+  var keys = new Array ();
+  keys[0x09] = 'tab';
+  keys[0x08] = 'backspace';
+  keys[0x0D] = 'return';
+  keys[0x1B] = 'escape';
+  keys[0x20] = 'space';
+  keys[0x21] = 'pageup';
+  keys[0x22] = 'pagedown';
+  keys[0x23] = 'end';
+  keys[0x24] = 'home';
+  keys[0x25] = 'left';
+  keys[0x26] = 'up';
+  keys[0x27] = 'right';
+  keys[0x28] = 'down';
+  keys[0x2D] = 'insert';
+  keys[0x2E] = 'delete';
+  for (var i = 1; i <= 12; i++)
+    keys[111 + i] = "f" + i;
+  keys[0x90] = "numlock";
+  keys[0xF0] = "capslock";
+
+  MIM.decode_key_event = function (event)
+  {
+    var key = ((event.type == 'keydown' || event.keyCode) ? event.keyCode
+              : event.charCode ? event.charCode
+              : false);
+    if (! key)
+      return false;
+    if (event.type == 'keydown')
+      {
+       key = keys[key];
+       if (! key)
+         return false;
+       if (event.shiftKey) key = "S-" + key ;
+      }
+    else
+      key = String.fromCharCode (key);
+    if (event.altKey) key = "A-" + key ;
+    if (event.ctrlKey) key = "C-" + key ;
+    return new MIM.Key (key);
+  }
+}) ();
 
-  MIMTEST = function (name) { this.nam = name; };
+MIM.add_event_listener
+  = (window.addEventListener
+     ? function (target, type, listener) {
+       target.addEventListener (type, listener, false);
+     }
+     : window.attachEvent
+     ? function (target, type, listener) {
+       target.attachEvent ('on' + type,
+                          function() {
+                            listener.call (target, window.event);
+                          });
+     }
+     : function (target, type, listener) {
+       target['on' + type]
+        = function (e) { listener.call (target, e || window.event); };
+     });
+
+MIM.debug_print = function (event, ic)
+{
+  if (! MIM.debug)
+    return;
+  if (! MIM.debug_nodes)
+    {
+      MIM.debug_nodes = new Array ();
+      MIM.debug_nodes['keydown'] = document.getElementById ('keydown');
+      MIM.debug_nodes['keypress'] = document.getElementById ('keypress');
+      MIM.debug_nodes['status0'] = document.getElementById ('status0');
+      MIM.debug_nodes['status1'] = document.getElementById ('status1');
+      MIM.debug_nodes['keymap0'] = document.getElementById ('keymap0');
+      MIM.debug_nodes['keymap1'] = document.getElementById ('keymap1');
+      MIM.debug_nodes['preedit0'] = document.getElementById ('preedit0');
+      MIM.debug_nodes['preedit1'] = document.getElementById ('preedit1');
+    }
+  var target = event.target;
+  var code = event.keyCode;
+  var ch = event.type == 'keydown' ? 0 : event.charCode;
+  var key = MIM.decode_key_event (event);
+  var index;
+
+  MIM.debug_nodes[event.type].innerHTML = "" + code + "/" + ch + " : " + key;
+  index = (event.type == 'keydown' ? '0' : '1');
+  if (ic)
+    MIM.debug_nodes['status' + index].innerHTML = ic.im.load_status;
+  else
+    MIM.debug_nodes['status' + index].innerHTML = 'no IM';
+  MIM.debug_nodes['keymap' + index].innerHTML = ic.state.name;
+  MIM.debug_nodes['preedit' + index].innerHTML = ic.preedit;
+  if (index == 0)
+    {
+      MIM.debug_nodes.keypress.innerHTML = '';
+      MIM.debug_nodes.status1.innerHTML = '';
+      MIM.debug_nodes.keymap1.innerHTML = '';
+      MIM.debug_nodes.preedit1.innerHTML = ''
+    }
+};
 
-  function testshow () { alert (this.nam); };
+MIM.get_range = function (target, ic)
+{
+  var from, to;
+  if (target.selectionStart != null) // for Mozilla
+    {
+      from = target.selectionStart;
+      to = target.selectionEnd;
+    }
+  else                         // for IE
+    {
+      var r = document.selection.createRange ();
+      var rr = r.duplicate ();
 
-  MIMTEST.prototype.show = function () { testshow.call (this); }
-  MIMTEST.prototype.cut = function (from, to) {
-    this.val.splice (from, to -from); return this.val; }
+      rr.moveToElementText (target);
+      rr.setEndPoint ('EndToEnd', range);
+      from = rr.text.length - r.text.length;
+      to = rr.text.length;
+    }
+  if (ic.range[0] == from && ic.range[1] == to
+      && (to == from || target.value.substring (from, to) == ic.preedit))
+    return true;
+  ic.range[0] = from;
+  ic.range[1] = to;
+  return false;
+}
 
-  MIMTEST2 = function (name) { this.nam = name;
-                              this.val = new Array (1,2,3,4,5);}
+MIM.set_caret = function (target, ic)
+{
+  if (target.setSelectionRange)        // Mozilla
+    {
+      var scrollTop = target.scrollTop;
+      target.setSelectionRange (ic.range[0], ic.range[1]);
+      target.scrollTop = scrollTop;
+    }
+  else                         // IE
+    {
+      var range = target.createTextRange ();
+      range.moveStart ('character', ic.range[0]);
+      range.moveEnd ('character', ic.range[1]);
+      range.select ();
+    }
+};
 
-  MIMTEST2.prototype = MIMTEST.prototype;
+MIM.update = function (target, ic)
+{
+  var text = target.value;
+  target.value = (text.substring (0, ic.range[0])
+                 + ic.produced
+                 + ic.preedit
+                 + text.substring (ic.range[1]));
+  ic.range[0] += ic.produced.length;
+  ic.range[1] = ic.range[0] + ic.preedit.length;
+  MIM.set_caret (target, ic);
+};
 
-  var x = new MIMTEST2 ('test2');
-}) ();
+MIM.reset_ic = function (event)
+{
+  if (event.target.mim_ic)
+    {
+      var target = event.target;
+      var ic = target.mim_ic;
+      if (ic.preedit.length > 0)
+       event.target.setSelectionRange (ic.range[1], ic.range[1]);
+      ic.reset ();
+    }
+};
 
-MIM.test = function ()
+MIM.keydown = function (event)
+{
+  var target = event.target;
+  if (target.id == 'log')
+    return;
+  if (! (target.type == "text" || target.type == "textarea"))
+    return;
+
+  var ic = target.mim_ic;
+  if (! ic || ic.im != MIM.current)
+    {
+      target.mim_ic = null;
+      Xex.Log ('creating IC');
+      ic = new MIM.IC (MIM.current, target);
+      if (ic.im.load_status != MIM.LoadStatus.Loaded)
+       return;
+      target.mim_ic = ic;
+      MIM.add_event_listener (target, 'blur', MIM.reset_ic);
+      MIM.get_range (target, ic)
+    }
+  else
+    {
+      if (! MIM.get_range (target, ic))
+       ic.reset ();
+    }
+  MIM.debug_print (event, ic);
+  ic.key = MIM.decode_key_event (event);
+};
+
+MIM.keypress = function (event)
+{
+  var target = event.target;
+  if (target.id == 'log')
+    return;
+  if (! (target.type == "text" || target.type == "textarea"))
+    return;
+
+  var ic = target.mim_ic;
+  var i;
+
+  try {
+    if (ic.im.load_status != MIM.LoadStatus.Loaded)
+      return;
+    if (! ic.key)
+      ic.key = MIM.decode_key_event (event);
+    if (! ic.key)
+      {
+       ic.reset ();
+       return;
+      }
+    
+    Xex.Log ("filtering " + ic.key);
+    var result = ic.Filter (ic.key);
+    MIM.update (target, ic);
+    if (! ic.key_unhandled)
+      event.preventDefault ();
+  } finally {
+    MIM.debug_print (event, ic);
+  }
+  return;
+};
+
+MIM.Lang = {
+  European: new Array ('de', 'fr'),
+  MiddleEast: new Array ('ar', 'he'),
+  SouthAsia: new Array ('hi'),
+  SouthEastAsia: new Array ('th'),
+  EastAsia: new Array ('ja', 'zh'),
+  Other: new Array ()
+};
+
+// Other
+// am
+// ath
+// bla
+// cr
+// el
+// eo
+// iu
+// nsk
+// oj
+
+// Middle Eastern
+// ar
+// dv
+// fa
+// he
+// hi
+// kk
+// ps
+// ug
+// yi*
+
+// South Asian
+// as
+// bn
+// bo
+// gu
+// kn
+// ks
+// ml
+// mr
+// ne
+// or
+// pa
+// sd
+// sa
+// si
+// ta
+// te
+// ur
+
+// European
+// cs
+// da
+// eo
+// fr
+// hr
+// hy
+// kk
+// ru
+// sk
+// sr
+// sv
+// vi* 
+// yi*
+
+// East Asian
+// ii
+// ja
+// ko
+// zh
+
+// SouthEast Asian
+// km
+// lo
+// my
+// tai
+// th
+// vi*
+
+MIM.select_im = function (event)
+{
+  var target = event.target.parentNode;
+  while (target.tagName != "mim-select-im")
+    target = target.parentNode;
+  var idx = 0;
+  var im = false;
+  for (var lang in MIM.imlist)
+    for (var name in MIM.imlist[lang])
+      if (idx++ == target.selectedIndex)
+       {
+         im = MIM.imlist[lang][name];
+         break;
+       }
+  document.getElementsByTagName ('body')[0].removeChild (target);
+  target.target.focus ();
+  if (im && im != MIM.current)
+    {
+      MIM.current = im;
+      Xex.Log ('select IM: ' + im.name);
+    }
+};
+
+MIM.destroy_menu = function (event)
+{
+  if (event.target.tagName == "mim-select-im")
+    document.getElementsByTagName ('body')[0].removeChild (event.target);
+};
+
+MIM.select_menu = function (event)
 {
-  var im = MIM.im_list['t']['latn-post'];
-  var ic = new MIM.IC (im);
+  var target = event.target;
+  var sel;
 
-  document.AIM = im;
+  if (! ((target.type == "text" || target.type == "textarea")
+        && event.which == 1 && event.ctrlKey))
+    return;
+  if (! sel)
+    {
+      sel = document.createElement ('select');
+      sel.onclick = MIM.select_im;
+      sel.onmouseout = MIM.destroy_menu;
+      sel.style.position='absolute';
+      sel.style.left = (event.clientX - 10) + "px";
+      sel.style.top = (event.clientY - 10) + "px";
+      sel.target = target;
+      var idx = 0;
+      for (var lang in MIM.imlist)
+       for (var name in MIM.imlist[lang])
+         {
+           var option = document.createElement ('option');
+           var imname = lang + "-" + name;
+           option.appendChild (document.createTextNode (imname));
+           option.value = imname;
+           sel.appendChild (option);
+           if (MIM.imlist[lang][name] == MIM.current)
+             sel.selectedIndex = idx;
+           idx++;
+         }
+      sel.size = idx;
+    }
+  document.getElementsByTagName ('body')[0].appendChild (sel);
+};
+
+MIM.select_menu = function (event)
+{
+  var target = event.target;
+  var menu;
+
+  if (! ((target.type == "text" || target.type == "textarea")
+        && event.which == 1 && event.ctrlKey))
+    return;
+  if (! menu)
+    {
+      menu = document.createElement ('ul');
+      menu.style.margin = '0';
+      menu.style.padding = '0';
+      menu.id = 'mim-select-im';
+      menu.onclick = MIM.select_im;
+      menu.onmouseout = MIM.destroy_menu;
+      menu.style.position='absolute';
+      menu.style.left = (event.clientX - 10) + "px";
+      menu.style.top = (event.clientY - 10) + "px";
+      menu.style['border-bottom'] = '1px solid #ccc';
+      menu.style['background-color'] = 'white';
+      menu.target = target;
+      var idx = 0;
+      for (var lang in MIM.imlist)
+       {
+         var li = document.createElement ('li');
+         li.style.position = 'relative';
+         li.style['list-style']= 'none';
+         li.style.margin = '0';
+         li.style.padding = '0';
+         li.onmouseover = function ()
+         {
+           this.style.backgroundColor = 'yellow';
+           var children = this.getElementsByTagName ('ul');
+           for (var i = children.length - 1; i >= 0; i--)
+             children[i].display = 'block';
+         };
+         li.onmouseout = function () { this.style.backgroundColor = 'white'; };
+         li.appendChild (document.createTextNode (lang));
+         var sub = document.createElement ('ul');
+         sub.style.position = 'absolute';
+         sub.style.top = '0px';
+         sub.style.display = 'none';
+         li.appendChild (sub);
+         for (var name in MIM.imlist[lang])
+           {
+             var sub_li = document.createElement ('li');
+             var imname = '  ' + lang + "-" + name;
+             sub_li.style.position = 'absolute';
+             sub_li.style.top = '0px';
+             sub_li.style['list-style']= 'none';
+             sub_li.style.display = 'none';
+             sub_li.appendChild (document.createTextNode (imname));
+             sub.appendChild (sub_li);
+             if (MIM.imlist[lang][name] == MIM.current)
+               menu.selectedIndex = idx;
+             idx++;
+           }
+         menu.appendChild (li);
+       }
+      menu.size = idx;
+    }
+  document.getElementsByTagName ('body')[0].appendChild (menu);
+};
+
+MIM.test = function ()
+{
+  var im = MIM.imlist['t']['latn-post'];
+  var ic = new MIM.IC (im, null);
 
   ic.Filter (new MIM.Key ('a'));
   ic.Filter (new MIM.Key ("'"));
 
   if (true)
-    document.getElementById ('text').value = ic.preedit;
+    document.getElementById ('text').value = ic.produced + ic.preedit;
   else {
     try {
       document.getElementById ('text').value
-       = Xex.ParseTerm (domain, body).Eval (domain).toString ();
+       = Xex.Term.Parse (domain, body).Eval (domain).toString ();
     } catch (e) {
       if (e instanceof Xex.ErrTerm)
        alert (e);
@@ -1944,3 +3254,21 @@ MIM.test = function ()
     }
   }
 }
+
+
+MIM.init = function ()
+{
+  MIM.add_event_listener (window, 'keydown', MIM.keydown);
+  MIM.add_event_listener (window, 'keypress', MIM.keypress);
+  MIM.add_event_listener (window, 'mousedown', MIM.select_menu);
+  if (window.location == 'http://localhost/mim/index.html')
+    MIM.server = 'http://localhost/mim';
+  MIM.current = MIM.imlist['vi']['telex'];
+};
+
+MIM.init_debug = function ()
+{
+  MIM.debug = true;
+  Xex.LogNode = document.getElementById ('log');
+  MIM.init ();
+};