diff --git a/src/commons/application/ApplicationTypes.ts b/src/commons/application/ApplicationTypes.ts index 0743db122c..85e1498ef4 100644 --- a/src/commons/application/ApplicationTypes.ts +++ b/src/commons/application/ApplicationTypes.ts @@ -198,9 +198,23 @@ const schemeSubLanguages: Array { - return { ...sublang, mainLanguage: SupportedLanguage.SCHEME, supports: { repl: true } }; + return { + ...sublang, + mainLanguage: SupportedLanguage.SCHEME, + supports: { repl: true, cseMachine: true } + }; }); +export function isSchemeLanguage(chapter: Chapter): boolean { + return [ + Chapter.SCHEME_1, + Chapter.SCHEME_2, + Chapter.SCHEME_3, + Chapter.SCHEME_4, + Chapter.FULL_SCHEME + ].includes(chapter); +} + const pySubLanguages: Array> = [ { chapter: Chapter.PYTHON_1, variant: Variant.DEFAULT, displayName: 'Python \xa71' } //{ chapter: Chapter.PYTHON_2, variant: Variant.DEFAULT, displayName: 'Python \xa72' }, diff --git a/src/commons/application/__tests__/__snapshots__/ApplicationTypes.ts.snap b/src/commons/application/__tests__/__snapshots__/ApplicationTypes.ts.snap index ca5fc6211d..3f9a03a47a 100644 --- a/src/commons/application/__tests__/__snapshots__/ApplicationTypes.ts.snap +++ b/src/commons/application/__tests__/__snapshots__/ApplicationTypes.ts.snap @@ -21,6 +21,7 @@ Array [ "displayName": "Scheme §1", "mainLanguage": "Scheme", "supports": Object { + "cseMachine": true, "repl": true, }, "variant": "explicit-control", @@ -30,6 +31,7 @@ Array [ "displayName": "Scheme §2", "mainLanguage": "Scheme", "supports": Object { + "cseMachine": true, "repl": true, }, "variant": "explicit-control", @@ -39,6 +41,7 @@ Array [ "displayName": "Scheme §3", "mainLanguage": "Scheme", "supports": Object { + "cseMachine": true, "repl": true, }, "variant": "explicit-control", @@ -48,6 +51,7 @@ Array [ "displayName": "Scheme §4", "mainLanguage": "Scheme", "supports": Object { + "cseMachine": true, "repl": true, }, "variant": "explicit-control", @@ -57,6 +61,7 @@ Array [ "displayName": "Full Scheme", "mainLanguage": "Scheme", "supports": Object { + "cseMachine": true, "repl": true, }, "variant": "explicit-control", diff --git a/src/commons/sagas/PlaygroundSaga.ts b/src/commons/sagas/PlaygroundSaga.ts index e7acf4fe01..ab212d471b 100644 --- a/src/commons/sagas/PlaygroundSaga.ts +++ b/src/commons/sagas/PlaygroundSaga.ts @@ -13,7 +13,7 @@ import { updateShortURL } from '../../features/playground/PlaygroundActions'; import { GENERATE_LZ_STRING, SHORTEN_URL } from '../../features/playground/PlaygroundTypes'; -import { isSourceLanguage, OverallState } from '../application/ApplicationTypes'; +import { isSchemeLanguage, isSourceLanguage, OverallState } from '../application/ApplicationTypes'; import { ExternalLibraryName } from '../application/types/ExternalTypes'; import { retrieveFilesInWorkspaceAsRecord } from '../fileSystem/utils'; import { visitSideContent } from '../sideContent/SideContentActions'; @@ -122,6 +122,10 @@ export default function* PlaygroundSaga(): SagaIterator { yield put(toggleUsingCse(true, workspaceLocation)); } } + + if (isSchemeLanguage(playgroundSourceChapter) && newId === SideContentType.cseMachine) { + yield put(toggleUsingCse(true, workspaceLocation)); + } } ); } diff --git a/src/commons/sagas/WorkspaceSaga/helpers/evalCode.ts b/src/commons/sagas/WorkspaceSaga/helpers/evalCode.ts index 0dae58611d..29426be942 100644 --- a/src/commons/sagas/WorkspaceSaga/helpers/evalCode.ts +++ b/src/commons/sagas/WorkspaceSaga/helpers/evalCode.ts @@ -14,7 +14,7 @@ import { notifyStoriesEvaluated } from 'src/features/stories/StoriesActions'; import { EVAL_STORY } from 'src/features/stories/StoriesTypes'; import { EventType } from '../../../../features/achievement/AchievementTypes'; -import { OverallState } from '../../../application/ApplicationTypes'; +import { isSchemeLanguage, OverallState } from '../../../application/ApplicationTypes'; import { BEGIN_DEBUG_PAUSE, BEGIN_INTERRUPT_EXECUTION, @@ -95,7 +95,8 @@ export function* evalCode( .currentStep ) : -1; - const cseActiveAndCorrectChapter = context.chapter >= 3 && cseIsActive; + const cseActiveAndCorrectChapter = + (isSchemeLanguage(context.chapter) || context.chapter >= 3) && cseIsActive; if (cseActiveAndCorrectChapter) { context.executionMethod = 'cse-machine'; } diff --git a/src/features/cseMachine/CseMachine.tsx b/src/features/cseMachine/CseMachine.tsx index ce6b61f94a..f829344807 100644 --- a/src/features/cseMachine/CseMachine.tsx +++ b/src/features/cseMachine/CseMachine.tsx @@ -84,7 +84,8 @@ export default class CseMachine { Layout.setContext( context.runtime.environmentTree as EnvTree, context.runtime.control, - context.runtime.stash + context.runtime.stash, + context.chapter ); this.setVis(Layout.draw()); this.setIsStepLimitExceeded(context.runtime.control.isEmpty()); diff --git a/src/features/cseMachine/CseMachineLayout.tsx b/src/features/cseMachine/CseMachineLayout.tsx index eb7aa4c5e2..07615cbf90 100644 --- a/src/features/cseMachine/CseMachineLayout.tsx +++ b/src/features/cseMachine/CseMachineLayout.tsx @@ -1,6 +1,6 @@ import Heap from 'js-slang/dist/cse-machine/heap'; import { Control, Stash } from 'js-slang/dist/cse-machine/interpreter'; -import { Frame } from 'js-slang/dist/types'; +import { Chapter, Frame } from 'js-slang/dist/types'; import { KonvaEventObject } from 'konva/lib/Node'; import React, { RefObject } from 'react'; import { Layer, Rect, Stage } from 'react-konva'; @@ -137,7 +137,12 @@ export class Layout { } /** processes the runtime context from JS Slang */ - static setContext(envTree: EnvTree, control: Control, stash: Stash): void { + static setContext( + envTree: EnvTree, + control: Control, + stash: Stash, + chapter: Chapter = Chapter.SOURCE_4 + ): void { Layout.currentLight = undefined; Layout.currentDark = undefined; Layout.currentStackDark = undefined; @@ -160,7 +165,7 @@ export class Layout { // initialize levels and frames Layout.initializeGrid(); // initialize control and stash - Layout.initializeControlStash(); + Layout.initializeControlStash(chapter); if (CseMachine.getControlStash()) { Layout.controlStashHeight = Math.max( @@ -193,11 +198,11 @@ export class Layout { CseAnimation.updateAnimation(); } - static initializeControlStash() { + static initializeControlStash(chapter: Chapter) { Layout.previousControlComponent = Layout.controlComponent; Layout.previousStashComponent = Layout.stashComponent; - this.controlComponent = new ControlStack(this.control); - this.stashComponent = new StashStack(this.stash); + this.controlComponent = new ControlStack(this.control, chapter); + this.stashComponent = new StashStack(this.stash, chapter); } /** @@ -205,7 +210,7 @@ export class Layout { * objects into the global environment head and heap */ private static removePreludeEnv() { - if (!Layout.globalEnvNode.children) return; + if (!Layout.globalEnvNode.children || Layout.globalEnvNode.children.length === 0) return; const preludeEnvNode = Layout.globalEnvNode.children[0]; const preludeEnv = preludeEnvNode.environment; diff --git a/src/features/cseMachine/CseMachineUtils.ts b/src/features/cseMachine/CseMachineUtils.ts index 780ccf6d45..f83ebf8caf 100644 --- a/src/features/cseMachine/CseMachineUtils.ts +++ b/src/features/cseMachine/CseMachineUtils.ts @@ -1,3 +1,5 @@ +import { estreeDecode } from 'js-slang/dist/alt-langs/scheme/scm-slang/src/utils/encoder-visitor'; +import { unparse } from 'js-slang/dist/alt-langs/scheme/scm-slang/src/utils/reverse_parser'; import JsSlangClosure from 'js-slang/dist/cse-machine/closure'; import { AppInstr, @@ -10,13 +12,14 @@ import { InstrType, UnOpInstr } from 'js-slang/dist/cse-machine/types'; -import { Environment, Value as StashValue } from 'js-slang/dist/types'; +import { Chapter, Environment, Value as StashValue } from 'js-slang/dist/types'; import { astToString } from 'js-slang/dist/utils/ast/astToString'; import { Group } from 'konva/lib/Group'; import { Node } from 'konva/lib/Node'; import { Shape } from 'konva/lib/Shape'; import { Text } from 'konva/lib/shapes/Text'; import { cloneDeep, isObject } from 'lodash'; +import { isSchemeLanguage } from 'src/commons/application/ApplicationTypes'; import classes from 'src/styles/Draggable.module.scss'; import { ArrayUnit } from './components/ArrayUnit'; @@ -49,7 +52,11 @@ import { StreamFn, Unassigned } from './CseMachineTypes'; - +import { + getAlternateControlItemComponent, + isCustomPrimitive, + needsNewRepresentation +} from './utils/altLangs'; class AssertionError extends Error { constructor(msg?: string) { super(msg); @@ -195,7 +202,8 @@ export function isPrimitiveData(data: Data): data is Primitive { isString(data) || isNumber(data) || isBoolean(data) || - isSourceObject(data) + isSourceObject(data) || + isCustomPrimitive(data) ); } @@ -568,12 +576,39 @@ export function getControlItemComponent( stackHeight: number, index: number, highlightOnHover: () => void, - unhighlightOnHover: () => void + unhighlightOnHover: () => void, + chapter: Chapter ): ControlItemComponent { const topItem = CseMachine.getStackTruncated() ? index === Math.min(Layout.control.size() - 1, 9) : index === Layout.control.size() - 1; if (!isInstr(controlItem)) { + // there's no reason to provide an alternate representation + // for a instruction. + if (needsNewRepresentation(chapter)) { + return getAlternateControlItemComponent( + controlItem, + stackHeight, + highlightOnHover, + unhighlightOnHover, + topItem, + chapter + ); + } + + if (isSchemeLanguage(chapter)) { + // use the js-slang decoder on the control item + controlItem = estreeDecode(controlItem as any); + const text = unparse(controlItem as any); + return new ControlItemComponent( + text, + text, + stackHeight, + highlightOnHover, + unhighlightOnHover, + topItem + ); + } switch (controlItem.type) { case 'Program': // If the control item is the whole program @@ -792,6 +827,24 @@ export function getControlItemComponent( unhighlightOnHover, topItem ); + case InstrType.GENERATE_CONT: + return new ControlItemComponent( + 'generate cont', + 'Generate continuation', + stackHeight, + highlightOnHover, + unhighlightOnHover, + topItem + ); + case InstrType.RESUME_CONT: + return new ControlItemComponent( + 'call cont', + 'call a continuation', + stackHeight, + highlightOnHover, + unhighlightOnHover, + topItem + ); default: return new ControlItemComponent( 'INSTRUCTION', @@ -805,7 +858,12 @@ export function getControlItemComponent( } } -export function getStashItemComponent(stashItem: StashValue, stackHeight: number, index: number) { +export function getStashItemComponent( + stashItem: StashValue, + stackHeight: number, + index: number, + _chapter: Chapter +): StashItemComponent { let arrowTo: ArrayValue | FnValue | GlobalFnValue | undefined; if (isFunction(stashItem) || isDataArray(stashItem)) { if (isClosure(stashItem) || isDataArray(stashItem)) { diff --git a/src/features/cseMachine/components/ControlStack.tsx b/src/features/cseMachine/components/ControlStack.tsx index 7b4673f265..6f0850380c 100644 --- a/src/features/cseMachine/components/ControlStack.tsx +++ b/src/features/cseMachine/components/ControlStack.tsx @@ -1,5 +1,6 @@ import { Control } from 'js-slang/dist/cse-machine/interpreter'; import { ControlItem, Instr } from 'js-slang/dist/cse-machine/types'; +import { Chapter, StatementSequence } from 'js-slang/dist/types'; import { Node } from 'js-slang/dist/types'; import { KonvaEventObject } from 'konva/lib/Node'; import React from 'react'; @@ -28,7 +29,8 @@ export class ControlStack extends Visible implements IHoverable { constructor( /** the control object */ - readonly control: Control + readonly control: Control, + readonly chapter: Chapter ) { super(); this._x = ControlStashConfig.ControlPosX; @@ -36,6 +38,7 @@ export class ControlStack extends Visible implements IHoverable { this._width = ControlStashConfig.ControlItemWidth; this._height = ControlStashConfig.StashItemHeight + ControlStashConfig.StashItemTextPadding * 2; this.control = control; + this.chapter = chapter; // Function to convert the stack items to their components let i = 0; @@ -57,7 +60,8 @@ export class ControlStack extends Visible implements IHoverable { this._height, i, highlightOnHover, - unhighlightOnHover + unhighlightOnHover, + this.chapter ); this._height += component.height(); i += 1; @@ -141,6 +145,6 @@ export const isInstr = (command: ControlItem): command is Instr => { * @param command A ControlItem * @returns true if the ControlItem is an esNode and false if it is an instruction. */ -export const isNode = (command: ControlItem): command is Node => { +export const isNode = (command: ControlItem): command is Node | StatementSequence => { return (command as Node).type !== undefined; }; diff --git a/src/features/cseMachine/components/StashStack.tsx b/src/features/cseMachine/components/StashStack.tsx index b02e47e8fb..e4e2486c79 100644 --- a/src/features/cseMachine/components/StashStack.tsx +++ b/src/features/cseMachine/components/StashStack.tsx @@ -1,5 +1,5 @@ import { Stash } from 'js-slang/dist/cse-machine/interpreter'; -import { Value } from 'js-slang/dist/types'; +import { Chapter, Value } from 'js-slang/dist/types'; import React from 'react'; import CseMachine from '../CseMachine'; @@ -14,18 +14,20 @@ export class StashStack extends Visible { constructor( /** the stash object */ - readonly stash: Stash + readonly stash: Stash, + readonly chapter: Chapter ) { super(); this._x = ControlStashConfig.StashPosX; this._y = ControlStashConfig.StashPosY; this._width = 0; this._height = 0; + this.chapter = chapter; // Function to convert the stack items to their components let i = 0; const stashItemToComponent = (stashItem: Value) => { - const component = getStashItemComponent(stashItem, this._width, i); + const component = getStashItemComponent(stashItem, this._width, i, this.chapter); this._width += component.width(); this._height = Math.max(this._height, component.height()); i += 1; diff --git a/src/features/cseMachine/components/Text.tsx b/src/features/cseMachine/components/Text.tsx index d199c97b22..2a5d93e19e 100644 --- a/src/features/cseMachine/components/Text.tsx +++ b/src/features/cseMachine/components/Text.tsx @@ -15,6 +15,7 @@ import { setHoveredCursor, setUnhoveredCursor } from '../CseMachineUtils'; +import { isCustomPrimitive } from '../utils/altLangs'; import { Visible } from './Visible'; export interface TextOptions { @@ -64,6 +65,8 @@ export class Text extends Visible implements IHoverable { this.fullStr = this.partialStr = isSourceObject(data) ? data.toReplString() + : isCustomPrimitive(data) + ? String(data) : isStringIdentifiable ? JSON.stringify(data) || String(data) : String(data); diff --git a/src/features/cseMachine/utils/altLangs.ts b/src/features/cseMachine/utils/altLangs.ts new file mode 100644 index 0000000000..77201a6029 --- /dev/null +++ b/src/features/cseMachine/utils/altLangs.ts @@ -0,0 +1,56 @@ +// alternate representations of data types + +import { ControlItem } from 'js-slang/dist/cse-machine/types'; +import { Chapter, Node } from 'js-slang/dist/types'; +import { isSchemeLanguage } from 'src/commons/application/ApplicationTypes'; + +import { ControlItemComponent } from '../components/ControlItemComponent'; +import { Data } from '../CseMachineTypes'; +import { convertNodeToScheme, isSchemeNumber } from './scheme'; + +// used to define custom primitives from alternate languages. +// MAKE SURE the custom primitive has a toString() method! +export function isCustomPrimitive(data: Data): boolean { + return isSchemeNumber(data); +} + +// tells the CSE machine whether a new representation is needed. +// expand when necessary. +export function needsNewRepresentation(chapter: Chapter): boolean { + return isSchemeLanguage(chapter); +} + +export function getAlternateControlItemComponent( + controlItem: ControlItem, + stackHeight: number, + highlightOnHover: () => void, + unhighlightOnHover: () => void, + topItem: boolean, + chapter: Chapter +) { + // keep in mind that the controlItem is a node. + // there's no reason to provide an alternate representation + // for a instruction. + const node = controlItem as Node; + switch (chapter) { + case Chapter.SCHEME_1: + case Chapter.SCHEME_2: + case Chapter.SCHEME_3: + case Chapter.SCHEME_4: + case Chapter.FULL_SCHEME: + const text = convertNodeToScheme(node); + return new ControlItemComponent( + text, + text, + stackHeight, + highlightOnHover, + unhighlightOnHover, + topItem + ); + default: + // this only happens if + // a chapter needing an alternate representation + // is not handled. + throw new Error('Unknown Chapter'); + } +} diff --git a/src/features/cseMachine/utils/scheme.ts b/src/features/cseMachine/utils/scheme.ts new file mode 100644 index 0000000000..80882d277e --- /dev/null +++ b/src/features/cseMachine/utils/scheme.ts @@ -0,0 +1,16 @@ +import { estreeDecode } from 'js-slang/dist/alt-langs/scheme/scm-slang/src/utils/encoder-visitor'; +import { unparse } from 'js-slang/dist/alt-langs/scheme/scm-slang/src/utils/reverse_parser'; +import { Node } from 'js-slang/dist/types'; + +import { Data } from '../CseMachineTypes'; + +/** Returns `true` if `data` is a scheme number + * TODO: make this less hacky. + */ +export function isSchemeNumber(data: Data): boolean { + return (data as any)?.numberType !== undefined; +} + +export function convertNodeToScheme(node: Node): string { + return unparse(estreeDecode(node as any)); +}