@predictive-text-studio/lexical-model-compiler
Version:
Keyman Developer lexical model compiler
136 lines • 5.69 kB
JavaScript
;
/*
lexical-model-compiler.ts: base file for lexical model compiler.
*/
Object.defineProperty(exports, "__esModule", { value: true });
/// <reference path="./model-info-file.ts" />
const build_trie_1 = require("./build-trie");
const join_word_breaker_decorator_1 = require("./join-word-breaker-decorator");
const script_overrides_decorator_1 = require("./script-overrides-decorator");
const parse_wordlist_1 = require("./parse-wordlist");
class DefaultLexicalModelCompiler {
constructor() {
this.snippets = [];
}
emit(code) {
this.snippets.push(code);
}
emitLine(code) {
this.emit((code + "\n"));
}
get func() {
return this.snippets.join('');
}
/**
* Returns the generated code for the model that will ultimately be loaded by
* the LMLayer worker. This code contains all model parameters, and specifies
* word breakers and auxilary functions that may be required.
*
* @param model_id The model ID. TODO: not sure if this is actually required!
* @param modelSource A specification of the model to compile
* @param sourcePath Where to find auxilary sources files
*/
compile(modelSource, getWordList) {
this.emitLine(`(function() {`);
this.emitLine(`'use strict';`);
// TODO: add metadata in comment
switch (modelSource.format) {
case "custom-1.0":
case "fst-foma-1.0":
throw new ModelSourceError(`Unimplemented model format: ${modelSource.format}`);
case "trie-1.0":
let wordlist = modelSource.sources.reduce((wl, source) => {
let wordlistSource = typeof source === "string" ? getWordList(source) : source;
parse_wordlist_1.parseWordList(wl, wordlistSource);
return wl;
}, {});
// Use the default search term to key function, if left unspecified.
let searchTermToKey = modelSource.searchTermToKey || build_trie_1.defaultSearchTermToKey;
this.emit(`LMLayerWorker.loadModel(new models.TrieModel(${build_trie_1.compileTrieFromWordlist(wordlist, searchTermToKey)}, {\n`);
let wordBreakerSourceCode = compileWordBreaker(normalizeWordBreakerSpec(modelSource.wordBreaker));
this.emit(` wordBreaker: ${wordBreakerSourceCode},\n`);
this.emit(` searchTermToKey: ${searchTermToKey.toString()},\n`);
if (modelSource.punctuation) {
this.emit(` punctuation: ${JSON.stringify(modelSource.punctuation)},\n`);
}
this.emit(`}));\n`);
break;
default:
throw new ModelSourceError(`Unknown model format: ${modelSource.format}`);
}
this.emit(`})();`);
return this.func;
}
}
exports.DefaultLexicalModelCompiler = DefaultLexicalModelCompiler;
;
class ModelSourceError extends Error {
}
exports.ModelSourceError = ModelSourceError;
/**
* Returns a JavaScript expression (as a string) that can serve as a word
* breaking function.
*/
function compileWordBreaker(spec) {
let wordBreakerCode = compileInnerWordBreaker(spec.use);
if (spec.joinWordsAt) {
wordBreakerCode = compileJoinDecorator(spec, wordBreakerCode);
}
if (spec.overrideScriptDefaults) {
wordBreakerCode = compileScriptOverrides(spec, wordBreakerCode);
}
return wordBreakerCode;
}
function compileJoinDecorator(spec, existingWordBreakerCode) {
// Bundle the source of the join decorator, as an IIFE,
// like this: (function join(breaker, joiners) {/*...*/}(breaker, joiners))
// The decorator will run IMMEDIATELY when the model is loaded,
// by the LMLayer returning the decorated word breaker to the
// LMLayer model.
let joinerExpr = JSON.stringify(spec.joinWordsAt);
return `(${join_word_breaker_decorator_1.decorateWithJoin.toString()}(${existingWordBreakerCode}, ${joinerExpr}))`;
}
function compileScriptOverrides(spec, existingWordBreakerCode) {
return `(${script_overrides_decorator_1.decorateWithScriptOverrides.toString()}(${existingWordBreakerCode}, '${spec.overrideScriptDefaults}'))`;
}
/**
* Compiles the base word breaker, that may be decorated later.
* Returns the source code of a JavaScript expression.
*/
function compileInnerWordBreaker(spec) {
if (typeof spec === "string") {
// It must be a builtin word breaker, so just instantiate it.
return `wordBreakers['${spec}']`;
}
else {
// It must be a function:
return spec.toString()
// Note: the .toString() might just be the property name, but we want a
// plain function:
.replace(/^wordBreak(ing|er)\b/, 'function');
}
}
/**
* Given a word breaker specification in any of the messy ways,
* normalizes it to a common form that the compiler can deal with.
*/
function normalizeWordBreakerSpec(wordBreakerSpec) {
if (wordBreakerSpec == undefined) {
// Use the default word breaker when it's unspecified
return { use: 'default' };
}
else if (isSimpleWordBreaker(wordBreakerSpec)) {
// The word breaker was passed as a literal function; use its source code.
return { use: wordBreakerSpec };
}
else if (wordBreakerSpec.use) {
return wordBreakerSpec;
}
else {
throw new Error(`Unknown word breaker: ${wordBreakerSpec}`);
}
}
function isSimpleWordBreaker(spec) {
return typeof spec === "function" || spec === "default" || spec === "ascii";
}
//# sourceMappingURL=lexical-model-compiler.js.map