@@ -19,23 +19,28 @@ import CseMachine from './CseMachine';
1919import { CseAnimation } from './CseMachineAnimation' ;
2020import { Config , ShapeDefaultProps } from './CseMachineConfig' ;
2121import {
22- Closure ,
2322 Data ,
2423 DataArray ,
2524 EnvTree ,
2625 EnvTreeNode ,
2726 GlobalFn ,
28- ReferenceType
27+ NonGlobalFn ,
28+ ReferenceType ,
29+ StreamFn
2930} from './CseMachineTypes' ;
3031import {
31- convertClosureToGlobalFn ,
32+ assert ,
3233 deepCopyTree ,
3334 getNextChildren ,
35+ isBuiltInFn ,
3436 isClosure ,
3537 isDataArray ,
36- isFunction ,
38+ isEnvEqual ,
3739 isGlobalFn ,
40+ isNonGlobalFn ,
3841 isPrimitiveData ,
42+ isSourceObject ,
43+ isStreamFn ,
3944 isUnassigned ,
4045 setDifference
4146} from './CseMachineUtils' ;
@@ -63,8 +68,6 @@ export class Layout {
6368 /** scale factor for zooming and out of canvas */
6469 static scaleFactor = 1.02 ;
6570
66- /** the environment tree */
67- static environmentTree : EnvTree ;
6871 /** the global environment */
6972 static globalEnvNode : EnvTreeNode ;
7073 /** grid of frames */
@@ -79,7 +82,7 @@ export class Layout {
7982 static previousStashComponent : StashStack ;
8083
8184 /** memoized values */
82- static values = new Map < Data , Value > ( ) ;
85+ static values = new Map < string | ( ( ) => any ) , Value > ( ) ;
8386 /** memoized layout */
8487 static prevLayout : React . ReactNode ;
8588 static currentDark : React . ReactNode ;
@@ -140,8 +143,7 @@ export class Layout {
140143 Layout . key = 0 ;
141144
142145 // deep copy so we don't mutate the context
143- Layout . environmentTree = deepCopyTree ( envTree ) ;
144- Layout . globalEnvNode = Layout . environmentTree . root ;
146+ Layout . globalEnvNode = deepCopyTree ( envTree ) . root ;
145147 Layout . control = control ;
146148 Layout . stash = stash ;
147149
@@ -201,50 +203,50 @@ export class Layout {
201203
202204 const preludeEnvNode = Layout . globalEnvNode . children [ 0 ] ;
203205 const preludeEnv = preludeEnvNode . environment ;
204- const globalEnvNode = Layout . globalEnvNode ;
205- const globalEnv = globalEnvNode . environment ;
206-
207- const preludeValueKeyMap = new Map (
208- Object . entries ( preludeEnv . head ) . map ( ( [ key , value ] ) => [ value , key ] )
209- ) ;
206+ const globalEnv = Layout . globalEnvNode . environment ;
207+
208+ // Add bindings from prelude environment head to global environment head
209+ for ( const [ key , value ] of Object . entries ( preludeEnv . head ) ) {
210+ delete preludeEnv . head [ key ] ;
211+ globalEnv . head [ key ] = value ;
212+ if ( isStreamFn ( value ) && isEnvEqual ( value . environment , preludeEnv ) ) {
213+ Object . defineProperty ( value , 'environment' , { value : globalEnv } ) ;
214+ }
215+ }
210216
211- // Change environments of each array and closure in the prelude to be the global environment
217+ // Move objects from prelude environment heap to global environment heap
212218 for ( const value of preludeEnv . heap . getHeap ( ) ) {
213- Object . defineProperty ( value , 'environment' , { value : globalEnvNode . environment } ) ;
214- globalEnv . heap . add ( value ) ;
215- const key = preludeValueKeyMap . get ( value ) ;
216- if ( key ) {
217- globalEnv . head [ key ] = value ;
219+ Object . defineProperty ( value , 'environment' , { value : globalEnv } ) ;
220+ if ( isDataArray ( value ) ) {
221+ for ( const item of value ) {
222+ if ( isStreamFn ( item ) && isEnvEqual ( item . environment , preludeEnv ) ) {
223+ Object . defineProperty ( item , 'environment' , { value : globalEnv } ) ;
224+ }
225+ }
218226 }
227+ preludeEnv . heap . move ( value , globalEnv . heap ) ;
219228 }
220229
221230 // update globalEnvNode children
222- globalEnvNode . resetChildren ( preludeEnvNode . children ) ;
231+ Layout . globalEnvNode . resetChildren ( preludeEnvNode . children ) ;
223232
224233 // update the tail of each child's environment to point to the global environment
225- globalEnvNode . children . forEach ( node => {
234+ Layout . globalEnvNode . children . forEach ( node => {
226235 node . environment . tail = globalEnv ;
227236 } ) ;
228-
229- // go through new bindings and update closures to be global functions
230- for ( const value of Object . values ( globalEnv . head ) ) {
231- if ( isClosure ( value ) ) {
232- convertClosureToGlobalFn ( value ) ;
233- }
234- }
235237 }
236238
237239 /** remove any global functions not referenced elsewhere in the program */
238240 private static removeUnreferencedGlobalFns ( ) : void {
239- const referencedGlobalFns = new Set < GlobalFn > ( ) ;
241+ const referencedFns = new Set < GlobalFn | NonGlobalFn > ( ) ;
240242 const visitedData = new Set < DataArray > ( ) ;
241243
242244 const findGlobalFnReferences = ( envNode : EnvTreeNode ) : void => {
243245 const headValues = Object . values ( envNode . environment . head ) ;
244246 const unreferenced = setDifference ( envNode . environment . heap . getHeap ( ) , new Set ( headValues ) ) ;
245247 for ( const data of headValues ) {
246248 if ( isGlobalFn ( data ) ) {
247- referencedGlobalFns . add ( data ) ;
249+ referencedFns . add ( data ) ;
248250 } else if ( isDataArray ( data ) ) {
249251 findGlobalFnReferencesInData ( data ) ;
250252 }
@@ -263,7 +265,7 @@ export class Layout {
263265 visitedData . add ( data ) ;
264266 data . forEach ( d => {
265267 if ( isGlobalFn ( d ) ) {
266- referencedGlobalFns . add ( d ) ;
268+ referencedFns . add ( d ) ;
267269 } else if ( isDataArray ( d ) ) {
268270 findGlobalFnReferencesInData ( d ) ;
269271 }
@@ -273,15 +275,18 @@ export class Layout {
273275 // First, add any referenced global functions in the stash
274276 for ( const item of Layout . stash . getStack ( ) ) {
275277 if ( isGlobalFn ( item ) ) {
276- referencedGlobalFns . add ( item ) ;
278+ referencedFns . add ( item ) ;
277279 } else if ( isDataArray ( item ) ) {
278280 findGlobalFnReferencesInData ( item ) ;
279281 }
280282 }
281283
282- // Then, find any references within any arrays inside the global environment heap
284+ // Then, find any references within any arrays inside the global environment heap,
285+ // and also add any non-global functions created in the global frame
283286 for ( const data of Layout . globalEnvNode . environment . heap . getHeap ( ) ) {
284- if ( isDataArray ( data ) ) {
287+ if ( isNonGlobalFn ( data ) ) {
288+ referencedFns . add ( data ) ;
289+ } else if ( isDataArray ( data ) ) {
285290 findGlobalFnReferencesInData ( data ) ;
286291 }
287292 }
@@ -293,13 +298,12 @@ export class Layout {
293298 Object . entries ( Layout . globalEnvNode . environment . head ) . map ( ( [ key , value ] ) => [ value , key ] )
294299 ) ;
295300
301+ let i = 0 ;
296302 const newHead = { } ;
297303 const newHeap = new Heap ( ) ;
298- for ( const fn of referencedGlobalFns ) {
299- newHead [ functionNames . get ( fn ) ! ] = fn ;
300- if ( fn . hasOwnProperty ( 'environment' ) ) {
301- newHeap . add ( fn as Closure ) ;
302- }
304+ for ( const fn of referencedFns ) {
305+ if ( isClosure ( fn ) ) newHeap . add ( fn ) ;
306+ if ( isGlobalFn ( fn ) ) newHead [ functionNames . get ( fn ) ?? `${ i ++ } ` ] = fn ;
303307 }
304308
305309 // add any arrays from the original heap to the new heap
@@ -349,50 +353,44 @@ export class Layout {
349353 }
350354 }
351355
352- /** memoize `Value` (used to detect circular references in non-primitive `Value`) */
353- static memoizeValue ( value : Value ) : void {
354- Layout . values . set ( value . data , value ) ;
355- }
356-
357- /** create an instance of the corresponding `Value` if it doesn't already exists,
358- * else, return the existing value */
356+ /** Creates an instance of the corresponding `Value` if it doesn't already exists,
357+ * else, returns the existing value */
359358 static createValue ( data : Data , reference : ReferenceType ) : Value {
360359 if ( isUnassigned ( data ) ) {
360+ assert ( reference instanceof Binding ) ;
361361 return new UnassignedValue ( reference ) ;
362362 } else if ( isPrimitiveData ( data ) ) {
363363 return new PrimitiveValue ( data , reference ) ;
364364 } else {
365- // try to find if this value is already created
366- const existingValue = Layout . values . get ( data ) ;
365+ const existingValue = Layout . values . get (
366+ isBuiltInFn ( data ) || isStreamFn ( data ) ? data : data . id
367+ ) ;
367368 if ( existingValue ) {
368369 existingValue . addReference ( reference ) ;
369370 return existingValue ;
370371 }
371372
372- // else create a new one
373- let newValue : Value = new PrimitiveValue ( null , reference ) ;
373+ let newValue : Value | undefined ;
374374 if ( isDataArray ( data ) ) {
375375 newValue = new ArrayValue ( data , reference ) ;
376- } else if ( isFunction ( data ) ) {
377- if ( isClosure ( data ) ) {
378- // normal JS Slang function
379- newValue = new FnValue ( data , reference ) ;
380- } else {
381- if ( reference instanceof Binding ) {
382- // function from the global env (has no extra props such as env, fnName)
383- newValue = new GlobalFnValue ( data , reference ) ;
384- } else {
385- // this should be impossible, since bindings for global function always get
386- // drawn first, before any other values like arrays get drawn
387- throw new Error ( 'First reference of global function value is not a binding!' ) ;
388- }
389- }
376+ } else if ( isGlobalFn ( data ) ) {
377+ assert ( reference instanceof Binding ) ;
378+ newValue = new GlobalFnValue ( data , reference ) ;
379+ } else if ( isNonGlobalFn ( data ) ) {
380+ newValue = new FnValue ( data , reference ) ;
381+ } else if ( isSourceObject ( data ) ) {
382+ return new PrimitiveValue ( data . toReplString ( ) , reference ) ;
390383 }
391384
392- return newValue ;
385+ return newValue ?? new PrimitiveValue ( null , reference ) ;
393386 }
394387 }
395388
389+ static memoizeValue ( data : GlobalFn | NonGlobalFn | StreamFn | DataArray , value : Value ) {
390+ if ( isBuiltInFn ( data ) || isStreamFn ( data ) ) Layout . values . set ( data , value ) ;
391+ else Layout . values . set ( data . id , value ) ;
392+ }
393+
396394 /**
397395 * Scrolls diagram to top left, resets the zoom, and saves the diagram as multiple images of width < MaxExportWidth.
398396 */
0 commit comments