Skip to content

Commit 5dba6a5

Browse files
CZX123martin-henz
andauthored
CSE Machine: Fixes for variadic function & global closures, added faded unreferenced objects (#2906)
* Initial Commit * More fixes and added faded gc objects * Run format * Fix params text and added SourceObject to display runes correctly * bumping js-slang * Revamp unreferenced behavior and update snapshots * Run format * Update snapshot * Bump js-slang --------- Co-authored-by: henz <henz@comp.nus.edu.sg>
1 parent c0ae307 commit 5dba6a5

26 files changed

+390
-306
lines changed

package.json

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -51,7 +51,7 @@
5151
"flexboxgrid-helpers": "^1.1.3",
5252
"hastscript": "^9.0.0",
5353
"java-slang": "^1.0.4",
54-
"js-slang": "^1.0.62",
54+
"js-slang": "^1.0.64",
5555
"js-yaml": "^4.1.0",
5656
"konva": "^9.2.0",
5757
"lodash": "^4.17.21",

src/commons/utils/JsSlangHelper.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -94,7 +94,7 @@ export function visualizeCseMachine({ context }: { context: Context }) {
9494
try {
9595
CseMachine.drawCse(context);
9696
} catch (err) {
97-
throw new Error('CSE machine is not enabled');
97+
console.error(err);
9898
}
9999
}
100100

src/features/cseMachine/CseMachine.tsx

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -4,7 +4,7 @@ import React from 'react';
44

55
import { Layout } from './CseMachineLayout';
66
import { EnvTree } from './CseMachineTypes';
7-
import { deepCopyTree, getEnvID } from './CseMachineUtils';
7+
import { deepCopyTree, getEnvId } from './CseMachineUtils';
88

99
type SetVis = (vis: React.ReactNode) => void;
1010
type SetEditorHighlightedLines = (segments: [number, number][]) => void;
@@ -75,7 +75,7 @@ export default class CseMachine {
7575
static drawCse(context: Context) {
7676
// store environmentTree at last breakpoint.
7777
CseMachine.environmentTree = deepCopyTree(context.runtime.environmentTree as EnvTree);
78-
CseMachine.currentEnvId = getEnvID(context.runtime.environments[0]);
78+
CseMachine.currentEnvId = getEnvId(context.runtime.environments[0]);
7979
if (!this.setVis || !context.runtime.control || !context.runtime.stash)
8080
throw new Error('CSE machine not initialized');
8181
CseMachine.control = context.runtime.control;

src/features/cseMachine/CseMachineConfig.ts

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -46,7 +46,9 @@ export const Config = Object.freeze({
4646
MaxExportHeight: 12000,
4747

4848
SA_WHITE: '#999999',
49+
SA_FADED_WHITE: '#5b6773',
4950
SA_BLUE: '#2c3e50',
51+
SA_FADED_BLUE: '#BBB',
5052
PRINT_BACKGROUND: 'white',
5153
SA_CURRENT_ITEM: '#030fff',
5254

src/features/cseMachine/CseMachineLayout.tsx

Lines changed: 65 additions & 67 deletions
Original file line numberDiff line numberDiff line change
@@ -19,23 +19,28 @@ import CseMachine from './CseMachine';
1919
import { CseAnimation } from './CseMachineAnimation';
2020
import { Config, ShapeDefaultProps } from './CseMachineConfig';
2121
import {
22-
Closure,
2322
Data,
2423
DataArray,
2524
EnvTree,
2625
EnvTreeNode,
2726
GlobalFn,
28-
ReferenceType
27+
NonGlobalFn,
28+
ReferenceType,
29+
StreamFn
2930
} from './CseMachineTypes';
3031
import {
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
*/

src/features/cseMachine/CseMachineTypes.ts

Lines changed: 29 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,7 @@ import {
22
EnvTree as EnvironmentTree,
33
EnvTreeNode as EnvironmentTreeNode
44
} from 'js-slang/dist/createContext';
5-
import JsSlangClosure from 'js-slang/dist/interpreter/closure';
5+
import JsSlangClosure from 'js-slang/dist/cse-machine/closure';
66
import { Environment } from 'js-slang/dist/types';
77
import { KonvaEventObject } from 'konva/lib/Node';
88
import React from 'react';
@@ -47,20 +47,44 @@ export type Unassigned = symbol;
4747
/** types of primitives in JS Slang */
4848
export type Primitive = number | string | boolean | null | undefined;
4949

50-
/** types of in-built functions in JS Slang */
51-
export type GlobalFn = Function;
50+
/** types of source objects such as runes */
51+
export type SourceObject = {
52+
[index: string]: any;
53+
toReplString: () => string;
54+
};
5255

53-
/** types of functions in JS Slang */
56+
/** types of closures in JS Slang, redefined here for convenience. */
5457
export type Closure = JsSlangClosure;
5558

59+
/** types of built-in functions in JS Slang */
60+
export type BuiltInFn = () => never; // Use `never` to differentiate from `StreamFn`
61+
62+
/** types of pre-defined functions in JS Slang */
63+
export type PredefinedFn = Omit<Closure, 'predefined'> & { predefined: true };
64+
65+
/**
66+
* Special type of a function returned from calling `stream`. It is mostly similar to a global
67+
* function, but has the extra `environment` property as it should be drawn next to the frame
68+
* in which `stream` is called.
69+
*
70+
* TODO: remove this and all other `StreamFn` code if `stream` becomes a pre-defined function
71+
*/
72+
export type StreamFn = (() => [any, StreamFn] | null) & { environment: Env };
73+
74+
/** types of global functions in JS Slang */
75+
export type GlobalFn = BuiltInFn | PredefinedFn;
76+
77+
/** types of global functions in JS Slang */
78+
export type NonGlobalFn = (Omit<Closure, 'predefined'> & { predefined: false }) | StreamFn;
79+
5680
/** types of arrays in JS Slang */
5781
export type DataArray = Data[] & {
5882
readonly id: string;
5983
environment: Env;
6084
};
6185

6286
/** the types of data in the JS Slang context */
63-
export type Data = Primitive | Closure | GlobalFn | Unassigned | DataArray;
87+
export type Data = Primitive | SourceObject | NonGlobalFn | GlobalFn | Unassigned | DataArray;
6488

6589
/** modified `Environment` to store children and associated frame */
6690
export type Env = Environment;

0 commit comments

Comments
 (0)