import { MalVal , MalError , isSymbol , isMap , MalMap , MalSymbol , MalBind , isSeq , getType , MalSeq , MalType , symbolFor , keywordFor , } from './types' ; import { printExp } from '.' ; export default class Env { private data = new Map < string , MalVal > ( ) ; /**
     * Stores a definition expression `(devar sym val)` for each symbol
     */ private defs = new Map < string , MalSeq > ( ) ; private bindings ! : Env [ ] ; private exps ? : MalVal [ ] ; constructor ( protected outer = null , binds ? , exps ? , public name = 'let' ) { if ( this . root === this ) { this . bindings = [ ] ; } if ( exps ) { this . exps = exps ; } if ( binds && exps ) { this . bindAll ( binds , exps ) ; } } protected get root ( ) : Env { if ( this . outer ) { return this . outer . root ; } else { return this ; } } public getAllSymbols ( ) { const merged = this . outer ? new Map ( { ... this . outer . data , ... this . data } ) : this . data ; return Array . from ( merged . keys ( ) ) . map ( v => symbolFor ( v ) ) ; } public bindAll ( binds , exps ) { if ( isSymbol ( binds ) ) { this . set ( binds , exps ) ; } else { for ( let i = 0 ; i < binds . length ; i ++ ) { const bind = binds [ i ] ; const exp = exps [ i ] ; const bindType = getType ( bind ) ; if ( bindType === MalType . Symbol && ( bind as MalSymbol ) . value === '&' ) { this . set ( binds [ i + 1 ] as MalSymbol , exps . slice ( i ) ) ; i ++ ; continue ; } else if ( bind === keywordFor ( 'as' ) ) { this . set ( binds [ i + 1 ] as MalSymbol , [ ... exps ] ) ; break ; } switch ( bindType ) { case MalType . Symbol : { if ( exp === undefined ) { throw new MalError ( `[${ this . name }] parameter '${ bind }' is not specified` ) ; } this . set ( bind as MalSymbol , exp ) ; break ; } case MalType . Vector : { if ( ! isSeq ( exp ) ) { throw new MalError ( `[${ this . name }] The destruction parameter ${ printExp ( bind , true ) } is not specified as sequence` ) ; } this . bindAll ( bind as MalBind , exp as MalVal [ ] ) ; break ; } case MalType . Map : { if ( ! isMap ( exp ) ) { throw new MalError ( `[${ this . name }] The destruction parameter '${ printExp ( bind , true ) }'} is not specified as map` ) ; } const hashBinds = [ ] as MalBind , hashExps = [ ] ; for ( const [ key , sym ] of Object . entries ( bind ) ) { if ( key === keywordFor ( 'as' ) ) { hashBinds . push ( sym ) ; hashExps . push ( exp ) ; } else { if ( ! ( key in ( exp as MalMap ) ) ) { throw new MalError ( `[${ this . name }] The destruction keyword :${ key . slice ( 1 ) } does not exist on the parameter` ) ; } hashBinds . push ( sym ) ; hashExps . push ( ( exp as MalMap ) [ key ] ) ; } } this . bindAll ( hashBinds , hashExps ) ; break ; } default : throw new MalError ( `[${ this . name }] Invalid bind expression` ) ; } } } } public set ( symbol , value , def ? ) { this . data . set ( symbol . value , value ) ; if ( def ) { this . defs . set ( symbol . value , def ) ; } return value ; } public getDef ( symbol ) { if ( this . defs . has ( symbol . value ) ) { return this . defs . get ( symbol . value ) as MalSeq ; } if ( this . outer !== null ) { return this . outer . getDef ( symbol ) ; } return null ; } public find ( symbol ) { const bindings = this . root . bindings ; if ( bindings . length > 0 ) { const bindingEnv = bindings [ bindings . length - 1 ] ; const value = bindingEnv . find ( symbol ) ; if ( value !== undefined ) { return value ; } } if ( this . data . has ( symbol . value ) ) { return this . data . get ( symbol . value ) ; } let argIndex ; if ( symbol . value [ 0 ] === '%' && this . exps && this . exps . length >= ( argIndex = parseInt ( symbol . value . slice ( 1 ) ) - 1 || 0 ) ) { return this . exps [ argIndex ] ; } if ( this . outer !== null ) { return this . outer . find ( symbol ) ; } return undefined ; } public hasOwn ( symbol ) { return this . data . has ( symbol . value ) ; } public get ( symbol ) { const value = this . find ( symbol ) ; if ( value === undefined ) { throw new MalError ( `[${ this . name }] Symbol ${ symbol } not found` ) ; } return value ; } public getChain ( ) { let _env = this ; const envs = [ ... this . root . bindings . reverse ( ) ] ; do { envs . push ( _env ) ; _env = _env . outer ; } while ( _env ) ; return envs ; } public setOuter ( outer ) { this . outer = outer ; } public pushBinding ( env ) { const bindings = this . root . bindings ; const outer = bindings . length > 0 ? bindings [ bindings . length - 1 ] : null ; env . name = 'binding' ; env . outer = outer ; bindings . push ( env ) ; return env ; } public popBinding ( ) { this . root . bindings . pop ( ) ; } }