ll1-validator
Version:
A tool that checks if a given grammar is LL(1).
383 lines (364 loc) • 9.7 kB
JavaScript
import test from 'ava';
const parser = require('../src/parser.js');
const errors = require('../src/errors');
const warnings = require('../src/warnings');
test('simple case', t => {
const input = `S -> a S;`
t.deepEqual(parser.parseString(input), {
grammar: {
'S': [
[
{ type: parser.TERMINAL, value: 'a' },
{ type: parser.NONTERMINAL, value: 'S' }
]
],
},
startSymbol: 'S',
rulesNumber: 1,
terminals: ['a'],
nonTerminals: ['S'],
warnings: [],
});
});
test('simple case 2', t => {
const input = `S => a S;`
t.deepEqual(parser.parseString(input), {
grammar: {
'S': [
[
{ type: parser.TERMINAL, value: 'a' },
{ type: parser.NONTERMINAL, value: 'S' }
]
],
},
startSymbol: 'S',
rulesNumber: 1,
terminals: ['a'],
nonTerminals: ['S'],
warnings: [],
})
});
test('simple case with comments', t => {
const input = `
/* this is\n
a multiline\n
comment */\n
\n
S -> a S; // this is an inline comment`
t.deepEqual(parser.parseString(input), {
grammar: {
'S': [
[
{ type: parser.TERMINAL, value: 'a' },
{ type: parser.NONTERMINAL, value: 'S' }
]
],
},
startSymbol: 'S',
rulesNumber: 1,
terminals: ['a'],
nonTerminals: ['S'],
warnings: [],
})
});
test('complex case', t => {
const input = `
S -> a D;
S -> ;
D -> b;
`;
t.deepEqual(parser.parseString(input), {
grammar: {
'S': [
[
{ type: parser.TERMINAL, value: 'a' },
{ type: parser.NONTERMINAL, value: 'D' }
],
[]
],
'D': [
[
{ type: parser.TERMINAL, value: 'b' }
]
],
},
startSymbol: 'S',
rulesNumber: 3,
terminals: ['a', 'b'],
nonTerminals: ['D', 'S'],
warnings: [],
})
});
test('complex case 2', t => {
const input = `
s -> a d;
s -> ;
d -> b;
`;
t.deepEqual(parser.parseString(input), {
grammar: {
's': [
[
{ type: parser.TERMINAL, value: 'a' },
{ type: parser.NONTERMINAL, value: 'd' }
],
[]
],
'd': [
[
{ type: parser.TERMINAL, value: 'b' }
]
],
},
startSymbol: 's',
rulesNumber: 3,
terminals: ['a', 'b'],
nonTerminals: ['d', 's'],
warnings: [],
})
});
test('custom start symbol case', t => {
const input = `#start_symbol D; S -> a S; S -> ; D -> b S;`
t.deepEqual(parser.parseString(input), {
grammar: {
'S': [
[
{ type: parser.TERMINAL, value: 'a' },
{ type: parser.NONTERMINAL, value: 'S' }
],
[]
],
'D': [
[
{ type: parser.TERMINAL, value: 'b' },
{ type: parser.NONTERMINAL, value: 'S' }
],
]
},
startSymbol: 'D',
rulesNumber: 3,
terminals: ['a', 'b'],
nonTerminals: ['D', 'S'],
warnings: [],
});
});
test('lexer error case 1', t => {
const input = `#start_sybol D;\nS -> a S\n S -> ;\nD -> b S;\n`;
const f = () => parser.parseString(input);
t.throws(f, errors.LexerError);
});
test('parser error case 1', t => {
const input = `#start_symbol D;\nS -> a S\n S -> ;\nD -> b S;\n`;
const f = () => parser.parseString(input);
t.throws(f, errors.ParserError);
});
test('parser error case 2', t => {
const input = `S -> a S\n S -> ;\nD -> b S;\n /* unclosed comment`;
const f = () => parser.parseString(input);
t.throws(f, errors.ParserError);
});
test('parser error empty input', t => {
const input = ``;
const f = () => parser.parseString(input);
t.throws(f, errors.ParserError);
});
test('parser error no rules input', t => {
const input = `#start_symbol S;`;
const f = () => parser.parseString(input);
t.throws(f, errors.ParserError);
});
test('start symbol not found error', t => {
const input = `
#start_symbol P;
S -> a D;
S -> ;
D -> b;
`;
const f = () => parser.parseString(input);
t.throws(f, errors.StartSymbolNotFound);
});
test('duplicated rule case 1', t => {
const input = `
S -> a;
S -> a;
`;
t.deepEqual(parser.parseString(input), {
grammar: {
'S': [
[
{ type: parser.TERMINAL, value: 'a' }
],
[
{ type: parser.TERMINAL, value: 'a' }
],
],
},
startSymbol: 'S',
rulesNumber: 2,
terminals: ['a'],
nonTerminals: ['S'],
warnings: [new warnings.DuplicatedRuleWarning('S', 1, 0)],
});
});
test('duplicated rule case 2', t => {
const input = `
S -> a A;
S -> ;
S -> a A;
S -> a A;
A -> b;
`;
t.deepEqual(parser.parseString(input), {
grammar: {
'S': [
[
{ type: parser.TERMINAL, value: 'a' },
{ type: parser.NONTERMINAL, value: 'A' }
],
[],
[
{ type: parser.TERMINAL, value: 'a' },
{ type: parser.NONTERMINAL, value: 'A' }
],
[
{ type: parser.TERMINAL, value: 'a' },
{ type: parser.NONTERMINAL, value: 'A' }
],
],
'A': [
[{ type: parser.TERMINAL, value: 'b' }]
]
},
startSymbol: 'S',
rulesNumber: 5,
terminals: ['a', 'b'],
nonTerminals: ['A', 'S'],
warnings: [
new warnings.DuplicatedRuleWarning('S', 2, 0),
new warnings.DuplicatedRuleWarning('S', 3, 0),
],
});
});
test('duplicated rule case 3', t => {
const input = `
S -> A;
A -> a;
A -> a;
`;
t.deepEqual(parser.parseString(input), {
grammar: {
'S': [
[
{ type: parser.NONTERMINAL, value: 'A' }
],
],
'A': [
[{ type: parser.TERMINAL, value: 'a' }],
[{ type: parser.TERMINAL, value: 'a' }],
],
},
startSymbol: 'S',
rulesNumber: 3,
terminals: ['a'],
nonTerminals: ['A', 'S'],
warnings: [new warnings.DuplicatedRuleWarning('A', 1, 0)],
});
});
test('unused rules case 1', t => {
const input = `
S -> ;
A -> a;
`;
t.deepEqual(parser.parseString(input), {
grammar: {
'S': [[]],
'A': [
[{ type: parser.TERMINAL, value: 'a' }],
],
},
startSymbol: 'S',
rulesNumber: 2,
terminals: ['a'],
nonTerminals: ['A', 'S'],
warnings: [new warnings.UnreachableRuleWarning('A')],
});
});
test('unused rules case 2', t => {
const input = `
S -> A S;
A -> B C;
B -> b;
C -> c;
D -> d;
`;
t.deepEqual(parser.parseString(input), {
grammar: {
'S': [
[
{ type: parser.NONTERMINAL, value: 'A' },
{ type: parser.NONTERMINAL, value: 'S' }
]
],
'A': [
[
{ type: parser.NONTERMINAL, value: 'B' },
{ type: parser.NONTERMINAL, value: 'C' }
]
],
'B': [
[{ type: parser.TERMINAL, value: 'b' }],
],
'C': [
[{ type: parser.TERMINAL, value: 'c' }],
],
'D': [
[{ type: parser.TERMINAL, value: 'd' }],
],
},
startSymbol: 'S',
rulesNumber: 5,
terminals: ['b', 'c', 'd'],
nonTerminals: ['A', 'B', 'C', 'D', 'S'],
warnings: [new warnings.UnreachableRuleWarning('D')],
});
});
test('unused rules case 3', t => {
const input = `
S -> A B;
A -> ;
B -> C S;
B -> ;
C -> ;
D -> A;
E -> ;
`;
t.deepEqual(parser.parseString(input), {
grammar: {
'S': [
[
{ type: parser.NONTERMINAL, value: 'A' },
{ type: parser.NONTERMINAL, value: 'B' }
]
],
'A': [[]],
'B': [
[
{ type: parser.NONTERMINAL, value: 'C' },
{ type: parser.NONTERMINAL, value: 'S' }
],
[]
],
'C': [[]],
'D': [[{ type: parser.NONTERMINAL, value: 'A' }]],
'E': [[]]
},
startSymbol: 'S',
rulesNumber: 7,
terminals: [],
nonTerminals: ['A', 'B', 'C', 'D', 'E', 'S'],
warnings: [
new warnings.UnreachableRuleWarning('D'),
new warnings.UnreachableRuleWarning('E'),
],
});
});