clearspeak_preferences.js 11 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319
  1. import { Engine } from '../common/engine.js';
  2. import * as EngineConst from '../common/engine_const.js';
  3. import { DynamicCstr } from '../rule_engine/dynamic_cstr.js';
  4. import { Axis, DefaultComparator, DynamicCstrParser, DynamicProperties } from '../rule_engine/dynamic_cstr.js';
  5. import * as MathCompoundStore from '../rule_engine/math_compound_store.js';
  6. import { SpeechRuleEngine } from '../rule_engine/speech_rule_engine.js';
  7. import { SemanticRole, SemanticType } from '../semantic_tree/semantic_meaning.js';
  8. export class ClearspeakPreferences extends DynamicCstr {
  9. static comparator() {
  10. 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]]));
  11. }
  12. static fromPreference(pref) {
  13. const pairs = pref.split(':');
  14. const preferences = {};
  15. const properties = PREFERENCES.getProperties();
  16. const validKeys = Object.keys(properties);
  17. for (let i = 0, key; (key = pairs[i]); i++) {
  18. const pair = key.split('_');
  19. if (validKeys.indexOf(pair[0]) === -1) {
  20. continue;
  21. }
  22. const value = pair[1];
  23. if (value &&
  24. value !== ClearspeakPreferences.AUTO &&
  25. properties[pair[0]].indexOf(value) !== -1) {
  26. preferences[pair[0]] = pair[1];
  27. }
  28. }
  29. return preferences;
  30. }
  31. static toPreference(pref) {
  32. const keys = Object.keys(pref);
  33. const str = [];
  34. for (let i = 0; i < keys.length; i++) {
  35. str.push(keys[i] + '_' + pref[keys[i]]);
  36. }
  37. return str.length ? str.join(':') : DynamicCstr.DEFAULT_VALUE;
  38. }
  39. static getLocalePreferences(opt_dynamic) {
  40. const dynamic = opt_dynamic ||
  41. MathCompoundStore.enumerate(SpeechRuleEngine.getInstance().enumerate());
  42. return ClearspeakPreferences.getLocalePreferences_(dynamic);
  43. }
  44. static currentPreference() {
  45. return EngineConst.DOMAIN_TO_STYLES['clearspeak'];
  46. }
  47. static relevantPreferences(node) {
  48. const roles = SEMANTIC_MAPPING_[node.type];
  49. if (!roles) {
  50. return 'ImpliedTimes';
  51. }
  52. return roles[node.role] || roles[''] || 'ImpliedTimes';
  53. }
  54. static findPreference(prefs, kind) {
  55. if (prefs === 'default') {
  56. return ClearspeakPreferences.AUTO;
  57. }
  58. const parsed = ClearspeakPreferences.fromPreference(prefs);
  59. return parsed[kind] || ClearspeakPreferences.AUTO;
  60. }
  61. static addPreference(prefs, kind, value) {
  62. if (prefs === 'default') {
  63. return kind + '_' + value;
  64. }
  65. const parsed = ClearspeakPreferences.fromPreference(prefs);
  66. parsed[kind] = value;
  67. return ClearspeakPreferences.toPreference(parsed);
  68. }
  69. static getLocalePreferences_(dynamic) {
  70. const result = {};
  71. for (const locale of Object.keys(dynamic)) {
  72. if (!dynamic[locale]['speech'] ||
  73. !dynamic[locale]['speech']['clearspeak']) {
  74. continue;
  75. }
  76. const locPrefs = Object.keys(dynamic[locale]['speech']['clearspeak']);
  77. if (locPrefs.length < 3)
  78. continue;
  79. const prefs = (result[locale] = {});
  80. for (const axis in PREFERENCES.getProperties()) {
  81. const allSty = PREFERENCES.getProperties()[axis];
  82. const values = [axis + '_Auto'];
  83. if (allSty) {
  84. for (const sty of allSty) {
  85. if (locPrefs.indexOf(axis + '_' + sty) !== -1) {
  86. values.push(axis + '_' + sty);
  87. }
  88. }
  89. }
  90. prefs[axis] = values;
  91. }
  92. }
  93. return result;
  94. }
  95. constructor(cstr, preference) {
  96. super(cstr);
  97. this.preference = preference;
  98. }
  99. equal(cstr) {
  100. const top = super.equal(cstr);
  101. if (!top) {
  102. return false;
  103. }
  104. const keys = Object.keys(this.preference);
  105. const preference = cstr.preference;
  106. if (keys.length !== Object.keys(preference).length) {
  107. return false;
  108. }
  109. for (let i = 0, key; (key = keys[i]); i++) {
  110. if (this.preference[key] !== preference[key]) {
  111. return false;
  112. }
  113. }
  114. return true;
  115. }
  116. }
  117. ClearspeakPreferences.AUTO = 'Auto';
  118. const PREFERENCES = new DynamicProperties({
  119. AbsoluteValue: ['Auto', 'AbsEnd', 'Cardinality', 'Determinant'],
  120. Bar: ['Auto', 'Conjugate'],
  121. Caps: ['Auto', 'SayCaps'],
  122. CombinationPermutation: ['Auto', 'ChoosePermute'],
  123. Currency: ['Auto', 'Position', 'Prefix'],
  124. Ellipses: ['Auto', 'AndSoOn'],
  125. Enclosed: ['Auto', 'EndEnclose'],
  126. Exponent: [
  127. 'Auto',
  128. 'AfterPower',
  129. 'Ordinal',
  130. 'OrdinalPower',
  131. 'Exponent'
  132. ],
  133. Fraction: [
  134. 'Auto',
  135. 'EndFrac',
  136. 'FracOver',
  137. 'General',
  138. 'GeneralEndFrac',
  139. 'Ordinal',
  140. 'Over',
  141. 'OverEndFrac',
  142. 'Per'
  143. ],
  144. Functions: [
  145. 'Auto',
  146. 'None',
  147. 'Reciprocal'
  148. ],
  149. ImpliedTimes: ['Auto', 'MoreImpliedTimes', 'None'],
  150. Log: ['Auto', 'LnAsNaturalLog'],
  151. Matrix: [
  152. 'Auto',
  153. 'Combinatoric',
  154. 'EndMatrix',
  155. 'EndVector',
  156. 'SilentColNum',
  157. 'SpeakColNum',
  158. 'Vector'
  159. ],
  160. MultiLineLabel: [
  161. 'Auto',
  162. 'Case',
  163. 'Constraint',
  164. 'Equation',
  165. 'Line',
  166. 'None',
  167. 'Row',
  168. 'Step'
  169. ],
  170. MultiLineOverview: ['Auto', 'None'],
  171. MultiLinePausesBetweenColumns: ['Auto', 'Long', 'Short'],
  172. MultsymbolDot: ['Auto', 'Dot'],
  173. MultsymbolX: ['Auto', 'By', 'Cross'],
  174. Paren: [
  175. 'Auto',
  176. 'CoordPoint',
  177. 'Interval',
  178. 'Silent',
  179. 'Speak',
  180. 'SpeakNestingLevel'
  181. ],
  182. Prime: ['Auto', 'Angle', 'Length'],
  183. Roots: ['Auto', 'PosNegSqRoot', 'PosNegSqRootEnd', 'RootEnd'],
  184. SetMemberSymbol: ['Auto', 'Belongs', 'Element', 'Member', 'In'],
  185. Sets: ['Auto', 'SilentBracket', 'woAll'],
  186. TriangleSymbol: ['Auto', 'Delta'],
  187. Trig: [
  188. 'Auto',
  189. 'ArcTrig',
  190. 'TrigInverse',
  191. 'Reciprocal'
  192. ],
  193. VerticalLine: ['Auto', 'Divides', 'Given', 'SuchThat']
  194. });
  195. class Comparator extends DefaultComparator {
  196. constructor(cstr, props) {
  197. super(cstr, props);
  198. this.preference =
  199. cstr instanceof ClearspeakPreferences ? cstr.preference : {};
  200. }
  201. match(cstr) {
  202. if (!(cstr instanceof ClearspeakPreferences)) {
  203. return super.match(cstr);
  204. }
  205. if (cstr.getComponents()[Axis.STYLE] === 'default') {
  206. return true;
  207. }
  208. const keys = Object.keys(cstr.preference);
  209. for (let i = 0, key; (key = keys[i]); i++) {
  210. if (this.preference[key] !== cstr.preference[key]) {
  211. return false;
  212. }
  213. }
  214. return true;
  215. }
  216. compare(cstr1, cstr2) {
  217. const top = super.compare(cstr1, cstr2);
  218. if (top !== 0) {
  219. return top;
  220. }
  221. const pref1 = cstr1 instanceof ClearspeakPreferences;
  222. const pref2 = cstr2 instanceof ClearspeakPreferences;
  223. if (!pref1 && pref2) {
  224. return 1;
  225. }
  226. if (pref1 && !pref2) {
  227. return -1;
  228. }
  229. if (!pref1 && !pref2) {
  230. return 0;
  231. }
  232. const length1 = Object.keys(cstr1.preference).length;
  233. const length2 = Object.keys(cstr2.preference).length;
  234. return length1 > length2 ? -1 : length1 < length2 ? 1 : 0;
  235. }
  236. }
  237. class Parser extends DynamicCstrParser {
  238. constructor() {
  239. super([Axis.LOCALE, Axis.MODALITY, Axis.DOMAIN, Axis.STYLE]);
  240. }
  241. parse(str) {
  242. const initial = super.parse(str);
  243. let style = initial.getValue(Axis.STYLE);
  244. const locale = initial.getValue(Axis.LOCALE);
  245. const modality = initial.getValue(Axis.MODALITY);
  246. let pref = {};
  247. if (style !== DynamicCstr.DEFAULT_VALUE) {
  248. pref = this.fromPreference(style);
  249. style = this.toPreference(pref);
  250. }
  251. return new ClearspeakPreferences({
  252. locale: locale,
  253. modality: modality,
  254. domain: 'clearspeak',
  255. style: style
  256. }, pref);
  257. }
  258. fromPreference(pref) {
  259. return ClearspeakPreferences.fromPreference(pref);
  260. }
  261. toPreference(pref) {
  262. return ClearspeakPreferences.toPreference(pref);
  263. }
  264. }
  265. const REVERSE_MAPPING = [
  266. [
  267. 'AbsoluteValue',
  268. SemanticType.FENCED,
  269. SemanticRole.NEUTRAL,
  270. SemanticRole.METRIC
  271. ],
  272. ['Bar', SemanticType.OVERSCORE, SemanticRole.OVERACCENT],
  273. ['Caps', SemanticType.IDENTIFIER, SemanticRole.LATINLETTER],
  274. ['CombinationPermutation', SemanticType.APPL, SemanticRole.UNKNOWN],
  275. ['Ellipses', SemanticType.PUNCTUATION, SemanticRole.ELLIPSIS],
  276. ['Exponent', SemanticType.SUPERSCRIPT, ''],
  277. ['Fraction', SemanticType.FRACTION, ''],
  278. ['Functions', SemanticType.APPL, SemanticRole.SIMPLEFUNC],
  279. ['ImpliedTimes', SemanticType.OPERATOR, SemanticRole.IMPLICIT],
  280. ['Log', SemanticType.APPL, SemanticRole.PREFIXFUNC],
  281. ['Matrix', SemanticType.MATRIX, ''],
  282. ['Matrix', SemanticType.VECTOR, ''],
  283. ['MultiLineLabel', SemanticType.MULTILINE, SemanticRole.LABEL],
  284. ['MultiLineOverview', SemanticType.MULTILINE, SemanticRole.TABLE],
  285. ['MultiLinePausesBetweenColumns', SemanticType.MULTILINE, SemanticRole.TABLE],
  286. ['MultiLineLabel', SemanticType.TABLE, SemanticRole.LABEL],
  287. ['MultiLineOverview', SemanticType.TABLE, SemanticRole.TABLE],
  288. ['MultiLinePausesBetweenColumns', SemanticType.TABLE, SemanticRole.TABLE],
  289. ['MultiLineLabel', SemanticType.CASES, SemanticRole.LABEL],
  290. ['MultiLineOverview', SemanticType.CASES, SemanticRole.TABLE],
  291. ['MultiLinePausesBetweenColumns', SemanticType.CASES, SemanticRole.TABLE],
  292. ['MultsymbolDot', SemanticType.OPERATOR, SemanticRole.MULTIPLICATION],
  293. ['MultsymbolX', SemanticType.OPERATOR, SemanticRole.MULTIPLICATION],
  294. ['Paren', SemanticType.FENCED, SemanticRole.LEFTRIGHT],
  295. ['Prime', SemanticType.SUPERSCRIPT, SemanticRole.PRIME],
  296. ['Roots', SemanticType.ROOT, ''],
  297. ['Roots', SemanticType.SQRT, ''],
  298. ['SetMemberSymbol', SemanticType.RELATION, SemanticRole.ELEMENT],
  299. ['Sets', SemanticType.FENCED, SemanticRole.SETEXT],
  300. ['TriangleSymbol', SemanticType.IDENTIFIER, SemanticRole.GREEKLETTER],
  301. ['Trig', SemanticType.APPL, SemanticRole.PREFIXFUNC],
  302. ['VerticalLine', SemanticType.PUNCTUATED, SemanticRole.VBAR]
  303. ];
  304. const SEMANTIC_MAPPING_ = (function () {
  305. const result = {};
  306. for (let i = 0, triple; (triple = REVERSE_MAPPING[i]); i++) {
  307. const pref = triple[0];
  308. let role = result[triple[1]];
  309. if (!role) {
  310. role = {};
  311. result[triple[1]] = role;
  312. }
  313. role[triple[2]] = pref;
  314. }
  315. return result;
  316. })();
  317. Engine.getInstance().comparators['clearspeak'] =
  318. ClearspeakPreferences.comparator;
  319. Engine.getInstance().parsers['clearspeak'] = new Parser();