1
+ import Heap from 'js-slang/dist/cse-machine/heap' ;
1
2
import { Control , Stash } from 'js-slang/dist/cse-machine/interpreter' ;
2
- import { Frame } from 'js-slang/dist/types' ;
3
3
import { KonvaEventObject } from 'konva/lib/Node' ;
4
4
import React , { RefObject } from 'react' ;
5
5
import { Layer , Rect , Stage } from 'react-konva' ;
6
6
import classes from 'src/styles/Draggable.module.scss' ;
7
7
8
+ import { Binding } from './components/Binding' ;
8
9
import { ControlStack } from './components/ControlStack' ;
9
10
import { Level } from './components/Level' ;
10
11
import { StashStack } from './components/StashStack' ;
@@ -17,17 +18,26 @@ import { Value } from './components/values/Value';
17
18
import CseMachine from './CseMachine' ;
18
19
import { CseAnimation } from './CseMachineAnimation' ;
19
20
import { Config , ShapeDefaultProps } from './CseMachineConfig' ;
20
- import { Data , EnvTree , EnvTreeNode , ReferenceType } from './CseMachineTypes' ;
21
21
import {
22
+ Closure ,
23
+ Data ,
24
+ DataArray ,
25
+ EnvTree ,
26
+ EnvTreeNode ,
27
+ GlobalFn ,
28
+ ReferenceType
29
+ } from './CseMachineTypes' ;
30
+ import {
31
+ convertClosureToGlobalFn ,
22
32
deepCopyTree ,
23
33
getNextChildren ,
24
- isArray ,
25
- isDummyKey ,
26
- isFn ,
34
+ isClosure ,
35
+ isDataArray ,
27
36
isFunction ,
28
37
isGlobalFn ,
29
38
isPrimitiveData ,
30
- isUnassigned
39
+ isUnassigned ,
40
+ setDifference
31
41
} from './CseMachineUtils' ;
32
42
33
43
/** this class encapsulates the logic for calculating the layout */
@@ -135,8 +145,8 @@ export class Layout {
135
145
Layout . control = control ;
136
146
Layout . stash = stash ;
137
147
138
- // remove program environment and merge bindings into global env
139
- Layout . removeProgramEnv ( ) ;
148
+ // remove prelude environment and merge bindings into global env
149
+ Layout . removePreludeEnv ( ) ;
140
150
// remove global functions that are not referenced in the program
141
151
Layout . removeUnreferencedGlobalFns ( ) ;
142
152
// initialize levels and frames
@@ -182,76 +192,128 @@ export class Layout {
182
192
this . stashComponent = new StashStack ( this . stash ) ;
183
193
}
184
194
185
- /** remove program environment containing predefined functions */
186
- private static removeProgramEnv ( ) {
195
+ /**
196
+ * remove prelude environment containing predefined functions, by merging the prelude
197
+ * objects into the global environment head and heap
198
+ */
199
+ private static removePreludeEnv ( ) {
187
200
if ( ! Layout . globalEnvNode . children ) return ;
188
201
189
- const programEnvNode = Layout . globalEnvNode . children [ 0 ] ;
202
+ const preludeEnvNode = Layout . globalEnvNode . children [ 0 ] ;
203
+ const preludeEnv = preludeEnvNode . environment ;
190
204
const globalEnvNode = Layout . globalEnvNode ;
205
+ const globalEnv = globalEnvNode . environment ;
191
206
192
- // merge programEnvNode bindings into globalEnvNode
193
- globalEnvNode . environment . head = {
194
- ...programEnvNode . environment . head ,
195
- ...globalEnvNode . environment . head
196
- } ;
207
+ const preludeValueKeyMap = new Map (
208
+ Object . entries ( preludeEnv . head ) . map ( ( [ key , value ] ) => [ value , key ] )
209
+ ) ;
197
210
198
- // update globalEnvNode children
199
- if ( programEnvNode . children ) {
200
- globalEnvNode . resetChildren ( programEnvNode . children ) ;
211
+ // Change environments of each array and closure in the prelude to be the global environment
212
+ 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 ;
218
+ }
201
219
}
202
220
203
- // go through new bindings and update functions to be global functions
204
- // by removing extra props such as functionName
205
- for ( const value of Object . values ( globalEnvNode . environment . head ) ) {
206
- if ( isFn ( value ) ) {
207
- delete ( value as { functionName ?: string } ) . functionName ;
221
+ // update globalEnvNode children
222
+ globalEnvNode . resetChildren ( preludeEnvNode . children ) ;
223
+
224
+ // update the tail of each child's environment to point to the global environment
225
+ globalEnvNode . children . forEach ( node => {
226
+ node . environment . tail = globalEnv ;
227
+ } ) ;
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 ) ;
208
233
}
209
234
}
210
235
}
211
236
212
237
/** remove any global functions not referenced elsewhere in the program */
213
238
private static removeUnreferencedGlobalFns ( ) : void {
214
- const referencedGlobalFns = new Set < ( ) => any > ( ) ;
215
- const visitedData = new Set < Data [ ] > ( ) ;
239
+ const referencedGlobalFns = new Set < GlobalFn > ( ) ;
240
+ const visitedData = new Set < DataArray > ( ) ;
241
+
216
242
const findGlobalFnReferences = ( envNode : EnvTreeNode ) : void => {
217
- for ( const data of Object . values ( envNode . environment . head ) ) {
243
+ const headValues = Object . values ( envNode . environment . head ) ;
244
+ const unreferenced = setDifference ( envNode . environment . heap . getHeap ( ) , new Set ( headValues ) ) ;
245
+ for ( const data of headValues ) {
218
246
if ( isGlobalFn ( data ) ) {
219
247
referencedGlobalFns . add ( data ) ;
220
- } else if ( isArray ( data ) ) {
248
+ } else if ( isDataArray ( data ) ) {
221
249
findGlobalFnReferencesInData ( data ) ;
222
250
}
223
251
}
224
- if ( envNode . children ) {
225
- envNode . children . forEach ( findGlobalFnReferences ) ;
252
+ for ( const data of unreferenced ) {
253
+ // The heap will never contain a global function, unless it is the global/prelude environment
254
+ if ( isDataArray ( data ) ) {
255
+ findGlobalFnReferencesInData ( data ) ;
256
+ }
226
257
}
258
+ envNode . children . forEach ( findGlobalFnReferences ) ;
227
259
} ;
228
260
229
- const findGlobalFnReferencesInData = ( data : Data [ ] ) : void => {
261
+ const findGlobalFnReferencesInData = ( data : DataArray ) : void => {
262
+ if ( visitedData . has ( data ) ) return ;
263
+ visitedData . add ( data ) ;
230
264
data . forEach ( d => {
231
265
if ( isGlobalFn ( d ) ) {
232
266
referencedGlobalFns . add ( d ) ;
233
- } else if ( isArray ( d ) && ! visitedData . has ( d ) ) {
234
- visitedData . add ( d ) ;
267
+ } else if ( isDataArray ( d ) ) {
235
268
findGlobalFnReferencesInData ( d ) ;
236
269
}
237
270
} ) ;
238
271
} ;
239
272
240
- if ( Layout . globalEnvNode . children ) {
241
- Layout . globalEnvNode . children . forEach ( findGlobalFnReferences ) ;
273
+ // First, add any referenced global functions in the stash
274
+ for ( const item of Layout . stash . getStack ( ) ) {
275
+ if ( isGlobalFn ( item ) ) {
276
+ referencedGlobalFns . add ( item ) ;
277
+ } else if ( isDataArray ( item ) ) {
278
+ findGlobalFnReferencesInData ( item ) ;
279
+ }
280
+ }
281
+
282
+ // Then, find any references within any arrays inside the global environment heap
283
+ for ( const data of Layout . globalEnvNode . environment . heap . getHeap ( ) ) {
284
+ if ( isDataArray ( data ) ) {
285
+ findGlobalFnReferencesInData ( data ) ;
286
+ }
287
+ }
288
+
289
+ // Finally, find any references inside the global environment children
290
+ Layout . globalEnvNode . children . forEach ( findGlobalFnReferences ) ;
291
+
292
+ const functionNames = new Map (
293
+ Object . entries ( Layout . globalEnvNode . environment . head ) . map ( ( [ key , value ] ) => [ value , key ] )
294
+ ) ;
295
+
296
+ const newHead = { } ;
297
+ 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
+ }
242
303
}
243
304
244
- const newFrame : Frame = { } ;
245
- for ( const [ key , data ] of Object . entries ( Layout . globalEnvNode . environment . head ) ) {
246
- if ( referencedGlobalFns . has ( data ) || isDummyKey ( key ) ) {
247
- newFrame [ key ] = data ;
305
+ // add any arrays from the original heap to the new heap
306
+ for ( const item of Layout . globalEnvNode . environment . heap . getHeap ( ) ) {
307
+ if ( isDataArray ( item ) ) {
308
+ newHeap . add ( item ) ;
248
309
}
249
310
}
250
311
251
312
Layout . globalEnvNode . environment . head = {
252
313
[ Config . GlobalFrameDefaultText ] : Symbol ( ) ,
253
- ...newFrame
314
+ ...newHead
254
315
} ;
316
+ Layout . globalEnvNode . environment . heap = newHeap ;
255
317
}
256
318
257
319
public static width ( ) : number {
@@ -296,9 +358,9 @@ export class Layout {
296
358
* else, return the existing value */
297
359
static createValue ( data : Data , reference : ReferenceType ) : Value {
298
360
if ( isUnassigned ( data ) ) {
299
- return new UnassignedValue ( [ reference ] ) ;
361
+ return new UnassignedValue ( reference ) ;
300
362
} else if ( isPrimitiveData ( data ) ) {
301
- return new PrimitiveValue ( data , [ reference ] ) ;
363
+ return new PrimitiveValue ( data , reference ) ;
302
364
} else {
303
365
// try to find if this value is already created
304
366
const existingValue = Layout . values . get ( data ) ;
@@ -308,16 +370,22 @@ export class Layout {
308
370
}
309
371
310
372
// else create a new one
311
- let newValue : Value = new PrimitiveValue ( null , [ reference ] ) ;
312
- if ( isArray ( data ) ) {
313
- newValue = new ArrayValue ( data , [ reference ] ) ;
373
+ let newValue : Value = new PrimitiveValue ( null , reference ) ;
374
+ if ( isDataArray ( data ) ) {
375
+ newValue = new ArrayValue ( data , reference ) ;
314
376
} else if ( isFunction ( data ) ) {
315
- if ( isFn ( data ) ) {
377
+ if ( isClosure ( data ) ) {
316
378
// normal JS Slang function
317
- newValue = new FnValue ( data , [ reference ] ) ;
379
+ newValue = new FnValue ( data , reference ) ;
318
380
} else {
319
- // function from the global env (has no extra props such as env, fnName)
320
- newValue = new GlobalFnValue ( data , [ reference ] ) ;
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
+ }
321
389
}
322
390
}
323
391
0 commit comments