import { MalVal , MalAtom , M_PARAMS , M_AST , M_ELMSTRS , M_DELIMITERS , getType , MalFunc , MalNode , MalSymbol , MalType , isSeq , MalSeq , M_ISSUGAR , isSymbol , } from './types' ; export const printer = { log : ( ... args ) => { console . info ( ... args ) ; } , return : ( ... args ) => { console . log ( ... args ) ; } , error : ( ... args ) => { console . error ( ... args ) ; } , pseudoExecute : ( command ) => { console . log ( command ) ; } , clear : console . clear , } ; function generateDefaultDelimiters ( elementCount ) { if ( elementCount === 0 ) { return [ '' ] ; } else { return [ '' , ... Array ( elementCount - 1 ) . fill ( ' ' ) , '' ] ; } } const SUGAR_INFO = new Map < string , { prefix : string ; } > ( [ [ 'quote' , { prefix : "'" } ] , [ 'ui-annotate' , { prefix : '#@' } ] , [ 'with-meta-sugar' , { prefix : '^' } ] , [ 'fn-sugar' , { prefix : '#' } ] , [ 'quasiquote' , { prefix : '`' } ] , [ 'unquote' , { prefix : '~' } ] , [ 'unquote-splicing' , { prefix : '~@' } ] , [ 'deref' , { prefix : '@' } ] , ] ) ; export default function printExp ( exp , printReadably = true ) { const _r = printReadably ; const type = getType ( exp ) ; switch ( type ) { case MalType . List : case MalType . Vector : case MalType . Map : { const coll = exp as MalNode ; const sugarInfo = type === MalType . List && SUGAR_INFO . get ( isSymbol ( ( coll as MalSeq ) [ 0 ] ) ? ( ( coll as MalSeq ) [ 0 ] as MalSymbol ) . value : '' ) ; if ( sugarInfo ) { ; ( coll as MalSeq ) [ M_ISSUGAR ] = true ; coll [ M_ELMSTRS ] = [ sugarInfo . prefix , ... ( coll as MalSeq ) . slice ( 1 ) . map ( e => printExp ( e , _r ) ) , ] ; coll [ M_DELIMITERS ] = Array ( ( coll as MalSeq ) . length + 1 ) . fill ( '' ) ; } else { if ( ! ( M_ELMSTRS in coll ) ) { let elmStrs ; if ( isSeq ( coll ) ) { elmStrs = Array . from ( coll ) . map ( e => printExp ( e , _r ) ) ; if ( sugarInfo ) { elmStrs [ 0 ] = '' ; } } else { elmStrs = Object . entries ( coll ) . map ( ( [ key , value ] ) => [ printExp ( key , _r ) , printExp ( value , _r ) ] ) . flat ( ) ; } coll [ M_ELMSTRS ] = elmStrs ; } if ( ! ( M_DELIMITERS in coll ) ) { let delimiters ; if ( isSeq ( coll ) ) { delimiters = generateDefaultDelimiters ( coll . length ) ; } else { delimiters = generateDefaultDelimiters ( Object . keys ( coll ) . length * 2 ) ; } coll [ M_DELIMITERS ] = delimiters ; } } const elmStrs = coll [ M_ELMSTRS ] ; const delimiters = coll [ M_DELIMITERS ] ; let ret = '' ; for ( let i = 0 ; i < elmStrs . length ; i ++ ) { ret += delimiters [ i ] + elmStrs [ i ] ; } ret += delimiters [ delimiters . length - 1 ] ; switch ( type ) { case MalType . List : if ( sugarInfo ) { return ret ; } else { return '(' + ret + ')' ; } case MalType . Vector : return '[' + ret + ']' ; default : return '{' + ret + '}' ; } } case MalType . Number : return ( exp as number ) . toFixed ( 4 ) . replace ( /\.?[0]+$/ , '' ) ; case MalType . String : if ( _r ) { return ( '"' + ( exp as string ) . replace ( /\\/g , '\\\\' ) . replace ( /"/g , '\\"' ) . replace ( /\n/g , '\\n' ) + '"' ) ; } else { return exp as string ; } case MalType . Boolean : return ( exp as boolean ) . toString ( ) ; case MalType . Nil : return 'nil' ; case MalType . Symbol : return ( exp as MalSymbol ) . value ; case MalType . Keyword : return ':' + ( exp as string ) . slice ( 1 ) ; case MalType . Atom : return `(atom ${ printExp ( ( exp as MalAtom ) . value , _r ) })` ; case MalType . Function : case MalType . Macro : { if ( M_AST in ( exp as MalFunc ) ) { const params = printExp ( ( exp as MalFunc ) [ M_PARAMS ] , _r ) ; const body = printExp ( ( exp as MalFunc ) [ M_AST ] , _r ) ; return `(${ type } ${ params } ${ body })` ; } else { return '<JS Function>' ; } } default : return '<undefined>' ; } }