diff --git a/web/source/kmwcallback.ts b/web/source/kmwcallback.ts index 6698cad515..40bf95b7c5 100644 --- a/web/source/kmwcallback.ts +++ b/web/source/kmwcallback.ts @@ -22,24 +22,32 @@ type KeyboardStore = PlainKeyboardStore; // | ComplexKeyboardStore; type RuleChar = string; class RuleDeadkey { + /** Discriminant field - 'd' for Deadkey. + */ + ['t']: 'd'; + /** - * Existence of this property indicates the object is a deadkey. - * * Value: the deadkey's ID. */ ['d']: number; // For 'd'eadkey; also reflects the Deadkey class's 'd' property. } class ContextAny { + /** Discriminant field - 'a' for `any()`. + */ + ['t']: 'a'; + /** - * Existence of this property indicates the object reflects an "any" statement. - * * Value: the store to search. */ ['a']: KeyboardStore; // For 'a'ny statement. } class RuleIndex { + /** Discriminant field - 'i' for `index()`. + */ + ['t']: 'i'; + /** * Existence of this property indicates the object reflects an "index" statement. * @@ -49,6 +57,10 @@ class RuleIndex { } class ContextEx { + /** Discriminant field - 'c' for `context()`. + */ + ['t']: 'c'; + /** * Existence of this property indicates that the object reflects a "context" Keyman language statement. * @@ -57,7 +69,8 @@ class ContextEx { ['c']: number; // For 'c'ontext statement. } -type ContextEntry = RuleChar | RuleDeadkey | ContextAny | RuleIndex | ContextEx; +type ContextNonCharEntry = RuleDeadkey | ContextAny | RuleIndex | ContextEx; +type ContextEntry = RuleChar | ContextNonCharEntry; /** * Cache of context storing and retrieving return values from KC @@ -414,40 +427,56 @@ class KeyboardInterface { var mismatch = false; + var assertNever = function(x: never): never { + // Could be accessed by improperly handwritten calls to `fullContextMatch`. + throw new Error("Unexpected object in fullContextMatch specification: " + x); + } + // Stage two: time to match against the rule specified. for(var i=0; i < rule.length; i++) { - if(typeof(rule[i]) == 'string') { + if(typeof rule[i] == 'string') { var str = rule[i] as string; if(str != context[i]) { mismatch = true; break; } - } else if(rule[i]['d'] !== undefined) { - var deadSpec = rule[i] as RuleDeadkey; - // We still need to set a flag here; - if(deadSpec['d'] != context[i]) { - mismatch = true; - } else { - deadContext[i].set(); - } - } else if(rule[i]['a'] !== undefined) { - var anySpec = rule[i] as ContextAny; - // TODO: Remove the `string` requirement. - if(!this.any(i, context[i] as string, anySpec.a)) { - mismatch = true; - } - } else if(rule[i]['i'] !== undefined) { - var indexSpec = rule[i] as RuleIndex; - var ch = this._Index(indexSpec.i.s, indexSpec.i.o); + } else { + // TypeScript needs a cast to this intermediate type to do its discriminated union magic. + var r = rule[i] as ContextNonCharEntry; + switch(r.t) { + case 'd': + var deadSpec = r as RuleDeadkey; + // We still need to set a flag here; + if(deadSpec['d'] != context[i]) { + mismatch = true; + } else { + deadContext[i].set(); + } + break; + case 'a': + var anySpec = r as ContextAny; + // TODO: Remove the `string` requirement. + if(!this.any(i, context[i] as string, anySpec.a)) { + mismatch = true; + } + break; + case 'i': + var indexSpec = r as RuleIndex; + var ch = this._Index(indexSpec.i.s, indexSpec.i.o); - if(ch != context[i]) { - mismatch = true; - } - } else if(rule[i]['c'] !== undefined) { - var contextSpec = rule[i] as ContextEx; - - if(context[contextSpec.c - 1] != context[i]) { - mismatch = true; + if(ch != context[i]) { + mismatch = true; + } + break; + case 'c': + var contextSpec = r as ContextEx; + + if(context[contextSpec.c - 1] != context[i]) { + mismatch = true; + } + break; + default: + assertNever(r); } } } @@ -820,9 +849,10 @@ class KeyboardInterface { if(this._AnyIndices[Pn] < Ps._kmwLength()) { //I3319 return Ps._kmwCharAt(this._AnyIndices[Pn]); } else { - /* Should this really be possible for a compiled keyboard? - * Should we throw an error / output a console.error("")? + /* Should not be possible for a compiled keyboard, but may arise + * during the development of handwritten keyboards. */ + console.warn("Unmatched contextual index() statement detected in rule with index " + Pn + "!"); return ""; } } @@ -841,6 +871,11 @@ class KeyboardInterface { var indexChar = this._Index(Ps, Pn-1); if(indexChar) { this.output(Pdn,Pelem,indexChar); //I3319 + } else { + /* Should not be possible for a compiled keyboard, but may arise + * during the development of handwritten keyboards. + */ + console.warn("Unmatched output index() statement detected in rule with index " + Pn + "!"); } } @@ -1034,9 +1069,8 @@ class KeyboardInterface { * Description Clear all matched deadkey flags */ _DeadkeyResetMatched(): void { - var _Dk = this._DeadKeys; - for(var Li = 0; Li < _Dk.length; Li++) { - _Dk[Li].reset(); + for(let dk of this._DeadKeys) { + dk.reset(); } } @@ -1062,10 +1096,9 @@ class KeyboardInterface { * Description Adjust saved positions of deadkeys in context */ _DeadkeyAdjustPos(Lstart: number, Ldelta: number): void { - var _Dk = this._DeadKeys; - for(var Li = 0; Li < _Dk.length; Li++) { - if(_Dk[Li].p > Lstart) { - _Dk[Li].p += Ldelta; + for(let dk of this._DeadKeys) { + if(dk.p > Lstart) { + dk.p += Ldelta; } } } diff --git a/web/unit_tests/cases/engine.js b/web/unit_tests/cases/engine.js index efaac55407..2a7d41f2af 100644 --- a/web/unit_tests/cases/engine.js +++ b/web/unit_tests/cases/engine.js @@ -44,7 +44,7 @@ function runEngineRuleSet(ruleSet, defaultNoun) { var DEADKEY_TEST_1 = { id: 1, // Match condition for rule - rule: [{d: 1}], + rule: [{t:'d', d: 1}], // Start of context relative to cursor n: 1, ln: 1, @@ -82,7 +82,7 @@ var DEADKEY_TEST_1 = { var DEADKEY_TEST_2 = { id: 2, // Match condition for rule - rule: ['a', {d: 0}, {d: 1}, 'b'], + rule: ['a', {t:'d', d: 0}, {t:'d', d: 1}, 'b'], // Start of context relative to cursor n: 5, ln: 4, @@ -150,7 +150,7 @@ var DEADKEY_TEST_2 = { var DEADKEY_TEST_3 = { id: 3, // Match condition for rule - rule: [{d: 0}, 'a', {d: 0}, {d: 0}, 'b'], + rule: [{t:'d', d: 0}, 'a', {t:'d', d: 0}, {t:'d', d: 0}, 'b'], // Start of context relative to cursor n: 5, ln: 5, @@ -238,7 +238,7 @@ var DEADKEY_TEST_3 = { var DEADKEY_TEST_4 = { id: 4, // Match condition for rule - rule: ['a', {d: 0}, {d: 0}, 'b', {d: 0}], + rule: ['a', {t:'d', d: 0}, {t:'d', d: 0}, 'b', {t:'d', d: 0}], // Start of context relative to cursor n: 6, ln: 5, @@ -280,7 +280,7 @@ var DEADKEY_TEST_5 = { var DEADKEY_TEST_6 = { id: 6, // Match condition for rule - rule: [{d: 1}, {d: 2}, {d: 0}, {d: 1}, {d: 2}], + rule: [{t:'d', d: 1}, {t:'d', d: 2}, {t:'d', d: 0}, {t:'d', d: 1}, {t:'d', d: 2}], // Start of context relative to cursor n: 5, ln: 5, @@ -301,7 +301,7 @@ var DEADKEY_TEST_6 = { var ANY_CONTEXT_TEST_1 = { id: 1, // Match condition for rule - rule: ['c', "a", "b", {c:3}, {c:2}], + rule: ['c', "a", "b", {t:'c', c:3}, {t:'c', c:2}], // Start of context relative to cursor n: 5, ln: 5, @@ -341,7 +341,7 @@ var ANY_CONTEXT_TEST_1 = { var ANY_CONTEXT_TEST_2 = { id: 2, // Match condition for rule - rule: ['c', 'a', {a: "bc"}, {c:3}, 'a'], + rule: ['c', 'a', {t:'a', a: "bc"}, {t:'c', c:3}, 'a'], // Start of context relative to cursor n: 5, ln: 5, @@ -381,7 +381,7 @@ var ANY_CONTEXT_TEST_2 = { var ANY_CONTEXT_TEST_3 = { id: 3, // Match condition for rule - rule: ['c', {a: "ac"}, {a: "bc"}, {c:3}, {c:2}], + rule: ['c', {t:'a', a: "ac"}, {t:'a', a: "bc"}, {t:'c', c:3}, {t:'c', c:2}], // Start of context relative to cursor n: 5, ln: 5, @@ -431,7 +431,7 @@ var ANY_CONTEXT_TEST_3 = { var ANY_INDEX_TEST_1 = { id: 1, // Match condition for rule - rule: ['c', 'a', {a: "bc"}, {i:{s:"bc", o:2}}, 'a'], + rule: ['c', 'a', {t:'a', a: "bc"}, {t:'i', i:{s:"bc", o:2}}, 'a'], // Start of context relative to cursor n: 5, ln: 5, @@ -471,7 +471,7 @@ var ANY_INDEX_TEST_1 = { var ANY_INDEX_TEST_2 = { id: 2, // Match condition for rule - rule: ['c', {a:"ab"}, {i: {s:"bc", o:1}}, {i:{s:"bc", o:1}}, {i:{s:"ab", o:1}}], + rule: ['c', {t:'a', a:"ab"}, {t:'i', i: {s:"bc", o:1}}, {t:'i', i:{s:"bc", o:1}}, {t:'i', i:{s:"ab", o:1}}], // Start of context relative to cursor n: 5, ln: 5, @@ -511,7 +511,7 @@ var ANY_INDEX_TEST_2 = { var ANY_INDEX_TEST_3 = { id: 3, // Match condition for rule - rule: ['c', {a:"ab"}, {a:"bc"}, {i:{s:"bc", o:2}}, {i:{s:"ab", o:1}}], + rule: ['c', {t:'a', a:"ab"}, {t:'a', a:"bc"}, {t:'i', i:{s:"bc", o:2}}, {t:'i', i:{s:"ab", o:1}}], // Start of context relative to cursor n: 5, ln: 5, diff --git a/web/unit_tests/resources/keyboards/temp_test_deadkeys_hand.js b/web/unit_tests/resources/keyboards/temp_test_deadkeys_hand.js index 8cd7f90ac5..060985e997 100644 --- a/web/unit_tests/resources/keyboards/temp_test_deadkeys_hand.js +++ b/web/unit_tests/resources/keyboards/temp_test_deadkeys_hand.js @@ -39,23 +39,23 @@ function Keyboard_test_deadkeys() }; this.g_main=function(t,e) { var k=KeymanWeb,r=0,m=0; - if(k.KKM(e, 0x4000, 0xBE)&&k.KFCM(7,t,['(','o',')',{d:18},'(','o',')'])) { // Line 51 + if(k.KKM(e, 0x4000, 0xBE)&&k.KFCM(7,t,['(','o',')',{t:'d',d:18},'(','o',')'])) { // Line 51 r=m=1; k.KO(6,t,"dk(o)"); } - else if(k.KKM(e, 0x4000, 0xBE)&&k.KFCM(4,t,[{d:18},'(','m',')'])) { // Line 45 + else if(k.KKM(e, 0x4000, 0xBE)&&k.KFCM(4,t,[{t:'d',d:18},'(','m',')'])) { // Line 45 r=m=1; k.KO(3,t,"dk(m)"); } - else if(k.KKM(e, 0x4000, 0xBE)&&k.KFCM(4,t,['(','n',')',{d:18}])) { // Line 48 + else if(k.KKM(e, 0x4000, 0xBE)&&k.KFCM(4,t,['(','n',')',{t:'d',d:18}])) { // Line 48 r=m=1; k.KO(3,t,"dk(n)"); } - else if(k.KKM(e, 0x4000, 0xBE)&&k.KFCM(2,t,[{d:16},{d:17}])) { // Line 40 + else if(k.KKM(e, 0x4000, 0xBE)&&k.KFCM(2,t,[{t:'d',d:16},{t:'d',d:17}])) { // Line 40 r=m=1; k.KO(0,t,"(a)-(s)"); } - else if(k.KKM(e, 0x4000, 0xBE)&&k.KFCM(2,t,[{d:17},{d:16}])) { // Line 41 + else if(k.KKM(e, 0x4000, 0xBE)&&k.KFCM(2,t,[{t:'d',d:17},{t:'d',d:16}])) { // Line 41 r=m=1; k.KO(0,t,"(s)+(a)"); } @@ -107,12 +107,12 @@ function Keyboard_test_deadkeys() r=m=1; k.KDO(0,t,16); } - else if(k.KKM(e, 0x4010, 0x42)&&k.KFCM(1, t, [{d:16}])) { // Line 94 + else if(k.KKM(e, 0x4010, 0x42)&&k.KFCM(1, t, [{t:'d',d:16}])) { // Line 94 r=m=1; k.KO(0,t,"ab"); k.KDO(-1,t,21); } - else if(k.KKM(e, 0x4010, 0x43)&&k.KFCM(1, t, [{d:22}])) { // Line 95 + else if(k.KKM(e, 0x4010, 0x43)&&k.KFCM(1, t, [{t:'d',d:22}])) { // Line 95 r=m=1; k.KO(0,t,"abc"); } @@ -120,7 +120,7 @@ function Keyboard_test_deadkeys() r=m=1; k.KO(3,t,"$abc$d"); } - else if(k.KKM(e, 0x4010, 0x45)&&k.KFCM(4,t,['a','b',{d:23},'d'])) { // Line 96 + else if(k.KKM(e, 0x4010, 0x45)&&k.KFCM(4,t,['a','b',{t:'d',d:23},'d'])) { // Line 96 r=m=1; k.KO(3,t,"success"); } @@ -130,15 +130,15 @@ function Keyboard_test_deadkeys() k.KDO(-1,t,20); k.KO(-1,t,"(P)"); } - else if(k.KKM(e, 0x4010, 0x51)&&k.KFCM(4,t,[{d:20},'(','P',')'])) { // Line 59 + else if(k.KKM(e, 0x4010, 0x51)&&k.KFCM(4,t,[{t:'d',d:20},'(','P',')'])) { // Line 59 r=m=1; k.KO(3,t,"(P)(Q)"); } - else if(k.KKM(e, 0x4010, 0x52)&&k.KFCM(8,t,[{d:20},{d:20},'(','P',')','(','Q',')'])) { // Line 61 + else if(k.KKM(e, 0x4010, 0x52)&&k.KFCM(8,t,[{t:'d',d:20},{t:'d',d:20},'(','P',')','(','Q',')'])) { // Line 61 r=m=1; k.KO(6,t,"ERROR"); } - else if(k.KKM(e, 0x4010, 0x52)&&k.KFCM(7,t,[{d:20},'(','P',')','(','Q',')'])) { // Line 60 + else if(k.KKM(e, 0x4010, 0x52)&&k.KFCM(7,t,[{t:'d',d:20},'(','P',')','(','Q',')'])) { // Line 60 r=m=1; k.KO(6,t,"(P)(Q)(R)"); } @@ -146,23 +146,23 @@ function Keyboard_test_deadkeys() r=m=1; k.KDO(0,t,16); } - else if(k.KKM(e, 0x4000, 0x42)&&k.KFCM(6,t,[{d:2},'d','e','a','d','1'])) { // Line 84 + else if(k.KKM(e, 0x4000, 0x42)&&k.KFCM(6,t,[{t:'d',d:2},'d','e','a','d','1'])) { // Line 84 r=m=1; k.KO(5,t,"ERROR"); } - else if(k.KKM(e, 0x4000, 0x42)&&k.KFCM(6,t,[{d:1},'d','e','a','d','2'])) { // Line 85 + else if(k.KKM(e, 0x4000, 0x42)&&k.KFCM(6,t,[{t:'d',d:1},'d','e','a','d','2'])) { // Line 85 r=m=1; k.KO(5,t,"success"); } - else if(k.KKM(e, 0x4000, 0x42)&&k.KFCM(6,t,[{d:4},'d','e','a','d','3'])) { // Line 89 + else if(k.KKM(e, 0x4000, 0x42)&&k.KFCM(6,t,[{t:'d',d:4},'d','e','a','d','3'])) { // Line 89 r=m=1; k.KO(5,t,"success"); } - else if(k.KKM(e, 0x4000, 0x42)&&k.KFCM(6,t,[{d:3},'d','e','a','d','4'])) { // Line 90 + else if(k.KKM(e, 0x4000, 0x42)&&k.KFCM(6,t,[{t:'d',d:3},'d','e','a','d','4'])) { // Line 90 r=m=1; k.KO(5,t,"ERROR"); } - else if(k.KKM(e, 0x4000, 0x42)&&k.KFCM(4,t,['(','b',')',{d:16}])) { // Line 24 + else if(k.KKM(e, 0x4000, 0x42)&&k.KFCM(4,t,['(','b',')',{t:'d',d:16}])) { // Line 24 r=m=1; k.KO(3,t,"(b)+(a)+(b)"); } @@ -170,23 +170,23 @@ function Keyboard_test_deadkeys() r=m=1; k.KO(3,t,"(b)+(b)"); } - else if(k.KKM(e, 0x4000, 0x42)&&k.KFCM(1, t, [{d:16}])) { // Line 19 + else if(k.KKM(e, 0x4000, 0x42)&&k.KFCM(1, t, [{t:'d',d:16}])) { // Line 19 r=m=1; k.KO(0,t,"(a)(b)"); } - else if(k.KKM(e, 0x4000, 0x42)&&k.KFCM(1, t, [{d:2}])) { // Line 82 + else if(k.KKM(e, 0x4000, 0x42)&&k.KFCM(1, t, [{t:'d',d:2}])) { // Line 82 r=m=1; k.KO(0,t,"dead2"); } - else if(k.KKM(e, 0x4000, 0x42)&&k.KFCM(1, t, [{d:1}])) { // Line 83 + else if(k.KKM(e, 0x4000, 0x42)&&k.KFCM(1, t, [{t:'d',d:1}])) { // Line 83 r=m=1; k.KO(0,t,"dead1"); } - else if(k.KKM(e, 0x4000, 0x42)&&k.KFCM(1, t, [{d:4}])) { // Line 87 + else if(k.KKM(e, 0x4000, 0x42)&&k.KFCM(1, t, [{t:'d',d:4}])) { // Line 87 r=m=1; k.KO(0,t,"dead4"); } - else if(k.KKM(e, 0x4000, 0x42)&&k.KFCM(1, t, [{d:3}])) { // Line 88 + else if(k.KKM(e, 0x4000, 0x42)&&k.KFCM(1, t, [{t:'d',d:3}])) { // Line 88 r=m=1; k.KO(0,t,"dead3"); } @@ -194,7 +194,7 @@ function Keyboard_test_deadkeys() r=m=1; k.KO(0,t,"(b)"); } - else if(k.KKM(e, 0x4000, 0x43)&&k.KFCM(1, t, [{d:16}])) { // Line 27 + else if(k.KKM(e, 0x4000, 0x43)&&k.KFCM(1, t, [{t:'d',d:16}])) { // Line 27 r=m=1; k.KO(0,t,"x"); } @@ -202,11 +202,11 @@ function Keyboard_test_deadkeys() r=m=1; k.KO(0,t,"d"); } - else if(k.KKM(e, 0x4000, 0x45)&&k.KFCM(3,t,['f',{d:16},'d'])) { // Line 35 + else if(k.KKM(e, 0x4000, 0x45)&&k.KFCM(3,t,['f',{t:'d',d:16},'d'])) { // Line 35 r=m=1; k.KO(2,t,"(f)-(a)-(d)-(e)"); } - else if(k.KKM(e, 0x4000, 0x45)&&k.KFCM(2,t,[{d:16},'d'])) { // Line 34 + else if(k.KKM(e, 0x4000, 0x45)&&k.KFCM(2,t,[{t:'d',d:16},'d'])) { // Line 34 r=m=1; k.KO(1,t,"(a)(d)(e)"); } @@ -236,11 +236,11 @@ function Keyboard_test_deadkeys() k.KDO(-1,t,10); k.KO(-1,t,"(p)"); } - else if(k.KKM(e, 0x4000, 0x51)&&k.KFCM(4,t,[{d:10},'(','p',')'])) { // Line 55 + else if(k.KKM(e, 0x4000, 0x51)&&k.KFCM(4,t,[{t:'d',d:10},'(','p',')'])) { // Line 55 r=m=1; k.KO(3,t,"(p)(q)"); } - else if(k.KKM(e, 0x4000, 0x52)&&k.KFCM(7,t,[{d:19},'(','p',')','(','q',')'])) { // Line 56 + else if(k.KKM(e, 0x4000, 0x52)&&k.KFCM(7,t,[{t:'d',d:19},'(','p',')','(','q',')'])) { // Line 56 r=m=1; k.KO(6,t,"(p)(q)(r)"); } @@ -262,16 +262,16 @@ function Keyboard_test_deadkeys() k.KDO(-1,t,23); k.KO(-1,t,"d"); } - else if(k.KFCM(3,t,['a','b',{d:21}])) { // Line 133 + else if(k.KFCM(3,t,['a','b',{t:'d',d:21}])) { // Line 133 m=1; k.KDO(2,t,22); } - else if(k.KFCM(2,t,[{d:2},{d:1}])) { // Line 130 + else if(k.KFCM(2,t,[{t:'d',d:2},{t:'d',d:1}])) { // Line 130 m=1; k.KDO(0,t,1); k.KDO(-1,t,2); } - else if(k.KFCM(2,t,[{d:3},{d:4}])) { // Line 131 + else if(k.KFCM(2,t,[{t:'d',d:3},{t:'d',d:4}])) { // Line 131 m=1; k.KDO(0,t,4); k.KDO(-1,t,3);