fruitsconfits
Version:
FruitsConfits - A well typed and sugared parser combinator framework for TypeScript/JavaScript.
446 lines (399 loc) • 17.4 kB
text/typescript
// 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()"' });
}
});
});