tynder
Version:
TypeScript friendly Data validator for JavaScript.
223 lines • 8.9 kB
JavaScript
// 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