import Env from './env' ; export type MalJSFunc = ( ... args : MalVal [ ] ) => MalVal | never ; export const M_META = Symbol . for ( 'meta' ) ; export const M_AST = Symbol . for ( 'ast' ) ; export const M_ENV = Symbol . for ( 'env' ) ; export const M_PARAMS = Symbol . for ( 'params' ) ; export const M_ISMACRO = Symbol . for ( 'ismacro' ) ; export const M_ISLIST = Symbol . for ( 'islist' ) ; export const M_TYPE = Symbol . for ( 'type' ) ; export const M_EVAL = Symbol . for ( 'eval' ) ; export const M_OUTER = Symbol . for ( 'outer' ) ; export const M_OUTER_INDEX = Symbol . for ( 'outer-key' ) ; const M_EXPAND = Symbol . for ( 'expand' ) ; export const M_ISSUGAR = Symbol ( 'issugar' ) ; export const M_ELMSTRS = Symbol . for ( 'elmstrs' ) ; export const M_DELIMITERS = Symbol . for ( 'delimiters' ) ; export const M_DEF = Symbol . for ( 'def' ) ; export type MalBind = ( MalSymbol | string | { [ k ] : MalSymbol ; } | MalBind ) [ ] ; export enum ExpandType { Constant = 1 , Env , Unchange } export interface ExpandInfoConstant { type : ExpandType . Constant ; exp : MalVal ; } export interface ExpandInfoEnv { type : ExpandType . Env ; exp : MalVal ; env : Env ; } export interface ExpandInfoUnchange { type : ExpandType . Unchange ; } export type ExpandInfo = ExpandInfoConstant | ExpandInfoEnv | ExpandInfoUnchange ; export interface MalFuncThis { callerEnv : Env ; } export interface MalFunc extends Function { ( this , ... args ) : MalVal ; [ M_META ] ? : MalVal ; [ M_AST ] : MalVal ; [ M_ENV ] : Env ; [ M_PARAMS ] : MalBind ; [ M_ISMACRO ] : boolean ; } export class MalError extends Error { } export type MalMap = { [ keyword ] : MalVal ; } ; export interface MalNodeMap extends MalMap { [ M_META ] ? : MalVal ; [ M_DELIMITERS ] : string [ ] ; [ M_ELMSTRS ] : string [ ] ; [ M_EVAL ] : MalVal ; [ M_OUTER ] : MalNode ; [ M_OUTER_INDEX ] : number ; } export interface MalSeq extends Array < MalVal > { [ M_ISLIST ] : boolean ; [ M_META ] ? : MalVal ; [ M_ISSUGAR ] : boolean ; [ M_DELIMITERS ] : string [ ] ; [ M_ELMSTRS ] : string [ ] ; [ M_EVAL ] : MalVal ; [ M_EXPAND ] : ExpandInfo ; [ M_OUTER ] : MalNode ; [ M_OUTER_INDEX ] : number ; } function expandSymbolsInExp ( exp , env ) { const type = getType ( exp ) ; switch ( type ) { case MalType . List : case MalType . Vector : { let ret = ( exp as MalVal [ ] ) . map ( val => expandSymbolsInExp ( val , env ) ) ; if ( type === MalType . List ) { ret = createList ( ... ret ) ; } return ret ; } case MalType . Map : { const ret = { } as MalMap ; Object . entries ( exp as MalMap ) . forEach ( ( [ key , val ] ) => { ret [ key ] = expandSymbolsInExp ( val , env ) ; } ) ; return ret ; } case MalType . Symbol : if ( env . hasOwn ( exp as MalSymbol ) ) { return env . get ( exp as MalSymbol ) ; } else { return exp ; } default : return exp ; } } export function setExpandInfo ( exp , info ) { exp [ M_EXPAND ] = info ; } export function expandExp ( exp ) { if ( isList ( exp ) && M_EXPAND in exp ) { const info = exp [ M_EXPAND ] ; switch ( info . type ) { case ExpandType . Constant : return info . exp ; case ExpandType . Env : return expandSymbolsInExp ( info . exp , info . env ) ; case ExpandType . Unchange : return exp ; } } else { return getEvaluated ( exp , false ) ; } } export function getOuter ( exp ) { if ( isNode ( exp ) && M_OUTER in exp ) { return exp [ M_OUTER ] ; } return null ; } export enum MalType { List = 'list' , Vector = 'vector' , Map = 'map' , Number = 'number' , String = 'string' , Boolean = 'boolean' , Nil = 'nil' , Symbol = 'symbol' , Keyword = 'keyword' , Atom = 'atom' , Function = 'fn' , Macro = 'macro' , Undefined = 'undefined' } export function getType ( obj ) { const _typeof = typeof obj ; switch ( _typeof ) { case 'object' : if ( obj === null ) { return MalType . Nil ; } else if ( Array . isArray ( obj ) ) { const islist = ( obj as MalSeq ) [ M_ISLIST ] ; return islist ? MalType . List : MalType . Vector ; } else if ( obj instanceof Float32Array ) { return MalType . Vector ; } else if ( ( obj as any ) [ M_TYPE ] === MalType . Symbol ) { return MalType . Symbol ; } else if ( ( obj as any ) [ M_TYPE ] === MalType . Atom ) { return MalType . Atom ; } else { return MalType . Map ; } case 'function' : { const ismacro = ( obj as MalFunc ) [ M_ISMACRO ] ; return ismacro ? MalType . Macro : MalType . Function ; } case 'string' : switch ( ( obj as string ) [ 0 ] ) { case KEYWORD_PREFIX : return MalType . Keyword ; default : return MalType . String ; } case 'number' : return MalType . Number ; case 'boolean' : return MalType . Boolean ; default : return MalType . Undefined ; } } export type MalNode = MalNodeMap | MalSeq ; export const isNode = ( v ) : v is MalNode => { const type = getType ( v ) ; return ( type === MalType . List || type === MalType . Map || type === MalType . Vector ) ; } ; export const isSeq = ( v ) : v is MalSeq => { const type = getType ( v ) ; return type === MalType . List || type === MalType . Vector ; } ; export function getMeta ( obj ) { if ( obj instanceof Object ) { return M_META in obj ? ( obj as any ) [ M_META ] : null ; } else { return null ; } } const TYPES_SUPPORT_META = new Set ( [ MalType . Function , MalType . Macro , MalType . List , MalType . Vector , MalType . Map , ] ) ; export function withMeta ( a , m ) { if ( m === undefined ) { throw new MalError ( '[with-meta] Need the metadata to attach' ) ; } if ( ! TYPES_SUPPORT_META . has ( getType ( a ) ) ) { throw new MalError ( '[with-meta] Object should not be atom' ) ; } const c = cloneExp ( a as MalNode ) ; c [ M_META ] = m ; return c ; } export function setMeta ( a , m ) { if ( ! ( a instanceof Object ) ) { throw new MalError ( '[with-meta] Object should not be atom' ) ; } ; ( a as any ) [ M_META ] = m ; return a ; } export type MalVal = number | string | boolean | null | MalSymbol | MalAtom | MalFunc | MalJSFunc | MalMap | MalVal [ ] | MalNode ; export interface MalNodeSelection { outer : MalNode ; index : number ; } interface MalRootSelection { root : MalVal ; } export type MalSelection = MalNodeSelection | MalRootSelection ; export function getMalFromSelection ( sel ) { if ( 'root' in sel ) { return sel . root ; } else { const { outer , index } = sel ; if ( isMap ( outer ) ) { return outer [ Object . keys ( outer ) [ index ] ] ; } else { return outer [ index ] ; } } } export function isEqual ( a , b ) { if ( Array . isArray ( a ) && Array . isArray ( b ) ) { if ( a . length !== b . length ) { return false ; } for ( let i = 0 ; i < a . length ; i ++ ) { if ( ! isEqual ( a [ i ] , b [ i ] ) ) { return false ; } } return true ; } else if ( isMap ( a ) && isMap ( b ) ) { if ( a . size !== b . size ) { return false ; } for ( const k of Object . keys ( a ) ) { const aval = a [ k ] , bval = b [ k ] ; if ( aval === undefined || bval === undefined || ! isEqual ( aval , bval ) ) { return false ; } } return true ; } else { return a === b ; } } export function cloneExp < T extends MalVal > ( exp , deep = false ) { switch ( getType ( exp ) ) { case MalType . List : { const children = deep ? ( exp as MalSeq ) . map ( e => cloneExp ( e , true ) ) : ( exp as MalSeq ) ; const cloned = createList ( ... children ) ; if ( Array . isArray ( ( exp as MalNode ) [ M_DELIMITERS ] ) ) { ; ( cloned as MalNode ) [ M_DELIMITERS ] = [ ... ( exp as MalNode ) [ M_DELIMITERS ] ] ; } return cloned as T ; } case MalType . Vector : { const children = deep ? ( exp as MalSeq ) . map ( e => cloneExp ( e , true ) ) : ( exp as MalSeq ) ; const cloned = createVector ( ... children ) ; if ( Array . isArray ( ( exp as MalNode ) [ M_DELIMITERS ] ) ) { ; ( cloned as MalNode ) [ M_DELIMITERS ] = [ ... ( exp as MalNode ) [ M_DELIMITERS ] ] ; } return cloned as T ; } case MalType . Map : { const cloned = deep ? Object . fromEntries ( Object . entries ( exp as MalMap ) . map ( ( [ k , v ] ) => [ k , cloneExp ( v , true ) , ] ) ) : { ... ( exp as MalMap ) } ; if ( Array . isArray ( ( exp as MalNode ) [ M_DELIMITERS ] ) ) { ; ( cloned as MalNode ) [ M_DELIMITERS ] = [ ... ( exp as MalNode ) [ M_DELIMITERS ] ] ; } return cloned as T ; } case MalType . Function : case MalType . Macro : { const fn = function ( this , ... args ) { return ( exp as MalFunc ) . apply ( this , args ) ; } ; return Object . assign ( fn , exp ) as T ; } case MalType . Symbol : return symbolFor ( ( exp as MalSymbol ) . value ) as T ; default : return exp ; } } export function getEvaluated ( exp , deep = true ) { if ( exp instanceof Object && M_EVAL in exp ) { const evaluated = ( exp as MalNode ) [ M_EVAL ] ; if ( isList ( evaluated ) && deep ) { return getEvaluated ( evaluated , deep ) ; } else { return evaluated ; } } else { return exp ; } } export function getName ( exp ) { switch ( getType ( exp ) ) { case MalType . String : return exp as string ; case MalType . Keyword : return ( exp as string ) . slice ( 1 ) ; case MalType . Symbol : return ( exp as MalSymbol ) . value ; default : throw new MalError ( 'getName() can only extract the name by string/keyword/symbol' ) ; } } export function createMalFunc ( fn , exp , env , params , meta = null , ismacro = false ) { const attrs = { [ M_AST ] : exp , [ M_ENV ] : env , [ M_PARAMS ] : params , [ M_META ] : meta , [ M_ISMACRO ] : ismacro , } ; return Object . assign ( fn , attrs ) ; } export const isFunc = ( exp ) : exp is MalFunc => exp instanceof Function ; export const isMalFunc = ( obj ) : obj is MalFunc => obj instanceof Function && ( obj as MalFunc ) [ M_AST ] ? true : false ; export const isString = ( obj ) : obj is string => getType ( obj ) === MalType . String ; export class MalSymbol { public constructor ( public value ) { ; ( this as any ) [ M_TYPE ] = MalType . Symbol ; } public set def ( def ) { ; ( this as any ) [ M_DEF ] = def ; } public get def ( ) : MalSeq | null { return ( this as any ) [ M_DEF ] || null ; } public set evaluated ( value ) { ; ( this as any ) [ M_EVAL ] = value ; } public get evaluated ( ) : MalVal { return ( this as any ) [ M_EVAL ] ; } public toString ( ) { return this . value ; } } export const isSymbol = ( obj ) : obj is MalSymbol => getType ( obj ) === MalType . Symbol ; export const isSymbolFor = ( obj , name ) : obj is MalSymbol => isSymbol ( obj ) && obj . value === name ; export const symbolFor = ( value ) => new MalSymbol ( value ) ; const KEYWORD_PREFIX = '\u029e' ; export const isKeyword = ( obj ) : obj is string => getType ( obj ) === MalType . Keyword ; export const keywordFor = ( k ) => KEYWORD_PREFIX + k ; export const isList = ( obj ) : obj is MalSeq => { return Array . isArray ( obj ) && ( obj as any ) [ M_ISLIST ] ; } ; export function createList ( ... coll ) { ; ( coll as MalSeq ) [ M_ISLIST ] = true ; coll . forEach ( ( child , i ) => { if ( isNode ( child ) ) { child [ M_OUTER ] = coll as MalSeq ; child [ M_OUTER_INDEX ] = i ; } } ) ; return coll as MalSeq ; } export const isVector = ( obj ) : obj is MalSeq => { return ( ( Array . isArray ( obj ) && ! ( obj as any ) [ M_ISLIST ] ) || obj instanceof Float32Array ) ; } ; export function createVector ( ... coll ) { coll . forEach ( ( child , i ) => { if ( isNode ( child ) ) { child [ M_OUTER ] = coll as MalSeq ; child [ M_OUTER_INDEX ] = i ; } } ) ; return coll as MalSeq ; } export const isMap = ( obj ) : obj is MalMap => getType ( obj ) === MalType . Map ; export function assocBang ( hm , ... args ) { if ( args . length % 2 === 1 ) { throw new MalError ( 'Odd number of map arguments' ) ; } for ( let i = 0 ; i < args . length ; i += 2 ) { if ( typeof args [ i ] !== 'string' ) { throw new MalError ( 'Hash map can only use string/symbol/keyword as key' ) ; } hm [ args [ i ] ] = args [ i + 1 ] ; } return hm ; } export class MalAtom { public constructor ( public value ) { ; ( this as any ) [ M_TYPE ] = MalType . Atom ; } } export function malEquals ( a , b ) { const type = getType ( a ) ; const typeB = getType ( b ) ; if ( type !== typeB ) { return false ; } switch ( type ) { case MalType . List : case MalType . Vector : if ( ( a as MalVal [ ] ) . length !== ( b as MalVal [ ] ) . length ) { return false ; } for ( let i = 0 ; i < ( a as MalVal [ ] ) . length ; i ++ ) { if ( ! malEquals ( ( a as MalVal [ ] ) [ i ] , ( b as MalVal [ ] ) [ i ] ) ) { return false ; } } return true ; case MalType . Map : { const keys = Object . keys ( a as MalMap ) ; if ( keys . length !== Object . keys ( b as MalMap ) . length ) { return false ; } for ( const key of keys ) { if ( ! malEquals ( ( a as MalMap ) [ key ] , ( b as MalMap ) [ key ] ) ) { return false ; } } return true ; } case MalType . Symbol : return ( a as MalSymbol ) . value === ( b as MalSymbol ) . value ; default : return a === b ; } }