Skip to content

Commit c1c784d

Browse files
authored
Add visualisations for the new Scheme Implementation (#3075)
* Update js-slang * Enable step limit for Scheme variants * Simplify logic for step limit setting * update snapshots * remove commented line * update tests * update execution time logic * make cse machine compilable * add continuation representation * make continuations point to their defined environments (wip) * fix formatting * Make continuations point to their captured environments * Clean up helper functions * clean up scm-slang frontend logic * bump js-slang
1 parent f1d32f7 commit c1c784d

19 files changed

+400
-50
lines changed

package.json

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -56,7 +56,7 @@
5656
"i18next-browser-languagedetector": "^8.0.0",
5757
"java-slang": "^1.0.13",
5858
"js-cookie": "^3.0.5",
59-
"js-slang": "^1.0.76",
59+
"js-slang": "^1.0.77",
6060
"js-yaml": "^4.1.0",
6161
"konva": "^9.2.0",
6262
"lodash": "^4.17.21",

src/features/cseMachine/CseMachineAnimation.tsx

Lines changed: 72 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -25,7 +25,8 @@ import { Frame } from './components/Frame';
2525
import { ArrayValue } from './components/values/ArrayValue';
2626
import CseMachine from './CseMachine';
2727
import { Layout } from './CseMachineLayout';
28-
import { isBuiltInFn, isStreamFn } from './CseMachineUtils';
28+
import { isBuiltInFn, isInstr, isStreamFn } from './CseMachineUtils';
29+
import { isList, isSymbol } from './utils/scheme';
2930

3031
export class CseAnimation {
3132
static readonly animations: Animatable[] = [];
@@ -153,7 +154,7 @@ export class CseAnimation {
153154
}
154155
if (isNode(lastControlItem)) {
155156
CseAnimation.handleNode(lastControlItem);
156-
} else {
157+
} else if (isInstr(lastControlItem)) {
157158
switch (lastControlItem.instrType) {
158159
case InstrType.APPLICATION:
159160
const appInstr = lastControlItem as AppInstr;
@@ -283,6 +284,75 @@ export class CseAnimation {
283284
case InstrType.RESET:
284285
break;
285286
}
287+
} else {
288+
// these are either scheme lists or values.
289+
// The value is a number, boolean, string or null. (control -> stash)
290+
if (
291+
lastControlItem === null ||
292+
typeof lastControlItem === 'number' ||
293+
typeof lastControlItem === 'boolean' ||
294+
typeof lastControlItem === 'string'
295+
) {
296+
CseAnimation.animations.push(
297+
new ControlToStashAnimation(lastControlComponent, currStashComponent!)
298+
);
299+
}
300+
// The value is a symbol. (lookup, control -> stash)
301+
else if (isSymbol(lastControlItem)) {
302+
CseAnimation.animations.push(
303+
new ControlToStashAnimation(lastControlComponent, currStashComponent!)
304+
);
305+
}
306+
// The value is a list. (control -> control)
307+
else if (isList(lastControlItem)) {
308+
// base our decision on the first element of the list.
309+
const firstElement = (lastControlItem as any)[0];
310+
if (isSymbol(firstElement)) {
311+
switch (firstElement.sym) {
312+
case 'lambda':
313+
case 'define':
314+
case 'set!':
315+
case 'if':
316+
case 'begin':
317+
CseAnimation.animations.push(
318+
new ControlExpansionAnimation(
319+
lastControlComponent,
320+
CseAnimation.getNewControlItems()
321+
)
322+
);
323+
break;
324+
case 'quote':
325+
CseAnimation.animations.push(
326+
new ControlToStashAnimation(lastControlComponent, currStashComponent!)
327+
);
328+
break;
329+
case 'define-syntax':
330+
// undefined was pushed onto the stash.
331+
CseAnimation.animations.push(
332+
new ControlToStashAnimation(lastControlComponent, currStashComponent!)
333+
);
334+
break;
335+
case 'syntax-rules':
336+
// nothing.
337+
default:
338+
// it's probably an application, or a macro expansion.
339+
// either way, it's a control -> control expansion.
340+
CseAnimation.animations.push(
341+
new ControlExpansionAnimation(
342+
lastControlComponent,
343+
CseAnimation.getNewControlItems()
344+
)
345+
);
346+
break;
347+
}
348+
} else {
349+
// it's probably an application.
350+
CseAnimation.animations.push(
351+
new ControlExpansionAnimation(lastControlComponent, CseAnimation.getNewControlItems())
352+
);
353+
}
354+
}
355+
return;
286356
}
287357
}
288358

src/features/cseMachine/CseMachineLayout.tsx

Lines changed: 11 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -11,6 +11,7 @@ import { ControlStack } from './components/ControlStack';
1111
import { Level } from './components/Level';
1212
import { StashStack } from './components/StashStack';
1313
import { ArrayValue } from './components/values/ArrayValue';
14+
import { ContValue } from './components/values/ContValue';
1415
import { FnValue } from './components/values/FnValue';
1516
import { GlobalFnValue } from './components/values/GlobalFnValue';
1617
import { PrimitiveValue } from './components/values/PrimitiveValue';
@@ -45,6 +46,7 @@ import {
4546
isUnassigned,
4647
setDifference
4748
} from './CseMachineUtils';
49+
import { Continuation, isContinuation, isSchemeNumber, isSymbol } from './utils/scheme';
4850

4951
/** this class encapsulates the logic for calculating the layout */
5052
export class Layout {
@@ -372,6 +374,8 @@ export class Layout {
372374
return new UnassignedValue(reference);
373375
} else if (isPrimitiveData(data)) {
374376
return new PrimitiveValue(data, reference);
377+
} else if (isSymbol(data) || isSchemeNumber(data)) {
378+
return new PrimitiveValue(data, reference);
375379
} else {
376380
const existingValue = Layout.values.get(
377381
isBuiltInFn(data) || isStreamFn(data) ? data : data.id
@@ -383,6 +387,8 @@ export class Layout {
383387

384388
if (isDataArray(data)) {
385389
return new ArrayValue(data, reference);
390+
} else if (isContinuation(data)) {
391+
return new ContValue(data, reference);
386392
} else if (isGlobalFn(data)) {
387393
assert(reference instanceof Binding);
388394
return new GlobalFnValue(data, reference);
@@ -394,9 +400,12 @@ export class Layout {
394400
}
395401
}
396402

397-
static memoizeValue(data: GlobalFn | NonGlobalFn | StreamFn | DataArray, value: Value) {
403+
static memoizeValue(
404+
data: GlobalFn | NonGlobalFn | StreamFn | Continuation | DataArray,
405+
value: Value
406+
) {
398407
if (isBuiltInFn(data) || isStreamFn(data)) Layout.values.set(data, value);
399-
else Layout.values.set(data.id, value);
408+
else Layout.values.set((data as any).id, value);
400409
}
401410

402411
/**

src/features/cseMachine/CseMachineTypes.ts

Lines changed: 12 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,5 @@
1+
import { _Symbol } from 'js-slang/dist/alt-langs/scheme/scm-slang/src/stdlib/base';
2+
import { SchemeNumber } from 'js-slang/dist/alt-langs/scheme/scm-slang/src/stdlib/core';
13
import {
24
EnvTree as EnvironmentTree,
35
EnvTreeNode as EnvironmentTreeNode
@@ -11,6 +13,7 @@ import { ArrayUnit } from './components/ArrayUnit';
1113
import { Binding } from './components/Binding';
1214
import { Frame } from './components/Frame';
1315
import { Level } from './components/Level';
16+
import { Continuation } from './utils/scheme';
1417

1518
/** this interface defines a drawing function */
1619
export interface Drawable {
@@ -84,7 +87,15 @@ export type DataArray = Data[] & {
8487
};
8588

8689
/** the types of data in the JS Slang context */
87-
export type Data = Primitive | NonGlobalFn | GlobalFn | Unassigned | DataArray;
90+
export type Data =
91+
| Primitive
92+
| NonGlobalFn
93+
| GlobalFn
94+
| Unassigned
95+
| DataArray
96+
| SchemeNumber
97+
| _Symbol
98+
| Continuation;
8899

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

src/features/cseMachine/CseMachineUtils.ts

Lines changed: 34 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -25,9 +25,11 @@ import classes from 'src/styles/Draggable.module.scss';
2525
import { ArrayUnit } from './components/ArrayUnit';
2626
import { Binding } from './components/Binding';
2727
import { ControlItemComponent } from './components/ControlItemComponent';
28+
import { isNode } from './components/ControlStack';
2829
import { Frame } from './components/Frame';
2930
import { StashItemComponent } from './components/StashItemComponent';
3031
import { ArrayValue } from './components/values/ArrayValue';
32+
import { ContValue } from './components/values/ContValue';
3133
import { FnValue } from './components/values/FnValue';
3234
import { GlobalFnValue } from './components/values/GlobalFnValue';
3335
import { Value } from './components/values/Value';
@@ -57,6 +59,7 @@ import {
5759
isCustomPrimitive,
5860
needsNewRepresentation
5961
} from './utils/altLangs';
62+
import { isContinuation, schemeToString } from './utils/scheme';
6063
class AssertionError extends Error {
6164
constructor(msg?: string) {
6265
super(msg);
@@ -233,6 +236,12 @@ export function setDifference<T>(set1: Set<T>, set2: Set<T>) {
233236
* always prioritised over array units.
234237
*/
235238
export function isMainReference(value: Value, reference: ReferenceType) {
239+
if (isContinuation(value.data)) {
240+
return (
241+
reference instanceof Binding &&
242+
isEnvEqual(reference.frame.environment, value.data.getEnv()[0])
243+
);
244+
}
236245
if (isGlobalFn(value.data)) {
237246
return (
238247
reference instanceof Binding &&
@@ -583,6 +592,19 @@ export function getControlItemComponent(
583592
? index === Math.min(Layout.control.size() - 1, 9)
584593
: index === Layout.control.size() - 1;
585594
if (!isInstr(controlItem)) {
595+
if (!isNode(controlItem)) {
596+
// at the moment, the only non-node and non-instruction control items are
597+
// literals from scheme.
598+
const representation = schemeToString(controlItem as any);
599+
return new ControlItemComponent(
600+
representation,
601+
representation,
602+
stackHeight,
603+
highlightOnHover,
604+
unhighlightOnHover,
605+
topItem
606+
);
607+
}
586608
// there's no reason to provide an alternate representation
587609
// for a instruction.
588610
if (needsNewRepresentation(chapter)) {
@@ -609,11 +631,13 @@ export function getControlItemComponent(
609631
topItem
610632
);
611633
}
612-
switch (controlItem.type) {
634+
635+
// at this point, the control item is a node.
636+
switch ((controlItem as any).type) {
613637
case 'Program':
614638
// If the control item is the whole program
615639
// add {} to represent the implicit block
616-
const originalText = astToString(controlItem)
640+
const originalText = astToString(controlItem as any)
617641
.trim()
618642
.split('\n')
619643
.map(line => `\t\t${line}`)
@@ -629,7 +653,9 @@ export function getControlItemComponent(
629653
);
630654
case 'Literal':
631655
const textL =
632-
typeof controlItem.value === 'string' ? `"${controlItem.value}"` : controlItem.value;
656+
typeof (controlItem as any).value === 'string'
657+
? `"${(controlItem as any).value}"`
658+
: (controlItem as any).value;
633659
return new ControlItemComponent(
634660
textL,
635661
String(textL),
@@ -639,7 +665,7 @@ export function getControlItemComponent(
639665
topItem
640666
);
641667
default:
642-
const text = astToString(controlItem).trim();
668+
const text = astToString(controlItem as any).trim();
643669
return new ControlItemComponent(
644670
text,
645671
text,
@@ -846,10 +872,10 @@ export function getStashItemComponent(
846872
index: number,
847873
_chapter: Chapter
848874
): StashItemComponent {
849-
let arrowTo: ArrayValue | FnValue | GlobalFnValue | undefined;
850-
if (isFunction(stashItem) || isDataArray(stashItem)) {
851-
if (isClosure(stashItem) || isDataArray(stashItem)) {
852-
arrowTo = Layout.values.get(stashItem.id) as ArrayValue | FnValue;
875+
let arrowTo: ArrayValue | FnValue | GlobalFnValue | ContValue | undefined;
876+
if (isFunction(stashItem) || isDataArray(stashItem || isContinuation(stashItem))) {
877+
if (isClosure(stashItem) || isDataArray(stashItem) || isContinuation(stashItem)) {
878+
arrowTo = Layout.values.get(stashItem.id) as ArrayValue | FnValue | ContValue;
853879
} else {
854880
arrowTo = Layout.values.get(stashItem) as FnValue | GlobalFnValue;
855881
}

src/features/cseMachine/components/Binding.tsx

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,7 @@ import { GenericArrow } from './arrows/GenericArrow';
99
import { Frame } from './Frame';
1010
import { Text } from './Text';
1111
import { ArrayValue } from './values/ArrayValue';
12+
import { ContValue } from './values/ContValue';
1213
import { FnValue } from './values/FnValue';
1314
import { GlobalFnValue } from './values/GlobalFnValue';
1415
import { PrimitiveValue } from './values/PrimitiveValue';
@@ -68,7 +69,9 @@ export class Binding extends Visible {
6869
? this.value.x() +
6970
this.value.width() -
7071
this.x() +
71-
(this.value instanceof FnValue || this.value instanceof GlobalFnValue
72+
(this.value instanceof FnValue ||
73+
this.value instanceof GlobalFnValue ||
74+
this.value instanceof ContValue
7275
? this.value.tooltipWidth
7376
: 0)
7477
: this.key.width();

src/features/cseMachine/components/ControlStack.tsx

Lines changed: 8 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -43,14 +43,18 @@ export class ControlStack extends Visible implements IHoverable {
4343
// Function to convert the stack items to their components
4444
let i = 0;
4545
const controlItemToComponent = (controlItem: ControlItem) => {
46-
const node = isNode(controlItem) ? controlItem : controlItem.srcNode;
46+
const node = isNode(controlItem)
47+
? controlItem
48+
: isInstr(controlItem)
49+
? controlItem.srcNode
50+
: controlItem;
4751
let highlightOnHover = () => {};
4852
let unhighlightOnHover = () => {};
4953

5054
highlightOnHover = () => {
51-
if (node.loc) {
52-
const start = node.loc.start.line - 1;
53-
const end = node.loc.end.line - 1;
55+
if ((node as any).loc !== undefined) {
56+
const start = (node as any).loc.start.line - 1;
57+
const end = (node as any).loc.end.line - 1;
5458
CseMachine.setEditorHighlightedLines([[start, end]]);
5559
}
5660
};

src/features/cseMachine/components/Frame.tsx

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -16,6 +16,7 @@ import {
1616
isPrimitiveData,
1717
isUnassigned
1818
} from '../CseMachineUtils';
19+
import { isContinuation } from '../utils/scheme';
1920
import { ArrowFromFrame } from './arrows/ArrowFromFrame';
2021
import { GenericArrow } from './arrows/GenericArrow';
2122
import { Binding } from './Binding';
@@ -124,7 +125,7 @@ export class Frame extends Visible implements IHoverable {
124125
const value = unreferencedValues[i];
125126
if (isDataArray(value)) {
126127
for (const data of value) {
127-
if ((isDataArray(data) && data !== value) || isClosure(data)) {
128+
if ((isDataArray(data) && data !== value) || isClosure(data) || isContinuation(data)) {
128129
const prev = unreferencedValues.findIndex(value => value.id === data.id);
129130
if (prev > -1) {
130131
unreferencedValues.splice(prev, 1);

src/features/cseMachine/components/StashItemComponent.tsx

Lines changed: 14 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -24,8 +24,10 @@ import {
2424
setUnhoveredStyle,
2525
truncateText
2626
} from '../CseMachineUtils';
27+
import { isContinuation } from '../utils/scheme';
2728
import { ArrowFromStashItemComponent } from './arrows/ArrowFromStashItemComponent';
2829
import { ArrayValue } from './values/ArrayValue';
30+
import { ContValue } from './values/ContValue';
2931
import { Visible } from './Visible';
3032

3133
export class StashItemComponent extends Visible implements IHoverable {
@@ -42,21 +44,23 @@ export class StashItemComponent extends Visible implements IHoverable {
4244
stackWidth: number,
4345
/** The index number of this stack item */
4446
readonly index: number,
45-
arrowTo?: FnValue | GlobalFnValue | ArrayValue
47+
arrowTo?: FnValue | GlobalFnValue | ContValue | ArrayValue
4648
) {
4749
super();
4850
const valToStashRep = (val: any): string => {
4951
return typeof val === 'string'
5052
? `'${val}'`.trim()
51-
: isNonGlobalFn(val)
52-
? 'closure'
53-
: isDataArray(val)
54-
? arrowTo
55-
? 'pair/array'
56-
: JSON.stringify(val)
57-
: isSourceObject(val)
58-
? val.toReplString()
59-
: String(value);
53+
: isContinuation(val)
54+
? 'continuation'
55+
: isNonGlobalFn(val)
56+
? 'closure'
57+
: isDataArray(val)
58+
? arrowTo
59+
? 'pair/array'
60+
: JSON.stringify(val)
61+
: isSourceObject(val)
62+
? val.toReplString()
63+
: String(value);
6064
};
6165
this.text = truncateText(
6266
valToStashRep(value),

0 commit comments

Comments
 (0)