From 4556d3fcceb9499a073edf2eb0790db5daf9d7b7 Mon Sep 17 00:00:00 2001 From: Liew Xin Yi Date: Sat, 13 Apr 2024 19:43:51 +0800 Subject: [PATCH] Implement and Integrate `java-slang` CSEC Visualizer (#2926) * Update ControlBarChapterSelect with Java * Implement Java CSEC Visualizer * Integrate CSEC Visualizer * Linting errors for java-slang-csec * Fix frontend test: rename Object to Obj to avoid potential name clash? * Update snapshot * Update imports from java-slang * Fix lint * Update snapshot * Fix linting * Fix typo Co-authored-by: Richard Dominick <34370238+RichDom2185@users.noreply.github.com> * Add TODO for err source node location --------- Co-authored-by: joel chan Co-authored-by: Richard Dominick <34370238+RichDom2185@users.noreply.github.com> --- src/commons/application/ApplicationTypes.ts | 2 +- .../controlBar/ControlBarChapterSelect.tsx | 4 +- src/commons/sagas/PlaygroundSaga.ts | 6 + .../sagas/WorkspaceSaga/helpers/evalCode.ts | 3 +- .../WorkspaceSaga/helpers/updateInspector.ts | 39 ++- .../SideContentCseMachine.tsx.snap | 24 +- .../content/SideContentCseMachine.tsx | 255 +++++++++++------- src/commons/utils/JavaHelper.ts | 54 +++- src/features/cseMachine/java/CseMachine.tsx | 152 +++++++++++ .../cseMachine/java/components/Arrow.tsx | 67 +++++ .../cseMachine/java/components/Binding.tsx | 96 +++++++ .../cseMachine/java/components/Control.tsx | 197 ++++++++++++++ .../java/components/ControlItem.tsx | 151 +++++++++++ .../java/components/Environment.tsx | 217 +++++++++++++++ .../cseMachine/java/components/Frame.tsx | 153 +++++++++++ .../cseMachine/java/components/Line.tsx | 58 ++++ .../cseMachine/java/components/Method.tsx | 118 ++++++++ .../cseMachine/java/components/Object.tsx | 43 +++ .../cseMachine/java/components/Stash.tsx | 89 ++++++ .../cseMachine/java/components/StashItem.tsx | 88 ++++++ .../cseMachine/java/components/Text.tsx | 51 ++++ .../cseMachine/java/components/Variable.tsx | 109 ++++++++ .../__snapshots__/Playground.tsx.snap | 24 +- 23 files changed, 1868 insertions(+), 132 deletions(-) create mode 100644 src/features/cseMachine/java/CseMachine.tsx create mode 100644 src/features/cseMachine/java/components/Arrow.tsx create mode 100644 src/features/cseMachine/java/components/Binding.tsx create mode 100644 src/features/cseMachine/java/components/Control.tsx create mode 100644 src/features/cseMachine/java/components/ControlItem.tsx create mode 100644 src/features/cseMachine/java/components/Environment.tsx create mode 100644 src/features/cseMachine/java/components/Frame.tsx create mode 100644 src/features/cseMachine/java/components/Line.tsx create mode 100644 src/features/cseMachine/java/components/Method.tsx create mode 100644 src/features/cseMachine/java/components/Object.tsx create mode 100644 src/features/cseMachine/java/components/Stash.tsx create mode 100644 src/features/cseMachine/java/components/StashItem.tsx create mode 100644 src/features/cseMachine/java/components/Text.tsx create mode 100644 src/features/cseMachine/java/components/Variable.tsx diff --git a/src/commons/application/ApplicationTypes.ts b/src/commons/application/ApplicationTypes.ts index 2015c1007c..0743db122c 100644 --- a/src/commons/application/ApplicationTypes.ts +++ b/src/commons/application/ApplicationTypes.ts @@ -219,7 +219,7 @@ export const javaLanguages: SALanguage[] = [ variant: Variant.DEFAULT, displayName: 'Java', mainLanguage: SupportedLanguage.JAVA, - supports: {} + supports: { cseMachine: true } } ]; export const cLanguages: SALanguage[] = [ diff --git a/src/commons/controlBar/ControlBarChapterSelect.tsx b/src/commons/controlBar/ControlBarChapterSelect.tsx index aad0e84e19..11759c56ca 100644 --- a/src/commons/controlBar/ControlBarChapterSelect.tsx +++ b/src/commons/controlBar/ControlBarChapterSelect.tsx @@ -9,6 +9,7 @@ import { fullJSLanguage, fullTSLanguage, htmlLanguage, + javaLanguages, pyLanguages, SALanguage, schemeLanguages, @@ -87,7 +88,8 @@ export const ControlBarChapterSelect: React.FC = ( // See https://github.com/source-academy/frontend/pull/2460#issuecomment-1528759912 ...(Constants.playgroundOnly ? [fullJSLanguage, fullTSLanguage, htmlLanguage] : []), ...schemeLanguages, - ...pyLanguages + ...pyLanguages, + ...javaLanguages ]; return ( diff --git a/src/commons/sagas/PlaygroundSaga.ts b/src/commons/sagas/PlaygroundSaga.ts index f8e1409145..e7acf4fe01 100644 --- a/src/commons/sagas/PlaygroundSaga.ts +++ b/src/commons/sagas/PlaygroundSaga.ts @@ -5,6 +5,7 @@ import qs from 'query-string'; import { SagaIterator } from 'redux-saga'; import { call, delay, put, race, select } from 'redux-saga/effects'; import CseMachine from 'src/features/cseMachine/CseMachine'; +import { CseMachine as JavaCseMachine } from 'src/features/cseMachine/java/CseMachine'; import { changeQueryString, @@ -100,12 +101,17 @@ export default function* PlaygroundSaga(): SagaIterator { if (newId !== SideContentType.cseMachine) { yield put(toggleUsingCse(false, workspaceLocation)); yield call([CseMachine, CseMachine.clearCse]); + yield call([JavaCseMachine, JavaCseMachine.clearCse]); yield put(updateCurrentStep(-1, workspaceLocation)); yield put(updateStepsTotal(0, workspaceLocation)); yield put(toggleUpdateCse(true, workspaceLocation)); yield put(setEditorHighlightedLines(workspaceLocation, 0, [])); } + if (playgroundSourceChapter === Chapter.FULL_JAVA && newId === SideContentType.cseMachine) { + yield put(toggleUsingCse(true, workspaceLocation)); + } + if ( isSourceLanguage(playgroundSourceChapter) && (newId === SideContentType.substVisualizer || newId === SideContentType.cseMachine) diff --git a/src/commons/sagas/WorkspaceSaga/helpers/evalCode.ts b/src/commons/sagas/WorkspaceSaga/helpers/evalCode.ts index 6dc00046f8..0dae58611d 100644 --- a/src/commons/sagas/WorkspaceSaga/helpers/evalCode.ts +++ b/src/commons/sagas/WorkspaceSaga/helpers/evalCode.ts @@ -250,6 +250,7 @@ export function* evalCode( let lastDebuggerResult = yield select( (state: OverallState) => state.workspaces[workspaceLocation].lastDebuggerResult ); + const isUsingCse = yield select((state: OverallState) => state.workspaces['playground'].usingCse); // Handles `console.log` statements in fullJS const detachConsole: () => void = @@ -266,7 +267,7 @@ export function* evalCode( : isC ? call(cCompileAndRun, entrypointCode, context) : isJava - ? call(javaRun, entrypointCode, context) + ? call(javaRun, entrypointCode, context, currentStep, isUsingCse) : call( runFilesInContext, isFolderModeEnabled diff --git a/src/commons/sagas/WorkspaceSaga/helpers/updateInspector.ts b/src/commons/sagas/WorkspaceSaga/helpers/updateInspector.ts index 7a0305091b..788e813ebf 100644 --- a/src/commons/sagas/WorkspaceSaga/helpers/updateInspector.ts +++ b/src/commons/sagas/WorkspaceSaga/helpers/updateInspector.ts @@ -1,24 +1,41 @@ +import { Chapter } from 'js-slang/dist/types'; import { SagaIterator } from 'redux-saga'; import { put, select } from 'redux-saga/effects'; import { OverallState } from '../../../application/ApplicationTypes'; import { actions } from '../../../utils/ActionsHelper'; +import { visualizeJavaCseMachine } from '../../../utils/JavaHelper'; import { visualizeCseMachine } from '../../../utils/JsSlangHelper'; import { WorkspaceLocation } from '../../../workspace/WorkspaceTypes'; export function* updateInspector(workspaceLocation: WorkspaceLocation): SagaIterator { try { - const lastDebuggerResult = yield select( - (state: OverallState) => state.workspaces[workspaceLocation].lastDebuggerResult - ); - const row = lastDebuggerResult.context.runtime.nodes[0].loc.start.line - 1; - // TODO: Hardcoded to make use of the first editor tab. Rewrite after editor tabs are added. - yield put(actions.setEditorHighlightedLines(workspaceLocation, 0, [])); - // We highlight only one row to show the current line - // If we highlight from start to end, the whole program block will be highlighted at the start - // since the first node is the program node - yield put(actions.setEditorHighlightedLines(workspaceLocation, 0, [[row, row]])); - visualizeCseMachine(lastDebuggerResult); + const [lastDebuggerResult, chapter] = yield select((state: OverallState) => [ + state.workspaces[workspaceLocation].lastDebuggerResult, + state.workspaces[workspaceLocation].context.chapter + ]); + if (chapter === Chapter.FULL_JAVA) { + const controlItem = lastDebuggerResult.context.control.peek(); + let start = -1; + let end = -1; + if (controlItem?.srcNode?.location) { + const node = controlItem.srcNode; + start = node.location.startLine - 1; + end = node.location.endLine ? node.location.endLine - 1 : start; + } + yield put(actions.setEditorHighlightedLines(workspaceLocation, 0, [])); + yield put(actions.setEditorHighlightedLines(workspaceLocation, 0, [[start, end]])); + visualizeJavaCseMachine(lastDebuggerResult); + } else { + const row = lastDebuggerResult.context.runtime.nodes[0].loc.start.line - 1; + // TODO: Hardcoded to make use of the first editor tab. Rewrite after editor tabs are added. + yield put(actions.setEditorHighlightedLines(workspaceLocation, 0, [])); + // We highlight only one row to show the current line + // If we highlight from start to end, the whole program block will be highlighted at the start + // since the first node is the program node + yield put(actions.setEditorHighlightedLines(workspaceLocation, 0, [[row, row]])); + visualizeCseMachine(lastDebuggerResult); + } } catch (e) { // TODO: Hardcoded to make use of the first editor tab. Rewrite after editor tabs are added. yield put(actions.setEditorHighlightedLines(workspaceLocation, 0, [])); diff --git a/src/commons/sideContent/__tests__/__snapshots__/SideContentCseMachine.tsx.snap b/src/commons/sideContent/__tests__/__snapshots__/SideContentCseMachine.tsx.snap index 3ded2a0fb5..f33e89daec 100644 --- a/src/commons/sideContent/__tests__/__snapshots__/SideContentCseMachine.tsx.snap +++ b/src/commons/sideContent/__tests__/__snapshots__/SideContentCseMachine.tsx.snap @@ -352,17 +352,19 @@ exports[`CSE Machine component renders correctly 1`] = ` data-testid="cse-machine-default-text" id="cse-machine-default-text" > - The CSE machine generates control, stash and environment model diagrams following a notation introduced in - - - - Structure and Interpretation of Computer Programs, JavaScript Edition, Chapter 3, Section 2 - - + + The CSE machine generates control, stash and environment model diagrams following a notation introduced in + + + + Structure and Interpretation of Computer Programs, JavaScript Edition, Chapter 3, Section 2 + + + .

diff --git a/src/commons/sideContent/content/SideContentCseMachine.tsx b/src/commons/sideContent/content/SideContentCseMachine.tsx index 009c2aec85..fe6adf3048 100644 --- a/src/commons/sideContent/content/SideContentCseMachine.tsx +++ b/src/commons/sideContent/content/SideContentCseMachine.tsx @@ -10,6 +10,7 @@ import { import { IconNames } from '@blueprintjs/icons'; import { Tooltip2 } from '@blueprintjs/popover2'; import classNames from 'classnames'; +import { Chapter } from 'js-slang/dist/types'; import { debounce } from 'lodash'; import React from 'react'; import { HotKeys } from 'react-hotkeys'; @@ -20,6 +21,7 @@ import type { PlaygroundWorkspaceState } from 'src/commons/workspace/WorkspaceTy import CseMachine from 'src/features/cseMachine/CseMachine'; import { CseAnimation } from 'src/features/cseMachine/CseMachineAnimation'; import { Layout } from 'src/features/cseMachine/CseMachineLayout'; +import { CseMachine as JavaCseMachine } from 'src/features/cseMachine/java/CseMachine'; import { InterpreterOutput, OverallState } from '../../application/ApplicationTypes'; import { HighlightedLines } from '../../editor/EditorTypes'; @@ -40,6 +42,7 @@ type State = { width: number; lastStep: boolean; stepLimitExceeded: boolean; + chapter: Chapter; }; type CseMachineProps = OwnProps & StateProps & DispatchProps; @@ -53,6 +56,7 @@ type StateProps = { changepointSteps: number[]; needCseUpdate: boolean; machineOutput: InterpreterOutput[]; + chapter: Chapter; }; type OwnProps = { @@ -85,25 +89,39 @@ class SideContentCseMachineBase extends React.Component width: this.calculateWidth(props.editorWidth), height: this.calculateHeight(props.sideContentHeight), lastStep: false, - stepLimitExceeded: false + stepLimitExceeded: false, + chapter: props.chapter }; - CseMachine.init( - visualization => { - this.setState({ visualization }, () => CseAnimation.playAnimation()); - if (visualization) this.props.handleAlertSideContent(); - }, - this.state.width, - this.state.height, - (segments: [number, number][]) => { - // TODO: Hardcoded to make use of the first editor tab. Rewrite after editor tabs are added. - // This comment is copied over from workspace saga - props.setEditorHighlightedLines(0, segments); - }, - // We shouldn't be able to move slider to a step number beyond the step limit - isControlEmpty => { - this.setState({ stepLimitExceeded: false }); - } - ); + if (this.isJava()) { + JavaCseMachine.init( + visualization => this.setState({ visualization }), + (segments: [number, number][]) => { + props.setEditorHighlightedLines(0, segments); + } + ); + } else { + CseMachine.init( + visualization => { + this.setState({ visualization }, () => CseAnimation.playAnimation()); + if (visualization) this.props.handleAlertSideContent(); + }, + this.state.width, + this.state.height, + (segments: [number, number][]) => { + // TODO: Hardcoded to make use of the first editor tab. Rewrite after editor tabs are added. + // This comment is copied over from workspace saga + props.setEditorHighlightedLines(0, segments); + }, + // We shouldn't be able to move slider to a step number beyond the step limit + isControlEmpty => { + this.setState({ stepLimitExceeded: false }); + } + ); + } + } + + private isJava(): boolean { + return this.props.chapter === Chapter.FULL_JAVA; } private calculateWidth(editorWidth?: string) { @@ -173,7 +191,11 @@ class SideContentCseMachineBase extends React.Component } if (prevProps.needCseUpdate && !this.props.needCseUpdate) { this.stepFirst(); - CseMachine.clearCse(); + if (this.isJava()) { + JavaCseMachine.clearCse(); + } else { + CseMachine.clearCse(); + } } } @@ -210,45 +232,53 @@ class SideContentCseMachineBase extends React.Component onRelease={this.sliderRelease} value={this.state.value < 0 ? 0 : this.state.value} /> -
- - - { - if (this.state.visualization) { - CseMachine.toggleControlStash(); - CseMachine.redraw(); - } - }} - icon="layers" - disabled={!this.state.visualization} - > - + {!this.isJava() && ( + + + { + if (this.state.visualization) { + CseMachine.toggleControlStash(); + CseMachine.redraw(); + } + }} + icon="layers" disabled={!this.state.visualization} - style={{ margin: 0 }} - /> - - - - { - if (this.state.visualization) { - CseMachine.toggleStackTruncated(); - CseMachine.redraw(); - } - }} - icon="minimize" - disabled={!this.state.visualization} - > - + + + + + { + if (this.state.visualization) { + CseMachine.toggleStackTruncated(); + CseMachine.redraw(); + } + }} + icon="minimize" disabled={!this.state.visualization} - style={{ margin: 0 }} - /> - - - + > + + + + + )}
{' '} {this.state.visualization && @@ -331,14 +369,34 @@ class SideContentCseMachineBase extends React.Component className={Classes.RUNNING_TEXT} data-testid="cse-machine-default-text" > - The CSE machine generates control, stash and environment model diagrams following a - notation introduced in{' '} - - - Structure and Interpretation of Computer Programs, JavaScript Edition, Chapter 3, - Section 2 - - + {this.isJava() ? ( + + The CSEC machine generates control, stash, environment and class model diagrams + adapted from the notation introduced in{' '} + + + Structure and Interpretation of Computer Programs, JavaScript Edition, Chapter + 3, Section 2 + + + {'. '} + You have chosen the sublanguage{' '} + + Java CSEC + + + ) : ( + + The CSE machine generates control, stash and environment model diagrams following a + notation introduced in{' '} + + + Structure and Interpretation of Computer Programs, JavaScript Edition, Chapter + 3, Section 2 + + + + )} .

On this tab, the REPL will be hidden from view, so do check that your code has no @@ -371,13 +429,13 @@ class SideContentCseMachineBase extends React.Component