Skip to content

Commit

Permalink
Merge pull request #9087 from keymanapp/chore/developer/kmc-kmn-kmwco…
Browse files Browse the repository at this point in the history
…mpiler-handle-layout-file-errors

chore(developer): handle layout file errors in kmw-compiler
  • Loading branch information
mcdurdin authored Jun 26, 2023
2 parents fbfd8c5 + 7ccef2e commit 5182b49
Show file tree
Hide file tree
Showing 3 changed files with 70 additions and 38 deletions.
2 changes: 1 addition & 1 deletion developer/src/kmc-kmn/src/kmw-compiler/constants.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
export enum TRequiredKey {
K_LOPT, K_BKSP, K_ENTER
K_LOPT='K_LOPT', K_BKSP='K_BKSP', K_ENTER='K_ENTER'
}; // I4447

export const
Expand Down
23 changes: 22 additions & 1 deletion developer/src/kmc-kmn/src/kmw-compiler/messages.ts
Original file line number Diff line number Diff line change
Expand Up @@ -43,10 +43,31 @@ export class KmwCompilerMessages extends KmnCompilerMessages {
`File ${o.filename} could not be loaded: ${(o.e??'').toString()}`);
static Warn_EmbedJsFileMissing = (o:{filename: string, e:any}) => m(this.WARN_EmbedJsFileMissing,
`File ${o.filename} could not be loaded: ${(o.e??'').toString()}`);

static Warn_TouchLayoutMissingLayer = (o:{keyId:string, platformName:string, layerId:string, nextLayer:string}) => m(this.WARN_TouchLayoutMissingLayer,
`Key "${o.keyId}" on platform "${o.platformName}", layer "${o.layerId}", references a missing layer "${o.nextLayer}"`);
static Warn_TouchLayoutUnidentifiedKey = (o:{layerId:string}) => m(this.WARN_TouchLayoutUnidentifiedKey,
`A key on layer "${o.layerId}" has no identifier.`);
static Error_TouchLayoutInvalidIdentifier = (o:{keyId:string, platformName: string, layerId:string}) => m(this.ERROR_TouchLayoutInvalidIdentifier,
`Key "${o.keyId}" on "${o.platformName}", layer "${o.layerId}" has an invalid identifier.`);
static Warn_TouchLayoutCustomKeyNotDefined = (o:{keyId:string, platformName:string, layerId:string}) => m(this.WARN_TouchLayoutCustomKeyNotDefined,
`Key "${o.keyId}" on platform "${o.platformName}", layer "${o.layerId}", is a custom key but has no corresponding rule in the source.`);
static Warn_TouchLayoutSpecialLabelOnNormalKey = (o:{keyId:string, platformName:string, layerId:string, label:string}) => m(this.WARN_TouchLayoutSpecialLabelOnNormalKey,
`Key "${o.keyId}" on platform "${o.platformName}", layer "${o.layerId}" does not have the key type "Special" or "Special (active)" but has the label "${o.label}". This feature is only supported in Keyman 14 or later`);
static Error_InvalidKeyCode = (o:{keyId: string}) => m(this.ERROR_InvalidKeyCode,
`Invalid key identifier "${o.keyId}"`);
static Error_InvalidTouchLayoutFile = (o:{msg: string}) => m(this.ERROR_InvalidTouchLayoutFile,
`Invalid touch layout file: ${o.msg}`);
static Warn_TouchLayoutFontShouldBeSameForAllPlatforms = () => m(this.WARN_TouchLayoutFontShouldBeSameForAllPlatforms,
`The touch layout font should be the same for all platforms.`);
static Warn_TouchLayoutMissingRequiredKeys = (o:{layerId:string, platformName:string, missingKeys:string}) => m(this.WARN_TouchLayoutMissingRequiredKeys,
`Layer "${o.layerId}" on platform "${o.platformName}" is missing the required key(s) '${o.missingKeys}.`);
// Following messages are kmw-compiler only, so use KmwCompiler error namespace

static Error_NotAnyRequiresVersion14 = () => m(this.ERROR_NotAnyRequiresVersion14,
`Statement notany in context() match requires version 14.0+ of KeymanWeb`);
static ERROR_NotAnyRequiresVersion14 = SevError | 0x0001;

static Error_TouchLayoutIdentifierRequires15 = (o:{keyId:string, platformName:string, layerId:string}) => m(this.ERROR_TouchLayoutIdentifierRequires15,
`Key "${o.keyId}" on "${o.platformName}", layer "${o.layerId}" has a multi-part identifier which requires version 15.0 or newer.`);
static ERROR_TouchLayoutIdentifierRequires15 = SevError | 0x0002;
};
83 changes: 47 additions & 36 deletions developer/src/kmc-kmn/src/kmw-compiler/validate-layout-file.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@ import { callbacks, IsKeyboardVersion14OrLater, IsKeyboardVersion15OrLater } fro
import { JavaScript_Key } from "./javascript-strings.js";
import { TRequiredKey, CRequiredKeys, CSpecialText10, CSpecialText14, CSpecialText14ZWNJ, CSpecialText14Map } from "./constants.js";
import { VKeyNames } from "./keymanweb-key-codes.js";
import { KmwCompilerMessages } from "./messages.js";


interface VLFOutput {
Expand Down Expand Up @@ -49,9 +50,17 @@ function KeyIdType(FId: string): TKeyIdType { // I4142
}


function CheckKey(FPlatform: TouchLayout.TouchLayoutPlatform,
FId: string, FText: string, FNextLayer: string, FKeyType: TouchLayout.TouchLayoutKeySp,
FRequiredKeys: TRequiredKey[], FDictionary: string[]) { // I4119
function CheckKey(
platformId: string,
FPlatform: TouchLayout.TouchLayoutPlatform,
layer: TouchLayout.TouchLayoutLayer,
FId: string,
FText: string,
FNextLayer: string,
FKeyType: TouchLayout.TouchLayoutKeySp,
FRequiredKeys: TRequiredKey[],
FDictionary: string[]
) { // I4119

//
// Check that each touch layer has K_LOPT, [K_ROPT,] K_BKSP, K_ENTER
Expand All @@ -70,7 +79,12 @@ function CheckKey(FPlatform: TouchLayout.TouchLayoutPlatform,

if(typeof FNextLayer == 'string' && FNextLayer.length > 0) {
if(FPlatform.layer.find(l => l.id.toLowerCase() == FNextLayer.toLowerCase()) == undefined) {
// TODO: callbacks.reportMessage() ReportError(0, CWARN_TouchLayoutMissingLayer, 'Key "'+FId+'" on platform "'+FPlatform.Name+'", layer "'+FLayer.Id+'", platform "'+FPlatform.Name+'", references a missing layer "'+FNextLayer+'".');
callbacks.reportMessage(KmwCompilerMessages.Warn_TouchLayoutMissingLayer({
keyId: FId,
layerId: layer.id,
nextLayer: FNextLayer,
platformName: platformId
}));
}
}

Expand All @@ -80,18 +94,18 @@ function CheckKey(FPlatform: TouchLayout.TouchLayoutPlatform,

if(FId.trim() == '') {
if(!(FKeyType in [TouchLayout.TouchLayoutKeySp.blank, TouchLayout.TouchLayoutKeySp.spacer]) && FNextLayer == '') {
// TODO: ReportError(0, CWARN_TouchLayoutUnidentifiedKey, 'A key on layer "'+FLayer.Id+'" has no identifier.');
callbacks.reportMessage(KmwCompilerMessages.Warn_TouchLayoutUnidentifiedKey({layerId: layer.id}));
}
return;
}

let FValid = KeyIdType(FId);

if(FValid == TKeyIdType.Key_Invalid) {
// TODO: ReportError(0, CERR_TouchLayoutInvalidIdentifier, 'Key "'+FId+'" on "'+FPlatform.Name+'", layer "'+FLayer.Id+'" has an invalid identifier.');
callbacks.reportMessage(KmwCompilerMessages.Error_TouchLayoutInvalidIdentifier({keyId: FId, platformName: platformId, layerId: layer.id}));
}
else if (FValid == TKeyIdType.Key_Unicode_Multi && !IsKeyboardVersion15OrLater()) {
// TODO: ReportError(0, CERR_TouchLayoutInvalidIdentifier, 'Key "'+FId+'" on "'+FPlatform.Name+'", layer "'+FLayer.Id+'" has a multi-part identifier which requires version 15.0 or newer.');
callbacks.reportMessage(KmwCompilerMessages.Error_TouchLayoutIdentifierRequires15({keyId: FId, platformName: platformId, layerId: layer.id}));
}

//
Expand All @@ -101,7 +115,7 @@ function CheckKey(FPlatform: TouchLayout.TouchLayoutPlatform,
if (FValid == TKeyIdType.Key_Touch && FNextLayer == '' && FKeyType in [TouchLayout.TouchLayoutKeySp.normal, TouchLayout.TouchLayoutKeySp.deadkey]) {
// Search for the key in the key dictionary - ignore K_LOPT, K_ROPT...
if(FDictionary.indexOf(FId) < 0) {
// TODO: ReportError(0, CWARN_TouchLayoutCustomKeyNotDefined, 'Key "'+FId+'" on layer "'+FLayer.Id+'", platform "'+FPlatform.Name+'", is a custom key but has no corresponding rule in the source.');
callbacks.reportMessage(KmwCompilerMessages.Warn_TouchLayoutCustomKeyNotDefined({keyId: FId, platformName: platformId, layerId: layer.id}));
}
}

Expand All @@ -116,10 +130,12 @@ function CheckKey(FPlatform: TouchLayout.TouchLayoutPlatform,
!CSpecialText14ZWNJ.includes(FText) &&
!IsKeyboardVersion14OrLater() &&
!(FKeyType in [TouchLayout.TouchLayoutKeySp.special, TouchLayout.TouchLayoutKeySp.specialActive])) {
// TODO: ReportError(0, CWARN_TouchLayoutSpecialLabelOnNormalKey,
// Format('Key "%s" on layout "%s", platform "%s" does not have the key type "Special" or "Special (active)" but has the label "%s". This feature is only supported in Keyman 14 or later', [
// FId, FLayer.Id, FPlatform.Name, FText
// ]));
callbacks.reportMessage(KmwCompilerMessages.Warn_TouchLayoutSpecialLabelOnNormalKey({
keyId: FId,
platformName: platformId,
layerId: layer.id,
label: FText
}));
}
}
}
Expand All @@ -138,7 +154,7 @@ function CheckDictionaryKeyValidity(fk: KMX.KEYBOARD, FDictionary: string[]) {
if(fgp.fUsingKeys) {
for(let fkp of fgp.keys) {
if(JavaScript_Key(fkp, fk.isMnemonic) == i+256) {
// TODO: ReportError(fkp.Line, CERR_InvalidKeyCode, 'Invalid key identifier "'+FDictionary[i]+'"');
callbacks.reportMessage(KmwCompilerMessages.Error_InvalidKeyCode({keyId: FDictionary[i]}));
}
}
}
Expand All @@ -164,28 +180,19 @@ function TransformSpecialKeys14(FDebug: boolean, sLayoutFile: string): string {
}

export function ValidateLayoutFile(fk: KMX.KEYBOARD, FDebug: boolean, sLayoutFile: string, sVKDictionary: string, displayMap: Osk.PuaMap): VLFOutput { // I4060 // I4139

/*
var
FPlatform: TTouchLayoutPlatform;
FLayer: TTouchLayoutLayer;
FRow: TTouchLayoutRow;
FKey: TTouchLayoutKey;
FSubKey: TTouchLayoutSubKey;
FRequiredKeys: set of TRequiredKey;
FDictionary: TStringList;
FDirection: TTouchLayoutFlickDirection;
*/

let FDictionary: string[] = sVKDictionary.split(/\s+/);

CheckDictionaryKeyValidity(fk, FDictionary); // I4142

let reader = new TouchLayoutFileReader();
let data = reader.read(callbacks.loadFile(sLayoutFile));
if(!data) {
// TODO: ReportError(0, CERR_InvalidTouchLayoutFile, sMsg);
let data: TouchLayout.TouchLayoutFile;
try {
data = reader.read(callbacks.loadFile(sLayoutFile));
if(!data) {
throw new Error('Unknown error reading touch layout file');
}
} catch(e) {
callbacks.reportMessage(KmwCompilerMessages.Error_InvalidTouchLayoutFile({msg: (e??'Unspecified error').toString()}));
return {output:null, result: false};
}

Expand All @@ -200,7 +207,7 @@ var
FTouchLayoutFont = platform.font;
}
else if(platform.font.toLowerCase() != FTouchLayoutFont) {
// TODO: ReportError(0, CWARN_TouchLayoutFontShouldBeSameForAllPlatforms, 'The touch layout font should be the same for all platforms.');
callbacks.reportMessage(KmwCompilerMessages.Warn_TouchLayoutFontShouldBeSameForAllPlatforms());
// TODO: why support multiple font values if it has to be the same across all platforms?!
}

Expand All @@ -209,30 +216,34 @@ var
let FRequiredKeys: TRequiredKey[] = [];
for(let row of layer.row) {
for(let key of row.key) {
CheckKey(platform, key.id, key.text, key.nextlayer, key.sp, FRequiredKeys, FDictionary); // I4119
CheckKey(pid, platform, layer, key.id, key.text, key.nextlayer, key.sp, FRequiredKeys, FDictionary); // I4119
if(key.sk) {
for(let subkey of key.sk) {
CheckKey(platform, subkey.id, subkey.text, subkey.nextlayer, subkey.sp, FRequiredKeys, FDictionary);
CheckKey(pid, platform, layer, subkey.id, subkey.text, subkey.nextlayer, subkey.sp, FRequiredKeys, FDictionary);
}
}
let direction: keyof TouchLayout.TouchLayoutFlick;
if(key.flick) {
for(direction in key.flick) {
CheckKey(platform, key.flick[direction].id, key.flick[direction].text,
CheckKey(pid, platform, layer, key.flick[direction].id, key.flick[direction].text,
key.flick[direction].nextlayer, key.flick[direction].sp, FRequiredKeys, FDictionary);
}
}

if(key.multitap) {
for(let subkey of key.multitap) {
CheckKey(platform, subkey.id, subkey.text, subkey.nextlayer, subkey.sp, FRequiredKeys, FDictionary);
CheckKey(pid, platform, layer, subkey.id, subkey.text, subkey.nextlayer, subkey.sp, FRequiredKeys, FDictionary);
}
}
}
}

if(FRequiredKeys.length != CRequiredKeys.length) {
// TODO: ReportError(0, CWARN_TouchLayoutMissingRequiredKeys, 'Layer "'+FLayer.Id+'" on platform "'+FPlatform.Name+'" is missing the required key(s) '+RequiredKeysToString(CRequiredKeys-FRequiredKeys)+'.');
callbacks.reportMessage(KmwCompilerMessages.Warn_TouchLayoutMissingRequiredKeys({
layerId: layer.id,
platformName: pid,
missingKeys: CRequiredKeys.filter(x => !FRequiredKeys.includes(x)).join(', ')
}));
}
}
}
Expand Down

0 comments on commit 5182b49

Please sign in to comment.