add error check for unknown tokens in feature portions of rules

This commit is contained in:
Sorrel Bri 2020-02-19 23:18:18 -08:00
parent 54dbe75a70
commit 6885aeba2f
2 changed files with 71 additions and 35 deletions

View file

@ -89,20 +89,39 @@ const decomposeRule = (rule: string, index: number): ruleBundle => {
} }
} }
const doesFeatureRuleContainUnknownToken = features => {
const unknownTokens = features
.match(/\W/g)
.filter(v => v !== '-' && v !== '+' && v !== ']' && v !== '[' && v !== ' ')
if (unknownTokens.length) throw `Unknown token '${unknownTokens[0]}'`;
return true
}
const getPositiveFeatures = phoneme => { const getPositiveFeatures = phoneme => {
const positiveFeatures = phoneme.match(/(?=\+.).*(?<=\-)|(?=\+.).*(?!\-).*(?<=\])/g) try {
return positiveFeatures ? positiveFeatures[0] const positiveFeatures = phoneme.match(/(?=\+.).*(?<=\-)|(?=\+.).*(?!\-).*(?<=\])/g)
.trim().match(/\w+/g) if (positiveFeatures) doesFeatureRuleContainUnknownToken(positiveFeatures[0])
.reduce((map, feature) => ({...map, [feature]: true}), {}) return positiveFeatures ? positiveFeatures[0]
: {} .trim().match(/\w+/g)
.reduce((map, feature) => ({...map, [feature]: true}), {})
: {}
} catch (err) {
throw err;
}
} }
const getNegativeFeatures = phoneme => { const getNegativeFeatures = phoneme => {
const negativeFeatures = phoneme.match(/(?=\-.).*(?<=\+)|(?=\-.).*(?!\+).*(?<=\])/g) try {
return negativeFeatures ? negativeFeatures[0] const negativeFeatures = phoneme.match(/(?=\-.).*(?<=\+)|(?=\-.).*(?!\+).*(?<=\])/g)
.trim().match(/\w+/g) if (negativeFeatures) doesFeatureRuleContainUnknownToken(negativeFeatures[0])
.reduce((map, feature) => ({...map, [feature]: false}), {}) return negativeFeatures ? negativeFeatures[0]
: {} .trim()
.match(/\w+/g)
.reduce((map, feature) => ({...map, [feature]: false}), {})
: {}
} catch (err) {
throw err;
}
} }
const mapToPositiveAndNegativeFeatures = phoneme => ( const mapToPositiveAndNegativeFeatures = phoneme => (
@ -114,28 +133,36 @@ const mapStringToFeatures = (ruleString, phones) => {
if (ruleString === '#') return ['#'] if (ruleString === '#') return ['#']
if (ruleString === '0') return []; if (ruleString === '0') return [];
const ruleBrackets = ruleString.match(/\[.*\]/) const ruleBrackets = ruleString.match(/\[.*\]/)
if (ruleBrackets) { try {
return ruleString if (ruleBrackets) {
return ruleString
.split('[') .split('[')
// filter out empty strings // filter out empty strings
.filter(v => v) .filter(v => v)
.map(mapToPositiveAndNegativeFeatures) .map(mapToPositiveAndNegativeFeatures)
}
return findFeaturesFromGrapheme(phones, ruleString);
} catch (err) {
throw err;
} }
return findFeaturesFromGrapheme(phones, ruleString);
} }
return {}; return {};
} }
const mapRuleBundleToFeatureBundle = phones => ruleBundle => { const mapRuleBundleToFeatureBundle = phones => ( ruleBundle, index ) => {
// for each object in ruleBundle, map values to array of objects with feature-boolean key-value pairs // for each object in ruleBundle, map values to array of objects with feature-boolean key-value pairs
const { newFeatures, environment:{ pre, position, post } } = ruleBundle; try {
return { const { newFeatures, environment:{ pre, position, post } } = ruleBundle;
environment: { return {
pre: mapStringToFeatures(pre, phones), environment: {
position: mapStringToFeatures(position, phones), pre: mapStringToFeatures(pre, phones),
post: mapStringToFeatures(post, phones), position: mapStringToFeatures(position, phones),
}, post: mapStringToFeatures(post, phones),
newFeatures: mapStringToFeatures(newFeatures, phones) },
newFeatures: mapStringToFeatures(newFeatures, phones)
}
} catch (err) {
throw errorMessage`Error in line ${index + 1}: ${err}`;
} }
} }
@ -228,18 +255,21 @@ const stringifyResults = lexemeBundle => Object.entries(lexemeBundle).map(getGra
export const run = (state: stateType, action: resultsAction): stateType => { export const run = (state: stateType, action: resultsAction): stateType => {
// TODO iterate through each epoch // TODO iterate through each epoch
const epoch = state.epochs[0]; try {
const epoch = state.epochs[0];
const { phones, lexicon, features } = state; const { phones, lexicon, features } = state;
const ruleBundle = decomposeRules(epoch, phones); const ruleBundle = decomposeRules(epoch, phones);
const lexiconBundle = formBundleFromLexicon(lexicon)(phones); const lexiconBundle = formBundleFromLexicon(lexicon)(phones);
const passResults = transformLexicon(lexiconBundle)(ruleBundle)(features); const passResults = transformLexicon(lexiconBundle)(ruleBundle)(features);
const stringifiedPassResults = passResults.map(stringifyResults); const stringifiedPassResults = passResults.map(stringifyResults);
const pass = { const pass = {
pass: epoch.name, pass: epoch.name,
lexicon: stringifiedPassResults lexicon: stringifiedPassResults
}
return {...state, results: [pass] }
} catch (err) {
return err;
} }
return {...state, results: [pass] }
} }

View file

@ -55,6 +55,12 @@ describe('Results', () => {
expect(decomposeRules(epoch, phones)).toEqual("Error in line 1: Too many '_' operators"); expect(decomposeRules(epoch, phones)).toEqual("Error in line 1: Too many '_' operators");
}) })
it('rule with incorrect feature syntax returns helpful error message', () => {
const { phones } = initState();
const epoch = { name: 'error epoch', changes: [ '[+ occlusive - nasal = obstruent]>n/_' ] }
expect(decomposeRules(epoch, phones)).toEqual("Error in line 1: Unknown token '='");
})
it('expect transform lexeme to apply rule to lexeme', () => { it('expect transform lexeme to apply rule to lexeme', () => {
const lexemeBundle = getlexemeBundle(); const lexemeBundle = getlexemeBundle();
const resultsLexeme = [...lexemeBundle] const resultsLexeme = [...lexemeBundle]