UNPKG

tynder

Version:

TypeScript friendly Data validator for JavaScript.

223 lines 8.9 kB
// Copyright (c) 2019 Shellyl_N and Authors // license: ISC // https://github.com/shellyln import { parserInput } from 'fruitsconfits/modules/lib/types'; import { formatErrorMessage } from 'fruitsconfits/modules/lib/parser'; import installCore from 'liyad/modules/s-exp/operators/core'; import { SExpression } from 'liyad/modules/s-exp/interpreters'; import { defaultConfig } from 'liyad/modules/s-exp/defaults'; import * as operators from './operators'; import { resolveMemberNames, resolveSchema } from './lib/resolver'; import { dummyTargetObject, isUnsafeVarNames } from './lib/protection'; import { externalTypeDef, program } from './lib/compiler'; function parseExternalDirective(s) { const z = externalTypeDef(parserInput(s, { /* TODO: set initial state to the context */})); if (!z.succeeded) { throw new Error('Invalid external directive.'); } return z.tokens; } export function parse(s) { const z = program(parserInput(s, { /* TODO: set initial state to the context */})); if (!z.succeeded) { throw new Error(formatErrorMessage(z)); } return z.tokens; } const lisp = (() => { let config = Object.assign({}, defaultConfig); config.reservedNames = Object.assign({}, config.reservedNames, { Template: '$concat', }); config = installCore(config); config.stripComments = true; return SExpression(config); })(); // tslint:disable: object-literal-key-quotes export function compile(s) { const mapTyToTySet = new Map(); const schema = new Map(); let gensymCount = 0; const def = (name, ty) => { let ret = ty; const sym = typeof name === 'string' ? name : name.symbol; if (isUnsafeVarNames(dummyTargetObject, sym)) { throw new Error(`Unsafe symbol name is appeared: ${sym}`); } if (!mapTyToTySet.has(ret)) { const originalTypeName = ret.typeName; ret = operators.withName(operators.withTypeName(originalTypeName ? operators.withOriginalTypeName(ret, originalTypeName) : ret, sym), sym); } const tySet = mapTyToTySet.has(ret) ? mapTyToTySet.get(ret) : { ty: ret, exported: false, isDeclare: false, resolved: false }; schema.set(sym, tySet); if (!mapTyToTySet.has(ret)) { // TODO: aliases are not exported correctly mapTyToTySet.set(ret, tySet); } return ret; }; const ref = (name, ...memberNames) => { const sym = typeof name === 'string' ? name : name.symbol; if (isUnsafeVarNames(dummyTargetObject, sym)) { throw new Error(`Unsafe symbol name is appeared: ${sym}`); } const memberTreeSymbols = memberNames.map(x => { const ms = typeof x === 'string' ? x : x.symbol; if (isUnsafeVarNames(dummyTargetObject, ms)) { throw new Error(`Unsafe symbol name is appeared: ${ms}`); } return ms; }); if (!schema.has(sym)) { return (Object.assign({ kind: 'symlink', symlinkTargetName: sym, name: sym, typeName: sym, }, (0 < memberTreeSymbols.length ? { memberTree: memberTreeSymbols, } : {}))); } let ty = resolveMemberNames(schema.get(sym).ty, sym, memberTreeSymbols, 0); if (ty.noOutput) { ty = Object.assign({}, ty); delete ty.noOutput; } return ty; }; const redef = (original, ty) => { if (original === ty) { return ty; } // NOTE: 'ty' should already be registered to 'mapTyToTySet' and 'schema' const tySet = mapTyToTySet.has(original) ? mapTyToTySet.get(original) : { ty: original, exported: false, isDeclare: false, resolved: false }; tySet.ty = ty; mapTyToTySet.set(tySet.ty, tySet); if (ty.name) { schema.set(ty.name, tySet); } return tySet.ty; }; const exported = (ty) => { if (ty.kind === 'never' && typeof ty.passThruCodeBlock === 'string') { ty.passThruCodeBlock = `export ${ty.passThruCodeBlock}`; return ty; } else { // NOTE: 'ty' should already be registered to 'mapTyToTySet' and 'schema' const tySet = mapTyToTySet.has(ty) ? mapTyToTySet.get(ty) : { ty, exported: false, isDeclare: false, resolved: false }; tySet.exported = true; return ty; } }; const external = (...names) => { for (const name of names) { let ty = null; if (typeof name === 'string') { ty = def(name, operators.primitive('any')); } else { ty = def(name[0], name[1] ? name[1] : operators.primitive('any')); } ty.noOutput = true; } }; const asConst = (ty) => { switch (ty.kind) { case 'enum': // NOTE: `ty` may already `def`ed. ty.isConst = true; break; default: throw new Error(`It cannot set to const: ${ty.kind} ${ty.typeName || '(unnamed)'}`); } return ty; }; const asDeclare = (ty) => { // NOTE: 'ty' should already be registered to 'mapTyToTySet' and 'schema' const tySet = mapTyToTySet.has(ty) ? mapTyToTySet.get(ty) : { ty, exported: false, isDeclare: false, resolved: false }; tySet.isDeclare = true; return ty; }; const passthru = (str, docCommentText) => { const ty = { kind: 'never', passThruCodeBlock: str || '', }; if (docCommentText) { ty.docComment = docCommentText; } schema.set(`__$$$gensym_${gensymCount++}$$$__`, { ty, exported: false, isDeclare: false, resolved: false }); return ty; }; const directive = (name, body) => { switch (name) { case '@tynder-external': lisp.evaluateAST(parseExternalDirective(`external ${body} ;`)); break; case '@tynder-pass-through': passthru(body); break; default: throw new Error(`Unknown directive is appeared: ${name}`); } return []; }; lisp.setGlobals({ picked: operators.picked, omit: operators.omit, partial: operators.partial, intersect: operators.intersect, oneOf: operators.oneOf, subtract: operators.subtract, primitive: operators.primitive, primitiveValue: operators.primitiveValue, optional: operators.optional, repeated: operators.repeated, sequenceOf: operators.sequenceOf, spread: operators.spread, enumType: operators.enumType, objectType: operators.objectType, derived: operators.derived, def, ref, redef, export: exported, external, asConst, asDeclare, passthru, directive, docComment: operators.withDocComment, '@range': (minValue, maxValue) => (ty) => operators.withRange(minValue, maxValue)(ty), '@minValue': (minValue) => (ty) => operators.withMinValue(minValue)(ty), '@maxValue': (maxValue) => (ty) => operators.withMaxValue(maxValue)(ty), '@greaterThan': (greaterThan) => (ty) => operators.withGreaterThan(greaterThan)(ty), '@lessThan': (lessThan) => (ty) => operators.withLessThan(lessThan)(ty), '@minLength': (minLength) => (ty) => operators.withMinLength(minLength)(ty), '@maxLength': (maxLength) => (ty) => operators.withMaxLength(maxLength)(ty), '@match': (pattern) => (ty) => operators.withMatch(pattern)(ty), '@stereotype': (stereotype) => (ty) => operators.withStereotype(stereotype)(ty), '@constraint': (name, args) => (ty) => operators.withConstraint(name, args)(ty), '@forceCast': () => (ty) => operators.withForceCast()(ty), '@recordType': () => (ty) => operators.withRecordType()(ty), '@meta': (meta) => (ty) => operators.withMeta(meta)(ty), '@msg': (messages) => (ty) => operators.withMsg(messages)(ty), '@msgId': (messageId) => (ty) => operators.withMsgId(messageId)(ty), }); const z = parse(s); lisp.evaluateAST(z); return resolveSchema(schema); } // tslint:enable: object-literal-key-quotes //# sourceMappingURL=compiler.js.map