base_rule_store.js 10 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316
  1. import { AuditoryDescription } from '../audio/auditory_description.js';
  2. import { Axis, DynamicCstr, DynamicCstrParser } from './dynamic_cstr.js';
  3. import { Action, Precondition, SpeechRule } from './speech_rule.js';
  4. import { SpeechRuleContext } from './speech_rule_context.js';
  5. export class BaseRuleStore {
  6. static compareStaticConstraints_(cstr1, cstr2) {
  7. if (cstr1.length !== cstr2.length) {
  8. return false;
  9. }
  10. for (let i = 0, cstr; (cstr = cstr1[i]); i++) {
  11. if (cstr2.indexOf(cstr) === -1) {
  12. return false;
  13. }
  14. }
  15. return true;
  16. }
  17. static comparePreconditions_(rule1, rule2) {
  18. const prec1 = rule1.precondition;
  19. const prec2 = rule2.precondition;
  20. if (prec1.query !== prec2.query) {
  21. return false;
  22. }
  23. return BaseRuleStore.compareStaticConstraints_(prec1.constraints, prec2.constraints);
  24. }
  25. constructor() {
  26. this.context = new SpeechRuleContext();
  27. this.parseOrder = DynamicCstr.DEFAULT_ORDER;
  28. this.parser = new DynamicCstrParser(this.parseOrder);
  29. this.locale = DynamicCstr.DEFAULT_VALUES[Axis.LOCALE];
  30. this.modality = DynamicCstr.DEFAULT_VALUES[Axis.MODALITY];
  31. this.domain = '';
  32. this.initialized = false;
  33. this.inherits = null;
  34. this.kind = 'standard';
  35. this.customTranscriptions = {};
  36. this.preconditions = new Map();
  37. this.speechRules_ = [];
  38. this.rank = 0;
  39. this.parseMethods = {
  40. Rule: this.defineRule,
  41. Generator: this.generateRules,
  42. Action: this.defineAction,
  43. Precondition: this.definePrecondition,
  44. Ignore: this.ignoreRules
  45. };
  46. }
  47. defineRule(name, dynamic, action, prec, ...args) {
  48. const postc = this.parseAction(action);
  49. const fullPrec = this.parsePrecondition(prec, args);
  50. const dynamicCstr = this.parseCstr(dynamic);
  51. if (!(postc && fullPrec && dynamicCstr)) {
  52. console.error(`Rule Error: ${prec}, (${dynamic}): ${action}`);
  53. return null;
  54. }
  55. const rule = new SpeechRule(name, dynamicCstr, fullPrec, postc);
  56. rule.precondition.rank = this.rank++;
  57. this.addRule(rule);
  58. return rule;
  59. }
  60. addRule(rule) {
  61. rule.context = this.context;
  62. this.speechRules_.unshift(rule);
  63. }
  64. deleteRule(rule) {
  65. const index = this.speechRules_.indexOf(rule);
  66. if (index !== -1) {
  67. this.speechRules_.splice(index, 1);
  68. }
  69. }
  70. findRule(pred) {
  71. for (let i = 0, rule; (rule = this.speechRules_[i]); i++) {
  72. if (pred(rule)) {
  73. return rule;
  74. }
  75. }
  76. return null;
  77. }
  78. findAllRules(pred) {
  79. return this.speechRules_.filter(pred);
  80. }
  81. evaluateDefault(node) {
  82. const rest = node.textContent.slice(0);
  83. if (rest.match(/^\s+$/)) {
  84. return this.evaluateWhitespace(rest);
  85. }
  86. return this.evaluateString(rest);
  87. }
  88. evaluateWhitespace(_str) {
  89. return [];
  90. }
  91. evaluateCustom(str) {
  92. const trans = this.customTranscriptions[str];
  93. return trans !== undefined
  94. ? AuditoryDescription.create({ text: trans }, { adjust: true, translate: false })
  95. : null;
  96. }
  97. evaluateCharacter(str) {
  98. return (this.evaluateCustom(str) ||
  99. AuditoryDescription.create({ text: str }, { adjust: true, translate: true }));
  100. }
  101. removeDuplicates(rule) {
  102. for (let i = this.speechRules_.length - 1, oldRule; (oldRule = this.speechRules_[i]); i--) {
  103. if (oldRule !== rule &&
  104. rule.dynamicCstr.equal(oldRule.dynamicCstr) &&
  105. BaseRuleStore.comparePreconditions_(oldRule, rule)) {
  106. this.speechRules_.splice(i, 1);
  107. }
  108. }
  109. }
  110. getSpeechRules() {
  111. return this.speechRules_;
  112. }
  113. setSpeechRules(rules) {
  114. this.speechRules_ = rules;
  115. }
  116. getPreconditions() {
  117. return this.preconditions;
  118. }
  119. parseCstr(cstr) {
  120. try {
  121. return this.parser.parse(this.locale +
  122. '.' +
  123. this.modality +
  124. (this.domain ? '.' + this.domain : '') +
  125. '.' +
  126. cstr);
  127. }
  128. catch (err) {
  129. if (err.name === 'RuleError') {
  130. console.error('Rule Error ', `Illegal Dynamic Constraint: ${cstr}.`, err.message);
  131. return null;
  132. }
  133. else {
  134. throw err;
  135. }
  136. }
  137. }
  138. parsePrecondition(query, rest) {
  139. try {
  140. const queryCstr = this.parsePrecondition_(query);
  141. query = queryCstr[0];
  142. let restCstr = queryCstr.slice(1);
  143. for (const cstr of rest) {
  144. restCstr = restCstr.concat(this.parsePrecondition_(cstr));
  145. }
  146. return new Precondition(query, ...restCstr);
  147. }
  148. catch (err) {
  149. if (err.name === 'RuleError') {
  150. console.error('Rule Error ', `Illegal preconditions: ${query}, ${rest}.`, err.message);
  151. return null;
  152. }
  153. else {
  154. throw err;
  155. }
  156. }
  157. }
  158. parseAction(action) {
  159. try {
  160. return Action.fromString(action);
  161. }
  162. catch (err) {
  163. if (err.name === 'RuleError') {
  164. console.error('Rule Error ', `Illegal action: ${action}.`, err.message);
  165. return null;
  166. }
  167. else {
  168. throw err;
  169. }
  170. }
  171. }
  172. parse(ruleSet) {
  173. this.modality = ruleSet.modality || this.modality;
  174. this.locale = ruleSet.locale || this.locale;
  175. this.domain = ruleSet.domain || this.domain;
  176. this.context.parse(ruleSet.functions || []);
  177. if (ruleSet.kind !== 'actions') {
  178. this.kind = ruleSet.kind || this.kind;
  179. this.inheritRules();
  180. }
  181. this.parseRules(ruleSet.rules || []);
  182. }
  183. parseRules(rules) {
  184. for (let i = 0, rule; (rule = rules[i]); i++) {
  185. const type = rule[0];
  186. const method = this.parseMethods[type];
  187. if (type && method) {
  188. method.apply(this, rule.slice(1));
  189. }
  190. }
  191. }
  192. generateRules(generator) {
  193. const method = this.context.customGenerators.lookup(generator);
  194. if (method) {
  195. method(this);
  196. }
  197. }
  198. defineAction(name, action) {
  199. let postc;
  200. try {
  201. postc = Action.fromString(action);
  202. }
  203. catch (err) {
  204. if (err.name === 'RuleError') {
  205. console.error('Action Error ', action, err.message);
  206. return;
  207. }
  208. else {
  209. throw err;
  210. }
  211. }
  212. const prec = this.getFullPreconditions(name);
  213. if (!prec) {
  214. console.error(`Action Error: No precondition for action ${name}`);
  215. return;
  216. }
  217. this.ignoreRules(name);
  218. const regexp = new RegExp('^\\w+\\.\\w+\\.' + (this.domain ? '\\w+\\.' : ''));
  219. prec.conditions.forEach(([dynamic, prec]) => {
  220. const newDynamic = this.parseCstr(dynamic.toString().replace(regexp, ''));
  221. this.addRule(new SpeechRule(name, newDynamic, prec, postc));
  222. });
  223. }
  224. getFullPreconditions(name) {
  225. const prec = this.preconditions.get(name);
  226. if (prec || !this.inherits) {
  227. return prec;
  228. }
  229. return this.inherits.getFullPreconditions(name);
  230. }
  231. definePrecondition(name, dynamic, prec, ...args) {
  232. const fullPrec = this.parsePrecondition(prec, args);
  233. const dynamicCstr = this.parseCstr(dynamic);
  234. if (!(fullPrec && dynamicCstr)) {
  235. console.error(`Precondition Error: ${prec}, (${dynamic})`);
  236. return;
  237. }
  238. fullPrec.rank = this.rank++;
  239. this.preconditions.set(name, new Condition(dynamicCstr, fullPrec));
  240. }
  241. inheritRules() {
  242. if (!this.inherits || !this.inherits.getSpeechRules().length) {
  243. return;
  244. }
  245. const regexp = new RegExp('^\\w+\\.\\w+\\.' + (this.domain ? '\\w+\\.' : ''));
  246. this.inherits.getSpeechRules().forEach((rule) => {
  247. const newDynamic = this.parseCstr(rule.dynamicCstr.toString().replace(regexp, ''));
  248. this.addRule(new SpeechRule(rule.name, newDynamic, rule.precondition, rule.action));
  249. });
  250. }
  251. ignoreRules(name, ...cstrs) {
  252. let rules = this.findAllRules((r) => r.name === name);
  253. if (!cstrs.length) {
  254. rules.forEach(this.deleteRule.bind(this));
  255. return;
  256. }
  257. let rest = [];
  258. for (const cstr of cstrs) {
  259. const dynamic = this.parseCstr(cstr);
  260. for (const rule of rules) {
  261. if (dynamic.equal(rule.dynamicCstr)) {
  262. this.deleteRule(rule);
  263. }
  264. else {
  265. rest.push(rule);
  266. }
  267. }
  268. rules = rest;
  269. rest = [];
  270. }
  271. }
  272. parsePrecondition_(cstr) {
  273. const generator = this.context.customGenerators.lookup(cstr);
  274. return generator ? generator() : [cstr];
  275. }
  276. }
  277. class Condition {
  278. constructor(base, condition) {
  279. this.base = base;
  280. this._conditions = [];
  281. this.constraints = [];
  282. this.allCstr = {};
  283. this.constraints.push(base);
  284. this.addCondition(base, condition);
  285. }
  286. get conditions() {
  287. return this._conditions;
  288. }
  289. addConstraint(dynamic) {
  290. if (this.constraints.filter((cstr) => cstr.equal(dynamic)).length) {
  291. return;
  292. }
  293. this.constraints.push(dynamic);
  294. const newConds = [];
  295. for (const [dyn, pre] of this.conditions) {
  296. if (this.base.equal(dyn)) {
  297. newConds.push([dynamic, pre]);
  298. }
  299. }
  300. this._conditions = this._conditions.concat(newConds);
  301. }
  302. addBaseCondition(cond) {
  303. this.addCondition(this.base, cond);
  304. }
  305. addFullCondition(cond) {
  306. this.constraints.forEach((cstr) => this.addCondition(cstr, cond));
  307. }
  308. addCondition(dynamic, cond) {
  309. const condStr = dynamic.toString() + ' ' + cond.toString();
  310. if (this.allCstr.condStr) {
  311. return;
  312. }
  313. this.allCstr[condStr] = true;
  314. this._conditions.push([dynamic, cond]);
  315. }
  316. }