import { parserInput } from 'fruitsconfits/modules/lib/types' ; import { formatErrorMessage } from 'fruitsconfits/modules/lib/parser' ; import { SxToken , SxSymbol , SxParserConfig } from 'liyad/modules/s-exp/types' ; 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 { TypeAssertion , PrimitiveTypeAssertion , ErrorMessages , TypeAssertionSetValue , TypeAssertionMap } from './types' ; 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 , { } ) ) ; if ( ! z . succeeded ) { throw new Error ( 'Invalid external directive.' ) ; } return z . tokens ; } export function parse ( s ) { const z = program ( parserInput ( s , { } ) ) ; 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 ) ; } ) ( ) ; export function compile ( s ) { const mapTyToTySet = new Map < TypeAssertion , TypeAssertionSetValue > ( ) ; const schema = new Map < string , TypeAssertionSetValue > ( ) ; let gensymCount = 0 ; const def = ( name , ty ) : TypeAssertion => { 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 ) as TypeAssertionSetValue : { ty : ret , exported : false , isDeclare : false , resolved : false } ; schema . set ( sym , tySet ) ; if ( ! mapTyToTySet . has ( ret ) ) { mapTyToTySet . set ( ret , tySet ) ; } return ret ; } ; const ref = ( name , ... memberNames ) : TypeAssertion => { 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 ( { ... { kind : 'symlink' , symlinkTargetName : sym , name : sym , typeName : sym , } , ... ( 0 < memberTreeSymbols . length ? { memberTree : memberTreeSymbols , } : { } ) , } ) ; } let ty = resolveMemberNames ( ( schema . get ( sym ) as TypeAssertionSetValue ) . ty , sym , memberTreeSymbols , 0 ) ; if ( ty . noOutput ) { ty = { ... ty } ; delete ty . noOutput ; } return ty ; } ; const redef = ( original , ty ) => { if ( original === ty ) { return ty ; } const tySet = mapTyToTySet . has ( original ) ? mapTyToTySet . get ( original ) as TypeAssertionSetValue : { 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 { const tySet = mapTyToTySet . has ( ty ) ? mapTyToTySet . get ( ty ) as TypeAssertionSetValue : { ty , exported : false , isDeclare : false , resolved : false } ; tySet . exported = true ; return ty ; } } ; const external = ( ... names ) => { for ( const name of names ) { let ty = null as any ; 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' : ty . isConst = true ; break ; default : throw new Error ( `It cannot set to const: ${ ty . kind } ${ ty . typeName || '(unnamed)' }` ) ; } return ty ; } ; const asDeclare = ( ty ) => { const tySet = mapTyToTySet . has ( ty ) ? mapTyToTySet . get ( ty ) as TypeAssertionSetValue : { 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 } ;` ) as SxToken [ ] ) ; 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 as SxToken [ ] ) ; return resolveSchema ( schema ) ; }