eliza-core
Version:
A rendition of ELIZA program engine by Weizenbaum sharable for all javascript environments
122 lines (121 loc) • 4.76 kB
JavaScript
import * as EString from './estring';
import { NoMentionDefException } from './exceptions';
import { cartesian } from './utils';
const NAMING_CHARACTERS = ('ABCDEFGHIJKLMNOPQRSTUVWXYZ'
+ 'abcdefghijklmnopqrstuvwxyz'
+ '0123456789-_').split('');
function validateMentionNaming(name) {
return name.split('').find(c => NAMING_CHARACTERS.indexOf(c) < 0) === undefined;
}
function segmentScope(pattern) {
const patternProfile = [];
let restToSegment = pattern;
while (true) {
const segmentedPat = EString.match(restToSegment, '*@*[*]*');
if (!segmentedPat) {
patternProfile.push({ pattern: restToSegment });
break;
}
if (validateMentionNaming(segmentedPat[1])) {
patternProfile.push({ pattern: segmentedPat[0] });
patternProfile.push({ pattern: segmentedPat[2], mentions: [segmentedPat[1]] });
restToSegment = segmentedPat[3];
continue;
}
const searchMentionNamePosition = segmentedPat[1].lastIndexOf('@');
if (searchMentionNamePosition > -1) {
patternProfile.push({
pattern: segmentedPat[0] + '@'
+ segmentedPat[1].substring(0, searchMentionNamePosition),
});
patternProfile.push({
pattern: segmentedPat[2],
mentions: [segmentedPat[1].substring(searchMentionNamePosition + 1)],
});
restToSegment = segmentedPat[3];
continue;
}
patternProfile.push({ pattern: restToSegment });
}
return patternProfile;
}
function cartesianAllScopes(synonyms, patternProfile) {
return patternProfile.reduce((agg, current) => {
if (agg.length < 1) {
return [[current]];
}
if (current.mentions) {
let possibleWords = [];
current.mentions.forEach(mentionTag => {
const mentionRoute = synonyms.find(synonym => synonym.tag === mentionTag);
if (mentionRoute) {
possibleWords = possibleWords.concat(mentionRoute.words.map(word => ({ pattern: word, mentionTag, innerPattern: current.pattern })));
}
else {
throw new NoMentionDefException(mentionTag);
}
});
return cartesian(agg, possibleWords).map(comb => [...comb[0], comb[1]]);
}
else {
return cartesian(agg, [current]).map(comb => [...comb[0], comb[1]]);
}
}, []);
}
export function matchDecomposition(synonyms, str, pat) {
const patternProfile = segmentScope(pat);
if (patternProfile.length < 3) {
const simpleMatch = EString.match(str, pat);
return simpleMatch ? {
slottedTokens: simpleMatch.map(t => ({
token: t, scopes: {},
})), scopes: {},
} : null;
}
const cartesianAllSyn = cartesianAllScopes(synonyms, patternProfile);
let matchedParts = [];
const matchedPattern = cartesianAllSyn.find(patternParts => {
const matchAttempt = EString.match(str, patternParts.map(p => p.pattern).join(''));
if (matchAttempt) {
matchedParts = matchAttempt;
}
return !!matchAttempt;
});
if (!matchedPattern) {
return null;
}
const ensuredParts = matchedParts;
const hyperDecomposeRes = {
slottedTokens: [],
scopes: {},
};
matchedPattern.forEach(p => {
const expectedParts = EString.count(p.pattern, '*');
if (p.mentionTag && p.innerPattern) {
const mentionTag = p.mentionTag;
const innerDecomposition = matchDecomposition(synonyms, p.pattern, p.innerPattern);
if (!innerDecomposition) {
throw new Error(`Fatal Error: Decomposing in scope failed: [${p.pattern}] --> [${p.innerPattern}]`);
}
innerDecomposition.slottedTokens
.forEach(part => {
part.scopes[mentionTag] = {
text: p.pattern, mentionTag: p.mentionTag,
};
hyperDecomposeRes.slottedTokens.push(part);
});
hyperDecomposeRes.scopes[mentionTag] = {
text: p.pattern, mentionTag: p.mentionTag,
};
return;
}
for (let index = 0; index < expectedParts; index++) {
const part = ensuredParts.shift();
if (part === undefined || part === null) {
throw new Error('Fatal Error: Extracted Terms not matching wildcards!');
}
hyperDecomposeRes.slottedTokens.push({ token: part, scopes: {} });
}
});
return hyperDecomposeRes;
}