@@ -64,8 +64,23 @@ class A {
6464 }
6565}
6666
67- // can be extended with call by need functionality
68- class Env extends Map { }
67+ class Env extends Map {
68+ // use inherited set, get for copying references
69+ // insert and retrieve values with setThunk and getValue
70+ // encoding of value is a Generator Object that yields value forever - this is opaque
71+ setThunk ( i , thunk ) {
72+ this . set ( i , function * ( ) {
73+ // console.warn(`expensively calculating ${ i }`);
74+ const result = thunk ( ) ;
75+ while ( true ) yield result ;
76+ } ( ) ) ;
77+ return this ;
78+ }
79+ getValue ( i ) {
80+ // console.warn(`inexpensively fetching ${ i }`);
81+ return this . get ( i ) . next ( ) . value ;
82+ }
83+ }
6984
7085// Term and Env pair, used internally to keep track of current computation in eval
7186class Tuple {
@@ -74,16 +89,11 @@ class Tuple {
7489 toString ( ) { return this . term . toString ( ) ; }
7590}
7691
77- // Used to insert an external (JS) value into evaluation manually (avoiding implicit number conversion)
78- function Primitive ( v ) { return new Tuple ( new V ( v . name || "<primitive>" ) , new Env ( [ [ v . name || "<primitive>" , v ] ] ) ) ; }
92+ // Used to insert an external (JS) value into evaluation manually ( avoiding implicit number conversion )
93+ function Primitive ( v ) { return new Tuple ( new V ( "<primitive>" ) , new Env ( [ [ "<primitive>" , function * ( ) { while ( true ) yield v ; } ( ) ] ] ) ) ; }
7994
80- const primitives = {
81- trace : function ( v ) { console . log ( String ( v . term ) ) ; return v ; }
82- }
83-
84- for ( const p in primitives ) {
85- primitives [ p ] = Primitive ( primitives [ p ] ) ;
86- }
95+ const primitives = new Env ;
96+ primitives . setThunk ( "trace" , ( ) => evalLC ( new Tuple ( Primitive ( function ( v ) { console . log ( String ( v . term ) ) ; return v ; } ) , new Env ) ) ) ;
8797
8898const Y = new L ( "f" , new A ( new L ( "x" , new A ( new V ( "f" ) , new A ( new V ( "x" ) , new V ( "x" ) ) ) ) , new L ( "x" , new A ( new V ( "f" ) , new A ( new V ( "x" ) , new V ( "x" ) ) ) ) ) ) ;
8999
@@ -128,9 +138,9 @@ function toIntWith(cfg={}) {
128138 const { numEncoding, verbosity} = Object . assign ( { } , config , cfg ) ;
129139 return function toInt ( term ) {
130140 try {
131- if ( numEncoding === "Church" )
132- return term ( x => x + 1 ) ( Primitive ( 0 ) ) ;
133- else if ( numEncoding === "Scott" ) {
141+ if ( numEncoding === "Church" ) {
142+ return term ( x => x + 1 ) ( Primitive ( 0 ) ) ; // still stack-limited
143+ } else if ( numEncoding === "Scott" ) {
134144 let result = 0 , evaluating = true ;
135145 while ( evaluating )
136146 term ( ( ) => evaluating = false ) ( n => ( ) => { term = n ; result ++ } ) ( ) ;
@@ -140,19 +150,18 @@ function toIntWith(cfg={}) {
140150 while ( evaluating )
141151 term ( ( ) => evaluating = false ) ( n => ( ) => { term = n ; bit *= 2 } ) ( n => ( ) => { term = n ; result += bit ; bit *= 2 } ) ( ) ;
142152 return result ;
143- } else if ( numEncoding === "None" ) {
153+ } else if ( numEncoding === "None" )
144154 return term ;
145- } else {
155+ else
146156 return numEncoding . toInt ( term ) ; // Custom encoding
147- }
148157 } catch ( e ) {
149158 if ( verbosity >= "Concise" ) console . error ( `toInt: ${ term } is not a number in numEncoding ${ numEncoding } ` ) ;
150159 throw e ;
151160 }
152161 } ;
153162}
154163
155- // parse :: String -> { String: Term}
164+ // parse :: String -> Env { String => Term }
156165function parse ( code ) { return parseWith ( ) ( code ) ; }
157166
158167function parseWith ( cfg = { } ) {
@@ -164,32 +173,32 @@ function parseWith(cfg={}) {
164173 const FV = term . free ( ) ; FV . delete ( "()" ) ;
165174 if ( purity === "Let" )
166175 return Array . from ( FV ) . reduce ( ( tm , nm ) => {
167- if ( nm in env )
168- return new A ( new L ( nm , tm ) , env [ nm ] ) ;
169- else {
176+ if ( env . has ( nm ) ) {
177+ tm . env . set ( nm , env . get ( nm ) ) ;
178+ return tm ;
179+ } else {
170180 if ( verbosity >= "Concise" ) console . error ( `parse: while defining ${ name } = ${ term } ` ) ;
171181 throw new ReferenceError ( `undefined free variable ${ nm } ` ) ;
172182 }
173- } , term ) ;
183+ } , new Tuple ( term , new Env ) ) ;
174184 else if ( purity === "LetRec" )
175- return Array . from ( FV ) . reduce ( ( tm , nm ) => { // this wraps terms in a snapshot of their environment at the moment of defining // TODO: tidy it
185+ return Array . from ( FV ) . reduce ( ( tm , nm ) => {
176186 if ( nm === name )
177187 return tm ;
178- else if ( nm in env )
179- return new A ( new L ( nm , tm ) , env [ nm ] ) ;
180- else {
188+ else if ( env . has ( nm ) ) {
189+ tm . env . set ( nm , env . get ( nm ) ) ;
190+ return tm ;
191+ } else {
181192 if ( verbosity >= "Concise" ) console . error ( `parse: while defining ${ name } = ${ term } ` ) ;
182193 throw new ReferenceError ( `undefined free variable ${ nm } ` ) ;
183194 }
184- }
185- , FV . has ( name ) ? new A ( Y , new L ( name , term ) ) : term
186- ) ;
195+ } , new Tuple ( FV . has ( name ) ? new A ( Y , new L ( name , term ) ) : term , new Env ) ) ;
187196 else if ( purity === "PureLC" )
188197 if ( FV . size ) {
189198 if ( verbosity >= "Concise" ) console . error ( `parse: while defining ${ name } = ${ term } ` ) ;
190199 throw new EvalError ( `unresolvable free variable(s) ${ Array . from ( FV ) } : all expressions must be closed in PureLC mode` ) ;
191200 } else
192- return term ;
201+ return new Tuple ( term , new Env ) ;
193202 else
194203 throw new RangeError ( `config.purity: unknown setting "${ purity } "` ) ;
195204 }
@@ -289,15 +298,16 @@ function parseWith(cfg={}) {
289298 const [ i , r ] = defn ( 0 ) ;
290299 if ( i === code . length ) {
291300 const [ name , term ] = r ;
292- return Object . assign ( env , { [ name ] : wrap ( name , term ) } ) ;
301+ const wrapped = wrap ( name , term ) ;
302+ return env . setThunk ( name , ( ) => evalLC ( wrapped ) ) ;
293303 } else
294304 error ( i , "defn: incomplete parse" ) ;
295305 }
296- return code . replace ( / # .* $ / gm, "" ) // Ignore comments
297- . replace ( / \n (? = \s ) / g, "" )
298- . split ( '\n' )
299- . filter ( term => / \S / . test ( term ) )
300- . reduce ( parseTerm , Object . assign ( { } , primitives ) ) ;
306+ return code . replace ( / # .* $ / gm, "" ) // ignore comments
307+ . replace ( / \n (? = \s ) / g, "" ) // continue lines
308+ . split ( '\n' ) // split lines
309+ . filter ( term => / \S / . test ( term ) ) // skip empty lines
310+ . reduce ( parseTerm , new Env ( primitives ) ) ; // parse lines
301311 }
302312}
303313
@@ -307,27 +317,10 @@ function compileWith(cfg={}) {
307317 const { numEncoding, purity, verbosity} = Object . assign ( { } , config , cfg ) ;
308318 return function compile ( code = fs . readFileSync ( "./solution.txt" , "utf8" ) ) {
309319 const env = parseWith ( { numEncoding, purity, verbosity} ) ( code ) ;
310- for ( const [ name , term ] of Object . entries ( env ) )
311- Object . defineProperty ( env , name , {
312- get ( ) {
313- return env . _cache . has ( name )
314- ? env . _cache . get ( name )
315- : env . _cache . set ( name , evalLC ( term ) ) . get ( name ) ;
316- }
317- } ) ;
318- env . _cache = new Map ; // this needs tearing down when Env gets smart
319- const envHandler = {
320- get : function ( target , property ) {
321- // Custom undefined error when trying to access functions not defined in environment
322- const result = Reflect . get ( target , property ) ;
323- if ( result === undefined ) {
324- throw ReferenceError ( `${ property } is not defined.` ) ;
325- } else {
326- return result ;
327- }
328- }
329- } ;
330- return new Proxy ( env , envHandler ) ;
320+ const r = { } ;
321+ for ( const [ name ] of env )
322+ Object . defineProperty ( r , name , { get ( ) { return env . getValue ( name ) ; } } ) ;
323+ return r ;
331324 } ;
332325}
333326
@@ -338,25 +331,23 @@ function evalLC(term) {
338331 function awaitArg ( term , stack , env ) {
339332
340333 // callback function which will apply the input to the term
341- const result = function ( arg ) {
334+ function result ( arg ) {
342335 let argEnv ;
343336 if ( arg . term && arg . env ) ( { term : arg , env : argEnv } = arg ) ; // If callback is passed another callback, or a term
344337 const termVal = new Tuple ( typeof arg !== 'number' ? arg : fromInt ( arg ) , new Env ( argEnv ) ) ;
345- const newEnv = new Env ( env ) . set ( term . name , termVal ) ;
338+ const newEnv = new Env ( env ) . setThunk ( term . name , ( ) => evalLC ( termVal ) ) ;
346339 return runEval ( new Tuple ( term . body , newEnv ) , stack ) ;
347- } ;
348-
349- // object 'methods/attributes'
340+ }
350341 return Object . assign ( result , { term, env} ) ;
351342 }
352343
353- function runEval ( { term, env} , stack ) { // stack: [[term, isRight]], arg: Tuple , env = { name: term}
354- while ( ! ( term instanceof L ) || stack . length > 0 ) {
344+ function runEval ( { term, env} , stack ) { // stack: [[term, isRight]], term: LC term , env = Env { name => term }
345+ while ( ! ( term instanceof L ) || stack . length > 0 ) {
355346 if ( term instanceof V )
356347 if ( term . name === "()" )
357- { console . error ( `eval: evaluating undefined inside definition of "${ term . defName } "` ) ; throw new EvalError ; }
348+ { console . error ( `eval: evaluating undefined inside definition of "${ term . defName } "` ) ; throw new EvalError ; } // depend on verbosity here
358349 else {
359- let res = env . get ( term . name ) ;
350+ let res = env . getValue ( term . name ) ;
360351 if ( ! res . env )
361352 term = res ;
362353 else
@@ -368,11 +359,10 @@ function evalLC(term) {
368359 } else if ( term instanceof L ) {
369360 let [ { term : lastTerm , env : lastEnv } , isRight ] = stack . pop ( ) ;
370361 if ( isRight ) {
371- if ( term . name !== "_" ) {
372- env = new Env ( env ) . set ( term . name , new Tuple ( lastTerm , lastEnv ) ) ;
373- }
362+ if ( term . name !== "_" )
363+ env = new Env ( env ) . setThunk ( term . name , ( ) => evalLC ( new Tuple ( lastTerm , lastEnv ) ) ) ;
374364 term = term . body ;
375- } else { // Pass the function some other function. This might need redoing
365+ } else { // Pass the function some other function. This might need redoing // either redo or not. if it works, don't fix it.
376366 term = lastTerm ( awaitArg ( term , stack , env ) ) ;
377367 }
378368 } else if ( term instanceof Tuple ) {
@@ -398,7 +388,7 @@ function evalLC(term) {
398388 // We need input
399389 return awaitArg ( term , stack , env ) ;
400390 }
401- return runEval ( new Tuple ( term , new Env ) , [ ] ) ;
391+ return runEval ( term , [ ] ) ;
402392}
403393
404394Object . defineProperty ( Function . prototype , "valueOf" , { value : function valueOf ( ) { return toInt ( this ) ; } } ) ;
0 commit comments