speech-rule-engine
Version:
A standalone speech rule engine for XML structures, based on the original engine from ChromeVox.
274 lines (273 loc) • 9.86 kB
JavaScript
import { Engine } from '../common/engine.js';
import { DynamicCstr } from '../rule_engine/dynamic_cstr.js';
import { Axis, DefaultComparator, DynamicCstrParser, DynamicProperties } from '../rule_engine/dynamic_cstr.js';
import * as MathCompoundStore from '../rule_engine/math_compound_store.js';
import { SpeechRuleEngine } from '../rule_engine/speech_rule_engine.js';
import { SemanticRole, SemanticType } from '../semantic_tree/semantic_meaning.js';
import { fromPreference, toPreference, PREFERENCES } from './clearspeak_preference_string.js';
export class ClearspeakPreferences extends DynamicCstr {
static comparator() {
return new Comparator(Engine.getInstance().dynamicCstr, DynamicProperties.createProp([DynamicCstr.DEFAULT_VALUES[Axis.LOCALE]], [DynamicCstr.DEFAULT_VALUES[Axis.MODALITY]], [DynamicCstr.DEFAULT_VALUES[Axis.DOMAIN]], [DynamicCstr.DEFAULT_VALUES[Axis.STYLE]]));
}
static getLocalePreferences(opt_dynamic) {
const dynamic = opt_dynamic ||
MathCompoundStore.enumerate(SpeechRuleEngine.getInstance().enumerate());
return ClearspeakPreferences.getLocalePreferences_(dynamic);
}
static relevantPreferences(node) {
const roles = SEMANTIC_MAPPING_[node.type];
if (!roles) {
return 'ImpliedTimes';
}
const cons = roles[node.role] || roles[''];
if (!cons) {
return 'ImpliedTimes';
}
if (typeof cons === 'string') {
return cons;
}
else {
return testSpecial(cons, node) || 'ImpliedTimes';
}
}
static getLocalePreferences_(dynamic) {
const result = {};
for (const locale of Object.keys(dynamic)) {
if (!dynamic[locale]['speech'] ||
!dynamic[locale]['speech']['clearspeak']) {
continue;
}
const locPrefs = Object.keys(dynamic[locale]['speech']['clearspeak']);
if (locPrefs.length < 3)
continue;
const prefs = (result[locale] = {});
for (const axis in PREFERENCES.getProperties()) {
const allSty = PREFERENCES.getProperties()[axis];
const values = [axis + '_Auto'];
if (allSty) {
for (const sty of allSty) {
if (locPrefs.indexOf(axis + '_' + sty) !== -1) {
values.push(axis + '_' + sty);
}
}
}
prefs[axis] = values;
}
}
return result;
}
constructor(cstr, preference) {
super(cstr);
this.preference = preference;
}
equal(cstr) {
const top = super.equal(cstr);
if (!top) {
return false;
}
const keys = Object.keys(this.preference);
const preference = cstr.preference;
if (keys.length !== Object.keys(preference).length) {
return false;
}
for (let i = 0, key; (key = keys[i]); i++) {
if (this.preference[key] !== preference[key]) {
return false;
}
}
return true;
}
}
class Comparator extends DefaultComparator {
constructor(cstr, props) {
super(cstr, props);
this.preference =
cstr instanceof ClearspeakPreferences ? cstr.preference : {};
}
match(cstr) {
if (!(cstr instanceof ClearspeakPreferences)) {
return super.match(cstr);
}
if (cstr.getComponents()[Axis.STYLE] === 'default') {
return true;
}
const keys = Object.keys(cstr.preference);
for (let i = 0, key; (key = keys[i]); i++) {
if (this.preference[key] !== cstr.preference[key]) {
return false;
}
}
return true;
}
compare(cstr1, cstr2) {
const top = super.compare(cstr1, cstr2);
if (top !== 0) {
return top;
}
const pref1 = cstr1 instanceof ClearspeakPreferences;
const pref2 = cstr2 instanceof ClearspeakPreferences;
if (!pref1 && pref2) {
return 1;
}
if (pref1 && !pref2) {
return -1;
}
if (!pref1 && !pref2) {
return 0;
}
const length1 = Object.keys(cstr1.preference).length;
const length2 = Object.keys(cstr2.preference).length;
return length1 > length2 ? -1 : length1 < length2 ? 1 : 0;
}
}
class Parser extends DynamicCstrParser {
constructor() {
super([Axis.LOCALE, Axis.MODALITY, Axis.DOMAIN, Axis.STYLE]);
}
parse(str) {
const initial = super.parse(str);
let style = initial.getValue(Axis.STYLE);
const locale = initial.getValue(Axis.LOCALE);
const modality = initial.getValue(Axis.MODALITY);
let pref = {};
if (style !== DynamicCstr.DEFAULT_VALUE) {
pref = this.fromPreference(style);
style = this.toPreference(pref);
}
return new ClearspeakPreferences({
locale: locale,
modality: modality,
domain: 'clearspeak',
style: style
}, pref);
}
fromPreference(pref) {
return fromPreference(pref);
}
toPreference(pref) {
return toPreference(pref);
}
}
const REVERSE_MAPPING = [
['AbsoluteValue', SemanticType.FENCED, SemanticRole.NEUTRAL],
['AbsoluteValue', SemanticType.FENCED, SemanticRole.METRIC],
['Bar', SemanticType.OVERSCORE, ''],
['Caps', SemanticType.IDENTIFIER, SemanticRole.LATINLETTER, 'category:Lu'],
['CombinationPermutation', SemanticType.APPL, SemanticRole.UNKNOWN],
['Currency', SemanticType.IDENTIFIER, SemanticRole.UNIT, 'unit:currency'],
['Currency', SemanticType.INFIXOP, SemanticRole.UNIT, 'unit:currency'],
['Enclosed', SemanticType.ENCLOSE, ''],
['Ellipses', SemanticType.PUNCTUATION, SemanticRole.ELLIPSIS],
['Exponent', SemanticType.SUPERSCRIPT, ''],
['Fraction', SemanticType.FRACTION, ''],
['Functions', SemanticType.APPL, SemanticRole.SIMPLEFUNC],
['ImpliedTimes', SemanticType.OPERATOR, SemanticRole.IMPLICIT],
['Log', SemanticType.APPL, SemanticRole.PREFIXFUNC, 'appl:Logarithm'],
['Log', SemanticType.FUNCTION, SemanticRole.PREFIXFUNC, 'category:Logarithm'],
['Matrix', SemanticType.MATRIX, ''],
['Matrix', SemanticType.VECTOR, ''],
['MultiLineLabel', SemanticType.MULTILINE, SemanticRole.LABEL],
['MultiLinePausesBetweenColumns', SemanticType.MULTILINE, SemanticRole.TABLE],
['MultiLineOverview', SemanticType.MULTILINE, ''],
['MultiLineLabel', SemanticType.TABLE, SemanticRole.LABEL],
['MultiLinePausesBetweenColumns', SemanticType.TABLE, ''],
['MultiLineOverview', SemanticType.TABLE, ''],
['MultiLineLabel', SemanticType.CASES, SemanticRole.LABEL],
['MultiLinePausesBetweenColumns', SemanticType.CASES, ''],
['MultiLineOverview', SemanticType.CASES, ''],
[
'MultsymbolDot',
SemanticType.OPERATOR,
SemanticRole.MULTIPLICATION,
'content:22C5'
],
[
'MultsymbolX',
SemanticType.OPERATOR,
SemanticRole.MULTIPLICATION,
'content:00D7'
],
['Paren', SemanticType.FENCED, SemanticRole.LEFTRIGHT],
['Prime', SemanticType.PUNCTUATION, SemanticRole.PRIME],
['Roots', SemanticType.ROOT, ''],
['Roots', SemanticType.SQRT, ''],
['SetMemberSymbol', SemanticType.OPERATOR, SemanticRole.ELEMENT],
['Sets', SemanticType.FENCED, SemanticRole.SETEXT],
[
'TriangleSymbol',
SemanticType.IDENTIFIER,
SemanticRole.GREEKLETTER,
'content:0394'
],
['Trig', SemanticType.APPL, SemanticRole.PREFIXFUNC, 'appl:Trigonometric'],
[
'Trig',
SemanticType.FUNCTION,
SemanticRole.PREFIXFUNC,
'category:Trigonometric'
],
['VerticalLine', SemanticType.PUNCTUATED, SemanticRole.VBAR],
['VerticalLine', SemanticType.PUNCTUATION, SemanticRole.VBAR]
];
const SEMANTIC_MAPPING_ = (function () {
const result = {};
for (let i = 0, triple; (triple = REVERSE_MAPPING[i]); i++) {
const pref = triple[0];
const special = triple[3];
let role = result[triple[1]];
if (!role) {
role = {};
result[triple[1]] = role;
}
if (!special) {
role[triple[2]] = pref;
continue;
}
let specialize = role[triple[2]];
if (!specialize) {
specialize = {};
role[triple[2]] = specialize;
}
specialize[special] = pref;
}
return result;
})();
function testSpecial(special, node) {
for (const [pred, res] of Object.entries(special)) {
if (executeSpecial(pred, node)) {
return res;
}
}
return '';
}
function executeSpecial(special, node) {
const [pred, arg] = special.split(':');
if (!pred) {
return false;
}
const func = specialPred[pred];
if (!func) {
return false;
}
return func(node, arg);
}
const specialPred = {
category: (node, arg) => {
return MathCompoundStore.lookupCategory(node.textContent) === arg;
},
content: (node, arg) => {
return node.textContent === String.fromCodePoint(parseInt(arg, 16));
},
appl: (node, arg) => {
const func = node.childNodes[0];
return func
? MathCompoundStore.lookupCategory(func.textContent) === arg
: false;
},
unit: (node, arg) => {
return MathCompoundStore.lookupCategory(node.textContent + ':unit') === arg;
}
};
Engine.getInstance().comparators['clearspeak'] =
ClearspeakPreferences.comparator;
Engine.getInstance().parsers['clearspeak'] = new Parser();