From 2a0257312245f909d7133541efa2b39492a119a9 Mon Sep 17 00:00:00 2001 From: xyliew25 <e0550381@u.nus.edu> Date: Wed, 10 Apr 2024 22:52:35 +0800 Subject: [PATCH 01/12] Update ControlBarChapterSelect with Java --- src/commons/controlBar/ControlBarChapterSelect.tsx | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/src/commons/controlBar/ControlBarChapterSelect.tsx b/src/commons/controlBar/ControlBarChapterSelect.tsx index aad0e84e19..47ba376344 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<ControlBarChapterSelectProps> = ( // See https://github.com/source-academy/frontend/pull/2460#issuecomment-1528759912 ...(Constants.playgroundOnly ? [fullJSLanguage, fullTSLanguage, htmlLanguage] : []), ...schemeLanguages, - ...pyLanguages + ...pyLanguages, + ...javaLanguages, ]; return ( From bb1f2b3f43d86c9cb439d345fbff37af7769d111 Mon Sep 17 00:00:00 2001 From: xyliew25 <e0550381@u.nus.edu> Date: Wed, 10 Apr 2024 22:54:13 +0800 Subject: [PATCH 02/12] Implement Java CSEC Visualizer --- src/features/cseMachine/java/CseMachine.tsx | 155 ++++++++++++++ .../cseMachine/java/components/Arrow.tsx | 72 +++++++ .../cseMachine/java/components/Binding.tsx | 98 +++++++++ .../cseMachine/java/components/Control.tsx | 202 ++++++++++++++++++ .../java/components/ControlItem.tsx | 154 +++++++++++++ .../java/components/Environment.tsx | 189 ++++++++++++++++ .../cseMachine/java/components/Frame.tsx | 150 +++++++++++++ .../cseMachine/java/components/Line.tsx | 62 ++++++ .../cseMachine/java/components/Method.tsx | 119 +++++++++++ .../cseMachine/java/components/Object.tsx | 44 ++++ .../cseMachine/java/components/Stash.tsx | 90 ++++++++ .../cseMachine/java/components/StashItem.tsx | 95 ++++++++ .../cseMachine/java/components/Text.tsx | 60 ++++++ .../cseMachine/java/components/Variable.tsx | 109 ++++++++++ 14 files changed, 1599 insertions(+) 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/features/cseMachine/java/CseMachine.tsx b/src/features/cseMachine/java/CseMachine.tsx new file mode 100644 index 0000000000..9893220420 --- /dev/null +++ b/src/features/cseMachine/java/CseMachine.tsx @@ -0,0 +1,155 @@ +import { Context } from "java-slang/dist/ec-evaluator/types"; +import { KonvaEventObject } from "konva/lib/Node"; +import React, { RefObject } from "react"; +import { Layer, Rect, Stage } from "react-konva"; + +import { Config, ShapeDefaultProps } from "./../CseMachineConfig"; +import { Control } from "./components/Control"; +import { Environment } from "./components/Environment"; +import { Stash } from "./components/Stash"; + +type SetVis = (vis: React.ReactNode) => void; +type SetEditorHighlightedLines = (segments: [number, number][]) => void; + +export class CseMachine { + /** the unique key assigned to each node */ + static key: number = 0; + + /** callback function to update the visualization state in the SideContentCseMachine component */ + private static setVis: SetVis; + /** function to highlight editor lines */ + public static setEditorHighlightedLines: SetEditorHighlightedLines; + + public static stageRef: RefObject<any> = React.createRef(); + /** scale factor for zooming and out of canvas */ + public static scaleFactor = 1.02; + + static environment: Environment | undefined; + static control: Control | undefined; + static stash: Stash | undefined; + + static init( + setVis: SetVis, + setEditorHighlightedLines: (segments: [number, number][]) => void, + ) { + this.setVis = setVis; + this.setEditorHighlightedLines = setEditorHighlightedLines; + } + + /** updates the visualization state in the SideContentCseMachine component based on + * the Java Slang context passed in */ + static drawCse(context: Context) { + if (!this.setVis || !context.environment || !context.control || !context.stash) { + throw new Error('Java CSE Machine not initialized'); + } + + CseMachine.environment = new Environment(context.environment); + CseMachine.control = new Control(context.control); + CseMachine.stash = new Stash(context.stash); + + this.setVis(this.draw()); + + // Set icon to blink. + const icon = document.getElementById('env_visualizer-icon'); + icon && icon.classList.add('side-content-tab-alert'); + } + + static clearCse() { + if (this.setVis) { + this.setVis(undefined); + CseMachine.environment = undefined; + CseMachine.control = undefined; + CseMachine.stash = undefined; + } + } + + /** + * Updates the scale of the stage after the user inititates a zoom in or out + * by scrolling or by the trackpad. + */ + static zoomStage(event: KonvaEventObject<WheelEvent> | boolean, multiplier: number = 1) { + typeof event != 'boolean' && event.evt.preventDefault(); + if (CseMachine.stageRef.current) { + const stage = CseMachine.stageRef.current; + const oldScale = stage.scaleX(); + const { x: pointerX, y: pointerY } = stage.getPointerPosition(); + const mousePointTo = { + x: (pointerX - stage.x()) / oldScale, + y: (pointerY - stage.y()) / oldScale + }; + + // zoom in or zoom out + const direction = + typeof event != 'boolean' ? (event.evt.deltaY > 0 ? -1 : 1) : event ? 1 : -1; + + // Check if the zoom limits have been reached + if ((direction > 0 && oldScale < 3) || (direction < 0 && oldScale > 0.4)) { + const newScale = + direction > 0 + ? oldScale * CseMachine.scaleFactor ** multiplier + : oldScale / CseMachine.scaleFactor ** multiplier; + stage.scale({ x: newScale, y: newScale }); + if (typeof event !== 'boolean') { + const newPos = { + x: pointerX - mousePointTo.x * newScale, + y: pointerY - mousePointTo.y * newScale + }; + stage.position(newPos); + stage.batchDraw(); + } + } + } + } + + static draw(): React.ReactNode { + const layout = ( + <div className={'sa-cse-machine'} data-testid="sa-cse-machine"> + <div + id="scroll-container" + style={{ + width: window.innerWidth - 50, + height: window.innerHeight - 150, + overflow: 'hidden' + }} + > + <div + id="large-container" + style={{ + width: Config.CanvasMinWidth, + height: Config.CanvasMinHeight, + overflow: 'hidden', + backgroundColor: Config.SA_BLUE + }} + > + <Stage + width={+Config.CanvasMinWidth} + height={+Config.CanvasMinHeight} + ref={this.stageRef} + draggable + onWheel={CseMachine.zoomStage} + className="draggable" + > + <Layer> + <Rect + {...ShapeDefaultProps} + x={0} + y={0} + width={Config.CanvasMinWidth} + height={Config.CanvasMinHeight} + fill={Config.SA_BLUE} + key={CseMachine.key++} + listening={false} + /> + {this.control?.draw()} + {this.stash?.draw()} + {this.environment?.draw()} + </Layer> + </Stage> + </div> + </div> + </div> + ); + + return layout; + } +} diff --git a/src/features/cseMachine/java/components/Arrow.tsx b/src/features/cseMachine/java/components/Arrow.tsx new file mode 100644 index 0000000000..51de860418 --- /dev/null +++ b/src/features/cseMachine/java/components/Arrow.tsx @@ -0,0 +1,72 @@ +import { KonvaEventObject } from 'konva/lib/Node'; +import { Arrow as KonvaArrow, Group as KonvaGroup, Path as KonvaPath } from 'react-konva'; + +import { Visible } from '../../components/Visible'; +import { Config, ShapeDefaultProps } from '../../CseMachineConfig'; +import { IHoverable } from '../../CseMachineTypes'; +import { + setHoveredCursor, + setHoveredStyle, + setUnhoveredCursor, + setUnhoveredStyle, +} from '../../CseMachineUtils'; +import { CseMachine } from '../CseMachine'; + +/** this class encapsulates an Arrow to be drawn between 2 points */ +export class Arrow extends Visible implements IHoverable { + private static readonly TO_X_INDEX = 2; + private readonly _points: number[] = []; + + constructor( + fromX: number, + fromY: number, + toX: number, + toY: number, + ) { + super(); + this._points.push(fromX, fromY, toX, toY); + } + + setToX(x: number) { + this._points[Arrow.TO_X_INDEX] = x; + } + + onMouseEnter(e: KonvaEventObject<MouseEvent>) { + setHoveredStyle(e.currentTarget) + setHoveredCursor(e.currentTarget); + } + + onMouseLeave(e: KonvaEventObject<MouseEvent>) { + setUnhoveredStyle(e.currentTarget); + setUnhoveredCursor(e.currentTarget); + } + + draw() { + const path = `M ${this._points[0]} ${this._points[1]} L ${this._points[2]} ${this._points[3]}`; + return ( + <KonvaGroup + key={CseMachine.key++} + onMouseEnter={e => this.onMouseEnter(e)} + onMouseLeave={e => this.onMouseLeave(e)} + > + <KonvaPath + {...ShapeDefaultProps} + stroke={String(Config.SA_WHITE)} + strokeWidth={Number(Config.ArrowStrokeWidth)} + hitStrokeWidth={Number(Config.ArrowHitStrokeWidth)} + data={path} + key={CseMachine.key++} + /> + <KonvaArrow + {...ShapeDefaultProps} + points={this._points.slice(this._points.length - 4)} + fill={String(Config.SA_WHITE)} + strokeEnabled={false} + pointerWidth={Number(Config.ArrowHeadSize)} + pointerLength={Number(Config.ArrowHeadSize)} + key={CseMachine.key++} + /> + </KonvaGroup> + ); + } +} diff --git a/src/features/cseMachine/java/components/Binding.tsx b/src/features/cseMachine/java/components/Binding.tsx new file mode 100644 index 0000000000..2a20032c9e --- /dev/null +++ b/src/features/cseMachine/java/components/Binding.tsx @@ -0,0 +1,98 @@ +import { Name, StructType, Value } from 'java-slang/dist/ec-evaluator/types'; +import React from 'react'; + +import { Visible } from '../../components/Visible'; +import { Config } from '../../CseMachineConfig'; +import { CseMachine } from '../CseMachine'; +import { Arrow } from './Arrow'; +import { Method } from './Method'; +import { Text } from './Text'; +import { Variable } from './Variable'; + +/** a Binding is a key-value pair in a Frame */ +export class Binding extends Visible { + private readonly _name: Text; + + private readonly _value: Variable | Method | Text; + // Only Method has arrow. + private readonly _arrow: Arrow | undefined; + + constructor( + name: Name, + value: Value, + x: number, + y: number + ) { + super(); + + // Position. + this._x = x; + this._y = y; + + if (value.kind === StructType.CLOSURE) { + // Name. + this._name = new Text( + name + Config.VariableColon, // := is part of name + this.x(), + this.y()); + // Value. + this._value = new Method( + this._name.x() + this._name.width(), + this.y() + this._name.height() / 2, + value); + this._arrow = new Arrow( + this._name.x() + this._name.width(), + this._name.y() + this._name.height() / 2, + this._value.x(), + this._value.y()); + } else if (value.kind === StructType.VARIABLE) { + // Name. + this._name = new Text( + name + Config.VariableColon, // := is part of name + this.x(), + this.y() + Config.FontSize + Config.TextPaddingX); + // Value. + this._value = new Variable( + this._name.x() + this._name.width(), + this.y(), + value); + } else /*if (value.kind === StructType.CLASS)*/ { + // Dummy value as class will nvr be drawn. + // Name. + this._name = new Text( + name + Config.VariableColon, // := is part of name + this.x(), + this.y() + Config.FontSize + Config.TextPaddingX); + // Value. + this._value = new Text( + "", + this._name.x() + this._name.width() + Config.TextPaddingX, + this.y() + Config.TextPaddingX); + } + + // Height and width. + this._height = Math.max(this._name.height(), this._value.height()); + this._width = this._value.x() + this._value.width() - this._name.x(); + } + + get value() { + return this._value; + } + + setArrowToX(x: number) { + this._arrow?.setToX(x); + } + + draw(): React.ReactNode { + return ( + <React.Fragment key={CseMachine.key++}> + {/* Name */} + {this._name.draw()} + + {/* Value */} + {this._value.draw()} + {this._arrow?.draw()} + </React.Fragment> + ); + } +} diff --git a/src/features/cseMachine/java/components/Control.tsx b/src/features/cseMachine/java/components/Control.tsx new file mode 100644 index 0000000000..7d2a7706bc --- /dev/null +++ b/src/features/cseMachine/java/components/Control.tsx @@ -0,0 +1,202 @@ +import { astToString } from "java-slang/dist/ast/utils/astToString"; +import { Control as JavaControl } from "java-slang/dist/ec-evaluator/components"; +import { + BinOpInstr, + ControlItem as JavaControlItem, + EnvInstr, + EvalVarInstr, + InstrType, + InvInstr, + NewInstr, + ResConOverloadInstr, + ResInstr, + ResOverloadInstr, + ResTypeContInstr, + ResTypeInstr, +} from "java-slang/dist/ec-evaluator/types"; +import { isInstr, isNode } from "java-slang/dist/ec-evaluator/utils"; +import { Group } from "react-konva"; + +import { Visible } from "../../components/Visible"; +import { Config } from "../../CseMachineConfig"; +import { ControlStashConfig } from "../../CseMachineControlStashConfig"; +import { CseMachine } from "../CseMachine"; +import { ControlItem } from "./ControlItem"; + +export class Control extends Visible { + private readonly _controlItems: ControlItem[] = []; + + constructor(control: JavaControl) { + super(); + + // Position. + this._x = ControlStashConfig.ControlPosX; + this._y = ControlStashConfig.ControlPosY + ControlStashConfig.StashItemHeight + ControlStashConfig.StashItemTextPadding * 2; + + // Create each ControlItem. + let controlItemY: number = this._y; + control.getStack().forEach((controlItem, index) => { + const controlItemText = this.getControlItemString(controlItem); + + const controlItemStroke = + index === control.getStack().length - 1 + ? Config.SA_CURRENT_ITEM + : ControlStashConfig.SA_WHITE; + + // TODO reference draw ltr? + const controlItemReference = + isInstr(controlItem) && controlItem.instrType === InstrType.ENV + ? CseMachine.environment?.frames.find(f => f.frame === (controlItem as EnvInstr).env) + : undefined; + + const controlItemTooltip = this.getControlItemTooltip(controlItem); + this.getControlItemTooltip(controlItem); + + const node = isNode(controlItem) ? controlItem : controlItem.srcNode; + const highlightOnHover = () => { + let start = -1; + let end = -1; + if (node.location) { + start = node.location.startLine - 1; + end = node.location.endLine ? node.location.endLine - 1 : start; + } + CseMachine.setEditorHighlightedLines([[start, end]]); + }; + const unhighlightOnHover = () => CseMachine.setEditorHighlightedLines([]); + + const currControlItem = new ControlItem( + controlItemY, + controlItemText, + controlItemStroke, + controlItemReference, + controlItemTooltip, + highlightOnHover, + unhighlightOnHover, + ); + + this._controlItems.push(currControlItem); + controlItemY += currControlItem.height(); + }); + + // Height and width. + this._height = controlItemY - this._y; + // TODO cal real width? + this._width = ControlStashConfig.ControlItemWidth; + } + + draw(): React.ReactNode { + return ( + <Group key={CseMachine.key++} ref={this.ref}> + {this._controlItems.map(c => c.draw())} + </Group> + ); + } + + private getControlItemString = (controlItem: JavaControlItem): string => { + if (isNode(controlItem)) { + return astToString(controlItem); + } + + switch (controlItem.instrType) { + case InstrType.RESET: + return "return"; + case InstrType.ASSIGNMENT: + return "asgn"; + case InstrType.BINARY_OP: + const binOpInstr = controlItem as BinOpInstr; + return binOpInstr.symbol; + case InstrType.POP: + return "pop"; + case InstrType.INVOCATION: + const appInstr = controlItem as InvInstr; + return `invoke ${appInstr.arity}`; + case InstrType.ENV: + return "env"; + case InstrType.MARKER: + return "mark"; + case InstrType.EVAL_VAR: + const evalVarInstr = controlItem as EvalVarInstr; + return `name ${evalVarInstr.symbol}`; + case InstrType.NEW: + const newInstr = controlItem as NewInstr; + return `new ${newInstr.c.frame.name}`; + case InstrType.RES_TYPE: + const resTypeInstr = controlItem as ResTypeInstr; + return `res_type ${resTypeInstr.value.kind === "Class" + ? resTypeInstr.value.frame.name : + astToString(resTypeInstr.value)}`; + case InstrType.RES_TYPE_CONT: + const resTypeContInstr = controlItem as ResTypeContInstr; + return `res_type_cont ${resTypeContInstr.name}`; + case InstrType.RES_OVERLOAD: + const resOverloadInstr = controlItem as ResOverloadInstr; + return `res_overload ${resOverloadInstr.name} ${resOverloadInstr.arity}`; + case InstrType.RES_OVERRIDE: + return `res_override`; + case InstrType.RES_CON_OVERLOAD: + const resConOverloadInstr = controlItem as ResConOverloadInstr; + return `res_con_overload ${resConOverloadInstr.arity}`; + case InstrType.RES: + const resInstr = controlItem as ResInstr; + return `res ${resInstr.name}`; + case InstrType.DEREF: + return "deref"; + default: + return "INSTRUCTION"; + } + } + + private getControlItemTooltip = (controlItem: JavaControlItem): string => { + if (isNode(controlItem)) { + return astToString(controlItem); + } + + switch (controlItem.instrType) { + case InstrType.RESET: + return "Skip control items until marker instruction is reached"; + case InstrType.ASSIGNMENT: + return "Assign value on top of stash to location on top of stash"; + case InstrType.BINARY_OP: + const binOpInstr = controlItem as BinOpInstr; + return `Perform ${binOpInstr.symbol} on top 2 stash values`; + case InstrType.POP: + return "Pop most recently pushed value from stash"; + case InstrType.INVOCATION: + const appInstr = controlItem as InvInstr; + return `Invoke method with ${appInstr.arity} argument${appInstr.arity === 1 ? '' : 's'}`; + case InstrType.ENV: + return "Set current environment to this environment"; + case InstrType.MARKER: + return "Mark return address"; + case InstrType.EVAL_VAR: + const evalVarInstr = controlItem as EvalVarInstr; + return `name ${evalVarInstr.symbol}`; + case InstrType.NEW: + const newInstr = controlItem as NewInstr; + return `Create new instance of class ${newInstr.c.frame.name}`; + case InstrType.RES_TYPE: + const resTypeInstr = controlItem as ResTypeInstr; + return `Resolve type of ${resTypeInstr.value.kind === "Class" + ? resTypeInstr.value.frame.name : + astToString(resTypeInstr.value)}`; + case InstrType.RES_TYPE_CONT: + const resTypeContInstr = controlItem as ResTypeContInstr; + return `Resolve type of ${resTypeContInstr.name} in most recently pushed type from stash`; + case InstrType.RES_OVERLOAD: + const resOverloadInstr = controlItem as ResOverloadInstr; + return `Resolve overloading of method ${resOverloadInstr.name} with ${resOverloadInstr.arity} argument${resOverloadInstr.arity === 1 ? '' : 's'}`; + case InstrType.RES_OVERRIDE: + return "Resolve overriding of resolved method on top of stash"; + case InstrType.RES_CON_OVERLOAD: + const resConOverloadInstr = controlItem as ResConOverloadInstr; + return `Resolve constructor overloading of class on stash with ${resConOverloadInstr.arity} argument${resConOverloadInstr.arity === 1 ? '' : 's'}`; + case InstrType.RES: + const resInstr = controlItem as ResInstr; + return `Resolve field ${resInstr.name} of most recently pushed value from stash`; + case InstrType.DEREF: + return "Dereference most recently pushed value from stash"; + default: + return "INSTRUCTION"; + } + } +} diff --git a/src/features/cseMachine/java/components/ControlItem.tsx b/src/features/cseMachine/java/components/ControlItem.tsx new file mode 100644 index 0000000000..d50dba4d29 --- /dev/null +++ b/src/features/cseMachine/java/components/ControlItem.tsx @@ -0,0 +1,154 @@ +import { KonvaEventObject } from 'konva/lib/Node'; +import React, { RefObject } from 'react'; +import { Label, Tag, Text } from 'react-konva'; + +import { Visible } from '../../components/Visible'; +import { Config, ShapeDefaultProps } from '../../CseMachineConfig'; +import { ControlStashConfig } from '../../CseMachineControlStashConfig'; +import { IHoverable } from '../../CseMachineTypes'; +import { + getTextHeight, + setHoveredCursor, + setHoveredStyle, + setUnhoveredCursor, + setUnhoveredStyle, + truncateText, +} from '../../CseMachineUtils'; +import { CseMachine } from '../CseMachine'; +import { Arrow } from './Arrow'; +import { Frame } from './Frame'; + +export class ControlItem extends Visible implements IHoverable { + private readonly _arrow: Arrow | undefined; + private readonly _tooltipRef: RefObject<any>; + + constructor( + y: number, + + private readonly _text: string, + private readonly _stroke: string, + + reference: Frame | undefined, + + private readonly _tooltip: string, + private readonly highlightOnHover: () => void, + private readonly unhighlightOnHover: () => void, + ) { + super(); + + // Position. + this._x = ControlStashConfig.ControlPosX; + this._y = y; + + // Text. + this._text = truncateText( + this._text, + ControlStashConfig.ControlMaxTextWidth, + ControlStashConfig.ControlMaxTextHeight + ); + + // Tooltip. + this._tooltipRef = React.createRef(); + + // Height and width. + this._height = getTextHeight(this._text, ControlStashConfig.ControlMaxTextWidth) + + ControlStashConfig.ControlItemTextPadding * 2; + this._width = ControlStashConfig.ControlItemWidth; + + // Arrow + if (reference) { + this._arrow = new Arrow( + this._x + this._width, + this._y + this._height / 2, + reference.x(), + reference.y() + reference.height() / 2 + reference.name.height(), + ); + } + } + + private isCurrentItem = (): boolean => { + return this._stroke === Config.SA_CURRENT_ITEM; + } + + onMouseEnter = (e: KonvaEventObject<MouseEvent>): void => { + this.highlightOnHover(); + !this.isCurrentItem() && setHoveredStyle(e.currentTarget); + setHoveredCursor(e.currentTarget); + this._tooltipRef.current.show(); + }; + + onMouseLeave = (e: KonvaEventObject<MouseEvent>): void => { + this.unhighlightOnHover(); + !this.isCurrentItem() && setUnhoveredStyle(e.currentTarget); + setUnhoveredCursor(e.currentTarget); + this._tooltipRef.current.hide(); + }; + + draw(): React.ReactNode { + const textProps = { + fill: ControlStashConfig.SA_WHITE, + padding: ControlStashConfig.ControlItemTextPadding, + fontFamily: ControlStashConfig.FontFamily, + fontSize: ControlStashConfig.FontSize, + fontStyle: ControlStashConfig.FontStyle, + fontVariant: ControlStashConfig.FontVariant, + }; + const tagProps = { + stroke: this._stroke, + cornerRadius: ControlStashConfig.ControlItemCornerRadius, + }; + return ( + <React.Fragment key={CseMachine.key++}> + {/* Text */} + <Label + x={this.x()} + y={this.y()} + onMouseEnter={this.onMouseEnter} + onMouseLeave={this.onMouseLeave} + key={CseMachine.key++} + > + <Tag + {...ShapeDefaultProps} + {...tagProps} + key={CseMachine.key++} + /> + <Text + {...ShapeDefaultProps} + {...textProps} + text={this._text} + width={this.width()} + height={this.height()} + key={CseMachine.key++} + /> + </Label> + + {/* Tooltip */} + <Label + x={this.x() + this.width() + ControlStashConfig.TooltipMargin} + y={this.y() + ControlStashConfig.TooltipMargin} + visible={false} + ref={this._tooltipRef} + key={CseMachine.key++} + > + <Tag + {...ShapeDefaultProps} + stroke="black" + fill={'black'} + opacity={ControlStashConfig.TooltipOpacity} + key={CseMachine.key++} + /> + <Text + {...ShapeDefaultProps} + {...textProps} + text={this._tooltip} + padding={ControlStashConfig.TooltipPadding} + key={CseMachine.key++} + /> + </Label> + + {/* Arrow */} + {this._arrow?.draw()} + </React.Fragment> + ); + } +} diff --git a/src/features/cseMachine/java/components/Environment.tsx b/src/features/cseMachine/java/components/Environment.tsx new file mode 100644 index 0000000000..0e456c170d --- /dev/null +++ b/src/features/cseMachine/java/components/Environment.tsx @@ -0,0 +1,189 @@ +import { Environment as JavaEnvironment, EnvNode } from "java-slang/dist/ec-evaluator/components"; +import { Class as JavaClass, StructType } from "java-slang/dist/ec-evaluator/types"; +import { Group } from "react-konva"; + +import { Visible } from "../../components/Visible"; +import { Config } from "../../CseMachineConfig"; +import { ControlStashConfig } from "../../CseMachineControlStashConfig"; +import { CseMachine } from "../CseMachine"; +import { Arrow } from "./Arrow"; +import { Frame } from "./Frame"; +import { Line } from "./Line"; +import { Object } from "./Object"; +import { Variable } from "./Variable"; + +export class Environment extends Visible { + private readonly _methodFrames: Frame[] = []; + private readonly _objects: Object[] = []; + private readonly _classFrames: Frame[] = []; + private readonly _lines: Line[] = []; + + constructor(environment: JavaEnvironment) { + super(); + + // Position. + this._x = ControlStashConfig.ControlPosX + ControlStashConfig.ControlItemWidth + 2 * Config.CanvasPaddingX; + this._y = ControlStashConfig.StashPosY + ControlStashConfig.StashItemHeight + 2 * Config.CanvasPaddingY; + + // Create method frames. + const methodFramesX = this._x; + let methodFramesY: number = this._y; + let methodFramesWidth = Number(Config.FrameMinWidth); + environment.global.children.forEach(env => { + if (env.name.includes("(")) { + let currEnv: EnvNode | undefined = env; + let parentFrame; + while (currEnv) { + const stroke = currEnv === environment.current ? Config.SA_CURRENT_ITEM : Config.SA_WHITE; + const frame = new Frame(currEnv, methodFramesX, methodFramesY, stroke); + this._methodFrames.push(frame); + methodFramesY += (frame.height() + Config.FramePaddingY); + methodFramesWidth = Math.max(methodFramesWidth, frame.width()); + parentFrame && frame.setParent(parentFrame); + + parentFrame = frame; + currEnv = currEnv.children.length ? currEnv.children[0] : undefined; + } + } + }); + + // Create objects. + const objectFramesX = methodFramesX + methodFramesWidth + Config.FrameMinWidth; + let objectFramesY: number = this._y; + let objectFramesWidth = Number(Config.FrameMinWidth); + environment.objects.forEach(obj => { + const objectFrames: Frame[] = []; + let objectFrameWidth = Number(Config.FrameMinWidth); + + // Get top env. + let env: EnvNode | undefined = obj.frame; + while (env.parent) { + env = env.parent; + } + + // Create frame top-down. + while (env) { + const stroke = env === environment.current ? Config.SA_CURRENT_ITEM : Config.SA_WHITE; + const frame = new Frame(env, objectFramesX, objectFramesY, stroke); + // No padding btwn obj frames thus no arrows required. + objectFramesY += frame.height(); + objectFramesWidth = Math.max(objectFramesWidth, frame.width()); + + env = env.children.length ? env.children[0] : undefined; + + objectFrames.push(frame); + objectFrameWidth = Math.max(objectFrameWidth, frame.width()); + } + + // Standardize obj frames width. + objectFrames.forEach(o => o.setWidth(objectFrameWidth)) + + // Only add padding btwn objects. + objectFramesY += Config.FramePaddingY + + this._objects.push(new Object(objectFrames, obj)); + }); + + // Create class frames. + const classFramesX = objectFramesX + objectFramesWidth + Config.FrameMinWidth; + let classFramesY = this._y; + for (const [_, c] of environment.global.frame.entries()) { + const classEnv = (c as JavaClass).frame; + const classFrameStroke = classEnv === environment.current ? Config.SA_CURRENT_ITEM : Config.SA_WHITE; + const highlightOnHover = () => { + const node = (c as JavaClass).classDecl; + let start = -1; + let end = -1; + if (node.location) { + start = node.location.startLine - 1; + end = node.location.endLine ? node.location.endLine - 1 : start; + } + CseMachine.setEditorHighlightedLines([[start, end]]); + }; + const unhighlightOnHover = () => CseMachine.setEditorHighlightedLines([]); + const classFrame = new Frame(classEnv, classFramesX, classFramesY, classFrameStroke, "", highlightOnHover, unhighlightOnHover); + const superClassName = (c as JavaClass).superclass?.frame.name; + if (superClassName) { + const parentFrame = this._classFrames.find(f => f.name.text === superClassName)!; + classFrame.setParent(parentFrame) + } + this._classFrames.push(classFrame); + classFramesY += (classFrame.height() + Config.FramePaddingY); + } + + // Draw arrow for var ref in mtd frames to corresponding obj. + this._methodFrames.forEach(mf => { + mf.bindings.forEach(b => { + if (b.value instanceof Variable && b.value.variable.value.kind === StructType.OBJECT) { + const objFrame = b.value.variable.value.frame; + const matchingObj = this._objects.filter(o => o.getFrame().frame === objFrame)[0]; + b.value.value = new Arrow( + b.value.x() + b.value.width() / 2, + b.value.y() + b.value.type.height() + (b.value.height() - b.value.type.height()) / 2, + matchingObj.x(), + matchingObj.y() + matchingObj.height() / 2 + ); + } + }) + }); + + // Draw arrow for var ref in obj frames to corresponding var or obj. + this._objects.flatMap(obj => obj.frames).forEach(of => { + of.bindings.forEach(b => { + if (b.value instanceof Variable && b.value.variable.value.kind === StructType.VARIABLE) { + const variable = b.value.variable.value; + const matchingVariable = this._classFrames.flatMap(c => c.bindings).filter(b => b.value instanceof Variable && b.value.variable === variable)[0].value as Variable; + b.value.value = new Arrow( + b.value.x() + b.value.width() / 2, + b.value.y() + b.value.type.height() + (b.value.height() - b.value.type.height()) / 2, + matchingVariable.x(), + matchingVariable.y() + matchingVariable.type.height()); + } + if (b.value instanceof Variable && b.value.variable.value.kind === StructType.OBJECT) { + const obj = b.value.variable.value.frame; + const matchingObj = this._objects.find(o => o.getFrame().frame === obj)!; + // Variable always has a box. + b.value.value = new Arrow( + b.value.x() + b.value.width() / 2, + b.value.y() + b.value.type.height() + (b.value.height() - b.value.type.height()) / 2, + matchingObj.x(), + matchingObj.y() + matchingObj.height() / 2); + } + }) + }); + + // Draw line for obj to class. + this._objects.forEach(obj => { + const matchingClass = this._classFrames.find(c => c.name.text === obj.object.class.frame.name)!; + const line = new Line( + obj.x() + obj.width(), + obj.y() + obj.height() / 2, + matchingClass.x(), + matchingClass.y() + matchingClass.height() / 2 + matchingClass.name.height()); + this._lines.push(line); + }); + } + + get classes() { + return this._classFrames; + } + + get objects() { + return this._objects.flatMap(obj => obj.frames); + } + + get frames() { + return this._methodFrames; + } + + draw(): React.ReactNode { + return ( + <Group key={CseMachine.key++}> + {this._methodFrames.map(f => f.draw())} + {this._objects.flatMap(obj => obj.frames).map(f => f.draw())} + {this._classFrames.map(f => f.draw())} + {this._lines.map(f => f.draw())} + </Group> + ); + } +} diff --git a/src/features/cseMachine/java/components/Frame.tsx b/src/features/cseMachine/java/components/Frame.tsx new file mode 100644 index 0000000000..975e07e79e --- /dev/null +++ b/src/features/cseMachine/java/components/Frame.tsx @@ -0,0 +1,150 @@ +import { EnvNode } from 'java-slang/dist/ec-evaluator/components'; +import { KonvaEventObject } from 'konva/lib/Node'; +import React, { RefObject } from 'react'; +import { Group, Label, Rect, Tag, Text as KonvaText } from 'react-konva'; + +import { Visible } from '../../components/Visible'; +import { Config, ShapeDefaultProps } from '../../CseMachineConfig'; +import { ControlStashConfig } from '../../CseMachineControlStashConfig'; +import { IHoverable } from '../../CseMachineTypes'; +import { setHoveredCursor, setUnhoveredCursor } from '../../CseMachineUtils'; +import { CseMachine } from '../CseMachine'; +import { Arrow } from './Arrow'; +import { Binding } from './Binding'; +import { Method } from './Method'; +import { Text } from './Text'; + +export class Frame extends Visible implements IHoverable { + readonly tooltipRef: RefObject<any>; + + readonly bindings: Binding[] = []; + readonly name: Text; + private parent: Frame | undefined; + + constructor( + readonly frame: EnvNode, + x: number, + y: number, + readonly stroke: string, + + readonly tooltip?: string, + readonly highlightOnHover?: () => void, + readonly unhighlightOnHover?: () => void, + ) { + super(); + + this._x = x; + this._y = y; + + this.name = new Text(frame.name, this._x + Config.FramePaddingX, this._y); + + this._width = Math.max(Config.FrameMinWidth, this.name.width() + 2 * Config.FramePaddingX); + this._height = Config.FramePaddingY + this.name.height(); + + // Create binding for each key-value pair + let bindingY: number = this._y + this.name.height() + Config.FramePaddingY; + for (const [key, data] of frame.frame) { + const currBinding: Binding = new Binding(key, data, this._x + Config.FramePaddingX, bindingY); + this.bindings.push(currBinding); + bindingY += (currBinding.height() + Config.FramePaddingY); + this._width = Math.max(this._width, currBinding.width() + 2 * Config.FramePaddingX); + this._height += (currBinding.height() + Config.FramePaddingY); + } + + // Set x of Method aft knowing frame width. + this.bindings.filter(b => b.value instanceof Method).forEach(b => { + (b.value as Method).setX(this._x + this._width + Config.FramePaddingX); + b.setArrowToX(this._x + this._width + Config.FramePaddingX); + }) + + this.tooltipRef = React.createRef(); + } + + setWidth(width: number) { + this._width = width; + } + + setParent(parent: Frame) { + this.parent = parent; + } + + onMouseEnter = (e: KonvaEventObject<MouseEvent>) => { + this.highlightOnHover && this.highlightOnHover(); + (this.tooltip || this.highlightOnHover) && setHoveredCursor(e.currentTarget); + this.tooltip && this.tooltipRef.current.show(); + }; + + onMouseLeave = (e: KonvaEventObject<MouseEvent>) => { + this.unhighlightOnHover && this.unhighlightOnHover(); + (this.tooltip || this.unhighlightOnHover) && setUnhoveredCursor(e.currentTarget); + this.tooltip && this.tooltipRef.current.hide(); + }; + + draw(): React.ReactNode { + const textProps = { + fill: ControlStashConfig.SA_WHITE.toString(), + padding: Number(ControlStashConfig.ControlItemTextPadding), + fontFamily: ControlStashConfig.FontFamily.toString(), + fontSize: Number(ControlStashConfig.FontSize), + fontStyle: ControlStashConfig.FontStyle.toString(), + fontVariant: ControlStashConfig.FontVariant.toString() + }; + + return ( + <Group key={CseMachine.key++}> + <Rect + {...ShapeDefaultProps} + x={this.x()} + y={this.y() + this.name.height()} + width={this.width()} + height={this.height()} + stroke={this.stroke} + cornerRadius={Number(Config.FrameCornerRadius)} + onMouseEnter={this.onMouseEnter} + onMouseLeave={this.onMouseLeave} + key={CseMachine.key++} + /> + {/* Frame name */} + {this.name.draw()} + + {/* Frame */} + {this.bindings.map(binding => binding.draw())} + + {/* Frame parent */} + {this.parent && new Arrow( + this._x + Config.FramePaddingX / 2, + this._y + this.name.height(), + this.parent.x() + Config.FramePaddingX / 2, + // TODO WHY NEED TO ADD NAME HEIGHT? + this.parent.y() + this.parent.height() + this.name?.height() + ).draw()} + + {/* Frame tooltip */} + {this.tooltip && + <Label + x={this.x() + this.width() + ControlStashConfig.TooltipMargin} + y={this.y() + ControlStashConfig.TooltipMargin} + visible={false} + ref={this.tooltipRef} + key={CseMachine.key++} + > + <Tag + {...ShapeDefaultProps} + stroke="black" + fill={'black'} + opacity={Number(ControlStashConfig.TooltipOpacity)} + key={CseMachine.key++} + /> + <KonvaText + {...ShapeDefaultProps} + {...textProps} + text={this.tooltip} + padding={Number(ControlStashConfig.TooltipPadding)} + key={CseMachine.key++} + /> + </Label> + } + </Group> + ); + } +} diff --git a/src/features/cseMachine/java/components/Line.tsx b/src/features/cseMachine/java/components/Line.tsx new file mode 100644 index 0000000000..ec462a6846 --- /dev/null +++ b/src/features/cseMachine/java/components/Line.tsx @@ -0,0 +1,62 @@ +import { KonvaEventObject } from 'konva/lib/Node'; +import { Group, Path } from 'react-konva'; + +import { Visible } from '../../components/Visible'; +import { Config, ShapeDefaultProps } from '../../CseMachineConfig'; +import { IHoverable } from '../../CseMachineTypes'; +import { + setHoveredCursor, + setHoveredStyle, + setUnhoveredCursor, + setUnhoveredStyle, +} from '../../CseMachineUtils'; +import { CseMachine } from '../CseMachine'; + +/** this class encapsulates a Line to be drawn between 2 points */ +export class Line extends Visible implements IHoverable { + private static readonly TO_X_INDEX = 2; + private readonly _points: number[] = []; + + constructor( + fromX: number, + fromY: number, + toX: number, + toY: number, + ) { + super(); + this._points.push(fromX, fromY, toX, toY); + } + + setToX(x: number) { + this._points[Line.TO_X_INDEX] = x; + } + + onMouseEnter(e: KonvaEventObject<MouseEvent>) { + setHoveredStyle(e.currentTarget) + setHoveredCursor(e.currentTarget); + } + + onMouseLeave(e: KonvaEventObject<MouseEvent>) { + setUnhoveredStyle(e.currentTarget) + setUnhoveredCursor(e.currentTarget); + } + + draw() { + const path = `M ${this._points[0]} ${this._points[1]} L ${this._points[2]} ${this._points[3]}`; + return ( + <Group key={CseMachine.key++} + onMouseEnter={e => this.onMouseEnter(e)} + onMouseLeave={e => this.onMouseLeave(e)} + > + <Path + {...ShapeDefaultProps} + stroke={String(Config.SA_WHITE)} + strokeWidth={Number(Config.ArrowStrokeWidth)} + hitStrokeWidth={Number(Config.ArrowHitStrokeWidth)} + data={path} + key={CseMachine.key++} + /> + </Group> + ); + } +} diff --git a/src/features/cseMachine/java/components/Method.tsx b/src/features/cseMachine/java/components/Method.tsx new file mode 100644 index 0000000000..f871358444 --- /dev/null +++ b/src/features/cseMachine/java/components/Method.tsx @@ -0,0 +1,119 @@ +import { astToString } from 'java-slang/dist/ast/utils/astToString'; +import { Closure as JavaMethod } from 'java-slang/dist/ec-evaluator/types'; +import { KonvaEventObject } from 'konva/lib/Node'; +import React, { RefObject } from 'react'; +import { Circle, Group, Label, Tag, Text } from 'react-konva'; + +import { Visible } from '../../components/Visible'; +import { Config, ShapeDefaultProps } from '../../CseMachineConfig'; +import { ControlStashConfig } from '../../CseMachineControlStashConfig'; +import { IHoverable } from '../../CseMachineTypes'; +import { + getTextHeight, + getTextWidth, + setHoveredCursor, + setUnhoveredCursor, +} from '../../CseMachineUtils'; +import { CseMachine } from '../CseMachine'; + +export class Method extends Visible implements IHoverable { + private _centerX: number; + + private readonly _tooltipRef: RefObject<any>; + private readonly _tooltip: string; + + constructor( + x: number, + y: number, + private readonly _method: JavaMethod, + ) { + super(); + + // Position. + this._x = x; + this._y = y; + + // Circle. + this._centerX = this._x + Config.FnRadius * 2; + + // Tooltip. + this._tooltipRef = React.createRef(); + this._tooltip = astToString(this._method.mtdOrCon); + } + + get method() { + return this._method; + } + + setX(x: number) { + this._x = x; + this._centerX = this._x + Config.FnRadius * 2; + } + + onMouseEnter = (e: KonvaEventObject<MouseEvent>) => { + setHoveredCursor(e.currentTarget); + this._tooltipRef.current.show(); + }; + + onMouseLeave = (e: KonvaEventObject<MouseEvent>) => { + setUnhoveredCursor(e.currentTarget); + this._tooltipRef.current.hide(); + }; + + draw(): React.ReactNode { + return ( + <React.Fragment key={CseMachine.key++}> + <Group + onMouseEnter={e => this.onMouseEnter(e)} + onMouseLeave={e => this.onMouseLeave(e)} + // ref={this.ref} + key={CseMachine.key++} + > + {/* Left outer */} + <Circle + {...ShapeDefaultProps} + key={CseMachine.key++} + x={this._centerX - Config.FnRadius} + y={this.y()} + radius={Config.FnRadius} + stroke={ControlStashConfig.SA_WHITE} + /> + {/* Left inner */} + <Circle + {...ShapeDefaultProps} + key={CseMachine.key++} + x={this._centerX - Config.FnRadius} + y={this.y()} + radius={Config.FnInnerRadius} + fill={String(ControlStashConfig.SA_WHITE)} + /> + </Group> + + {/* Tooltip */} + <Label + x={this.x() + Config.FnRadius * 2 + Config.TextPaddingX * 2} + y={this.y() - getTextHeight(this._tooltip, getTextWidth(this._tooltip)) / 2} + visible={false} + ref={this._tooltipRef} + key={CseMachine.key++} + > + <Tag + stroke="black" + fill={'black'} + opacity={Config.FnTooltipOpacity} + key={CseMachine.key++} + /> + <Text + text={this._tooltip} + fontFamily={ControlStashConfig.FontFamily} + fontSize={ControlStashConfig.FontSize} + fontStyle={ControlStashConfig.FontStyle} + fill={ControlStashConfig.SA_WHITE} + padding={5} + key={CseMachine.key++} + /> + </Label> + </React.Fragment> + ); + } +} diff --git a/src/features/cseMachine/java/components/Object.tsx b/src/features/cseMachine/java/components/Object.tsx new file mode 100644 index 0000000000..054a72944a --- /dev/null +++ b/src/features/cseMachine/java/components/Object.tsx @@ -0,0 +1,44 @@ +import { Object as JavaObject } from 'java-slang/dist/ec-evaluator/types'; +import React from 'react'; +import { Group } from 'react-konva'; + +import { Visible } from '../../components/Visible'; +import { CseMachine } from '../CseMachine'; +import { Frame } from './Frame'; + +export class Object extends Visible { + constructor( + private readonly _frames: Frame[], + private readonly _object: JavaObject, + ) { + super(); + + // Position. + this._x = _frames[0].x(); + this._y = _frames[0].y(); + + // Height and width. + this._height = this._frames.reduce((accHeight, currFrame) => accHeight + currFrame.height(), 0); + this._width = this._frames.reduce((maxWidth, currFrame) => Math.max(maxWidth, currFrame.width()), 0); + } + + get frames() { + return this._frames; + } + + get object() { + return this._object; + } + + getFrame(): Frame { + return this._frames[this._frames.length - 1]; + } + + draw(): React.ReactNode { + return ( + <Group key={CseMachine.key++}> + {this._frames.map(f => f.draw())} + </Group> + ); + } +} diff --git a/src/features/cseMachine/java/components/Stash.tsx b/src/features/cseMachine/java/components/Stash.tsx new file mode 100644 index 0000000000..d303dc6fe0 --- /dev/null +++ b/src/features/cseMachine/java/components/Stash.tsx @@ -0,0 +1,90 @@ +import { Stash as JavaStash } from 'java-slang/dist/ec-evaluator/components'; +import { StashItem as JavaStashItem, StructType } from 'java-slang/dist/ec-evaluator/types'; +import React from 'react'; +import { Group } from 'react-konva'; + +import { Visible } from '../../components/Visible'; +import { ControlStashConfig } from '../../CseMachineControlStashConfig'; +import { CseMachine } from '../CseMachine'; +import { Method } from './Method'; +import { StashItem } from './StashItem'; +import { Variable } from './Variable'; + +export class Stash extends Visible { + private readonly _stashItems: StashItem[] = []; + + constructor(stash: JavaStash) { + super(); + + // Position. + this._x = ControlStashConfig.StashPosX; + this._y = ControlStashConfig.StashPosY; + + // Create each StashItem. + let stashItemX: number = this._x; + for (const stashItem of stash.getStack()) { + const stashItemText = this.getStashItemString(stashItem); + const stashItemStroke = ControlStashConfig.SA_WHITE; + const stashItemReference = this.getStashItemRef(stashItem); + const currStashItem = new StashItem( + stashItemX, + stashItemText, + stashItemStroke, + stashItemReference); + + this._stashItems.push(currStashItem); + stashItemX += currStashItem.width(); + } + + // Height and width. + this._height = ControlStashConfig.StashItemHeight; + this._width = stashItemX - this._x; + } + + draw(): React.ReactNode { + return ( + <Group key={CseMachine.key++} > + {this._stashItems.map(s => s.draw())} + </Group> + ); + } + + private getStashItemString = (stashItem: JavaStashItem): string => { + switch (stashItem.kind) { + case "Literal": + return stashItem.literalType.value; + case StructType.VARIABLE: + return "location"; + case StructType.TYPE: + return stashItem.type; + default: + return stashItem.kind.toLowerCase(); + } + } + + private getStashItemRef = (stashItem: JavaStashItem) => { + return stashItem.kind === StructType.CLOSURE + ? CseMachine.environment && + CseMachine.environment.classes + .flatMap(c => c.bindings) + .find(b => b.value instanceof Method && b.value.method === stashItem)?.value as Method + : stashItem.kind === StructType.VARIABLE + ? CseMachine.environment && + (CseMachine.environment.frames + .flatMap(c => c.bindings) + .find(b => b.value instanceof Variable && b.value.variable === stashItem)?.value as Variable || + CseMachine.environment.classes + .flatMap(c => c.bindings) + .find(b => b.value instanceof Variable && b.value.variable === stashItem)?.value as Variable || + CseMachine.environment.objects + .flatMap(o => o.bindings) + .find(b => b.value instanceof Variable && b.value.variable === stashItem)?.value as Variable) + : stashItem.kind === StructType.CLASS + ? CseMachine.environment && + CseMachine.environment.classes.find(c => c.frame === stashItem.frame) + : stashItem.kind === StructType.OBJECT + ? CseMachine.environment && + CseMachine.environment.objects.find(o => o.frame === stashItem.frame) + : undefined; + } +} diff --git a/src/features/cseMachine/java/components/StashItem.tsx b/src/features/cseMachine/java/components/StashItem.tsx new file mode 100644 index 0000000000..2cdb91d8f1 --- /dev/null +++ b/src/features/cseMachine/java/components/StashItem.tsx @@ -0,0 +1,95 @@ +import React from 'react'; +import { + Group as KonvaGroup, + Label as KonvaLabel, + Tag as KonvaTag, + Text as KonvaText, +} from 'react-konva'; + +import { Visible } from '../../components/Visible'; +import { ShapeDefaultProps } from '../../CseMachineConfig'; +import { ControlStashConfig } from '../../CseMachineControlStashConfig'; +import { getTextWidth } from '../../CseMachineUtils'; +import { CseMachine } from '../CseMachine'; +import { Arrow } from './Arrow'; +import { Frame } from './Frame'; +import { Method } from './Method'; +import { Variable } from './Variable'; + +export class StashItem extends Visible { + private readonly _arrow: Arrow | undefined; + + constructor( + x: number, + private readonly _text: string, + private readonly _stroke: string, + reference?: Method | Frame | Variable, + ) { + super(); + + // Position. + this._x = x; + this._y = ControlStashConfig.StashPosY; + + // Height and width. + this._height = ControlStashConfig.StashItemHeight + ControlStashConfig.StashItemTextPadding * 2; + this._width = ControlStashConfig.StashItemTextPadding * 2 + getTextWidth(this._text); + + // Arrow + if (reference) { + const toY = + reference instanceof Frame + ? reference.y() + reference.name.height() + : reference instanceof Method + ? reference.y() + : reference.y() + reference.type.height(); + this._arrow = new Arrow( + this._x + this._width / 2, + this._y + this._height, + reference.x(), + toY); + } + } + + draw(): React.ReactNode { + const textProps = { + fill: ControlStashConfig.SA_WHITE, + padding: ControlStashConfig.StashItemTextPadding, + fontFamily: ControlStashConfig.FontFamily, + fontSize: ControlStashConfig.FontSize, + fontStyle: ControlStashConfig.FontStyle, + fontVariant: ControlStashConfig.FontVariant, + }; + + const tagProps = { + stroke: this._stroke, + cornerRadius: ControlStashConfig.StashItemCornerRadius, + }; + + return ( + <KonvaGroup key={CseMachine.key++}> + {/* Text */} + <KonvaLabel + x={this.x()} + y={this.y()} + key={CseMachine.key++} + > + <KonvaTag + {...ShapeDefaultProps} + {...tagProps} + key={CseMachine.key++} + /> + <KonvaText + {...ShapeDefaultProps} + {...textProps} + text={this._text} + key={CseMachine.key++} + /> + </KonvaLabel> + + {/* Arrow */} + {this._arrow?.draw()} + </KonvaGroup> + ); + } +} diff --git a/src/features/cseMachine/java/components/Text.tsx b/src/features/cseMachine/java/components/Text.tsx new file mode 100644 index 0000000000..89d10c3366 --- /dev/null +++ b/src/features/cseMachine/java/components/Text.tsx @@ -0,0 +1,60 @@ +import React from 'react'; +import { Group as KonvaGroup, Label as KonvaLabel, Text as KonvaText } from 'react-konva'; + +import { Visible } from '../../components/Visible'; +import { Config, ShapeDefaultProps } from '../../CseMachineConfig'; +import { getTextWidth } from '../../CseMachineUtils'; +import { CseMachine } from '../CseMachine'; + +/** this class encapsulates a string to be drawn onto the canvas */ +export class Text extends Visible { + constructor( + private readonly _text: string, + x: number, + y: number, + ) { + super(); + + // Position + this._x = x; + this._y = y; + + // Height and width + this._height = Config.FontSize; + this._width = getTextWidth(this._text); + } + + get text() { + return this._text; + } + + setY(y: number) { + this._y = y; + } + + draw(): React.ReactNode { + const props = { + fontFamily: Config.FontFamily, + fontSize: Config.FontSize, + fontStyle: Config.FontStyle, + fill: Config.SA_WHITE, + }; + + return ( + <KonvaGroup key={CseMachine.key++}> + <KonvaLabel + x={this.x()} + y={this.y()} + key={CseMachine.key++} + > + <KonvaText + {...ShapeDefaultProps} + key={CseMachine.key++} + text={this._text} + {...props} + /> + </KonvaLabel> + </KonvaGroup> + ); + } +} diff --git a/src/features/cseMachine/java/components/Variable.tsx b/src/features/cseMachine/java/components/Variable.tsx new file mode 100644 index 0000000000..a784db2f7e --- /dev/null +++ b/src/features/cseMachine/java/components/Variable.tsx @@ -0,0 +1,109 @@ +import { StructType, Variable as JavaVariable } from 'java-slang/dist/ec-evaluator/types'; +import React from 'react'; +import { Group, Rect } from 'react-konva'; + +import { Visible } from '../../components/Visible'; +import { Config, ShapeDefaultProps } from '../../CseMachineConfig'; +import { CseMachine } from '../CseMachine'; +import { Arrow } from './Arrow'; +import { Text } from './Text'; + +export interface TextOptions { + maxWidth: number; + fontSize: number; + fontFamily: string; + fontStyle: string; + fontVariant: string; + isStringIdentifiable: boolean; +} + +export const defaultOptions: TextOptions = { + maxWidth: Number.MAX_VALUE, // maximum width this text should be + fontFamily: Config.FontFamily.toString(), // default is Arial + fontSize: Number(Config.FontSize), // in pixels. Default is 12 + fontStyle: Config.FontStyle.toString(), // can be normal, bold, or italic. Default is normal + fontVariant: Config.FontVariant.toString(), // can be normal or small-caps. Default is normal + isStringIdentifiable: false // if true, contain strings within double quotation marks "". Default is false +}; + +export class Variable extends Visible { + private readonly _type: Text; + private _value: Text | Arrow; + + constructor( + x: number, + y: number, + private readonly _variable: JavaVariable, + ) { + super(); + + // Position. + this._x = x; + this._y = y; + + // Type. + this._type = new Text( + this._variable.type, + this._x, + this._y); + + // Value. + if (this.variable.value.kind === "Literal") { + this._value = new Text( + this.variable.value.literalType.value, + this._x + Config.TextPaddingX, + this._y + this._type.height() + Config.TextPaddingX); + } else if (this.variable.value.kind === StructType.SYMBOL) { + this._value = new Text( + "", + this._x + Config.TextPaddingX, + this._y + this._type.height() + Config.TextPaddingX); + } else { + this._value = new Text( + "", + this._x + Config.TextPaddingX, + this._y + this._type.height() + Config.TextPaddingX); + } + + // Height and width. + this._height = this._type.height() + this._value.height() + 2 * Config.TextPaddingX; + this._width = Math.max(this._type.width(), this._value.width() + 2 * Config.TextPaddingX); + } + + get variable() { + return this._variable; + } + + set value(value: Arrow) { + this._value = value; + this._height = this._type.height() + Config.FontSize + 2 * Config.TextPaddingX; + this._width = Math.max(this._type.width(), Config.TextMinWidth); + } + + get type() { + return this._type; + } + + draw(): React.ReactNode { + return ( + <Group key={CseMachine.key++}> + {/* Type */} + {this._type.draw()} + + {/* Box */} + <Rect + {...ShapeDefaultProps} + x={this._x} + y={this._y + this._type.height()} + width={this._width} + height={this._height - this._type.height()} + stroke={Config.SA_WHITE} + key={CseMachine.key++} + /> + + {/* Text */} + {this._value.draw()} + </Group> + ); + } +} From b384d101b7f30fccdf179d6cfa40af91a2c26da3 Mon Sep 17 00:00:00 2001 From: xyliew25 <e0550381@u.nus.edu> Date: Wed, 10 Apr 2024 22:55:27 +0800 Subject: [PATCH 03/12] Integrate CSEC Visualizer --- src/commons/application/ApplicationTypes.ts | 2 +- src/commons/sagas/PlaygroundSaga.ts | 6 + .../sagas/WorkspaceSaga/helpers/evalCode.ts | 5 +- .../WorkspaceSaga/helpers/updateInspector.ts | 39 ++++-- .../content/SideContentCseMachine.tsx | 123 ++++++++++++------ src/commons/utils/JavaHelper.ts | 53 +++++++- 6 files changed, 177 insertions(+), 51 deletions(-) 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/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..3f4f6e208c 100644 --- a/src/commons/sagas/WorkspaceSaga/helpers/evalCode.ts +++ b/src/commons/sagas/WorkspaceSaga/helpers/evalCode.ts @@ -250,6 +250,9 @@ 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 +269,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..f109aa76e9 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/content/SideContentCseMachine.tsx b/src/commons/sideContent/content/SideContentCseMachine.tsx index 009c2aec85..e77c96a220 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<CseMachineProps, State> 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<CseMachineProps, State> } if (prevProps.needCseUpdate && !this.props.needCseUpdate) { this.stepFirst(); - CseMachine.clearCse(); + if (this.isJava()) { + JavaCseMachine.clearCse(); + } else { + CseMachine.clearCse(); + } } } @@ -210,8 +232,8 @@ class SideContentCseMachineBase extends React.Component<CseMachineProps, State> onRelease={this.sliderRelease} value={this.state.value < 0 ? 0 : this.state.value} /> - <div style={{ display: 'flex', justifyContent: 'space-between', alignItems: 'center' }}> - <ButtonGroup> + <div style={{ display: 'flex', justifyContent: this.isJava() ? 'center' : 'space-between', alignItems: 'center' }}> + {!this.isJava() && <ButtonGroup> <Tooltip2 content="Control and Stash" compact> <AnchorButton onMouseUp={() => { @@ -248,7 +270,7 @@ class SideContentCseMachineBase extends React.Component<CseMachineProps, State> /> </AnchorButton> </Tooltip2> - </ButtonGroup> + </ButtonGroup>} <ButtonGroup> <Button disabled={!this.state.visualization} @@ -259,13 +281,13 @@ class SideContentCseMachineBase extends React.Component<CseMachineProps, State> disabled={!this.state.visualization} icon="chevron-left" onClick={ - CseMachine.getControlStash() ? this.stepPrevious : this.stepPrevChangepoint + this.isJava() || CseMachine.getControlStash() ? this.stepPrevious : this.stepPrevChangepoint } /> <Button disabled={!this.state.visualization} icon="chevron-right" - onClick={CseMachine.getControlStash() ? this.stepNext : this.stepNextChangepoint} + onClick={this.isJava() || CseMachine.getControlStash() ? this.stepNext : this.stepNextChangepoint} /> <Button disabled={!this.state.visualization} @@ -273,7 +295,7 @@ class SideContentCseMachineBase extends React.Component<CseMachineProps, State> onClick={this.stepNextBreakpoint} /> </ButtonGroup> - <ButtonGroup> + {!this.isJava() && <ButtonGroup> <Tooltip2 content="Print" compact> <AnchorButton onMouseUp={() => { @@ -299,7 +321,7 @@ class SideContentCseMachineBase extends React.Component<CseMachineProps, State> onClick={Layout.exportImage} /> </Tooltip2> - </ButtonGroup> + </ButtonGroup>} </div> </div>{' '} {this.state.visualization && @@ -331,14 +353,32 @@ class SideContentCseMachineBase extends React.Component<CseMachineProps, State> 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{' '} - <a href={Links.textbookChapter3_2} rel="noopener noreferrer" target="_blank"> - <i> - Structure and Interpretation of Computer Programs, JavaScript Edition, Chapter 3, - Section 2 - </i> - </a> + {this.isJava() + ? <span> + The CSEC machine generates control, stash, environment and class model diagrams adapted from the + notation introduced in{' '} + <a href={Links.textbookChapter3_2} rel="noopener noreferrer" target="_blank"> + <i> + Structure and Interpretation of Computer Programs, JavaScript Edition, Chapter 3, + Section 2 + </i> + </a>{'. '} + + You have chosen the sublanguage{' '} + <a href={`${Links.sourceDocs}java_csec/`} rel="noopener noreferrer" target="_blank"> + <i>Java CSEC</i> + </a> + </span> + : <span> + The CSE machine generates control, stash and environment model diagrams following a + notation introduced in{' '} + <a href={Links.textbookChapter3_2} rel="noopener noreferrer" target="_blank"> + <i> + Structure and Interpretation of Computer Programs, JavaScript Edition, Chapter 3, + Section 2 + </i> + </a> + </span>} . <br /> <br /> On this tab, the REPL will be hidden from view, so do check that your code has no @@ -371,13 +411,13 @@ class SideContentCseMachineBase extends React.Component<CseMachineProps, State> <Button icon="plus" disabled={!this.state.visualization} - onClick={() => Layout.zoomStage(true, 5)} + onClick={() => this.zoomStage(true, 5)} style={{ marginBottom: '5px', borderRadius: '3px' }} /> <Button icon="minus" disabled={!this.state.visualization} - onClick={() => Layout.zoomStage(false, 5)} + onClick={() => this.zoomStage(false, 5)} style={{ borderRadius: '3px' }} /> </ButtonGroup> @@ -385,6 +425,14 @@ class SideContentCseMachineBase extends React.Component<CseMachineProps, State> ); } + private zoomStage = (isZoomIn: boolean, multiplier: number) => { + if (this.isJava()) { + JavaCseMachine.zoomStage(isZoomIn, multiplier); + } else { + Layout.zoomStage(false, 5); + } + } + private sliderRelease = (newValue: number) => { if (newValue === this.props.stepsTotal) { this.setState({ lastStep: true }); @@ -509,7 +557,8 @@ const mapStateToProps: MapStateToProps<StateProps, OwnProps, OverallState> = ( breakpointSteps: workspace.breakpointSteps, changepointSteps: workspace.changepointSteps, needCseUpdate: workspace.updateCse, - machineOutput: workspace.output + machineOutput: workspace.output, + chapter: workspace.context.chapter, }; }; diff --git a/src/commons/utils/JavaHelper.ts b/src/commons/utils/JavaHelper.ts index 21ddc66f51..0d4d0ade5f 100644 --- a/src/commons/utils/JavaHelper.ts +++ b/src/commons/utils/JavaHelper.ts @@ -1,12 +1,17 @@ +import { SourceError as JavaSourceError } from 'java-slang/dist/ec-evaluator/errors'; +import { runECEvaluator } from 'java-slang/dist/ec-evaluator/index'; +import { Context as JavaContext } from 'java-slang/dist/ec-evaluator/types'; import setupJVM, { parseBin } from 'java-slang/dist/jvm'; import { createModuleProxy, loadCachedFiles } from 'java-slang/dist/jvm/utils/integration'; import { Context } from 'js-slang'; import loadSourceModules from 'js-slang/dist/modules/loader'; +import { ErrorSeverity, ErrorType, Result, SourceError } from 'js-slang/dist/types'; +import { CseMachine } from '../../features/cseMachine/java/CseMachine'; import Constants from './Constants'; import DisplayBufferService from './DisplayBufferService'; -export async function javaRun(javaCode: string, context: Context) { +export async function javaRun(javaCode: string, context: Context, targetStep: number, isUsingCse: boolean) { let compiled = {}; let files = {}; @@ -86,6 +91,8 @@ export async function javaRun(javaCode: string, context: Context) { }); }; + if (isUsingCse) return await runJavaCseMachine(javaCode, targetStep, context); + // FIXME: Remove when the compiler is working try { const json = JSON.parse(javaCode); @@ -136,3 +143,47 @@ export async function javaRun(javaCode: string, context: Context) { return { status: 'error' }; }); } + +export function visualizeJavaCseMachine({ context }: { context: JavaContext }) { + try { + CseMachine.drawCse(context); + } catch (err) { + throw new Error('Java CSE machine is not enabled'); + } +} + +export async function runJavaCseMachine( + code: string, + targetStep: number, + context: Context +) { + const convertJavaErrorToJsError = (e: JavaSourceError): SourceError => ({ + type: ErrorType.RUNTIME, + severity: ErrorSeverity.ERROR, + location: { + start: { + line: 0, + column: 0, + }, + end: { + line: 0, + column: 0, + }, + }, + explain: () => e.explain(), + elaborate: () => e.explain(), + }); + context.executionMethod = 'cse-machine'; + return runECEvaluator(code, targetStep) + .then(result => { + context.runtime.envStepsTotal = result.context.totalSteps; + if (result.status === 'error') { + context.errors = result.context.errors.map(e => convertJavaErrorToJsError(e)); + } + return result; + }) + .catch(e => { + console.error(e); + return { status: 'error' } as Result; + }); +} From f93bf8e9b878d55d5e76de6921f98e62ac6deac2 Mon Sep 17 00:00:00 2001 From: joel chan <joelchanzhiyang@gmail.com> Date: Fri, 12 Apr 2024 08:51:08 +0000 Subject: [PATCH 04/12] Linting errors for java-slang-csec --- .../controlBar/ControlBarChapterSelect.tsx | 2 +- .../sagas/WorkspaceSaga/helpers/evalCode.ts | 4 +- .../WorkspaceSaga/helpers/updateInspector.ts | 2 +- .../content/SideContentCseMachine.tsx | 204 ++++++++++-------- src/commons/utils/JavaHelper.ts | 21 +- src/features/cseMachine/java/CseMachine.tsx | 21 +- .../cseMachine/java/components/Arrow.tsx | 13 +- .../cseMachine/java/components/Binding.tsx | 36 ++-- .../cseMachine/java/components/Control.tsx | 103 +++++---- .../java/components/ControlItem.tsx | 25 +-- .../java/components/Environment.tsx | 123 ++++++----- .../cseMachine/java/components/Frame.tsx | 37 ++-- .../cseMachine/java/components/Line.tsx | 16 +- .../cseMachine/java/components/Method.tsx | 6 +- .../cseMachine/java/components/Object.tsx | 13 +- .../cseMachine/java/components/Stash.tsx | 46 ++-- .../cseMachine/java/components/StashItem.tsx | 33 ++- .../cseMachine/java/components/Text.tsx | 17 +- .../cseMachine/java/components/Variable.tsx | 24 +-- 19 files changed, 383 insertions(+), 363 deletions(-) diff --git a/src/commons/controlBar/ControlBarChapterSelect.tsx b/src/commons/controlBar/ControlBarChapterSelect.tsx index 47ba376344..11759c56ca 100644 --- a/src/commons/controlBar/ControlBarChapterSelect.tsx +++ b/src/commons/controlBar/ControlBarChapterSelect.tsx @@ -89,7 +89,7 @@ export const ControlBarChapterSelect: React.FC<ControlBarChapterSelectProps> = ( ...(Constants.playgroundOnly ? [fullJSLanguage, fullTSLanguage, htmlLanguage] : []), ...schemeLanguages, ...pyLanguages, - ...javaLanguages, + ...javaLanguages ]; return ( diff --git a/src/commons/sagas/WorkspaceSaga/helpers/evalCode.ts b/src/commons/sagas/WorkspaceSaga/helpers/evalCode.ts index 3f4f6e208c..0dae58611d 100644 --- a/src/commons/sagas/WorkspaceSaga/helpers/evalCode.ts +++ b/src/commons/sagas/WorkspaceSaga/helpers/evalCode.ts @@ -250,9 +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 - ); + const isUsingCse = yield select((state: OverallState) => state.workspaces['playground'].usingCse); // Handles `console.log` statements in fullJS const detachConsole: () => void = diff --git a/src/commons/sagas/WorkspaceSaga/helpers/updateInspector.ts b/src/commons/sagas/WorkspaceSaga/helpers/updateInspector.ts index f109aa76e9..788e813ebf 100644 --- a/src/commons/sagas/WorkspaceSaga/helpers/updateInspector.ts +++ b/src/commons/sagas/WorkspaceSaga/helpers/updateInspector.ts @@ -12,7 +12,7 @@ export function* updateInspector(workspaceLocation: WorkspaceLocation): SagaIter try { const [lastDebuggerResult, chapter] = yield select((state: OverallState) => [ state.workspaces[workspaceLocation].lastDebuggerResult, - state.workspaces[workspaceLocation].context.chapter, + state.workspaces[workspaceLocation].context.chapter ]); if (chapter === Chapter.FULL_JAVA) { const controlItem = lastDebuggerResult.context.control.peek(); diff --git a/src/commons/sideContent/content/SideContentCseMachine.tsx b/src/commons/sideContent/content/SideContentCseMachine.tsx index e77c96a220..355f3c0cd5 100644 --- a/src/commons/sideContent/content/SideContentCseMachine.tsx +++ b/src/commons/sideContent/content/SideContentCseMachine.tsx @@ -90,7 +90,7 @@ class SideContentCseMachineBase extends React.Component<CseMachineProps, State> height: this.calculateHeight(props.sideContentHeight), lastStep: false, stepLimitExceeded: false, - chapter: props.chapter, + chapter: props.chapter }; if (this.isJava()) { JavaCseMachine.init( @@ -232,45 +232,53 @@ class SideContentCseMachineBase extends React.Component<CseMachineProps, State> onRelease={this.sliderRelease} value={this.state.value < 0 ? 0 : this.state.value} /> - <div style={{ display: 'flex', justifyContent: this.isJava() ? 'center' : 'space-between', alignItems: 'center' }}> - {!this.isJava() && <ButtonGroup> - <Tooltip2 content="Control and Stash" compact> - <AnchorButton - onMouseUp={() => { - if (this.state.visualization) { - CseMachine.toggleControlStash(); - CseMachine.redraw(); - } - }} - icon="layers" - disabled={!this.state.visualization} - > - <Checkbox - checked={CseMachine.getControlStash()} + <div + style={{ + display: 'flex', + justifyContent: this.isJava() ? 'center' : 'space-between', + alignItems: 'center' + }} + > + {!this.isJava() && ( + <ButtonGroup> + <Tooltip2 content="Control and Stash" compact> + <AnchorButton + onMouseUp={() => { + if (this.state.visualization) { + CseMachine.toggleControlStash(); + CseMachine.redraw(); + } + }} + icon="layers" disabled={!this.state.visualization} - style={{ margin: 0 }} - /> - </AnchorButton> - </Tooltip2> - <Tooltip2 content="Truncate Control" compact> - <AnchorButton - onMouseUp={() => { - if (this.state.visualization) { - CseMachine.toggleStackTruncated(); - CseMachine.redraw(); - } - }} - icon="minimize" - disabled={!this.state.visualization} - > - <Checkbox - checked={CseMachine.getStackTruncated()} + > + <Checkbox + checked={CseMachine.getControlStash()} + disabled={!this.state.visualization} + style={{ margin: 0 }} + /> + </AnchorButton> + </Tooltip2> + <Tooltip2 content="Truncate Control" compact> + <AnchorButton + onMouseUp={() => { + if (this.state.visualization) { + CseMachine.toggleStackTruncated(); + CseMachine.redraw(); + } + }} + icon="minimize" disabled={!this.state.visualization} - style={{ margin: 0 }} - /> - </AnchorButton> - </Tooltip2> - </ButtonGroup>} + > + <Checkbox + checked={CseMachine.getStackTruncated()} + disabled={!this.state.visualization} + style={{ margin: 0 }} + /> + </AnchorButton> + </Tooltip2> + </ButtonGroup> + )} <ButtonGroup> <Button disabled={!this.state.visualization} @@ -281,13 +289,19 @@ class SideContentCseMachineBase extends React.Component<CseMachineProps, State> disabled={!this.state.visualization} icon="chevron-left" onClick={ - this.isJava() || CseMachine.getControlStash() ? this.stepPrevious : this.stepPrevChangepoint + this.isJava() || CseMachine.getControlStash() + ? this.stepPrevious + : this.stepPrevChangepoint } /> <Button disabled={!this.state.visualization} icon="chevron-right" - onClick={this.isJava() || CseMachine.getControlStash() ? this.stepNext : this.stepNextChangepoint} + onClick={ + this.isJava() || CseMachine.getControlStash() + ? this.stepNext + : this.stepNextChangepoint + } /> <Button disabled={!this.state.visualization} @@ -295,33 +309,35 @@ class SideContentCseMachineBase extends React.Component<CseMachineProps, State> onClick={this.stepNextBreakpoint} /> </ButtonGroup> - {!this.isJava() && <ButtonGroup> - <Tooltip2 content="Print" compact> - <AnchorButton - onMouseUp={() => { - if (this.state.visualization) { - CseMachine.togglePrintableMode(); - CseMachine.redraw(); - } - }} - icon="print" - disabled={!this.state.visualization} - > - <Checkbox + {!this.isJava() && ( + <ButtonGroup> + <Tooltip2 content="Print" compact> + <AnchorButton + onMouseUp={() => { + if (this.state.visualization) { + CseMachine.togglePrintableMode(); + CseMachine.redraw(); + } + }} + icon="print" + disabled={!this.state.visualization} + > + <Checkbox + disabled={!this.state.visualization} + checked={CseMachine.getPrintableMode()} + style={{ margin: 0 }} + /> + </AnchorButton> + </Tooltip2> + <Tooltip2 content="Save" compact> + <AnchorButton + icon="floppy-disk" disabled={!this.state.visualization} - checked={CseMachine.getPrintableMode()} - style={{ margin: 0 }} + onClick={Layout.exportImage} /> - </AnchorButton> - </Tooltip2> - <Tooltip2 content="Save" compact> - <AnchorButton - icon="floppy-disk" - disabled={!this.state.visualization} - onClick={Layout.exportImage} - /> - </Tooltip2> - </ButtonGroup>} + </Tooltip2> + </ButtonGroup> + )} </div> </div>{' '} {this.state.visualization && @@ -353,32 +369,34 @@ class SideContentCseMachineBase extends React.Component<CseMachineProps, State> className={Classes.RUNNING_TEXT} data-testid="cse-machine-default-text" > - {this.isJava() - ? <span> - The CSEC machine generates control, stash, environment and class model diagrams adapted from the - notation introduced in{' '} - <a href={Links.textbookChapter3_2} rel="noopener noreferrer" target="_blank"> - <i> - Structure and Interpretation of Computer Programs, JavaScript Edition, Chapter 3, - Section 2 - </i> - </a>{'. '} - - You have chosen the sublanguage{' '} - <a href={`${Links.sourceDocs}java_csec/`} rel="noopener noreferrer" target="_blank"> - <i>Java CSEC</i> - </a> - </span> - : <span> - The CSE machine generates control, stash and environment model diagrams following a - notation introduced in{' '} - <a href={Links.textbookChapter3_2} rel="noopener noreferrer" target="_blank"> - <i> - Structure and Interpretation of Computer Programs, JavaScript Edition, Chapter 3, - Section 2 - </i> - </a> - </span>} + {this.isJava() ? ( + <span> + The CSEC machine generates control, stash, environment and class model diagrams + adapted from the notation introduced in{' '} + <a href={Links.textbookChapter3_2} rel="noopener noreferrer" target="_blank"> + <i> + Structure and Interpretation of Computer Programs, JavaScript Edition, Chapter + 3, Section 2 + </i> + </a> + {'. '} + You have chosen the sublanguage{' '} + <a href={`${Links.sourceDocs}java_csec/`} rel="noopener noreferrer" target="_blank"> + <i>Java CSEC</i> + </a> + </span> + ) : ( + <span> + The CSE machine generates control, stash and environment model diagrams following a + notation introduced in{' '} + <a href={Links.textbookChapter3_2} rel="noopener noreferrer" target="_blank"> + <i> + Structure and Interpretation of Computer Programs, JavaScript Edition, Chapter + 3, Section 2 + </i> + </a> + </span> + )} . <br /> <br /> On this tab, the REPL will be hidden from view, so do check that your code has no @@ -431,7 +449,7 @@ class SideContentCseMachineBase extends React.Component<CseMachineProps, State> } else { Layout.zoomStage(false, 5); } - } + }; private sliderRelease = (newValue: number) => { if (newValue === this.props.stepsTotal) { @@ -558,7 +576,7 @@ const mapStateToProps: MapStateToProps<StateProps, OwnProps, OverallState> = ( changepointSteps: workspace.changepointSteps, needCseUpdate: workspace.updateCse, machineOutput: workspace.output, - chapter: workspace.context.chapter, + chapter: workspace.context.chapter }; }; diff --git a/src/commons/utils/JavaHelper.ts b/src/commons/utils/JavaHelper.ts index 0d4d0ade5f..aa90646e61 100644 --- a/src/commons/utils/JavaHelper.ts +++ b/src/commons/utils/JavaHelper.ts @@ -11,7 +11,12 @@ import { CseMachine } from '../../features/cseMachine/java/CseMachine'; import Constants from './Constants'; import DisplayBufferService from './DisplayBufferService'; -export async function javaRun(javaCode: string, context: Context, targetStep: number, isUsingCse: boolean) { +export async function javaRun( + javaCode: string, + context: Context, + targetStep: number, + isUsingCse: boolean +) { let compiled = {}; let files = {}; @@ -152,26 +157,22 @@ export function visualizeJavaCseMachine({ context }: { context: JavaContext }) { } } -export async function runJavaCseMachine( - code: string, - targetStep: number, - context: Context -) { +export async function runJavaCseMachine(code: string, targetStep: number, context: Context) { const convertJavaErrorToJsError = (e: JavaSourceError): SourceError => ({ type: ErrorType.RUNTIME, severity: ErrorSeverity.ERROR, location: { start: { line: 0, - column: 0, + column: 0 }, end: { line: 0, - column: 0, - }, + column: 0 + } }, explain: () => e.explain(), - elaborate: () => e.explain(), + elaborate: () => e.explain() }); context.executionMethod = 'cse-machine'; return runECEvaluator(code, targetStep) diff --git a/src/features/cseMachine/java/CseMachine.tsx b/src/features/cseMachine/java/CseMachine.tsx index 9893220420..bd2660867c 100644 --- a/src/features/cseMachine/java/CseMachine.tsx +++ b/src/features/cseMachine/java/CseMachine.tsx @@ -1,12 +1,12 @@ -import { Context } from "java-slang/dist/ec-evaluator/types"; -import { KonvaEventObject } from "konva/lib/Node"; -import React, { RefObject } from "react"; -import { Layer, Rect, Stage } from "react-konva"; +import { Context } from 'java-slang/dist/ec-evaluator/types'; +import { KonvaEventObject } from 'konva/lib/Node'; +import React, { RefObject } from 'react'; +import { Layer, Rect, Stage } from 'react-konva'; -import { Config, ShapeDefaultProps } from "./../CseMachineConfig"; -import { Control } from "./components/Control"; -import { Environment } from "./components/Environment"; -import { Stash } from "./components/Stash"; +import { Config, ShapeDefaultProps } from './../CseMachineConfig'; +import { Control } from './components/Control'; +import { Environment } from './components/Environment'; +import { Stash } from './components/Stash'; type SetVis = (vis: React.ReactNode) => void; type SetEditorHighlightedLines = (segments: [number, number][]) => void; @@ -28,10 +28,7 @@ export class CseMachine { static control: Control | undefined; static stash: Stash | undefined; - static init( - setVis: SetVis, - setEditorHighlightedLines: (segments: [number, number][]) => void, - ) { + static init(setVis: SetVis, setEditorHighlightedLines: (segments: [number, number][]) => void) { this.setVis = setVis; this.setEditorHighlightedLines = setEditorHighlightedLines; } diff --git a/src/features/cseMachine/java/components/Arrow.tsx b/src/features/cseMachine/java/components/Arrow.tsx index 51de860418..ddbb8dfba7 100644 --- a/src/features/cseMachine/java/components/Arrow.tsx +++ b/src/features/cseMachine/java/components/Arrow.tsx @@ -8,7 +8,7 @@ import { setHoveredCursor, setHoveredStyle, setUnhoveredCursor, - setUnhoveredStyle, + setUnhoveredStyle } from '../../CseMachineUtils'; import { CseMachine } from '../CseMachine'; @@ -17,12 +17,7 @@ export class Arrow extends Visible implements IHoverable { private static readonly TO_X_INDEX = 2; private readonly _points: number[] = []; - constructor( - fromX: number, - fromY: number, - toX: number, - toY: number, - ) { + constructor(fromX: number, fromY: number, toX: number, toY: number) { super(); this._points.push(fromX, fromY, toX, toY); } @@ -30,9 +25,9 @@ export class Arrow extends Visible implements IHoverable { setToX(x: number) { this._points[Arrow.TO_X_INDEX] = x; } - + onMouseEnter(e: KonvaEventObject<MouseEvent>) { - setHoveredStyle(e.currentTarget) + setHoveredStyle(e.currentTarget); setHoveredCursor(e.currentTarget); } diff --git a/src/features/cseMachine/java/components/Binding.tsx b/src/features/cseMachine/java/components/Binding.tsx index 2a20032c9e..6893d82ed2 100644 --- a/src/features/cseMachine/java/components/Binding.tsx +++ b/src/features/cseMachine/java/components/Binding.tsx @@ -12,17 +12,12 @@ import { Variable } from './Variable'; /** a Binding is a key-value pair in a Frame */ export class Binding extends Visible { private readonly _name: Text; - + private readonly _value: Variable | Method | Text; // Only Method has arrow. private readonly _arrow: Arrow | undefined; - constructor( - name: Name, - value: Value, - x: number, - y: number - ) { + constructor(name: Name, value: Value, x: number, y: number) { super(); // Position. @@ -34,40 +29,43 @@ export class Binding extends Visible { this._name = new Text( name + Config.VariableColon, // := is part of name this.x(), - this.y()); + this.y() + ); // Value. this._value = new Method( this._name.x() + this._name.width(), this.y() + this._name.height() / 2, - value); + value + ); this._arrow = new Arrow( this._name.x() + this._name.width(), this._name.y() + this._name.height() / 2, this._value.x(), - this._value.y()); + this._value.y() + ); } else if (value.kind === StructType.VARIABLE) { // Name. this._name = new Text( name + Config.VariableColon, // := is part of name this.x(), - this.y() + Config.FontSize + Config.TextPaddingX); + this.y() + Config.FontSize + Config.TextPaddingX + ); // Value. - this._value = new Variable( - this._name.x() + this._name.width(), - this.y(), - value); - } else /*if (value.kind === StructType.CLASS)*/ { + this._value = new Variable(this._name.x() + this._name.width(), this.y(), value); + } /*if (value.kind === StructType.CLASS)*/ else { // Dummy value as class will nvr be drawn. // Name. this._name = new Text( name + Config.VariableColon, // := is part of name this.x(), - this.y() + Config.FontSize + Config.TextPaddingX); + this.y() + Config.FontSize + Config.TextPaddingX + ); // Value. this._value = new Text( - "", + '', this._name.x() + this._name.width() + Config.TextPaddingX, - this.y() + Config.TextPaddingX); + this.y() + Config.TextPaddingX + ); } // Height and width. diff --git a/src/features/cseMachine/java/components/Control.tsx b/src/features/cseMachine/java/components/Control.tsx index 7d2a7706bc..cd6550fb2c 100644 --- a/src/features/cseMachine/java/components/Control.tsx +++ b/src/features/cseMachine/java/components/Control.tsx @@ -1,5 +1,5 @@ -import { astToString } from "java-slang/dist/ast/utils/astToString"; -import { Control as JavaControl } from "java-slang/dist/ec-evaluator/components"; +import { astToString } from 'java-slang/dist/ast/utils/astToString'; +import { Control as JavaControl } from 'java-slang/dist/ec-evaluator/components'; import { BinOpInstr, ControlItem as JavaControlItem, @@ -12,16 +12,16 @@ import { ResInstr, ResOverloadInstr, ResTypeContInstr, - ResTypeInstr, -} from "java-slang/dist/ec-evaluator/types"; -import { isInstr, isNode } from "java-slang/dist/ec-evaluator/utils"; -import { Group } from "react-konva"; + ResTypeInstr +} from 'java-slang/dist/ec-evaluator/types'; +import { isInstr, isNode } from 'java-slang/dist/ec-evaluator/utils'; +import { Group } from 'react-konva'; -import { Visible } from "../../components/Visible"; -import { Config } from "../../CseMachineConfig"; -import { ControlStashConfig } from "../../CseMachineControlStashConfig"; -import { CseMachine } from "../CseMachine"; -import { ControlItem } from "./ControlItem"; +import { Visible } from '../../components/Visible'; +import { Config } from '../../CseMachineConfig'; +import { ControlStashConfig } from '../../CseMachineControlStashConfig'; +import { CseMachine } from '../CseMachine'; +import { ControlItem } from './ControlItem'; export class Control extends Visible { private readonly _controlItems: ControlItem[] = []; @@ -31,27 +31,30 @@ export class Control extends Visible { // Position. this._x = ControlStashConfig.ControlPosX; - this._y = ControlStashConfig.ControlPosY + ControlStashConfig.StashItemHeight + ControlStashConfig.StashItemTextPadding * 2; + this._y = + ControlStashConfig.ControlPosY + + ControlStashConfig.StashItemHeight + + ControlStashConfig.StashItemTextPadding * 2; // Create each ControlItem. let controlItemY: number = this._y; control.getStack().forEach((controlItem, index) => { const controlItemText = this.getControlItemString(controlItem); - + const controlItemStroke = - index === control.getStack().length - 1 - ? Config.SA_CURRENT_ITEM - : ControlStashConfig.SA_WHITE; - + index === control.getStack().length - 1 + ? Config.SA_CURRENT_ITEM + : ControlStashConfig.SA_WHITE; + // TODO reference draw ltr? const controlItemReference = isInstr(controlItem) && controlItem.instrType === InstrType.ENV - ? CseMachine.environment?.frames.find(f => f.frame === (controlItem as EnvInstr).env) - : undefined; + ? CseMachine.environment?.frames.find(f => f.frame === (controlItem as EnvInstr).env) + : undefined; const controlItemTooltip = this.getControlItemTooltip(controlItem); this.getControlItemTooltip(controlItem); - + const node = isNode(controlItem) ? controlItem : controlItem.srcNode; const highlightOnHover = () => { let start = -1; @@ -71,7 +74,7 @@ export class Control extends Visible { controlItemReference, controlItemTooltip, highlightOnHover, - unhighlightOnHover, + unhighlightOnHover ); this._controlItems.push(currControlItem); @@ -99,21 +102,21 @@ export class Control extends Visible { switch (controlItem.instrType) { case InstrType.RESET: - return "return"; + return 'return'; case InstrType.ASSIGNMENT: - return "asgn"; + return 'asgn'; case InstrType.BINARY_OP: const binOpInstr = controlItem as BinOpInstr; return binOpInstr.symbol; case InstrType.POP: - return "pop"; + return 'pop'; case InstrType.INVOCATION: const appInstr = controlItem as InvInstr; return `invoke ${appInstr.arity}`; case InstrType.ENV: - return "env"; + return 'env'; case InstrType.MARKER: - return "mark"; + return 'mark'; case InstrType.EVAL_VAR: const evalVarInstr = controlItem as EvalVarInstr; return `name ${evalVarInstr.symbol}`; @@ -122,9 +125,11 @@ export class Control extends Visible { return `new ${newInstr.c.frame.name}`; case InstrType.RES_TYPE: const resTypeInstr = controlItem as ResTypeInstr; - return `res_type ${resTypeInstr.value.kind === "Class" - ? resTypeInstr.value.frame.name : - astToString(resTypeInstr.value)}`; + return `res_type ${ + resTypeInstr.value.kind === 'Class' + ? resTypeInstr.value.frame.name + : astToString(resTypeInstr.value) + }`; case InstrType.RES_TYPE_CONT: const resTypeContInstr = controlItem as ResTypeContInstr; return `res_type_cont ${resTypeContInstr.name}`; @@ -140,11 +145,11 @@ export class Control extends Visible { const resInstr = controlItem as ResInstr; return `res ${resInstr.name}`; case InstrType.DEREF: - return "deref"; + return 'deref'; default: - return "INSTRUCTION"; + return 'INSTRUCTION'; } - } + }; private getControlItemTooltip = (controlItem: JavaControlItem): string => { if (isNode(controlItem)) { @@ -153,21 +158,21 @@ export class Control extends Visible { switch (controlItem.instrType) { case InstrType.RESET: - return "Skip control items until marker instruction is reached"; + return 'Skip control items until marker instruction is reached'; case InstrType.ASSIGNMENT: - return "Assign value on top of stash to location on top of stash"; + return 'Assign value on top of stash to location on top of stash'; case InstrType.BINARY_OP: const binOpInstr = controlItem as BinOpInstr; return `Perform ${binOpInstr.symbol} on top 2 stash values`; case InstrType.POP: - return "Pop most recently pushed value from stash"; + return 'Pop most recently pushed value from stash'; case InstrType.INVOCATION: const appInstr = controlItem as InvInstr; return `Invoke method with ${appInstr.arity} argument${appInstr.arity === 1 ? '' : 's'}`; case InstrType.ENV: - return "Set current environment to this environment"; + return 'Set current environment to this environment'; case InstrType.MARKER: - return "Mark return address"; + return 'Mark return address'; case InstrType.EVAL_VAR: const evalVarInstr = controlItem as EvalVarInstr; return `name ${evalVarInstr.symbol}`; @@ -176,27 +181,33 @@ export class Control extends Visible { return `Create new instance of class ${newInstr.c.frame.name}`; case InstrType.RES_TYPE: const resTypeInstr = controlItem as ResTypeInstr; - return `Resolve type of ${resTypeInstr.value.kind === "Class" - ? resTypeInstr.value.frame.name : - astToString(resTypeInstr.value)}`; + return `Resolve type of ${ + resTypeInstr.value.kind === 'Class' + ? resTypeInstr.value.frame.name + : astToString(resTypeInstr.value) + }`; case InstrType.RES_TYPE_CONT: const resTypeContInstr = controlItem as ResTypeContInstr; return `Resolve type of ${resTypeContInstr.name} in most recently pushed type from stash`; case InstrType.RES_OVERLOAD: const resOverloadInstr = controlItem as ResOverloadInstr; - return `Resolve overloading of method ${resOverloadInstr.name} with ${resOverloadInstr.arity} argument${resOverloadInstr.arity === 1 ? '' : 's'}`; + return `Resolve overloading of method ${resOverloadInstr.name} with ${ + resOverloadInstr.arity + } argument${resOverloadInstr.arity === 1 ? '' : 's'}`; case InstrType.RES_OVERRIDE: - return "Resolve overriding of resolved method on top of stash"; + return 'Resolve overriding of resolved method on top of stash'; case InstrType.RES_CON_OVERLOAD: const resConOverloadInstr = controlItem as ResConOverloadInstr; - return `Resolve constructor overloading of class on stash with ${resConOverloadInstr.arity} argument${resConOverloadInstr.arity === 1 ? '' : 's'}`; + return `Resolve constructor overloading of class on stash with ${ + resConOverloadInstr.arity + } argument${resConOverloadInstr.arity === 1 ? '' : 's'}`; case InstrType.RES: const resInstr = controlItem as ResInstr; return `Resolve field ${resInstr.name} of most recently pushed value from stash`; case InstrType.DEREF: - return "Dereference most recently pushed value from stash"; + return 'Dereference most recently pushed value from stash'; default: - return "INSTRUCTION"; + return 'INSTRUCTION'; } - } + }; } diff --git a/src/features/cseMachine/java/components/ControlItem.tsx b/src/features/cseMachine/java/components/ControlItem.tsx index d50dba4d29..55fa748887 100644 --- a/src/features/cseMachine/java/components/ControlItem.tsx +++ b/src/features/cseMachine/java/components/ControlItem.tsx @@ -12,7 +12,7 @@ import { setHoveredStyle, setUnhoveredCursor, setUnhoveredStyle, - truncateText, + truncateText } from '../../CseMachineUtils'; import { CseMachine } from '../CseMachine'; import { Arrow } from './Arrow'; @@ -32,14 +32,14 @@ export class ControlItem extends Visible implements IHoverable { private readonly _tooltip: string, private readonly highlightOnHover: () => void, - private readonly unhighlightOnHover: () => void, + private readonly unhighlightOnHover: () => void ) { super(); // Position. this._x = ControlStashConfig.ControlPosX; this._y = y; - + // Text. this._text = truncateText( this._text, @@ -51,8 +51,9 @@ export class ControlItem extends Visible implements IHoverable { this._tooltipRef = React.createRef(); // Height and width. - this._height = getTextHeight(this._text, ControlStashConfig.ControlMaxTextWidth) - + ControlStashConfig.ControlItemTextPadding * 2; + this._height = + getTextHeight(this._text, ControlStashConfig.ControlMaxTextWidth) + + ControlStashConfig.ControlItemTextPadding * 2; this._width = ControlStashConfig.ControlItemWidth; // Arrow @@ -61,14 +62,14 @@ export class ControlItem extends Visible implements IHoverable { this._x + this._width, this._y + this._height / 2, reference.x(), - reference.y() + reference.height() / 2 + reference.name.height(), + reference.y() + reference.height() / 2 + reference.name.height() ); } } private isCurrentItem = (): boolean => { return this._stroke === Config.SA_CURRENT_ITEM; - } + }; onMouseEnter = (e: KonvaEventObject<MouseEvent>): void => { this.highlightOnHover(); @@ -91,11 +92,11 @@ export class ControlItem extends Visible implements IHoverable { fontFamily: ControlStashConfig.FontFamily, fontSize: ControlStashConfig.FontSize, fontStyle: ControlStashConfig.FontStyle, - fontVariant: ControlStashConfig.FontVariant, + fontVariant: ControlStashConfig.FontVariant }; const tagProps = { stroke: this._stroke, - cornerRadius: ControlStashConfig.ControlItemCornerRadius, + cornerRadius: ControlStashConfig.ControlItemCornerRadius }; return ( <React.Fragment key={CseMachine.key++}> @@ -107,11 +108,7 @@ export class ControlItem extends Visible implements IHoverable { onMouseLeave={this.onMouseLeave} key={CseMachine.key++} > - <Tag - {...ShapeDefaultProps} - {...tagProps} - key={CseMachine.key++} - /> + <Tag {...ShapeDefaultProps} {...tagProps} key={CseMachine.key++} /> <Text {...ShapeDefaultProps} {...textProps} diff --git a/src/features/cseMachine/java/components/Environment.tsx b/src/features/cseMachine/java/components/Environment.tsx index 0e456c170d..f1da220ea5 100644 --- a/src/features/cseMachine/java/components/Environment.tsx +++ b/src/features/cseMachine/java/components/Environment.tsx @@ -1,16 +1,16 @@ -import { Environment as JavaEnvironment, EnvNode } from "java-slang/dist/ec-evaluator/components"; -import { Class as JavaClass, StructType } from "java-slang/dist/ec-evaluator/types"; -import { Group } from "react-konva"; - -import { Visible } from "../../components/Visible"; -import { Config } from "../../CseMachineConfig"; -import { ControlStashConfig } from "../../CseMachineControlStashConfig"; -import { CseMachine } from "../CseMachine"; -import { Arrow } from "./Arrow"; -import { Frame } from "./Frame"; -import { Line } from "./Line"; -import { Object } from "./Object"; -import { Variable } from "./Variable"; +import { Environment as JavaEnvironment, EnvNode } from 'java-slang/dist/ec-evaluator/components'; +import { Class as JavaClass, StructType } from 'java-slang/dist/ec-evaluator/types'; +import { Group } from 'react-konva'; + +import { Visible } from '../../components/Visible'; +import { Config } from '../../CseMachineConfig'; +import { ControlStashConfig } from '../../CseMachineControlStashConfig'; +import { CseMachine } from '../CseMachine'; +import { Arrow } from './Arrow'; +import { Frame } from './Frame'; +import { Line } from './Line'; +import { Object } from './Object'; +import { Variable } from './Variable'; export class Environment extends Visible { private readonly _methodFrames: Frame[] = []; @@ -22,25 +22,29 @@ export class Environment extends Visible { super(); // Position. - this._x = ControlStashConfig.ControlPosX + ControlStashConfig.ControlItemWidth + 2 * Config.CanvasPaddingX; - this._y = ControlStashConfig.StashPosY + ControlStashConfig.StashItemHeight + 2 * Config.CanvasPaddingY; + this._x = + ControlStashConfig.ControlPosX + + ControlStashConfig.ControlItemWidth + + 2 * Config.CanvasPaddingX; + this._y = + ControlStashConfig.StashPosY + ControlStashConfig.StashItemHeight + 2 * Config.CanvasPaddingY; // Create method frames. const methodFramesX = this._x; let methodFramesY: number = this._y; let methodFramesWidth = Number(Config.FrameMinWidth); environment.global.children.forEach(env => { - if (env.name.includes("(")) { + if (env.name.includes('(')) { let currEnv: EnvNode | undefined = env; let parentFrame; while (currEnv) { const stroke = currEnv === environment.current ? Config.SA_CURRENT_ITEM : Config.SA_WHITE; const frame = new Frame(currEnv, methodFramesX, methodFramesY, stroke); this._methodFrames.push(frame); - methodFramesY += (frame.height() + Config.FramePaddingY); + methodFramesY += frame.height() + Config.FramePaddingY; methodFramesWidth = Math.max(methodFramesWidth, frame.width()); parentFrame && frame.setParent(parentFrame); - + parentFrame = frame; currEnv = currEnv.children.length ? currEnv.children[0] : undefined; } @@ -76,10 +80,10 @@ export class Environment extends Visible { } // Standardize obj frames width. - objectFrames.forEach(o => o.setWidth(objectFrameWidth)) + objectFrames.forEach(o => o.setWidth(objectFrameWidth)); // Only add padding btwn objects. - objectFramesY += Config.FramePaddingY + objectFramesY += Config.FramePaddingY; this._objects.push(new Object(objectFrames, obj)); }); @@ -89,7 +93,8 @@ export class Environment extends Visible { let classFramesY = this._y; for (const [_, c] of environment.global.frame.entries()) { const classEnv = (c as JavaClass).frame; - const classFrameStroke = classEnv === environment.current ? Config.SA_CURRENT_ITEM : Config.SA_WHITE; + const classFrameStroke = + classEnv === environment.current ? Config.SA_CURRENT_ITEM : Config.SA_WHITE; const highlightOnHover = () => { const node = (c as JavaClass).classDecl; let start = -1; @@ -101,14 +106,22 @@ export class Environment extends Visible { CseMachine.setEditorHighlightedLines([[start, end]]); }; const unhighlightOnHover = () => CseMachine.setEditorHighlightedLines([]); - const classFrame = new Frame(classEnv, classFramesX, classFramesY, classFrameStroke, "", highlightOnHover, unhighlightOnHover); + const classFrame = new Frame( + classEnv, + classFramesX, + classFramesY, + classFrameStroke, + '', + highlightOnHover, + unhighlightOnHover + ); const superClassName = (c as JavaClass).superclass?.frame.name; if (superClassName) { const parentFrame = this._classFrames.find(f => f.name.text === superClassName)!; - classFrame.setParent(parentFrame) + classFrame.setParent(parentFrame); } this._classFrames.push(classFrame); - classFramesY += (classFrame.height() + Config.FramePaddingY); + classFramesY += classFrame.height() + Config.FramePaddingY; } // Draw arrow for var ref in mtd frames to corresponding obj. @@ -124,42 +137,52 @@ export class Environment extends Visible { matchingObj.y() + matchingObj.height() / 2 ); } - }) + }); }); // Draw arrow for var ref in obj frames to corresponding var or obj. - this._objects.flatMap(obj => obj.frames).forEach(of => { - of.bindings.forEach(b => { - if (b.value instanceof Variable && b.value.variable.value.kind === StructType.VARIABLE) { - const variable = b.value.variable.value; - const matchingVariable = this._classFrames.flatMap(c => c.bindings).filter(b => b.value instanceof Variable && b.value.variable === variable)[0].value as Variable; - b.value.value = new Arrow( - b.value.x() + b.value.width() / 2, - b.value.y() + b.value.type.height() + (b.value.height() - b.value.type.height()) / 2, - matchingVariable.x(), - matchingVariable.y() + matchingVariable.type.height()); - } - if (b.value instanceof Variable && b.value.variable.value.kind === StructType.OBJECT) { - const obj = b.value.variable.value.frame; - const matchingObj = this._objects.find(o => o.getFrame().frame === obj)!; - // Variable always has a box. - b.value.value = new Arrow( - b.value.x() + b.value.width() / 2, - b.value.y() + b.value.type.height() + (b.value.height() - b.value.type.height()) / 2, - matchingObj.x(), - matchingObj.y() + matchingObj.height() / 2); - } - }) - }); + this._objects + .flatMap(obj => obj.frames) + .forEach(of => { + of.bindings.forEach(b => { + if (b.value instanceof Variable && b.value.variable.value.kind === StructType.VARIABLE) { + const variable = b.value.variable.value; + const matchingVariable = this._classFrames + .flatMap(c => c.bindings) + .filter(b => b.value instanceof Variable && b.value.variable === variable)[0] + .value as Variable; + b.value.value = new Arrow( + b.value.x() + b.value.width() / 2, + b.value.y() + b.value.type.height() + (b.value.height() - b.value.type.height()) / 2, + matchingVariable.x(), + matchingVariable.y() + matchingVariable.type.height() + ); + } + if (b.value instanceof Variable && b.value.variable.value.kind === StructType.OBJECT) { + const obj = b.value.variable.value.frame; + const matchingObj = this._objects.find(o => o.getFrame().frame === obj)!; + // Variable always has a box. + b.value.value = new Arrow( + b.value.x() + b.value.width() / 2, + b.value.y() + b.value.type.height() + (b.value.height() - b.value.type.height()) / 2, + matchingObj.x(), + matchingObj.y() + matchingObj.height() / 2 + ); + } + }); + }); // Draw line for obj to class. this._objects.forEach(obj => { - const matchingClass = this._classFrames.find(c => c.name.text === obj.object.class.frame.name)!; + const matchingClass = this._classFrames.find( + c => c.name.text === obj.object.class.frame.name + )!; const line = new Line( obj.x() + obj.width(), obj.y() + obj.height() / 2, matchingClass.x(), - matchingClass.y() + matchingClass.height() / 2 + matchingClass.name.height()); + matchingClass.y() + matchingClass.height() / 2 + matchingClass.name.height() + ); this._lines.push(line); }); } diff --git a/src/features/cseMachine/java/components/Frame.tsx b/src/features/cseMachine/java/components/Frame.tsx index 975e07e79e..8465b8b65f 100644 --- a/src/features/cseMachine/java/components/Frame.tsx +++ b/src/features/cseMachine/java/components/Frame.tsx @@ -29,7 +29,7 @@ export class Frame extends Visible implements IHoverable { readonly tooltip?: string, readonly highlightOnHover?: () => void, - readonly unhighlightOnHover?: () => void, + readonly unhighlightOnHover?: () => void ) { super(); @@ -46,16 +46,18 @@ export class Frame extends Visible implements IHoverable { for (const [key, data] of frame.frame) { const currBinding: Binding = new Binding(key, data, this._x + Config.FramePaddingX, bindingY); this.bindings.push(currBinding); - bindingY += (currBinding.height() + Config.FramePaddingY); + bindingY += currBinding.height() + Config.FramePaddingY; this._width = Math.max(this._width, currBinding.width() + 2 * Config.FramePaddingX); - this._height += (currBinding.height() + Config.FramePaddingY); + this._height += currBinding.height() + Config.FramePaddingY; } // Set x of Method aft knowing frame width. - this.bindings.filter(b => b.value instanceof Method).forEach(b => { - (b.value as Method).setX(this._x + this._width + Config.FramePaddingX); - b.setArrowToX(this._x + this._width + Config.FramePaddingX); - }) + this.bindings + .filter(b => b.value instanceof Method) + .forEach(b => { + (b.value as Method).setX(this._x + this._width + Config.FramePaddingX); + b.setArrowToX(this._x + this._width + Config.FramePaddingX); + }); this.tooltipRef = React.createRef(); } @@ -111,16 +113,17 @@ export class Frame extends Visible implements IHoverable { {this.bindings.map(binding => binding.draw())} {/* Frame parent */} - {this.parent && new Arrow( - this._x + Config.FramePaddingX / 2, - this._y + this.name.height(), - this.parent.x() + Config.FramePaddingX / 2, - // TODO WHY NEED TO ADD NAME HEIGHT? - this.parent.y() + this.parent.height() + this.name?.height() - ).draw()} - + {this.parent && + new Arrow( + this._x + Config.FramePaddingX / 2, + this._y + this.name.height(), + this.parent.x() + Config.FramePaddingX / 2, + // TODO WHY NEED TO ADD NAME HEIGHT? + this.parent.y() + this.parent.height() + this.name?.height() + ).draw()} + {/* Frame tooltip */} - {this.tooltip && + {this.tooltip && ( <Label x={this.x() + this.width() + ControlStashConfig.TooltipMargin} y={this.y() + ControlStashConfig.TooltipMargin} @@ -143,7 +146,7 @@ export class Frame extends Visible implements IHoverable { key={CseMachine.key++} /> </Label> - } + )} </Group> ); } diff --git a/src/features/cseMachine/java/components/Line.tsx b/src/features/cseMachine/java/components/Line.tsx index ec462a6846..67323c32b5 100644 --- a/src/features/cseMachine/java/components/Line.tsx +++ b/src/features/cseMachine/java/components/Line.tsx @@ -8,7 +8,7 @@ import { setHoveredCursor, setHoveredStyle, setUnhoveredCursor, - setUnhoveredStyle, + setUnhoveredStyle } from '../../CseMachineUtils'; import { CseMachine } from '../CseMachine'; @@ -17,12 +17,7 @@ export class Line extends Visible implements IHoverable { private static readonly TO_X_INDEX = 2; private readonly _points: number[] = []; - constructor( - fromX: number, - fromY: number, - toX: number, - toY: number, - ) { + constructor(fromX: number, fromY: number, toX: number, toY: number) { super(); this._points.push(fromX, fromY, toX, toY); } @@ -32,19 +27,20 @@ export class Line extends Visible implements IHoverable { } onMouseEnter(e: KonvaEventObject<MouseEvent>) { - setHoveredStyle(e.currentTarget) + setHoveredStyle(e.currentTarget); setHoveredCursor(e.currentTarget); } onMouseLeave(e: KonvaEventObject<MouseEvent>) { - setUnhoveredStyle(e.currentTarget) + setUnhoveredStyle(e.currentTarget); setUnhoveredCursor(e.currentTarget); } draw() { const path = `M ${this._points[0]} ${this._points[1]} L ${this._points[2]} ${this._points[3]}`; return ( - <Group key={CseMachine.key++} + <Group + key={CseMachine.key++} onMouseEnter={e => this.onMouseEnter(e)} onMouseLeave={e => this.onMouseLeave(e)} > diff --git a/src/features/cseMachine/java/components/Method.tsx b/src/features/cseMachine/java/components/Method.tsx index f871358444..b814920666 100644 --- a/src/features/cseMachine/java/components/Method.tsx +++ b/src/features/cseMachine/java/components/Method.tsx @@ -12,20 +12,20 @@ import { getTextHeight, getTextWidth, setHoveredCursor, - setUnhoveredCursor, + setUnhoveredCursor } from '../../CseMachineUtils'; import { CseMachine } from '../CseMachine'; export class Method extends Visible implements IHoverable { private _centerX: number; - + private readonly _tooltipRef: RefObject<any>; private readonly _tooltip: string; constructor( x: number, y: number, - private readonly _method: JavaMethod, + private readonly _method: JavaMethod ) { super(); diff --git a/src/features/cseMachine/java/components/Object.tsx b/src/features/cseMachine/java/components/Object.tsx index 054a72944a..ed278db2df 100644 --- a/src/features/cseMachine/java/components/Object.tsx +++ b/src/features/cseMachine/java/components/Object.tsx @@ -9,7 +9,7 @@ import { Frame } from './Frame'; export class Object extends Visible { constructor( private readonly _frames: Frame[], - private readonly _object: JavaObject, + private readonly _object: JavaObject ) { super(); @@ -19,7 +19,10 @@ export class Object extends Visible { // Height and width. this._height = this._frames.reduce((accHeight, currFrame) => accHeight + currFrame.height(), 0); - this._width = this._frames.reduce((maxWidth, currFrame) => Math.max(maxWidth, currFrame.width()), 0); + this._width = this._frames.reduce( + (maxWidth, currFrame) => Math.max(maxWidth, currFrame.width()), + 0 + ); } get frames() { @@ -35,10 +38,6 @@ export class Object extends Visible { } draw(): React.ReactNode { - return ( - <Group key={CseMachine.key++}> - {this._frames.map(f => f.draw())} - </Group> - ); + return <Group key={CseMachine.key++}>{this._frames.map(f => f.draw())}</Group>; } } diff --git a/src/features/cseMachine/java/components/Stash.tsx b/src/features/cseMachine/java/components/Stash.tsx index d303dc6fe0..79e3008be5 100644 --- a/src/features/cseMachine/java/components/Stash.tsx +++ b/src/features/cseMachine/java/components/Stash.tsx @@ -27,10 +27,11 @@ export class Stash extends Visible { const stashItemStroke = ControlStashConfig.SA_WHITE; const stashItemReference = this.getStashItemRef(stashItem); const currStashItem = new StashItem( - stashItemX, + stashItemX, stashItemText, stashItemStroke, - stashItemReference); + stashItemReference + ); this._stashItems.push(currStashItem); stashItemX += currStashItem.width(); @@ -42,43 +43,42 @@ export class Stash extends Visible { } draw(): React.ReactNode { - return ( - <Group key={CseMachine.key++} > - {this._stashItems.map(s => s.draw())} - </Group> - ); + return <Group key={CseMachine.key++}>{this._stashItems.map(s => s.draw())}</Group>; } private getStashItemString = (stashItem: JavaStashItem): string => { switch (stashItem.kind) { - case "Literal": + case 'Literal': return stashItem.literalType.value; case StructType.VARIABLE: - return "location"; + return 'location'; case StructType.TYPE: return stashItem.type; default: return stashItem.kind.toLowerCase(); } - } + }; private getStashItemRef = (stashItem: JavaStashItem) => { return stashItem.kind === StructType.CLOSURE ? CseMachine.environment && - CseMachine.environment.classes - .flatMap(c => c.bindings) - .find(b => b.value instanceof Method && b.value.method === stashItem)?.value as Method + (CseMachine.environment.classes + .flatMap(c => c.bindings) + .find(b => b.value instanceof Method && b.value.method === stashItem)?.value as Method) : stashItem.kind === StructType.VARIABLE - ? CseMachine.environment && - (CseMachine.environment.frames - .flatMap(c => c.bindings) - .find(b => b.value instanceof Variable && b.value.variable === stashItem)?.value as Variable || - CseMachine.environment.classes + ? CseMachine.environment && + ((CseMachine.environment.frames .flatMap(c => c.bindings) - .find(b => b.value instanceof Variable && b.value.variable === stashItem)?.value as Variable || - CseMachine.environment.objects - .flatMap(o => o.bindings) - .find(b => b.value instanceof Variable && b.value.variable === stashItem)?.value as Variable) + .find(b => b.value instanceof Variable && b.value.variable === stashItem) + ?.value as Variable) || + (CseMachine.environment.classes + .flatMap(c => c.bindings) + .find(b => b.value instanceof Variable && b.value.variable === stashItem) + ?.value as Variable) || + (CseMachine.environment.objects + .flatMap(o => o.bindings) + .find(b => b.value instanceof Variable && b.value.variable === stashItem) + ?.value as Variable)) : stashItem.kind === StructType.CLASS ? CseMachine.environment && CseMachine.environment.classes.find(c => c.frame === stashItem.frame) @@ -86,5 +86,5 @@ export class Stash extends Visible { ? CseMachine.environment && CseMachine.environment.objects.find(o => o.frame === stashItem.frame) : undefined; - } + }; } diff --git a/src/features/cseMachine/java/components/StashItem.tsx b/src/features/cseMachine/java/components/StashItem.tsx index 2cdb91d8f1..27646641e6 100644 --- a/src/features/cseMachine/java/components/StashItem.tsx +++ b/src/features/cseMachine/java/components/StashItem.tsx @@ -3,7 +3,7 @@ import { Group as KonvaGroup, Label as KonvaLabel, Tag as KonvaTag, - Text as KonvaText, + Text as KonvaText } from 'react-konva'; import { Visible } from '../../components/Visible'; @@ -23,14 +23,14 @@ export class StashItem extends Visible { x: number, private readonly _text: string, private readonly _stroke: string, - reference?: Method | Frame | Variable, + reference?: Method | Frame | Variable ) { super(); // Position. this._x = x; this._y = ControlStashConfig.StashPosY; - + // Height and width. this._height = ControlStashConfig.StashItemHeight + ControlStashConfig.StashItemTextPadding * 2; this._width = ControlStashConfig.StashItemTextPadding * 2 + getTextWidth(this._text); @@ -39,15 +39,16 @@ export class StashItem extends Visible { if (reference) { const toY = reference instanceof Frame - ? reference.y() + reference.name.height() - : reference instanceof Method - ? reference.y() - : reference.y() + reference.type.height(); + ? reference.y() + reference.name.height() + : reference instanceof Method + ? reference.y() + : reference.y() + reference.type.height(); this._arrow = new Arrow( this._x + this._width / 2, this._y + this._height, reference.x(), - toY); + toY + ); } } @@ -58,27 +59,19 @@ export class StashItem extends Visible { fontFamily: ControlStashConfig.FontFamily, fontSize: ControlStashConfig.FontSize, fontStyle: ControlStashConfig.FontStyle, - fontVariant: ControlStashConfig.FontVariant, + fontVariant: ControlStashConfig.FontVariant }; const tagProps = { stroke: this._stroke, - cornerRadius: ControlStashConfig.StashItemCornerRadius, + cornerRadius: ControlStashConfig.StashItemCornerRadius }; return ( <KonvaGroup key={CseMachine.key++}> {/* Text */} - <KonvaLabel - x={this.x()} - y={this.y()} - key={CseMachine.key++} - > - <KonvaTag - {...ShapeDefaultProps} - {...tagProps} - key={CseMachine.key++} - /> + <KonvaLabel x={this.x()} y={this.y()} key={CseMachine.key++}> + <KonvaTag {...ShapeDefaultProps} {...tagProps} key={CseMachine.key++} /> <KonvaText {...ShapeDefaultProps} {...textProps} diff --git a/src/features/cseMachine/java/components/Text.tsx b/src/features/cseMachine/java/components/Text.tsx index 89d10c3366..db11a00abe 100644 --- a/src/features/cseMachine/java/components/Text.tsx +++ b/src/features/cseMachine/java/components/Text.tsx @@ -11,7 +11,7 @@ export class Text extends Visible { constructor( private readonly _text: string, x: number, - y: number, + y: number ) { super(); @@ -37,22 +37,13 @@ export class Text extends Visible { fontFamily: Config.FontFamily, fontSize: Config.FontSize, fontStyle: Config.FontStyle, - fill: Config.SA_WHITE, + fill: Config.SA_WHITE }; return ( <KonvaGroup key={CseMachine.key++}> - <KonvaLabel - x={this.x()} - y={this.y()} - key={CseMachine.key++} - > - <KonvaText - {...ShapeDefaultProps} - key={CseMachine.key++} - text={this._text} - {...props} - /> + <KonvaLabel x={this.x()} y={this.y()} key={CseMachine.key++}> + <KonvaText {...ShapeDefaultProps} key={CseMachine.key++} text={this._text} {...props} /> </KonvaLabel> </KonvaGroup> ); diff --git a/src/features/cseMachine/java/components/Variable.tsx b/src/features/cseMachine/java/components/Variable.tsx index a784db2f7e..798cf0965f 100644 --- a/src/features/cseMachine/java/components/Variable.tsx +++ b/src/features/cseMachine/java/components/Variable.tsx @@ -33,7 +33,7 @@ export class Variable extends Visible { constructor( x: number, y: number, - private readonly _variable: JavaVariable, + private readonly _variable: JavaVariable ) { super(); @@ -42,29 +42,29 @@ export class Variable extends Visible { this._y = y; // Type. - this._type = new Text( - this._variable.type, - this._x, - this._y); + this._type = new Text(this._variable.type, this._x, this._y); // Value. - if (this.variable.value.kind === "Literal") { + if (this.variable.value.kind === 'Literal') { this._value = new Text( this.variable.value.literalType.value, this._x + Config.TextPaddingX, - this._y + this._type.height() + Config.TextPaddingX); + this._y + this._type.height() + Config.TextPaddingX + ); } else if (this.variable.value.kind === StructType.SYMBOL) { this._value = new Text( - "", + '', this._x + Config.TextPaddingX, - this._y + this._type.height() + Config.TextPaddingX); + this._y + this._type.height() + Config.TextPaddingX + ); } else { this._value = new Text( - "", + '', this._x + Config.TextPaddingX, - this._y + this._type.height() + Config.TextPaddingX); + this._y + this._type.height() + Config.TextPaddingX + ); } - + // Height and width. this._height = this._type.height() + this._value.height() + 2 * Config.TextPaddingX; this._width = Math.max(this._type.width(), this._value.width() + 2 * Config.TextPaddingX); From 4c9b21008c0bfdafa43c01b8d6ab08219d2845e3 Mon Sep 17 00:00:00 2001 From: xyliew25 <e0550381@u.nus.edu> Date: Sat, 13 Apr 2024 08:55:38 +0800 Subject: [PATCH 05/12] Fix frontend test: rename Object to Obj to avoid potential name clash? --- src/features/cseMachine/java/components/Environment.tsx | 6 +++--- src/features/cseMachine/java/components/Object.tsx | 2 +- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/src/features/cseMachine/java/components/Environment.tsx b/src/features/cseMachine/java/components/Environment.tsx index f1da220ea5..817a6ee234 100644 --- a/src/features/cseMachine/java/components/Environment.tsx +++ b/src/features/cseMachine/java/components/Environment.tsx @@ -9,12 +9,12 @@ import { CseMachine } from '../CseMachine'; import { Arrow } from './Arrow'; import { Frame } from './Frame'; import { Line } from './Line'; -import { Object } from './Object'; +import { Obj } from './Object'; import { Variable } from './Variable'; export class Environment extends Visible { private readonly _methodFrames: Frame[] = []; - private readonly _objects: Object[] = []; + private readonly _objects: Obj[] = []; private readonly _classFrames: Frame[] = []; private readonly _lines: Line[] = []; @@ -85,7 +85,7 @@ export class Environment extends Visible { // Only add padding btwn objects. objectFramesY += Config.FramePaddingY; - this._objects.push(new Object(objectFrames, obj)); + this._objects.push(new Obj(objectFrames, obj)); }); // Create class frames. diff --git a/src/features/cseMachine/java/components/Object.tsx b/src/features/cseMachine/java/components/Object.tsx index ed278db2df..25c06d19a4 100644 --- a/src/features/cseMachine/java/components/Object.tsx +++ b/src/features/cseMachine/java/components/Object.tsx @@ -6,7 +6,7 @@ import { Visible } from '../../components/Visible'; import { CseMachine } from '../CseMachine'; import { Frame } from './Frame'; -export class Object extends Visible { +export class Obj extends Visible { constructor( private readonly _frames: Frame[], private readonly _object: JavaObject From 0693cb768130ee97bb145aebc32b3fbbc31eb039 Mon Sep 17 00:00:00 2001 From: xyliew25 <e0550381@u.nus.edu> Date: Sat, 13 Apr 2024 09:54:02 +0800 Subject: [PATCH 06/12] Update snapshot --- .../SideContentCseMachine.tsx.snap | 24 ++++++++++--------- 1 file changed, 13 insertions(+), 11 deletions(-) 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 - - <a - href="https://sourceacademy.org/sicpjs/3.2" - rel="noopener noreferrer" - target="_blank" - > - <i> - Structure and Interpretation of Computer Programs, JavaScript Edition, Chapter 3, Section 2 - </i> - </a> + <span> + The CSE machine generates control, stash and environment model diagrams following a notation introduced in + + <a + href="https://sourceacademy.org/sicpjs/3.2" + rel="noopener noreferrer" + target="_blank" + > + <i> + Structure and Interpretation of Computer Programs, JavaScript Edition, Chapter 3, Section 2 + </i> + </a> + </span> . <br /> <br /> From af547e97c979d3cbd201b1553c9b8bfe5600b383 Mon Sep 17 00:00:00 2001 From: xyliew25 <e0550381@u.nus.edu> Date: Sat, 13 Apr 2024 18:19:27 +0800 Subject: [PATCH 07/12] Update imports from java-slang --- src/commons/utils/JavaHelper.ts | 11 +- src/features/cseMachine/java/CseMachine.tsx | 4 +- .../cseMachine/java/components/Binding.tsx | 8 +- .../cseMachine/java/components/Control.tsx | 134 ++++++++---------- .../java/components/Environment.tsx | 21 ++- .../cseMachine/java/components/Frame.tsx | 4 +- .../cseMachine/java/components/Method.tsx | 5 +- .../cseMachine/java/components/Object.tsx | 4 +- .../cseMachine/java/components/Stash.tsx | 21 ++- .../cseMachine/java/components/Variable.tsx | 6 +- 10 files changed, 98 insertions(+), 120 deletions(-) diff --git a/src/commons/utils/JavaHelper.ts b/src/commons/utils/JavaHelper.ts index 7ea2344e5b..493fcbfd81 100644 --- a/src/commons/utils/JavaHelper.ts +++ b/src/commons/utils/JavaHelper.ts @@ -1,7 +1,4 @@ -import { SourceError as JavaSourceError } from 'java-slang/dist/ec-evaluator/errors'; -import { runECEvaluator } from 'java-slang/dist/ec-evaluator/index'; -import { Context as JavaContext } from 'java-slang/dist/ec-evaluator/types'; -import { compileFromSource, typeCheck } from 'java-slang'; +import { compileFromSource, ECE, typeCheck } from 'java-slang'; import { BinaryWriter } from 'java-slang/dist/compiler/binary-writer'; import setupJVM, { parseBin } from 'java-slang/dist/jvm'; import { createModuleProxy, loadCachedFiles } from 'java-slang/dist/jvm/utils/integration'; @@ -161,7 +158,7 @@ export async function javaRun( }); } -export function visualizeJavaCseMachine({ context }: { context: JavaContext }) { +export function visualizeJavaCseMachine({ context }: { context: ECE.Context }) { try { CseMachine.drawCse(context); } catch (err) { @@ -170,7 +167,7 @@ export function visualizeJavaCseMachine({ context }: { context: JavaContext }) { } export async function runJavaCseMachine(code: string, targetStep: number, context: Context) { - const convertJavaErrorToJsError = (e: JavaSourceError): SourceError => ({ + const convertJavaErrorToJsError = (e: ECE.SourceError): SourceError => ({ type: ErrorType.RUNTIME, severity: ErrorSeverity.ERROR, location: { @@ -187,7 +184,7 @@ export async function runJavaCseMachine(code: string, targetStep: number, contex elaborate: () => e.explain() }); context.executionMethod = 'cse-machine'; - return runECEvaluator(code, targetStep) + return ECE.runECEvaluator(code, targetStep) .then(result => { context.runtime.envStepsTotal = result.context.totalSteps; if (result.status === 'error') { diff --git a/src/features/cseMachine/java/CseMachine.tsx b/src/features/cseMachine/java/CseMachine.tsx index bd2660867c..d259b4e021 100644 --- a/src/features/cseMachine/java/CseMachine.tsx +++ b/src/features/cseMachine/java/CseMachine.tsx @@ -1,4 +1,4 @@ -import { Context } from 'java-slang/dist/ec-evaluator/types'; +import { ECE } from 'java-slang'; import { KonvaEventObject } from 'konva/lib/Node'; import React, { RefObject } from 'react'; import { Layer, Rect, Stage } from 'react-konva'; @@ -35,7 +35,7 @@ export class CseMachine { /** updates the visualization state in the SideContentCseMachine component based on * the Java Slang context passed in */ - static drawCse(context: Context) { + static drawCse(context: ECE.Context) { if (!this.setVis || !context.environment || !context.control || !context.stash) { throw new Error('Java CSE Machine not initialized'); } diff --git a/src/features/cseMachine/java/components/Binding.tsx b/src/features/cseMachine/java/components/Binding.tsx index 6893d82ed2..6ecdb6cb47 100644 --- a/src/features/cseMachine/java/components/Binding.tsx +++ b/src/features/cseMachine/java/components/Binding.tsx @@ -1,4 +1,4 @@ -import { Name, StructType, Value } from 'java-slang/dist/ec-evaluator/types'; +import { ECE } from 'java-slang'; import React from 'react'; import { Visible } from '../../components/Visible'; @@ -17,14 +17,14 @@ export class Binding extends Visible { // Only Method has arrow. private readonly _arrow: Arrow | undefined; - constructor(name: Name, value: Value, x: number, y: number) { + constructor(name: ECE.Name, value: ECE.Value, x: number, y: number) { super(); // Position. this._x = x; this._y = y; - if (value.kind === StructType.CLOSURE) { + if (value.kind === ECE.StructType.CLOSURE) { // Name. this._name = new Text( name + Config.VariableColon, // := is part of name @@ -43,7 +43,7 @@ export class Binding extends Visible { this._value.x(), this._value.y() ); - } else if (value.kind === StructType.VARIABLE) { + } else if (value.kind === ECE.StructType.VARIABLE) { // Name. this._name = new Text( name + Config.VariableColon, // := is part of name diff --git a/src/features/cseMachine/java/components/Control.tsx b/src/features/cseMachine/java/components/Control.tsx index cd6550fb2c..8305ec1551 100644 --- a/src/features/cseMachine/java/components/Control.tsx +++ b/src/features/cseMachine/java/components/Control.tsx @@ -1,20 +1,4 @@ -import { astToString } from 'java-slang/dist/ast/utils/astToString'; -import { Control as JavaControl } from 'java-slang/dist/ec-evaluator/components'; -import { - BinOpInstr, - ControlItem as JavaControlItem, - EnvInstr, - EvalVarInstr, - InstrType, - InvInstr, - NewInstr, - ResConOverloadInstr, - ResInstr, - ResOverloadInstr, - ResTypeContInstr, - ResTypeInstr -} from 'java-slang/dist/ec-evaluator/types'; -import { isInstr, isNode } from 'java-slang/dist/ec-evaluator/utils'; +import { astToString, ECE } from 'java-slang'; import { Group } from 'react-konva'; import { Visible } from '../../components/Visible'; @@ -26,7 +10,7 @@ import { ControlItem } from './ControlItem'; export class Control extends Visible { private readonly _controlItems: ControlItem[] = []; - constructor(control: JavaControl) { + constructor(control: ECE.Control) { super(); // Position. @@ -48,14 +32,14 @@ export class Control extends Visible { // TODO reference draw ltr? const controlItemReference = - isInstr(controlItem) && controlItem.instrType === InstrType.ENV - ? CseMachine.environment?.frames.find(f => f.frame === (controlItem as EnvInstr).env) + ECE.isInstr(controlItem) && controlItem.instrType === ECE.InstrType.ENV + ? CseMachine.environment?.frames.find(f => f.frame === (controlItem as ECE.EnvInstr).env) : undefined; const controlItemTooltip = this.getControlItemTooltip(controlItem); this.getControlItemTooltip(controlItem); - const node = isNode(controlItem) ? controlItem : controlItem.srcNode; + const node = ECE.isNode(controlItem) ? controlItem : controlItem.srcNode; const highlightOnHover = () => { let start = -1; let end = -1; @@ -95,116 +79,116 @@ export class Control extends Visible { ); } - private getControlItemString = (controlItem: JavaControlItem): string => { - if (isNode(controlItem)) { + private getControlItemString = (controlItem: ECE.ControlItem): string => { + if (ECE.isNode(controlItem)) { return astToString(controlItem); } switch (controlItem.instrType) { - case InstrType.RESET: + case ECE.InstrType.RESET: return 'return'; - case InstrType.ASSIGNMENT: + case ECE.InstrType.ASSIGNMENT: return 'asgn'; - case InstrType.BINARY_OP: - const binOpInstr = controlItem as BinOpInstr; + case ECE.InstrType.BINARY_OP: + const binOpInstr = controlItem as ECE.BinOpInstr; return binOpInstr.symbol; - case InstrType.POP: + case ECE.InstrType.POP: return 'pop'; - case InstrType.INVOCATION: - const appInstr = controlItem as InvInstr; + case ECE.InstrType.INVOCATION: + const appInstr = controlItem as ECE.InvInstr; return `invoke ${appInstr.arity}`; - case InstrType.ENV: + case ECE.InstrType.ENV: return 'env'; - case InstrType.MARKER: + case ECE.InstrType.MARKER: return 'mark'; - case InstrType.EVAL_VAR: - const evalVarInstr = controlItem as EvalVarInstr; + case ECE.InstrType.EVAL_VAR: + const evalVarInstr = controlItem as ECE.EvalVarInstr; return `name ${evalVarInstr.symbol}`; - case InstrType.NEW: - const newInstr = controlItem as NewInstr; + case ECE.InstrType.NEW: + const newInstr = controlItem as ECE.NewInstr; return `new ${newInstr.c.frame.name}`; - case InstrType.RES_TYPE: - const resTypeInstr = controlItem as ResTypeInstr; + case ECE.InstrType.RES_TYPE: + const resTypeInstr = controlItem as ECE.ResTypeInstr; return `res_type ${ resTypeInstr.value.kind === 'Class' ? resTypeInstr.value.frame.name : astToString(resTypeInstr.value) }`; - case InstrType.RES_TYPE_CONT: - const resTypeContInstr = controlItem as ResTypeContInstr; + case ECE.InstrType.RES_TYPE_CONT: + const resTypeContInstr = controlItem as ECE.ResTypeContInstr; return `res_type_cont ${resTypeContInstr.name}`; - case InstrType.RES_OVERLOAD: - const resOverloadInstr = controlItem as ResOverloadInstr; + case ECE.InstrType.RES_OVERLOAD: + const resOverloadInstr = controlItem as ECE.ResOverloadInstr; return `res_overload ${resOverloadInstr.name} ${resOverloadInstr.arity}`; - case InstrType.RES_OVERRIDE: + case ECE.InstrType.RES_OVERRIDE: return `res_override`; - case InstrType.RES_CON_OVERLOAD: - const resConOverloadInstr = controlItem as ResConOverloadInstr; + case ECE.InstrType.RES_CON_OVERLOAD: + const resConOverloadInstr = controlItem as ECE.ResConOverloadInstr; return `res_con_overload ${resConOverloadInstr.arity}`; - case InstrType.RES: - const resInstr = controlItem as ResInstr; + case ECE.InstrType.RES: + const resInstr = controlItem as ECE.ResInstr; return `res ${resInstr.name}`; - case InstrType.DEREF: + case ECE.InstrType.DEREF: return 'deref'; default: return 'INSTRUCTION'; } }; - private getControlItemTooltip = (controlItem: JavaControlItem): string => { - if (isNode(controlItem)) { + private getControlItemTooltip = (controlItem: ECE.ControlItem): string => { + if (ECE.isNode(controlItem)) { return astToString(controlItem); } switch (controlItem.instrType) { - case InstrType.RESET: + case ECE.InstrType.RESET: return 'Skip control items until marker instruction is reached'; - case InstrType.ASSIGNMENT: + case ECE.InstrType.ASSIGNMENT: return 'Assign value on top of stash to location on top of stash'; - case InstrType.BINARY_OP: - const binOpInstr = controlItem as BinOpInstr; + case ECE.InstrType.BINARY_OP: + const binOpInstr = controlItem as ECE.BinOpInstr; return `Perform ${binOpInstr.symbol} on top 2 stash values`; - case InstrType.POP: + case ECE.InstrType.POP: return 'Pop most recently pushed value from stash'; - case InstrType.INVOCATION: - const appInstr = controlItem as InvInstr; + case ECE.InstrType.INVOCATION: + const appInstr = controlItem as ECE.InvInstr; return `Invoke method with ${appInstr.arity} argument${appInstr.arity === 1 ? '' : 's'}`; - case InstrType.ENV: + case ECE.InstrType.ENV: return 'Set current environment to this environment'; - case InstrType.MARKER: + case ECE.InstrType.MARKER: return 'Mark return address'; - case InstrType.EVAL_VAR: - const evalVarInstr = controlItem as EvalVarInstr; + case ECE.InstrType.EVAL_VAR: + const evalVarInstr = controlItem as ECE.EvalVarInstr; return `name ${evalVarInstr.symbol}`; - case InstrType.NEW: - const newInstr = controlItem as NewInstr; + case ECE.InstrType.NEW: + const newInstr = controlItem as ECE.NewInstr; return `Create new instance of class ${newInstr.c.frame.name}`; - case InstrType.RES_TYPE: - const resTypeInstr = controlItem as ResTypeInstr; + case ECE.InstrType.RES_TYPE: + const resTypeInstr = controlItem as ECE.ResTypeInstr; return `Resolve type of ${ resTypeInstr.value.kind === 'Class' ? resTypeInstr.value.frame.name : astToString(resTypeInstr.value) }`; - case InstrType.RES_TYPE_CONT: - const resTypeContInstr = controlItem as ResTypeContInstr; + case ECE.InstrType.RES_TYPE_CONT: + const resTypeContInstr = controlItem as ECE.ResTypeContInstr; return `Resolve type of ${resTypeContInstr.name} in most recently pushed type from stash`; - case InstrType.RES_OVERLOAD: - const resOverloadInstr = controlItem as ResOverloadInstr; + case ECE.InstrType.RES_OVERLOAD: + const resOverloadInstr = controlItem as ECE.ResOverloadInstr; return `Resolve overloading of method ${resOverloadInstr.name} with ${ resOverloadInstr.arity } argument${resOverloadInstr.arity === 1 ? '' : 's'}`; - case InstrType.RES_OVERRIDE: + case ECE.InstrType.RES_OVERRIDE: return 'Resolve overriding of resolved method on top of stash'; - case InstrType.RES_CON_OVERLOAD: - const resConOverloadInstr = controlItem as ResConOverloadInstr; + case ECE.InstrType.RES_CON_OVERLOAD: + const resConOverloadInstr = controlItem as ECE.ResConOverloadInstr; return `Resolve constructor overloading of class on stash with ${ resConOverloadInstr.arity } argument${resConOverloadInstr.arity === 1 ? '' : 's'}`; - case InstrType.RES: - const resInstr = controlItem as ResInstr; + case ECE.InstrType.RES: + const resInstr = controlItem as ECE.ResInstr; return `Resolve field ${resInstr.name} of most recently pushed value from stash`; - case InstrType.DEREF: + case ECE.InstrType.DEREF: return 'Dereference most recently pushed value from stash'; default: return 'INSTRUCTION'; diff --git a/src/features/cseMachine/java/components/Environment.tsx b/src/features/cseMachine/java/components/Environment.tsx index 817a6ee234..a66f963eb6 100644 --- a/src/features/cseMachine/java/components/Environment.tsx +++ b/src/features/cseMachine/java/components/Environment.tsx @@ -1,5 +1,4 @@ -import { Environment as JavaEnvironment, EnvNode } from 'java-slang/dist/ec-evaluator/components'; -import { Class as JavaClass, StructType } from 'java-slang/dist/ec-evaluator/types'; +import { ECE } from 'java-slang'; import { Group } from 'react-konva'; import { Visible } from '../../components/Visible'; @@ -18,7 +17,7 @@ export class Environment extends Visible { private readonly _classFrames: Frame[] = []; private readonly _lines: Line[] = []; - constructor(environment: JavaEnvironment) { + constructor(environment: ECE.Environment) { super(); // Position. @@ -35,7 +34,7 @@ export class Environment extends Visible { let methodFramesWidth = Number(Config.FrameMinWidth); environment.global.children.forEach(env => { if (env.name.includes('(')) { - let currEnv: EnvNode | undefined = env; + let currEnv: ECE.EnvNode | undefined = env; let parentFrame; while (currEnv) { const stroke = currEnv === environment.current ? Config.SA_CURRENT_ITEM : Config.SA_WHITE; @@ -60,7 +59,7 @@ export class Environment extends Visible { let objectFrameWidth = Number(Config.FrameMinWidth); // Get top env. - let env: EnvNode | undefined = obj.frame; + let env: ECE.EnvNode | undefined = obj.frame; while (env.parent) { env = env.parent; } @@ -92,11 +91,11 @@ export class Environment extends Visible { const classFramesX = objectFramesX + objectFramesWidth + Config.FrameMinWidth; let classFramesY = this._y; for (const [_, c] of environment.global.frame.entries()) { - const classEnv = (c as JavaClass).frame; + const classEnv = (c as ECE.Class).frame; const classFrameStroke = classEnv === environment.current ? Config.SA_CURRENT_ITEM : Config.SA_WHITE; const highlightOnHover = () => { - const node = (c as JavaClass).classDecl; + const node = (c as ECE.Class).classDecl; let start = -1; let end = -1; if (node.location) { @@ -115,7 +114,7 @@ export class Environment extends Visible { highlightOnHover, unhighlightOnHover ); - const superClassName = (c as JavaClass).superclass?.frame.name; + const superClassName = (c as ECE.Class).superclass?.frame.name; if (superClassName) { const parentFrame = this._classFrames.find(f => f.name.text === superClassName)!; classFrame.setParent(parentFrame); @@ -127,7 +126,7 @@ export class Environment extends Visible { // Draw arrow for var ref in mtd frames to corresponding obj. this._methodFrames.forEach(mf => { mf.bindings.forEach(b => { - if (b.value instanceof Variable && b.value.variable.value.kind === StructType.OBJECT) { + if (b.value instanceof Variable && b.value.variable.value.kind === ECE.StructType.OBJECT) { const objFrame = b.value.variable.value.frame; const matchingObj = this._objects.filter(o => o.getFrame().frame === objFrame)[0]; b.value.value = new Arrow( @@ -145,7 +144,7 @@ export class Environment extends Visible { .flatMap(obj => obj.frames) .forEach(of => { of.bindings.forEach(b => { - if (b.value instanceof Variable && b.value.variable.value.kind === StructType.VARIABLE) { + if (b.value instanceof Variable && b.value.variable.value.kind === ECE.StructType.VARIABLE) { const variable = b.value.variable.value; const matchingVariable = this._classFrames .flatMap(c => c.bindings) @@ -158,7 +157,7 @@ export class Environment extends Visible { matchingVariable.y() + matchingVariable.type.height() ); } - if (b.value instanceof Variable && b.value.variable.value.kind === StructType.OBJECT) { + if (b.value instanceof Variable && b.value.variable.value.kind === ECE.StructType.OBJECT) { const obj = b.value.variable.value.frame; const matchingObj = this._objects.find(o => o.getFrame().frame === obj)!; // Variable always has a box. diff --git a/src/features/cseMachine/java/components/Frame.tsx b/src/features/cseMachine/java/components/Frame.tsx index 8465b8b65f..2c7ec35570 100644 --- a/src/features/cseMachine/java/components/Frame.tsx +++ b/src/features/cseMachine/java/components/Frame.tsx @@ -1,4 +1,4 @@ -import { EnvNode } from 'java-slang/dist/ec-evaluator/components'; +import { ECE } from 'java-slang'; import { KonvaEventObject } from 'konva/lib/Node'; import React, { RefObject } from 'react'; import { Group, Label, Rect, Tag, Text as KonvaText } from 'react-konva'; @@ -22,7 +22,7 @@ export class Frame extends Visible implements IHoverable { private parent: Frame | undefined; constructor( - readonly frame: EnvNode, + readonly frame: ECE.EnvNode, x: number, y: number, readonly stroke: string, diff --git a/src/features/cseMachine/java/components/Method.tsx b/src/features/cseMachine/java/components/Method.tsx index b814920666..45846e10e6 100644 --- a/src/features/cseMachine/java/components/Method.tsx +++ b/src/features/cseMachine/java/components/Method.tsx @@ -1,5 +1,4 @@ -import { astToString } from 'java-slang/dist/ast/utils/astToString'; -import { Closure as JavaMethod } from 'java-slang/dist/ec-evaluator/types'; +import { astToString, ECE } from 'java-slang'; import { KonvaEventObject } from 'konva/lib/Node'; import React, { RefObject } from 'react'; import { Circle, Group, Label, Tag, Text } from 'react-konva'; @@ -25,7 +24,7 @@ export class Method extends Visible implements IHoverable { constructor( x: number, y: number, - private readonly _method: JavaMethod + private readonly _method: ECE.Closure ) { super(); diff --git a/src/features/cseMachine/java/components/Object.tsx b/src/features/cseMachine/java/components/Object.tsx index 25c06d19a4..73760e232f 100644 --- a/src/features/cseMachine/java/components/Object.tsx +++ b/src/features/cseMachine/java/components/Object.tsx @@ -1,4 +1,4 @@ -import { Object as JavaObject } from 'java-slang/dist/ec-evaluator/types'; +import { ECE } from 'java-slang'; import React from 'react'; import { Group } from 'react-konva'; @@ -9,7 +9,7 @@ import { Frame } from './Frame'; export class Obj extends Visible { constructor( private readonly _frames: Frame[], - private readonly _object: JavaObject + private readonly _object: ECE.Object ) { super(); diff --git a/src/features/cseMachine/java/components/Stash.tsx b/src/features/cseMachine/java/components/Stash.tsx index 79e3008be5..263fd097ac 100644 --- a/src/features/cseMachine/java/components/Stash.tsx +++ b/src/features/cseMachine/java/components/Stash.tsx @@ -1,5 +1,4 @@ -import { Stash as JavaStash } from 'java-slang/dist/ec-evaluator/components'; -import { StashItem as JavaStashItem, StructType } from 'java-slang/dist/ec-evaluator/types'; +import { ECE } from 'java-slang'; import React from 'react'; import { Group } from 'react-konva'; @@ -13,7 +12,7 @@ import { Variable } from './Variable'; export class Stash extends Visible { private readonly _stashItems: StashItem[] = []; - constructor(stash: JavaStash) { + constructor(stash: ECE.Stash) { super(); // Position. @@ -46,26 +45,26 @@ export class Stash extends Visible { return <Group key={CseMachine.key++}>{this._stashItems.map(s => s.draw())}</Group>; } - private getStashItemString = (stashItem: JavaStashItem): string => { + private getStashItemString = (stashItem: ECE.StashItem): string => { switch (stashItem.kind) { case 'Literal': return stashItem.literalType.value; - case StructType.VARIABLE: + case ECE.StructType.VARIABLE: return 'location'; - case StructType.TYPE: + case ECE.StructType.TYPE: return stashItem.type; default: return stashItem.kind.toLowerCase(); } }; - private getStashItemRef = (stashItem: JavaStashItem) => { - return stashItem.kind === StructType.CLOSURE + private getStashItemRef = (stashItem: ECE.StashItem) => { + return stashItem.kind === ECE.StructType.CLOSURE ? CseMachine.environment && (CseMachine.environment.classes .flatMap(c => c.bindings) .find(b => b.value instanceof Method && b.value.method === stashItem)?.value as Method) - : stashItem.kind === StructType.VARIABLE + : stashItem.kind === ECE.StructType.VARIABLE ? CseMachine.environment && ((CseMachine.environment.frames .flatMap(c => c.bindings) @@ -79,10 +78,10 @@ export class Stash extends Visible { .flatMap(o => o.bindings) .find(b => b.value instanceof Variable && b.value.variable === stashItem) ?.value as Variable)) - : stashItem.kind === StructType.CLASS + : stashItem.kind === ECE.StructType.CLASS ? CseMachine.environment && CseMachine.environment.classes.find(c => c.frame === stashItem.frame) - : stashItem.kind === StructType.OBJECT + : stashItem.kind === ECE.StructType.OBJECT ? CseMachine.environment && CseMachine.environment.objects.find(o => o.frame === stashItem.frame) : undefined; diff --git a/src/features/cseMachine/java/components/Variable.tsx b/src/features/cseMachine/java/components/Variable.tsx index 798cf0965f..380a7df816 100644 --- a/src/features/cseMachine/java/components/Variable.tsx +++ b/src/features/cseMachine/java/components/Variable.tsx @@ -1,4 +1,4 @@ -import { StructType, Variable as JavaVariable } from 'java-slang/dist/ec-evaluator/types'; +import { ECE } from 'java-slang'; import React from 'react'; import { Group, Rect } from 'react-konva'; @@ -33,7 +33,7 @@ export class Variable extends Visible { constructor( x: number, y: number, - private readonly _variable: JavaVariable + private readonly _variable: ECE.Variable ) { super(); @@ -51,7 +51,7 @@ export class Variable extends Visible { this._x + Config.TextPaddingX, this._y + this._type.height() + Config.TextPaddingX ); - } else if (this.variable.value.kind === StructType.SYMBOL) { + } else if (this.variable.value.kind === ECE.StructType.SYMBOL) { this._value = new Text( '', this._x + Config.TextPaddingX, From 8be3377d184233fd97c86d9483978eceb3e74da7 Mon Sep 17 00:00:00 2001 From: xyliew25 <e0550381@u.nus.edu> Date: Sat, 13 Apr 2024 18:30:19 +0800 Subject: [PATCH 08/12] Fix lint --- .../cseMachine/java/components/Environment.tsx | 10 ++++++++-- 1 file changed, 8 insertions(+), 2 deletions(-) diff --git a/src/features/cseMachine/java/components/Environment.tsx b/src/features/cseMachine/java/components/Environment.tsx index a66f963eb6..e578fb3a54 100644 --- a/src/features/cseMachine/java/components/Environment.tsx +++ b/src/features/cseMachine/java/components/Environment.tsx @@ -144,7 +144,10 @@ export class Environment extends Visible { .flatMap(obj => obj.frames) .forEach(of => { of.bindings.forEach(b => { - if (b.value instanceof Variable && b.value.variable.value.kind === ECE.StructType.VARIABLE) { + if ( + b.value instanceof Variable && + b.value.variable.value.kind === ECE.StructType.VARIABLE + ) { const variable = b.value.variable.value; const matchingVariable = this._classFrames .flatMap(c => c.bindings) @@ -157,7 +160,10 @@ export class Environment extends Visible { matchingVariable.y() + matchingVariable.type.height() ); } - if (b.value instanceof Variable && b.value.variable.value.kind === ECE.StructType.OBJECT) { + if ( + b.value instanceof Variable && + b.value.variable.value.kind === ECE.StructType.OBJECT + ) { const obj = b.value.variable.value.frame; const matchingObj = this._objects.find(o => o.getFrame().frame === obj)!; // Variable always has a box. From 7934f6a602ea1c1e15a44ac509e301fb3b764ba1 Mon Sep 17 00:00:00 2001 From: xyliew25 <e0550381@u.nus.edu> Date: Sat, 13 Apr 2024 18:54:42 +0800 Subject: [PATCH 09/12] Update snapshot --- .../__snapshots__/Playground.tsx.snap | 24 ++++++++++--------- 1 file changed, 13 insertions(+), 11 deletions(-) diff --git a/src/pages/playground/__tests__/__snapshots__/Playground.tsx.snap b/src/pages/playground/__tests__/__snapshots__/Playground.tsx.snap index e0ee8352d6..561f62ea1a 100644 --- a/src/pages/playground/__tests__/__snapshots__/Playground.tsx.snap +++ b/src/pages/playground/__tests__/__snapshots__/Playground.tsx.snap @@ -979,17 +979,19 @@ and also the 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 - - <a - href="https://sourceacademy.org/sicpjs/3.2" - rel="noopener noreferrer" - target="_blank" - > - <i> - Structure and Interpretation of Computer Programs, JavaScript Edition, Chapter 3, Section 2 - </i> - </a> + <span> + The CSE machine generates control, stash and environment model diagrams following a notation introduced in + + <a + href="https://sourceacademy.org/sicpjs/3.2" + rel="noopener noreferrer" + target="_blank" + > + <i> + Structure and Interpretation of Computer Programs, JavaScript Edition, Chapter 3, Section 2 + </i> + </a> + </span> . <br /> <br /> From b962fd02e5993195838f4382a5122c22c2479522 Mon Sep 17 00:00:00 2001 From: xyliew25 <e0550381@u.nus.edu> Date: Sat, 13 Apr 2024 19:07:04 +0800 Subject: [PATCH 10/12] Fix linting --- src/features/cseMachine/java/components/Environment.tsx | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/features/cseMachine/java/components/Environment.tsx b/src/features/cseMachine/java/components/Environment.tsx index e578fb3a54..f8be475a31 100644 --- a/src/features/cseMachine/java/components/Environment.tsx +++ b/src/features/cseMachine/java/components/Environment.tsx @@ -90,7 +90,7 @@ export class Environment extends Visible { // Create class frames. const classFramesX = objectFramesX + objectFramesWidth + Config.FrameMinWidth; let classFramesY = this._y; - for (const [_, c] of environment.global.frame.entries()) { + for (const c of environment.global.frame.values()) { const classEnv = (c as ECE.Class).frame; const classFrameStroke = classEnv === environment.current ? Config.SA_CURRENT_ITEM : Config.SA_WHITE; From edf485bea2039042644396f7869d69bd5a3d2c43 Mon Sep 17 00:00:00 2001 From: Liew Xin Yi <e0550381@u.nus.edu> Date: Sat, 13 Apr 2024 19:30:06 +0800 Subject: [PATCH 11/12] Fix typo Co-authored-by: Richard Dominick <34370238+RichDom2185@users.noreply.github.com> --- src/commons/sideContent/content/SideContentCseMachine.tsx | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/commons/sideContent/content/SideContentCseMachine.tsx b/src/commons/sideContent/content/SideContentCseMachine.tsx index 355f3c0cd5..fe6adf3048 100644 --- a/src/commons/sideContent/content/SideContentCseMachine.tsx +++ b/src/commons/sideContent/content/SideContentCseMachine.tsx @@ -447,7 +447,7 @@ class SideContentCseMachineBase extends React.Component<CseMachineProps, State> if (this.isJava()) { JavaCseMachine.zoomStage(isZoomIn, multiplier); } else { - Layout.zoomStage(false, 5); + Layout.zoomStage(isZoomIn, multiplier); } }; From 3d5fc872c919d8581fab97b3b4587f6dd107b64d Mon Sep 17 00:00:00 2001 From: xyliew25 <e0550381@u.nus.edu> Date: Sat, 13 Apr 2024 19:36:41 +0800 Subject: [PATCH 12/12] Add TODO for err source node location --- src/commons/utils/JavaHelper.ts | 1 + 1 file changed, 1 insertion(+) diff --git a/src/commons/utils/JavaHelper.ts b/src/commons/utils/JavaHelper.ts index 493fcbfd81..fab36ed3c3 100644 --- a/src/commons/utils/JavaHelper.ts +++ b/src/commons/utils/JavaHelper.ts @@ -170,6 +170,7 @@ export async function runJavaCseMachine(code: string, targetStep: number, contex const convertJavaErrorToJsError = (e: ECE.SourceError): SourceError => ({ type: ErrorType.RUNTIME, severity: ErrorSeverity.ERROR, + // TODO update err source node location once location info is avail location: { start: { line: 0,