tynder
Version:
TypeScript friendly Data validator for JavaScript.
1,008 lines (884 loc) • 38.1 kB
text/typescript
// Copyright (c) 2019 Shellyl_N and Authors
// license: ISC
// https://github.com/shellyln
import { ParserInputWithCtx,
ParserFnWithCtx } from 'fruitsconfits/modules/lib/types';
import { getStringParsers } from 'fruitsconfits/modules/lib/string-parser';
import { getObjectParsers } from 'fruitsconfits/modules/lib/object-parser';
import { SxTokenChild,
SxToken } from 'liyad/modules/s-exp/types';
import { dummyTargetObject,
isUnsafeVarNames } from './protection';
export interface SxOp {
'op': string;
}
export type AstChild = SxTokenChild | SxOp | undefined;
export type Ast = SxToken | AstChild | SxOp | undefined;
interface Ctx {
docComment?: string;
}
const $s = getStringParsers<Ctx, Ast>({
rawToToken: rawToken => rawToken,
concatTokens: tokens => (tokens.length ?
[tokens.reduce((a, b) => String(a) + b)] : []),
});
const $o = getObjectParsers<Ast[], Ctx, Ast>({
rawToToken: rawToken => rawToken,
concatTokens: tokens => (tokens.length ?
[tokens.reduce((a, b) => String(a) + b)] : []),
comparator: (a, b) => a === b,
});
const {seq, cls, notCls, clsFn, classes, numbers, cat,
once, repeat, qty, zeroWidth, err, beginning, end,
first, or, combine, erase, trans, ahead, rules,
makeProgram} = $s;
const directiveLineComment =
trans(tokens => [[{symbol: 'directive'}, ...tokens]])(
erase(qty(2)(cls('/'))),
erase(repeat(classes.space)),
cat(seq('@tynder-'), repeat(first(classes.alnum, cls('-')))), // [0]
erase(repeat(classes.space)),
cat(repeat(notCls('\r\n', '\n', '\r'))), // [1]
erase(first(classes.newline, ahead(end()))), );
const directiveBlockComment =
trans(tokens => [[{symbol: 'directive'}, ...tokens]])(
erase(seq('/*')),
erase(repeat(classes.space)),
cat(seq('@tynder-'), repeat(first(classes.alnum, cls('-')))), // [0]
erase(repeat(classes.space)),
cat(repeat(notCls('*/'))), // [1]
erase(seq('*/')), );
const lineComment =
combine(
erase(qty(2)(cls('/'))),
first(
combine(
ahead(repeat(classes.space),
notCls('@tynder-'), ),
repeat(notCls('\r\n', '\n', '\r')),
first(classes.newline, ahead(end())), ),
first(classes.newline, ahead(end())), ));
const hashLineComment =
combine(
seq('#'),
repeat(notCls('\r\n', '\n', '\r')),
first(classes.newline, ahead(end())), );
const docComment =
combine(
seq('/**'),
repeat(classes.space),
input => {
const ret = cat(repeat(notCls('*/')))(input);
if (ret.succeeded) {
// define a reducer
const ctx2 = {...ret.next.context}; // NOTE: context is immutable
ctx2.docComment = (ret.tokens[0] as string || '').trim();
ret.next.context = ctx2;
}
return ret;
},
seq('*/'), );
const blockComment =
combine(
seq('/*'),
ahead(repeat(classes.space),
notCls('@tynder-'), ),
repeat(notCls('*/')),
seq('*/'), );
const commentOrSpace =
first(classes.space, lineComment, hashLineComment, docComment, blockComment);
const trueValue =
trans(tokens => [true])
(seq('true'));
const falseValue =
trans(tokens => [false])
(seq('false'));
const nullValue =
trans(tokens => [null])
(seq('null'));
const undefinedValue =
trans(tokens => [void 0])
(seq('undefined'));
const positiveInfinityValue =
trans(tokens => [Number.POSITIVE_INFINITY])
(qty(0, 1)(seq('+')), seq('Infinity'));
const negativeInfinityValue =
trans(tokens => [Number.NEGATIVE_INFINITY])
(seq('-Infinity'));
const nanValue =
trans(tokens => [Number.NaN])
(seq('NaN'));
const binaryIntegerValue =
trans(tokens => [Number.parseInt((tokens as string[])[0].replace(/_/g, ''), 2)])
(numbers.bin(seq('0b')));
const octalIntegerValue =
trans(tokens => [Number.parseInt((tokens as string[])[0].replace(/_/g, ''), 8)])
(numbers.oct(seq('0o'), seq('0')));
const hexIntegerValue =
trans(tokens => [Number.parseInt((tokens as string[])[0].replace(/_/g, ''), 16)])
(numbers.hex(seq('0x'), seq('0X')));
const decimalIntegerValue =
trans(tokens => [Number.parseInt((tokens as string[])[0].replace(/_/g, ''), 10)])
(numbers.int);
const bigDecimalIntegerValue =
trans(tokens => [BigInt((tokens as string[])[0].replace(/_/g, '')) as any])
(numbers.bigint);
const floatingPointNumberValue =
trans(tokens => [Number.parseFloat((tokens as string[])[0].replace(/_/g, ''))])
(numbers.float);
const numberValue =
first(octalIntegerValue,
hexIntegerValue,
binaryIntegerValue,
bigDecimalIntegerValue,
floatingPointNumberValue,
decimalIntegerValue,
positiveInfinityValue,
negativeInfinityValue,
nanValue, );
const stringEscapeSeq = first(
trans(t => ['\''])(seq('\\\'')),
trans(t => ['\"'])(seq('\\"')),
trans(t => ['\`'])(seq('\\`')),
trans(t => ['/'])(seq('\\/')),
trans(t => ['\\'])(seq('\\\\')),
trans(t => [''])(seq('\\\r\n')),
trans(t => [''])(seq('\\\r')),
trans(t => [''])(seq('\\\n')),
trans(t => ['\n'])(seq('\\n')),
trans(t => ['\r'])(seq('\\r')),
trans(t => ['\v'])(seq('\\v')),
trans(t => ['\t'])(seq('\\t')),
trans(t => ['\b'])(seq('\\b')),
trans(t => ['\f'])(seq('\\f')),
trans(t => [String.fromCodePoint(Number.parseInt((t as string[])[0], 16))])(
cat(erase(seq('\\u')),
qty(4, 4)(classes.hex), )),
trans(t => [String.fromCodePoint(Number.parseInt((t as string[])[0], 16))])(
cat(erase(seq('\\u{')),
qty(1, 6)(classes.hex),
erase(seq('}')), )),
trans(t => [String.fromCodePoint(Number.parseInt((t as string[])[0], 16))])(
cat(erase(seq('\\x')),
qty(2, 2)(classes.hex), )),
trans(t => [String.fromCodePoint(Number.parseInt((t as string[])[0], 8))])(
cat(erase(seq('\\')),
qty(3, 3)(classes.oct), )));
const signleQuotStringValue =
trans(tokens => [tokens[0] ?? ''])(
erase(seq("'")),
cat(repeat(first(
stringEscapeSeq,
combine(cls('\r', '\n'), err('Line breaks within strings are not allowed.')),
notCls("'"),
))),
erase(seq("'")), );
const doubleQuotStringValue =
trans(tokens => [tokens[0] ?? ''])(
erase(seq('"')),
cat(repeat(first(
stringEscapeSeq,
combine(cls('\r', '\n'), err('Line breaks within strings are not allowed.')),
notCls('"'),
))),
erase(seq('"')), );
const backQuotStringValue =
trans(tokens => [tokens[0] ?? ''])(
erase(seq('`')),
cat(repeat(first(
stringEscapeSeq,
notCls('`'),
))),
erase(seq('`')), );
const stringValue =
first(signleQuotStringValue, doubleQuotStringValue, backQuotStringValue);
const regexpStringValue =
// TODO: '/' ']' '\\' in character class '[]' is not parsed correctly.
trans(tokens => [{value: tokens[1] ?
new RegExp((tokens[0] ?? '') as string, tokens[1] as string) :
new RegExp((tokens[0] ?? '') as string)}])(
erase(seq('/')),
cat(repeat(first(
stringEscapeSeq,
notCls('/'),
))),
erase(seq('/')),
cat(qty(0)(cls('g', 'i', 'm', 's', 'u', 'y'))), );
const symbolName =
trans(tokens => tokens)
(cat(combine(
first(classes.alpha, cls('$', '_')),
repeat(first(classes.alnum, cls('$', '_'))), )));
const decoratorSymbolName =
trans(tokens => [{symbol: (tokens as string[])[0]}])
(cat(combine(
seq('@'),
first(classes.alpha, cls('$', '_')),
repeat(first(classes.alnum, cls('$', '_'))), )));
const simpleConstExpr =
first(trueValue, falseValue, nullValue, undefinedValue,
numberValue, stringValue, );
const objKey =
first(stringValue, symbolName);
const listValue = first(
trans(tokens => [[]])(erase(
seq('['),
repeat(commentOrSpace),
seq(']'), )),
trans(tokens => {
const ast: Ast = [{symbol: '$list'}];
for (const token of tokens) {
ast.push(token as any);
}
return [ast];
})(
erase(seq('[')),
combine(
erase(repeat(commentOrSpace)),
first(input => listValue(input), // NOTE: recursive definitions
input => objectValue(input), // should place as lambda.
simpleConstExpr,
),
erase(repeat(commentOrSpace)), ),
repeat(combine(
erase(repeat(commentOrSpace),
seq(','),
repeat(commentOrSpace)),
first(input => listValue(input), // NOTE: recursive definitions
input => objectValue(input), // should place as lambda.
simpleConstExpr,
),
erase(repeat(commentOrSpace)), )),
qty(0, 1)(erase(
seq(','),
repeat(commentOrSpace), )),
first(ahead(seq(']')), err('listValue: Unexpected token has appeared.')),
erase(seq(']')),
), );
const objectKeyValuePair =
combine(
objKey,
erase(repeat(commentOrSpace),
first(seq(':'), err('":" is needed.')),
repeat(commentOrSpace)),
first(input => listValue(input), // NOTE: recursive definitions
input => objectValue(input), // should place as lambda.
simpleConstExpr,
err('object value is needed.')), );
const objectValue = first(
trans(tokens => [[{symbol: '#'}]])(erase(
seq('{'),
repeat(commentOrSpace),
seq('}'),
)),
trans(tokens => {
const ast: Ast = [{symbol: '#'}];
for (let i = 0; i < tokens.length; i += 2) {
if (isUnsafeVarNames(dummyTargetObject, tokens[i] as string)) {
throw new Error(`Unsafe symbol name is appeared in object literal: ${tokens[i]}`);
}
ast.push([tokens[i], tokens[i + 1]]);
}
return [ast];
})(
erase(seq('{')),
combine(
erase(repeat(commentOrSpace)),
objectKeyValuePair,
erase(repeat(commentOrSpace)), ),
repeat(combine(
erase(seq(','),
repeat(commentOrSpace)),
objectKeyValuePair,
erase(repeat(commentOrSpace)), )),
qty(0, 1)(erase(
seq(','),
repeat(commentOrSpace), )),
first(ahead(seq('}')), err('objectValue: Unexpected token has appeared.')),
erase(seq('}')),
), );
const constExpr =
first(simpleConstExpr,
listValue,
objectValue, );
// const primitiveValue = trans(tokens => [[{symbol: 'primitiveValue'}, tokens[0]]])(
// first(trueValue, falseValue, nullValue, undefinedValue,
// numberValue, stringValue, ));
const primitiveValueNoNullUndefined =
trans(tokens => [[{symbol: 'primitiveValue'}, tokens[0]]])(
first(trueValue, falseValue,
numberValue, stringValue, ));
const primitiveTypeName =
trans(tokens => [[{symbol: 'primitive'}, tokens[0]]])(
first(seq('number?'), seq('integer?'), seq('bigint?'), seq('string?'), seq('boolean?'), // TODO: '?' is allowed in the sequence assertion
seq('number'), seq('integer'), seq('bigint'), seq('string'), seq('boolean'), )); // TODO: function
const additionalPropPrimitiveTypeName =
first(seq('number'), seq('string'));
const nullUndefinedTypeName =
trans(tokens => [[{symbol: 'primitive'}, tokens[0]]])(
first(seq('null'), seq('undefined'), seq('any'), seq('unknown'), seq('never')), );
const simpleOrDottedTypeName =
first(primitiveTypeName,
nullUndefinedTypeName,
trans(tokens =>
[[{symbol: 'ref'}, ...tokens]])(
ahead(notCls('Array', 'Partial', 'Pick', 'Omit')),
combine(
symbolName,
repeat(combine(
erase(repeat(commentOrSpace), seq('.'), repeat(commentOrSpace)),
symbolName, )))));
const sequenceType =
trans(tokens => [[{symbol: 'sequenceOf'}, ...tokens]])(
combine(
erase(seq('[')),
combine(
erase(repeat(commentOrSpace)),
input => spreadOrComplexType(first(seq(','), seq(']')))(input),
erase(repeat(commentOrSpace)), ),
repeat(combine(
erase(seq(','),
repeat(commentOrSpace)),
input => spreadOrComplexType(first(seq(','), seq(']')))(input),
erase(repeat(commentOrSpace)), )),
qty(0, 1)(erase(
seq(','),
repeat(commentOrSpace), )),
first(ahead(seq(']')), err('sequenceType: Unexpected token has appeared.')),
erase(seq(']')), ));
const arraySizeFactorInner =
first(
trans(tokens => [[{symbol: '#'}, ['max', tokens[0]]]])(
erase(seq('..')),
erase(repeat(commentOrSpace)),
decimalIntegerValue, ),
trans(tokens => [[{symbol: '#'}, ['min', tokens[0]], ['max', tokens[1]]]])(
decimalIntegerValue,
erase(repeat(commentOrSpace)),
erase(seq('..')),
erase(repeat(commentOrSpace)),
decimalIntegerValue, ),
trans(tokens => [[{symbol: '#'}, ['min', tokens[0]]]])(
decimalIntegerValue,
erase(repeat(commentOrSpace)),
erase(seq('..')), ),
trans(tokens => [[{symbol: '#'}, ['min', tokens[0]], ['max', tokens[0]]]])(
decimalIntegerValue, ));
const arraySizeFactor =
trans(tokens =>
tokens.length > 0 ?
tokens :
[[{symbol: '#'}]])(
erase(seq('[')),
erase(repeat(commentOrSpace)),
qty(0, 1)(arraySizeFactorInner),
erase(repeat(commentOrSpace)),
erase(seq(']')), );
const complexArrayType =
trans(tokens => [[{symbol: 'repeated'}, tokens[0], tokens[1]]])(
erase(seq('Array')),
erase(repeat(commentOrSpace)),
erase(seq('<')),
erase(repeat(commentOrSpace)),
first(input => complexType(first(seq(','), seq('>')))(input),
err('type is expected in Array type.'), ), // [0]
erase(repeat(commentOrSpace)),
qty(0, 1)(combine(
erase(seq(',')),
erase(repeat(commentOrSpace)),
first(arraySizeFactorInner, // [1]
err('complexArrayType: Unexpected token has appeared. Expect array size.'), ),
erase(repeat(commentOrSpace)), )),
first(ahead(seq('>')),
err('\'>\' is expected in Array type.'), ),
erase(seq('>')), );
const partialType =
trans(tokens => [[{symbol: 'partial'}, tokens[0], tokens[1]]])(
erase(seq('Partial')),
erase(repeat(commentOrSpace)),
erase(seq('<')),
erase(repeat(commentOrSpace)),
first(input => complexType(first(seq(','), seq('>')))(input),
err('type is expected in Partial type.'), ), // [0]
erase(repeat(commentOrSpace)),
first(ahead(seq('>')),
err('\'>\' is expected in Partial type.'), ),
erase(seq('>')), );
const pickOrOmitType =
trans(tokens => [[{symbol: tokens[0] === 'Pick' ? 'picked' : 'omit'}, tokens[1], ...tokens.slice(2)]])(
first(seq('Pick'),
seq('Omit'), ), // [0]
erase(repeat(commentOrSpace)),
erase(seq('<')),
erase(repeat(commentOrSpace)),
first(input => complexType(first(seq(','), seq('>')))(input),
err('type is expected in Partial type.'), ), // [1]
erase(repeat(commentOrSpace)),
combine(
erase(seq(',')),
erase(repeat(commentOrSpace)),
stringValue, // [2]
qty(0)(combine(
erase(repeat(commentOrSpace)),
erase(seq('|')),
erase(repeat(commentOrSpace)),
stringValue, )), // [3],...
erase(repeat(commentOrSpace)), ),
first(ahead(seq('>')),
err('\'>\' is expected in Pick|Omit type.'), ),
erase(seq('>')), );
const genericOrSimpleType =
trans(tokens => [tokens[0]])( // remove generics parameters
simpleOrDottedTypeName, // [0]
erase(repeat(commentOrSpace)),
qty(0, 1)(combine(
erase(seq('<')),
combine( // [1]
erase(repeat(commentOrSpace)),
first(input => complexType(first(seq(','), seq('>')))(input),
err('type is expected in generic type.'), ),
erase(repeat(commentOrSpace)), ),
repeat(combine( // [2]...
erase(seq(','),
repeat(commentOrSpace)),
first(input => complexType(first(seq(','), seq('>')))(input),
err('type is expected in generic type.'), ),
erase(repeat(commentOrSpace)), )),
qty(0, 1)(erase(
seq(','),
repeat(commentOrSpace), )),
first(ahead(seq('>')), err('genericType: Unexpected token has appeared.')),
erase(seq('>')), )));
const spreadType =
trans(tokens => [[{symbol: 'spread'}, tokens[0], tokens[1]]])(
erase(seq('...')),
erase(repeat(commentOrSpace)),
erase(seq('<')),
erase(repeat(commentOrSpace)),
input => complexType(first(seq(','), seq('>')))(input),
erase(repeat(commentOrSpace)),
qty(0, 1)(combine(
erase(seq(',')),
erase(repeat(commentOrSpace)),
first(arraySizeFactorInner,
err('spreadType: Unexpected token has appeared. Expect array size.'), ),
erase(repeat(commentOrSpace)), )),
first(ahead(seq('>')), err('spreadType: Unexpected token has appeared.')),
erase(seq('>')), );
const decorator =
trans(tokens => [tokens])(
decoratorSymbolName,
qty(0, 1)(first(
combine(erase(
seq('('),
repeat(commentOrSpace),
seq(')'), )),
combine(
erase(seq('(')),
first(
combine(
combine(
erase(repeat(commentOrSpace)),
first(regexpStringValue, constExpr),
erase(repeat(commentOrSpace)), ),
repeat(combine(
erase(repeat(commentOrSpace)),
erase(seq(',')),
erase(repeat(commentOrSpace)),
first(regexpStringValue, constExpr),
erase(repeat(commentOrSpace)), )),
qty(0, 1)(erase(
seq(','),
repeat(commentOrSpace), )),
first(ahead(seq(')')), err('decorator: Unexpected token has appeared. Expect ")".')), ),
err('decorator: Unexpected token has appeared.'), ),
erase(seq(')')),
), )));
const decoratorsClause =
trans(tokens => tokens)(
repeat(combine(
decorator,
erase(repeat(commentOrSpace)), )));
const complexTypeInnerWOSinpleArrayType = (edge: ParserFnWithCtx<string, Ctx, Ast>) =>
first(primitiveValueNoNullUndefined,
genericOrSimpleType,
partialType,
pickOrOmitType,
complexArrayType,
sequenceType,
input => interfaceDefInner(first(seq(';'), seq(',')))(input), );
const complexTypeInnerRoot: (separator: ParserFnWithCtx<string, Ctx, Ast>) => ParserFnWithCtx<string, Ctx, Ast> =
(edge: ParserFnWithCtx<string, Ctx, Ast>) =>
trans(tokens => {
let ty = [{symbol: '$pipe'}, tokens[1], ...(tokens[0] as Ast[])];
if (tokens[2] !== null) {
for (const z of tokens[2] as Ast[]) {
ty = [{symbol: 'repeated'}, ty, z];
}
}
return ([[
ty,
...(tokens[3] ? [tokens[3]] : []),
...tokens.slice(4),
]]);
})(
trans(tokens => [tokens])(qty(0, 1)(decoratorsClause)), // [0]
first( // [1]
input => complexTypeInnerWOSinpleArrayType(edge)(input),
combine(
erase(seq('(')),
erase(repeat(commentOrSpace)),
input => complexType(edge)(input),
erase(repeat(commentOrSpace)),
erase(seq(')')), )),
combine(
trans(tokens => tokens[0] !== null ? [tokens] : [null])( // [2]
first(
qty(1)(combine(
erase(repeat(commentOrSpace)),
arraySizeFactor, )),
zeroWidth(() => null), )),
combine(first( // [3]...
trans(tokens => [tokens[0], ...(tokens[1] as Ast[])])(
qty(1)(combine(
erase(repeat(commentOrSpace)),
trans(tokens => [{op: tokens[0]} as any])(or(seq('&'), seq('|'), seq('-'))),
erase(repeat(commentOrSpace)),
input => complexTypeInnerRoot(edge)(input), ))),
trans(tokens => [])(), ))));
const binaryOp = (op: string, op1: any, op2: any) => {
return [{symbol: op}, op1, op2];
};
const isOperator = (v: any, op: string) => {
if (typeof v === 'object' && v.op === op) {
return true;
}
return false;
};
const isValue = (v: any) => {
// TODO: check type
return true;
};
// production rules:
// S -> S "&" S
const complexTypeExprRule3 = $o.trans(tokens => [binaryOp('intersect', tokens[0], tokens[2])])(
$o.clsFn(t => isValue(t)),
$o.clsFn(t => isOperator(t, '&')),
$o.clsFn(t => isValue(t)),
);
// production rules:
// S -> S "|" S
const complexTypeExprRule2 = $o.trans(tokens => [binaryOp('oneOf', tokens[0], tokens[2])])(
$o.clsFn(t => isValue(t)),
$o.clsFn(t => isOperator(t, '|')),
$o.clsFn(t => isValue(t)),
);
// production rules:
// S -> S "-" S
const complexTypeExprRule1 = $o.trans(tokens => [binaryOp('subtract', tokens[0], tokens[2])])(
$o.clsFn(t => isValue(t)),
$o.clsFn(t => isOperator(t, '-')),
$o.clsFn(t => isValue(t)),
);
const complexType = (edge: ParserFnWithCtx<string, Ctx, Ast>) => rules({
rules: [
complexTypeExprRule3,
complexTypeExprRule2,
complexTypeExprRule1,
],
check: $o.combine($o.classes.any, $o.end()),
})(trans(tokens => tokens[0] as Ast[])(complexTypeInnerRoot(edge)));
const spreadOrComplexType: (separator: ParserFnWithCtx<string, Ctx, Ast>) => ParserFnWithCtx<string, Ctx, Ast> =
(edge: ParserFnWithCtx<string, Ctx, Ast>) =>
first(spreadType, complexType(edge));
const setDocComment = (input: ParserInputWithCtx<string, Ctx>) => {
const ret = zeroWidth(() => [])(input);
if (ret.succeeded) {
const text = ret.next.context.docComment;
ret.next.context = {...ret.next.context};
delete ret.next.context.docComment;
ret.tokens.length = 0;
ret.tokens.push(text ? text : null);
}
return ret;
};
const typeDef =
trans(tokens => [[{symbol: 'def'}, tokens[1], [{symbol: 'docComment'}, tokens[2], tokens[0] ] ]])(
erase(seq('type')),
setDocComment, // [0]
erase(qty(1)(commentOrSpace)),
first(symbolName, // [1]
err('typeDef: Unexpected token has appeared. Expect symbol name.'), ),
erase(repeat(commentOrSpace)),
first(ahead(seq('=')), err('typeDef: Unexpected token has appeared. Expect "=".')),
erase(seq('=')),
first(
combine(erase(repeat(commentOrSpace)),
input => complexType(first(seq(','), seq(';')))(input), // [2]
erase(repeat(commentOrSpace)), ),
err('typeDef: Unexpected token has appeared.'), ),
first(ahead(seq(';')), err('typeDef: Unexpected token has appeared. Expect ";".')),
erase(seq(';')), );
const interfaceExtendsClause =
trans(tokens => [
[{symbol: '$list'},
...tokens.map(x => [{symbol: 'ref'}, x])], ])(
erase(first(
seq('extends'),
combine(symbolName,
err('interfaceExtendsClause: Unexpected token has appeared. Expect "extends" keyword.'), ))),
erase(qty(1)(commentOrSpace)),
first(symbolName,
err('interfaceExtendsClause: Unexpected token has appeared. Expect symbol name.'), ),
repeat(combine(
erase(repeat(commentOrSpace)),
erase(seq(',')),
erase(repeat(commentOrSpace)),
first(symbolName,
err('interfaceExtendsClause: Unexpected token has appeared. Expect symbol name.'), ))));
const interfaceKey =
first(
trans(tokens => [[{symbol: '$list'}, ...tokens]])(
erase(seq('[')),
erase(repeat(commentOrSpace),
objKey,
repeat(commentOrSpace),
first(seq(':'), err('":" is needed.')),
repeat(commentOrSpace), ),
repeat(combine(
first(regexpStringValue,
additionalPropPrimitiveTypeName, ),
erase(repeat(commentOrSpace),
seq('|'),
repeat(commentOrSpace), ))),
first(regexpStringValue,
additionalPropPrimitiveTypeName, ),
erase(repeat(commentOrSpace)),
first(ahead(seq(']')), err('interfaceKey: Unexpected token has appeared. Expect "]".')),
erase(seq(']')), ),
objKey, );
const interfaceKeyTypePair = (separator: ParserFnWithCtx<string, Ctx, Ast>) =>
trans(tokens => [
[{symbol: '$list'},
tokens[2],
[{symbol: '$pipe'},
tokens[3] === '?' ?
[{symbol: 'optional'}, tokens[4]] :
tokens[4], ...(tokens[0] as Ast[]), ],
tokens[1], ]])(
trans(tokens => [tokens])(first(
decoratorsClause,
zeroWidth(() => []), )), // [0] decorators
setDocComment, // [1]
interfaceKey, // [2] key
first( // [3] '?' | ''
combine(
erase(repeat(commentOrSpace)),
seq('?'),
erase(repeat(commentOrSpace)), ),
zeroWidth(() => ['']), ),
erase(repeat(commentOrSpace),
first(seq(':'), err('":" is needed.')),
repeat(commentOrSpace), ),
first( // [4] type
input => complexType(first(separator, seq('}')))(input),
err('interface member type is needed.'), ));
const interfaceDefInner: (separator: ParserFnWithCtx<string, Ctx, Ast>) => ParserFnWithCtx<string, Ctx, Ast> =
(separator: ParserFnWithCtx<string, Ctx, Ast>) =>
trans(tokens => [[{symbol: 'objectType'}, ...tokens]])(
first(
combine(erase(
seq('{'),
repeat(commentOrSpace),
seq('}'), )),
combine(
erase(seq('{')),
combine(
erase(repeat(commentOrSpace)),
interfaceKeyTypePair(separator),
erase(repeat(commentOrSpace)), ),
repeat(combine(
erase(separator,
repeat(commentOrSpace)),
interfaceKeyTypePair(separator),
erase(repeat(commentOrSpace)), )),
qty(0, 1)(erase(
separator,
repeat(commentOrSpace), )),
first(ahead(seq('}')), err('interfaceDefInner: Unexpected token has appeared. Expect "}".')),
erase(seq('}')), )));
const interfaceDef =
trans(tokens => [
[{symbol: 'def'},
tokens[1],
[{symbol: 'docComment'},
[{symbol: 'derived'}, tokens[3], [{symbol: '$spread'}, tokens[2]]],
tokens[0], ]]])(
erase(seq('interface')),
setDocComment, // [0] base types
erase(qty(1)(commentOrSpace)),
first(symbolName, // [1] symbol
err('interfaceDef: Unexpected token has appeared. Expect symbol name.'), ),
erase(repeat(commentOrSpace)),
first(interfaceExtendsClause, // [2]
zeroWidth(() => []), ),
erase(repeat(commentOrSpace)),
first(
input => interfaceDefInner(
first(seq(';'), seq(',')), )(input), // [3]
err('interfaceDef: Unexpected token has appeared.'), ),
);
const enumKeyValue =
trans(tokens => [[{symbol: '$list'}, tokens[1], tokens[2], tokens[0]]])(
setDocComment, // [0]
symbolName,
erase(repeat(commentOrSpace)),
first(
combine(
erase(seq('=')),
first(
combine(erase(repeat(commentOrSpace)),
first(decimalIntegerValue,
stringValue, ),
erase(repeat(commentOrSpace)), ),
err('enumKeyValue: Unexpected token has appeared.'), )),
zeroWidth(() => null), ));
const enumDef =
trans(tokens => [
[{symbol: 'def'}, tokens[1],
[{symbol: 'docComment'},
[{symbol: 'enumType'}, ...tokens.slice(2)],
tokens[0], ]]])(
erase(seq('enum')),
setDocComment, // [0]
erase(qty(1)(commentOrSpace)),
first(symbolName,
err('enumDef: Unexpected token has appeared. Expect symbol name.'), ),
erase(repeat(commentOrSpace)),
first(
combine(erase(
seq('{'),
repeat(commentOrSpace),
seq('}'), )),
combine(
erase(seq('{')),
combine(
erase(repeat(commentOrSpace)),
enumKeyValue,
erase(repeat(commentOrSpace)), ),
repeat(combine(
erase(seq(','),
repeat(commentOrSpace)),
enumKeyValue,
erase(repeat(commentOrSpace)), )),
qty(0, 1)(erase(
seq(','),
repeat(commentOrSpace), )),
first(ahead(seq('}')), err('enumDef: Unexpected token has appeared. Expect "}".')),
erase(seq('}')), ),
err('enumDef: Unexpected token has appeared.'), ));
const internalDef =
first(typeDef,
interfaceDef,
enumDef, );
const constDef =
trans(tokens => [[{symbol: 'asConst'}, tokens[0]]])(
erase(seq('const'),
qty(1)(commentOrSpace), ),
first(enumDef,
err('constDef: Unexpected token has appeared.'), ));
const constDefNoErr =
trans(tokens => [[{symbol: 'asConst'}, tokens[0]]])(
erase(seq('const'),
qty(1)(commentOrSpace), ),
first(enumDef), );
const exportedDef =
trans(tokens => [[{symbol: 'export'}, tokens[0]]])(
erase(seq('export'),
qty(1)(commentOrSpace), ),
first(constDef,
internalDef,
input => declareTypeAndEnumStatement(input),
input => declareVarStatement(input),
err('exportedDef: Unexpected token has appeared.'), ));
const defStatement =
trans(tokens => [
[{symbol: '$local'}, [
[{symbol: '_ty'}, tokens[1]],
],
[{symbol: 'redef'},
{symbol: '_ty'},
[{symbol: '$pipe'}, {symbol: '_ty'}, ...(tokens[0] as Ast[])], ]]])(
trans(tokens => [tokens])(first(
decoratorsClause,
zeroWidth(() => []), )), // [0] decorators
first(exportedDef, // [1] body
input => declareTypeAndEnumStatement(input),
constDef,
internalDef, ));
const externalSymbolAndType =
trans(tokens => [[{symbol: '$list'}, ...tokens]])(
symbolName,
erase(repeat(commentOrSpace)),
qty(0, 1)(
combine(erase(seq(':')),
erase(repeat(commentOrSpace)),
input => complexType(first(seq(';'), seq(',')))(input), )));
export const externalTypeDef =
trans(tokens => [[{symbol: 'external'}, ...tokens]])(
erase(seq('external')),
erase(qty(1)(commentOrSpace)),
externalSymbolAndType,
repeat(combine(
erase(repeat(commentOrSpace)),
erase(cls(',')),
erase(repeat(commentOrSpace)),
first(externalSymbolAndType,
err('externalTypeDef: Unexpected token has appeared. Expect symbol name.'), ),
erase(repeat(commentOrSpace)),
)),
erase(repeat(commentOrSpace)),
first(ahead(cls(';')), err('externalTypeDef: Unexpected token has appeared. Expect ";".')),
erase(cls(';')), );
const declareTypeAndEnumStatement =
trans(tokens => [[{symbol: 'asDeclare'}, ...tokens]])(
erase(seq('declare')),
erase(qty(1)(commentOrSpace)),
first(constDefNoErr, // NOTE: There is still the possibility of "const varName". -> `declareVarStatement` will be called.
internalDef), );
const declareVarStatement =
trans(tokens => [[{symbol: 'passthru'}, tokens[0], tokens[1]]])(
cat(seq('declare'),
qty(1)(commentOrSpace),
first(seq('var'),
seq('let'),
seq('const'),
err('declareVarStatement: Unexpected token has appeared. Expect "var|let|const".') ),
qty(1)(commentOrSpace),
cat(repeat(notCls(';'))),
first(ahead(seq(';')), err('declareVarStatement: Unexpected token has appeared. Expect ";".')),
cls(';'), ),
setDocComment, ); // [1]
const importStatement =
trans(tokens => [[{symbol: 'passthru'}, tokens[0]]])(
cat(seq('import'),
qty(1)(commentOrSpace),
cat(repeat(notCls(';'))),
first(ahead(seq(';')), err('importStatement: Unexpected token has appeared. Expect ";".')),
cls(';'), ));
const definition =
first(directiveLineComment,
directiveBlockComment,
defStatement,
externalTypeDef,
declareVarStatement,
importStatement, );
export const program =
makeProgram(combine(
erase(repeat(commentOrSpace)),
repeat(combine(
definition,
erase(repeat(commentOrSpace)), )),
erase(repeat(commentOrSpace)),
first(ahead(end()), err('program: Unexpected token has appeared.')),
end(), ));