// 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({ rawToToken: rawToken => rawToken, concatTokens: tokens => (tokens.length ? [tokens.reduce((a, b) => String(a) + b)] : []), }); const $o = getObjectParsers({ 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) => first(primitiveValueNoNullUndefined, genericOrSimpleType, partialType, pickOrOmitType, complexArrayType, sequenceType, input => interfaceDefInner(first(seq(';'), seq(',')))(input), ); const complexTypeInnerRoot: (separator: ParserFnWithCtx) => ParserFnWithCtx = (edge: ParserFnWithCtx) => 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) => 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) => ParserFnWithCtx = (edge: ParserFnWithCtx) => first(spreadType, complexType(edge)); const setDocComment = (input: ParserInputWithCtx) => { 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) => 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) => ParserFnWithCtx = (separator: ParserFnWithCtx) => 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(), ));