UNPKG

eliza-core

Version:

A rendition of ELIZA program engine by Weizenbaum sharable for all javascript environments

375 lines (374 loc) 15 kB
import * as tslib_1 from "tslib"; import { of, concat } from 'rxjs'; import { scan, tap, concatMap, filter } from 'rxjs/operators'; import { Decomp } from './decompo'; import { Reassemble } from './Reassemble'; import * as estring from './estring'; import * as mention from './mention-router'; import { Key } from './key'; import { GotoKey } from './key-goto'; import { buildKeyStack } from './key-stack'; import * as PrePostUtil from './pre-post-utils'; import * as printers from './printers'; import { notEmpty } from './utils'; import { UnexpectedNumberException, UnknownRuleException, GotoLostException, ScriptInterpretingError, DuplicateAnnotateException, InvalidStringException, } from './exceptions'; const printSynonyms = true; const printKeys = true; const PRINT_PRE_POST = true; const PRINT_INITIAL_FINAL = true; export function loadEliza(script$, keyFilter) { return tslib_1.__awaiter(this, void 0, void 0, function* () { const eliza = new ElizaImpl(keyFilter || (key => true)); yield eliza.readScript(script$).toPromise(); return eliza; }); } export function loadElizaInEnglish(script$) { return tslib_1.__awaiter(this, void 0, void 0, function* () { const eliza = new ElizaImpl(key => true, sentence => sentence.split(' ')); yield eliza.readScript(script$).toPromise(); return eliza; }); } class ElizaImpl { constructor(keyFilter, tokenizer) { this.keyFilter = keyFilter; this.tokenizer = tokenizer; this.mem = []; this.keys = []; this.synonyms = []; this.preList = []; this.postList = []; this.annotates = []; this.tweakList = []; this.initialStr = 'Hello.'; this.finalStr = 'Goodbye.'; this.quitList = []; this.lastDecomp = []; this.lastReasemb = []; this.finished = false; } getInitialStr() { return this.initialStr; } isFinished() { return this.finished; } collect(s) { const matchedPattern = [ { tryMatch: (testStr) => estring.match(testStr, '*reasmb: *'), onMatched: (matchedParts) => { if (!this.lastReasemb) { throw new ScriptInterpretingError('A Reasmb rule missing decomp rule'); } this.lastReasemb.push(new Reassemble(matchedParts[1], this.lastDecomp[this.lastDecomp.length - 1])); }, }, { tryMatch: (testStr) => estring.match(testStr, '*decomp: *'), onMatched: (matchedParts) => { if (!this.lastDecomp) { throw new ScriptInterpretingError('A Decomp rule missing Key Initialization'); } this.lastReasemb = []; const temp = matchedParts[1]; const newMatch = estring.match(temp, '$ *'); this.lastDecomp.push(new Decomp(newMatch ? newMatch[0] : temp, newMatch ? true : false, this.lastReasemb)); }, }, { tryMatch: (testStr) => estring.match(testStr, '*key: * #*'), onMatched: (matchedParts) => { this.lastDecomp = []; this.lastReasemb = null; let n = 0; if (matchedParts[2].length > 0) { n = parseInt(matchedParts[2], 10); if (isNaN(n)) { throw new UnexpectedNumberException(matchedParts[2], 'key'); } } this.keys.push(new Key(matchedParts[1], n, this.lastDecomp)); }, }, { tryMatch: (testStr) => estring.match(testStr, '*key: *'), onMatched: (matchedParts) => { this.lastDecomp = []; this.lastReasemb = null; this.keys.push(new Key(matchedParts[1], 0, this.lastDecomp)); }, }, { tryMatch: (testStr) => estring.match(testStr, '*mention: *@* *'), onMatched: (matchedParts) => { const words = JSON.parse(`[${matchedParts[3]}]`); this.synonyms.push({ tag: matchedParts[2], words: words.slice(1), }); }, }, { tryMatch: (testStr) => estring.match(testStr, '*annotate: *'), onMatched: (matchedParts) => { const tag = matchedParts[1].trim(); if (tag.length < 1 || this.annotates.find(w => w === tag)) { throw new DuplicateAnnotateException(tag); } this.annotates.push(tag); }, }, { tryMatch: (testStr) => estring.match(testStr, '*pre: * => *'), onMatched: (matchedParts) => { this.preList.push({ src: JSON.parse(matchedParts[1]), dest: JSON.parse(matchedParts[2]) }); }, }, { tryMatch: (testStr) => estring.match(testStr, '*post: * => *'), onMatched: (matchedParts) => { this.postList.push({ src: JSON.parse(matchedParts[1]), dest: JSON.parse(matchedParts[2]) }); }, }, { tryMatch: (testStr) => estring.match(testStr, '*tweak: * => *'), onMatched: (matchedParts) => { this.tweakList.push({ src: JSON.parse(matchedParts[1]), dest: JSON.parse(matchedParts[2]) }); }, }, { tryMatch: (testStr) => estring.match(testStr, '*initial: *'), onMatched: (matchedParts) => { this.initialStr = matchedParts[1]; }, }, { tryMatch: (testStr) => estring.match(testStr, '*final: *'), onMatched: (matchedParts) => { this.finalStr = matchedParts[1]; }, }, { tryMatch: (testStr) => estring.match(testStr, '*quit: *'), onMatched: (matchedParts) => { this.quitList.push(` ${matchedParts[1]} `); }, }, { tryMatch: (testStr) => { const parts = estring.match(testStr, '*: *'); if (parts && this.annotates.indexOf(estring.trim(parts[0])) > -1) { return parts; } return null; }, onMatched: (matchedParts) => { const instruction = estring.trim(matchedParts[0]); if (!this.lastReasemb) { throw new ScriptInterpretingError('An annotated Reasmb rule missing decomp rule'); } this.lastReasemb.push(new Reassemble(matchedParts[1], this.lastDecomp[this.lastDecomp.length - 1], instruction)); }, }, ].find(({ tryMatch, onMatched }) => { const matchedParts = tryMatch(s); if (matchedParts) { onMatched(matchedParts); return true; } return false; }); if (!matchedPattern) { throw new UnknownRuleException(s); } } toJson() { const toPrint = {}; if (printKeys) { toPrint.keys = this.keys.map(k => printers.snapshotKey(k)); } if (printSynonyms) { toPrint.mentionRoutes = this.synonyms; } if (PRINT_PRE_POST) { toPrint.preList = this.preList; toPrint.postList = this.postList; } if (PRINT_INITIAL_FINAL) { toPrint.initial = this.initialStr; toPrint.final = this.finalStr; toPrint.quitList = this.quitList.join(' '); } return toPrint; } processHyperInput(s) { let matchedParts = estring.match(s, '*.*'); while (matchedParts) { const reply = this.sentence(matchedParts[0]); if (reply) { return reply; } s = estring.trim(matchedParts[1]); matchedParts = estring.match(s, '*.*'); } if (s.length > 0) { const reply = this.sentence(s); if (reply) { return reply; } } const m = this.mem.shift(); if (m) { return { assembled: { reassembled: m } }; } const key = this.keys.find(k => k.getKey() === 'xnone'); if (key) { const context = this.fullyDecompose(key, s); if (context && context.assembled) { return context; } } return null; } processInput(s) { if (typeof s !== 'string') { throw new InvalidStringException(s); } s = estring.replaceAll(s, 'ABCDEFGHIJKLMNOPQRSTUVWXYZ', 'abcdefghijklmnopqrstuvwxyz'); s = estring.replaceAll(s, '@#$%^&*()_-+=~`{[}]|:;<>\\"', ' '); s = estring.replaceAll(s, ',?!', '...'); s = estring.compress(s); return this.processHyperInput(s); } readScript(script$) { return concat(script$, of('\n')).pipe(scan(({ buffer }, chunk) => { const split = (buffer + chunk).split('\n'); const rest = split.pop(); return { buffer: rest || '', lines: split }; }, { buffer: '', lines: [''] }), concatMap(pack => pack.lines), filter(line => line.trim().length > 0 && !line.startsWith('%')), tap(line => this.collect(line))); } sentence(s) { s = PrePostUtil.translate(this.preList, s).trim(); s = estring.pad(s); if (this.quitList.indexOf(s) >= 0) { this.finished = true; return { assembled: { reassembled: this.finalStr } }; } if (this.tokenizer) { let result = null; return buildKeyStack(this.keys.filter(k => this.keyFilter(k.getKey())), this.tokenizer(s)) .find(key => { const ctx = this.fullyDecompose(key, s); if (ctx) { result = ctx; return true; } return false; }) ? result : null; } const sortedReplyContexts = this.keys.filter(k => this.keyFilter(k.getKey())) .map(key => this.fullyDecompose(key, s)) .filter(notEmpty).sort((ctxA, ctxB) => (ctxA.matches || { slottedTokens: [{ token: s, scopes: {} }] }) .slottedTokens.filter(t => Object.keys(t.scopes).length < 1) .map(t => t.token).join('').length - (ctxB.matches || { slottedTokens: [{ token: s, scopes: {} }] }) .slottedTokens.filter(t => Object.keys(t.scopes).length < 1) .map(t => t.token).join('').length); return sortedReplyContexts[0] || null; } decompose(key, s) { const ASSEMBLE_FUNC = this.assemble; return (key.getDecomp() || []).map(decomposition => { const matches = mention.matchDecomposition(this.synonyms, s, decomposition.getPattern()); if (!matches) { return null; } return { decomposition, matches, assembled: null, }; }).filter(notEmpty).sort((ctxA, ctxB) => (ctxA.matches || { slottedTokens: [{ token: s, scopes: {} }] }) .slottedTokens.filter(t => Object.keys(t.scopes).length < 1) .map(t => t.token).join('').length - (ctxB.matches || { slottedTokens: [{ token: s, scopes: {} }] }) .slottedTokens.filter(t => Object.keys(t.scopes).length < 1) .map(t => t.token).join('').length) .find(ctx => { ctx.assembled = this.assemble(ctx.decomposition, ctx.matches.slottedTokens); if (!ctx.assembled) { return false; } if (ctx.assembled instanceof GotoKey) { if (ctx.assembled.getKey()) { return true; } } else { return true; } return false; }) || null; } fullyDecompose(key, s) { let decomposeCtx = this.decompose(key, s); while (decomposeCtx) { if (decomposeCtx.assembled instanceof GotoKey) { const gotoKey = decomposeCtx.assembled; decomposeCtx = this.decompose(gotoKey, s); continue; } if (!decomposeCtx.assembled) { return null; } const assembledResult = decomposeCtx.assembled; assembledResult.reassembled = PrePostUtil .translate(this.tweakList, assembledResult.reassembled); Object.keys(assembledResult.annotations).forEach(k => { assembledResult.annotations[k] = PrePostUtil .translate(this.tweakList, assembledResult.annotations[k]); }); return { decomposition: decomposeCtx.decomposition, matches: decomposeCtx.matches, assembled: assembledResult, }; } return null; } assemble(d, decomposedSlots) { const decomposedTokens = decomposedSlots.map(s => s.token); const rule = d.nextRule(); const assembledResult = rule.assemble(decomposedTokens.map(token => PrePostUtil.translate(this.postList, token).trim())); if (assembledResult.gotoKey && assembledResult.gotoKey.length > 0) { const gotoKey = this.keys.find(k => k.getKey() === assembledResult.gotoKey); if (gotoKey && gotoKey.getKey()) { return new GotoKey(gotoKey); } throw new GotoLostException(rule.getTemplate()); } if (!assembledResult.result) { return null; } if (d.isMemoryKey()) { this.mem.push(assembledResult.result); return null; } return { reassembled: assembledResult.result, annotations: d.getAnnotates() .map(annotate => annotate.assemble(decomposedTokens)) .reduce((reduced, current) => { if (current.annotation) { reduced[current.annotation] = current.result + ''; } return reduced; }, {}), }; } }