UNPKG

earley-sgf

Version:

Early algorithm used to parse SGF file

144 lines (143 loc) 4.18 kB
import { assert, test } from 'vitest'; import { Symbol, Rule, Grammar, Earley } from './earley.js'; function display(tree) { if ('children' in tree) { let a = tree.token; tree.children.forEach(child => a += (" " + display(child))); return "( " + a + " )"; } else { return "'" + tree.text + "'"; } } /* * Creates a terminal. */ function t(token) { return new Symbol(token, true); } /* * Creates a non-terminal. */ function nt(token) { return new Symbol(token, false); } /* * S -> b * S -> aS * sentence = aab */ test('simple grammar', () => { const S = new Symbol('S', false); const a = new Symbol('a', true); const b = new Symbol('b', true); const rule1 = new Rule(S, [b]); const rule2 = new Rule(S, [a, S]); const grammar = new Grammar([rule1, rule2], S); const sentence = ['a', 'a', 'b'].map(x => { return { token: x, text: x }; }); const tree = new Earley(grammar).parse(sentence); assert(tree !== null, "Failed parsed"); assert(display(tree) === "( S 'a' ( S 'a' ( S 'b' ) ) )"); }); /* * Will not parse. * S -> aS * sentence = aa OR ab */ test('no parse tree', () => { const S = new Symbol('S', false); const a = new Symbol('a', true); const rule2 = new Rule(S, [a, S]); const grammar = new Grammar([rule2], S); const sentence1 = ['a', 'a'].map(x => { return { token: x, text: x }; }); const tree1 = new Earley(grammar).parse(sentence1); assert(tree1 === null, "Invalid parse"); const sentence2 = ['a', 'b'].map(x => { return { token: x, text: x }; }); const tree2 = new Earley(grammar).parse(sentence2); assert(tree2 === null, "Invalid parse"); }); /* * S -> Ab * A -> aA * A -> * sentence = aab */ test('nullable rule', () => { const S = new Symbol('S', false); const A = new Symbol('A', false); const a = new Symbol('a', true); const b = new Symbol('b', true); const rule1 = new Rule(S, [A, b]); const rule2 = new Rule(A, [a, A]); const rule3 = new Rule(A, []); const grammar = new Grammar([rule1, rule2, rule3], S); const sentence = ['a', 'a', 'b'] .map(x => { return { token: x, text: x }; }); const tree = new Earley(grammar).parse(sentence); assert(tree !== null, "Failed parsed"); assert(display(tree) === "( S ( A 'a' ( A 'a' ( A ) ) ) 'b' )"); }); /* * S -> Ab * A -> Aa * A -> * sentence = aab */ test('left recursion', () => { const S = new Symbol('S', false); const A = new Symbol('A', false); const a = new Symbol('a', true); const b = new Symbol('b', true); const rule1 = new Rule(S, [A, b]); const rule2 = new Rule(A, [A, a]); const rule3 = new Rule(A, []); const grammar = new Grammar([rule1, rule2, rule3], S); const sentence = ['a', 'a', 'b'] .map(x => { return { token: x, text: x }; }); const tree = new Earley(grammar).parse(sentence); assert(tree !== null, "Failed parsed"); assert(display(tree) === "( S ( A ( A ( A ) 'a' ) 'a' ) 'b' )"); }); /* * tree -> '(' node nodes trees ')' * nodes -> node nodes * nodes -> * trees -> tree trees * trees -> * node -> ';' props * props -> prop props * props -> * prop -> 'move' * prop -> 'komi' */ test('simplified SGF', () => { const trees = nt('trees'); const tree = nt('tree'); const nodes = nt('nodes'); const node = nt('node'); const props = nt('props'); const prop = nt('prop'); const rules = [ new Rule(node, [t(';'), props]), new Rule(nodes, []), new Rule(nodes, [node, nodes]), new Rule(prop, [t('komi')]), new Rule(prop, [t('move')]), new Rule(props, []), new Rule(props, [prop, props]), new Rule(tree, [t('('), node, nodes, trees, t(')')]), new Rule(trees, []), new Rule(trees, [tree, trees]), ]; const sentence = ['(', ';', 'komi', ';', 'move', ')'] .map(x => { return { token: x, text: x }; }); const result = new Earley(new Grammar(rules, tree)).parse(sentence); assert(result !== null, "Failed parsed"); /* console.log(display(result)); */ });