Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat(developer) marker steps 🙀 #9364

Merged
merged 10 commits into from
Jul 31, 2023
3 changes: 3 additions & 0 deletions common/web/types/src/ldml-keyboard/pattern-parser.ts
Original file line number Diff line number Diff line change
Expand Up @@ -51,6 +51,9 @@ export class MarkerParser {
* @returns `[]` or an array of all markers referenced
*/
public static allReferences(str: string): string[] {
if (!str) {
return [];
}
return matchArray(str, this.REFERENCE);
}
}
Expand Down
1 change: 0 additions & 1 deletion core/src/ldml/C9134_ldml_markers.md
Original file line number Diff line number Diff line change
Expand Up @@ -27,7 +27,6 @@ Markers can appear in both 'emitting' and 'matching-only' areas:
#### Match only

- `transform from=` to match markers
- `transform after=` to match markers
Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

where did this come from?

- `display to=` for matching keys which contain markers

## Theory / Encoding
Expand Down
8 changes: 7 additions & 1 deletion developer/src/kmc-ldml/src/compiler/disp.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
import { constants } from "@keymanapp/ldml-keyboard-constants";
import { KMXPlus } from '@keymanapp/common-types';
import { KMXPlus, LDMLKeyboard, MarkerParser } from '@keymanapp/common-types';

import { CompilerMessages } from "./messages.js";
import { SectionCompiler } from "./section-compiler.js";
Expand All @@ -9,6 +9,12 @@ import Disp = KMXPlus.Disp;
import DispItem = KMXPlus.DispItem;

export class DispCompiler extends SectionCompiler {
static validateMarkers(keyboard: LDMLKeyboard.LKKeyboard, emitMarkers: Set<string>, matchMarkers: Set<string>): boolean {
keyboard.displays?.display?.forEach(({ to }) => {
MarkerParser.allReferences(to).forEach(marker => matchMarkers.add(marker));
});
return true;
}

public get id() {
return constants.section.disp;
Expand Down
8 changes: 7 additions & 1 deletion developer/src/kmc-ldml/src/compiler/keys.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
import { constants } from '@keymanapp/ldml-keyboard-constants';
import { LDMLKeyboard, KMXPlus, Constants } from '@keymanapp/common-types';
import { LDMLKeyboard, KMXPlus, Constants, MarkerParser } from '@keymanapp/common-types';
import { CompilerMessages } from './messages.js';
import { SectionCompiler } from "./section-compiler.js";

Expand All @@ -10,6 +10,12 @@ import KeysFlicks = KMXPlus.KeysFlicks;
import { allUsedKeyIdsInLayers, calculateUniqueKeys, translateLayerAttrToModifier, validModifier } from '../util/util.js';

export class KeysCompiler extends SectionCompiler {
static validateMarkers(keyboard: LDMLKeyboard.LKKeyboard, emitMarkers: Set<string>, matchMarkers: Set<string>): boolean {
keyboard.keys?.key?.forEach(({ to }) => {
MarkerParser.allReferences(to).forEach(marker => emitMarkers.add(marker));
});
return true;
}

public get id() {
return constants.section.keys;
Expand Down
4 changes: 4 additions & 0 deletions developer/src/kmc-ldml/src/compiler/messages.ts
Original file line number Diff line number Diff line change
Expand Up @@ -130,5 +130,9 @@ export class CompilerMessages {
static Error_CantReferenceSetFromUnicodeSet = (o:{id: string}) =>
m(this.ERROR_CantReferenceSetFromUnicodeSet, `Illegal use of set variable from within UnicodeSet: \$[${o.id}]`);
static ERROR_CantReferenceSetFromUnicodeSet = SevError | 0x0020;

static Error_MissingMarkers = (o: { ids: string[] }) =>
m(this.ERROR_MissingMarkers, `Markers used for matching but not defined: ${o.ids?.join(',')}`);
static ERROR_MissingMarkers = SevError | 0x0021;
}

15 changes: 13 additions & 2 deletions developer/src/kmc-ldml/src/compiler/tran.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
import { constants, SectionIdent } from "@keymanapp/ldml-keyboard-constants";
import { KMXPlus, LDMLKeyboard, CompilerCallbacks, VariableParser } from '@keymanapp/common-types';
import { KMXPlus, LDMLKeyboard, CompilerCallbacks, VariableParser, MarkerParser } from '@keymanapp/common-types';
import { SectionCompiler } from "./section-compiler.js";

import Bksp = KMXPlus.Bksp;
Expand All @@ -18,7 +18,18 @@ import { CompilerMessages } from "./messages.js";

type TransformCompilerType = 'simple' | 'backspace';

class TransformCompiler<T extends TransformCompilerType, TranBase extends Tran> extends SectionCompiler {
export class TransformCompiler<T extends TransformCompilerType, TranBase extends Tran> extends SectionCompiler {

static validateMarkers(keyboard: LDMLKeyboard.LKKeyboard, emitMarkers: Set<string>, matchMarkers: Set<string>): boolean {
keyboard?.transforms?.forEach(transforms =>
transforms.transformGroup.forEach(transformGroup => {
transformGroup.transform?.forEach(({ to, from }) => {
MarkerParser.allReferences(from).forEach(marker => matchMarkers.add(marker));
MarkerParser.allReferences(to).forEach(marker => emitMarkers.add(marker));
});
}));
return true;
}

protected type: T;

Expand Down
56 changes: 55 additions & 1 deletion developer/src/kmc-ldml/src/compiler/vars.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
import { SectionIdent, constants } from "@keymanapp/ldml-keyboard-constants";
import { KMXPlus, LDMLKeyboard, CompilerCallbacks } from '@keymanapp/common-types';
import { KMXPlus, LDMLKeyboard, CompilerCallbacks, MarkerParser } from '@keymanapp/common-types';
import { VariableParser } from '@keymanapp/common-types';
import { SectionCompiler } from "./section-compiler.js";
import Vars = KMXPlus.Vars;
Expand All @@ -9,6 +9,9 @@ import UnicodeSetItem = KMXPlus.UnicodeSetItem;
import DependencySections = KMXPlus.DependencySections;
import LDMLKeyboardXMLSourceFile = LDMLKeyboard.LDMLKeyboardXMLSourceFile;
import { CompilerMessages } from "./messages.js";
import { KeysCompiler } from "./keys.js";
import { TransformCompiler } from "./tran.js";
import { DispCompiler } from "./disp.js";
export class VarsCompiler extends SectionCompiler {
public get id() {
return constants.section.vars;
Expand Down Expand Up @@ -130,9 +133,60 @@ export class VarsCompiler extends SectionCompiler {
}));
valid = false;
}

valid = this.validateMarkers() && valid; // accumulate validity

return valid;
}

private collectMarkers(emitMarkers : Set<string>, matchMarkers : Set<string>) : boolean {
let valid = true;

// call our friends to validate
valid = this.validateVarsMarkers(this.keyboard, emitMarkers, matchMarkers) && valid; // accumulate validity
valid = KeysCompiler.validateMarkers(this.keyboard, emitMarkers, matchMarkers) && valid; // accumulate validity
valid = TransformCompiler.validateMarkers(this.keyboard, emitMarkers, matchMarkers) && valid; // accumulate validity
valid = DispCompiler.validateMarkers(this.keyboard, emitMarkers, matchMarkers) && valid; // accumulate validity
srl295 marked this conversation as resolved.
Show resolved Hide resolved

return valid;
}

private validateMarkers(): boolean {
/** only the markers used in emitters */
const emitMarkers : Set<string> = new Set<string>();
/** only the markers used in matchers */
const matchMarkers : Set<string> = new Set<string>();


let valid = this.collectMarkers(emitMarkers, matchMarkers);

// see if there are any matched-but-not-emitted
const matchedNotEmitted : string[] = [];
for (const m of matchMarkers.values()) {
if (m === '.') continue; // match-all marker
if (!emitMarkers.has(m)) {
matchedNotEmitted.push(m);
}
}

// report once
if (matchedNotEmitted.length) {
matchedNotEmitted.sort();
this.callbacks.reportMessage(CompilerMessages.Error_MissingMarkers({ ids: matchedNotEmitted }));
valid = false;
}
return valid;
}

validateVarsMarkers(keyboard: LDMLKeyboard.LKKeyboard, emitMarkers: Set<string>, matchMarkers: Set<string>) : boolean {
keyboard?.variables?.string?.forEach(({value}) =>
MarkerParser.allReferences(value).forEach(marker => {
emitMarkers.add(marker);
matchMarkers.add(marker);
}));
return true;
}

public compile(sections: DependencySections): Vars {
const result = new Vars();

Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,43 @@
<?xml version="1.0" encoding="UTF-8"?>
<!--

This will fail because the two markers given don't exist anywhere.

-->
<!DOCTYPE keyboard SYSTEM "../../../../../../../resources/standards-data/ldml-keyboards/techpreview/dtd/ldmlKeyboard.dtd">
<keyboard locale="mt" conformsTo="techpreview">
<names>
<name value="vars-markers-badref-0" />
</names>

<displays>
<!-- Not ok, nobody emits this, so this display doesn't make sense -->
<display to="\m{doesnt_exist_1}" display="¬" />
<!-- OK - it si in the strings list. Could warn that it matches no keys -->
srl295 marked this conversation as resolved.
Show resolved Hide resolved
<display to="\m{maybe_emitted}" display="‽" />
</displays>

<variables>
<!-- This may be emitted, so at present it does not cause an error. -->
<!-- A linter could, however, hint on an unused variable. -->
<!-- Removing this variable would cause \m{maybe-emitted} to error in the transform group -->
<string id="unused_variable" value="\m{maybe_emitted}" />
</variables>

<transforms type="simple">
<transformGroup>
<!-- not OK, nobody emits this. -->
<transform from="\m{doesnt_exist_3}" to="3" />
</transformGroup>
</transforms>
<transforms type="backspace">
<transformGroup>
<!-- not OK, nobody emits this. -->
<transform from="\m{doesnt_exist_2}" />
<!-- OK - it's in the variable list -->
<transform from="\m{maybe_emitted}" />
<!-- OK - matches all markers -->
<transform from="\m{.}" />
</transformGroup>
</transforms>
</keyboard>
Original file line number Diff line number Diff line change
@@ -0,0 +1,44 @@
<?xml version="1.0" encoding="UTF-8"?>
<!--

This keyboard won't make a lot of sense, it's mostly
a test of validating markers.

When we get further on in implementation, we'll shape this up
into something testable for implementation.

-->
<!DOCTYPE keyboard SYSTEM "../../../../../../../resources/standards-data/ldml-keyboards/techpreview/dtd/ldmlKeyboard.dtd">
<keyboard locale="mt" conformsTo="techpreview">
<names>
<name value="vars-markers-maximal" />
</names>

<displays>
<display to="\{m}" display="Ⓜ️" />
</displays>

<keys>
<key id="m" to="\{m}" />
</keys>

<!-- from spec -->
<variables>
<string id="x" value="\{x}" />
</variables>

<transforms type="simple">
<transformGroup>
<transform from="m" to="\{m}"/>
<transform from="x" to="\{x}"/>
</transformGroup>
<transformGroup>
<transform from="e\{x}" to="é" />
</transformGroup>
</transforms>
<transforms type="backspace">
<transformGroup>
<transform from="A\{.}B" />
</transformGroup>
</transforms>
</keyboard>
21 changes: 21 additions & 0 deletions developer/src/kmc-ldml/test/test-vars.ts
Original file line number Diff line number Diff line change
Expand Up @@ -183,4 +183,25 @@ describe('vars', function () {
],
},
]);
describe('markers', function () {
this.slow(500); // 0.5 sec -- json schema validation takes a while

testCompilationCases(VarsCompiler, [
{
subpath: 'sections/vars/markers-maximal.xml',
},
{
subpath: 'sections/vars/fail-markers-badref-0.xml',
errors: [
CompilerMessages.Error_MissingMarkers({
ids: [
'doesnt_exist_1',
'doesnt_exist_2',
'doesnt_exist_3',
]
}),
],
},
]);
});
});