UNPKG

fruitsconfits

Version:

FruitsConfits - A well typed and sugared parser combinator framework for TypeScript/JavaScript.

446 lines (399 loc) 17.4 kB
// tslint:disable: no-eval // tslint:disable: interface-over-type-literal import { ParserInputWithCtx, ParserInput, StringParserInputWithCtx, StringParserInput, ParseError, parserInput, templateStringsParserInput, ParserFnSucceededResult, ParserFnFailedResult, ParserFnWithCtx, ParserFn, StringParserFnWithCtx, StringParserFn } from '../lib/types'; import { charSequence, charClass, charClassNot, charClassByNeedleFn, getStringParsers } from '../lib/string-parser'; import { getObjectParsers } from '../lib/object-parser'; import { parse as parseCsv } from '../examples/csv-parser'; import { parse as parseFormula, evaluate as evaluateFormula } from '../examples/formula-parser'; import { parse as parseJson } from '../examples/json-parser'; describe("foo", function() { it("foo1", function() { const x = parserInput('foo'); const y = parserInput([{type: 'aaa', value: 0}]); expect(1).toEqual(1); }); it("string-parser-1", function() { const parse: StringParserFnWithCtx<undefined, {ch: string}> = (input: StringParserInput) => { return (input.start < input.end ? { succeeded: true, next: { src: input.src, start: input.start + 1, end: input.end, context: input.context, }, tokens: [{ch: input.src[input.start]}], } : { succeeded: false, error: false, src: input.src, pos: input.start, message: `parse failed at ${input.start}: ${input.src.slice(input.start, 50)}`, }); }; const x = parse(parserInput('abcdefg')); expect(x.succeeded && x.tokens).toEqual([ {ch: 'a'}, ]); }); it("string-parser-2", function() { type Ctx = number; type Ast = {token: string}; const {seq, cls, notCls, clsFn, classes, cat, qty, repeat, zeroWidth, beginning, end, first, or, combine} = getStringParsers<Ctx, Ast>({ rawToToken: token => ({token}), concatTokens: tokens => [tokens.reduce((a, b) => ({token: a.token + b.token}))], }); const zw = zeroWidth(() => ({token: '@'})); const parse = combine( cat(seq('Hello'), seq(','), ), cat(zw, seq('Wor'), notCls('z', 'd'), first(cls('z', 'y'), cls('d', 'l')), ), cat(or(seq('?'), seq('!'), seq('!!'), qty(0, 10)(seq('!'))), ), end(), ); const x = parse(parserInput('Hello,World!!!!!!!!!!', 1)); if (x.succeeded) { // tslint:disable-next-line: no-unused-expression x.next.context; } expect(1).toEqual(1); }); it("csv-1", function() { const x = parseCsv('1, 2 2 ,3 3,4\n 5 , 6 , 7 , 8 \n\n" a , b , ""\n c ","",9,"",'); // console.log(JSON.stringify(x, void 0, 2)); expect(x).toEqual([ ['1', '2 2', '3 3', '4'], ['5', '6', '7', '8'], [''], [' a , b , "\n c ', '', '9', '', ''], ]); }); it("formula-1", function() { const code = `-77,+88,-99-5,+77+1, 10,(11),((12)),(99,98,97),[],{},[[1],2,3,{foo:1}],{bar:[4,{baz:5},[],{}]} ,'ccc\\'\\"\\\`\\v\\t\\b\\f\\r\\n\\u26F1\\u{1F608}\\xa9\\256' , 20 ,-one(),one(),11 + 13,2+3*4**2+5+2 +twice(5)+max(13,one(),twice(3),17,3)+(3) * 4 + 5 + (6,7,8)+5+10-2*11*(1)`; // 102 // 55+2 + 10 + 17 + 12 + 5 + 8 +5+10-22 // 109 +5+10-22 // 124-22 const one = () => 1; const twice = (a: number) => a * 2; const max = Math.max; const x = parseFormula(code); // console.log(JSON.stringify(x, void 0, 2)); const z = evaluateFormula(code); console.log(z); expect(z).toEqual(eval(code .replace(/# /g, '// ') .replace(/0555/, '0o555') .replace(/\\256/, '\\xae'))); }); it("formula-2", function() { const code = `-77,+88,-99-5,+77+1, 10,(11),((12)),(99,98,97),[],{},[[1],2,3,{foo:1}],{bar:[4,{baz:5},[],{}]} ,'ccc\\'\\"\\\`\\v\\t\\b\\f\\r\\n\\u26F1\\u{1F608}\\xa9\\256' , 20 ,-one(),one(),11 + 13,2+3*4**2+5+2 +twice(5)+max(13,one(),twice(3),17,3)+(3) * 4 + 5 + (6,7,8)+5+10-2*11*(1)+true?3:4+one()+(22+33)+44`; // 3 // 55+2 + 10 + 17 + 12 + 5 + 8 +5+10-22 +3 // 109 +5+10-22 +3 // 124-22 +3 const one = () => 1; const twice = (a: number) => a * 2; const max = Math.max; const x = parseFormula(code); console.log(JSON.stringify(x, void 0, 2)); const z = evaluateFormula(code); console.log(z); expect(z).toEqual(eval(code .replace(/# /g, '// ') .replace(/0555/, '0o555') .replace(/\\256/, '\\xae'))); }); it("formula-3", function() { const code = `[ 12+3*4-2, [1,2,3+5] ]`; const one = () => 1; const twice = (a: number) => a * 2; const max = Math.max; const x = parseFormula(code); // console.log(JSON.stringify(x, void 0, 2)); const z = evaluateFormula(code); // console.log(z); expect(z).toEqual(eval(code .replace(/# /g, '// ') .replace(/0555/, '0o555') .replace(/\\256/, '\\xae'))); }); it("formula-4-0", function() { const code = ` { "bar":[ (7,6,5), // === 5 // ((3)), // === 3 // ], }`; const one = () => 1; const twice = (a: number) => a * 2; const max = Math.max; const x = parseFormula(code); // console.log(JSON.stringify(x, void 0, 2)); const z = evaluateFormula(code); // console.log(z); expect(z).toEqual(eval( ('(' + code + ')') .replace(/# /g, '// ') .replace(/0555/, '0o555') .replace(/\\256/, '\\xae'))); }); it("formula-4", function() { const code = ` # qqqqqqqqqqqqqq { "foo":null, "bar":[ { "baz":[null,[1,2,[],3]]// ffffffff ff aaaaa /* ggggggggggggggggggg ffffff */ ,"zzz" : -5432, 'zzzz':+1-2+3-4, wwwwwww: {"p":7}, wwwww: {}, qwerty: -4321.342e-1, qqq:\`aa a\\ bbb\`, asdf :'ccc\\'\\"\\\`\\v\\t\\b\\f\\r\\n\\u26F1\\u{1F608}\\xa9\\256' },12, null,undefined,0x1,0b111,0o777,0555,-777,+4321.342, 5*3, // === 15 7 * 2 + 3 + 4, // === 21 7 + 2 * 3 + 4, // === 17 7 + 2 * 3 ** 4, // === 169 1 + (2 * 3) + 4, // === 11 (1 + 2) * 3, // === 9 2 * (3 + 4), // === 14 2 * (3 + 4) + 5, // === 19 (7,6,5), // === 5 (7), // === 7 ((3)), // === 3 +1-2+3-4, ], }`; const one = () => 1; const twice = (a: number) => a * 2; const max = Math.max; const x = parseFormula(code); // console.log(JSON.stringify(x, void 0, 2)); const z = evaluateFormula(code); // console.log(z); expect(z).toEqual(eval( ('(' + code + ')') .replace(/# /g, '// ') .replace(/0555/, '0o555') .replace(/\\256/, '\\xae'))); }); it("json-1", function() { const src = `1234`; const x = parseJson(src); // console.log(JSON.stringify(x, void 0, 2)); expect(x).toEqual(eval(src)); }); it("json-2", function() { const src = `{"foo":null,"bar":[],}`; const x = parseJson(src); // console.log(JSON.stringify(x, void 0, 2)); expect(x).toEqual(eval('(' + src + ')')); }); it("json-3", function() { const src = ` { "foo" : null , "bar" : [ null , 1 ,2, "aaaaaa", ] , } `; const x = parseJson(src); // console.log(JSON.stringify(x, void 0, 2)); expect(x).toEqual(eval('(' + src + ')')); }); it("json-4", function() { const src = ` # qqqqqqqqqqqqqq { "foo":null, "bar":[ { "baz":[null,[1,2,[],3]]// ffffffff ff aaaaa /* ggggggggggggggggggg ffffff */ ,"zzz" : -5432, 'zzzz':+1-2+3-4, wwwwwww: {"p":7}, wwwww: {}, qwerty: -4321.342e-1, qqq:\`aa a\\ bbb\`, asdf :'ccc\\'\\"\\\`\\v\\t\\b\\f\\r\\n\\u26F1\\u{1F608}\\xa9\\256' },12, null,undefined,0x1,0b111,0o777,0555,-777,+4321.342, 5*3, // === 15 7 * 2 + 3 + 4, // === 21 7 + 2 * 3 + 4, // === 17 7 + 2 * 3 ** 4, // === 169 1 + (2 * 3) + 4, // === 11 (1 + 2) * 3, // === 9 2 * (3 + 4), // === 14 2 * (3 + 4) + 5, // === 19 (7,6,5), // === 5 (7), // === 7 ((3)), // === 3 +1-2+3-4, ], }`; const x = parseJson(src); // console.log(JSON.stringify(x, void 0, 2)); expect(x).toEqual(eval('(' + src.replace(/# /g, '// ') .replace(/0555/, '0o555') .replace(/\\256/, '\\xae') + ')')); }); it("json-5", function() { const src = ` // // ] `; expect(() => parseJson(src)).toThrowMatching(err => // eslint-disable-next-line @typescript-eslint/no-unsafe-return, @typescript-eslint/no-unsafe-member-access, @typescript-eslint/no-unsafe-call err.message.includes('parse failed at position:43 line:4 col:14 operator "charSequence([)"\n' )); }); it("string-parser-3", function() { type Ctx = number; type Ast = {token: string}; const {seq, cls, notCls, clsFn, classes, cat, qty, repeat, zeroWidth, beginning, end, first, or, combine, err, makeProgram} = getStringParsers<Ctx, Ast>({ rawToToken: token => ({token}), concatTokens: tokens => [tokens.reduce((a, b) => ({token: a.token + b.token}))], }); const zw = zeroWidth(() => ({token: '@'})); const parse = makeProgram(combine( err('Err!'), cat(seq('Hello'), seq(','), ), cat(zw, seq('Wor'), notCls('z', 'd'), first(cls('z', 'y'), cls('d', 'l')), ), cat(or(seq('?'), seq('!'), seq('!!'), qty(0, 10)(seq('!'))), ), end(), )); const x = parse(parserInput('Hello,World!!!!!!!!!!', 1)); expect(x).toEqual({ succeeded: false, error: true, src: 'Hello,World!!!!!!!!!!', pos: 0, message: 'Err!' }); }); it("template-strings-1", function() { type Ctx = number; type Ast = {token: string}; const {seq, cls, notCls, clsFn, isParam, classes, cat, qty, repeat, zeroWidth, beginning, end, first, or, combine, err, makeProgram} = getStringParsers<Ctx, Ast>({ rawToToken: token => ({token}), concatTokens: tokens => [tokens.reduce((a, b) => ({token: a.token + b.token}))], }); const parse = makeProgram(combine(seq('Hello,'), isParam(o => String(o) === 'world'), seq('!'), end())); function exec(strings: TemplateStringsArray, ...values: any[]) { return parse(templateStringsParserInput(strings, values)); } { const result = exec`Hello,${'world'}!`; expect(result.succeeded).toEqual(true); expect(result.succeeded ? result.tokens : null).toEqual([{ token: 'Hello,' }, 'world', { token: '!' }] as any); } { const result = exec`Hello${'world'}!`; expect(result).toEqual({ succeeded: false, error: false, src: 'Hello\x00!', pos: 0, message: 'operator "charSequence(Hello,)"' }); } { const result = exec`Hello,${'world'}?`; expect(result).toEqual({ succeeded: false, error: false, src: 'Hello,\x00?', pos: 7, message: 'operator "charSequence(!)"' }); } }); it("template-strings-2", function() { type Ctx = number; type Ast = {token: string}; const {seq, cls, notCls, clsFn, isParam, classes, cat, qty, repeat, zeroWidth, beginning, end, first, or, combine, err, makeProgram} = getStringParsers<Ctx, Ast>({ rawToToken: token => ({token}), concatTokens: tokens => [tokens.reduce((a, b) => ({token: a.token + b.token}))], }); const parse = makeProgram(combine(seq('Hello,'), isParam(o => String(o) === 'world', o => (o as string).toUpperCase()), seq('!'), end())); function exec(strings: TemplateStringsArray, ...values: any[]) { return parse(templateStringsParserInput(strings, values)); } { const result = exec`Hello,${'world'}!`; expect(result.succeeded).toEqual(true); expect(result.succeeded ? result.tokens : null).toEqual([{ token: 'Hello,' }, 'WORLD', { token: '!' }] as any); } { const result = exec`Hello${'world'}!`; expect(result).toEqual({ succeeded: false, error: false, src: 'Hello\x00!', pos: 0, message: 'operator "charSequence(Hello,)"' }); } { const result = exec`Hello,${'world'}?`; expect(result).toEqual({ succeeded: false, error: false, src: 'Hello,\x00?', pos: 7, message: 'operator "charSequence(!)"' }); } }); it("template-strings-3", function() { type Ctx = number; type Ast = {token: string}; const {seq, cls, notCls, clsFn, isParam, classes, cat, qty, repeat, zeroWidth, beginning, end, first, or, combine, err, makeProgram} = getStringParsers<Ctx, Ast>({ rawToToken: token => ({token}), concatTokens: tokens => [tokens.reduce((a, b) => ({token: a.token + b.token}))], }); const parse = makeProgram(combine(isParam(o => String(o) === 'world', o => (o as string).toUpperCase()), end())); function exec(strings: TemplateStringsArray, ...values: any[]) { return parse(templateStringsParserInput(strings, values)); } { const result = exec`${'world'}`; expect(result.succeeded).toEqual(true); expect(result.succeeded ? result.tokens : null).toEqual(['WORLD'] as any); } { const result = exec`Hello${'world'}!`; expect(result).toEqual({ succeeded: false, error: false, src: 'Hello\x00!', pos: 0, message: 'operator "stringTemplatesParam()"' }); } { const result = exec`Hello,${'world'}?`; expect(result).toEqual({ succeeded: false, error: false, src: 'Hello,\x00?', pos: 0, message: 'operator "stringTemplatesParam()"' }); } }); });