using System; using System.Collections; using System.Collections.Generic; using System.Reflection; using System.IO; using System.Xml; using M17N; using M17N.Core; using M17N.Input; using Xex = System.Xml.Xexpression; namespace M17N.Input { public class MInputMethod { // Delegaes public delegate bool Callback (Context ic, MPlist args); internal static Xex.Domain im_domain = new Xex.Domain ("input-method", null); private static MSymbol Minput_method = "input-method"; private static MSymbol Mdescription = "description"; private static MSymbol Mvariable = "variable"; private static MSymbol Mcommand = "command"; private static MSymbol Mmodule = "module"; private static MSymbol Mtitle = "title"; private static MSymbol Minclude = "include"; private static MSymbol Mmacro = "macro"; private static MSymbol Mmap = "map"; private static MSymbol Mmap_list = "map-list"; private static MSymbol Mstate = "state"; internal static MSymbol Mcandidates = "candidates"; private static MSymbol Mat_minus_zero = "@-0"; private static MSymbol Matat = "@@"; private static Xex.Symbol Qxi_include = "xi:include"; private static Xex.Symbol Qmap = "map"; private static Xex.Symbol Qrule = "rule"; private static Xex.Symbol Qkeyseq = "keyseq"; private static Xex.Symbol Qprogn = "progn"; private static Xex.Symbol Qcatch = "catch"; private static Xex.Symbol Qinsert = "insert"; private static Xex.Symbol Qinsert_candidates = "insert-candidates"; private static Xex.Symbol Qchar_at = "char-at"; private static Xex.Symbol Qselect = "select"; private static Xex.Symbol Qdelete = "delete"; private static Xex.Symbol Qshift = "shift"; private static Xex.Symbol Qmove = "move"; private static Xex.Symbol Qmark = "mark"; private static Xex.Symbol Qset = "set"; private static Xex.Symbol Qadd = "add"; private static Xex.Symbol Qsub = "sub"; private static Xex.Symbol Qmul = "mul"; private static Xex.Symbol Qdiv = "div"; private static Xex.Symbol Qcond = "cond"; private static Xex.Symbol Qsname = "sname"; private static Xex.Symbol Qmname = "mname"; private static Xex.Symbol Qstate_hook = "state-hook"; private static Xex.Symbol Qcatch_all_branch = "catch-all-branch"; private static Xex.Symbol Qbranch = "branch"; private static Xex.Symbol Qstate = "state"; private static Xex.Symbol Qtitle = "title"; private static Xex.Symbol Qeq = "="; private static Xex.Symbol Qeqeq = "=="; private static Xex.Symbol Qhide = "hide"; private static Xex.Symbol Qhide_candidates = "hide-candidates"; private static Xex.Symbol Qshow = "show"; private static Xex.Symbol Qshow_candidates = "show-candidates"; private static Xex.Symbol Qkey_count = "key-count"; private static Xex.Symbol Qsurrounding_text_flag = "surrounding-text-flag"; private static Xex.Symbol Qcandidates_group_size = "candidates-group-size"; private static Xex.Term Tnil = new Xex.Term ((Xex.Symbol) "nil"); private static Xex.Term Tcatch_tag = new Xex.Term ((Xex.Symbol) "@mimtag"); private static Dictionary im_table = new Dictionary (); internal static MInputMethod im_global = null; [FlagsAttribute] protected enum LoadStatus { None = 0x00, Header = 0x01, Body = 0x02, Full = 0x03, Error = 0x04, }; [FlagsAttribute] public enum ChangedStatus { None = 0x00, StateTitle = 0x01, PreeditText = 0x02, CursorPos = 0x04, CandidateList = 0x08, CandidateIndex = 0x10, CandidateShow = 0x20, Preedit = PreeditText | CursorPos, Candidate = CandidateList | CandidateIndex | CandidateShow, } private static ChangedStatus CandidateAll = (ChangedStatus.CandidateList | ChangedStatus.CandidateIndex | ChangedStatus.CandidateShow); [FlagsAttribute] public enum KeyModifier { None = 0x00000000, Shift_L = 0x00400000, Shift_R = 0x00800000, Shift = 0x00C00000, Control_L = 0x01000000, Control_R = 0x02000000, Control = 0x03000000, Alt_L = 0x04000000, Alt_R = 0x08000000, Alt = 0x0C000000, AltGr = 0x10000000, Super = 0x20000000, Hyper = 0x40000000, High = 0x70000000, All = 0x7FC00000, }; public struct Key { internal uint key; private static Dictionary keysyms = new Dictionary (); private static Dictionary keymodifiers = new Dictionary (); private static uint keysym_base = 0x200000; private static uint char_mask = ~((uint) KeyModifier.All); public static readonly Key Reload; static Key () { keysyms["null"] = 0x00; keysyms["bs"] = keysyms["backspace"] = 0x08; keysyms["tab"] = 0x09; keysyms["lf"] = keysyms["linefeed"] = 0x10; keysyms["cr"] = keysyms["return"] = keysyms["enter"] = 0x13; keysyms["esc"] = keysyms["escape"] = 0x1B; keysyms["spc"] = keysyms["space"] = 0x20; keysyms["del"] = keysyms["delete"] = 0x7F; keymodifiers["shift-l"] = KeyModifier.Shift_L; keymodifiers["shift-r"] = KeyModifier.Shift_R; keymodifiers["shift"] = KeyModifier.Shift; keymodifiers["control-l"] = KeyModifier.Control_L; keymodifiers["control-r"] = KeyModifier.Control_R; keymodifiers["control"] = KeyModifier.Control; keymodifiers["alt-l"] = KeyModifier.Alt_L; keymodifiers["alt-r"] = KeyModifier.Alt_R; keymodifiers["alt"] = KeyModifier.Alt; keymodifiers["altgr"] = KeyModifier.AltGr; keymodifiers["super"] = KeyModifier.Super; keymodifiers["hyper"] = KeyModifier.Hyper; Reload = new Key ((MSymbol) "-reload"); } private static uint decode_keysym (MSymbol keysym) { uint key; string name = keysym.Name; if (name.Length == 1) return name[0]; name = name.ToLower (); if (! keysyms.TryGetValue (name, out key)) keysyms[name] = key = keysym_base++; return key; } private static uint combine_modifiers (uint c, KeyModifier modifiers) { if (c < 0x7F && c != 0x20) { if ((modifiers & KeyModifier.Shift) != KeyModifier.None && Char.IsLower ((char) c)) { modifiers &= ~KeyModifier.Shift; c = Char.ToUpper ((char) c); } if ((modifiers & KeyModifier.Control) != KeyModifier.None) { modifiers &= ~KeyModifier.Control; c &= 0x1F; } } return c | (uint) modifiers; } public Key (uint c) { key = c; } public Key (int c) { key = (uint) c; } public Key (uint c, KeyModifier modifiers) { key = combine_modifiers (c, modifiers); } public Key (MSymbol keysym, KeyModifier modifiers) { key = combine_modifiers (decode_keysym (keysym), modifiers); } public Key (MSymbol keysym) { string str = keysym.Name; int len = str.Length; int i; KeyModifier modifiers = KeyModifier.None; for (i = 0; i + 2 < len && str[i + 1] == '-'; i += 2) { if (str[i] == 'S') modifiers |= KeyModifier.Shift; else if (str[i] == 'C') modifiers |= KeyModifier.Control; else if (str[i] == 'A') modifiers |= KeyModifier.Alt; else if (str[i] == 'G') modifiers |= KeyModifier.AltGr; else if (str[i] == 's') modifiers |= KeyModifier.Super; else if (str[i] == 'H') modifiers |= KeyModifier.Hyper; } if (i + 1 == len) key = combine_modifiers (str[i], modifiers); else key = combine_modifiers (decode_keysym (keysym), modifiers); } public Key (MPlist plist) { KeyModifier modifiers = KeyModifier.None; MPlist p; for (p = plist; ! p.IsEmpty; p = p.next) { if (p.IsInteger) { if (! p.next.IsEmpty) throw new Exception ("Invalid Key: " + plist); break; } else if (! p.IsSymbol) throw new Exception ("Invalid Key: " + plist); else { string name = p.Symbol.Name.ToLower (); KeyModifier m; if (! keymodifiers.TryGetValue (name, out m)) break; modifiers |= m; } } if (p.IsEmpty || ! p.next.IsEmpty) throw new Exception ("Invalid Key: " + plist); if (p.IsInteger) key = combine_modifiers ((uint) p.Integer, modifiers); else key = combine_modifiers (decode_keysym (p.Symbol), modifiers); } public bool HasModifier { get { return ((key & (uint) KeyModifier.All) != 0); } } public static bool operator== (Key k1, Key k2) { return k1.key == k2.key; } public static bool operator!= (Key k1, Key k2) { return k1.key != k2.key; } public override bool Equals (object o) { return key == ((Key) o).key; } public override int GetHashCode () { return (int) key; } public bool Match (Key k) { if (k.key == key) return true; if ((k.key & char_mask) != (key & char_mask)) return false; KeyModifier m1 = ((KeyModifier) key) & KeyModifier.All; KeyModifier m2 = ((KeyModifier) k.key) & KeyModifier.All; return (((m1 & KeyModifier.Shift) == (m2 & KeyModifier.Shift) || ((m1 & KeyModifier.Shift) == KeyModifier.Shift && (m2 & KeyModifier.Shift) != KeyModifier.None)) && ((m1 & KeyModifier.Control) == (m2 & KeyModifier.Control) || ((m1 & KeyModifier.Control) == KeyModifier.Control && (m2 & KeyModifier.Control) != KeyModifier.None)) && ((m1 & KeyModifier.Alt) == (m2 & KeyModifier.Alt) || ((m1 & KeyModifier.Alt) == KeyModifier.Alt && (m2 & KeyModifier.Alt) != KeyModifier.None)) && ((m1 & KeyModifier.High) == (m2 & KeyModifier.High))); } public int ToChar () { return (key & 0x3FFFFF) <= 0x1FFFFF ? (int) (key & 0x1FFFFF) : -1; } public override string ToString () { int c = ToChar (); MText mt = null; if (c < 0x20) foreach (KeyValuePair kv in keysyms) if ((key & 0x3FFFFF) == kv.Value) { mt = kv.Key; break; } if (mt == null) mt = new MText (c); KeyModifier m = ((KeyModifier) key) & KeyModifier.All; if (m != KeyModifier.None) { if ((m & KeyModifier.Shift) != KeyModifier.None) mt.Ins (0, "S-"); if ((m & KeyModifier.Control) != KeyModifier.None) mt.Ins (0, "C-"); if ((m & KeyModifier.Alt) != KeyModifier.None) mt.Ins (0, "A-"); if ((m & KeyModifier.AltGr) != KeyModifier.None) mt.Ins (0, "G-"); if ((m & KeyModifier.Super) != KeyModifier.None) mt.Ins (0, "s-"); if ((m & KeyModifier.Hyper) != KeyModifier.None) mt.Ins (0, "H-"); } return (string) mt; } } internal class KeySeq : Xex.TermValue { private static Xex.Symbol name = "keyseq"; public static Xex.Symbol Name { get { return name; } } public List keyseq = new List (); public override Xex.TermValue Clone () { KeySeq ks = new KeySeq (); ks.keyseq.InsertRange (0, keyseq); return ks; } public KeySeq () { } public KeySeq (MPlist plist) { foreach (MPlist p in plist) { if (p.IsSymbol) keyseq.Add (new Key (p.Symbol)); else if (p.IsInteger) keyseq.Add (new Key ((char) p.Integer)); else if (p.IsPlist) keyseq.Add (new Key (p.Plist)); else throw new Exception ("Invalid Key Sequence: " + plist); } } public KeySeq (MText mt) : base () { for (int i = 0; i < mt.Length; i++) keyseq.Add (new Key ((uint) mt[i])); } public KeySeq (List list) { int len = list.Count; for (int i = 0; i < len; i++) { if (list[i].IsInt) keyseq.Add (new Key (list[i].Intval)); else if (list[i].IsStr) keyseq.Add (new Key (list[i].Strval.ToString ())); else if (list[i].IsSymbol) keyseq.Add (new Key ((string) list[i].Symval)); else throw new Exception ("Invalid key: " + list[i]); } } public static Xex.TermValue Parser (Xex.Domain domain, XmlNode node) { Xex.Term term = Xex.Parse (domain, node.FirstChild); term = Xex.Eval (domain, term); return (term.IsStr ? new KeySeq ((MText) term.Strval.ToString ()) : new KeySeq (term.Listval)); } public override bool Equals (object obj) { KeySeq ks = obj as KeySeq; if (ks == null || ks.keyseq.Count != keyseq.Count) return false; for (int i = 0; i < keyseq.Count; i++) if (keyseq[i] != ks.keyseq[i]) return false; return true; } public override int GetHashCode () { int code = 0; for (int i = 0; i < keyseq.Count; i++) code ^= keyseq[i].GetHashCode (); return code; } public override string ToString () { MText mt; foreach (Key key in keyseq) if (key.HasModifier || key.ToChar () < 0x20) { mt = "("; foreach (Key k in keyseq) { if (mt.Length > 1) mt.Cat (' '); mt.Cat (k.ToString ()); } return (string) mt.Cat (")"); } mt = "\""; foreach (Key k in keyseq) { int c = k.ToChar (); if (c == '\\' || c == '"') mt.Cat ('\\'); mt.Cat (c); } return (string) mt.Cat ("\""); } } public class Command { public MSymbol name; public MText description; internal List keys; public Command (MPlist p) { name = p.Symbol; p = p.Next; description = parse_description (p); if (description == null) description = "No description"; keys = new List (); for (p = p.next; ! p.IsEmpty; p = p.next) { if (p.IsMText) keys.Add (new KeySeq (p.Text)); else if (p.IsPlist) keys.Add (new KeySeq (p.Plist)); } } public Command (XmlNode node) { name = node.Attributes[0].Value; keys = new List (); for (node = node.FirstChild; node != null; node = node.NextSibling) { if (node.Name == "description") description = parse_description (node); else if (node.Name == "keyseq") keys.Add ((KeySeq) KeySeq.Parser (null, node)); } } public override string ToString () { string str = "(" + name + " \"" + (string) description; foreach (KeySeq keyseq in keys) str += " " + keyseq; return str + ")"; } } internal class Plugin { private string name; private Assembly assembly; private Type plugin_type; public Plugin (string name) { this.name = name; } public MethodInfo GetMethod (Xex.Symbol name) { if (assembly == null) { assembly = Assembly.LoadFrom (name + ".dll"); plugin_type = assembly.GetType ("M17n.MInputMethod.Plugin"); } MethodInfo info = plugin_type.GetMethod ((string) name); if (info == null) throw new Exception ("Invalid plugin method: " + name); return info; } public override string ToString () { return String.Format ("(module {0}", name); } } internal class PluginMethod : Xex.Function { private Plugin plugin; private MethodInfo method_info; object[] parameters = new object[2]; public PluginMethod (Plugin plugin, string name) : base ((Xex.Symbol) name, false, 0, -1) { this.plugin = plugin; } public override Xex.Term Call (Xex.Domain domain, Xex.Variable vari, Xex.Term[] args) { args = (Xex.Term[]) args.Clone (); for (int i = 0; i < args.Length; i++) { args[i] = Xex.Eval (domain, args[i]); if (domain.Thrown ()) return args[i]; } if (method_info == null) method_info = plugin.GetMethod (name); parameters[0] = domain.context; parameters[1] = args; return (Xex.Term) method_info.Invoke (null, parameters); } } internal abstract class Marker : Xex.TermValue { private static Xex.Symbol name = "marker"; public static Xex.Symbol Name { get { return name; } } private MSymbol mname; private Marker (MSymbol mname) { this.mname = mname; } public abstract int Position (Context ic); public virtual void Mark (Context ic) { throw new Exception ("Can't set predefined marker: " + mname); } public virtual int CharAt (Context ic) { int pos = Position (ic); return ((pos >= 0 && pos < ic.preedit.Length) ? ic.preedit[pos] : -1); } public override string ToString () { return "" + mname + ""; } public static Xex.TermValue Parser (Xex.Domain domain, XmlNode node) { return Get ((MSymbol) node.InnerText); } public override bool Equals (object obj) { Marker m = obj as Marker; return (m != null && m.mname == mname); } public override int GetHashCode () { return mname.GetHashCode (); } public class Named : Marker { public Named (MSymbol mname) : base (mname) { } public override int Position (Context ic) { MPlist p = ic.marker_positions.Find (mname); return (p == null ? 0 : p.Integer); } public override void Mark (Context ic) { ic.marker_positions.Put (mname, ic.cursor_pos); } } public class Predefined : Marker { char tag; public Predefined (char tag) : base ("@" + tag) { this.tag = tag; } public override int Position (Context ic) { switch (tag) { case '<': return 0; case '>': return ic.preedit.Length; case '-': return ic.cursor_pos - 1; case '+': return ic.cursor_pos + 1; case '[': if (ic.cursor_pos > 0) { int pos = ic.cursor_pos; int to; ic.preedit.FindProp (Mcandidates, pos - 1, out pos, out to); return pos; } return 0; case ']': if (ic.cursor_pos < ic.preedit.Length - 1) { int pos = ic.cursor_pos; int from; ic.preedit.FindProp (Mcandidates, pos, out from, out pos); return pos; } return ic.preedit.Length; default: return tag - '0'; } } } public class PredefinedAbsolute : Marker { private int pos; public PredefinedAbsolute (MSymbol mname) : base (mname) { if (! int.TryParse (((string) mname).Substring (1), out pos)) throw new Exception ("Invalid marker name: " + mname); } public override int Position (Context ic) { return (pos < ic.preedit.Length ? pos : ic.preedit.Length); } } public class PredefinedSurround : Marker { private int distance; public PredefinedSurround (MSymbol mname) : base (mname) { if (! int.TryParse (((string) name).Substring (1), out distance)) throw new Exception ("Invalid marker name: " + mname); if (distance > 0) distance--; } public override int Position (Context ic) { return ic.cursor_pos + distance; } public override int CharAt (Context ic) { int pos = ic.cursor_pos + distance; if (pos < 0) return ic.GetSurroundingChar (pos); else if (pos >= ic.preedit.Length) return ic.GetSurroundingChar (pos - ic.preedit.Length); return ic.preedit[pos]; } } static internal Dictionary predefineds; static Marker () { predefineds = new Dictionary (); predefineds ["@<"] = predefineds["@first"] = new Predefined ('<'); predefineds ["@>"] = predefineds["@last"] = new Predefined ('>'); predefineds ["@-"] = predefineds["@previous"] = new Predefined ('-'); predefineds ["@+"] = predefineds["@next"] = new Predefined ('+'); predefineds ["@["] = predefineds["@previous-candidate-change"] = new Predefined ('['); predefineds ["@]"] = predefineds["@next-candidate-change"] = new Predefined (']'); } public static Marker Get (MSymbol mname) { string str = mname.Name; if (str[0] == '@') { Predefined pred; if (predefineds.TryGetValue (mname, out pred)) return pred; if (str.Length == 1) throw new Exception ("Invalid marker name: " + mname); if (Char.IsDigit (str[1])) return new PredefinedAbsolute (mname); if (str.Length == 2 || mname == Mat_minus_zero || ! (str[1] == '-' || str[1] == '+')) throw new Exception ("Invalid marker name: " + mname); return new PredefinedSurround (mname); } return new Named (mname); } } internal class Candidates { private class Block { public int Index; public object Data; public Block (int index, Xex.Term term) { Index = index; if (term.IsStr) Data = (MText) term.Strval.ToString (); else { MPlist plist = new MPlist (); MPlist p = plist; foreach (Xex.Term t in term.Listval) p = p.Add (MSymbol.mtext, (MText) t.Strval.ToString ()); Data = plist; } } public Block (int index, MPlist plist) { Index = index; if (plist.IsMText) Data = plist.Text; else if (plist.IsPlist) Data = plist.Plist; else throw new Exception ("Invalid candidate: " + plist); } public int Count { get { return (Data is MText ? ((MText) Data).Length : ((MPlist) Data).Count); } } public object this[int i] { get { if (Data is MText) return ((MText) Data)[i]; return ((MPlist) Data)[i]; } } } private Block[] blocks; private int row = 0; private int index = 0; public object[] group; private bool IsFixed { get { return group != null; } } private int Total { get { Block last = blocks[blocks.Length - 1]; return last.Index + last.Count; } } public int Column { get { return (IsFixed ? index % group.Length : index - blocks[row].Index); } } public object Group { get { return (IsFixed ? group : blocks[row].Data); } } public int GroupLength { get { if (IsFixed) { int nitems = group.Length; int start = index - (index % nitems); int total = Total; return (start + nitems <= total ? nitems : total - start); } return blocks[row].Count; } } public object Current { get { return (IsFixed ? group[index % group.Length] : blocks[row][index - blocks[row].Index]); } } public Candidates (MPlist list, int column) { int nblocks = list.Count; blocks = new Block[nblocks]; for (int i = 0, start = 0; i < nblocks; i++, list = list.next) start += (blocks[i] = new Block (index, list)).Count; if (column > 0) { group = new object[column]; fill_group (0); } } public Candidates (Xex.Term[] candidates, int column) { int nblocks = candidates.Length; blocks = new Block[nblocks]; for (int i = 0, start = 0; i < nblocks; i++) start += (blocks[i] = new Block (index, candidates[i])).Count; if (column > 0) { group = new object[column]; fill_group (0); } } public static void Detach (Context ic) { ic.preedit.PopProp (0, ic.preedit.Length, Mcandidates); ic.candidates = null; ic.changed |= (ChangedStatus.Preedit | ChangedStatus.CursorPos | CandidateAll); } // Fill the array "group" by candidates stating from START. // START must be a multiple of "column". Return the number of // valid candidates in "group". private int fill_group (int start) { int nitems = group.Length; int r = row; Block b = blocks[r]; if (start < b.Index) while (start < b.Index) b = blocks[--r]; else while (start >= b.Index + b.Count) b = blocks[++r]; row = r; int count = b.Count; start -= b.Index; for (int i = 0; i < nitems; i++, start++) { if (start >= count) { r++; if (r == blocks.Length) return i; b = blocks[r]; count = b.Count; start = 0; } group[i] = b[start]; } return nitems; } // Update "row" to what contains the first candidate of // the previous candidate-group, update "current_index", and // update "group" if necessary. Return the previous // candidate-group. Set NITEMS to the number of valid // candidates contained in that group. public int PrevGroup () { int nitems; int col = Column; if (IsFixed) { nitems = group.Length; if ((index -= col + nitems) < 0) index = (Total / nitems) * nitems; nitems = fill_group (index); } else { row = row > 0 ? row-- : blocks.Length - 1; nitems = blocks[row].Count; index = blocks[row].Index; } index += col < nitems ? col : nitems - 1; return nitems; } public int NextGroup () { int nitems; int col = Column; if (IsFixed) { nitems = group.Length; if ((index += nitems - col) >= Total) index = 0; nitems = fill_group (index); } else { row = row < blocks.Length - 1 ? row + 1 : 0; nitems = blocks[row].Count; index = blocks[row].Count; } index += col < nitems ? col : nitems - 1; return nitems; } public void Prev () { int col = Column; if (col == 0) { int nitems = PrevGroup (); index += col < nitems - 1 ? col : nitems - 1; } else index--; } public void Next () { int col = Column; int nitems = GroupLength; if (col == nitems - 1) { nitems = NextGroup (); index -= Column; } else index++; } public void First () { index -= Column; } public void Last () { index += GroupLength - (Column + 1); } public object Select (int col) { int maxcol = GroupLength - 1; if (col > maxcol) col = maxcol; index = index - Column + col; return Current; } public object Select (Selector selector) { switch (selector.Tag) { case '<': First (); break; case '>': Last (); break; case '-': Prev (); break; case '+': Next (); break; case '[': PrevGroup (); break; case ']': NextGroup (); break; default: break; } return Current; } public override string ToString () { return (String.Format ("", row, index) + Group + ""); } } internal class Selector : Xex.TermValue { private static Xex.Symbol name = "selector"; public static Xex.Symbol Name { get { return name; } } static Dictionary selectors; static Selector () { selectors = new Dictionary (); selectors["@<"] = selectors["@first"] = new Selector ('<'); selectors["@="] = selectors["@current"] = new Selector ('='); selectors["@>"] = selectors["@last"] = new Selector ('>'); selectors["@-"] = selectors["@previous"] = new Selector ('-'); selectors["@+"] = selectors["@next"] = new Selector ('+'); selectors["@["] = selectors["@previous-candidate-change"] = new Selector ('['); selectors["@]"] = selectors["@next-candidate-change"] = new Selector (']'); } private readonly char tag; public char Tag { get { return tag; } } private Selector (char tag) { this.tag = tag; } public static Xex.TermValue Parser (Xex.Domain domain, XmlNode node) { return Get ((MSymbol) node.InnerText); } public static Xex.TermValue Get (MSymbol name) { Selector selector; if (! selectors.TryGetValue (name, out selector)) throw new Exception ("Invalid selector name: " + name); return selector; } public override bool Equals (object obj) { Selector s = obj as Selector; return (s != null && s.tag == tag); } public override int GetHashCode () { return (int) tag; } public override string ToString () { return "@" + tag + ""; } } internal class Map { public MSymbol name; public List entries = new List (); public Map (MSymbol name) { this.name = name; } public class Entry { public KeySeq keyseq; public Xex.Term[] actions; public Entry (Xex.Domain domain, KeySeq keyseq, Xex.Term[] actions) { this.keyseq = keyseq; this.actions = actions; } } public override string ToString () { string str = "(" + name; foreach (Entry e in entries) str += " " + e.keyseq.ToString (); return str + ")"; } } protected class Action { private Xex.Term action; public Action (Xex.Domain domain, Xex.Term[] terms) { Xex.Term[] args = new Xex.Term[terms.Length]; args[0] = Tcatch_tag; for (int i = 0; i < terms.Length; i++) args[i + 1] = terms[i]; action = new Xex.Term (domain, Qcatch, args); } public bool Run (Xex.Domain domain) { Xex.Term result = Xex.Eval (domain, action); if (result.IsError) { ((Context) domain.context).Error = result.ToString (); return false; } return (result != Tcatch_tag); } } internal class Keymap { public Dictionary submaps; public Xex.Term[] map_actions, branch_actions; public Keymap () { } public void Add (KeySeq keys, int index, Xex.Term[] map_actions, Xex.Term[] branch_actions) { if (index == keys.keyseq.Count) { this.map_actions = map_actions; this.branch_actions = branch_actions; } else { Key key = keys.keyseq[index]; Keymap sub = null; if (submaps == null) submaps = new Dictionary (); else submaps.TryGetValue (key, out sub); if (sub == null) submaps[key] = sub = new Keymap (); sub.Add (keys, index + 1, map_actions, branch_actions); } } public void AddMap (Map map, Xex.Term[] branch_actions) { foreach (Map.Entry entry in map.entries) Add (entry.keyseq, 0, entry.actions, branch_actions); } public Keymap Lookup (KeySeq keys, ref int index) { Keymap sub; if (index < keys.keyseq.Count && submaps != null && submaps.TryGetValue (keys.keyseq[index], out sub)) { index++; return sub.Lookup (keys, ref index); } return this; } private void describe (MText mt, KeySeq keyseq) { if (map_actions != null || branch_actions != null) { if (mt.Length > 0) mt.Cat (" "); mt.Cat ('(').Cat (keyseq.ToString ()); if (map_actions != null) foreach (Xex.Term term in map_actions) mt.Cat (' ').Cat (term.ToString ()); if (branch_actions != null) foreach (Xex.Term term in branch_actions) mt.Cat (' ').Cat (term.ToString ()); mt.Cat (')'); } if (submaps != null) foreach (KeyValuePair kv in submaps) { keyseq.keyseq.Add (kv.Key); kv.Value.describe (mt, keyseq); keyseq.keyseq.RemoveAt (keyseq.keyseq.Count - 1); } } public override string ToString () { MText mt = ""; KeySeq keyseq = new KeySeq (); describe (mt, keyseq); return (string) mt; } } internal class State { public MSymbol name; public MText title; public Xex.Term[] enter_actions, fallback_actions; public Keymap keymap = new Keymap (); public State (MSymbol name, MText title) { this.name = name; this.title = title; } public State (MInputMethod im, XmlNode node) { this.name = node.Attributes[Qsname].Value; XmlAttribute attr = node.Attributes[Qtitle]; if (attr != null) title = (MText) attr.Value; else title = im.title; keymap = new Keymap (); for (node = node.FirstChild; node != null; node = node.NextSibling) { if (node.Name == Qstate_hook) enter_actions = Xex.Parse (im.domain, node.FirstChild, null); else if (node.Name == Qcatch_all_branch) fallback_actions = Xex.Parse (im.domain, node.FirstChild, null); else if (node.Name == Qbranch) { MSymbol mapname = node.Attributes[Qmname].Value; Map map; if (im.maps.TryGetValue (mapname, out map)) keymap.AddMap (map, Xex.Parse (im.domain, node.FirstChild, null)); else throw new Exception ("Unknown map: " + mapname); } } } public State (MInputMethod im, MPlist plist) { if (! plist.IsSymbol) throw new Exception ("Invalid state: " + plist); this.name = plist.Symbol; plist = plist.next; if (plist.IsMText) { title = plist.Text; plist = plist.next; } else title = im.title; keymap = new Keymap (); for (; ! plist.IsEmpty; plist = plist.next) { if (! plist.IsPlist) throw new Exception ("Invalid branch: " + plist); MPlist p = plist.Plist; if (! p.IsSymbol) throw new Exception ("Invalid branch: " + p); MSymbol mapname = p.Symbol; if (mapname == MSymbol.t) enter_actions = im.parse_actions (p.next, false); else if (mapname == MSymbol.nil) fallback_actions = im.parse_actions (p.next, false); else { Map map; if (im.maps.TryGetValue (mapname, out map)) keymap.AddMap (map, im.parse_actions (p.next, false)); else throw new Exception ("Unknown map: " + mapname); } } } public override string ToString () { MText mt = "(" + name; if (title != null) mt.Cat (" \"" + title + "\""); mt.Cat (keymap.ToString ()); return (string) mt + ")"; } } // Instance members internal Xex.Domain domain; protected LoadStatus load_status = LoadStatus.None; protected MDatabase.Tag tag; private MDatabase mdb; private MText description; internal MText title; internal Command[] commands; internal Xex.Symbol[] var_names; internal Dictionary plugins; internal Dictionary maps; internal MPlist states; static MInputMethod () { im_domain.DefType (typeof (KeySeq)); im_domain.DefType (typeof (Marker)); im_domain.DefType (typeof (Selector)); 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); MDatabase.Tag tag = new MDatabase.Tag (Minput_method, "*", "*", "*"); List list = MDatabase.List (tag); M17n.DebugPrint ("Found {0} input methods\n", list.Count); foreach (MDatabase mdb in list) im_table[mdb.tag] = new MInputMethod (mdb.tag); } // Constructor private MInputMethod (MDatabase.Tag tag) { this.tag = tag; domain = new Xex.Domain (tag[1].Name, im_domain, null); } // Instance Properties public MSymbol Language { get { return tag[1]; } } public MSymbol Name { get { return tag[2]; } } public MSymbol SubName { get { return tag[3]; } } public bool Info (out MText description, out MText title, out Xex.Variable[] variables, out Command[] commands) { if ((load_status & LoadStatus.Header) != LoadStatus.Header && ! load_header ()) { description = null; title = null; variables = null; commands = null; return false; } description = this.description; title = this.title; if (var_names == null) variables = null; else { variables = new Xex.Variable[var_names.Length]; int i = 0; foreach (Xex.Symbol name in var_names) variables[i++] = domain.GetVar (name); } commands = this.commands; return true; } public static MInputMethod Find (MSymbol language, MSymbol name) { return Find (language, name, MSymbol.nil); } public static MInputMethod Find (MSymbol language, MSymbol name, MSymbol subname) { MDatabase.Tag tag = new MDatabase.Tag (Minput_method, language, name, subname); MInputMethod im; return (im_table.TryGetValue (tag, out im) ? im : null); } private bool Open () { return ((load_status == LoadStatus.Full) || load_body ()); } public static MInputMethod[] List () { MInputMethod[] array = new MInputMethod[im_table.Count]; int i = 0; foreach (KeyValuePair kv in im_table) array[i++] = kv.Value; return array; } private bool load_header () { mdb = MDatabase.Find (tag); if (mdb == null) return false; try { if (mdb.Format == MSymbol.plist) load (mdb.Load (Mmap), false); else { XmlDocument doc = new XmlDocument (Xex.Symbol.NameTable); if (! mdb.Load (doc, Mmap_list)) throw new Exception ("Load error" + mdb.tag); load (doc.DocumentElement, false); } } catch (Exception e) { Console.WriteLine ("{0}\n", e); load_status = LoadStatus.Error; return false; } load_status |= LoadStatus.Header; return true; } private bool load_body () { mdb = MDatabase.Find (tag); if (mdb == null) return false; try { if (mdb.Format == MSymbol.plist) load (mdb.Load (), true); else { XmlDocument doc = new XmlDocument (Xex.Symbol.NameTable); if (! mdb.Load (doc)) throw new Exception ("Load error" + mdb.tag); load (doc.DocumentElement, true); } } catch (Exception e) { Console.WriteLine (e); load_status = LoadStatus.Error; return false; } load_status = LoadStatus.Full; return true; } private void add_default_state () { MSymbol Qinit = "init"; State state = new State (Qinit, title); foreach (KeyValuePairkv in maps) state.keymap.AddMap (kv.Value, null); states.Add (Qinit, state); } private void load (MPlist plist, bool full) { maps = new Dictionary (); states = new MPlist (); for (; ! plist.IsEmpty; plist = plist.next) if (plist.IsPlist) { MPlist pl = plist.Plist; if (pl.IsSymbol) { MSymbol sym = pl.Symbol; pl = pl.next; if (sym == Mdescription) description = parse_description (pl); else if (sym == Mtitle) { if (pl.IsMText) title = pl.Text; } else if (sym == Mvariable) parse_variables (pl); else if (sym == Mcommand) parse_commands (pl); else if (full) { if (sym == Mmodule) parse_plugins (pl); else if (sym == Minclude) parse_include (pl); else if (sym == Mmacro) parse_macros (pl); else if (sym == Mmap) parse_maps (pl); else if (sym == Mstate) parse_states (pl); } } } if (description == null) description = (MText) "No description"; if (title == null) title = new MText (tag[2].Name); if (commands == null) commands = new Command[0]; if (! full) return; if (states.IsEmpty) add_default_state (); } private void load (XmlNode node, bool full) { bool skip_header = load_status == LoadStatus.Header; maps = new Dictionary (); states = new MPlist (); if (node.NodeType == XmlNodeType.Document) node = node.FirstChild; while (node.NodeType != XmlNodeType.Element) node = node.NextSibling; for (node = node.FirstChild; node != null; node = node.NextSibling) { if (node.NodeType != XmlNodeType.Element) continue; if (! skip_header) { if (node.Name == "description") description = parse_description (node); else if (node.Name == "title") title = parse_title (node); else if (node.Name == "variable-list") parse_variables (node); else if (node.Name == "command-list") parse_commands (node); } if (full) { if (node.Name == "module-list") parse_plugins (node); else if (node.Name == "macro-list") parse_macros (node); else if (node.Name == "map-list") parse_maps (node); else if (node.Name == "state-list") parse_states (node); } } if (description == null) description = (MText) "No description"; if (title == null) title = new MText (tag[2].Name); if (commands == null) commands = new Command[0]; if (! full) return; if (states.IsEmpty) add_default_state (); } private static MText parse_description (MPlist plist) { if (plist.IsMText) return plist.Text; if (plist.IsPlist) { plist = plist.Plist; if (plist.IsSymbol && plist.Symbol == (MSymbol) "_" && plist.next.IsMText) return plist.next.Text; } return null; } private static MText parse_description (XmlNode node) { if (node.HasChildNodes) node = node.FirstChild; return node.InnerText; } private static MText parse_title (XmlNode node) { return node.InnerText; } private Xex.Variable get_global_var (Xex.Symbol name) { if (im_global == null || this != im_global) { MDatabase.Tag tag = new MDatabase.Tag (Minput_method, MSymbol.t, MSymbol.nil, "global"); im_global = im_table[tag]; if (! im_global.Open ()) throw new Exception ("Failed to load global"); } return im_global.domain.GetVar (name); } private void parse_variables (MPlist plist) { var_names = new Xex.Symbol[plist.Count]; for (int i = 0; ! plist.IsEmpty; i++, plist = plist.next) { if (! plist.IsPlist || ! plist.Plist.IsSymbol) throw new Exception ("Invalid variable: " + plist); MPlist p = plist.Plist; Xex.Symbol name = (Xex.Symbol) p.Symbol.Name; var_names[i] = name; p = p.next; MText mt = parse_description (p); string desc = mt == null ? null : (string) mt; if (! p.IsEmpty) p = p.next; Xex.Variable vari = get_global_var (name); if (vari != null) domain.Defvar (vari); if (p.IsInteger) { int n = p.Integer; int[] range = null; p = p.Next; if (! p.IsEmpty) { int nrange = p.Count; range = new int[nrange * 2]; for (int j = 0; j < nrange; j++) { if (p.IsPlist) { MPlist p0 = p.Plist; if (! p0.IsInteger || ! p0.next.IsInteger) throw new Exception ("Invalid range: " + p0); range[j * 2] = p0.Integer; range[j * 2 + 1] = p0.next.Integer; } else if (p.IsInteger) range[j * 2] = range[j * 2 + 1] = p.Integer; else throw new Exception ("Invalid range: " + p); } } domain.DefvarInt (name, n, desc, range); } else if (p.IsMText) { string str = (string) p.Text; string[] range = null; p = p.next; if (! p.IsEmpty) { range = new string[p.Count]; for (int j = 0; j < range.Length; j++) { if (p.IsMText) range[j] = (string) p.Text; else throw new Exception ("Invalid range: " + p); } } domain.DefvarStr (name, str, desc, range); } else if (p.IsSymbol) { Xex.Symbol sym = p.Symbol.Name; Xex.Symbol[] range; p = p.next; if (p.IsEmpty) range = null; else { range = new Xex.Symbol[p.Count]; for (int j = 0; j < range.Length; j++) { if (p.IsSymbol) range[j] = p.Symbol.Name; else throw new Exception ("Invalid range: " + p); } } domain.DefvarSym (name, sym, desc, range); } else if (! p.IsEmpty) throw new Exception ("Invalid variable type: " + p.val); } } private void parse_variables (XmlNode node) { XmlNodeList node_list = node.ChildNodes; var_names = new Xex.Symbol[node_list.Count]; for (int i = 0; i < node_list.Count; i++) { Xex.Symbol name = node_list[i].Attributes[0].Value; Xex.Variable vari = get_global_var (name); if (vari != null) domain.Defvar (vari); Xex.Parse (domain, node_list[i]); var_names[i] = name; } } private void parse_commands (MPlist plist) { commands = new Command[plist.Count]; for (int i = 0; ! plist.IsEmpty; plist = plist.next) if (plist.IsPlist && plist.Plist.IsSymbol) commands[i++] = new Command (plist.Plist); } private void parse_commands (XmlNode node) { XmlNodeList node_list = node.ChildNodes; commands = new Command[node_list.Count]; for (int i = 0; i < node_list.Count; i++) { if (node_list[i].NodeType == XmlNodeType.Element) commands[i] = new Command (node_list[i]); } } private void parse_plugins (MPlist plist) { plugins = new Dictionary (); for (; ! plist.IsEmpty; plist = plist.Next) { MPlist p = plist.Plist; MSymbol sym = p.Symbol; Plugin plugin = new Plugin (sym.Name); for (p = p.next; ! p.IsEmpty; p = p.next) { Xex.Function func = new PluginMethod (plugin, p.Symbol.Name); domain.Defun (func); } } } private void parse_plugins (XmlNode node) { plugins = new Dictionary (); foreach (XmlNode n in node.ChildNodes) { Plugin plugin = new Plugin (n.Attributes[0].Value); foreach (XmlNode nn in n.ChildNodes) { Xex.Function func = new PluginMethod (plugin, nn.Attributes[0].Value); domain.Defun (func); } } } private void parse_include (XmlNode node) { XmlNode n; MSymbol language, name, subname; MSymbol part, section; node = node.FirstChild; n = node.FirstChild; language = n.InnerText; n = n.NextSibling; name = n.InnerText; n = n.NextSibling; if (n != null) subname = n.InnerText; else subname = MSymbol.nil; node = node.NextSibling; part = node.InnerText; node = node.NextSibling; if (node != null) section = node.InnerText; else section = MSymbol.nil; include_part (language, name, subname, part, section); } private void parse_macros (XmlNode node) { for (XmlNode nn = node.FirstChild; nn != null; nn = nn.NextSibling) if (nn.NodeType == XmlNodeType.Element && nn.Name == Qxi_include) { parse_include (nn); nn = nn.PreviousSibling; node.RemoveChild (nn.NextSibling); } Xex.Parse (domain, node.FirstChild, null); } private void parse_maps (XmlNode node) { for (node = node.FirstChild; node != null; node = node.NextSibling) { if (node.Name == Qmap) { MSymbol name = node.Attributes[0].Value; Map map = new Map (name); maps[name] = map; for (XmlNode nd = node.FirstChild; nd != null; nd = nd.NextSibling) if (nd.Name == Qrule) { XmlNode n = nd.FirstChild; if (n.Name != Qkeyseq) continue; KeySeq keyseq = (KeySeq) KeySeq.Parser (domain, n); n = n.NextSibling; Xex.Term[] actions = Xex.Parse (domain, n, null); map.entries.Add (new Map.Entry (domain, keyseq, actions)); } } else if (node.Name == Qxi_include) parse_include (node); } } private void parse_states (MPlist plist) { for (; ! plist.IsEmpty; plist = plist.next) if (plist.IsPlist) { State state = new State (this, plist.Plist); states.Add (state.name, state); } } private void parse_states (XmlNode node) { for (node = node.FirstChild; node != null; node = node.NextSibling) { if (node.Name == Qstate) { State state = new State (this, node); states.Add (state.name, state); } else if (node.Name == Qxi_include) parse_include (node); } } private void include_part (MSymbol language, MSymbol name, MSymbol subname, MSymbol part, MSymbol section) { MInputMethod im = MInputMethod.Find (language, name, subname); if (im == null) return; if (! im.Open ()) return; if (part == Mmacro) { if (section == MSymbol.nil) im.domain.CopyFunc (domain); else im.domain.CopyFunc (domain, (Xex.Symbol) section.Name); } else if (part == Mmap) { if (section == MSymbol.nil) { foreach (KeyValuePair kv in im.maps) maps[kv.Key] = kv.Value; } else { Map map; if (im.maps.TryGetValue (section, out map)) maps[section] = map; } } else if (part == Mstate) { if (section == MSymbol.nil) { for (MPlist p = im.states; ! p.IsEmpty; p = p.next) states.Add (p.Key, p.Val); } else { MSymbol state_name = (string) section.Name; State state = (State) im.states.Get (state_name); if (state != null) states.Add (state.name, state); } } } private void parse_include (MPlist plist) { if (! plist.IsPlist) return; MPlist p = plist.Plist; MSymbol language, name, subname; language = p.Symbol; p = p.next; if (! p.IsSymbol) name = subname = MSymbol.nil; else { name = p.Symbol; p = p.next; if (! p.IsSymbol) subname = MSymbol.nil; else subname = p.Symbol; } plist = plist.next; if (! plist.IsSymbol) return; MSymbol part = plist.Symbol; plist = plist.next; MSymbol section = MSymbol.nil; if (plist.IsSymbol) section = plist.Symbol; include_part (language, name, subname, part, section); } private Xex.Term parse_cond (MPlist plist) { Xex.Term[] args = new Xex.Term[plist.Count]; for (int i = 0; ! plist.IsEmpty; i++, plist = plist.next) { if (! plist.IsPlist) throw new Exception ("Invalid cond args: " + plist); MPlist p = plist.Plist; List arg = new List (); arg.Add (parse_action (p, true)); for (p = p.next; ! p.IsEmpty; p = p.next) arg.Add (parse_action (p, false)); args[i] = new Xex.Term (arg); } return new Xex.Term (domain, Qcond, args); } private Xex.Term parse_insert (MPlist plist) { Xex.Term[] args; Xex.Term arg; if (plist.IsSymbol) arg = new Xex.Term (domain, (Xex.Symbol) plist.Symbol.Name); else if (plist.IsMText) arg = new Xex.Term ((string) plist.Text); else if (plist.IsInteger) arg = new Xex.Term (plist.Integer); else if (plist.IsPlist) { MPlist pl = plist.Plist; args = new Xex.Term[pl.Count]; int i; for (i = 0; ! pl.IsEmpty; i++, pl = pl.next) { if (pl.IsMText) args[i] = new Xex.Term ((string) pl.Text); else if (pl.IsPlist) { List list = new List (); for (MPlist p = pl.Plist; ! p.IsEmpty; p = p.next) { if (p.IsMText) list.Add (new Xex.Term ((string) p.Text)); else throw new Exception ("Invalid candidates: " + p); } } else throw new Exception ("Invalid candidates: " + pl); } return new Xex.Term (domain, Qinsert_candidates, args); } else throw new Exception ("Invalid arg to insert: " + plist); args = new Xex.Term[1]; args[0] = arg; return new Xex.Term (domain, Qinsert, args); } private Xex.Term parse_select (MPlist plist) { Xex.Term[] args = new Xex.Term[1]; if (plist.IsInteger) args[0] = new Xex.Term (plist.Integer); else if (! plist.IsSymbol) throw new Exception ("Invalid arg to select: " + plist); else if (plist.Symbol.Name[0] == '@') args[0] = new Xex.Term (Selector.Get (plist.Symbol)); else args[0] = new Xex.Term (domain, (Xex.Symbol) plist.Symbol.Name); return new Xex.Term (domain, Qselect, args); } private Xex.Term parse_funcall_with_marker (MPlist plist, Xex.Symbol func) { Xex.Term[] args = new Xex.Term[1]; if (plist.IsInteger && func != Qmark) args[0] = new Xex.Term (plist.Integer); else if (plist.IsSymbol) args[0] = new Xex.Term (Marker.Get (plist.Symbol)); else throw new Exception ("Invalid arg to " + func + ": " + plist); return new Xex.Term (domain, func, args); } private Xex.Term parse_char_at (MSymbol name) { Xex.Term[] args = new Xex.Term[1]; args[0] = new Xex.Term (Marker.Get (name)); return new Xex.Term (domain, Qchar_at, args); } private Xex.Term parse_shift (MPlist plist) { Xex.Term[] args = new Xex.Term[1]; if (! plist.IsSymbol) throw new Exception ("Invalid arg to shift: " + plist); args[0] = new Xex.Term ((Xex.Symbol) plist.Symbol.Name); return new Xex.Term (domain, Qshift, args); } private Xex.Term parse_action (MPlist plist, bool as_funarg) { if (plist.IsPlist) { MPlist p = plist.Plist; if (p.IsMText || p.IsPlist) return parse_insert (plist); if (! p.IsSymbol) throw new Exception ("Invalid action: " + p); MSymbol sym = p.Symbol; Xex.Symbol name = sym.Name; p = p.next; if (name == Qcond) return parse_cond (p); if (name == Qinsert) return parse_insert (p); if (name == Qselect) return parse_select (p); if (name == Qdelete || name == Qmove || name == Qmark) return parse_funcall_with_marker (p, name); if (name == Qshift) return parse_shift (p); if (name == Qset || name == Qadd || name == Qsub || name == Qmul || name == Qdiv) { if (! p.IsSymbol) throw new Exception ("Invalid action: " + p); Xex.Symbol varname = p.Symbol.Name; Xex.Term[] args = new Xex.Term[1]; args[0] = parse_action (p.next, true); return new Xex.Term (domain, name, varname, args); } else { if (name == Qeq) name = Qeqeq; else if (name == Qhide) name = Qhide_candidates; else if (name == Qshow) name = Qshow_candidates; if (p.IsEmpty) return new Xex.Term (domain, name, null); else return new Xex.Term (domain, name, parse_actions (p, true)); } } else if (plist.IsSymbol) { if (plist.Symbol == Matat) return new Xex.Term (domain, Qkey_count, null); if (plist.Symbol == Mat_minus_zero) return new Xex.Term (domain, Qsurrounding_text_flag, null); if (plist.Symbol.Name[0] == '@') return parse_char_at (plist.Symbol); return new Xex.Term (domain, (Xex.Symbol) plist.Symbol.Name); } else if (plist.IsMText) return (as_funarg ? new Xex.Term ((string) plist.Text) : parse_insert (plist)); else if (plist.IsInteger) return (as_funarg ? new Xex.Term (plist.Integer) : parse_insert (plist)); else throw new Exception ("Invalid action: " + plist); } private Xex.Term[] parse_actions (MPlist plist, bool as_funarg) { Xex.Term[] terms = new Xex.Term[plist.Count]; for (int i = 0; ! plist.IsEmpty; i++, plist = plist.next) terms[i] = parse_action (plist, as_funarg); return terms; } private void parse_macros (MPlist plist) { for (MPlist pl = plist; ! pl.IsEmpty; pl = pl.next) if (pl.IsPlist) { MPlist p = pl.Plist; if (! p.IsSymbol) continue; domain.Defun ((Xex.Symbol) p.Symbol.Name, 0, 0, null, null); } for (MPlist pl = plist; ! pl.IsEmpty; pl = pl.next) if (pl.IsPlist) { MPlist p = pl.Plist; if (! p.IsSymbol) continue; domain.Defun ((Xex.Symbol) p.Symbol.Name, 0, 0, null, parse_actions (p.next, false)); } } private void parse_maps (MPlist plist) { for (; ! plist.IsEmpty; plist = plist.next) if (plist.IsPlist) { MPlist pl = plist.Plist; if (! pl.IsSymbol) continue; Map map = new Map (pl.Symbol); maps[pl.Symbol] = map; for (pl = pl.next; ! pl.IsEmpty; pl = pl.next) { if (! pl.IsPlist) continue; MPlist p = pl.Plist; KeySeq keys; if (p.IsMText) keys = new KeySeq (p.Text); else if (p.IsPlist) keys = new KeySeq (p.Plist); else continue; p = p.next; Xex.Term[] actions = p.IsEmpty ? null : parse_actions (p, false); map.entries.Add (new Map.Entry (domain, keys, actions)); } } } private static Xex.Term Finsert (Xex.Domain domain, Xex.Variable vari, Xex.Term[] args) { if (args[0].IsInt) ((Context) domain.context).insert (args[0].Intval, null); else ((Context) domain.context).insert ((MText) args[0].Strval.ToString (), null); return args[0]; } private static Xex.Term Finsert_candidates (Xex.Domain domain, Xex.Variable vari, Xex.Term[] args) { Context ic = (Context) domain.context; Xex.Variable v = ic.domain.GetVar (Qcandidates_group_size); int column = v == null ? 0 : v.Value.Intval; Candidates candidates = new Candidates (args, column); object candidate = candidates.Current; if (candidate is MText) ic.insert ((MText) candidate, candidates); else ic.insert ((int) candidate, candidates); return args[0]; } private static Xex.Term Fchar_at (Xex.Domain domain, Xex.Variable vari, Xex.Term[] args) { Context ic = (Context) domain.context; Marker m = (Marker) args[0].Objval; return new Xex.Term (m.CharAt (ic)); } private static Xex.Term Fdelete (Xex.Domain domain, Xex.Variable vari, Xex.Term[] args) { Context ic = (Context) domain.context; int pos; if (args[0].IsInt) pos = args[0].Intval; else { Marker m = (Marker) args[0].Objval; pos = m.Position (ic); } return new Xex.Term (ic.delete (pos)); } private static Xex.Term Fselect (Xex.Domain domain, Xex.Variable vari, Xex.Term[] args) { Context ic = (Context) domain.context; Candidates can = ic.candidates; if (can != null) { object candidate = can.Current; if (candidate is MText) ic.delete (ic.cursor_pos - ((MText) candidate).Length); else ic.delete (ic.cursor_pos - 1); if (args[0].IsInt) candidate = can.Select (args[0].Intval); else candidate = can.Select ((Selector) args[0].Objval); if (candidate is MText) ic.insert ((MText) candidate, can); else ic.insert ((int) candidate, can); } return args[0]; } private static Xex.Term Fshow (Xex.Domain domain, Xex.Variable vari, Xex.Term[] args) { ((Context) domain.context).show (); return Tnil; } private static Xex.Term Fhide (Xex.Domain domain, Xex.Variable vari, Xex.Term[] args) { ((Context) domain.context).hide (); return Tnil; } private static Xex.Term Fmove (Xex.Domain domain, Xex.Variable vari, Xex.Term[] args) { Context ic = (Context) domain.context; int pos = (args[0].IsInt ? args[0].Intval : ((Marker) args[0].Objval).Position (ic)); ic.move (pos); return args[0]; } private static Xex.Term Fmark (Xex.Domain domain, Xex.Variable vari, Xex.Term[] args) { Marker m = (Marker) args[0].Objval; m.Mark ((Context) domain.context); return args[0]; } private static Xex.Term Fpushback (Xex.Domain domain, Xex.Variable vari, Xex.Term[] args) { Context ic = (Context) domain.context; if (args[0].IsInt) ic.pushback (args[0].Intval); else if (args[0].IsStr) ic.pushback (new KeySeq (args[0].Strval.ToString ())); else ic.pushback ((KeySeq) args[0].Objval); return args[0]; } private static Xex.Term Fpop (Xex.Domain domain, Xex.Variable vari, Xex.Term[] args) { ((Context) domain.context).pop (); return Tnil; } private static Xex.Term Fundo (Xex.Domain domain, Xex.Variable vari, Xex.Term[] args) { int n = args.Length == 0 ? -2 : args[0].Intval; ((Context) domain.context).undo (n); return Tnil; } private static Xex.Term Fcommit (Xex.Domain domain, Xex.Variable vari, Xex.Term[] args) { ((Context) domain.context).commit (); return Tnil; } private static Xex.Term Funhandle (Xex.Domain domain, Xex.Variable vari, Xex.Term[] args) { ((Context) domain.context).commit (); return Xex.Fthrow (domain, vari, new Xex.Term[1] { Tcatch_tag }); } private static Xex.Term Fshift (Xex.Domain domain, Xex.Variable vari, Xex.Term[] args) { Context ic = (Context) domain.context; MSymbol state_name = (string) args[0].Symval; State state = (State) ic.im.states.Get (state_name); if (state == null) throw new Exception ("Unknown state: " + state_name); ((Context) domain.context).shift (state); return args[0]; } private static Xex.Term Fshiftback (Xex.Domain domain, Xex.Variable vari, Xex.Term[] args) { ((Context) domain.context).shift (null); return Tnil; } private static Xex.Term Fkey_count (Xex.Domain domain, Xex.Variable vari, Xex.Term[] args) { return new Xex.Term (((Context) domain.context).key_head); } private static Xex.Term Fsurrounding_flag (Xex.Domain domain, Xex.Variable vari, Xex.Term[] args) { return new Xex.Term (((Context) domain.context).GetSurroundingText == null ? -2 : -1); } public override string ToString () { this.Open (); string str = (String.Format ("({0} (title \"{1}\")", tag, title)); if (commands != null) { str += " (commands"; foreach (Command cmd in commands) str += " " + cmd; str += ")"; } if (var_names != null) { str += " (variables"; foreach (Xex.Symbol var in var_names) str += " " + var; str += ")"; } if (plugins != null) { str += " (modules"; foreach (KeyValuePair kv in plugins) str += " " + kv.Value; str += ")"; } str += " (maps"; foreach (KeyValuePair kv in maps) str += " " + kv.Value; str += ") (states"; for (MPlist p = states; ! p.IsEmpty; p = p.next) str += " (" + p.Key + " " + ((State) p.Val).keymap + ")"; return str + "))"; } public class Context { internal MInputMethod im; internal Xex.Domain domain; private bool active; public Callback PreeditChanged; public Callback StatusChanged; public Callback CandidateChanged; public Callback GetSurroundingText; public Callback DelSurroundingText; private MText status; private MText produced = new MText (); internal MText preedit = new MText (); internal int cursor_pos; internal MPlist marker_positions = new MPlist (); internal Candidates candidates; private bool candidate_show; public bool CandidateShow { get { return candidate_show; } } private State initial_state, state, prev_state; private MText state_preedit = new MText (); private int state_key_head; private object state_var_values, state_initial_var_values; private int state_pos; private Keymap keymap; // Sequence of input keys. internal KeySeq keys = new KeySeq (); // Index into KEYS specifying the next key to handle. internal int key_head; internal MText preceding_text = new MText (); internal MText following_text = new MText (); // Set to false before calling the method 'handle_key', and set // to true when some key is unhandled. private bool key_unhandled; // The unhandled key. It has the meaning only when // 'key_unhandled' is true. private Key unhandled_key; internal ChangedStatus changed; private string error_message; public string Error { get { return error_message; } set { error_message = value; } } private void set_cursor (string prefix, int pos) { cursor_pos = pos; if (cursor_pos > 0) candidates = (Candidates) preedit.GetProp (cursor_pos - 1, Mcandidates); else candidates = null; } internal void reset () { status = initial_state.title; produced.Del (); preedit.Del (); set_cursor ("reset", 0); marker_positions.Clear (); candidates = null; candidate_show = false; state = prev_state = null; state_preedit.Del (); state_var_values = state_initial_var_values; state_pos = 0; shift (initial_state); preceding_text.Del (); following_text.Del (); changed = ChangedStatus.None; } static Xex.Term[] catch_args = new Xex.Term[2]; private bool take_actions (Xex.Term[] actions) { catch_args[0] = Tcatch_tag; catch_args[1]= new Xex.Term (domain, Qprogn, actions); Xex.Term term = new Xex.Term (domain, Qcatch, catch_args); term = Xex.Eval (domain, term); return (! term.IsSymbol || term.Symval != Tcatch_tag.Symval); } static MPlist callback_arg = new MPlist (); private bool get_surrounding_text (int len) { if (len < 0 ? -len <= preceding_text.Length : len <= following_text.Length) return true; if (GetSurroundingText == null) return false; callback_arg.Set (MSymbol.integer, len); if (! GetSurroundingText (this, callback_arg) || ! callback_arg.IsMText) return false; if (len < 0) { preceding_text = callback_arg.Text; return (-len <= preceding_text.Length); } following_text = callback_arg.Text; return (len <= following_text.Length); } internal int GetSurroundingChar (int pos) { if (! get_surrounding_text (pos < 0 ? pos : pos + 1)) return 0; if (pos < 0) return preceding_text[preceding_text.Length + pos]; return following_text[pos]; } private void adjust_markers (int from, int to, int inserted) { int diff = inserted - (to - from); for (MPlist p = marker_positions; ! p.IsEmpty; p = p.next) { int pos = p.Integer; if (pos > from) p.Set (p.Key, pos >= to ? pos + diff : from); } if (cursor_pos >= to) set_cursor ("adjust", cursor_pos + diff); else if (cursor_pos > from) set_cursor ("adjust", from); } private void preedit_replace (int from, int to, int c, Candidates candidates) { preedit.Del (from, to); preedit.Ins (from, c); if (candidates != null) { preedit.PushProp (from, from + 1, Mcandidates, candidates); changed |= (ChangedStatus.Preedit | ChangedStatus.CursorPos | CandidateAll); } adjust_markers (from, to, 1); } private void preedit_replace (int from, int to, MText mt, Candidates candidates) { preedit[from, to] = mt; if (candidates != null) { preedit.PushProp (from, from + mt.Length, Mcandidates, candidates); changed |= (ChangedStatus.Preedit | ChangedStatus.CursorPos | CandidateAll); } adjust_markers (from, to, mt == null ? 0 : mt.Length); } internal void insert (int c, Candidates candidates) { preedit_replace (cursor_pos, cursor_pos, c, candidates); changed |= ChangedStatus.Preedit | ChangedStatus.CursorPos; } internal void insert (MText mt, Candidates candidates) { preedit_replace (cursor_pos, cursor_pos, mt, candidates); changed |= ChangedStatus.Preedit | ChangedStatus.CursorPos; } internal int delete (int pos) { int deleted = pos - cursor_pos; if (pos < cursor_pos) { if (pos < 0) { if (DelSurroundingText != null) { Console.WriteLine ("deleting the prev {0} chars", - pos); callback_arg.Set (MSymbol.integer, pos); if (DelSurroundingText (this, callback_arg)) { if (callback_arg.IsInteger) deleted = callback_arg.Integer - cursor_pos; preceding_text.Del (); } else deleted = - cursor_pos; } pos = 0; } if (pos < cursor_pos) preedit_replace (pos, cursor_pos, null, null); } else { if (pos > preedit.Length) { if (DelSurroundingText != null) { Console.WriteLine ("deleting the next {0} chars", pos - preedit.Length); callback_arg.Set (MSymbol.integer, pos - preedit.Length); if (DelSurroundingText (this, callback_arg)) { if (callback_arg.IsInteger) deleted = callback_arg.Integer - cursor_pos; preceding_text.Del (); } else deleted = preedit.Length - cursor_pos; } pos = preedit.Length; } if (pos > cursor_pos) preedit_replace (cursor_pos, pos, null, null); } if (deleted != 0) changed |= ChangedStatus.Preedit | ChangedStatus.CursorPos; return deleted; } internal void show () { candidate_show = true; changed |= ChangedStatus.CandidateShow; } internal void hide () { candidate_show = false; changed |= ChangedStatus.CandidateShow; } internal void move (int pos) { if (pos < 0) pos = 0; else if (pos > preedit.Length) pos = preedit.Length; if (pos != cursor_pos) { set_cursor ("move", pos); changed |= ChangedStatus.Preedit; } } internal void pushback (int n) { if (n > 0) { key_head -= n; if (key_head < 0) key_head = 0; } else if (n == 0) key_head = 0; else { key_head = - n; if (key_head > keys.keyseq.Count) key_head = keys.keyseq.Count; } } internal void pushback (KeySeq keyseq) { if (key_head > 0) key_head--; if (key_head < keys.keyseq.Count) keys.keyseq.RemoveRange (key_head, keys.keyseq.Count - key_head); for (int i = 0; i < keyseq.keyseq.Count; i++) keys.keyseq.Add (keyseq.keyseq[i]); } internal void pop () { if (key_head < keys.keyseq.Count) keys.keyseq.RemoveRange (key_head, 1); } internal void undo (int n) { if (n < 0) keys.keyseq.RemoveRange (keys.keyseq.Count + n, - n); else keys.keyseq.RemoveRange (n, keys.keyseq.Count - n); reset (); } internal void commit () { if (preedit.Length > 0) { Candidates.Detach (this); produced.Cat (preedit); preedit_replace (0, preedit.Length, null, null); } } internal void shift (State state) { if (state == null) { if (prev_state == null) return; state = prev_state; } if (state == initial_state) { commit (); keys.keyseq.RemoveRange (0, key_head); key_head = 0; if (state != this.state) { domain.RestoreValues (state_initial_var_values); if (state.enter_actions != null) take_actions (state.enter_actions); } prev_state = null; } else { if (state != this.state && state.enter_actions != null) take_actions (state.enter_actions); prev_state = this.state; } save_state (); if (this.state == null || this.state.title != state.title) this.changed |= ChangedStatus.StateTitle; this.state = state; keymap = state.keymap; } public Context (MInputMethod im) { if (im.load_status != LoadStatus.Full && ! im.Open ()) throw new Exception ("Opening " + im.tag + " failed"); this.im = im; domain = new Xex.Domain ("context", im.domain, this); initial_state = (State) im.states.Val; state_initial_var_values = domain.SaveValues (); reset (); active = true; if (PreeditChanged != null) { callback_arg.Set (MSymbol.mtext, preedit); PreeditChanged (this, callback_arg); } if (StatusChanged != null) { callback_arg.Set (MSymbol.mtext, status); StatusChanged (this, callback_arg); } } public ChangedStatus Changed { get { return changed; } } internal object GetCandidates (out int column) { column = 0; if (cursor_pos == 0) return null; Candidates candidates = (Candidates) preedit.GetProp (cursor_pos - 1, Mcandidates); if (candidates == null) return null; column = candidates.Column; return candidates.Current; } private void save_state () { state_var_values = domain.SaveValues (); state_preedit.Del (); state_preedit.Ins (0, preedit); state_key_head = key_head; state_pos = cursor_pos; } private void restore_state () { domain.RestoreValues (state_var_values); preedit.Del (); preedit.Ins (0, state_preedit); set_cursor ("restore", state_pos); } private bool handle_key () { Console.WriteLine ("{0}:key='{1}'", state.name, keys.keyseq[key_head]); Keymap sub = keymap.Lookup (keys, ref key_head); if (sub != keymap) { restore_state (); keymap = sub; if (keymap.map_actions != null) { if (! take_actions (keymap.map_actions)) return false; } else if (keymap.submaps != null) { for (int i = state_key_head; i < key_head; i++) preedit_replace (cursor_pos, cursor_pos, keys.keyseq[i].ToChar (), null); } if (keymap.submaps == null) { if (keymap.branch_actions != null) { if (! take_actions (keymap.branch_actions)) return false; } if (keymap != state.keymap) shift (state); } } else { State current_state = state; if (keymap.branch_actions != null) { if (! take_actions (keymap.branch_actions)) return false; } if (state == current_state) { if (state == initial_state && key_head < keys.keyseq.Count) return false; if (keymap != state.keymap) shift (state); else if (keymap.branch_actions == null) shift (initial_state); } } return true; } public bool Toggle () { active = ! active; return active; } public bool UnhandledKey (out Key key) { key = unhandled_key; return key_unhandled; } public MText Preedit { get { return preedit; } } public MText Produced { get { return produced; } } // Return value: // true: All keys are handled and there's no text to commit. // false: Some key is left unhandled or there's a text to // commit. The caller should refer to UnhandledKey and // Produced. public bool Filter (Key key) { if (! active) { key_unhandled = true; unhandled_key = key; return false; } if (key == Key.Reload) return true; changed = ChangedStatus.None; produced.Del (); preceding_text.Del (); following_text.Del (); key_unhandled = false; keys.keyseq.Add (key); int count = 0; while (key_head < keys.keyseq.Count) { if (! handle_key ()) { unhandled_key = keys.keyseq[key_head++]; key_unhandled = true; break; } if (++count == 10) break; } keys.keyseq.RemoveRange (0, key_head); key_head = 0; if ((changed & ChangedStatus.Preedit) != ChangedStatus.None && PreeditChanged != null) { callback_arg.Set (MSymbol.mtext, preedit); PreeditChanged (this, callback_arg); } if ((changed & ChangedStatus.StateTitle) != ChangedStatus.None && StatusChanged != null) { callback_arg.Set (MSymbol.mtext, status); StatusChanged (this, callback_arg); } if ((changed & ChangedStatus.Candidate) != ChangedStatus.None && CandidateChanged != null) { CandidateChanged (this, callback_arg); } return (! key_unhandled && produced.Length == 0); } public bool Filter () { changed = ChangedStatus.None; produced.Del (); preceding_text.Del (); following_text.Del (); commit (); if ((changed & ChangedStatus.Preedit) != ChangedStatus.None && PreeditChanged != null) { callback_arg.Set (MSymbol.mtext, preedit); PreeditChanged (this, callback_arg); } if ((changed & ChangedStatus.StateTitle) != ChangedStatus.None && StatusChanged != null) { callback_arg.Set (MSymbol.mtext, status); StatusChanged (this, callback_arg); } if ((changed & ChangedStatus.Candidate) != ChangedStatus.None && CandidateChanged != null) { CandidateChanged (this, callback_arg); } return (produced.Length == 0); } } public class Session { Context ic; MText mt; int pos; public Session (MInputMethod im, MText mt, int pos) { ic = new Context (im); this.mt = mt; this.pos = pos; ic.GetSurroundingText = get_surrounding_text; ic.DelSurroundingText = del_surrounding_text; } private bool get_surrounding_text (Context ic, MPlist args) { int len = args.Integer; if (len < 0) args.Set (MSymbol.mtext, mt[0, pos]); else args.Set (MSymbol.mtext, mt[pos, mt.Length]); return true; } private bool del_surrounding_text (Context ic, MPlist args) { int pos = this.pos + args.Integer; Console.WriteLine ("del-surround: {0}-{1}", this.pos, pos); if (pos < this.pos) { mt.Del (pos, this.pos); this.pos = pos; } else mt.Del (this.pos, pos); return true; } public bool HandleKey (ref Key key) { if (! ic.Filter (key)) { MText produced = ic.Produced; mt.Ins (pos, produced); pos += produced.Length; Key unhandled; if (ic.UnhandledKey (out unhandled)) { key = unhandled; return false; } } return true; } public bool Close () { bool result = ic.Filter (); if (! result) { mt.Ins (pos, ic.Produced); pos += ic.Produced.Length; } ic = null; mt = null; return result; } public int CurrentPos { get { return pos; } set { pos = value; } } public MText Preedit { get { return ic.Preedit; } } } } }