Skip to content

Commit ef6c172

Browse files
martin-henzCZX123
andauthored
bumping js-slang (#2885)
* Frontend changes for js-slang PR #1600 * bumping js-slang * Update tests, workaround `stream_tail` problems * Formatting * Update comment --------- Co-authored-by: CZX <CZX123@users.noreply.github.com>
1 parent 227edff commit ef6c172

20 files changed

+2366
-2098
lines changed

package.json

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -50,7 +50,7 @@
5050
"flexboxgrid": "^6.3.1",
5151
"flexboxgrid-helpers": "^1.1.3",
5252
"hastscript": "^9.0.0",
53-
"js-slang": "^1.0.52",
53+
"js-slang": "^1.0.55",
5454
"js-yaml": "^4.1.0",
5555
"konva": "^9.2.0",
5656
"lodash": "^4.17.21",

src/commons/mocks/ContextMocks.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -36,6 +36,7 @@ export function mockRuntimeContext(): Context {
3636
],
3737
control: null,
3838
stash: null,
39+
objectCount: 0,
3940
envStepsTotal: 0,
4041
breakpointSteps: [],
4142
changepointSteps: []

src/features/cseMachine/CseMachineLayout.tsx

Lines changed: 117 additions & 49 deletions
Original file line numberDiff line numberDiff line change
@@ -1,10 +1,11 @@
1+
import Heap from 'js-slang/dist/cse-machine/heap';
12
import { Control, Stash } from 'js-slang/dist/cse-machine/interpreter';
2-
import { Frame } from 'js-slang/dist/types';
33
import { KonvaEventObject } from 'konva/lib/Node';
44
import React, { RefObject } from 'react';
55
import { Layer, Rect, Stage } from 'react-konva';
66
import classes from 'src/styles/Draggable.module.scss';
77

8+
import { Binding } from './components/Binding';
89
import { ControlStack } from './components/ControlStack';
910
import { Level } from './components/Level';
1011
import { StashStack } from './components/StashStack';
@@ -17,17 +18,26 @@ import { Value } from './components/values/Value';
1718
import CseMachine from './CseMachine';
1819
import { CseAnimation } from './CseMachineAnimation';
1920
import { Config, ShapeDefaultProps } from './CseMachineConfig';
20-
import { Data, EnvTree, EnvTreeNode, ReferenceType } from './CseMachineTypes';
2121
import {
22+
Closure,
23+
Data,
24+
DataArray,
25+
EnvTree,
26+
EnvTreeNode,
27+
GlobalFn,
28+
ReferenceType
29+
} from './CseMachineTypes';
30+
import {
31+
convertClosureToGlobalFn,
2232
deepCopyTree,
2333
getNextChildren,
24-
isArray,
25-
isDummyKey,
26-
isFn,
34+
isClosure,
35+
isDataArray,
2736
isFunction,
2837
isGlobalFn,
2938
isPrimitiveData,
30-
isUnassigned
39+
isUnassigned,
40+
setDifference
3141
} from './CseMachineUtils';
3242

3343
/** this class encapsulates the logic for calculating the layout */
@@ -135,8 +145,8 @@ export class Layout {
135145
Layout.control = control;
136146
Layout.stash = stash;
137147

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();
140150
// remove global functions that are not referenced in the program
141151
Layout.removeUnreferencedGlobalFns();
142152
// initialize levels and frames
@@ -182,76 +192,128 @@ export class Layout {
182192
this.stashComponent = new StashStack(this.stash);
183193
}
184194

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() {
187200
if (!Layout.globalEnvNode.children) return;
188201

189-
const programEnvNode = Layout.globalEnvNode.children[0];
202+
const preludeEnvNode = Layout.globalEnvNode.children[0];
203+
const preludeEnv = preludeEnvNode.environment;
190204
const globalEnvNode = Layout.globalEnvNode;
205+
const globalEnv = globalEnvNode.environment;
191206

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+
);
197210

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+
}
201219
}
202220

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);
208233
}
209234
}
210235
}
211236

212237
/** remove any global functions not referenced elsewhere in the program */
213238
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+
216242
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) {
218246
if (isGlobalFn(data)) {
219247
referencedGlobalFns.add(data);
220-
} else if (isArray(data)) {
248+
} else if (isDataArray(data)) {
221249
findGlobalFnReferencesInData(data);
222250
}
223251
}
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+
}
226257
}
258+
envNode.children.forEach(findGlobalFnReferences);
227259
};
228260

229-
const findGlobalFnReferencesInData = (data: Data[]): void => {
261+
const findGlobalFnReferencesInData = (data: DataArray): void => {
262+
if (visitedData.has(data)) return;
263+
visitedData.add(data);
230264
data.forEach(d => {
231265
if (isGlobalFn(d)) {
232266
referencedGlobalFns.add(d);
233-
} else if (isArray(d) && !visitedData.has(d)) {
234-
visitedData.add(d);
267+
} else if (isDataArray(d)) {
235268
findGlobalFnReferencesInData(d);
236269
}
237270
});
238271
};
239272

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+
}
242303
}
243304

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);
248309
}
249310
}
250311

251312
Layout.globalEnvNode.environment.head = {
252313
[Config.GlobalFrameDefaultText]: Symbol(),
253-
...newFrame
314+
...newHead
254315
};
316+
Layout.globalEnvNode.environment.heap = newHeap;
255317
}
256318

257319
public static width(): number {
@@ -296,9 +358,9 @@ export class Layout {
296358
* else, return the existing value */
297359
static createValue(data: Data, reference: ReferenceType): Value {
298360
if (isUnassigned(data)) {
299-
return new UnassignedValue([reference]);
361+
return new UnassignedValue(reference);
300362
} else if (isPrimitiveData(data)) {
301-
return new PrimitiveValue(data, [reference]);
363+
return new PrimitiveValue(data, reference);
302364
} else {
303365
// try to find if this value is already created
304366
const existingValue = Layout.values.get(data);
@@ -308,16 +370,22 @@ export class Layout {
308370
}
309371

310372
// 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);
314376
} else if (isFunction(data)) {
315-
if (isFn(data)) {
377+
if (isClosure(data)) {
316378
// normal JS Slang function
317-
newValue = new FnValue(data, [reference]);
379+
newValue = new FnValue(data, reference);
318380
} 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+
}
321389
}
322390
}
323391

src/features/cseMachine/CseMachineTypes.ts

Lines changed: 13 additions & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +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';
56
import { Environment } from 'js-slang/dist/types';
67
import { KonvaEventObject } from 'konva/lib/Node';
78
import React from 'react';
@@ -41,33 +42,28 @@ export interface IVisible extends Drawable {
4142
}
4243

4344
/** unassigned is internally represented as a symbol */
44-
export type UnassignedData = symbol;
45+
export type Unassigned = symbol;
4546

4647
/** types of primitives in JS Slang */
47-
export type PrimitiveTypes = number | string | boolean | null | undefined;
48+
export type Primitive = number | string | boolean | null | undefined;
4849

49-
/** types of functions in JS Slang */
50-
export type FnTypes = {
51-
/** the function itself */
52-
(): any;
53-
54-
/** the enclosing environment */
55-
environment: Environment;
50+
/** types of in-built functions in JS Slang */
51+
export type GlobalFn = Function;
5652

57-
/** string representation of the function */
58-
functionName: string;
59-
60-
/** unique id of the function */
61-
id: string;
53+
/** types of functions in JS Slang */
54+
export type Closure = JsSlangClosure;
6255

63-
node: any;
56+
/** types of arrays in JS Slang */
57+
export type DataArray = Data[] & {
58+
readonly id: string;
59+
environment: Env;
6460
};
6561

6662
/** the types of data in the JS Slang context */
67-
export type Data = PrimitiveTypes | FnTypes | (() => any) | UnassignedData | Data[];
63+
export type Data = Primitive | Closure | GlobalFn | Unassigned | DataArray;
6864

6965
/** modified `Environment` to store children and associated frame */
70-
export type Env = Environment | null;
66+
export type Env = Environment;
7167

7268
/** modified `EnvTree` */
7369
export type EnvTree = EnvironmentTree & { root: EnvTreeNode };

0 commit comments

Comments
 (0)