UNPKG

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
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'); }); });