add latl parse for features

This commit is contained in:
Sorrel Bri 2020-03-03 01:20:06 -08:00
parent da45699c59
commit d3ebe61577
5 changed files with 508 additions and 32 deletions

View file

@ -80,9 +80,9 @@ export const addFeature = (state: stateType, action: featureAction): stateType =
}
export const deleteFeature = (state, action) => {
console.log('deleting')
const deletedFeature = action.value;
delete state.features[deletedFeature];
console.log(state)
return {...state}
const deletedFeature = state.features[action.value];
deletedFeature.positive.forEach(phone => delete phone.features[action.value])
deletedFeature.negative.forEach(phone => delete phone.features[action.value])
delete state.features[action.value];
return state
}

View file

@ -31,4 +31,17 @@ describe('Features', () => {
);
});
it('feature deletion returns new feature list', () => {
const action = {type: 'DELETE_FEATURE', value: 'occlusive'}
expect(stateReducer(state, action)).toEqual(
{...state,
features: {},
phones: {
a: {features: {}, grapheme: 'a'},
n: {features: {}, grapheme: 'n'}
}
}
)
})
});

View file

@ -1,3 +1,5 @@
import { stateReducer } from './reducer';
export const setLatl = (state, action) => {
let latl = action.value;
return {...state, latl, parseResults: ''};
@ -47,6 +49,14 @@ const parseLineBreak = (tree, token, index, tokens) => {
return tree;
}
}
case 'feature--plus': {
// tree[tree.length - 1].type === 'feature';
return tree;
}
case 'feature--minus': {
// tree[tree.length - 1].type === 'feature';
return tree;
}
default:
return tree;
}
@ -112,6 +122,32 @@ const parseReferent = (tree, token, index, tokens) => {
case 'ruleSet': {
return [...tree, { type: 'rule', value: token.value }]
}
case 'feature': {
if (!lastNode.value) {
tree[tree.length - 1].value = token.value;
return tree;
}
}
case 'feature--plus': {
if (lastNode.value) {
lastNode.positivePhones = [...lastNode.positivePhones, token.value ]
}
else {
lastNode.value = token.value;
}
tree[tree.length - 1] = lastNode;
return [...tree]
}
case 'feature--minus': {
if (lastNode.value) {
lastNode.negativePhones = [...lastNode.negativePhones, token.value ]
}
else {
lastNode.value = token.value;
}
tree[tree.length - 1] = lastNode;
return [...tree]
}
default:
return [...tree, `unexpected referent ${token.value}`]
}
@ -131,17 +167,26 @@ const parsePhone = (tree, token, index, tokens) => {
const parseOpenBracket = (tree, token, index, tokens) => {
const lastNode = tree[tree.length - 1];
switch (lastNode.type) {
case 'epoch':
return [...tree, {type: 'rule', value: token.value}]
case 'rule':
tree[tree.length - 1] = {...lastNode, value: lastNode.value + token.value }
return tree;
case 'ruleSet':
return [...tree, {type: 'rule', value: token.value}]
default:
return [...tree, 'unexpected open bracket']
if (lastNode) {
switch (lastNode.type) {
case 'epoch':
return [...tree, {type: 'rule', value: token.value}]
case 'rule':
tree[tree.length - 1] = {...lastNode, value: lastNode.value + token.value }
return tree;
case 'ruleSet':
return [...tree, {type: 'rule', value: token.value}];
// case 'feature':
// return [{type: 'feature', positivePhones: [], negativePhones: []}];
case 'feature--plus':
return [...tree, {type: 'feature', positivePhones: [], negativePhones: []}];
case 'feature--minus':
return [...tree, {type: 'feature', positivePhones: [], negativePhones: []}];
default:
return [...tree, 'unexpected open bracket']
}
}
return [{type: 'feature', positivePhones: [], negativePhones: []}]
}
const parseCloseBracket = (tree, token, index, tokens) => {
@ -150,33 +195,83 @@ const parseCloseBracket = (tree, token, index, tokens) => {
case 'rule':
tree[tree.length - 1] = {...lastNode, value: lastNode.value + token.value }
return tree;
case 'feature--plus':
return tree;
case 'feature--minus':
return tree;
default:
return [...tree, 'unexpected close bracket']
}
}
const parsePositiveAssignment = (tree, token, index, tokens) => {
const lastNode = tree[tree.length - 1];
switch (lastNode.type) {
case 'feature':
tree[tree.length - 1].type = 'feature--plus'
return tree;
default:
return [...tree, 'unexpected positive assignment']
}
}
const parseNegativeAssignment = (tree, token, index, tokens) => {
const lastNode = tree[tree.length - 1];
switch (lastNode.type) {
case 'feature':
tree[tree.length - 1].type = 'feature--minus'
return tree;
case 'feature--plus':
tree[tree.length - 1].type = 'feature--minus';
return tree;
default:
return [...tree, 'unexpected negative assignment']
}
}
const parsePlus = (tree, token, index, tokens) => {
const lastNode = tree[tree.length - 1];
switch (lastNode.type) {
case 'rule':
tree[tree.length - 1] = {...lastNode, value: lastNode.value + token.value}
return tree;
case 'feature':
tree[tree.length - 1] = {...lastNode, type: 'feature--plus'}
return tree;
case 'feature--minus':
tree[tree.length - 1] = {...lastNode, type: 'feature--minus'}
return tree;
default:
return [...tree, 'unexpected plus']
}
}
const parseMinus = (tree, token, index, tokens) => {
const lastNode = tree[tree.length - 1];
switch (lastNode.type) {
case 'rule':
tree[tree.length - 1] = {...lastNode, value: lastNode.value + token.value}
return tree;
case 'feature':
tree[tree.length - 1] = {...lastNode, type: 'feature--minus'}
return tree;
default:
return [...tree, 'unexpected minus']
}
}
const parseEqual = (tree, token, index, tokens) => {
const lastNode = tree[tree.length - 1];
switch (lastNode.type) {
case 'feature--plus':
return tree;
case 'feature--minus':
return tree;
default:
return [...tree, 'unexpected equal'];
}
}
const parseGreaterThan = (tree, token, index, tokens) => {
const lastNode = tree[tree.length - 1];
switch (lastNode.type) {
@ -194,6 +289,10 @@ const parseSlash = (tree, token, index, tokens) => {
case 'rule':
tree[tree.length - 1] = {...lastNode, value: lastNode.value + token.value}
return tree;
case 'feature--plus':
return tree;
case 'feature--minus':
return tree;
default:
return [...tree, 'unexpected slash']
}
@ -254,10 +353,16 @@ const generateNode = (tree, token, index, tokens) => {
return parseOpenBracket(tree, token, index, tokens);
case 'closeBracket':
return parseCloseBracket(tree, token, index, tokens);
case 'positiveAssignment':
return parsePositiveAssignment(tree, token, index, tokens);
case 'negativeAssignment':
return parseNegativeAssignment(tree, token, index, tokens);
case 'plus':
return parsePlus(tree, token, index, tokens);
case 'minus':
return parseMinus(tree, token, index, tokens);
case 'equal':
return parseEqual(tree, token, index, tokens);
case 'greaterThan':
return parseGreaterThan(tree, token, index, tokens);
case 'slash':
@ -279,7 +384,30 @@ const connectNodes = (tree, node, index, nodes) => {
switch (node.type) {
case 'epoch':
delete node.type;
return {...tree, epochs: [...tree.epochs, {...node, index: tree.epochs.length} ]}
return {...tree, epochs: [...tree.epochs, {...node, index: tree.epochs.length } ] }
case 'feature':
node.feature = node.value;
delete node.value;
delete node.type;
return {...tree, features: [...tree.features, {...node } ] }
case 'feature--minus':
node.feature = node.value;
delete node.value;
delete node.type;
if (tree.features.length && tree.features[tree.features.length - 1].feature === node.feature) {
tree.features[tree.features.length - 1].negativePhones = node.negativePhones
return tree;
}
return {...tree, features: [...tree.features, {...node} ] }
case 'feature--plus':
delete node.type;
node.feature = node.value;
delete node.value;
if (tree.features.length && tree.features[tree.features.length - 1].feature === node.feature) {
tree.features[tree.features.length - 1].positivePhones = node.positivePhones
return tree;
}
return {...tree, features: [...tree.features, {...node} ] }
default:
return tree;
}
@ -288,11 +416,18 @@ const connectNodes = (tree, node, index, nodes) => {
export const buildTree = tokens => {
const bareTree = {
epochs: [],
features: [],
phones: []
}
const nodes = tokens.reduce(addToken, []);
// return nodes
const tree = nodes.reduce(connectNodes, bareTree);
return tree;
const filterProps = Object.entries(tree).filter(([key, value]) => !value.length)
.map(([key, value]) => key)
return filterProps.reduce((tree, badProp) => {
delete tree[badProp];
return tree;
}, tree);
}
export const generateAST = latl => {
@ -307,6 +442,16 @@ export const parseLatl = (state, action) => {
try {
const latl = state.latl;
const AST = generateAST(latl);
const features = AST.features;
if (features) {
if (state.features) {
state = Object.keys(state.features).reduce((state, feature) => {
return stateReducer(state, {type: 'DELETE_FEATURE', value: feature})
}, state)
}
state = features.reduce((state, feature) => stateReducer(state, {type:'ADD_FEATURE', value: feature}), state);
}
delete AST.features;
Object.entries(AST).forEach(([key, value]) => state[key] = value);
return { ...state, parseResults: 'latl parsed successfully', results:[] }
}

View file

@ -18,10 +18,10 @@ describe('LATL', () => {
expect(tokens).toStrictEqual(tokenizedEpoch)
});
// it('returns tokens from well-formed latl feature definition', () => {
// const tokens = tokenize(featureDefinitionLatl);
// expect(tokens).toStrictEqual(tokenizedFeature);
// });
it('returns tokens from well-formed latl feature definition', () => {
const tokens = tokenize(featureDefinitionLatl);
expect(tokens).toStrictEqual(tokenizedFeature);
});
// it('returns tokens from well-formed latl lexicon definition', () => {
// const tokens = tokenize(lexiconDefinitionLatl);
@ -41,6 +41,22 @@ describe('LATL', () => {
expect(tree).toStrictEqual(treeEpoch);
})
it('returns AST from well-formed feature tokens', () => {
const tree = buildTree(tokenizedFeature);
expect(tree).toStrictEqual(treeFeature);
})
it('parse returns state from well-formed feature latl', () => {
const state = initState();
const setAction = {
type: 'SET_LATL',
value: featureDefinitionLatl
}
const latlState = stateReducer(state, setAction);
const parseState = parseLatl(latlState, {});
expect(parseState).toStrictEqual(featureState)
})
it('returns run from well-formed epoch latl', () => {
const state = initState();
const setAction = {
@ -116,25 +132,327 @@ const epochState = {
const featureDefinitionLatl = `
[+ PLOSIVE] = kp/p/b/d/t/g/k
[- PLOSIVE] = m/n/s/z
[SONORANT
[SONORANT
+= m/n
-= s/z/kp/p/b/d/t/g/k
]
`
const tokenizedFeature = [
{ type: "openBracket", value: "[" }, { type: "plus", value: "+" }, { type: "whiteSpace", value: "" }, { type: "referent", value: "PLOSIVE" }, { type: "closeBracket", value: "]" }, { type: "whiteSpace", value: "" },
{ type: "equal", value: "=" }, { type: "whiteSpace", value: "" }, { type: "referent", value: "kp" }, { type: "slash", value: "/" }, { type: "whiteSpace", value: "" }, { type: "referent", value: "p" }, { type: "slash", value: "/" }, { type: "whiteSpace", value: "" }, { type: "referent", value: "b" }, { type: "slash", value: "/" }, { type: "whiteSpace", value: "" }, { type: "referent", value: "d" }, { type: "slash", value: "/" }, { type: "whiteSpace", value: "" }, { type: "referent", value: "t" }, { type: "slash", value: "/" }, { type: "whiteSpace", value: "" }, { type: "referent", value: "g" }, { type: "slash", value: "/" }, { type: "whiteSpace", value: "" }, { type: "referent", value: "k" }, { type: "whiteSpace", value: "" }, { type: 'lineBreak', value: '' },
{ type: "openBracket", value: "[" }, { type: "minus", value: "-" }, { type: "whiteSpace", value: "" }, { type: "referent", value: "PLOSIVE" }, { type: "closeBracket", value: "]" }, { type: "whiteSpace", value: "" },
{type: "openBracket", value: "[" }, { type: "plus", value: "+" }, { type: "whiteSpace", value: "" }, { type: "referent", value: "PLOSIVE" }, { type: "closeBracket", value: "]" }, { type: "whiteSpace", value: "" },
{ type: "equal", value: "=" }, { type: "whiteSpace", value: "" }, { type: "referent", value: "kp" }, { type: "slash", value: "/" }, { type: "referent", value: "p" }, { type: "slash", value: "/" }, { type: "referent", value: "b" }, { type: "slash", value: "/" }, { type: "referent", value: "d" }, { type: "slash", value: "/" }, { type: "referent", value: "t" }, { type: "slash", value: "/" }, { type: "referent", value: "g" }, { type: "slash", value: "/" }, { type: "referent", value: "k" }, { type: 'lineBreak', value: '' },
{type: "openBracket", value: "[" }, { type: "minus", value: "-" }, { type: "whiteSpace", value: "" }, { type: "referent", value: "PLOSIVE" }, { type: "closeBracket", value: "]" }, { type: "whiteSpace", value: "" },
{ type: "equal", value: "=" }, { type: "whiteSpace", value: "" }, { type: "referent", value: "m" }, { type: "slash", value: "/" }, { type: "referent", value: "n" }, { type: "slash", value: "/" }, { type: "referent", value: "s" }, { type: "slash", value: "/" }, { type: "referent", value: "z" }, { type: 'lineBreak', value: '' },
{ type: "openBracket", value: "[" }, { type: "referent", value: "SONORANT" }, { type: 'lineBreak', value: '' },
{ type: "whiteSpace", value: "" },{ type: "whiteSpace", value: "" },{ type: "positiveAssignment", value: "+=" }, { type: "whiteSpace", value: "" },
{ type: "referent", value: "m" }, { type: "slash", value: "/" }, { type: "whiteSpace", value: "" }, { type: "referent", value: "n" }, { type: 'lineBreak', value: '' },
{ type: "whiteSpace", value: "" }, { type: "whiteSpace", value: "" },{ type: "negativeAssignment", value: "-=" }, { type: "whiteSpace", value: "" },
{ type: "referent", value: "s" }, { type: "slash", value: "/" }, { type: "referent", value: "z" }, { type: "slash", value: "/" }, { type: "referent", value: "kp" }, { type: "slash", value: "/" }, { type: "referent", value: "p" }, { type: "slash", value: "/" }, { type: "referent", value: "b" }, { type: "slash", value: "/" }, { type: "referent", value: "d" }, { type: "slash", value: "/" }, { type: "referent", value: "t" }, { type: "slash", value: "/" }, { type: "referent", value: "g" }, { type: "slash", value: "/" }, { type: "referent", value: "k" }, { type: "whiteSpace", value: "" },{ type: 'lineBreak', value: '' },
{type: "openBracket", value: "[" }, { type: "referent", value: "SONORANT" }, { type: 'lineBreak', value: '' },
{ type: "whiteSpace", value: "" }, { type: "positiveAssignment", value: "+=" }, { type: "whiteSpace", value: "" },
{ type: "referent", value: "m" }, { type: "slash", value: "/" }, { type: "referent", value: "n" }, { type: 'lineBreak', value: '' },
{ type: "whiteSpace", value: "" }, { type: "negativeAssignment", value: "-=" }, { type: "whiteSpace", value: "" },
{ type: "referent", value: "s" }, { type: "slash", value: "/" }, { type: "referent", value: "z" }, { type: "slash", value: "/" }, { type: "referent", value: "kp" }, { type: "slash", value: "/" }, { type: "referent", value: "p" }, { type: "slash", value: "/" }, { type: "referent", value: "b" }, { type: "slash", value: "/" }, { type: "referent", value: "d" }, { type: "slash", value: "/" }, { type: "referent", value: "t" }, { type: "slash", value: "/" }, { type: "referent", value: "g" }, { type: "slash", value: "/" }, { type: "referent", value: "k" }, { type: 'lineBreak', value: '' },
{ type: "closeBracket", value: "]" },
]
const treeFeature = { features: [
{
feature: 'PLOSIVE',
positivePhones: ['kp', 'p', 'b', 'd', 't', 'g', 'k'],
negativePhones: ['m', 'n', 's', 'z']
},
{
feature: 'SONORANT',
positivePhones: ['m', 'n'],
negativePhones: ['s' ,'z' ,'kp' ,'p' ,'b' ,'d' ,'t' ,'g' ,'k']
}
]}
const featureState = {
...initState(),
features: {
PLOSIVE: {
negative: [
{
features: {
PLOSIVE: false,
SONORANT: true,
},
grapheme: "m",
},
{
features: {
PLOSIVE: false,
SONORANT: true,
},
grapheme: "n",
},
{
features: {
PLOSIVE: false,
SONORANT: false,
},
grapheme: "s",
},
{
features: {
PLOSIVE: false,
SONORANT: false,
},
grapheme: "z",
},
],
positive: [
{
features: {
PLOSIVE: true,
},
grapheme: "kp",
},
{
features: {
PLOSIVE: true,
SONORANT: false,
},
grapheme: "p",
},
{
features: {
PLOSIVE: true,
SONORANT: false,
},
grapheme: "b",
},
{
features: {
PLOSIVE: true,
SONORANT: false,
},
grapheme: "d",
},
{
features: {
PLOSIVE: true,
SONORANT: false,
},
grapheme: "t",
ʰ: {
features: {},
grapheme: "tʰ",
},
},
{
features: {
PLOSIVE: true,
SONORANT: false,
},
grapheme: "g",
},
{
features: {
PLOSIVE: true,
SONORANT: false,
},
grapheme: "k",
p: {
features: {
SONORANT: false,
},
grapheme: "kp",
},
},
],
},
SONORANT: {
negative: [
{
features: {
PLOSIVE: false,
SONORANT: false,
},
grapheme: "s",
},
{
features: {
PLOSIVE: false,
SONORANT: false,
},
grapheme: "z",
},
{
features: {
SONORANT: false,
},
grapheme: "kp",
},
{
features: {
PLOSIVE: true,
SONORANT: false,
},
grapheme: "p",
},
{
features: {
PLOSIVE: true,
SONORANT: false,
},
grapheme: "b",
},
{
features: {
PLOSIVE: true,
SONORANT: false,
},
grapheme: "d",
},
{
features: {
PLOSIVE: true,
SONORANT: false,
},
grapheme: "t",
ʰ: {
features: {},
grapheme: "tʰ",
},
},
{
features: {
PLOSIVE: true,
SONORANT: false,
},
grapheme: "g",
},
{
features: {
PLOSIVE: true,
SONORANT: false,
},
grapheme: "k",
p: {
features: {
SONORANT: false,
},
grapheme: "kp",
},
},
],
positive: [
{
features: {
PLOSIVE: false,
SONORANT: true,
},
grapheme: "m",
},
{
features: {
PLOSIVE: false,
SONORANT: true,
},
grapheme: "n",
},
],
}, },
parseResults: 'latl parsed successfully',
latl: featureDefinitionLatl,
phones: {
a: {
features: {},
grapheme: "a",
},
b: {
features: {
PLOSIVE: true,
SONORANT: false,
},
grapheme: "b",
},
d: {
features: {
PLOSIVE: true,
SONORANT: false,
},
grapheme: "d",
},
g: {
features: {
PLOSIVE: true,
SONORANT: false,
},
grapheme: "g",
},
k: {
features: {
PLOSIVE: true,
SONORANT: false,
},
grapheme: "k",
p: {
features: {
SONORANT: false,
},
grapheme: "kp",
},
},
m: {
features: {
PLOSIVE: false,
SONORANT: true,
},
grapheme: "m",
},
n: {
features: {
PLOSIVE: false,
SONORANT: true,
},
grapheme: "n",
},
p: {
features: {
PLOSIVE: true,
SONORANT: false,
},
grapheme: "p",
},
s: {
features: {
PLOSIVE: false,
SONORANT: false,
},
grapheme: "s",
},
t: {
features: {
PLOSIVE: true,
SONORANT: false,
},
grapheme: "t",
ʰ: {
features: {},
grapheme: "tʰ",
},
},
u: {
features: {},
grapheme: "u",
},
z: {
features: {
PLOSIVE: false,
SONORANT: false,
},
grapheme: "z",
},
ə: {
features: {},
grapheme: "ə",
},
ɯ: {
features: {},
grapheme: "ɯ",
},
}
}
const lexiconDefinitionLatl = `
/PROTO
kpn

View file

@ -211,7 +211,7 @@ const transformLexemeInitial = (newLexeme, pre, post, position, phoneme, index,
if (!isEnvironmentBoundByRule(lexemeBundle.slice(index + position.length, index + post.length + position.length), post)) return [...newLexeme, phoneme];
const newPhoneme = transformPhoneme(phoneme, newFeatures[0], features);
// if deletion occurs
if (!newPhoneme.grapheme) return [ ...newLexeme] ;
if (!newPhoneme || !newPhoneme.grapheme) return [ ...newLexeme] ;
return [...newLexeme, newPhoneme];
}