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 { internal static MExpression.Domain domain = new MExpression.Domain (null); internal MExpression.Domain local_domain; 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"); private static MSymbol Mdelete = MSymbol.Of ("delete"); private static MSymbol Mmove = MSymbol.Of ("move"); private static MSymbol Mmark = MSymbol.Of ("mark"); private static MSymbol Mmarker = MSymbol.Of ("marker"); private static MSymbol Madd = MSymbol.Of ("add"); private static MSymbol Msub = MSymbol.Of ("sub"); private static MSymbol Mmul = MSymbol.Of ("mul"); private static MSymbol Mdiv = MSymbol.Of ("div"); private static MSymbol Mif = MSymbol.Of ("if"); private static MSymbol Mcond = MSymbol.Of ("cond"); private static MSymbol Mchar_at = MSymbol.Of ("char-at"); 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 (); public readonly MSymbol language; public readonly MSymbol name; public readonly MSymbol subname; 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 () { domain.Defun ("insert", insert, 1, 1); domain.Defun ("candidates", insert_candidates, 1, 1); domain.Defun ("delete", delete, 1, 1); domain.Defun ("select", select, 1, 1); domain.Defun ("show", show, 0, 0); domain.Defun ("hide", hide, 0, 0); domain.Defun ("move", move, 1, 1); domain.Defun ("mark", mark, 1, 1); domain.Defun ("pushback", pushback, 1, 1); domain.Defun ("pop", pop, 0, 0); domain.Defun ("undo", undo, 0, 1); domain.Defun ("commit", commit, 0, 0); domain.Defun ("unhandle", unhandle, 0, 0); domain.Defun ("shift", shift, 1, 1); domain.Defun ("call", call, 2, -1); domain.Defun ("marker", marker, 1, 1, true); domain.Defun ("char-at", char_at, 1, 1, true); } 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]; local_domain = new MExpression.Domain (domain, null); 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 void transform (MPlist plist) { for (; ! plist.IsEmpty; plist = plist.next) { if (plist.IsMText) { MPlist p = new MPlist (); p.Add (MSymbol.symbol, Minsert); p.Add (MSymbol.mtext, plist.Text); plist.Set (MSymbol.plist, p); } else if (plist.IsInteger) { MPlist p = new MPlist (); p.Add (MSymbol.symbol, Minsert); p.Add (MSymbol.integer, plist.Integer); plist.Set (MSymbol.plist, p); } else if (plist.IsPlist) { MPlist pl = plist.Plist; if (pl.IsSymbol) { if (pl.Symbol == Madd) pl.Set (MSymbol.symbol, MSymbol.Of ("+=")); else if (pl.Symbol == Msub) pl.Set (MSymbol.symbol, MSymbol.Of ("-=")); else if (pl.Symbol == Mmul) pl.Set (MSymbol.symbol, MSymbol.Of ("*=")); else if (pl.Symbol == Mdiv) pl.Set (MSymbol.symbol, MSymbol.Of ("/=")); else if (pl.Symbol == Minsert) { // (insert (CANDIDATES ...)) // => (candidates CANDIDATES ...) if (pl.next.IsPlist) { pl.Set (MSymbol.symbol, Mcandidates); pl = pl.next; MPlist p = pl.Plist; pl.Set (p.key, p.val); for (p = p.next; ! p.IsEmpty; p = p.next); pl.Add (p.key, p.val); } } else if (pl.Symbol == Mif) { pl = pl.next; if (! pl.IsEmpty) transform (pl.next); } else if (pl.Symbol == Mcond) { for (pl = pl.next; ! pl.IsEmpty; pl = pl.next) if (pl.IsPlist) { MPlist p = pl.Plist; if (p.IsPlist) transform (p); else transform (p.next); } } else if (pl.Symbol == Mdelete || pl.Symbol == Mmove || pl.Symbol == Mmark) { pl = pl.next; if (pl.IsSymbol) { MSymbol sym = pl.Symbol; MPlist p = new MPlist (); p.Add (MSymbol.symbol, Mmarker); p.Add (MSymbol.symbol, sym); pl.Set (MSymbol.plist, p); } } else if (pl.Symbol == Mstate) { pl = pl.next; if (pl.IsSymbol) { MSymbol sym = pl.Symbol; MPlist p = new MPlist (); p.Add (MSymbol.symbol, Mstate); p.Add (MSymbol.symbol, sym); pl.Set (MSymbol.plist, p); } } } else if (pl.IsMText) { // (CANDIDATES ...) => (candidates CANDIDATES ...) pl.Push (MSymbol.symbol, Mcandidates); } } else if (plist.IsSymbol) { MSymbol sym = plist.Symbol; if (sym.Name[0] == '@') { MPlist p = new MPlist (); p.Add (MSymbol.symbol, Mchar_at); p.Add (MSymbol.symbol, sym); plist.Set (MSymbol.plist, p); } } } } 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_domain.CopyFunc (local_domain); else im.local_domain.CopyFunc (target_name, local_domain); } 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; transform (pl.next); local_domain.Defun (pl.Symbol, new MPlist (), pl.next); } } 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; transform (p); MExpression expr = new MExpression (p, local_domain); 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; transform (p); state.branches[map_name] = new MExpression (p, local_domain); } } } 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 (MExpression[] args, MExpression.Domain domain) { MInputContext ic = (MInputContext) domain.context; object arg = args[0].Val; 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 (MExpression[] args, MExpression.Domain domain) { MInputContext ic = (MInputContext) domain.context; MPlist list = (MPlist) args[0].Val; int column = 0; if (domain.IsBound (Mcandidates_group_size)) { object val = domain.GetValue (Mcandidates_group_size); if (val is int) column = (int) val; } Candidates candidates = new Candidates (list, column); candidates.Update (ic); return 1; } private static object state (MExpression[] args, MExpression.Domain domain) { MInputContext ic = (MInputContext) domain.context; MSymbol sym = (MSymbol) args[0].Args[0].Val; if (sym == MSymbol.t) return MSymbol.t; return ic.im.states[sym]; } private static object marker (MExpression[] args, MExpression.Domain domain) { MInputContext ic = (MInputContext) domain.context; MSymbol sym = (MSymbol) args[0].Args[0].Val; int pos = ic.cursor_pos; if (sym.Name.Length == 2 && sym.Name[0] == '@') { switch (sym.Name[0]) { case '<': pos = 0; break; case '>': pos = ic.preedit.Length; break; case '-': pos = ic.cursor_pos - 1; break; case '+': pos = ic.cursor_pos + 1; break; case '[': if (pos > 0) { int to; ic.preedit.FindProp (Mcandidates, pos - 1, out pos, out to); } else pos = 0; break; case ']': if (ic.cursor_pos < ic.preedit.Length - 1) { int from; ic.preedit.FindProp (Mcandidates, pos, out from, out pos); } else pos = ic.preedit.Length; break; default: if (sym.Name[0] >= '0' && sym.Name[0] <= '9') pos = sym.Name[0]; break; } } else if (sym.Name.Length >= 3 && sym.Name[0] == '@') { } else { object val = ic.markers.Get (sym); if (val is int) pos = (int) val; } return pos; } private static object char_at (MExpression[] args, MExpression.Domain domain) { MInputContext ic = (MInputContext) domain.context; MSymbol sym = (MSymbol) args[0].Args[0].Val; if ( return 0; } private static object delete (MExpression[] args, MExpression.Domain domain) { MInputContext ic = (MInputContext) domain.context; int pos = (int) args[0].Val; if (pos < ic.cursor_pos) preedit_replace (ic, pos, ic.cursor_pos, null); else preedit_replace (ic, ic.cursor_pos, pos, null); ic.preedit_changed = true; ic.cursor_pos_changed = true; return true; } private static object select (MExpression[] args, MExpression.Domain domain) { MInputContext ic = (MInputContext) domain.context; object arg = args[0].Val; 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 show (MExpression[] args, MExpression.Domain domain) { return 1; } private static object hide (MExpression[] args, MExpression.Domain domain) { return 1; } private static object move (MExpression[] args, MExpression.Domain domain) { return 1; } private static object mark (MExpression[] args, MExpression.Domain domain) { return 1; } private static object pushback (MExpression[] args, MExpression.Domain domain) { return 1; } private static object pop (MExpression[] args, MExpression.Domain domain) { return 1; } private static object undo (MExpression[] args, MExpression.Domain domain) { return 1; } private static object commit (MExpression[] args, MExpression.Domain domain) { return 1; } private static object unhandle (MExpression[] args, MExpression.Domain domain) { return 1; } private static object shift (MExpression[] args, MExpression.Domain domain) { return 1; } private static object call (MExpression[] args, MExpression.Domain domain) { 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 MExpression.Domain domain; public MInputContext (MInputMethod im) { this.im = im; domain = new MExpression.Domain (im.local_domain, this); } 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; } } }