speech-rule-engine
Version:
A standalone speech rule engine for XML structures, based on the original engine from ChromeVox.
339 lines (338 loc) • 10.9 kB
JavaScript
;
Object.defineProperty(exports, "__esModule", { value: true });
exports.OutputError = exports.Precondition = exports.Action = exports.Component = exports.ActionType = exports.SpeechRule = void 0;
const engine_js_1 = require("../common/engine.js");
const Grammar = require("./grammar.js");
class SpeechRule {
constructor(name, dynamicCstr, precondition, action) {
this.name = name;
this.dynamicCstr = dynamicCstr;
this.precondition = precondition;
this.action = action;
this.context = null;
}
toString() {
return (this.name +
' | ' +
this.dynamicCstr.toString() +
' | ' +
this.precondition.toString() +
' ==> ' +
this.action.toString());
}
}
exports.SpeechRule = SpeechRule;
var ActionType;
(function (ActionType) {
ActionType["NODE"] = "NODE";
ActionType["MULTI"] = "MULTI";
ActionType["TEXT"] = "TEXT";
ActionType["PERSONALITY"] = "PERSONALITY";
})(ActionType || (exports.ActionType = ActionType = {}));
function actionFromString(str) {
switch (str) {
case '[n]':
return ActionType.NODE;
case '[m]':
return ActionType.MULTI;
case '[t]':
return ActionType.TEXT;
case '[p]':
return ActionType.PERSONALITY;
default:
throw 'Parse error: ' + str;
}
}
function actionToString(speechType) {
switch (speechType) {
case ActionType.NODE:
return '[n]';
case ActionType.MULTI:
return '[m]';
case ActionType.TEXT:
return '[t]';
case ActionType.PERSONALITY:
return '[p]';
default:
throw 'Unknown type error: ' + speechType;
}
}
class Component {
static grammarFromString(grammar) {
return Grammar.Grammar.parseInput(grammar);
}
static fromString(input) {
const output = {
type: actionFromString(input.substring(0, 3))
};
let rest = input.slice(3).trim();
if (!rest) {
throw new OutputError('Missing content.');
}
switch (output.type) {
case ActionType.TEXT:
if (rest[0] === '"') {
const quotedString = splitString(rest, '\\(')[0].trim();
if (quotedString.slice(-1) !== '"') {
throw new OutputError('Invalid string syntax.');
}
output.content = quotedString;
rest = rest.slice(quotedString.length).trim();
if (rest.indexOf('(') === -1) {
rest = '';
}
break;
}
case ActionType.NODE:
case ActionType.MULTI:
{
const bracket = rest.indexOf(' (');
if (bracket === -1) {
output.content = rest.trim();
rest = '';
break;
}
output.content = rest.substring(0, bracket).trim();
rest = rest.slice(bracket).trim();
}
break;
}
if (rest) {
const attributes = Component.attributesFromString(rest);
if (attributes.grammar) {
output.grammar = attributes.grammar;
delete attributes.grammar;
}
if (Object.keys(attributes).length) {
output.attributes = attributes;
}
}
return new Component(output);
}
static attributesFromString(attrs) {
if (attrs[0] !== '(' || attrs.slice(-1) !== ')') {
throw new OutputError('Invalid attribute expression: ' + attrs);
}
const attributes = {};
const attribs = splitString(attrs.slice(1, -1), ',');
for (const attr of attribs) {
const colon = attr.indexOf(':');
if (colon === -1) {
attributes[attr.trim()] = 'true';
}
else {
const key = attr.substring(0, colon).trim();
const value = attr.slice(colon + 1).trim();
attributes[key] =
key === Grammar.ATTRIBUTE
? Component.grammarFromString(value)
: value;
}
}
return attributes;
}
constructor({ type, content, attributes, grammar }) {
this.type = type;
this.content = content;
this.attributes = attributes;
this.grammar = grammar;
}
toString() {
let strs = '';
strs += actionToString(this.type);
strs += this.content ? ' ' + this.content : '';
const attrs = this.attributesToString();
strs += attrs ? ' ' + attrs : '';
return strs;
}
grammarToString() {
return this.getGrammar().join(':');
}
getGrammar() {
if (!this.grammar) {
return [];
}
const attribs = [];
for (const [key, val] of Object.entries(this.grammar)) {
attribs.push(val === true ? key : val === false ? `!${key}` : `${key}=${val}`);
}
return attribs;
}
attributesToString() {
const attribs = this.getAttributes();
const grammar = this.grammarToString();
if (grammar) {
attribs.push('grammar:' + grammar);
}
return attribs.length > 0 ? '(' + attribs.join(', ') + ')' : '';
}
getAttributes() {
if (!this.attributes) {
return [];
}
const attribs = [];
for (const [key, val] of Object.entries(this.attributes)) {
attribs.push(val === 'true' ? key : `${key}:${val}`);
}
return attribs;
}
}
exports.Component = Component;
class Action {
static fromString(input) {
const comps = splitString(input, ';')
.filter(function (x) {
return x.match(/\S/);
})
.map(function (x) {
return x.trim();
});
const newComps = [];
for (let i = 0, m = comps.length; i < m; i++) {
const comp = Component.fromString(comps[i]);
if (comp) {
newComps.push(comp);
}
}
Action.naiveSpan(newComps);
return new Action(newComps);
}
static naiveSpan(comps) {
var _a;
let first = false;
for (let i = 0, comp; (comp = comps[i]); i++) {
if (first &&
(comp.type !== ActionType.TEXT ||
(comp.content[0] !== '"' && !comp.content.match(/^CSF/))))
continue;
if (!first && comp.type === ActionType.PERSONALITY)
continue;
if (!first) {
first = true;
continue;
}
if ((_a = comp.attributes) === null || _a === void 0 ? void 0 : _a.span)
continue;
const next = comps[i + 1];
if (next && next.type !== ActionType.NODE)
continue;
Action.addNaiveSpan(comp, next ? next.content : 'LAST');
}
}
static addNaiveSpan(comp, span) {
if (!comp.attributes) {
comp.attributes = {};
}
comp.attributes['span'] = span;
}
constructor(components) {
this.components = components;
}
toString() {
const comps = this.components.map(function (c) {
return c.toString();
});
return comps.join('; ');
}
}
exports.Action = Action;
class Precondition {
static constraintValue(constr, priorities) {
for (let i = 0, regexp; (regexp = priorities[i]); i++) {
if (constr.match(regexp)) {
return ++i;
}
}
return 0;
}
toString() {
const constrs = this.constraints.join(', ');
return `${this.query}, ${constrs} (${this.priority}, ${this.rank})`;
}
constructor(query, ...cstr) {
this.query = query;
this.constraints = cstr;
const [exists, user] = this.presetPriority();
this.priority = exists ? user : this.calculatePriority();
}
calculatePriority() {
const query = Precondition.constraintValue(this.query, Precondition.queryPriorities);
if (!query) {
return 0;
}
const match = this.query.match(/^self::.+\[(.+)\]/);
let attr = 0;
if ((match === null || match === void 0 ? void 0 : match.length) && match[1]) {
const inner = match[1];
attr = Precondition.constraintValue(inner, Precondition.attributePriorities);
}
return query * 100 + attr * 10;
}
presetPriority() {
if (!this.constraints.length) {
return [false, 0];
}
const last = this.constraints[this.constraints.length - 1].match(/^priority=(.*$)/);
if (!last) {
return [false, 0];
}
this.constraints.pop();
const numb = parseFloat(last[1]);
return [true, isNaN(numb) ? 0 : numb];
}
}
exports.Precondition = Precondition;
Precondition.queryPriorities = [
/^self::\*$/,
/^self::[\w-]+$/,
/^self::\*\[.+\]$/,
/^self::[\w-]+\[.+\]$/
];
Precondition.attributePriorities = [
/^@[\w-]+$/,
/^@[\w-]+!=".+"$/,
/^not\(contains\(@[\w-]+,\s*".+"\)\)$/,
/^contains\(@[\w-]+,".+"\)$/,
/^@[\w-]+=".+"$/
];
class OutputError extends engine_js_1.SREError {
constructor(msg) {
super(msg);
this.name = 'RuleError';
}
}
exports.OutputError = OutputError;
function splitString(str, sep) {
const strList = [];
let prefix = '';
while (str !== '') {
const sepPos = str.search(sep);
if (sepPos === -1) {
if ((str.match(/"/g) || []).length % 2 !== 0) {
throw new OutputError('Invalid string in expression: ' + str);
}
strList.push(prefix + str);
prefix = '';
str = '';
}
else if ((str.substring(0, sepPos).match(/"/g) || []).length % 2 === 0) {
strList.push(prefix + str.substring(0, sepPos));
prefix = '';
str = str.substring(sepPos + 1);
}
else {
const nextQuot = str.substring(sepPos).search('"');
if (nextQuot === -1) {
throw new OutputError('Invalid string in expression: ' + str);
}
else {
prefix = prefix + str.substring(0, sepPos + nextQuot + 1);
str = str.substring(sepPos + nextQuot + 1);
}
}
}
if (prefix) {
strList.push(prefix);
}
return strList;
}