woving
Version:
Parses woving patterns. Woving is a domain specific language for generating patterns and instructions for weaving drafts.
443 lines (367 loc) • 16.6 kB
JavaScript
const nearley = require("nearley");
const woving = require("./woving.js");
const evaluateString = require('./woving-evaluator.js');
// Test configuration
const RANDOM_TEST_ITERATIONS = 50;
// Create a Parser object from our grammar.
const createParser = (input) => new nearley.Parser(nearley.Grammar.fromCompiled(woving)).feed(input).results[0];
const expectEvaluationOf = (input) => {
const parse = createParser(input);
const expr = evaluateString(parse);
return expect(expr);
}
const expectProbabilisticEvaluationOf = (input, regex, iterations = RANDOM_TEST_ITERATIONS) => {
for (let i = 0; i < iterations; i++) {
expectEvaluationOf(input).toMatch(regex);
}
};
describe('Parses symbols correctly when used in isolation.', () => {
test('parses single number as identity', () => {
expectEvaluationOf('1').toBe('1');
});
test('parses number sequence as identity', () => {
expectEvaluationOf('1234').toBe('1234');
});
test('parses basic repeat correctly', () => {
expectEvaluationOf('12:2').toBe('1212');
});
test('parses basic group correctly', () => {
expectEvaluationOf('[1234]').toBe('1234');
});
test('parses basic symmetry operator \'|\' usage correctly', () => {
expectEvaluationOf('1234|').toBe('12344321');
});
test('parses basic point symmetry operator \'!\' usage correctly', () => {
expectEvaluationOf('1234!').toBe('1234321');
});
test('parses step array group /.../ correctly', () => {
expectEvaluationOf('/414241/').toBe('43212343234321');
});
});
describe('Precedence is correct between:', () => {
test('postfix and binary operators; Binary ops have greater precendence.', () => {
expectEvaluationOf('1:2!')
.toBe('111');
});
test('binary operators and sequences; Sequences have greater precedence.', () => {
expectEvaluationOf('12:2')
.toBe('1212');
});
test('groups and operators; Groups have greater precedence.', () => {
// group [1|] is evaluated first to be '11'
// then 11:2 is evaluated to 11 repeated twice, '1111'
expectEvaluationOf('[1|]:2')
.toBe('1111');
}); test('subgroups and groups; Subgroups have greater precedence.', () => {
// subgroup of [1:2] evalutes to '11'
// then 1:11 is evaluated to 1 repeated 0x11 (17 in decimal) times
expectEvaluationOf('[1:[1:2]]')
.toBe('11111111111111111');
});
});
describe('Real examples: ', () => {
test('example 1, treadling of "Stars of Bethlehem"', () => {
expectEvaluationOf('[2341 2:3 3:3 4:3 1:4 2341]|')
.toBe('234122233344411112341143211114443332221432');
});
test('example 2, threading of "Stars of Bethlehem"', () => {
expectEvaluationOf('/14//12132434/14/14/1!')
.toBe('12341212323434141234143214143432321214321');
});
test('example 2 symmetried twice', () => {
expectEvaluationOf('[/14//12132434/14/14/1!!]')
.toBe('123412123234341412341432141434323212143212341212323434141234143214143432321214321');
});
test('example 3, threading of "Diadem", multiple expressions in groups', () => {
expectEvaluationOf('[/14/:2 1232! 434! 12!]!')
.toBe('123412341232321434341212143434123232143214321')
})
test('example 4, threading of "Rings and Chains"', () => {
expectEvaluationOf('[/41/4! 14! /43423121/ 41214! 12]!')
.toBe('43214123414143432321214121412141214121412141212323434141432141234')
})
});
describe('Handles ? operator correctly:', () => {
test('parses single ? operator', () => {
expectProbabilisticEvaluationOf('1?', /^(1|)$/);
});
test('? operator with sequences', () => {
expectProbabilisticEvaluationOf('123?', /^(123|)$/);
});
test('? operator with groups', () => {
expectProbabilisticEvaluationOf('[123]?', /^(123|)$/);
});
test('? operator with step arrays', () => {
expectProbabilisticEvaluationOf('/12/?', /^(12|)$/);
});
test('multiple ? operators', () => {
expectProbabilisticEvaluationOf('1? 2?', /^(12|1|2|)$/);
});
test('? operator with other postfix operators', () => {
expectProbabilisticEvaluationOf('123?!', /^(12321|1|)$/);
});
test('? operator followed by ! operator', () => {
expectProbabilisticEvaluationOf('123!?', /^(12321|)$/);
});
test('? operator in complex expressions', () => {
expectProbabilisticEvaluationOf('[1? 2?]!', /^(121|1|2|)$/);
});
test('statistical distribution test', () => {
let keepCount = 0;
let dropCount = 0;
const iterations = 1000;
for (let i = 0; i < iterations; i++) {
const result = createParser('1?');
const evaluated = evaluateString(result);
if (evaluated === '1') {
keepCount++;
} else if (evaluated === '') {
dropCount++;
}
}
// Should be roughly 50/50 distribution (allowing for 20% variance)
const keepRatio = keepCount / iterations;
expect(keepRatio).toBeGreaterThan(0.3);
expect(keepRatio).toBeLessThan(0.7);
expect(keepCount + dropCount).toBe(iterations);
}); test('? operator precedence with binary operators', () => {
// 1:2? should be (1:2)? not 1:(2?)
expectProbabilisticEvaluationOf('1:2?', /^(11|)$/);
});
});
describe('Handles joins correctly:', () => {
test('single space', () => {
expectEvaluationOf('1 2:2').toBe('122');
});
test('single plus', () => {
expectEvaluationOf('1+2:2').toBe('122');
});
test('multi space expression', () => {
expectEvaluationOf('1:4 2:4 3:4 4:4').toBe('1111222233334444');
});
test('mixed multi space expression', () => {
expectEvaluationOf('1:4+2:4 3:4+4:4').toBe('1111222233334444');
});
});
describe('Handles multi-operator expressions:', () => {
test('parses double "!" operator', () => {
expectEvaluationOf('123!!').toBe('123212321');
});
test('parses triple "!" operator', () => {
expectEvaluationOf('123!!!').toBe('12321232123212321');
});
test('parses step-array with double "!" operator', () => {
expectEvaluationOf('/14/!!').toBe('1234321234321');
});
test('parses step-array followed by number with double "!" operator', () => {
expectEvaluationOf('/14/1!!').toBe('12341432123414321');
});
test('parses nested groups with postfixes', () => {
expectEvaluationOf('[1[23]4!]').toBe('1234321');
});
test('parses step-array and postfix operators within groups correctly', () => {
expectEvaluationOf('[/2/ 1!]!')
.toBe('212');
})
});
describe('Handles various edge cases:', () => {
test('parses empty string as valid', () => {
expectEvaluationOf('').toBe('');
});
test('parses simple non-step step array group /.../ correctly', () => {
expectEvaluationOf('/11/').toBe('11');
});
test('parses multi-expressions', () => {
expectEvaluationOf('[1][2]').toBe('12');
});
test('parses redundant nested expressions', () => {
expectEvaluationOf('[[1234]]').toBe('1234');
});
test('parses non-redundant nested expressions', () => {
expectEvaluationOf('[1[23]4]').toBe('1234');
});
test('doesn\'t break on high repeat patterns', () => {
expectEvaluationOf('1:111').toBe('1');
})
});
describe('Error handling and malformed input:', () => {
test('throws error for invalid numbers', () => {
expect(() => createParser('0')).toThrow(); // 0 is still invalid
expect(() => createParser('g')).toThrow(); // g is invalid (beyond f)
expect(() => createParser('z')).toThrow(); // z is invalid
});
test('throws error for mismatched brackets', () => {
expect(() => createParser('1]')).toThrow();
expect(() => createParser('[1]2]')).toThrow();
});
test('throws error for invalid operator placement', () => {
expect(() => createParser(':2')).toThrow();
expect(() => createParser('!')).toThrow();
expect(() => createParser('|')).toThrow();
expect(() => createParser('?')).toThrow();
});
test('throws error for empty groups', () => {
expect(() => createParser('[]')).toThrow();
});
});
describe('Step array edge cases:', () => {
test('handles single character step arrays', () => {
expectEvaluationOf('/1/').toBe('1');
expectEvaluationOf('/2/').toBe('2');
expectEvaluationOf('/3/').toBe('3');
expectEvaluationOf('/4/').toBe('4');
});
test('handles duplicate numbers in step arrays', () => {
expectEvaluationOf('/11/').toBe('11');
expectEvaluationOf('/22/').toBe('22');
expectEvaluationOf('/111/').toBe('111');
});
test('handles descending step arrays', () => {
expectEvaluationOf('/41/').toBe('4321');
expectEvaluationOf('/421/').toBe('4321'); // Fixed expectation: step arrays create sequences, not repetitions
expectEvaluationOf('/4321/').toBe('4321');
}); test('handles mixed ascending/descending step arrays', () => {
expectEvaluationOf('/1413/').toBe('123432123');
expectEvaluationOf('/2142/').toBe('2123432'); // Corrected based on actual step array algorithm
});
test('handles step arrays with groups inside', () => {
expectEvaluationOf('/[12]/').toBe('12');
expectEvaluationOf('/[12]3/').toBe('123'); // Fixed: step arrays work on the result of groups
}); test('handles step arrays containing operators', () => {
// Test that step arrays can contain expressions with operators
expectEvaluationOf('/[1!]/').toBe('1');
expectEvaluationOf('/1 2/').toBe('12'); // Step array processes the join result
});
});
describe('Evaluator edge cases:', () => {
test('handles repeat with valid numbers only', () => {
// Grammar only allows 1-4, so test within valid range
expectEvaluationOf('1:1').toBe('1');
expectEvaluationOf('1:2').toBe('11');
expectEvaluationOf('1:3').toBe('111');
expectEvaluationOf('1:4').toBe('1111');
});
test('handles very large repeat values', () => {
// Should be clamped to 1 due to safety limit
expectEvaluationOf('1:444').toBe('1'); // 444 > 100, gets clamped
});
test('handles edge cases in repeat patterns', () => {
expectEvaluationOf('12:1').toBe('12');
expectEvaluationOf('1234:1').toBe('1234');
});
test('handles single character in point symmetry', () => {
expectEvaluationOf('1!').toBe('1');
expectEvaluationOf('2!').toBe('2');
});
test('handles complex nested expressions', () => {
expectEvaluationOf('[[[[1]]]]').toBe('1');
expectEvaluationOf('[[[1!]!]!]').toBe('1');
});
test('handles deeply nested step arrays', () => {
expectEvaluationOf('/[/12/]/').toBe('12');
expectEvaluationOf('/[/12/]3/').toBe('123'); // Fixed expectation
});
});
describe('Integration and parser edge cases:', () => {
test('handles single space joins correctly', () => {
expectEvaluationOf('1 2').toBe('12');
expectEvaluationOf('12 34').toBe('1234');
});
test('handles mixed join operators', () => {
expectEvaluationOf('1+2 3+4').toBe('1234');
expectEvaluationOf('1 2+3 4').toBe('1234');
});
test('handles operator precedence edge cases', () => {
expectEvaluationOf('1:2:3').toBe('111111'); // Left associative
expectEvaluationOf('1!|').toBe('11'); // Postfix operators right associative
expectEvaluationOf('1|!').toBe('111'); // Different order
}); test('handles complex real-world patterns', () => {
// Test actual complex patterns that work with the grammar
expectEvaluationOf('[1:2 /23/ 4!]:2').toBe('1123411234'); // Fixed expectation
expectEvaluationOf('/[12]! [34]!/').toBe('1212343'); // Corrected based on actual behavior
});
});
describe('Random operator edge cases:', () => {
test('? operator with complex nested expressions', () => {
expectProbabilisticEvaluationOf('[1? 2?]? 3?', /^(123|12|13|1|23|2|3|)$/);
});
test('? operator precedence with complex expressions', () => {
expectProbabilisticEvaluationOf('[1:2]?!', /^(111|1|)$/);
expectProbabilisticEvaluationOf('/12/?|', /^(1221|)$/); // Fixed expected pattern
});
});
describe('Performance and stress tests:', () => {
test('handles moderately complex expressions efficiently', () => {
const start = Date.now();
expectEvaluationOf('[1234! 4321| /1234/ /4321/]!');
const duration = Date.now() - start;
expect(duration).toBeLessThan(100); // Should complete in under 100ms
});
test('handles multiple nested groups', () => {
expectEvaluationOf('[[1][2]][[3][4]]').toBe('1234');
expectEvaluationOf('[[[1]]][[[2]]]').toBe('12');
});
test('handles long sequences correctly', () => {
expectEvaluationOf('1234123412341234').toBe('1234123412341234');
// Fixed expectation for step array behavior
expectEvaluationOf('/1234123412341234/').toBe('1234321234321234321234');
});
});
describe('Hex digit support:', () => {
test('parses hex digits 5-9', () => {
expectEvaluationOf('5').toBe('5');
expectEvaluationOf('6').toBe('6');
expectEvaluationOf('7').toBe('7');
expectEvaluationOf('8').toBe('8');
expectEvaluationOf('9').toBe('9');
});
test('parses hex letters a-f', () => {
expectEvaluationOf('a').toBe('a');
expectEvaluationOf('b').toBe('b');
expectEvaluationOf('c').toBe('c');
expectEvaluationOf('d').toBe('d');
expectEvaluationOf('e').toBe('e');
expectEvaluationOf('f').toBe('f');
});
test('parses hex sequences', () => {
expectEvaluationOf('5a').toBe('5a');
expectEvaluationOf('abc').toBe('abc');
expectEvaluationOf('9ef').toBe('9ef');
expectEvaluationOf('123456789abcdef').toBe('123456789abcdef');
});
test('hex step arrays work correctly', () => {
expectEvaluationOf('/5a/').toBe('56789a');
expectEvaluationOf('/af/').toBe('abcdef');
expectEvaluationOf('/a1/').toBe('a987654321');
expectEvaluationOf('/19a/').toBe('123456789a');
});
test('hex repeat operations work correctly', () => {
expectEvaluationOf('5:a').toBe('5555555555'); // 5 repeated 10 times (a = 10)
expectEvaluationOf('a:5').toBe('aaaaa'); // a repeated 5 times
expectEvaluationOf('7:c').toBe('777777777777'); // 7 repeated 12 times (c = 12)
});
test('hex with symmetry operators', () => {
expectEvaluationOf('5a|').toBe('5aa5');
expectEvaluationOf('abc|').toBe('abccba');
expectEvaluationOf('5a!').toBe('5a5');
expectEvaluationOf('abc!').toBe('abcba');
});
test('hex with random operator', () => {
expectProbabilisticEvaluationOf('5?', /^(5|)$/);
expectProbabilisticEvaluationOf('a?', /^(a|)$/);
expectProbabilisticEvaluationOf('abc?', /^(abc|)$/);
}); test('mixed hex and traditional digits', () => {
expectEvaluationOf('123abc').toBe('123abc');
expectEvaluationOf('/14af/').toBe('123456789abcdef'); // Fixed: step array fills the gaps
expectEvaluationOf('[1a 2b]!').toBe('1a2b2a1');
}); test('complex hex expressions', () => {
expectEvaluationOf('[a:2 /5a/ b!]:2').toBe('aa56789abaa56789ab');
expectEvaluationOf('/[ab]! [cd]!/').toBe('ababcdc'); // Fixed: step array result
});
test('hex step arrays with edge cases', () => {
expectEvaluationOf('/f1/').toBe('fedcba987654321');
expectEvaluationOf('/1f/').toBe('123456789abcdef');
expectEvaluationOf('/a5/').toBe('a98765');
expectEvaluationOf('/5f/').toBe('56789abcdef');
});
});