using System; using System.Collections; using System.Collections.Generic; using System.IO; using M17N; using M17N.Core; using M17N.Input; namespace M17N.Input { public class MInputMethod { private static MSymbol Minput_method = MSymbol.Of ("input-method"); private static MSymbol Mdescription = MSymbol.Of ("description"); private static MSymbol Mvariable = MSymbol.Of ("variable"); private static MSymbol Mcommand = MSymbol.Of ("command"); private static MSymbol Mtitle = MSymbol.Of ("title"); private static MSymbol Minclude = MSymbol.Of ("include"); private static MSymbol Mmacro = MSymbol.Of ("macro"); private static MSymbol Mmap = MSymbol.Of ("map"); private static MSymbol Mstate = MSymbol.Of ("state"); internal static MSymbol Mcandidates = MSymbol.Of ("candidates"); internal static MSymbol Mcandidates_group_size = MSymbol.Of ("candidates-group-size"); private static MSymbol Mat_less_than = MSymbol.Of ("@<"); private static MSymbol Mat_greater_than = MSymbol.Of ("@>"); private static MSymbol Mat_minus = MSymbol.Of ("@-"); private static MSymbol Mat_plus = MSymbol.Of ("@+"); private static MSymbol Mat_open_square_bracket = MSymbol.Of ("@["); private static MSymbol Mat_close_square_bracket = MSymbol.Of ("@]"); private static MSymbol Minsert = MSymbol.Of ("insert"); internal class Variable { public MSymbol name; public MText description; public Type type; public object value; public MPlist candidates; } internal class Command { public MSymbol name; public MText description; public MSymbol[][] keys; } internal class KeySeq { public MSymbol[] keys; private static MSymbol char_to_symbol (int c) { return MSymbol.Of (String.Format ("#{0:X}", c)); } public KeySeq (MPlist plist) { keys = new MSymbol[plist.Count]; int i = 0; foreach (MPlist p in plist) { if (p.IsSymbol) keys[i++] = p.Symbol; else if (p.IsInteger) keys[i++] = char_to_symbol (p.Integer); else keys[i] = null; } } public KeySeq (MText mt) { keys = new MSymbol[mt.Length]; for (int i = 0; i < mt.Length; i++) keys[i] = char_to_symbol (mt[i]); } public MSymbol this[int i] { get { return keys[i]; } } public int Length { get { return keys.Length; } } } internal class Map { public MSymbol name; public Dictionary submaps; public MExpression actions; public void Add (KeySeq keys, int index, MExpression actions) { Map sub = null; if (submaps == null) submaps = new Dictionary (); else submaps.TryGetValue (keys[index], out sub); if (sub == null) { MSymbol sym = keys[index]; submaps[sym] = sub = new Map (); } if (index + 1 < keys.Length) sub.Add (keys, index + 1, actions); else this.actions = actions; } public MExpression Lookup (KeySeq keys, int index) { Map sub; if (index + 1 == keys.Length) return actions; if (submaps.TryGetValue (keys[index], out sub)) return sub.Lookup (keys, index + 1); return null; } } internal class State { public MSymbol name; public MText title; public Dictionary branches = new Dictionary (); } private static Dictionary im_table = new Dictionary (); private static MExpression.FunctionTable global_table = new MExpression.FunctionTable (); public readonly MSymbol language; public readonly MSymbol name; public readonly MSymbol subname; internal MExpression.FunctionTable local_table = new MExpression.FunctionTable (global_table); internal MDatabase mdb; internal MText description; internal MText title; internal Command[] commands; internal Variable[] variables; internal MPlist bindings; internal Dictionary maps = new Dictionary (); internal State init_state; internal Dictionary states = new Dictionary (); internal MPlist externals; static MInputMethod () { MExpression.Defun (global_table, "insert", new MExpression.Evaluator (insert), 1, 1, typeof (MExpression)); MExpression.Defun (global_table, "candidates", new MExpression.Evaluator (insert_candidates), 1, 1, typeof (object)); MExpression.Defun (global_table, "delete", new MExpression.Evaluator (delete), 1, 1, typeof (object)); MExpression.Defun (global_table, "select", new MExpression.Evaluator (select), 1, 1, typeof (object)); MExpression.Defun (global_table, "show", new MExpression.Evaluator (show), 0, 0); MExpression.Defun (global_table, "hide", new MExpression.Evaluator (hide), 0, 0); MExpression.Defun (global_table, "move", new MExpression.Evaluator (move), 1, 1, typeof (object)); MExpression.Defun (global_table, "mark", new MExpression.Evaluator (mark), 1, 1, typeof (MSymbol)); MExpression.Defun (global_table, "pushback", new MExpression.Evaluator (pushback), 1, 1, typeof (object)); MExpression.Defun (global_table, "pop", new MExpression.Evaluator (pop), 0, 0); MExpression.Defun (global_table, "undo", new MExpression.Evaluator (undo), 0, 1, typeof (object)); MExpression.Defun (global_table, "commit", new MExpression.Evaluator (commit), 0, 0); MExpression.Defun (global_table, "unhandle", new MExpression.Evaluator (unhandle), 0, 0); MExpression.Defun (global_table, "shift", new MExpression.Evaluator (shift), 1, 1, typeof (MSymbol)); MExpression.Defun (global_table, "call", new MExpression.Evaluator (call), 2, -1, typeof (MSymbol), typeof (MSymbol), typeof (object)); } private MInputMethod (MDatabase.Tag tag) { mdb = MDatabase.Find (tag); if (mdb == null) throw new Exception (String.Format ("Input method {0} not available", tag)); language = tag[1]; name = tag[2]; subname = tag[3]; MPlist plist = (MPlist) mdb.Load (); if (plist == null) return; 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); if (description == null) description = new MText ("No description"); } 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 (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); } } } private static MText parse_description (MPlist plist) { return (plist.IsMText ? plist.Text : plist.IsPlist && plist.Plist.IsMText ? plist.Plist.Text : null); } private void parse_variables (MPlist plist) { variables = new Variable[plist.Count]; for (int i = 0; ! plist.IsEmpty; plist = plist.next) if (plist.IsPlist && plist.Plist.IsSymbol) { Variable var = new Variable (); MPlist p = plist.Plist; var.name = p.Symbol; p = p.Next; var.description = parse_description (p); if (var.description == null) var.description = new MText ("No description"); else p = p.next; var.type = (p.IsMText ? typeof (MText) : p.IsInteger ? typeof (int) : p.IsSymbol ? typeof (MSymbol) : typeof (object)); var.value = p.val; var.candidates = p.next; variables[i++] = var; } } 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) { Command cmd = new Command (); MPlist p = plist.Plist; cmd.name = p.Symbol; p = p.Next; cmd.description = parse_description (p); if (cmd.description == null) cmd.description = new MText ("No description"); else p = p.next; KeySeq[] keys = new KeySeq[p.Count]; for (int j = 0; ! p.IsEmpty; p = p.next) { if (p.IsMText) keys[j++] = new KeySeq (p.Text); else if (p.IsPlist) keys[j++] = new KeySeq (p.Plist); } commands[i++] = cmd; } } private void parse_include (MPlist plist) { if (! plist.IsPlist) return; MPlist p = plist.Plist; MSymbol language, name, extra; language = p.Symbol; p = p.next; if (! p.IsSymbol) name = extra = MSymbol.nil; else { name = p.Symbol; p = p.next; if (! p.IsSymbol) extra = MSymbol.nil; else extra = p.Symbol; } MInputMethod im = MInputMethod.Get (language, name, extra); if (im == null) return; plist = plist.next; if (! plist.IsSymbol) return; MSymbol target_type = plist.Symbol; plist = plist.next; MSymbol target_name = MSymbol.nil; if (plist.IsSymbol) target_name = plist.Symbol; if (target_type == Mmacro) { if (target_name == MSymbol.nil) im.local_table.Copy (local_table); else im.local_table.Copy (target_name, local_table); } else if (target_type == Mmap) { if (target_name == MSymbol.nil) { foreach (KeyValuePair kv in im.maps) maps[kv.Key] = kv.Value; } else { Map map; if (im.maps.TryGetValue (target_name, out map)) maps[target_name] = map; } } else if (target_type == Mstate) { if (target_name == MSymbol.nil) { foreach (KeyValuePair kv in im.states) states[kv.Key] = kv.Value; } else { State state; if (im.states.TryGetValue (target_name, out state)) states[target_name] = state; } } } private void parse_macros (MPlist plist) { for (; ! plist.IsEmpty; plist = plist.next) if (plist.IsPlist) { MPlist pl = plist.Plist; if (! pl.IsSymbol) continue; MSymbol name = pl.Symbol; MExpression expr = new MExpression (pl.next, local_table); MExpression.Defmacro (local_table, name, expr); } } 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 (); map.name = pl.Symbol; maps[map.name] = 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; if (keys.keys.Length == 0 && keys.keys[0] == null) continue; p = p.next; if (p.IsEmpty) continue; MExpression expr = new MExpression (p, local_table); map.Add (keys, 0, expr); } } } private void parse_states (MPlist plist) { for (; ! plist.IsEmpty; plist = plist.next) if (plist.IsPlist) { MPlist pl = plist.Plist; MText title = null; if (pl.IsMText) { title = pl.Text; pl = pl.next; } if (! pl.IsSymbol) continue; State state = new State (); state.name = pl.Symbol; state.title = title; states[state.name] = state; if (init_state == null) init_state = state; for (pl = pl.next; ! pl.IsEmpty; pl = pl.next) { if (! pl.IsPlist) continue; MPlist p = pl.Plist; if (! p.IsSymbol) continue; MSymbol map_name = p.Symbol; p = p.next; state.branches[map_name] = new MExpression (p, local_table); } } } public static MInputMethod Get (MSymbol language, MSymbol name, MSymbol extra) { MDatabase.Tag tag = new MDatabase.Tag (Minput_method, language, name, extra); MInputMethod im; if (im_table.TryGetValue (tag, out im)) return im; try { im = new MInputMethod (tag); } catch (Exception e) { Console.WriteLine (e); im = null; } return im; } private static void adjust_markers (MInputContext ic, int from, int to, object inserted) { int ins = (inserted == null ? 0 : inserted is int ? 1 : ((MText) inserted).Length); int diff = ins - (to - from); for (MPlist plist = ic.markers; ! plist.IsEmpty; plist = plist.next) { int pos = plist.Integer; if (pos > from) { if (pos >= to) plist.val = pos + diff; else plist.val = from; } } if (ic.cursor_pos >= to) ic.cursor_pos += diff; else if (ic.cursor_pos > from) ic.cursor_pos = from; } private static void preedit_replace (MInputContext ic, int from, int to, int c) { ic.preedit.Del (from, to); ic.preedit.Ins (from, c); adjust_markers (ic, from, to, c); } private static void preedit_replace (MInputContext ic, int from, int to, MText mt) { ic.preedit[from, to] = mt; adjust_markers (ic, from, to, mt); } private static object insert (object[] args, MPlist bindings, object context) { MInputContext ic = (MInputContext) context; object arg = ((MExpression) args[0]).Eval (bindings, ic); if (arg is int) preedit_replace (ic, ic.cursor_pos, ic.cursor_pos, (int) arg); else preedit_replace (ic, ic.cursor_pos, ic.cursor_pos, (MText) arg); ic.preedit_changed = true; ic.cursor_pos_changed = true; return 1; } internal class Candidates { private class Block { public int Index; public object Data; public Block (int index, MPlist plist) { Index = index; if (plist.IsMText) Data = plist.Text; else Data = plist.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]; } public static void Detach (MInputContext ic) { ic.preedit.PopProp (0, ic.preedit.Length, Mcandidates); ic.candidates = null; ic.preedit_changed = true; ic.cursor_pos_changed = true; ic.candidate_changed = true; } // Fill the array "group" by candidates stating from INDEX. // INDEX must be a multiple of "column". Set NTIMES to the // number of valid candidates in "group". Update "block" if // necessary. Return "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 void Select (int col) { int maxcol = GroupLength - 1; if (col > maxcol) col = maxcol; index = index - Column + col; } public void Update (MInputContext ic) { int from, to; if (ic.candidates == null) { from = ic.cursor_pos; to = ic.cursor_pos; ic.candidates = this; } else { from = ic.candidate_from; to = ic.candidate_to; } object candidate = ic.candidates.Current; if (candidate is MText) preedit_replace (ic, from, to, (MText) candidate); else preedit_replace (ic, from, to, (int) candidate); ic.preedit.PushProp (from, to, Mcandidates, this); ic.cursor_pos = to; ic.candidate_from = from; ic.candidate_to = to; ic.preedit_changed = true; ic.cursor_pos_changed = true; ic.candidate_changed = true; } } private static object insert_candidates (object[] args, MPlist bindings, object context) { MInputContext ic = (MInputContext) context; MPlist list = (MPlist) args[0]; int column = 0; MPlist slot = (MPlist) bindings.Find (Mcandidates_group_size); if (slot != null) column = slot.Integer; Candidates candidates = new Candidates (list, column); candidates.Update (ic); return 1; } private static object select (object[] args, MPlist bindings, object context) { MInputContext ic = (MInputContext) context; object arg = args[0]; if (ic.candidates == null) return 0; if (arg is MSymbol) { MSymbol sym = (MSymbol) arg; if (sym == Mat_less_than) ic.candidates.First (); else if (sym == Mat_greater_than) ic.candidates.Last (); else if (sym == Mat_minus) ic.candidates.Prev (); else if (sym == Mat_plus) ic.candidates.Next (); else if (sym == Mat_open_square_bracket) ic.candidates.PrevGroup (); else if (sym == Mat_close_square_bracket) ic.candidates.NextGroup (); else return 0; } else if (arg is int) ic.candidates.Select ((int) arg); ic.candidates.Update (ic); return 0; } private static object delete (object[] args, MPlist bindings, object context) { return 1; } private static object show (object[] args, MPlist bindings, object context) { return 1; } private static object hide (object[] args, MPlist bindings, object context) { return 1; } private static object move (object[] args, MPlist bindings, object context) { return 1; } private static object mark (object[] args, MPlist bindings, object context) { return 1; } private static object pushback (object[] args, MPlist bindings, object context) { return 1; } private static object pop (object[] args, MPlist bindings, object context) { return 1; } private static object undo (object[] args, MPlist bindings, object context) { return 1; } private static object commit (object[] args, MPlist bindings, object context) { return 1; } private static object unhandle (object[] args, MPlist bindings, object context) { return 1; } private static object shift (object[] args, MPlist bindings, object context) { return 1; } private static object call (object[] args, MPlist bindings, object context) { return 1; } } public class MInputContext { public MInputMethod im; public MText produced; public bool active; public MText status; public bool status_changed; public MText preedit; public bool preedit_changed; public int cursor_pos; public bool cursor_pos_changed; internal MInputMethod.Candidates candidates; public MPlist candidate_group; public int candidate_index; public int candidate_from, candidate_to; public bool candidate_show; public bool candidate_changed; public MPlist callback_args; private MInputMethod.State state; private MInputMethod.State prev_state; private Stack keys; private int state_key_head; private int key_head; private int commit_key_head; private MText preedit_saved; private int state_pos; internal MPlist markers = new MPlist (); private MPlist vars; private MPlist vars_saved; private MText preceding_text; private MText following_text; private bool key_unhandled; internal object GetCandidates (out int column) { column = 0; if (cursor_pos == 0) return null; MInputMethod.Candidates candidates = (MInputMethod.Candidates) preedit.GetProp (cursor_pos - 1, MInputMethod.Mcandidates); if (candidates == null) return null; column = candidates.Column; return candidates.Current; } } }