From 54dbe75a7087f4633e8736206777ab77fcc1b82b Mon Sep 17 00:00:00 2001 From: Sorrel Bri Date: Wed, 19 Feb 2020 22:14:21 -0800 Subject: [PATCH] add error messages for rule syntax --- src/reducers/reducer.init.js | 4 +- src/reducers/reducer.results.js | 34 ++++- src/reducers/reducer.results.test.js | 179 ++++++++++++++++----------- 3 files changed, 137 insertions(+), 80 deletions(-) diff --git a/src/reducers/reducer.init.js b/src/reducers/reducer.init.js index f3a3257..1b323e0 100644 --- a/src/reducers/reducer.init.js +++ b/src/reducers/reducer.init.js @@ -15,8 +15,8 @@ export const initState = (changesArgument: number): stateType => { 'a>ɯ/._#', '[+ sonorant - low rounded high back]>0/._.', '[+ obstruent]>[+ obstruent aspirated ]/#_.', - '[+ sonorant - rounded]>[+ sonorant + rounded]/._#' - // 'nn>nun/._.', + '[+ sonorant - rounded]>[+ sonorant + rounded]/._#', + 'nn>nun/._.' ] } ], diff --git a/src/reducers/reducer.results.js b/src/reducers/reducer.results.js index c1d8aa5..33fe83d 100644 --- a/src/reducers/reducer.results.js +++ b/src/reducers/reducer.results.js @@ -64,12 +64,28 @@ const findFeaturesFromGrapheme = (phones: {}, lexeme:string): [] => { return featureBundle; } -const decomposeRule = (rule: string): ruleBundle => { +const errorMessage = ([prefix, separator], location, err) => `${prefix}${location}${separator}${err}` + +const lintRule = (rule) => { + if (rule.match(/>/g) === null) throw `Insert '>' operator between target and result` + if (rule.match(/\//g) === null) throw `Insert '/' operator between change and environment` + if (rule.match(/_/g) === null) throw `Insert '_' operator in environment` + if (rule.match(/>/g).length > 1) throw `Too many '>' operators` + if (rule.match(/\//g).length > 1) throw `Too many '/' operators` + if (rule.match(/_/g).length > 1) throw `Too many '_' operators` + return rule.split(/>|\/|_/g); +} + +const decomposeRule = (rule: string, index: number): ruleBundle => { // splits rule at '>' '/' and '_' substrings resulting in array of length 4 - const [position, newFeatures, pre, post] = rule.split(/>|\/|_/g); - return { - environment: { pre, position, post }, - newFeatures + try { + const [position, newFeatures, pre, post] = lintRule(rule); + return { + environment: { pre, position, post }, + newFeatures + } + } catch (err) { + throw errorMessage`Error in line ${index + 1}: ${err}`; } } @@ -125,7 +141,13 @@ const mapRuleBundleToFeatureBundle = phones => ruleBundle => { export const decomposeRules = (epoch: epochType, phones: {[key: string]: phoneType}): decomposedRulesType => { const { changes } = epoch - return changes.map(decomposeRule).map(mapRuleBundleToFeatureBundle(phones)); + try { + return changes + .map(decomposeRule) + .map(mapRuleBundleToFeatureBundle(phones)); + } catch (err) { + return err; + } } const isPhonemeBoundByRule = phonemeFeatures => (ruleFeature, index) => { diff --git a/src/reducers/reducer.results.test.js b/src/reducers/reducer.results.test.js index f5699ec..88c521c 100644 --- a/src/reducers/reducer.results.test.js +++ b/src/reducers/reducer.results.test.js @@ -14,77 +14,54 @@ describe('Results', () => { }); it('rules decomposed properly', () => { - const epoch = initState().epochs[0]; - epoch.changes = epoch.changes.slice(0,1) - const phones = initState().phones; - const result = [ - { - // ! '[+ occlusive - nasal]>[+ occlusive nasal]/n_', - environment: { - pre: [ - { - sonorant: true, nasal: true, occlusive: true, coronal: true - } - ], - position: [ - {occlusive: true, nasal: false} - ], - post: [], - }, - newFeatures: [{occlusive: true, nasal: true}] - } - ]; - expect(decomposeRules(epoch, phones)).toStrictEqual(result); + const { epochs, phones } = initState(1); + const result = getResult(); + expect(decomposeRules(epochs[0], phones)).toStrictEqual(result); }); - it('expect transform lexeme to apply rule to lexeme', () => { - const lexemeBundle = [ - { - grapheme: 'a', - features: { - sonorant: true, - back: true, - low: true, - high: false, - rounded: false - } - }, - { - grapheme: 'n', - features: { sonorant: true, nasal: true, occlusive: true, coronal: true } - }, - { - grapheme: 't', - features: { occlusive: true, coronal: true, obstruent: true, nasal: false } - }, - { - grapheme: 'a', - features: { - sonorant: true, - back: true, - low: true, - high: false, - rounded: false - } - } - ] - - const resultsLexeme = [...lexemeBundle] - resultsLexeme[2] = lexemeBundle[1] - - const rule = { - environment: { - pre: [ { sonorant: true, nasal: true, occlusive: true, coronal: true } ], - position: [ { occlusive: true, nasal: false } ], - post: [] - }, - newFeatures: [ { occlusive: true, nasal: true } ] - } - - expect(transformLexeme(lexemeBundle, rule, initState().features)).toEqual(resultsLexeme) - + it('rule without ">" returns helpful error message', () => { + const { phones } = initState(); + const epoch = { name: 'error epoch', changes: [ 't/n/_' ] } + expect(decomposeRules(epoch, phones)).toEqual("Error in line 1: Insert '>' operator between target and result"); }) + it('rule with too many ">" returns helpful error message', () => { + const { phones } = initState(); + const epoch = { name: 'error epoch', changes: [ 't>n>/_' ] } + expect(decomposeRules(epoch, phones)).toEqual("Error in line 1: Too many '>' operators"); + }) + + it('rule without "/" returns helpful error message', () => { + const { phones } = initState(); + const epoch = { name: 'error epoch', changes: [ 't>n_' ] } + expect(decomposeRules(epoch, phones)).toEqual("Error in line 1: Insert '/' operator between change and environment"); + }) + + it('rule with too many "/" returns helpful error message', () => { + const { phones } = initState(); + const epoch = { name: 'error epoch', changes: [ 't>n/_/' ] } + expect(decomposeRules(epoch, phones)).toEqual("Error in line 1: Too many '/' operators"); + }) + + it('rule without "_" returns helpful error message', () => { + const { phones } = initState(); + const epoch = { name: 'error epoch', changes: [ 't>n/' ] } + expect(decomposeRules(epoch, phones)).toEqual("Error in line 1: Insert '_' operator in environment"); + }) + + it('rule with too many "_" returns helpful error message', () => { + const { phones } = initState(); + const epoch = { name: 'error epoch', changes: [ 't>n/__' ] } + expect(decomposeRules(epoch, phones)).toEqual("Error in line 1: Too many '_' operators"); + }) + + it('expect transform lexeme to apply rule to lexeme', () => { + const lexemeBundle = getlexemeBundle(); + const resultsLexeme = [...lexemeBundle] + resultsLexeme[2] = lexemeBundle[1] + const rule = getRule(); + expect(transformLexeme(lexemeBundle, rule, initState().features)).toEqual(resultsLexeme) + }) it('results returned from first sound change rule', () => { const action = {type: 'RUN'}; @@ -150,19 +127,77 @@ describe('Results', () => { } ]); }); - - // if('results returned from sound change suite', () => { + + + // it('results returned through sixth sound change rule', () => { // const action = {type: 'RUN'}; - // state = initState() - // console.log(stateReducer(state, action).results) + // state = initState(5) // expect(stateReducer(state, action).results).toEqual([ // { // pass: 'epoch 1', // lexicon: [ - // 'anna', 'anta', 'anət', 'anna', 'tan', 'ənna' + // 'anunu', 'anat', 'ant', 'anunu', 'tʰan', 'nunu' // ] // } // ]); // }); }); + + +const getlexemeBundle = () => ([ + { + grapheme: 'a', + features: { + sonorant: true, + back: true, + low: true, + high: false, + rounded: false + } + }, + { + grapheme: 'n', + features: { sonorant: true, nasal: true, occlusive: true, coronal: true } + }, + { + grapheme: 't', + features: { occlusive: true, coronal: true, obstruent: true, nasal: false } + }, + { + grapheme: 'a', + features: { + sonorant: true, + back: true, + low: true, + high: false, + rounded: false + } + } +]) + +const getRule = () => ({ + environment: { + pre: [ { sonorant: true, nasal: true, occlusive: true, coronal: true } ], + position: [ { occlusive: true, nasal: false } ], + post: [] + }, + newFeatures: [ { occlusive: true, nasal: true } ] +}) + +const getResult = () => ([ + { + environment: { + pre: [ + { + sonorant: true, nasal: true, occlusive: true, coronal: true + } + ], + position: [ + {occlusive: true, nasal: false} + ], + post: [], + }, + newFeatures: [{occlusive: true, nasal: true}] + } +]); \ No newline at end of file