earley-sgf
Version:
Early algorithm used to parse SGF file
144 lines (143 loc) • 4.18 kB
JavaScript
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)); */
});