├── .gitignore ├── .prettierrc ├── .vscode └── launch.json ├── README.md ├── favicon.ico ├── index.html ├── package-lock.json ├── package.json ├── public └── default.js ├── src ├── index.ts ├── scripts │ ├── ApplicationState.ts │ ├── animation │ │ └── animation.ts │ ├── editor │ │ ├── Editor.ts │ │ ├── EditorThemes.ts │ │ └── Separators.ts │ ├── environment │ │ ├── EnvironmentState.ts │ │ ├── data.ts │ │ └── environment.ts │ ├── execution │ │ ├── execution.ts │ │ ├── graph │ │ │ └── ExecutionGraph.ts │ │ └── primitive │ │ │ ├── Binary │ │ │ ├── BinaryExpressionEvaluate.ts │ │ │ └── LogicalExpressionEvaluate.ts │ │ │ ├── Binding │ │ │ ├── BindAnimation.ts │ │ │ └── BindFunctionAnimation.ts │ │ │ ├── Container │ │ │ ├── ArrayStartAnimation.ts │ │ │ └── ObjectStartAnimation.ts │ │ │ ├── Data │ │ │ ├── ConsumeDataAnimation.ts │ │ │ ├── CopyDataAnimation.ts │ │ │ ├── CopyReferenceAnimation.ts │ │ │ ├── CreateLiteralAnimation.ts │ │ │ ├── FindMember.ts │ │ │ ├── FindVariableAnimation.ts │ │ │ ├── FloatAnimation.ts │ │ │ ├── GetMember.ts │ │ │ ├── MoveAndPlaceAnimation.ts │ │ │ └── UpdateAnimation.ts │ │ │ ├── ExecutionNode.ts │ │ │ ├── Functions │ │ │ ├── Native │ │ │ │ ├── Array │ │ │ │ │ ├── ArrayConcatAnimation.ts │ │ │ │ │ └── ArrayPushAnimation.ts │ │ │ │ └── Math │ │ │ │ │ ├── FloorAnimation.ts │ │ │ │ │ └── SqrtAnimation.ts │ │ │ └── ReturnStatementAnimation.ts │ │ │ ├── Scope │ │ │ ├── CreateScopeAnimation.ts │ │ │ └── PopScopeAnimation.ts │ │ │ └── Unary │ │ │ └── UnaryExpressionEvaluate.ts │ ├── renderer │ │ ├── Action │ │ │ ├── Abyss.ts │ │ │ ├── Action.ts │ │ │ ├── Dynamic │ │ │ │ ├── AssignmentExpressionRepresentation.ts │ │ │ │ ├── BinaryExpressionRepresentation.ts │ │ │ │ ├── BlockStatementRepresentation.ts │ │ │ │ ├── CallExpressionRepresentation.ts │ │ │ │ ├── ExpressionStatementRepresentation.ts │ │ │ │ ├── ForStatementRepresentation.ts │ │ │ │ ├── FunctionCallRepresentation.ts │ │ │ │ ├── IfStatementRepresentation.ts │ │ │ │ ├── ProgramRepresentation.ts │ │ │ │ ├── Representation.ts │ │ │ │ ├── ReturnStatementRepresentation.ts │ │ │ │ ├── UpdateExpressionRepresentation.ts │ │ │ │ └── VariableDeclarationRepresentation.ts │ │ │ └── Mapping │ │ │ │ ├── ActionMapping.ts │ │ │ │ ├── ActionMappingCursor.ts │ │ │ │ ├── ActionProxy.ts │ │ │ │ ├── ControlFlowCursor.ts │ │ │ │ ├── ControlFlowState.ts │ │ │ │ └── index.d.ts │ │ ├── Trail │ │ │ ├── Renderers │ │ │ │ ├── CreateTrailRenderer.ts │ │ │ │ ├── MoveTrailRenderer.ts │ │ │ │ ├── PartialCreateTrailRenderer.ts │ │ │ │ ├── PartialMoveTrailRenderer.ts │ │ │ │ └── TrailRenderer.ts │ │ │ ├── ReturnAnimation.ts │ │ │ ├── Trail.ts │ │ │ ├── TrailGroup.ts │ │ │ └── TrailState.ts │ │ └── View │ │ │ ├── Environment │ │ │ ├── EnvironmentRenderer.ts │ │ │ ├── HardScopeRenderer.ts │ │ │ ├── data │ │ │ │ ├── DataRenderer.ts │ │ │ │ ├── DataState.ts │ │ │ │ ├── array │ │ │ │ │ ├── ArrayRenderer.ts │ │ │ │ │ └── IndexRenderer.ts │ │ │ │ ├── literal │ │ │ │ │ └── LiteralRenderer.ts │ │ │ │ ├── object │ │ │ │ │ ├── ObjectItemRenderer.ts │ │ │ │ │ └── ObjectRenderer.ts │ │ │ │ └── reference │ │ │ │ │ ├── FunctionRenderer.ts │ │ │ │ │ └── ReferenceRenderer.ts │ │ │ └── identifier │ │ │ │ └── IdentifierRenderer.ts │ │ │ └── View.ts │ ├── transpiler │ │ ├── Compiler.ts │ │ ├── Expressions │ │ │ ├── Array │ │ │ │ └── ArrayExpression.ts │ │ │ ├── BinaryOperations │ │ │ │ ├── AssigmentExpression.ts │ │ │ │ ├── BinaryExpression │ │ │ │ │ └── BinaryExpression.ts │ │ │ │ ├── LogicalExpression.ts │ │ │ │ └── MemberExpression.ts │ │ │ ├── CallExpression.ts │ │ │ ├── ObjectExpression.ts │ │ │ └── UnaryOperations │ │ │ │ ├── UnaryExpression.ts │ │ │ │ └── UpdateExpression.ts │ │ ├── Functions │ │ │ ├── FunctionCall.ts │ │ │ ├── FunctionDeclaration.ts │ │ │ └── ReturnStatement.ts │ │ ├── Identifier.ts │ │ ├── Literal.ts │ │ └── Statements │ │ │ ├── BlockStatement.ts │ │ │ ├── Choice │ │ │ └── IfStatement.ts │ │ │ ├── ExpressionStatement.ts │ │ │ ├── Loops │ │ │ ├── ForStatement.ts │ │ │ └── WhileStatement.ts │ │ │ ├── Program.ts │ │ │ ├── VariableDeclaration.ts │ │ │ └── VariableDeclarator.ts │ ├── utilities │ │ ├── Keyboard.ts │ │ ├── Mouse.ts │ │ ├── Ticker.ts │ │ ├── action.ts │ │ ├── dom.ts │ │ ├── executor.ts │ │ ├── generic.ts │ │ ├── math.ts │ │ ├── objects.ts │ │ ├── rfdc.js │ │ └── string.ts │ └── visualization │ │ ├── Focus.ts │ │ ├── Selection.ts │ │ ├── Visualization.ts │ │ └── query │ │ └── CodeSelectionQuery.ts ├── styles │ ├── editor.scss │ ├── generic │ │ ├── button.scss │ │ └── checkbox.scss │ ├── main.scss │ ├── renderer │ │ ├── action.scss │ │ ├── data │ │ │ ├── array.scss │ │ │ ├── data.scss │ │ │ ├── identifier.scss │ │ │ └── literal.scss │ │ ├── trail.scss │ │ └── view.scss │ └── themes │ │ ├── coffee.scss │ │ └── dark.scss └── vite-env.d.ts ├── test.js ├── tsconfig.json ├── tutorial ├── 01_basic.js ├── 02_if.js ├── 03_for_func.js └── 03_rec.js └── vite.config.ts /.gitignore: -------------------------------------------------------------------------------- 1 | # Logs 2 | logs 3 | *.log 4 | npm-debug.log* 5 | yarn-debug.log* 6 | yarn-error.log* 7 | pnpm-debug.log* 8 | lerna-debug.log* 9 | 10 | node_modules 11 | dist 12 | dist-ssr 13 | *.local 14 | 15 | # Editor directories and files 16 | .vscode/* 17 | !.vscode/extensions.json 18 | .idea 19 | .DS_Store 20 | *.suo 21 | *.ntvs* 22 | *.njsproj 23 | *.sln 24 | *.sw? 25 | -------------------------------------------------------------------------------- /.prettierrc: -------------------------------------------------------------------------------- 1 | { 2 | "tabWidth": 4, 3 | "semi": false, 4 | "singleQuote": true, 5 | "bracketSameLine": true, 6 | "printWidth": 120 7 | } 8 | -------------------------------------------------------------------------------- /.vscode/launch.json: -------------------------------------------------------------------------------- 1 | { 2 | // Use IntelliSense to learn about possible attributes. 3 | // Hover to view descriptions of existing attributes. 4 | // For more information, visit: https://go.microsoft.com/fwlink/?linkid=830387 5 | "version": "0.2.0", 6 | "configurations": [ 7 | { 8 | "type": "pwa-chrome", 9 | "request": "launch", 10 | "name": "Launch Chrome against localhost", 11 | "url": "http://localhost:8080", 12 | "webRoot": "${workspaceFolder}" 13 | } 14 | ] 15 | } -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # CrossCode 2 | 3 | Note: this is an experimental research prototype that is being actively developed. It may not work as expected. It supports a limited subset of JavaScript with constants, function calls and definitions, binary operators, let-bindings, if-statements, and for-loops. 4 | 5 | ## Setup instructions 6 | 1. `npm install` 7 | 2. `npm run dev` 8 | 9 | ## Using the tool 10 | 1. The default source code file that is loaded is under `./public/default.js`. 11 | 2. Sample tutorial code snippets are under the `./tutorials` folder. 12 | -------------------------------------------------------------------------------- /favicon.ico: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/hayatpur/crosscode/8a73693812fc5afd02cfa41107128f149770ec7a/favicon.ico -------------------------------------------------------------------------------- /index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | CrossCode 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 |
19 |
20 | 21 | 22 | 28 | 29 | 30 | 31 | 32 | 33 | 34 | 35 | 36 | 37 | 38 | 39 | 40 | 41 | 42 | 43 | 44 | 45 | 46 | 47 | 48 | 49 | 50 | 51 | 52 | 53 | 54 | 55 | 56 | 57 | 58 | 59 | 60 | 61 | 62 | 63 | 64 | 65 | 66 | 67 | 68 | 69 | 70 | 71 | 72 | 73 | 74 | 75 | 76 | 77 | 78 | 79 | 80 | 81 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "cross-code", 3 | "scripts": { 4 | "dev": "vite", 5 | "build": "vite build", 6 | "preview": "vite preview" 7 | }, 8 | "dependencies": { 9 | "@tweakpane/plugin-essentials": "^0.1.4", 10 | "@types/acorn": "^4.0.6", 11 | "@types/estree": "0.0.51", 12 | "acorn": "^8.7.1", 13 | "acorn-walk": "^8.2.0", 14 | "astring": "^1.8.3", 15 | "csstype": "^3.1.0", 16 | "monaco-editor": "^0.33.0", 17 | "perfect-arrows": "^0.3.7", 18 | "tweakpane": "^3.1.0", 19 | "vite-plugin-monaco-editor": "^1.1.0" 20 | }, 21 | "devDependencies": { 22 | "@tweakpane/core": "^1.1.0", 23 | "@types/svg-intersections": "^0.4.1", 24 | "@typescript-eslint/eslint-plugin": "^5.29.0", 25 | "@typescript-eslint/parser": "^5.29.0", 26 | "css-loader": "^6.7.1", 27 | "file-loader": "^6.2.0", 28 | "ignore-loader": "^0.1.2", 29 | "sass": "^1.53.0", 30 | "sass-loader": "^13.0.1", 31 | "style-loader": "^3.3.1", 32 | "ts-loader": "^9.3.1", 33 | "typescript": "^4.7.4", 34 | "url-loader": "^4.1.1", 35 | "vite": "^2.9.12" 36 | } 37 | } 38 | -------------------------------------------------------------------------------- /public/default.js: -------------------------------------------------------------------------------- 1 | // Controls: 2 | // - Left key to move one step back 3 | // - Right key to move one step forward 4 | // - Up key to move animation backward 5 | // - Down key to move animation forward 6 | // - Grab control flow cursor to move 7 | // - Control/Cmd click to get break down a step 8 | 9 | let list = [1, 2, 3, 4, 5, 6, 7, 8, 9] 10 | let n = list.length 11 | 12 | if (list[0] < list[n - 1]) { 13 | let temp = list[0] 14 | list[0] = list[n - 1] 15 | list[n - 1] = temp 16 | } 17 | -------------------------------------------------------------------------------- /src/index.ts: -------------------------------------------------------------------------------- 1 | import { ApplicationState } from './scripts/ApplicationState' 2 | import { Editor } from './scripts/editor/Editor' 3 | import { Ticker } from './scripts/utilities/Ticker' 4 | import { createCodeQuerySelector } from './scripts/visualization/query/CodeSelectionQuery' 5 | import { 6 | compileVisualization, 7 | createVisualization, 8 | setupTweakpane, 9 | tickVisualization, 10 | } from './scripts/visualization/Visualization' 11 | import './styles/main.scss' 12 | // import './styles/themes/dark.scss' 13 | 14 | function main() { 15 | ApplicationState.editor = new Editor() 16 | 17 | ApplicationState.visualization = createVisualization() 18 | 19 | setupTweakpane(ApplicationState.visualization) 20 | 21 | /* --- Live programming: update after 0.5s of activity -- */ 22 | let typingTimer: any 23 | let firstCompilation = true 24 | ApplicationState.editor.onChangeContent.add(() => { 25 | clearTimeout(typingTimer) 26 | 27 | const delay = firstCompilation ? 500 : 50 // Stagger on first execution to allow time for monaco to load 28 | typingTimer = setTimeout(() => { 29 | compileVisualization(ApplicationState.visualization, ApplicationState.editor.getValue()) 30 | }, delay) 31 | 32 | firstCompilation = false 33 | }) 34 | 35 | createCodeQuerySelector() 36 | 37 | /* ------------------------ Ticks ----------------------- */ 38 | Ticker.instance.registerTick(tick) 39 | } 40 | 41 | function tick(dt: number) { 42 | tickVisualization(ApplicationState.visualization, dt) 43 | } 44 | 45 | main() 46 | -------------------------------------------------------------------------------- /src/scripts/ApplicationState.ts: -------------------------------------------------------------------------------- 1 | import { Editor } from './editor/Editor' 2 | import { AbyssState } from './renderer/Action/Abyss' 3 | import { ActionState } from './renderer/Action/Action' 4 | import { VisualizationState } from './visualization/Visualization' 5 | 6 | export class ApplicationState { 7 | // Visualization 8 | static visualization: VisualizationState 9 | static editor: Editor 10 | 11 | // Interaction 12 | // static currentQuery: CodeQueryState 13 | 14 | // Constants 15 | static proxyHeightMultiplier = 0.4 16 | static proxyWidthMultiplier = 0.25 17 | 18 | // Collections 19 | static actions: { [id: string]: ActionState } = {} 20 | static abysses: { [id: string]: AbyssState } = {} 21 | static traceIndicators: { trace: HTMLElement; source: HTMLElement }[] = [] 22 | 23 | static Epsilon = 0.001 24 | } 25 | -------------------------------------------------------------------------------- /src/scripts/editor/Separators.ts: -------------------------------------------------------------------------------- 1 | // import { Ticker } from '../utilities/Ticker'; 2 | // import { View } from '../view/ViewRenderer'; 3 | 4 | // export class Separators { 5 | // static instance: Separators; 6 | // separatorElements: { 7 | // [viewID: string]: { 8 | // top: SVGPathElement; 9 | // bottom: SVGPathElement; 10 | // separator: SVGPathElement; 11 | // label: HTMLDivElement; 12 | // }; 13 | // } = {}; 14 | 15 | // constructor() { 16 | // Separators.instance = this; 17 | // Ticker.instance.registerTick(this.tick.bind(this)); 18 | // } 19 | 20 | // addSeparator(view: View) { 21 | // // The movement paths 22 | // const top = document.createElementNS('http://www.w3.org/2000/svg', 'path'); 23 | // top.classList.add('separator-path'); 24 | 25 | // const bottom = document.createElementNS('http://www.w3.org/2000/svg', 'path'); 26 | // bottom.classList.add('separator-path'); 27 | 28 | // // const separator = document.createElementNS('http://www.w3.org/2000/svg', 'path'); 29 | // // separator.classList.add('separator-path'); 30 | 31 | // // Add them to global svg canvas 32 | // const svg = document.getElementById('svg-canvas'); 33 | 34 | // svg.append(top); 35 | // svg.append(bottom); 36 | // // svg.append(separator); 37 | 38 | // // Store them in the separatorElements object 39 | // // this.separatorElements[view.animation.id] = { top: top, bottom: bottom, separator: separator, label: null }; 40 | // } 41 | 42 | // tick() { 43 | // for (const id of Object.keys(this.separatorElements)) { 44 | // this.updatePath(id); 45 | // } 46 | // } 47 | 48 | // updatePath(id: string) { 49 | // const view = View.views[id]; 50 | // if (view.codeSection == null || (!view.animation.playing && !view.animation.hasPlayed)) return; 51 | 52 | // const code_bbox = view.codeSection.getBoundingClientRect(); 53 | // const bbox = view.element.getBoundingClientRect(); 54 | 55 | // const top = this.separatorElements[id].top; 56 | // const bottom = this.separatorElements[id].bottom; 57 | // const separator = this.separatorElements[id].separator; 58 | 59 | // const x1 = code_bbox.right + 10; 60 | // const x2 = x1 + 50; 61 | 62 | // const y2 = bbox.top - 10; 63 | 64 | // // Update top points 65 | // // this.updatePoints(top, x1, code_bbox.top, x2, y2); 66 | // // this.updatePoints(bottom, x1, code_bbox.bottom, x2, y2); 67 | 68 | // separator.setAttribute('d', `M ${x2} ${y2} Q ${x2} ${y2} ${x2 + 1000} ${y2}`); 69 | 70 | // // if (this.separatorElements[id].label == null) { 71 | // // // Set label 72 | // // const labelEl = document.createElement('div'); 73 | // // labelEl.classList.add('view-label'); 74 | // // labelEl.innerHTML = `[${view.animation.id}] ${camelCaseToSentence( 75 | // // view.animation.getName() 76 | // // )}`; 77 | // // createViewControls(labelEl); 78 | 79 | // // document.body.append(labelEl); 80 | // // this.separatorElements[id].label = labelEl; 81 | // // } else { 82 | // // const label = this.separatorElements[id].label; 83 | // // const label_bbox = label.getBoundingClientRect(); 84 | // // label.style.left = `${x2 + bbox.width / 2 - label_bbox.width / 2}px`; 85 | // // label.style.top = `${y2 - label_bbox.height / 2}px`; 86 | // // } 87 | 88 | // // let x1 = code_bbox.right; 89 | // // let y1 = code_bbox.top; 90 | // // let x2 = bbox.left; 91 | // // let y2 = bbox.top + bbox.height / 2; 92 | // // let mx = (x1 + x2) / 2; 93 | // // let my = (y1 + y2) / 2; 94 | // // my += (y1 < y2 ? 20 : -20); 95 | // } 96 | 97 | // updatePoints(path: SVGPathElement, x1: number, y1: number, x2: number, y2: number) { 98 | // let mx = (x1 + x2) / 2; 99 | // let my = (y1 + y2) / 2; 100 | // // my += y1 < y2 ? 20 : -20; 101 | // path.setAttribute('d', `M ${x1} ${y1} Q ${mx} ${y1} ${mx} ${my} Q ${mx} ${y2} ${x2} ${y2}`); 102 | // } 103 | // } 104 | -------------------------------------------------------------------------------- /src/scripts/environment/EnvironmentState.ts: -------------------------------------------------------------------------------- 1 | import { ExecutionGraph } from '../execution/graph/ExecutionGraph' 2 | import { ExecutionNode } from '../execution/primitive/ExecutionNode' 3 | import { DataState, Transform } from '../renderer/View/Environment/data/DataState' 4 | import { ScopeType } from '../transpiler/Statements/BlockStatement' 5 | import { stringHashCode } from '../utilities/string' 6 | 7 | export enum AccessorType { 8 | ID = 'ID', 9 | Symbol = 'Symbol', 10 | Index = 'Index', 11 | Register = 'Register', 12 | } 13 | 14 | export type Accessor = { 15 | type: AccessorType 16 | value: string 17 | } 18 | 19 | export function accessorToString(accessor: Accessor): string { 20 | if (accessor.type === AccessorType.Register) { 21 | return `${accessor.type}(0x${stringHashCode(accessor.value as string) 22 | .toString() 23 | .substring(0, 4)})` 24 | } 25 | return `${accessor.type}(${accessor.value})` 26 | } 27 | 28 | export function accessorsToString(accessors: Accessor[]): string { 29 | return `${accessors.map((acc) => accessorToString(acc)).join(' > ')}` 30 | } 31 | 32 | export type IdentifierState = { 33 | name: string 34 | location: Accessor[] 35 | } 36 | 37 | export type Scope = { 38 | bindings: { [name: string]: IdentifierState } 39 | type: ScopeType 40 | } 41 | 42 | export type EnvironmentTransform = Transform & {} 43 | 44 | export type Residual = { 45 | data: DataState 46 | location: Accessor[] 47 | } 48 | 49 | export type EnvironmentState = { 50 | _type: 'EnvironmentState' 51 | 52 | // Variable name bindings 53 | scope: Scope[] 54 | 55 | // Storage data 56 | memory: { [id: string]: DataState } 57 | 58 | // Temporary data 59 | registers: { [name: string]: DataState } 60 | 61 | // Residual data (indexed with time) 62 | residuals: Residual[][] 63 | 64 | // ID map of timestamp of each data 65 | timestamps: { [id: string]: number } 66 | 67 | id: string 68 | } 69 | 70 | export type FrameInfo = { 71 | environment: EnvironmentState 72 | actionId: string 73 | overrideExecution?: ExecutionGraph | ExecutionNode 74 | } 75 | 76 | export function instanceOfEnvironment(environment: any): environment is EnvironmentState { 77 | return environment['_type'] === 'EnvironmentState' 78 | } 79 | -------------------------------------------------------------------------------- /src/scripts/environment/data.ts: -------------------------------------------------------------------------------- 1 | import * as ESTree from 'estree' 2 | import { 3 | DataState, 4 | DataType, 5 | instanceOfObjectData, 6 | ObjectDataState, 7 | PrimitiveDataState, 8 | Transform, 9 | } from '../renderer/View/Environment/data/DataState' 10 | import { clone } from '../utilities/objects' 11 | import { Accessor } from './EnvironmentState' 12 | 13 | export function createTransform(): Transform { 14 | return { 15 | styles: {}, 16 | renderOnlyStyles: {}, 17 | rendered: { 18 | x: 0, 19 | y: 0, 20 | width: 0, 21 | height: 0, 22 | }, 23 | classList: [], 24 | } 25 | } 26 | 27 | export function createPrimitiveData( 28 | type: DataType, 29 | value: string | boolean | number | Accessor[] | Function | null, 30 | id: string, 31 | frame: number = -1, 32 | builtin: boolean = false 33 | ): PrimitiveDataState { 34 | return { 35 | _type: 'PrimitiveDataState', 36 | type: type, 37 | value: value, 38 | id: id, 39 | frame: frame, 40 | builtin, 41 | } 42 | } 43 | 44 | export function createObjectData( 45 | value: object, 46 | id: string, 47 | frame: number = -1, 48 | builtin: boolean = false 49 | ): ObjectDataState { 50 | return { 51 | _type: 'ObjectDataState', 52 | value: value, 53 | id: id, 54 | frame: frame, 55 | builtin, 56 | } 57 | } 58 | 59 | export function getDataClassNames(type: DataType): string[] { 60 | const mapping = { 61 | [DataType.Literal]: ['data-literal-i'], 62 | [DataType.Array]: ['data-array-i'], 63 | } 64 | 65 | return mapping[type] ?? [] 66 | } 67 | 68 | export function cloneData( 69 | data: DataState, 70 | copyID: boolean = true, 71 | srcID: string = null 72 | ): DataState { 73 | const copy = clone(data) 74 | copy.id = copyID ? data.id : srcID 75 | copy.builtin = false 76 | return copy 77 | } 78 | 79 | export function replaceDataWith( 80 | original: DataState, 81 | data: DataState, 82 | mask: { id?: boolean; frame?: boolean } = { id: false, frame: false } 83 | ) { 84 | const originalCopy = cloneData(original) 85 | 86 | Object.assign(original, clone(data)) 87 | 88 | if (mask.id) original.id = originalCopy.id 89 | 90 | if (mask.frame) { 91 | original.frame = originalCopy.frame 92 | 93 | if (instanceOfObjectData(original)) { 94 | if (Array.isArray(original.value)) { 95 | original.value.forEach((el) => (el.frame = original.frame)) 96 | } else { 97 | Object.values(original.value).forEach( 98 | (el) => (el.frame = original.frame) 99 | ) 100 | } 101 | } 102 | } 103 | 104 | original.builtin = false 105 | } 106 | 107 | export function convertIdentifierToLiteral( 108 | data: ESTree.Identifier 109 | ): ESTree.Literal { 110 | return { 111 | ...data, 112 | type: 'Literal', 113 | value: data.name, 114 | } 115 | } 116 | -------------------------------------------------------------------------------- /src/scripts/execution/graph/ExecutionGraph.ts: -------------------------------------------------------------------------------- 1 | import { Accessor, EnvironmentState } from '../../environment/EnvironmentState' 2 | import { getEmptyNodeData } from '../execution' 3 | import { ExecutionNode, NodeData } from '../primitive/ExecutionNode' 4 | 5 | export type ExecutionRuntimeOptions = { 6 | indent?: number 7 | } 8 | 9 | export type DataInfo = { 10 | location: Accessor[] 11 | id: string 12 | } 13 | 14 | export type GlobalDataInfo = { 15 | location: { viewID: string; localLocation: Accessor[] } 16 | id: string 17 | } 18 | 19 | export type ExecutionGraph = { 20 | // Meta info 21 | _type: 'ExecutionGraph' 22 | id: string 23 | nodeData: NodeData 24 | 25 | // General info 26 | precondition: EnvironmentState | null 27 | postcondition: EnvironmentState | null 28 | isGroup: boolean 29 | 30 | // Animation info 31 | vertices: (ExecutionGraph | ExecutionNode)[] 32 | isParallel: boolean 33 | parallelStarts: number[] 34 | } 35 | 36 | export function instanceOfExecutionGraph( 37 | animation: any 38 | ): animation is ExecutionGraph { 39 | return animation._type == 'ExecutionGraph' 40 | } 41 | 42 | let __EXECUTION_GRAPH_ID = 0 43 | export function createExecutionGraph( 44 | nodeData: NodeData, 45 | options: { isGroup?: boolean } = {} 46 | ): ExecutionGraph { 47 | return { 48 | // Meta info 49 | _type: 'ExecutionGraph', 50 | id: `AG(${++__EXECUTION_GRAPH_ID})`, 51 | nodeData: options.isGroup ? getEmptyNodeData(nodeData) : nodeData, 52 | 53 | // Invariant to abstraction info 54 | precondition: null, 55 | postcondition: null, 56 | isGroup: options.isGroup || false, 57 | 58 | vertices: [], 59 | isParallel: false, 60 | parallelStarts: [], 61 | } 62 | } 63 | -------------------------------------------------------------------------------- /src/scripts/execution/primitive/Binary/LogicalExpressionEvaluate.ts: -------------------------------------------------------------------------------- 1 | import * as ESTree from 'estree' 2 | import { createPrimitiveData, replaceDataWith } from '../../../environment/data' 3 | import { 4 | addDataAt, 5 | getMemoryLocation, 6 | removeAt, 7 | resolvePath, 8 | } from '../../../environment/environment' 9 | import { 10 | Accessor, 11 | accessorsToString, 12 | EnvironmentState, 13 | } from '../../../environment/EnvironmentState' 14 | import { 15 | DataState, 16 | DataType, 17 | PrimitiveDataState, 18 | } from '../../../renderer/View/Environment/data/DataState' 19 | import { DataInfo } from '../../graph/ExecutionGraph' 20 | import { createExecutionNode, ExecutionNode } from '../ExecutionNode' 21 | 22 | export type LogicalExpressionEvaluate = ExecutionNode & { 23 | leftSpecifier: Accessor[] 24 | rightSpecifier: Accessor[] 25 | shortCircuit: boolean 26 | operator: ESTree.LogicalOperator 27 | outputRegister: Accessor[] 28 | } 29 | 30 | function apply( 31 | animation: LogicalExpressionEvaluate, 32 | environment: EnvironmentState 33 | ) { 34 | // Find left data 35 | let left = resolvePath( 36 | environment, 37 | animation.leftSpecifier, 38 | `${animation.id}_Left` 39 | ) as DataState 40 | 41 | let evaluated: DataState, right: DataState 42 | 43 | if (animation.shortCircuit) { 44 | evaluated = createPrimitiveData( 45 | DataType.Literal, 46 | (left as PrimitiveDataState).value, 47 | `${animation.id}_EvaluatedData` 48 | ) 49 | addDataAt(environment, evaluated, [], null) 50 | } else { 51 | // Find right data 52 | right = resolvePath( 53 | environment, 54 | animation.rightSpecifier, 55 | `${animation.id}_Right` 56 | ) as DataState 57 | 58 | evaluated = createPrimitiveData( 59 | DataType.Literal, 60 | eval(`${left.value}${animation.operator}${right.value}`), 61 | `${animation.id}_EvaluatedData` 62 | ) 63 | addDataAt(environment, evaluated, [], null) 64 | } 65 | 66 | // Reads and writes 67 | 68 | const leftData = { 69 | location: getMemoryLocation(environment, left).foundLocation, 70 | id: left.id, 71 | } 72 | const rightData = 73 | right == null 74 | ? null 75 | : { 76 | location: getMemoryLocation(environment, right).foundLocation, 77 | id: right.id, 78 | } 79 | const evaluatedData = { 80 | location: getMemoryLocation(environment, evaluated).foundLocation, 81 | id: evaluated.id, 82 | } 83 | 84 | computeReadAndWrites(animation, leftData, rightData, evaluatedData) 85 | 86 | // Point output to evaluated 87 | if (animation.outputRegister.length > 0) { 88 | const output = resolvePath( 89 | environment, 90 | animation.outputRegister, 91 | `${animation.id}_Floating` 92 | ) as DataState 93 | replaceDataWith( 94 | output, 95 | createPrimitiveData( 96 | DataType.ID, 97 | evaluated.id, 98 | `${animation.id}_Placed` 99 | ) 100 | ) 101 | } else { 102 | removeAt( 103 | environment, 104 | getMemoryLocation(environment, evaluated).foundLocation 105 | ) 106 | } 107 | 108 | // Clean up 109 | removeAt(environment, getMemoryLocation(environment, left).foundLocation) 110 | if (!animation.shortCircuit) { 111 | removeAt( 112 | environment, 113 | getMemoryLocation(environment, right).foundLocation 114 | ) 115 | } 116 | } 117 | 118 | function computeReadAndWrites( 119 | animation: LogicalExpressionEvaluate, 120 | leftData: DataInfo, 121 | rightData: DataInfo, 122 | evaluatedData: DataInfo 123 | ) { 124 | animation._reads = rightData == null ? [leftData] : [leftData, rightData] 125 | animation._writes = [evaluatedData] 126 | } 127 | 128 | export function logicalExpressionEvaluate( 129 | leftSpecifier: Accessor[], 130 | rightSpecifier: Accessor[], 131 | shortCircuit: boolean, 132 | operator: ESTree.LogicalOperator, 133 | outputRegister: Accessor[] 134 | ): LogicalExpressionEvaluate { 135 | return { 136 | ...createExecutionNode(), 137 | _name: 'LogicalExpressionEvaluate', 138 | 139 | name: `Logical Evaluate ${accessorsToString( 140 | leftSpecifier 141 | )} ${operator} ${accessorsToString( 142 | rightSpecifier 143 | )} onto ${accessorsToString(outputRegister)}`, 144 | 145 | // Attributes 146 | leftSpecifier, 147 | rightSpecifier, 148 | shortCircuit, 149 | operator, 150 | outputRegister, 151 | 152 | // Callbacks 153 | apply, 154 | } 155 | } 156 | -------------------------------------------------------------------------------- /src/scripts/execution/primitive/Binding/BindAnimation.ts: -------------------------------------------------------------------------------- 1 | import { createPrimitiveData } from '../../../environment/data' 2 | import { 3 | addDataAt, 4 | declareVariable, 5 | getMemoryLocation, 6 | resolvePath, 7 | } from '../../../environment/environment' 8 | import { 9 | Accessor, 10 | EnvironmentState, 11 | } from '../../../environment/EnvironmentState' 12 | import { 13 | DataState, 14 | DataType, 15 | } from '../../../renderer/View/Environment/data/DataState' 16 | import { DataInfo } from '../../graph/ExecutionGraph' 17 | import { createExecutionNode, ExecutionNode } from '../ExecutionNode' 18 | 19 | export type BindAnimation = ExecutionNode & { 20 | identifier: string 21 | existingMemorySpecifier: Accessor[] 22 | } 23 | 24 | function apply(animation: BindAnimation, environment: EnvironmentState) { 25 | let data = null 26 | let location = null 27 | 28 | // Create a reference for variable 29 | const reference = createPrimitiveData( 30 | DataType.Reference, 31 | [], 32 | `${animation.id}_Reference` 33 | ) 34 | const loc = addDataAt(environment, reference, [], `${animation.id}_Add`) 35 | 36 | if (animation.existingMemorySpecifier != null) { 37 | data = resolvePath( 38 | environment, 39 | animation.existingMemorySpecifier, 40 | `${animation.id}_Existing` 41 | ) as DataState 42 | location = getMemoryLocation(environment, data).foundLocation 43 | } else { 44 | data = createPrimitiveData( 45 | DataType.Literal, 46 | undefined, 47 | `${animation.id}_BindNew` 48 | ) 49 | location = addDataAt(environment, data, [], null) 50 | } 51 | 52 | reference.value = location 53 | 54 | declareVariable(environment, animation.identifier, loc) 55 | 56 | computeReadAndWrites( 57 | animation, 58 | { location, id: data.id }, 59 | { location: loc, id: reference.id }, 60 | { location: loc, id: animation.identifier }, 61 | animation.existingMemorySpecifier == null 62 | ) 63 | } 64 | 65 | // TODO also add variable identifier as write 66 | function computeReadAndWrites( 67 | animation: BindAnimation, 68 | data: DataInfo, 69 | reference: DataInfo, 70 | variable: DataInfo, 71 | dataCreated: boolean 72 | ) { 73 | animation._reads = [data] 74 | animation._writes = dataCreated 75 | ? [variable, reference, data] 76 | : [variable, reference] 77 | } 78 | 79 | export function bindAnimation( 80 | identifier: string, 81 | existingMemorySpecifier: Accessor[] = null 82 | ): BindAnimation { 83 | return { 84 | ...createExecutionNode(null), 85 | _name: 'BindAnimation', 86 | 87 | // name: `Bind Variable (${identifier}), with data at ${accessorsToString( 88 | // existingMemorySpecifier ?? [] 89 | // )}`, 90 | name: `Bind Variable (${identifier})`, 91 | 92 | // Attributes 93 | identifier, 94 | existingMemorySpecifier, 95 | 96 | // Callbacks 97 | apply, 98 | } 99 | } 100 | -------------------------------------------------------------------------------- /src/scripts/execution/primitive/Binding/BindFunctionAnimation.ts: -------------------------------------------------------------------------------- 1 | import * as ESTree from 'estree' 2 | import { createPrimitiveData } from '../../../environment/data' 3 | import { addDataAt, declareVariable } from '../../../environment/environment' 4 | import { EnvironmentState } from '../../../environment/EnvironmentState' 5 | import { DataType } from '../../../renderer/View/Environment/data/DataState' 6 | import { DataInfo } from '../../graph/ExecutionGraph' 7 | import { createExecutionNode, ExecutionNode } from '../ExecutionNode' 8 | 9 | export type BindFunctionAnimation = ExecutionNode & { 10 | identifier: string 11 | ast: ESTree.FunctionDeclaration 12 | } 13 | 14 | function apply( 15 | animation: BindFunctionAnimation, 16 | environment: EnvironmentState 17 | ) { 18 | // Create a reference for variable 19 | const reference = createPrimitiveData( 20 | DataType.Reference, 21 | [], 22 | `${animation.id}_ReferenceFunction` 23 | ) 24 | const referenceLocation = addDataAt( 25 | environment, 26 | reference, 27 | [], 28 | `${animation.id}_AddFunction` 29 | ) 30 | 31 | const data = createPrimitiveData( 32 | DataType.Function, 33 | JSON.stringify(animation.ast), 34 | `${animation.id}_BindFunctionNew` 35 | ) 36 | const location = addDataAt(environment, data, [], null) 37 | 38 | reference.value = location 39 | 40 | declareVariable(environment, animation.identifier, referenceLocation) 41 | computeReadAndWrites( 42 | animation, 43 | { location, id: data.id }, 44 | { location, id: animation.identifier } 45 | ) 46 | } 47 | 48 | function computeReadAndWrites( 49 | animation: BindFunctionAnimation, 50 | funcData: DataInfo, 51 | funcIdentifier: DataInfo 52 | ) { 53 | animation._reads = [] 54 | animation._writes = [funcData, funcIdentifier] 55 | } 56 | 57 | export function bindFunctionAnimation( 58 | identifier: string, 59 | ast: ESTree.FunctionDeclaration = null 60 | ): BindFunctionAnimation { 61 | return { 62 | ...createExecutionNode(null), 63 | _name: 'BindFunctionAnimation', 64 | name: `Bind Function (${identifier})`, 65 | 66 | // Attributes 67 | identifier, 68 | ast, 69 | 70 | // Callbacks 71 | apply, 72 | } 73 | } 74 | -------------------------------------------------------------------------------- /src/scripts/execution/primitive/Container/ArrayStartAnimation.ts: -------------------------------------------------------------------------------- 1 | import { 2 | createObjectData, 3 | createPrimitiveData, 4 | replaceDataWith, 5 | } from '../../../environment/data' 6 | import { addDataAt, resolvePath } from '../../../environment/environment' 7 | import { 8 | Accessor, 9 | accessorsToString, 10 | EnvironmentState, 11 | } from '../../../environment/EnvironmentState' 12 | import { 13 | DataState, 14 | DataType, 15 | } from '../../../renderer/View/Environment/data/DataState' 16 | import { DataInfo } from '../../graph/ExecutionGraph' 17 | import { createExecutionNode, ExecutionNode } from '../ExecutionNode' 18 | 19 | export type ArrayStartAnimation = ExecutionNode & { 20 | dataSpecifier: Accessor[] 21 | } 22 | 23 | function apply(animation: ArrayStartAnimation, environment: EnvironmentState) { 24 | // Create a new array somewhere in memory 25 | const data = createObjectData([], `${animation.id}_CreateArray`) 26 | 27 | const loc = addDataAt(environment, data, [], null) 28 | 29 | computeReadAndWrites(animation, { 30 | location: loc, 31 | id: data.id, 32 | }) 33 | 34 | // Point the output register to the newly created data 35 | const outputRegister = resolvePath( 36 | environment, 37 | animation.dataSpecifier, 38 | `${animation.id}_Floating` 39 | ) as DataState 40 | replaceDataWith( 41 | outputRegister, 42 | createPrimitiveData( 43 | DataType.ID, 44 | data.id, 45 | `${animation.id}_OutputRegister` 46 | ) 47 | ) 48 | } 49 | 50 | function computeReadAndWrites(animation: ArrayStartAnimation, data: DataInfo) { 51 | animation._reads = [data] 52 | animation._writes = [data] 53 | } 54 | 55 | export function arrayStartAnimation( 56 | dataSpecifier: Accessor[] 57 | ): ArrayStartAnimation { 58 | return { 59 | ...createExecutionNode(null), 60 | 61 | _name: 'ArrayStartAnimation', 62 | 63 | name: `Initialize array at ${accessorsToString(dataSpecifier)}`, 64 | 65 | // Attributes 66 | dataSpecifier, 67 | 68 | // Callbacks 69 | apply, 70 | } 71 | } 72 | -------------------------------------------------------------------------------- /src/scripts/execution/primitive/Container/ObjectStartAnimation.ts: -------------------------------------------------------------------------------- 1 | import { 2 | createObjectData, 3 | createPrimitiveData, 4 | replaceDataWith, 5 | } from '../../../environment/data' 6 | import { addDataAt, resolvePath } from '../../../environment/environment' 7 | import { 8 | Accessor, 9 | accessorsToString, 10 | EnvironmentState, 11 | } from '../../../environment/EnvironmentState' 12 | import { 13 | DataState, 14 | DataType, 15 | } from '../../../renderer/View/Environment/data/DataState' 16 | import { DataInfo } from '../../graph/ExecutionGraph' 17 | import { createExecutionNode, ExecutionNode } from '../ExecutionNode' 18 | 19 | export type ObjectStartAnimation = ExecutionNode & { 20 | dataSpecifier: Accessor[] 21 | } 22 | 23 | function apply(animation: ObjectStartAnimation, environment: EnvironmentState) { 24 | // Create a new array somewhere in memory 25 | const data = createObjectData({}, `${animation.id}_CreateObject`) 26 | 27 | const loc = addDataAt(environment, data, [], null) 28 | 29 | computeReadAndWrites(animation, { 30 | location: loc, 31 | id: data.id, 32 | }) 33 | 34 | // Point the output register to the newly created data 35 | const outputRegister = resolvePath( 36 | environment, 37 | animation.dataSpecifier, 38 | `${animation.id}_Floating` 39 | ) as DataState 40 | replaceDataWith( 41 | outputRegister, 42 | createPrimitiveData( 43 | DataType.ID, 44 | data.id, 45 | `${animation.id}_OutputRegister` 46 | ) 47 | ) 48 | } 49 | 50 | function computeReadAndWrites(animation: ObjectStartAnimation, data: DataInfo) { 51 | animation._reads = [data] 52 | animation._writes = [data] 53 | } 54 | 55 | export function objectStartAnimation( 56 | dataSpecifier: Accessor[] 57 | ): ObjectStartAnimation { 58 | return { 59 | ...createExecutionNode(null), 60 | 61 | _name: 'ObjectStartAnimation', 62 | 63 | name: `Initialize object at ${accessorsToString(dataSpecifier)}`, 64 | 65 | // Attributes 66 | dataSpecifier, 67 | 68 | // Callbacks 69 | apply, 70 | } 71 | } 72 | -------------------------------------------------------------------------------- /src/scripts/execution/primitive/Data/ConsumeDataAnimation.ts: -------------------------------------------------------------------------------- 1 | import { 2 | getMemoryLocation, 3 | removeAt, 4 | resolvePath, 5 | } from '../../../environment/environment' 6 | import { 7 | Accessor, 8 | EnvironmentState, 9 | } from '../../../environment/EnvironmentState' 10 | import { DataState } from '../../../renderer/View/Environment/data/DataState' 11 | import { DataInfo } from '../../graph/ExecutionGraph' 12 | import { createExecutionNode, ExecutionNode } from '../ExecutionNode' 13 | 14 | export type ConsumeDataAnimation = ExecutionNode & { 15 | register: Accessor[] 16 | } 17 | 18 | function apply(animation: ConsumeDataAnimation, environment: EnvironmentState) { 19 | // Get data 20 | const data = resolvePath( 21 | environment, 22 | animation.register, 23 | `${animation.id}_Property`, 24 | null, 25 | { 26 | noResolvingReference: true, 27 | } 28 | ) as DataState 29 | 30 | const dataLocation = getMemoryLocation(environment, data).foundLocation 31 | 32 | // Consume data 33 | removeAt(environment, dataLocation) 34 | 35 | computeReadAndWrites(animation, { 36 | location: dataLocation, 37 | id: data.id, 38 | }) 39 | } 40 | 41 | function computeReadAndWrites(animation: ConsumeDataAnimation, data: DataInfo) { 42 | animation._reads = [] 43 | animation._writes = [data] 44 | } 45 | 46 | export function consumeDataAnimation( 47 | register: Accessor[] 48 | ): ConsumeDataAnimation { 49 | return { 50 | ...createExecutionNode(null), 51 | _name: 'ConsumeDataAnimation', 52 | 53 | name: 'ConsumeDataAnimation', 54 | 55 | // Attributes 56 | register, 57 | 58 | // Callbacks 59 | apply, 60 | } 61 | } 62 | -------------------------------------------------------------------------------- /src/scripts/execution/primitive/Data/CopyDataAnimation.ts: -------------------------------------------------------------------------------- 1 | import { cloneData, createPrimitiveData, replaceDataWith } from '../../../environment/data' 2 | import { addDataAt, getMemoryLocation, resolvePath } from '../../../environment/environment' 3 | import { Accessor, accessorsToString, AccessorType, EnvironmentState } from '../../../environment/EnvironmentState' 4 | import { DataState, DataType } from '../../../renderer/View/Environment/data/DataState' 5 | import { DataInfo } from '../../graph/ExecutionGraph' 6 | import { createExecutionNode, ExecutionNode } from '../ExecutionNode' 7 | 8 | export type CopyDataAnimation = ExecutionNode & { 9 | dataSpecifier: Accessor[] 10 | outputRegister: Accessor[] 11 | hardCopy: boolean 12 | } 13 | 14 | function apply(animation: CopyDataAnimation, environment: EnvironmentState) { 15 | const data = resolvePath(environment, animation.dataSpecifier, `${animation.id}_Data`) as DataState 16 | const copy = cloneData(data, false, `${animation.id}_Copy`) 17 | const location = addDataAt(environment, copy, [], null) 18 | 19 | // Put it in the floating stack 20 | const register = resolvePath(environment, animation.outputRegister, `${animation.id}_Floating`) as DataState 21 | replaceDataWith(register, createPrimitiveData(DataType.ID, copy.id, `${animation.id}_Floating`)) 22 | 23 | if (animation.hardCopy) { 24 | data.value = undefined 25 | } 26 | 27 | computeReadAndWrites( 28 | animation, 29 | { 30 | location: getMemoryLocation(environment, data).foundLocation, 31 | id: data.id, 32 | }, 33 | { location, id: copy.id } 34 | ) 35 | 36 | if (animation.dataSpecifier[0].type == AccessorType.Symbol) { 37 | animation._reads.push({ 38 | id: animation.dataSpecifier[0].value, 39 | location: animation.dataSpecifier, 40 | }) 41 | } 42 | } 43 | 44 | function computeReadAndWrites(animation: CopyDataAnimation, original: DataInfo, copy: DataInfo) { 45 | animation._reads = [original] 46 | animation._writes = [copy] 47 | } 48 | 49 | export function copyDataAnimation( 50 | dataSpecifier: Accessor[], 51 | outputRegister: Accessor[], 52 | hardCopy: boolean = false 53 | ): CopyDataAnimation { 54 | return { 55 | ...createExecutionNode(null), 56 | _name: 'CopyDataAnimation', 57 | 58 | // name: `Copy ${accessorsToString(dataSpecifier)} to ${accessorsToString(outputRegister)}`, 59 | name: `Copy ${accessorsToString(dataSpecifier)}`, 60 | 61 | // Attributes 62 | dataSpecifier, 63 | outputRegister, 64 | hardCopy, 65 | 66 | // Callbacks 67 | apply, 68 | } 69 | } 70 | -------------------------------------------------------------------------------- /src/scripts/execution/primitive/Data/CopyReferenceAnimation.ts: -------------------------------------------------------------------------------- 1 | import { createPrimitiveData, replaceDataWith } from '../../../environment/data' 2 | import { 3 | addDataAt, 4 | getMemoryLocation, 5 | resolvePath, 6 | } from '../../../environment/environment' 7 | import { 8 | Accessor, 9 | accessorsToString, 10 | EnvironmentState, 11 | } from '../../../environment/EnvironmentState' 12 | import { 13 | DataState, 14 | DataType, 15 | } from '../../../renderer/View/Environment/data/DataState' 16 | import { DataInfo } from '../../graph/ExecutionGraph' 17 | import { createExecutionNode, ExecutionNode } from '../ExecutionNode' 18 | 19 | export type CopyReferenceAnimation = ExecutionNode & { 20 | dataSpecifier: Accessor[] 21 | outputRegister: Accessor[] 22 | } 23 | 24 | function apply( 25 | animation: CopyReferenceAnimation, 26 | environment: EnvironmentState 27 | ) { 28 | const data = resolvePath( 29 | environment, 30 | animation.dataSpecifier, 31 | `${animation.id}_Data` 32 | ) as DataState 33 | const reference = createPrimitiveData( 34 | DataType.Reference, 35 | getMemoryLocation(environment, data).foundLocation, 36 | `${animation.id}_Reference` 37 | ) 38 | const loc = addDataAt(environment, reference, [], null) 39 | 40 | computeReadAndWrites( 41 | animation, 42 | { 43 | location: getMemoryLocation(environment, data).foundLocation, 44 | id: data.id, 45 | }, 46 | { location: loc, id: reference.id } 47 | ) 48 | 49 | // Put it in the floating stack 50 | const register = resolvePath( 51 | environment, 52 | animation.outputRegister, 53 | `${animation.id}_FloatingRegister` 54 | ) as DataState 55 | replaceDataWith( 56 | register, 57 | createPrimitiveData( 58 | DataType.ID, 59 | reference.id, 60 | `${animation.id}_Floating` 61 | ) 62 | ) 63 | } 64 | 65 | function computeReadAndWrites( 66 | animation: CopyReferenceAnimation, 67 | original: DataInfo, 68 | copy: DataInfo 69 | ) { 70 | animation._reads = [original] 71 | animation._writes = [copy] 72 | } 73 | 74 | export function copyReferenceAnimation( 75 | dataSpecifier: Accessor[], 76 | outputRegister: Accessor[] 77 | ): CopyReferenceAnimation { 78 | return { 79 | ...createExecutionNode(null), 80 | _name: 'CopyReferenceAnimation', 81 | 82 | name: `CopyReference ${accessorsToString( 83 | dataSpecifier 84 | )} to ${accessorsToString(outputRegister)}`, 85 | 86 | // Attributes 87 | dataSpecifier, 88 | outputRegister, 89 | 90 | // Callbacks 91 | apply, 92 | } 93 | } 94 | -------------------------------------------------------------------------------- /src/scripts/execution/primitive/Data/CreateLiteralAnimation.ts: -------------------------------------------------------------------------------- 1 | import { createPrimitiveData, replaceDataWith } from '../../../environment/data' 2 | import { addDataAt, resolvePath } from '../../../environment/environment' 3 | import { 4 | Accessor, 5 | accessorsToString, 6 | EnvironmentState, 7 | } from '../../../environment/EnvironmentState' 8 | import { 9 | DataState, 10 | DataType, 11 | } from '../../../renderer/View/Environment/data/DataState' 12 | import { DataInfo } from '../../graph/ExecutionGraph' 13 | import { createExecutionNode, ExecutionNode } from '../ExecutionNode' 14 | 15 | export type CreateLiteralAnimation = ExecutionNode & { 16 | value: string | number | bigint | boolean | RegExp 17 | locationHint: Accessor[] 18 | outputRegister: Accessor[] 19 | } 20 | 21 | /** 22 | * Puts data into context.output register. 23 | * @param animation 24 | * @param view 25 | * @param options 26 | */ 27 | function apply( 28 | animation: CreateLiteralAnimation, 29 | environment: EnvironmentState 30 | ) { 31 | const data = createPrimitiveData( 32 | DataType.Literal, 33 | animation.value as number | string | boolean, 34 | `${animation.id}_Create` 35 | ) 36 | 37 | // Add the newly created data to environment 38 | const loc = addDataAt(environment, data, [], null) 39 | 40 | computeReadAndWrites(animation, { 41 | location: loc, 42 | id: data.id, 43 | }) 44 | 45 | // Point the output register to the newly created data 46 | const outputRegister = resolvePath( 47 | environment, 48 | animation.outputRegister, 49 | `${animation.id}_Floating` 50 | ) as DataState 51 | replaceDataWith( 52 | outputRegister, 53 | createPrimitiveData( 54 | DataType.ID, 55 | data.id, 56 | `${animation.id}_OutputRegister` 57 | ) 58 | ) 59 | } 60 | 61 | function computeReadAndWrites( 62 | animation: CreateLiteralAnimation, 63 | data: DataInfo 64 | ) { 65 | animation._reads = [] 66 | animation._writes = [data] 67 | } 68 | 69 | export function createLiteralAnimation( 70 | value: string | number | bigint | boolean | RegExp, 71 | outputRegister: Accessor[], 72 | locationHint: Accessor[] 73 | ): CreateLiteralAnimation { 74 | return { 75 | ...createExecutionNode(null), 76 | _name: 'CreateLiteralAnimation', 77 | name: `Create Literal ${value} at ${accessorsToString(outputRegister)}`, 78 | 79 | // Attributes 80 | value, 81 | outputRegister, 82 | locationHint, 83 | 84 | // Callbacks 85 | apply, 86 | } 87 | } 88 | -------------------------------------------------------------------------------- /src/scripts/execution/primitive/Data/FindVariableAnimation.ts: -------------------------------------------------------------------------------- 1 | import { createPrimitiveData, replaceDataWith } from '../../../environment/data' 2 | import { getMemoryLocation, resolvePath } from '../../../environment/environment' 3 | import { Accessor, AccessorType, EnvironmentState } from '../../../environment/EnvironmentState' 4 | import { DataState, DataType } from '../../../renderer/View/Environment/data/DataState' 5 | import { createExecutionNode, ExecutionNode } from '../ExecutionNode' 6 | 7 | export type FindVariableAnimation = ExecutionNode & { 8 | identifier: string 9 | outputRegister: Accessor[] 10 | } 11 | 12 | function apply(animation: FindVariableAnimation, environment: EnvironmentState) { 13 | const reference = resolvePath( 14 | environment, 15 | [{ type: AccessorType.Symbol, value: animation.identifier }], 16 | null, 17 | null, 18 | { noResolvingReference: true } 19 | ) 20 | 21 | // Put it in the floating stack 22 | const register = resolvePath(environment, animation.outputRegister, `${animation.id}_FloatingRegister`) as DataState 23 | 24 | replaceDataWith(register, createPrimitiveData(DataType.ID, reference.id, `${animation.id}_Floating`)) 25 | 26 | computeReadAndWrites(animation, { 27 | location: getMemoryLocation(environment, reference as DataState).foundLocation!, 28 | id: reference.id, 29 | }) 30 | } 31 | 32 | /** 33 | * TODO 34 | */ 35 | function computeReadAndWrites(animation: FindVariableAnimation, ref: DataInfo) { 36 | animation._reads = [ref] 37 | animation._writes = [] 38 | } 39 | 40 | export function findVariableAnimation(identifier: string, outputRegister: Accessor[]): FindVariableAnimation { 41 | return { 42 | ...createExecutionNode(null), 43 | _name: 'FindVariableAnimation', 44 | 45 | name: `Locating ${identifier}`, 46 | 47 | // Attributes 48 | identifier, 49 | outputRegister, 50 | 51 | // Callbacks 52 | apply, 53 | } 54 | } 55 | -------------------------------------------------------------------------------- /src/scripts/execution/primitive/Data/FloatAnimation.ts: -------------------------------------------------------------------------------- 1 | import { 2 | getMemoryLocation, 3 | resolvePath, 4 | } from '../../../environment/environment' 5 | import { 6 | Accessor, 7 | EnvironmentState, 8 | } from '../../../environment/EnvironmentState' 9 | import { DataState } from '../../../renderer/View/Environment/data/DataState' 10 | import { DataInfo } from '../../graph/ExecutionGraph' 11 | import { createExecutionNode, ExecutionNode } from '../ExecutionNode' 12 | 13 | export type FloatAnimation = ExecutionNode & { 14 | dataSpecifier: Accessor[] 15 | } 16 | 17 | function apply(animation: FloatAnimation, environment: EnvironmentState) { 18 | const data = resolvePath( 19 | environment, 20 | animation.dataSpecifier, 21 | null 22 | ) as DataState 23 | 24 | computeReadAndWrites(animation, { 25 | location: getMemoryLocation(environment, data).foundLocation, 26 | id: data.id, 27 | }) 28 | } 29 | 30 | function computeReadAndWrites(animation: FloatAnimation, data: DataInfo) { 31 | animation._reads = [data] 32 | animation._writes = [] 33 | } 34 | 35 | export function floatAnimation(dataSpecifier: Accessor[]): FloatAnimation { 36 | return { 37 | ...createExecutionNode(null), 38 | _name: 'FloatAnimation', 39 | 40 | name: 'FloatAnimation', 41 | 42 | // Attributes 43 | dataSpecifier, 44 | 45 | // Callbacks 46 | apply, 47 | } 48 | } 49 | -------------------------------------------------------------------------------- /src/scripts/execution/primitive/Data/GetMember.ts: -------------------------------------------------------------------------------- 1 | import { 2 | cloneData, 3 | createPrimitiveData, 4 | replaceDataWith, 5 | } from '../../../environment/data' 6 | import { 7 | addDataAt, 8 | getMemoryLocation, 9 | removeAt, 10 | resolvePath, 11 | } from '../../../environment/environment' 12 | import { 13 | Accessor, 14 | EnvironmentState, 15 | } from '../../../environment/EnvironmentState' 16 | import { 17 | DataState, 18 | DataType, 19 | instanceOfData, 20 | } from '../../../renderer/View/Environment/data/DataState' 21 | import { DataInfo } from '../../graph/ExecutionGraph' 22 | import { createExecutionNode, ExecutionNode } from '../ExecutionNode' 23 | 24 | export type GetMember = ExecutionNode & { 25 | objectRegister: Accessor[] 26 | propertyRegister?: Accessor[] // Only if computed 27 | outputRegister: Accessor[] 28 | } 29 | 30 | function apply(animation: GetMember, environment: EnvironmentState) { 31 | // Get object 32 | const object = resolvePath( 33 | environment, 34 | animation.objectRegister, 35 | `${animation.id}_Object` 36 | ) as DataState 37 | 38 | // Get property 39 | const property = resolvePath( 40 | environment, 41 | animation.propertyRegister, 42 | `${animation.id}_Property` 43 | ) as DataState 44 | const propertyLocation = getMemoryLocation( 45 | environment, 46 | property 47 | ).foundLocation 48 | 49 | // Index into it 50 | const original = (object.value as DataState[])[ 51 | property.value as number | string 52 | ] 53 | 54 | let copy: DataState 55 | 56 | if (instanceOfData(original)) { 57 | // Create a copy of data 58 | copy = cloneData(original, false, `${animation.id}_Copy`) 59 | } else { 60 | // Create a new data 61 | copy = createPrimitiveData( 62 | DataType.Literal, 63 | original, 64 | `${animation.id}_Copy` 65 | ) 66 | } 67 | 68 | const location = addDataAt(environment, copy, [], null) 69 | 70 | // Consume property 71 | removeAt( 72 | environment, 73 | getMemoryLocation(environment, property).foundLocation 74 | ) 75 | 76 | // Remove object (reference) 77 | const objectReference = resolvePath( 78 | environment, 79 | animation.objectRegister, 80 | `${animation.id}_Object`, 81 | null, 82 | { 83 | noResolvingReference: true, 84 | } 85 | ) as DataState 86 | const objectLocation = getMemoryLocation( 87 | environment, 88 | objectReference 89 | ).foundLocation 90 | 91 | removeAt( 92 | environment, 93 | getMemoryLocation(environment, objectReference).foundLocation, 94 | { 95 | noResolvingReference: true, 96 | } 97 | ) 98 | 99 | const propertyData = { 100 | location: propertyLocation, 101 | id: property.id, 102 | } 103 | 104 | const objectData = { 105 | location: objectLocation, 106 | id: objectReference.id, 107 | } 108 | 109 | const copyData = { location, id: copy.id } 110 | 111 | if (instanceOfData(original)) { 112 | computeReadAndWrites(animation, objectData, propertyData, copyData, { 113 | location: getMemoryLocation(environment, original).foundLocation, 114 | id: original.id, 115 | }) 116 | } else { 117 | computeReadAndWrites(animation, objectData, propertyData, copyData) 118 | } 119 | 120 | // Point the output register to the newly created data 121 | const outputRegister = resolvePath( 122 | environment, 123 | animation.outputRegister, 124 | `${animation.id}_Floating` 125 | ) as DataState 126 | replaceDataWith( 127 | outputRegister, 128 | createPrimitiveData( 129 | DataType.ID, 130 | copy.id, 131 | `${animation.id}_OutputRegister` 132 | ) 133 | ) 134 | } 135 | 136 | function computeReadAndWrites( 137 | animation: GetMember, 138 | obj: DataInfo, 139 | property: DataInfo, 140 | copy: DataInfo, 141 | original: DataInfo = null 142 | ) { 143 | animation._reads = 144 | original != null ? [original, obj, property] : [obj, property] 145 | animation._writes = [copy, obj] 146 | } 147 | 148 | export function getMember( 149 | objectRegister: Accessor[], 150 | propertyRegister: Accessor[], 151 | outputRegister: Accessor[] 152 | ): GetMember { 153 | return { 154 | ...createExecutionNode(null), 155 | _name: 'GetMember', 156 | 157 | name: 'GetMember', 158 | 159 | // Attributes 160 | objectRegister, 161 | propertyRegister, 162 | outputRegister, 163 | 164 | // Callbacks 165 | apply, 166 | } 167 | } 168 | -------------------------------------------------------------------------------- /src/scripts/execution/primitive/Data/MoveAndPlaceAnimation.ts: -------------------------------------------------------------------------------- 1 | import { replaceDataWith } from '../../../environment/data' 2 | import { 3 | getMemoryLocation, 4 | removeAt, 5 | resolvePath, 6 | } from '../../../environment/environment' 7 | import { 8 | Accessor, 9 | accessorsToString, 10 | EnvironmentState, 11 | instanceOfEnvironment, 12 | } from '../../../environment/EnvironmentState' 13 | import { 14 | DataState, 15 | instanceOfData, 16 | } from '../../../renderer/View/Environment/data/DataState' 17 | import { DataInfo } from '../../graph/ExecutionGraph' 18 | import { createExecutionNode, ExecutionNode } from '../ExecutionNode' 19 | 20 | /** 21 | * Move and place animation or just place if the from 22 | * is not defined. 23 | */ 24 | export type MoveAndPlaceAnimation = Omit & { 25 | inputSpecifier: Accessor[] 26 | outputSpecifier: Accessor[] 27 | 28 | apply: ( 29 | animation: MoveAndPlaceAnimation, 30 | environment: EnvironmentState 31 | ) => void 32 | } 33 | 34 | function apply( 35 | animation: MoveAndPlaceAnimation, 36 | environment: EnvironmentState 37 | ) { 38 | const from = resolvePath( 39 | environment, 40 | animation.inputSpecifier, 41 | null, 42 | null, 43 | { 44 | noResolvingReference: true, 45 | } 46 | ) as DataState 47 | 48 | const to = resolvePath( 49 | environment, 50 | animation.outputSpecifier, 51 | `${animation.id}_To(${accessorsToString(animation.outputSpecifier)})` 52 | ) 53 | 54 | const fromData = { 55 | location: getMemoryLocation(environment, from).foundLocation, 56 | id: from.id, 57 | } 58 | 59 | if (instanceOfEnvironment(to)) { 60 | computeReadAndWrites(animation, fromData, null) 61 | } else { 62 | computeReadAndWrites(animation, fromData, { 63 | location: getMemoryLocation(environment, to).foundLocation, 64 | id: to.id, 65 | }) 66 | } 67 | 68 | if (instanceOfData(to)) { 69 | removeAt( 70 | environment, 71 | getMemoryLocation(environment, from).foundLocation 72 | ) 73 | replaceDataWith(to, from, { frame: true, id: true }) 74 | } 75 | } 76 | 77 | function computeReadAndWrites( 78 | animation: MoveAndPlaceAnimation, 79 | inputData: DataInfo, 80 | outputData: DataInfo | null 81 | ) { 82 | animation._reads = [inputData] 83 | animation._writes = outputData ? [outputData, inputData] : [inputData] 84 | } 85 | 86 | export function moveAndPlaceAnimation( 87 | inputSpecifier: Accessor[], 88 | outputSpecifier: Accessor[] 89 | ): MoveAndPlaceAnimation { 90 | return { 91 | ...createExecutionNode(), 92 | _name: 'MoveAndPlaceAnimation', 93 | 94 | name: `Move data at ${accessorsToString( 95 | inputSpecifier 96 | )} onto ${accessorsToString(outputSpecifier)}`, 97 | 98 | // Attributes 99 | inputSpecifier, 100 | outputSpecifier, 101 | 102 | // Callbacks 103 | apply, 104 | } 105 | } 106 | -------------------------------------------------------------------------------- /src/scripts/execution/primitive/Data/UpdateAnimation.ts: -------------------------------------------------------------------------------- 1 | import * as ESTree from 'estree' 2 | import { 3 | getMemoryLocation, 4 | resolvePath, 5 | } from '../../../environment/environment' 6 | import { 7 | Accessor, 8 | EnvironmentState, 9 | } from '../../../environment/EnvironmentState' 10 | import { DataState } from '../../../renderer/View/Environment/data/DataState' 11 | import { DataInfo } from '../../graph/ExecutionGraph' 12 | import { createExecutionNode, ExecutionNode } from '../ExecutionNode' 13 | 14 | export type UpdateAnimation = ExecutionNode & { 15 | dataSpecifier: Accessor[] 16 | operator: ESTree.UpdateOperator 17 | } 18 | 19 | function apply(animation: UpdateAnimation, environment: EnvironmentState) { 20 | const data = resolvePath( 21 | environment, 22 | animation.dataSpecifier, 23 | `${animation.id}_Data` 24 | ) as DataState 25 | 26 | switch (animation.operator) { 27 | case '++': 28 | data.value = (data.value as number) + 1 29 | break 30 | case '--': 31 | data.value = (data.value as number) - 1 32 | break 33 | default: 34 | console.warn('Unrecognized update operator', animation.operator) 35 | } 36 | 37 | computeReadAndWrites(animation, { 38 | id: data.id, 39 | location: getMemoryLocation(environment, data).foundLocation, 40 | }) 41 | } 42 | 43 | function computeReadAndWrites(animation: UpdateAnimation, data: DataInfo) { 44 | animation._reads = [data] 45 | animation._writes = [data] 46 | } 47 | 48 | export function updateAnimation( 49 | dataSpecifier: Accessor[], 50 | operator: ESTree.UpdateOperator 51 | ): UpdateAnimation { 52 | return { 53 | ...createExecutionNode(null), 54 | _name: 'UpdateAnimation', 55 | 56 | name: 'UpdateAnimation', 57 | 58 | // Attributes 59 | dataSpecifier, 60 | operator, 61 | 62 | // Callbacks 63 | apply, 64 | } 65 | } 66 | -------------------------------------------------------------------------------- /src/scripts/execution/primitive/ExecutionNode.ts: -------------------------------------------------------------------------------- 1 | import * as ESTree from 'estree' 2 | import { Accessor, EnvironmentState } from '../../environment/EnvironmentState' 3 | import { DataInfo } from '../graph/ExecutionGraph' 4 | 5 | export type NodeData = { 6 | location?: ESTree.SourceLocation 7 | type?: string 8 | preLabel?: string 9 | hasLineBreak?: boolean 10 | } 11 | 12 | export type ChunkNodeData = NodeData & {} 13 | 14 | export enum ControlOutput { 15 | None = 'None', 16 | Break = 'Break', 17 | Continue = 'Continue', 18 | Return = 'Return', 19 | } 20 | 21 | export type ControlOutputData = { 22 | output: ControlOutput 23 | } 24 | 25 | export type ReturnData = { 26 | frame: number 27 | register: Accessor[] 28 | environmentID: string 29 | } 30 | 31 | export type ExecutionContext = { 32 | locationHint?: Accessor[] // Hint of where to place data (for aesthetic reasons) 33 | outputRegister?: Accessor[] // Register to place data at 34 | feed?: boolean // Puts the location of data instead of data itself - used for feeding (assignment / declaration)' 35 | args?: Accessor[][] // Arguments to pass to the function 36 | object?: Accessor[] // Object to pass to the function 37 | controlOutput?: ControlOutputData 38 | returnData?: ReturnData // Register to place the return value at 39 | } 40 | 41 | export type ExecutionNode = { 42 | _type: 'ExecutionNode' 43 | _name: string // Name of the node 44 | 45 | name: string // Name of operation 46 | id: string 47 | 48 | precondition: EnvironmentState | null 49 | postcondition: EnvironmentState | null 50 | 51 | _reads: DataInfo[] | null 52 | _writes: DataInfo[] | null 53 | nodeData: NodeData 54 | 55 | apply: (animation: ExecutionNode, environment: EnvironmentState) => void 56 | } 57 | 58 | export function instanceOfExecutionNode(animation: any): animation is ExecutionNode { 59 | return animation._type == 'ExecutionNode' 60 | } 61 | 62 | let __EXECUTION_NODE_ID = 0 63 | export function createExecutionNode(nodeData: NodeData = {}): ExecutionNode { 64 | return { 65 | _type: 'ExecutionNode', 66 | 67 | _name: 'ExecutionNode', 68 | name: 'Animation Node', 69 | 70 | id: `AN(${++__EXECUTION_NODE_ID})`, 71 | precondition: null, 72 | postcondition: null, 73 | 74 | _reads: null, 75 | _writes: null, 76 | 77 | nodeData, 78 | 79 | apply: () => console.warn('[ExecutionNode] Non-implemented apply callback'), 80 | } 81 | } 82 | -------------------------------------------------------------------------------- /src/scripts/execution/primitive/Functions/Native/Array/ArrayPushAnimation.ts: -------------------------------------------------------------------------------- 1 | import { cloneData } from '../../../../../environment/data' 2 | import { 3 | getMemoryLocation, 4 | resolvePath, 5 | } from '../../../../../environment/environment' 6 | import { 7 | Accessor, 8 | EnvironmentState, 9 | } from '../../../../../environment/EnvironmentState' 10 | import { DataState } from '../../../../../renderer/View/Environment/data/DataState' 11 | import { DataInfo } from '../../../../graph/ExecutionGraph' 12 | import { createExecutionNode, ExecutionNode } from '../../../ExecutionNode' 13 | 14 | export type ArrayPushAnimation = ExecutionNode & { 15 | object: Accessor[] 16 | args: Accessor[][] 17 | } 18 | 19 | function apply(animation: ArrayPushAnimation, environment: EnvironmentState) { 20 | const object = resolvePath( 21 | environment, 22 | animation.object, 23 | `${animation.id}_Data` 24 | ) as DataState 25 | 26 | const args = animation.args.map( 27 | (arg) => 28 | resolvePath(environment, arg, `${animation.id}_Data`) as DataState 29 | ) 30 | 31 | const objectValue = object.value as DataState[] 32 | 33 | // TODO: Copy data 34 | let originalLength = objectValue.length 35 | for (let i = 0; i < args.length; i++) { 36 | objectValue[objectValue.length] = cloneData( 37 | args[i], 38 | false, 39 | `${animation.id}_Arg${i}` 40 | ) 41 | } 42 | 43 | computeReadAndWrites( 44 | animation, 45 | { 46 | location: getMemoryLocation(environment, object).foundLocation, 47 | id: object.id, 48 | }, 49 | args.map((arg) => { 50 | return { 51 | location: getMemoryLocation(environment, arg).foundLocation, 52 | id: arg.id, 53 | } 54 | }), 55 | args.map((arg, i) => { 56 | const element = objectValue[originalLength + i] 57 | return { 58 | location: getMemoryLocation(environment, element).foundLocation, 59 | id: element.id, 60 | } 61 | }) 62 | ) 63 | } 64 | 65 | function computeReadAndWrites( 66 | animation: ArrayPushAnimation, 67 | object: DataInfo, 68 | args: DataInfo[], 69 | elements: DataInfo[] 70 | ) { 71 | animation._reads = [...args] 72 | animation._writes = [object, ...elements] 73 | } 74 | 75 | export function ArrayPushAnimation( 76 | object: Accessor[], 77 | args: Accessor[][] 78 | ): ArrayPushAnimation { 79 | return { 80 | ...createExecutionNode(null), 81 | _name: 'ArrayPushAnimation', 82 | 83 | name: 'ArrayPushAnimation', 84 | object, 85 | args, 86 | 87 | // Callbacks 88 | apply, 89 | } 90 | } 91 | -------------------------------------------------------------------------------- /src/scripts/execution/primitive/Functions/Native/Math/FloorAnimation.ts: -------------------------------------------------------------------------------- 1 | import { 2 | cloneData, 3 | createPrimitiveData, 4 | replaceDataWith, 5 | } from '../../../../../environment/data' 6 | import { 7 | addDataAt, 8 | getMemoryLocation, 9 | resolvePath, 10 | } from '../../../../../environment/environment' 11 | import { 12 | Accessor, 13 | EnvironmentState, 14 | } from '../../../../../environment/EnvironmentState' 15 | import { 16 | DataState, 17 | DataType, 18 | } from '../../../../../renderer/View/Environment/data/DataState' 19 | import { DataInfo } from '../../../../graph/ExecutionGraph' 20 | import { createExecutionNode, ExecutionNode } from '../../../ExecutionNode' 21 | 22 | export type FloorAnimation = ExecutionNode & { 23 | argument: Accessor[] 24 | outputRegister: Accessor[] 25 | } 26 | 27 | function apply(animation: FloorAnimation, environment: EnvironmentState) { 28 | const arg = resolvePath( 29 | environment, 30 | animation.argument, 31 | `${animation.id}_Data` 32 | ) as DataState 33 | const copy = cloneData(arg, false, `${animation.id}_Arg`) 34 | const location = addDataAt(environment, copy, [], null) 35 | copy.value = Math.floor(arg.value as number) 36 | 37 | // Put it in the floating stack 38 | const register = resolvePath( 39 | environment, 40 | animation.outputRegister, 41 | `${animation.id}_Floating` 42 | ) as DataState 43 | replaceDataWith( 44 | register, 45 | createPrimitiveData(DataType.ID, copy.id, `${animation.id}_Floating`) 46 | ) 47 | 48 | computeReadAndWrites( 49 | animation, 50 | { 51 | location: getMemoryLocation(environment, arg).foundLocation, 52 | id: arg.id, 53 | }, 54 | { 55 | location: location, 56 | id: copy.id, 57 | } 58 | ) 59 | } 60 | 61 | function computeReadAndWrites( 62 | animation: FloorAnimation, 63 | argument: DataInfo, 64 | data: DataInfo 65 | ) { 66 | animation._reads = [argument] 67 | animation._writes = [data] 68 | } 69 | 70 | export function FloorAnimation( 71 | object: Accessor[], 72 | args: Accessor[][], 73 | outputRegister: Accessor[] 74 | ): FloorAnimation { 75 | return { 76 | ...createExecutionNode(null), 77 | _name: 'FloorAnimation', 78 | 79 | name: 'FloorAnimation', 80 | argument: args[0], 81 | outputRegister: outputRegister, 82 | 83 | // Callbacks 84 | apply, 85 | } 86 | } 87 | -------------------------------------------------------------------------------- /src/scripts/execution/primitive/Functions/Native/Math/SqrtAnimation.ts: -------------------------------------------------------------------------------- 1 | import { 2 | cloneData, 3 | createPrimitiveData, 4 | replaceDataWith, 5 | } from '../../../../../environment/data' 6 | import { 7 | addDataAt, 8 | getMemoryLocation, 9 | resolvePath, 10 | } from '../../../../../environment/environment' 11 | import { 12 | Accessor, 13 | EnvironmentState, 14 | } from '../../../../../environment/EnvironmentState' 15 | import { 16 | DataState, 17 | DataType, 18 | } from '../../../../../renderer/View/Environment/data/DataState' 19 | import { DataInfo } from '../../../../graph/ExecutionGraph' 20 | import { createExecutionNode, ExecutionNode } from '../../../ExecutionNode' 21 | 22 | export type SqrtAnimation = ExecutionNode & { 23 | argument: Accessor[] 24 | outputRegister: Accessor[] 25 | } 26 | 27 | function apply(animation: SqrtAnimation, environment: EnvironmentState) { 28 | const arg = resolvePath( 29 | environment, 30 | animation.argument, 31 | `${animation.id}_Data` 32 | ) as DataState 33 | const copy = cloneData(arg, false, `${animation.id}_Arg`) 34 | const location = addDataAt(environment, copy, [], null) 35 | copy.value = Math.sqrt(arg.value as number) 36 | 37 | // Put it in the floating stack 38 | const register = resolvePath( 39 | environment, 40 | animation.outputRegister, 41 | `${animation.id}_Floating` 42 | ) as DataState 43 | replaceDataWith( 44 | register, 45 | createPrimitiveData(DataType.ID, copy.id, `${animation.id}_Floating`) 46 | ) 47 | 48 | computeReadAndWrites( 49 | animation, 50 | { 51 | location: getMemoryLocation(environment, arg).foundLocation, 52 | id: arg.id, 53 | }, 54 | { 55 | location: location, 56 | id: copy.id, 57 | } 58 | ) 59 | } 60 | 61 | function computeReadAndWrites( 62 | animation: SqrtAnimation, 63 | argument: DataInfo, 64 | data: DataInfo 65 | ) { 66 | animation._reads = [argument] 67 | animation._writes = [data] 68 | } 69 | 70 | export function SqrtAnimation( 71 | object: Accessor[], 72 | args: Accessor[][], 73 | outputRegister: Accessor[] 74 | ): SqrtAnimation { 75 | return { 76 | ...createExecutionNode(null), 77 | _name: 'SqrtAnimation', 78 | 79 | name: 'SqrtAnimation', 80 | argument: args[0], 81 | outputRegister: outputRegister, 82 | 83 | // Callbacks 84 | apply, 85 | } 86 | } 87 | -------------------------------------------------------------------------------- /src/scripts/execution/primitive/Functions/ReturnStatementAnimation.ts: -------------------------------------------------------------------------------- 1 | import { getMemoryLocation, resolvePath } from '../../../environment/environment' 2 | import { EnvironmentState } from '../../../environment/EnvironmentState' 3 | import { DataState } from '../../../renderer/View/Environment/data/DataState' 4 | import { DataInfo } from '../../graph/ExecutionGraph' 5 | import { createExecutionNode, ExecutionNode, ReturnData } from '../ExecutionNode' 6 | 7 | export type ReturnStatementAnimation = ExecutionNode & { 8 | returnData: ReturnData 9 | } 10 | 11 | function apply(animation: ReturnStatementAnimation, environment: EnvironmentState) { 12 | const data = resolvePath(environment, animation.returnData.register, `${animation.id}_Data`) as DataState 13 | data.frame = animation.returnData.frame 14 | 15 | const ref = resolvePath(environment, animation.returnData.register, `${animation.id}_Data`, null, { 16 | noResolvingReference: true, 17 | }) as DataState 18 | ref.frame = animation.returnData.frame 19 | 20 | // Any reference that points to data should also be uplifted 21 | // for (const id of Object.keys(environment.memory)) { 22 | // const ref = environment.memory[id] 23 | // if (instanceOfPrimitiveData(ref) && ref.type == DataType.Reference) { 24 | // const resolves = resolvePath( 25 | // environment, 26 | // ref.value as Accessor[], 27 | // `${animation.id}_RefData` 28 | // ) as DataState 29 | // if (resolves.value == data.value) { 30 | // ref.frame = animation.returnData.frame 31 | // } 32 | // } 33 | // } 34 | 35 | computeReadAndWrites(animation, { 36 | location: getMemoryLocation(environment, data).foundLocation, 37 | id: data.id, 38 | }) 39 | } 40 | 41 | function computeReadAndWrites(animation: ReturnStatementAnimation, data: DataInfo) { 42 | animation._reads = [data] 43 | animation._writes = [] 44 | } 45 | 46 | export function returnStatementAnimation(returnData: ReturnData): ReturnStatementAnimation { 47 | return { 48 | ...createExecutionNode(null), 49 | _name: 'ReturnStatementAnimation', 50 | 51 | name: 'Return Statement', 52 | 53 | returnData, 54 | 55 | // Callbacks 56 | apply, 57 | } 58 | } 59 | -------------------------------------------------------------------------------- /src/scripts/execution/primitive/Scope/CreateScopeAnimation.ts: -------------------------------------------------------------------------------- 1 | import { createScope } from '../../../environment/environment' 2 | import { EnvironmentState } from '../../../environment/EnvironmentState' 3 | import { ScopeType } from '../../../transpiler/Statements/BlockStatement' 4 | import { createExecutionNode, ExecutionNode } from '../ExecutionNode' 5 | 6 | export type CreateScopeAnimation = ExecutionNode & { 7 | type: ScopeType 8 | } 9 | 10 | function apply(animation: CreateScopeAnimation, environment: EnvironmentState) { 11 | createScope(environment, animation.type) 12 | 13 | computeReadAndWrites(animation) 14 | } 15 | 16 | function computeReadAndWrites(animation: CreateScopeAnimation) { 17 | animation._reads = [] 18 | animation._writes = [] 19 | } 20 | 21 | export function createScopeAnimation(type: ScopeType = ScopeType.Default): CreateScopeAnimation { 22 | return { 23 | ...createExecutionNode(null), 24 | _name: 'CreateScopeAnimation', 25 | 26 | name: 'CreateScopeAnimation', 27 | 28 | type, 29 | 30 | // Callbacks 31 | apply, 32 | } 33 | } 34 | -------------------------------------------------------------------------------- /src/scripts/execution/primitive/Scope/PopScopeAnimation.ts: -------------------------------------------------------------------------------- 1 | import { popScope } from '../../../environment/environment' 2 | import { EnvironmentState } from '../../../environment/EnvironmentState' 3 | import { createExecutionNode, ExecutionNode } from '../ExecutionNode' 4 | 5 | export type PopScopeAnimation = ExecutionNode & {} 6 | 7 | function apply(animation: PopScopeAnimation, environment: EnvironmentState) { 8 | popScope(environment) 9 | computeReadAndWrites(animation) 10 | } 11 | 12 | // TODO: Reads and writes 13 | function computeReadAndWrites(animation: PopScopeAnimation) { 14 | animation._reads = [] 15 | animation._writes = [] 16 | } 17 | 18 | export function popScopeAnimation(): PopScopeAnimation { 19 | return { 20 | ...createExecutionNode(null), 21 | _name: 'PopScopeAnimation', 22 | 23 | name: 'Pop Scope', 24 | 25 | // Callbacks 26 | apply, 27 | } 28 | } 29 | -------------------------------------------------------------------------------- /src/scripts/execution/primitive/Unary/UnaryExpressionEvaluate.ts: -------------------------------------------------------------------------------- 1 | import * as ESTree from 'estree' 2 | import { createPrimitiveData, replaceDataWith } from '../../../environment/data' 3 | import { 4 | addDataAt, 5 | getMemoryLocation, 6 | removeAt, 7 | resolvePath, 8 | } from '../../../environment/environment' 9 | import { 10 | Accessor, 11 | EnvironmentState, 12 | } from '../../../environment/EnvironmentState' 13 | import { 14 | DataState, 15 | DataType, 16 | } from '../../../renderer/View/Environment/data/DataState' 17 | import { DataInfo } from '../../graph/ExecutionGraph' 18 | import { createExecutionNode, ExecutionNode } from '../ExecutionNode' 19 | 20 | export type UnaryExpressionEvaluate = ExecutionNode & { 21 | specifier: Accessor[] 22 | operator: ESTree.UnaryOperator 23 | outputRegister: Accessor[] 24 | } 25 | 26 | function apply( 27 | animation: UnaryExpressionEvaluate, 28 | environment: EnvironmentState 29 | ) { 30 | // Find arg 31 | let arg = resolvePath( 32 | environment, 33 | animation.specifier, 34 | `${animation.id}_Left` 35 | ) as DataState 36 | 37 | // Evaluated 38 | const evaluated = createPrimitiveData( 39 | DataType.Literal, 40 | computeUnaryExpression(arg.value, animation.operator), 41 | `${animation.id}_EvaluatedData` 42 | ) 43 | addDataAt(environment, evaluated, [], null) 44 | 45 | computeReadAndWrites( 46 | animation, 47 | { 48 | location: getMemoryLocation(environment, arg).foundLocation, 49 | id: arg.id, 50 | }, 51 | { 52 | location: getMemoryLocation(environment, evaluated).foundLocation, 53 | id: evaluated.id, 54 | } 55 | ) 56 | 57 | // Point output to evaluated 58 | if (animation.outputRegister.length > 0) { 59 | const output = resolvePath( 60 | environment, 61 | animation.outputRegister, 62 | `${animation.id}_Floating` 63 | ) as DataState 64 | replaceDataWith( 65 | output, 66 | createPrimitiveData( 67 | DataType.ID, 68 | evaluated.id, 69 | `${animation.id}_Placed` 70 | ) 71 | ) 72 | } else { 73 | removeAt( 74 | environment, 75 | getMemoryLocation(environment, evaluated).foundLocation 76 | ) 77 | } 78 | 79 | removeAt(environment, getMemoryLocation(environment, arg).foundLocation) 80 | } 81 | 82 | function computeReadAndWrites( 83 | animation: UnaryExpressionEvaluate, 84 | original: DataInfo, 85 | evaluatedData: DataInfo 86 | ) { 87 | animation._reads = [original] 88 | animation._writes = [evaluatedData] 89 | } 90 | 91 | export function unaryExpressionEvaluate( 92 | specifier: Accessor[], 93 | operator: ESTree.UnaryOperator, 94 | outputRegister: Accessor[] 95 | ): UnaryExpressionEvaluate { 96 | return { 97 | ...createExecutionNode(null), 98 | _name: 'UnaryExpressionEvaluate', 99 | 100 | // name: `Binary Evaluate ${accessorsToString(leftSpecifier)} ${operator} ${accessorsToString( 101 | // rightSpecifier 102 | // )} onto ${accessorsToString(outputRegister)}`, 103 | 104 | name: `Binary ${operator}`, 105 | 106 | // Attributes 107 | specifier, 108 | operator, 109 | outputRegister, 110 | 111 | // Callbacks 112 | apply, 113 | } 114 | } 115 | 116 | function computeUnaryExpression(data: any, operator: ESTree.UnaryOperator) { 117 | switch (operator) { 118 | case '-': 119 | return -data 120 | case '+': 121 | return +data 122 | case '!': 123 | return !data 124 | case '~': 125 | return ~data 126 | case 'typeof': 127 | return typeof data 128 | case 'void': 129 | return void data 130 | default: 131 | throw new Error(`Unknown unary operator: ${operator}`) 132 | } 133 | } 134 | -------------------------------------------------------------------------------- /src/scripts/renderer/Action/Dynamic/AssignmentExpressionRepresentation.ts: -------------------------------------------------------------------------------- 1 | import { ApplicationState } from '../../../ApplicationState' 2 | import { getConsumedAbyss } from '../Abyss' 3 | import { ActionState } from '../Action' 4 | import { Representation } from './Representation' 5 | 6 | export class AssignmentExpressionRepresentation extends Representation { 7 | constructor(action: ActionState) { 8 | super(action) 9 | this.shouldHover = true 10 | } 11 | 12 | getControlFlowPoints( 13 | usePlaceholder: boolean = true, 14 | referencePoint: { x: number; y: number } = { x: 0, y: 0 } 15 | ): [number, number][] | null { 16 | const action = ApplicationState.actions[this.actionId] 17 | const abyssInfo = getConsumedAbyss(action.id) 18 | 19 | if (abyssInfo != null && !action.isSpatial) { 20 | // Find the abyss that it's in 21 | return super.getControlFlowPoints(usePlaceholder) 22 | } 23 | 24 | const parent = ApplicationState.actions[action.parentID!] 25 | 26 | if ( 27 | action.execution.nodeData.preLabel == 'Update' && 28 | parent.execution.nodeData.type == 'ForStatement' && 29 | !action.isShowingSteps 30 | ) { 31 | let bbox = action.proxy.element.getBoundingClientRect() 32 | 33 | const offset = Math.min(2, bbox.height * 0.1) 34 | 35 | return [ 36 | [bbox.x + bbox.width, bbox.y + bbox.height / 2], 37 | [bbox.x, bbox.y + bbox.height / 2], 38 | ] 39 | } else { 40 | return super.getControlFlowPoints(usePlaceholder) 41 | } 42 | } 43 | 44 | createSteps(): void { 45 | super.createSteps() 46 | 47 | const action = ApplicationState.actions[this.actionId] 48 | const proxy = action.proxy 49 | 50 | // Swap second and third child in the proxy 51 | const secondChild = proxy.element.children[1] 52 | const thirdChild = proxy.element.children[2] 53 | proxy.element.insertBefore(thirdChild, secondChild) 54 | 55 | console.log('Runs...') 56 | } 57 | } 58 | -------------------------------------------------------------------------------- /src/scripts/renderer/Action/Dynamic/BinaryExpressionRepresentation.ts: -------------------------------------------------------------------------------- 1 | import { ApplicationState } from '../../../ApplicationState' 2 | import { getConsumedAbyss } from '../Abyss' 3 | import { ActionState } from '../Action' 4 | import { Representation } from './Representation' 5 | 6 | export class BinaryStatementRepresentation extends Representation { 7 | // operatorLabelElement: HTMLElement 8 | 9 | constructor(action: ActionState) { 10 | super(action) 11 | 12 | // this.operatorLabelElement = createElement('div', ['action-proxy-code-label', 'action-proxy-binary-label']) 13 | // const evaluation = (action.execution as ExecutionGraph).vertices[2] 14 | 15 | // if (evaluation.nodeData.location == null) { 16 | // throw new Error('Binary expression evaluation has no associated location.') 17 | // } 18 | 19 | // const evaluationText = ApplicationState.editor.getValueAt(evaluation.nodeData.location)?.trim() 20 | 21 | // if (evaluationText == null) { 22 | // throw new Error('Binary expression evaluation has no text.') 23 | // } 24 | 25 | // this.operatorLabelElement.innerText = evaluationText 26 | // monaco.editor.colorize(this.operatorLabelElement.innerHTML, 'javascript', {}).then((html) => { 27 | // this.operatorLabelElement.innerHTML = html 28 | // }) 29 | 30 | this.shouldHover = true 31 | } 32 | 33 | createSteps(): void { 34 | super.createSteps() 35 | 36 | if (!this.isBreakable) return 37 | 38 | const action = ApplicationState.actions[this.actionId] 39 | const proxy = action.proxy 40 | 41 | if (!action.isShowingSteps) return 42 | 43 | const [leftElement, rightElement, operatorElement] = Array.from(proxy.element.children).filter((child) => 44 | child.classList.contains('action-proxy-container') 45 | ) 46 | 47 | // Remove all action-proxy children 48 | leftElement.remove() 49 | rightElement.remove() 50 | operatorElement.remove() 51 | 52 | // Add left element 53 | proxy.element.appendChild(leftElement) 54 | 55 | // Add operator label 56 | // proxy.element.appendChild(this.operatorLabelElement) 57 | 58 | // Add operator element 59 | // operatorElement.classList.add('skip-') 60 | proxy.element.appendChild(operatorElement) 61 | 62 | // Add right element 63 | proxy.element.appendChild(rightElement) 64 | } 65 | 66 | destroySteps(): void { 67 | super.destroySteps() 68 | // this.operatorLabelElement.remove() 69 | } 70 | 71 | getControlFlowPoints( 72 | usePlaceholder: boolean = true, 73 | referencePoint: { x: number; y: number } = { x: 0, y: 0 } 74 | ): [number, number][] | null { 75 | const action = ApplicationState.actions[this.actionId] 76 | const abyssInfo = getConsumedAbyss(action.id) 77 | 78 | if (abyssInfo != null && !action.isSpatial) { 79 | // Find the abyss that it's in 80 | return super.getControlFlowPoints(usePlaceholder) 81 | } 82 | 83 | const parent = ApplicationState.actions[action.parentID!] 84 | 85 | if ( 86 | action.execution.nodeData.preLabel == 'Test' && 87 | parent.execution.nodeData.type == 'ForStatement' && 88 | !action.isShowingSteps 89 | ) { 90 | let bbox = action.proxy.element.getBoundingClientRect() 91 | 92 | const offset = Math.min(2, bbox.height * 0.1) 93 | 94 | return [ 95 | [bbox.x, bbox.y + bbox.height / 2], 96 | [bbox.x + bbox.width / 2, bbox.y + bbox.height / 2], 97 | [bbox.x + bbox.width / 2, bbox.y + bbox.height], 98 | ] 99 | } else { 100 | return super.getControlFlowPoints(usePlaceholder) 101 | } 102 | } 103 | } 104 | -------------------------------------------------------------------------------- /src/scripts/renderer/Action/Dynamic/BlockStatementRepresentation.ts: -------------------------------------------------------------------------------- 1 | import { ApplicationState } from '../../../ApplicationState' 2 | import { getExecutionSteps } from '../../../utilities/action' 3 | import { getConsumedAbyss } from '../Abyss' 4 | import { ActionState } from '../Action' 5 | import { Representation } from './Representation' 6 | 7 | export class BlockStatementRepresentation extends Representation { 8 | constructor(action: ActionState) { 9 | super(action) 10 | 11 | this.shouldHover = true 12 | 13 | const executionSteps = getExecutionSteps(action.execution) 14 | this.isSelectableGroup = !(executionSteps.length == 1 && action.execution.nodeData.preLabel != 'Body') 15 | } 16 | 17 | postCreate(): void { 18 | const action = ApplicationState.actions[this.actionId] 19 | 20 | // If there is only one step, then expand it 21 | const executionSteps = getExecutionSteps(action.execution) 22 | if (executionSteps.length == 1 && action.execution.nodeData.preLabel != 'Body') { 23 | this.createSteps() 24 | 25 | if (executionSteps[0].nodeData.preLabel != 'IfStatement') { 26 | action.proxy.container.classList.add('is-singular') 27 | } 28 | } 29 | } 30 | 31 | getControlFlowPoints( 32 | usePlaceholder: boolean = true, 33 | referencePoint: { x: number; y: number } = { x: 0, y: 0 } 34 | ): [number, number][] | null { 35 | if (this.isTrimmed) { 36 | return null 37 | } 38 | 39 | const action = ApplicationState.actions[this.actionId] 40 | const abyssInfo = getConsumedAbyss(action.id) 41 | 42 | if (abyssInfo != null && !action.isSpatial) { 43 | // Find the abyss that it's in 44 | return super.getControlFlowPoints(usePlaceholder) 45 | } 46 | 47 | const parent = ApplicationState.actions[action.parentID!] 48 | 49 | if ( 50 | action.execution.nodeData.preLabel == 'Body' && 51 | parent.execution.nodeData.type == 'ForStatement' && 52 | !action.isShowingSteps 53 | ) { 54 | let bbox = action.proxy.element.getBoundingClientRect() 55 | 56 | const offset = Math.min(2, bbox.height * 0.1) 57 | 58 | let mid = referencePoint!.x 59 | 60 | return [ 61 | [mid, bbox.y], 62 | [mid, bbox.y + bbox.height / 2], 63 | [bbox.x + bbox.width, bbox.y + bbox.height / 2], 64 | ] 65 | } else { 66 | return super.getControlFlowPoints(usePlaceholder) 67 | } 68 | } 69 | } 70 | -------------------------------------------------------------------------------- /src/scripts/renderer/Action/Dynamic/CallExpressionRepresentation.ts: -------------------------------------------------------------------------------- 1 | import * as monaco from 'monaco-editor' 2 | import { ApplicationState } from '../../../ApplicationState' 3 | import { createElement } from '../../../utilities/dom' 4 | import { collapseActionIntoAbyss } from '../Abyss' 5 | import { ActionState } from '../Action' 6 | import { getTotalDuration } from '../Mapping/ControlFlowCursor' 7 | import { Representation } from './Representation' 8 | 9 | export class CallExpressionRepresentation extends Representation { 10 | callExpressionLabelElement: HTMLElement 11 | 12 | constructor(action: ActionState) { 13 | super(action) 14 | 15 | this.callExpressionLabelElement = createElement('div', ['action-proxy-code-label']) 16 | 17 | if (action.execution.nodeData.location == undefined) { 18 | throw new Error('Call expression has no nodeData location.') 19 | } 20 | 21 | this.callExpressionLabelElement.innerText = 22 | ApplicationState.editor.getValueAt(action.execution.nodeData.location) ?? 'Unknown' 23 | 24 | monaco.editor.colorize(this.callExpressionLabelElement.innerHTML, 'javascript', {}).then((html) => { 25 | this.callExpressionLabelElement.innerHTML = html 26 | }) 27 | } 28 | 29 | createSteps(): void { 30 | const action = ApplicationState.actions[this.actionId] 31 | 32 | const selection = ApplicationState.visualization.selections['main'] 33 | const globalTime = selection.targetGlobalTime 34 | const timeDelta = { 35 | start: globalTime - action.globalTimeOffset, 36 | end: globalTime - action.globalTimeOffset - getTotalDuration(action.id), 37 | } 38 | 39 | super.createSteps() 40 | 41 | // If was before the global time, then seek to the start of the function call 42 | if (timeDelta.start < 0) { 43 | setTimeout(() => { 44 | let firstChild = ApplicationState.actions[action.vertices[1]] 45 | while (firstChild.vertices.length > 0) { 46 | firstChild = ApplicationState.actions[firstChild.vertices[0]] 47 | } 48 | selection.targetGlobalTime = firstChild.globalTimeOffset + getTotalDuration(firstChild.id) 49 | }, 0) 50 | } else if (timeDelta.end >= 0) { 51 | setTimeout(() => { 52 | let lastChild = ApplicationState.actions[action.vertices.at(-1)!] 53 | while (lastChild.vertices.length > 0) { 54 | lastChild = ApplicationState.actions[lastChild.vertices.at(-1)!] 55 | } 56 | 57 | // const parent = ApplicationState.actions[action.parentID!] 58 | selection.targetGlobalTime = lastChild.globalTimeOffset + getTotalDuration(lastChild.id) 59 | selection._globalTime = lastChild.globalTimeOffset + getTotalDuration(lastChild.id) 60 | 61 | setTimeout(() => { 62 | const lastChildParent = ApplicationState.actions[lastChild.parentID!] 63 | lastChildParent.representation.focus() 64 | }, 0) 65 | }, 100) 66 | } 67 | 68 | const proxy = action.proxy 69 | 70 | // const [argElement, bodyElement] = Array.from(proxy.element.children).filter( 71 | // (child) => 72 | // child.classList.contains('action-proxy-container') || 73 | // child.classList.contains('action-proxy-placeholder') 74 | // ) 75 | 76 | if (ApplicationState.visualization.PARAMS.Closure) { 77 | const spatialParent = ApplicationState.actions[action.spatialParentID!] 78 | spatialParent.representation.minimize() 79 | 80 | if (spatialParent.parentID != null) { 81 | const spatialParentParent = ApplicationState.actions[spatialParent.parentID!] 82 | const spatialParentParentSpatialParent = ApplicationState.actions[spatialParentParent.spatialParentID!] 83 | 84 | if ( 85 | spatialParentParentSpatialParent.minimized && 86 | spatialParentParentSpatialParent.execution.nodeData.type != 'Program' && 87 | !spatialParentParentSpatialParent.proxy.container.classList.contains('consumed') 88 | ) { 89 | collapseActionIntoAbyss(spatialParentParentSpatialParent.id) 90 | } 91 | } 92 | } 93 | 94 | // Remove all action-proxy children 95 | // argElement.remove() 96 | // bodyElement.remove() 97 | 98 | // Add call expression label to placeholder 99 | // TODO 100 | 101 | // action.proxy.element.append(bodyElement) 102 | } 103 | } 104 | -------------------------------------------------------------------------------- /src/scripts/renderer/Action/Dynamic/ExpressionStatementRepresentation.ts: -------------------------------------------------------------------------------- 1 | import { ApplicationState } from '../../../ApplicationState' 2 | import { ExecutionGraph } from '../../../execution/graph/ExecutionGraph' 3 | import { ActionState } from '../Action' 4 | import { Representation } from './Representation' 5 | 6 | export class ExpressionStatementRepresentation extends Representation { 7 | constructor(action: ActionState) { 8 | super(action) 9 | this.shouldHover = true 10 | } 11 | 12 | createSteps(): void { 13 | super.createSteps() 14 | 15 | const action = ApplicationState.actions[this.actionId] 16 | const execution = action.execution as ExecutionGraph 17 | 18 | // let isAssignment = false 19 | // if (execution.vertices.length > 0 && execution.vertices[0].nodeData.preLabel == 'AssignmentIdentifier') { 20 | // isAssignment = true 21 | // } 22 | 23 | // const proxy = action.proxy 24 | 25 | // if (isAssignment) { 26 | // TODO 27 | // Swap second and third child in the proxy 28 | // const secondChild = proxy.element.children[1] 29 | // const thirdChild = proxy.element.children[2] 30 | // proxy.element.insertBefore(thirdChild, secondChild) 31 | 32 | // this.isBreakable = false 33 | // } 34 | } 35 | } 36 | -------------------------------------------------------------------------------- /src/scripts/renderer/Action/Dynamic/ProgramRepresentation.ts: -------------------------------------------------------------------------------- 1 | import { ApplicationState } from '../../../ApplicationState' 2 | import { getAccumulatedBoundingBox } from '../../../utilities/action' 3 | import { ActionState } from '../Action' 4 | import { Representation } from './Representation' 5 | 6 | export class ProgramRepresentation extends Representation { 7 | constructor(action: ActionState) { 8 | super(action) 9 | 10 | setTimeout(() => { 11 | this.createSteps() 12 | }) 13 | } 14 | 15 | updateSpatialActionProxyPositionChildren(offset: { x: number; y: number }): string[] { 16 | const action = ApplicationState.actions[this.actionId] 17 | 18 | const rOffset = { x: 50, y: 0 } 19 | const spatialIDs: string[] = [] 20 | 21 | action.vertices.forEach((id) => { 22 | const vertex = ApplicationState.actions[id] 23 | spatialIDs.push(...vertex.representation.updateSpatialActionProxyPosition({ ...rOffset })) 24 | 25 | // If hit something 26 | if (spatialIDs.length > 0) { 27 | const bbox = getAccumulatedBoundingBox(spatialIDs) 28 | rOffset.y += bbox.height + 20 29 | } 30 | }) 31 | 32 | return spatialIDs 33 | } 34 | 35 | clicked() { 36 | return false 37 | } 38 | } 39 | -------------------------------------------------------------------------------- /src/scripts/renderer/Action/Dynamic/ReturnStatementRepresentation.ts: -------------------------------------------------------------------------------- 1 | import * as monaco from 'monaco-editor' 2 | import { ApplicationState } from '../../../ApplicationState' 3 | import { createElement } from '../../../utilities/dom' 4 | import { assert } from '../../../utilities/generic' 5 | import { ActionState } from '../Action' 6 | import { getTotalDuration } from '../Mapping/ControlFlowCursor' 7 | import { Representation } from './Representation' 8 | 9 | export class ReturnStatementRepresentation extends Representation { 10 | returnLabelElement: HTMLElement 11 | 12 | constructor(action: ActionState) { 13 | super(action) 14 | 15 | this.returnLabelElement = createElement('div', ['action-proxy-code-label', 'action-proxy-return-label']) 16 | this.returnLabelElement.innerText = 'return' 17 | monaco.editor.colorize(this.returnLabelElement.innerHTML, 'javascript', {}).then((html) => { 18 | this.returnLabelElement.innerHTML = html 19 | }) 20 | 21 | this.shouldHover = true 22 | } 23 | 24 | focus() { 25 | const action = ApplicationState.actions[this.actionId] 26 | 27 | // Update time 28 | const spatialId = action.spatialParentID 29 | assert(spatialId != null, 'Spatial parent ID is null') 30 | 31 | const spatial = ApplicationState.actions[spatialId] 32 | assert(spatial.isSpatial, 'Action is not spatial') 33 | const controlFlow = spatial.controlFlow 34 | assert(controlFlow != null, 'Control flow is null') 35 | 36 | const selection = ApplicationState.visualization.selections[controlFlow.cursor.selectionId] 37 | 38 | const child = ApplicationState.actions[action.vertices[0]] 39 | selection.targetGlobalTime = child.globalTimeOffset + getTotalDuration(child.id) 40 | } 41 | 42 | postCreate() { 43 | this.createSteps() 44 | } 45 | 46 | createSteps(): void { 47 | super.createSteps() 48 | 49 | const action = ApplicationState.actions[this.actionId] 50 | const proxy = action.proxy 51 | if (!action.isShowingSteps) return 52 | 53 | const children = Array.from(proxy.element.children).filter((child) => 54 | child.classList.contains('action-proxy-container') 55 | ) 56 | 57 | // Remove all action-proxy children 58 | for (const child of children) { 59 | child.remove() 60 | } 61 | 62 | // Add return label 63 | proxy.element.appendChild(this.returnLabelElement) 64 | 65 | // Add children 66 | for (const child of children) { 67 | proxy.element.appendChild(child) 68 | } 69 | } 70 | 71 | destroySteps(): void { 72 | super.destroySteps() 73 | 74 | this.returnLabelElement.remove() 75 | } 76 | } 77 | -------------------------------------------------------------------------------- /src/scripts/renderer/Action/Dynamic/UpdateExpressionRepresentation.ts: -------------------------------------------------------------------------------- 1 | import { ApplicationState } from '../../../ApplicationState' 2 | import { getConsumedAbyss } from '../Abyss' 3 | import { ActionState } from '../Action' 4 | import { Representation } from './Representation' 5 | 6 | export class UpdateExpressionRepresentation extends Representation { 7 | constructor(action: ActionState) { 8 | super(action) 9 | this.shouldHover = true 10 | } 11 | 12 | getControlFlowPoints( 13 | usePlaceholder: boolean = true, 14 | referencePoint: { x: number; y: number } = { x: 0, y: 0 } 15 | ): [number, number][] | null { 16 | const action = ApplicationState.actions[this.actionId] 17 | const abyssInfo = getConsumedAbyss(action.id) 18 | 19 | if (abyssInfo != null && !action.isSpatial) { 20 | // Find the abyss that it's in 21 | return super.getControlFlowPoints(usePlaceholder) 22 | } 23 | 24 | const parent = ApplicationState.actions[action.parentID!] 25 | 26 | if ( 27 | action.execution.nodeData.preLabel == 'Update' && 28 | parent.execution.nodeData.type == 'ForStatement' && 29 | !action.isShowingSteps 30 | ) { 31 | let bbox = action.proxy.element.getBoundingClientRect() 32 | 33 | const offset = Math.min(2, bbox.height * 0.1) 34 | 35 | return [ 36 | [bbox.x + bbox.width, bbox.y + bbox.height / 2], 37 | [bbox.x, bbox.y + bbox.height / 2], 38 | ] 39 | } else { 40 | return super.getControlFlowPoints(usePlaceholder) 41 | } 42 | } 43 | } 44 | -------------------------------------------------------------------------------- /src/scripts/renderer/Action/Mapping/ActionProxy.ts: -------------------------------------------------------------------------------- 1 | import { ApplicationState } from '../../../ApplicationState' 2 | import { ExecutionGraph } from '../../../execution/graph/ExecutionGraph' 3 | import { ExecutionNode } from '../../../execution/primitive/ExecutionNode' 4 | import { getAccumulatedBoundingBox } from '../../../utilities/action' 5 | import { createElement } from '../../../utilities/dom' 6 | import { getTitleFromExecution } from '../Dynamic/Representation' 7 | 8 | export type ActionProxyState = { 9 | // Container of this things indicator and its steps 10 | container: HTMLElement 11 | header: HTMLElement 12 | controls: HTMLElement | null 13 | element: HTMLElement 14 | 15 | // Corresponding action 16 | actionID: string 17 | 18 | isHovering: boolean 19 | } 20 | 21 | /** 22 | * Creates an executor. 23 | * @param overrides 24 | * @returns 25 | */ 26 | export function createActionProxy(overrides: Partial = {}): ActionProxyState { 27 | if (overrides.actionID === undefined) { 28 | throw new Error('ActionProxy requires an action') 29 | } 30 | 31 | const action = ApplicationState.actions[overrides.actionID] 32 | 33 | // Container for the action and its label 34 | const container = createElement('div', 'action-proxy-container') 35 | 36 | // Header for the action 37 | const header = createElement('div', 'action-proxy-header', container) 38 | 39 | // Label 40 | const label = createElement('div', 'action-proxy-label', header) 41 | label.innerText = `${getTitleFromExecution(action.execution)}` 42 | 43 | const element = createElement( 44 | 'div', 45 | [ 46 | 'action-proxy', 47 | `type-${action.execution.nodeData.type?.replace(' ', '-')}`, 48 | `pre-label-${action.execution.nodeData.preLabel?.replace(' ', '-')}`, 49 | ], 50 | container 51 | ) 52 | 53 | element.addEventListener('mouseenter', () => { 54 | action?.element?.classList.add('hover') 55 | }) 56 | 57 | element.addEventListener('mouseleave', () => { 58 | action?.element?.classList.remove('hover') 59 | }) 60 | 61 | // autoAnimate(element) 62 | 63 | const base: ActionProxyState = { 64 | element: element, 65 | header: header, 66 | controls: null, 67 | container: container, 68 | actionID: overrides.actionID, 69 | timeOffset: 0, 70 | isHovering: false, 71 | } 72 | 73 | return { ...base, ...overrides } 74 | } 75 | 76 | export function destroyActionProxy(proxy: ActionProxyState) { 77 | proxy.container.remove() 78 | } 79 | 80 | export function isSpatialByDefault(execution: ExecutionGraph | ExecutionNode) { 81 | return execution.nodeData.type == 'FunctionCall' || execution.nodeData.type == 'Program' 82 | } 83 | 84 | export function clipActionProxy(actionID: string) { 85 | const action = ApplicationState.actions[actionID] 86 | 87 | if (action.spatialVertices.size > 0) { 88 | const bbox = getAccumulatedBoundingBox( 89 | [...action.spatialVertices].map((v) => ApplicationState.actions[v].parentID as string) 90 | ) 91 | const actionBbox = action.proxy.element.getBoundingClientRect() 92 | let { y, height } = bbox 93 | 94 | y = y - actionBbox.y 95 | 96 | // action.proxy.element.style.margin 97 | const firstChild = action.proxy.element.children[0] as HTMLElement 98 | 99 | firstChild.style.marginTop = `${-y}px` 100 | action.proxy.element.style.maxHeight = `${height}px` 101 | 102 | action.proxy.element.classList.add('clipped') 103 | } else { 104 | // Get what things are not going to be clipped by default 105 | throw new Error('No implementation for clipping') 106 | } 107 | } 108 | 109 | export function unClipActionProxy(actionID: string) { 110 | // TODO 111 | } 112 | -------------------------------------------------------------------------------- /src/scripts/renderer/Action/Mapping/index.d.ts: -------------------------------------------------------------------------------- 1 | declare module 'kld-intersections' { 2 | const Intersection: any 3 | const ShapeInfo: any 4 | } 5 | -------------------------------------------------------------------------------- /src/scripts/renderer/Trail/Renderers/CreateTrailRenderer.ts: -------------------------------------------------------------------------------- 1 | import { getMemoryLocation, resolvePath } from '../../../environment/environment' 2 | import { AccessorType, EnvironmentState, instanceOfEnvironment, Residual } from '../../../environment/EnvironmentState' 3 | import { remap } from '../../../utilities/math' 4 | import { DataRenderer } from '../../View/Environment/data/DataRenderer' 5 | import { EnvironmentRenderer } from '../../View/Environment/EnvironmentRenderer' 6 | import { Trail } from '../Trail' 7 | import { resetResidual, TrailRenderer, updateResidual } from './TrailRenderer' 8 | 9 | export class CreateTrailRenderer extends TrailRenderer { 10 | /* ----------------------- Create ----------------------- */ 11 | constructor(trail: Trail) { 12 | super(trail) 13 | } 14 | 15 | /* ----------------------- Animate ---------------------- */ 16 | update(amount: number, environment: EnvironmentRenderer, totalTime: number) { 17 | /* ---------------------- Old data ---------------------- */ 18 | const prev = environment.getResidualOf(this.trail.state.toDataID, this.trail.time) 19 | if (prev != null) { 20 | updateResidual(amount, prev) 21 | } else { 22 | // Reset the residual 23 | const prev = environment.findRendererById(this.trail.state.toDataID) 24 | if (prev != null) { 25 | resetResidual(prev.data) 26 | } 27 | } 28 | 29 | /* ---------------------- New data ---------------------- */ 30 | const t = remap(amount, 0, 1, 5, 0) 31 | const data = environment.getResidualOf(this.trail.state.toDataID, this.trail.time + 1) as DataRenderer 32 | 33 | if ((amount == 1 && data == null) || (amount == 0 && data == null)) { 34 | // Has been destroyed / not yet initialized 35 | return 36 | } else if (data == null) { 37 | console.warn('Data is null in CreateTrailRenderer') 38 | return 39 | } 40 | 41 | // data.element.style.transform = `translate(${t}px, ${-t}px)` 42 | data.element.style.opacity = `${Math.min(1, amount * 2)}` 43 | data.element.style.left = '0px' 44 | data.element.style.top = '0px' 45 | } 46 | 47 | /** 48 | * @param environment Prev environment 49 | * @returns 50 | */ 51 | computeResidual(environment: EnvironmentState): Residual | null { 52 | const prev = resolvePath(environment, [{ type: AccessorType.ID, value: this.trail.state.toDataID }], null) 53 | 54 | if (instanceOfEnvironment(prev)) { 55 | return null 56 | } 57 | 58 | const location = getMemoryLocation(environment, prev).foundLocation 59 | 60 | if (location == null) { 61 | throw new Error('Location not found') 62 | } 63 | 64 | return { 65 | data: prev, 66 | location: location, 67 | } 68 | } 69 | 70 | applyTimestamps(environment: EnvironmentState) { 71 | environment.timestamps[this.trail.state.toDataID] = this.trail.time 72 | } 73 | 74 | /* ----------------------- Destroy ---------------------- */ 75 | destroy() { 76 | super.destroy() 77 | } 78 | } 79 | -------------------------------------------------------------------------------- /src/scripts/renderer/Trail/Renderers/PartialCreateTrailRenderer.ts: -------------------------------------------------------------------------------- 1 | import { getMemoryLocation, resolvePath } from '../../../environment/environment' 2 | import { AccessorType, EnvironmentState, instanceOfEnvironment, Residual } from '../../../environment/EnvironmentState' 3 | import { remap } from '../../../utilities/math' 4 | import { DataRenderer } from '../../View/Environment/data/DataRenderer' 5 | import { EnvironmentRenderer } from '../../View/Environment/EnvironmentRenderer' 6 | import { Trail } from '../Trail' 7 | import { resetResidual, TrailRenderer, updateResidual } from './TrailRenderer' 8 | 9 | export class PartialCreateTrailRenderer extends TrailRenderer { 10 | /* ----------------------- Create ----------------------- */ 11 | constructor(trail: Trail) { 12 | super(trail) 13 | } 14 | 15 | /* ----------------------- Animate ---------------------- */ 16 | update(amount: number, environment: EnvironmentRenderer, totalTime: number) { 17 | /* ---------------------- Old data ---------------------- */ 18 | const prev = environment.getResidualOf(this.trail.state.toDataID, this.trail.time) 19 | 20 | if (prev != null) { 21 | updateResidual(amount, prev) 22 | } else { 23 | // Reset the residual 24 | const prev = environment.findRendererById(this.trail.state.toDataID) 25 | if (prev != null) { 26 | resetResidual(prev.data) 27 | } 28 | } 29 | 30 | /* ---------------------- New data ---------------------- */ 31 | const t = remap(amount, 0, 1, 5, 0) 32 | 33 | const data = environment.getResidualOf(this.trail.state.toDataID, this.trail.time + 1) as DataRenderer 34 | 35 | if ((amount == 1 && data == null) || (amount == 0 && data == null)) { 36 | // Has been destroyed / not yet initialized 37 | return 38 | } else if (data == null) { 39 | console.warn('Data is null in PartialCreateTrailRenderer') 40 | return 41 | } 42 | 43 | // data.element.style.transform = `translate(${t}px, ${-t}px)` 44 | data.element.style.opacity = `${Math.min(1, amount * 2)}` 45 | data.element.style.left = '0px' 46 | data.element.style.top = '0px' 47 | } 48 | 49 | /** 50 | * @param environment Prev environment 51 | * @returns 52 | */ 53 | computeResidual(environment: EnvironmentState): Residual | null { 54 | const prev = resolvePath(environment, [{ type: AccessorType.ID, value: this.trail.state.toDataID }], null) 55 | 56 | if (instanceOfEnvironment(prev)) { 57 | return null 58 | } 59 | 60 | const location = getMemoryLocation(environment, prev).foundLocation 61 | 62 | if (location == null) { 63 | throw new Error('Location not found') 64 | } 65 | 66 | return { 67 | data: prev, 68 | location: location, 69 | } 70 | } 71 | 72 | applyTimestamps(environment: EnvironmentState) { 73 | environment.timestamps[this.trail.state.toDataID] = this.trail.time 74 | } 75 | 76 | /* ----------------------- Destroy ---------------------- */ 77 | destroy() { 78 | super.destroy() 79 | } 80 | } 81 | -------------------------------------------------------------------------------- /src/scripts/renderer/Trail/Renderers/TrailRenderer.ts: -------------------------------------------------------------------------------- 1 | import { EnvironmentState, Residual } from '../../../environment/EnvironmentState' 2 | import { remap } from '../../../utilities/math' 3 | import { DataRenderer } from '../../View/Environment/data/DataRenderer' 4 | import { EnvironmentRenderer } from '../../View/Environment/EnvironmentRenderer' 5 | import { IdentifierRenderer } from '../../View/Environment/identifier/IdentifierRenderer' 6 | import { Trail } from '../Trail' 7 | 8 | export class TrailRenderer { 9 | trail: Trail 10 | 11 | /* ----------------------- Create ----------------------- */ 12 | constructor(trail: Trail) { 13 | this.trail = trail 14 | } 15 | 16 | /* ----------------------- Animate ---------------------- */ 17 | update(amount: number, environment: EnvironmentRenderer, totalTime: number) { 18 | console.warn('Update not implemented for', this) 19 | } 20 | 21 | postUpdate(amount: number, environment: EnvironmentRenderer, totalTime: number) {} 22 | 23 | alwaysUpdate(environment: EnvironmentRenderer) {} 24 | 25 | computeResidual(environment: EnvironmentState): Residual | null { 26 | console.warn('Compute residual not implemented for', this) 27 | return null 28 | } 29 | 30 | /** 31 | * @param environment Current environment 32 | */ 33 | applyTimestamps(environment: EnvironmentState) { 34 | console.warn('Apply timestamps not implemented for', this) 35 | } 36 | 37 | /* ----------------------- Destroy ---------------------- */ 38 | destroy() {} 39 | } 40 | 41 | export function updateResidual(amount: number, prev: DataRenderer | IdentifierRenderer) { 42 | const pt = remap(amount, 0, 1, 0, 5) 43 | let offset = 0 44 | let hit = false 45 | 46 | if (prev.element.parentElement == null) { 47 | throw new Error('Element has no parent') 48 | } 49 | 50 | for (let i = 0; i < prev.element.parentElement.childElementCount; i++) { 51 | const child = prev.element.parentElement.children[i] 52 | if (hit) { 53 | offset++ 54 | } 55 | 56 | if (child == prev.element) { 57 | hit = true 58 | } 59 | } 60 | offset *= 5 61 | 62 | prev.element.style.transform = `translate(${-pt - offset}px, ${pt + offset}px) scale(${1})` 63 | 64 | let opacity 65 | 66 | if (offset / 5 >= 2) { 67 | opacity = 0 68 | } else if (offset / 5 >= 1) { 69 | opacity = Math.max(0, 1 - 0.8 * amount - offset / 20) 70 | } else { 71 | opacity = Math.max(0.1, 1 - 0.8 * amount) 72 | } 73 | 74 | prev.element.style.color = `rgba(138, 138, 138, ${opacity})` 75 | prev.element.style.filter = `saturate(${Math.max(0, 1 - 2 * amount)})` 76 | } 77 | 78 | export function resetResidual(prev: DataRenderer | IdentifierRenderer) { 79 | prev.element.style.transform = `translate(0px, 0px) scale(1)` 80 | prev.element.style.filter = `saturate(1)` 81 | prev.element.style.color = `` 82 | } 83 | -------------------------------------------------------------------------------- /src/scripts/renderer/Trail/Trail.ts: -------------------------------------------------------------------------------- 1 | import { ExecutionGraph } from '../../execution/graph/ExecutionGraph' 2 | import { ExecutionNode } from '../../execution/primitive/ExecutionNode' 3 | import { EnvironmentRenderer } from '../View/Environment/EnvironmentRenderer' 4 | import { CreateTrailRenderer } from './Renderers/CreateTrailRenderer' 5 | import { MoveTrailRenderer } from './Renderers/MoveTrailRenderer' 6 | import { PartialCreateTrailRenderer } from './Renderers/PartialCreateTrailRenderer' 7 | import { PartialMoveTrailRenderer } from './Renderers/PartialMoveTrailRenderer' 8 | import { TrailRenderer } from './Renderers/TrailRenderer' 9 | import { TrailState, TrailType } from './TrailState' 10 | 11 | /* ------------------------------------------------------ */ 12 | /* Trails visualize change via path or animation */ 13 | /* ------------------------------------------------------ */ 14 | export class Trail { 15 | state: TrailState 16 | renderer: TrailRenderer 17 | execution: ExecutionGraph | ExecutionNode 18 | 19 | time: number 20 | 21 | constructor(state: TrailState, execution: ExecutionGraph | ExecutionNode, time: number) { 22 | this.state = state 23 | this.execution = execution 24 | this.renderer = createTrailRenderer(this) 25 | this.time = time 26 | } 27 | 28 | /* ----------------------- Update ----------------------- */ 29 | update(amount: number, environment: EnvironmentRenderer, totalTime: number) { 30 | this.renderer.update(amount, environment, totalTime) 31 | } 32 | 33 | /* ----------------------- Destroy ---------------------- */ 34 | destroy() { 35 | this.renderer.destroy() 36 | } 37 | } 38 | 39 | export function createTrailRenderer(trail: Trail) { 40 | switch (trail.state.type) { 41 | case TrailType.Create: 42 | return new CreateTrailRenderer(trail) 43 | case TrailType.PartialCreate: 44 | return new PartialCreateTrailRenderer(trail) 45 | case TrailType.Move: 46 | return new MoveTrailRenderer(trail) 47 | case TrailType.PartialMove: 48 | return new PartialMoveTrailRenderer(trail) 49 | default: 50 | throw new Error(`Unsupported trail type ${trail.state.type}`) 51 | } 52 | } 53 | -------------------------------------------------------------------------------- /src/scripts/renderer/Trail/TrailState.ts: -------------------------------------------------------------------------------- 1 | /* ------------------------------------------------------ */ 2 | /* Trail State */ 3 | /* ------------------------------------------------------ */ 4 | 5 | import { AnimationTraceOperator } from '../../execution/execution' 6 | 7 | /* --------------------- Definition --------------------- */ 8 | 9 | export enum TrailType { 10 | Create = 'Create', 11 | Move = 'Move', 12 | Delete = 'Delete', 13 | 14 | PartialMove = 'PartialMove', 15 | PartialCreate = 'PartialCreate', 16 | } 17 | 18 | export type TrailState = { 19 | fromDataIDs?: string[] 20 | toDataID: string 21 | type: TrailType 22 | operations: AnimationTraceOperator[] 23 | } 24 | -------------------------------------------------------------------------------- /src/scripts/renderer/View/Environment/HardScopeRenderer.ts: -------------------------------------------------------------------------------- 1 | // export class HardScopeRenderer { 2 | // isGlobal: boolean 3 | // element: HTMLElement 4 | 5 | // constructor(isGlobal: boolean) { 6 | // this.isGlobal = isGlobal 7 | 8 | // this.element = document.createElement('div') 9 | // } 10 | // } 11 | -------------------------------------------------------------------------------- /src/scripts/renderer/View/Environment/data/DataRenderer.ts: -------------------------------------------------------------------------------- 1 | import { FrameInfo } from '../../../../environment/EnvironmentState' 2 | import { DataState } from './DataState' 3 | 4 | export class DataRenderer { 5 | element: HTMLDivElement 6 | array: DataRenderer[] 7 | 8 | _cachedState: DataState | null = null 9 | 10 | constructor() { 11 | this.element = document.createElement('div') 12 | this.element.classList.add('data') 13 | } 14 | 15 | setState(data: DataState, frame: FrameInfo) { 16 | console.warn('No fallback for', data) 17 | } 18 | 19 | destroy() { 20 | this.element.remove() 21 | } 22 | 23 | getAllChildRenderers(): { [id: string]: DataRenderer } { 24 | return {} 25 | } 26 | 27 | /* ------------------------ Focus ----------------------- */ 28 | secondaryFocus() { 29 | this.element.classList.add('secondary-focused') 30 | } 31 | 32 | focus() { 33 | this.element.classList.remove('secondary-focused') 34 | } 35 | 36 | clearFocus() { 37 | this.element.classList.remove('secondary-focused') 38 | } 39 | } 40 | -------------------------------------------------------------------------------- /src/scripts/renderer/View/Environment/data/DataState.ts: -------------------------------------------------------------------------------- 1 | import * as CSS from 'csstype' 2 | import { Accessor } from '../../../../environment/EnvironmentState' 3 | 4 | export enum DataType { 5 | Literal = 'Literal', 6 | Array = 'Array', 7 | ID = 'ID', 8 | Reference = 'Reference', 9 | Register = 'Register', 10 | Function = 'Function', 11 | } 12 | 13 | export type TransformStyles = CSS.Properties & { 14 | elevation?: number 15 | xoffset?: number 16 | yoffset?: number 17 | } 18 | 19 | export type Transform = { 20 | rendered: { 21 | x: number 22 | y: number 23 | width: number 24 | height: number 25 | } 26 | styles: TransformStyles 27 | renderOnlyStyles: TransformStyles 28 | classList: string[] 29 | } 30 | 31 | export type DataTransform = { 32 | classList: string[] 33 | } 34 | 35 | export type PrimitiveDataState = { 36 | _type: 'PrimitiveDataState' 37 | type: DataType 38 | 39 | value: string | number | boolean | null | Accessor[] | Function 40 | 41 | // Binding frame 42 | frame: number 43 | 44 | id: string 45 | 46 | builtin?: boolean 47 | } 48 | 49 | export type ObjectDataState = { 50 | _type: 'ObjectDataState' 51 | value: object 52 | 53 | // Binding frame 54 | frame: number 55 | 56 | id: string 57 | 58 | builtin?: boolean 59 | } 60 | 61 | export type DataState = PrimitiveDataState | ObjectDataState 62 | 63 | export function instanceOfData(data: any): data is DataState { 64 | return ( 65 | data != null && 66 | (data['_type'] === 'PrimitiveDataState' || 67 | data['_type'] === 'ObjectDataState') 68 | ) 69 | } 70 | 71 | export function instanceOfPrimitiveData(data: any): data is PrimitiveDataState { 72 | return data != null && data['_type'] === 'PrimitiveDataState' 73 | } 74 | 75 | export function instanceOfObjectData(data: any): data is ObjectDataState { 76 | return data != null && data['_type'] === 'ObjectDataState' 77 | } 78 | -------------------------------------------------------------------------------- /src/scripts/renderer/View/Environment/data/array/IndexRenderer.ts: -------------------------------------------------------------------------------- 1 | export class IndexRenderer { 2 | element: HTMLDivElement 3 | 4 | constructor() { 5 | this.element = document.createElement('div') 6 | this.element.classList.add('data-array-index') 7 | } 8 | 9 | setState(index: number, dataElement: HTMLElement, environmentElement: HTMLElement) { 10 | this.element.innerText = `${index}` 11 | 12 | const dataBbox = dataElement.getBoundingClientRect() 13 | const environmentBbox = environmentElement.getBoundingClientRect() 14 | const delta = dataBbox.x - environmentBbox.x 15 | 16 | let ratio = 1 17 | this.element.style.top = `${18}px` 18 | this.element.style.left = `${(1 / ratio) * delta}px` 19 | } 20 | 21 | destroy() { 22 | this.element.remove() 23 | } 24 | } 25 | -------------------------------------------------------------------------------- /src/scripts/renderer/View/Environment/data/literal/LiteralRenderer.ts: -------------------------------------------------------------------------------- 1 | import { FrameInfo } from '../../../../../environment/EnvironmentState' 2 | import { clone } from '../../../../../utilities/objects' 3 | import { DataRenderer } from '../DataRenderer' 4 | import { DataState, TransformStyles } from '../DataState' 5 | 6 | export class LiteralRenderer extends DataRenderer { 7 | prevRenderStyles!: TransformStyles 8 | 9 | constructor() { 10 | super() 11 | this.element.classList.add('data-literal') 12 | } 13 | 14 | setState(data: DataState, frame: FrameInfo) { 15 | this.element.innerText = data.value?.toString() ?? 'null' 16 | 17 | if (typeof data.value == 'string') { 18 | this.element.classList.add('data-literal-string') 19 | this.element.classList.remove('data-literal-boolean') 20 | this.element.classList.remove('true') 21 | this.element.classList.remove('false') 22 | 23 | this.element.innerText = `'${data.value.toString()}'` 24 | } else if (typeof data.value == 'boolean') { 25 | this.element.classList.add('data-literal-boolean') 26 | this.element.classList.remove('data-literal-string') 27 | 28 | if (data.value) { 29 | this.element.classList.add('true') 30 | this.element.classList.remove('false') 31 | } else { 32 | this.element.classList.add('false') 33 | this.element.classList.remove('true') 34 | } 35 | } else { 36 | this.element.classList.remove('data-literal-string') 37 | this.element.classList.remove('data-literal-boolean') 38 | 39 | this.element.classList.remove('true') 40 | this.element.classList.remove('false') 41 | } 42 | 43 | this._cachedState = clone(data) 44 | } 45 | } 46 | -------------------------------------------------------------------------------- /src/scripts/renderer/View/Environment/data/object/ObjectItemRenderer.ts: -------------------------------------------------------------------------------- 1 | import { FrameInfo } from '../../../../../environment/EnvironmentState' 2 | import { createDataRenderer } from '../../EnvironmentRenderer' 3 | import { DataRenderer } from '../DataRenderer' 4 | import { DataState } from '../DataState' 5 | 6 | export class ObjectItemRenderer { 7 | keyRenderer: HTMLElement 8 | dataRenderer: DataRenderer 9 | 10 | element: HTMLElement 11 | 12 | constructor() { 13 | this.element = document.createElement('div') 14 | this.element.classList.add('data-object-item') 15 | this.keyRenderer = document.createElement('div') 16 | this.keyRenderer.classList.add('data-object-item-key') 17 | } 18 | 19 | setState(key: string, value: DataState, frame: FrameInfo) { 20 | this.dataRenderer = createDataRenderer(value) 21 | this.dataRenderer.setState(value, frame) 22 | 23 | this.keyRenderer.innerText = key 24 | 25 | this.element.innerHTML = '' 26 | this.element.append(this.keyRenderer) 27 | this.element.append(this.dataRenderer.element) 28 | } 29 | 30 | destroy() { 31 | this.dataRenderer.destroy() 32 | this.element.remove() 33 | } 34 | } 35 | -------------------------------------------------------------------------------- /src/scripts/renderer/View/Environment/data/object/ObjectRenderer.ts: -------------------------------------------------------------------------------- 1 | import { FrameInfo } from '../../../../../environment/EnvironmentState' 2 | import { DataRenderer } from '../DataRenderer' 3 | import { DataState } from '../DataState' 4 | import { ObjectItemRenderer } from './ObjectItemRenderer' 5 | 6 | export class ObjectRenderer extends DataRenderer { 7 | dataRenderers: { 8 | [id: string]: ObjectItemRenderer 9 | } = {} 10 | 11 | constructor() { 12 | super() 13 | 14 | this.element.classList.add('object-renderer') 15 | } 16 | 17 | findRendererById(id: string): { 18 | data: DataRenderer 19 | column: HTMLElement 20 | } | null { 21 | console.log(id, Object.keys(this.dataRenderers)) 22 | 23 | for (const [key, renderer] of Object.entries(this.dataRenderers)) { 24 | if (key.split('@')[0] == id) { 25 | return { data: renderer.dataRenderer, column: null! } 26 | } 27 | } 28 | 29 | for (const [containerId, containerRenderer] of Object.entries(this.dataRenderers)) { 30 | if (containerRenderer.dataRenderer instanceof ObjectRenderer) { 31 | const item = containerRenderer.dataRenderer.findRendererById(id) 32 | if (item != null) { 33 | return item 34 | } 35 | } 36 | } 37 | 38 | return null 39 | } 40 | 41 | setState(data: DataState, frame: FrameInfo) { 42 | const { actionId } = frame 43 | const obj = data.value as { [id: string]: DataState } 44 | const hits = new Set() 45 | 46 | // Need to do both data *and* key as separate things 47 | for (const [key, value] of Object.entries(obj)) { 48 | const storeKey = `${value.id}@${key}` 49 | 50 | // Create renderer if not there 51 | if (!(storeKey in this.dataRenderers)) { 52 | const renderer = new ObjectItemRenderer() 53 | this.dataRenderers[storeKey] = renderer 54 | } 55 | 56 | hits.add(storeKey) 57 | this.element.append(this.dataRenderers[storeKey].element) 58 | this.dataRenderers[storeKey].setState(key, value, actionId) 59 | } 60 | 61 | // Remove data that are no longer in the view 62 | for (const id of Object.keys(this.dataRenderers)) { 63 | if (!hits.has(id)) { 64 | const renderer = this.dataRenderers[id] 65 | renderer.destroy() 66 | delete this.dataRenderers[id] 67 | } 68 | } 69 | } 70 | 71 | destroy(): void { 72 | super.destroy() 73 | for (const id of Object.keys(this.dataRenderers)) { 74 | const renderer = this.dataRenderers[id] 75 | renderer.destroy() 76 | } 77 | this.dataRenderers = {} 78 | } 79 | 80 | getAllChildRenderers() { 81 | let renderers: { [id: string]: DataRenderer } = {} 82 | for (const [id, item] of Object.entries(this.dataRenderers)) { 83 | renderers[id.split('@')[0]] = item.dataRenderer 84 | renderers = { 85 | ...renderers, 86 | ...item.dataRenderer.getAllChildRenderers(), 87 | } 88 | } 89 | 90 | // console.log('The whole thing:', renderers, this.dataRenderers) 91 | return renderers 92 | } 93 | 94 | select(selection: Set) { 95 | // this.selection = new Set([...selection, ...this.selection]) 96 | // for (const id of Object.keys(this.dataRenderers)) { 97 | // const renderer = this.dataRenderers[id].renderer 98 | // if (this.selection.has(id)) { 99 | // renderer.select(this.selection) 100 | // } 101 | // } 102 | } 103 | 104 | deselect() { 105 | // for (const id of Object.keys(this.dataRenderers)) { 106 | // const renderer = this.dataRenderers[id].renderer 107 | // renderer.deselect() 108 | // } 109 | } 110 | } 111 | -------------------------------------------------------------------------------- /src/scripts/renderer/View/Environment/data/reference/FunctionRenderer.ts: -------------------------------------------------------------------------------- 1 | import { FrameInfo } from '../../../../../environment/EnvironmentState' 2 | import { DataRenderer } from '../DataRenderer' 3 | import { DataState, TransformStyles } from '../DataState' 4 | 5 | export class FunctionRenderer extends DataRenderer { 6 | prevRenderStyles: TransformStyles 7 | 8 | constructor() { 9 | super() 10 | this.element.classList.add('data-function') 11 | } 12 | 13 | setState(data: DataState, frame: FrameInfo) { 14 | this.element.innerText = 'f' 15 | } 16 | } 17 | -------------------------------------------------------------------------------- /src/scripts/renderer/View/Environment/data/reference/ReferenceRenderer.ts: -------------------------------------------------------------------------------- 1 | import { FrameInfo } from '../../../../../environment/EnvironmentState' 2 | import { clone } from '../../../../../utilities/objects' 3 | import { DataRenderer } from '../DataRenderer' 4 | import { DataState, TransformStyles } from '../DataState' 5 | 6 | export class ReferenceRenderer extends DataRenderer { 7 | prevRenderStyles!: TransformStyles 8 | 9 | constructor() { 10 | super() 11 | this.element.classList.add('data-function') 12 | } 13 | 14 | setState(data: DataState, frame: FrameInfo) { 15 | this.element.innerText = 'Ref' 16 | this._cachedState = clone(data) 17 | } 18 | } 19 | -------------------------------------------------------------------------------- /src/scripts/renderer/View/Environment/identifier/IdentifierRenderer.ts: -------------------------------------------------------------------------------- 1 | import { IdentifierState } from '../../../../environment/EnvironmentState' 2 | import { clone } from '../../../../utilities/objects' 3 | import { Ticker } from '../../../../utilities/Ticker' 4 | 5 | export class IdentifierRenderer { 6 | element: HTMLElement 7 | environmentReference: HTMLElement 8 | 9 | private _tickerID: string 10 | _cachedState: IdentifierState | null = null 11 | 12 | constructor() { 13 | this.element = document.createElement('div') 14 | this.element.classList.add('identifier') 15 | 16 | this._tickerID = Ticker.instance.registerTick(this.tick.bind(this)) 17 | } 18 | 19 | setState(state: IdentifierState) { 20 | this.element.innerHTML = `${state.name}` 21 | 22 | this._cachedState = clone(state) 23 | } 24 | 25 | tick(dt: number) {} 26 | 27 | destroy() { 28 | Ticker.instance.removeTickFrom(this._tickerID) 29 | this.element.remove() 30 | } 31 | 32 | /* ------------------------ Focus ----------------------- */ 33 | 34 | secondaryFocus() { 35 | this.element.classList.add('secondary-focused') 36 | } 37 | 38 | focus() { 39 | this.element.classList.remove('secondary-focused') 40 | } 41 | 42 | clearFocus() { 43 | this.element.classList.remove('secondary-focused') 44 | } 45 | } 46 | -------------------------------------------------------------------------------- /src/scripts/transpiler/Compiler.ts: -------------------------------------------------------------------------------- 1 | //@ts-check 2 | 3 | import * as ESTree from 'estree' 4 | import { EnvironmentState } from '../environment/EnvironmentState' 5 | import { ExecutionGraph } from '../execution/graph/ExecutionGraph' 6 | import { ExecutionContext, NodeData } from '../execution/primitive/ExecutionNode' 7 | import { ArrayExpression } from './Expressions/Array/ArrayExpression' 8 | import { AssignmentExpression } from './Expressions/BinaryOperations/AssigmentExpression' 9 | import { BinaryExpression } from './Expressions/BinaryOperations/BinaryExpression/BinaryExpression' 10 | import { LogicalExpression } from './Expressions/BinaryOperations/LogicalExpression' 11 | import { MemberExpression } from './Expressions/BinaryOperations/MemberExpression' 12 | import { CallExpression } from './Expressions/CallExpression' 13 | import { ObjectExpression } from './Expressions/ObjectExpression' 14 | import { UnaryExpression } from './Expressions/UnaryOperations/UnaryExpression' 15 | import { UpdateExpression } from './Expressions/UnaryOperations/UpdateExpression' 16 | import { FunctionCall } from './Functions/FunctionCall' 17 | import { FunctionDeclaration } from './Functions/FunctionDeclaration' 18 | import { ReturnStatement } from './Functions/ReturnStatement' 19 | import { Identifier } from './Identifier' 20 | import { Literal } from './Literal' 21 | import { BlockStatement } from './Statements/BlockStatement' 22 | import { IfStatement } from './Statements/Choice/IfStatement' 23 | import { ExpressionStatement } from './Statements/ExpressionStatement' 24 | import { ForStatement } from './Statements/Loops/ForStatement' 25 | import { WhileStatement } from './Statements/Loops/WhileStatement' 26 | import { Program } from './Statements/Program' 27 | import { VariableDeclaration } from './Statements/VariableDeclaration' 28 | import { VariableDeclarator } from './Statements/VariableDeclarator' 29 | 30 | /* ------------------------------------------------------ */ 31 | /* Compiles an execution graph from an ESTree AST. */ 32 | /* ------------------------------------------------------ */ 33 | export class Compiler { 34 | static compile(ast: ESTree.Node, environment: EnvironmentState, context: ExecutionContext): ExecutionGraph { 35 | const mapping = { 36 | VariableDeclarator, 37 | 38 | BinaryExpression, 39 | ArrayExpression, 40 | ObjectExpression, 41 | MemberExpression, 42 | ExpressionStatement, 43 | VariableDeclaration, 44 | AssignmentExpression, 45 | Identifier, 46 | Literal, 47 | Program, 48 | BlockStatement, 49 | ForStatement, 50 | ReturnStatement, 51 | 52 | IfStatement, 53 | FunctionCall, 54 | CallExpression, 55 | LogicalExpression, 56 | UpdateExpression, 57 | 58 | WhileStatement, 59 | FunctionDeclaration, 60 | 61 | UnaryExpression, 62 | } 63 | 64 | let type = `${ast.type}` 65 | if (!(type in mapping)) { 66 | throw new Error(`Unknown type ${ast.type}`) 67 | } 68 | 69 | const node = mapping[type](ast, environment, context) 70 | return node 71 | } 72 | } 73 | 74 | /* ------------------------------------------------------ */ 75 | /* Helpers */ 76 | /* ------------------------------------------------------ */ 77 | 78 | export function getNodeData(node: ESTree.Node | ESTree.Node[], preLabel?: string, hasLineBreak?: boolean): NodeData { 79 | if (Array.isArray(node)) { 80 | return { 81 | location: getUnionOfLocations(node.map((n) => n.loc as ESTree.SourceLocation)), 82 | preLabel: preLabel, 83 | type: preLabel, 84 | hasLineBreak: hasLineBreak ?? false, 85 | } 86 | } else { 87 | return { 88 | location: node.loc as ESTree.SourceLocation | undefined, 89 | type: node.type, 90 | preLabel, 91 | hasLineBreak: hasLineBreak ?? false, 92 | } 93 | } 94 | } 95 | 96 | export function getUnionOfLocations(locs: ESTree.SourceLocation[]): ESTree.SourceLocation { 97 | let start = locs[0].start 98 | let end = locs[0].end 99 | 100 | for (const loc of locs) { 101 | if (loc.start.line < start.line) { 102 | start = loc.start 103 | } 104 | 105 | if (loc.end.line > end.line) { 106 | end = loc.end 107 | } 108 | 109 | if (loc.start.column < start.column) { 110 | start = loc.start 111 | } 112 | 113 | if (loc.end.column > end.column) { 114 | end = loc.end 115 | } 116 | } 117 | 118 | return { 119 | start, 120 | end, 121 | } 122 | } 123 | -------------------------------------------------------------------------------- /src/scripts/transpiler/Expressions/Array/ArrayExpression.ts: -------------------------------------------------------------------------------- 1 | import * as ESTree from 'estree' 2 | import { cleanUpRegister } from '../../../environment/environment' 3 | import { AccessorType, EnvironmentState } from '../../../environment/EnvironmentState' 4 | import { addVertex, applyExecutionNode } from '../../../execution/execution' 5 | import { createExecutionGraph, ExecutionGraph } from '../../../execution/graph/ExecutionGraph' 6 | import { arrayStartAnimation } from '../../../execution/primitive/Container/ArrayStartAnimation' 7 | import { moveAndPlaceAnimation } from '../../../execution/primitive/Data/MoveAndPlaceAnimation' 8 | import { ExecutionContext } from '../../../execution/primitive/ExecutionNode' 9 | import { clone } from '../../../utilities/objects' 10 | import { Compiler, getNodeData } from '../../Compiler' 11 | 12 | export function ArrayExpression(ast: ESTree.ArrayExpression, environment: EnvironmentState, context: ExecutionContext) { 13 | const graph: ExecutionGraph = createExecutionGraph(getNodeData(ast)) 14 | graph.precondition = clone(environment) 15 | 16 | const start = arrayStartAnimation(context.outputRegister) 17 | addVertex(graph, start, { nodeData: getNodeData(ast, 'start') }) 18 | applyExecutionNode(start, environment) 19 | 20 | for (let i = 0; i < ast.elements.length; i++) { 21 | // Create a register that'll point to the RHS 22 | const register = [ 23 | { 24 | type: AccessorType.Register, 25 | value: `${graph.id}_ArrayExpression_${i}`, 26 | }, 27 | ] 28 | 29 | // Array element 30 | const animation = Compiler.compile(ast.elements[i], environment, { 31 | ...context, 32 | outputRegister: register, 33 | }) 34 | addVertex(graph, animation, { 35 | nodeData: getNodeData(ast.elements[i], `element ${i}`), 36 | }) 37 | 38 | const place = moveAndPlaceAnimation(register, [ 39 | ...context.outputRegister, 40 | { type: AccessorType.Index, value: i.toString() }, 41 | ]) 42 | addVertex(graph, place, { nodeData: getNodeData(ast.elements[i]) }) 43 | applyExecutionNode(place, environment) 44 | 45 | cleanUpRegister(environment, register[0].value) 46 | } 47 | 48 | // const end = arrayEndAnimation(context.outputRegister); 49 | // addVertex(graph, end, getNodeData(ast)); 50 | // applyExecution(end, view); 51 | 52 | graph.postcondition = clone(environment) 53 | return graph 54 | } 55 | -------------------------------------------------------------------------------- /src/scripts/transpiler/Expressions/BinaryOperations/AssigmentExpression.ts: -------------------------------------------------------------------------------- 1 | import * as ESTree from 'estree' 2 | import { cleanUpRegister } from '../../../environment/environment' 3 | import { AccessorType, EnvironmentState } from '../../../environment/EnvironmentState' 4 | import { addVertex, applyExecutionNode } from '../../../execution/execution' 5 | import { createExecutionGraph, ExecutionGraph } from '../../../execution/graph/ExecutionGraph' 6 | import { moveAndPlaceAnimation } from '../../../execution/primitive/Data/MoveAndPlaceAnimation' 7 | import { ExecutionContext } from '../../../execution/primitive/ExecutionNode' 8 | import { clone } from '../../../utilities/objects' 9 | import { Compiler, getNodeData } from '../../Compiler' 10 | 11 | export function AssignmentExpression( 12 | ast: ESTree.AssignmentExpression, 13 | environment: EnvironmentState, 14 | context: ExecutionContext 15 | ) { 16 | const graph: ExecutionGraph = createExecutionGraph(getNodeData(ast)) 17 | graph.precondition = clone(environment) 18 | 19 | const leftRegister = [{ type: AccessorType.Register, value: `${graph.id}_Assignment_Left` }] 20 | const rightRegister = [{ type: AccessorType.Register, value: `${graph.id}_Assignment_Right` }] 21 | 22 | // Put the *location* of the LHS in the left register 23 | const left = Compiler.compile(ast.left, environment, { 24 | ...context, 25 | outputRegister: leftRegister, 26 | feed: true, 27 | }) 28 | addVertex(graph, left, { nodeData: getNodeData(ast.left, 'AssignmentIdentifier') }) 29 | 30 | // RHS should be in the stack 31 | const right = Compiler.compile(ast.right, environment, { 32 | ...context, 33 | outputRegister: rightRegister, 34 | }) 35 | addVertex(graph, right, { nodeData: getNodeData(ast.right) }) 36 | 37 | const place = moveAndPlaceAnimation(rightRegister, leftRegister) 38 | addVertex(graph, place, { nodeData: getNodeData(ast.left, 'AssignmentEquals') }) 39 | applyExecutionNode(place, environment) 40 | 41 | cleanUpRegister(environment, leftRegister[0].value) 42 | cleanUpRegister(environment, rightRegister[0].value) 43 | 44 | graph.postcondition = clone(environment) 45 | return graph 46 | } 47 | -------------------------------------------------------------------------------- /src/scripts/transpiler/Expressions/BinaryOperations/BinaryExpression/BinaryExpression.ts: -------------------------------------------------------------------------------- 1 | import * as ESTree from 'estree' 2 | import { cleanUpRegister } from '../../../../environment/environment' 3 | import { AccessorType, EnvironmentState } from '../../../../environment/EnvironmentState' 4 | import { addVertex, applyExecutionNode } from '../../../../execution/execution' 5 | import { createExecutionGraph } from '../../../../execution/graph/ExecutionGraph' 6 | import { binaryExpressionEvaluate } from '../../../../execution/primitive/Binary/BinaryExpressionEvaluate' 7 | import { ExecutionContext } from '../../../../execution/primitive/ExecutionNode' 8 | import { clone } from '../../../../utilities/objects' 9 | import { Compiler, getNodeData } from '../../../Compiler' 10 | 11 | export function BinaryExpression( 12 | ast: ESTree.BinaryExpression, 13 | environment: EnvironmentState, 14 | context: ExecutionContext 15 | ) { 16 | const graph = createExecutionGraph(getNodeData(ast)) 17 | graph.precondition = clone(environment) 18 | 19 | const leftRegister = [ 20 | { 21 | type: AccessorType.Register, 22 | value: `${graph.id}_BinaryExpressionLeft`, 23 | }, 24 | ] 25 | const rightRegister = [ 26 | { 27 | type: AccessorType.Register, 28 | value: `${graph.id}_BinaryExpressionRight`, 29 | }, 30 | ] 31 | 32 | const left = Compiler.compile(ast.left, environment, { 33 | ...context, 34 | outputRegister: leftRegister, 35 | }) 36 | 37 | addVertex(graph, left, { nodeData: getNodeData(ast.left, 'Left') }) 38 | 39 | // Fix left position 40 | // if (left.nodeData.location != null && ast.loc != null) { 41 | // // Put it at the start of the binary expression 42 | // left.nodeData.location.start.column = ast.loc.start.column 43 | 44 | // // Find how long the left is 45 | 46 | // } 47 | 48 | const right = Compiler.compile(ast.right, environment, { 49 | ...context, 50 | outputRegister: rightRegister, 51 | }) 52 | addVertex(graph, right, { nodeData: getNodeData(ast.right, 'Right') }) 53 | 54 | const evaluate = binaryExpressionEvaluate(leftRegister, rightRegister, ast.operator, context.outputRegister) 55 | 56 | const location = clone(ast.left.loc) as ESTree.SourceLocation 57 | location.end = (clone(ast.right.loc) as ESTree.SourceLocation).start 58 | location.start = (clone(ast.left.loc) as ESTree.SourceLocation).end 59 | 60 | addVertex(graph, evaluate, { 61 | nodeData: { 62 | ...getNodeData(ast, 'Evaluate'), 63 | type: 'BinaryExpressionEvaluate', 64 | location, 65 | }, 66 | }) 67 | applyExecutionNode(evaluate, environment) 68 | 69 | cleanUpRegister(environment, leftRegister[0].value) 70 | cleanUpRegister(environment, rightRegister[0].value) 71 | 72 | graph.postcondition = clone(environment) 73 | return graph 74 | } 75 | -------------------------------------------------------------------------------- /src/scripts/transpiler/Expressions/BinaryOperations/LogicalExpression.ts: -------------------------------------------------------------------------------- 1 | import * as ESTree from 'estree' 2 | import { cleanUpRegister, resolvePath } from '../../../environment/environment' 3 | import { AccessorType, EnvironmentState } from '../../../environment/EnvironmentState' 4 | import { addVertex, applyExecutionNode } from '../../../execution/execution' 5 | import { createExecutionGraph } from '../../../execution/graph/ExecutionGraph' 6 | import { logicalExpressionEvaluate } from '../../../execution/primitive/Binary/LogicalExpressionEvaluate' 7 | import { ExecutionContext } from '../../../execution/primitive/ExecutionNode' 8 | import { DataState } from '../../../renderer/View/Environment/data/DataState' 9 | import { clone } from '../../../utilities/objects' 10 | import { Compiler, getNodeData } from '../../Compiler' 11 | 12 | export function LogicalExpression( 13 | ast: ESTree.LogicalExpression, 14 | environment: EnvironmentState, 15 | context: ExecutionContext 16 | ) { 17 | const graph = createExecutionGraph(getNodeData(ast)) 18 | graph.precondition = clone(environment) 19 | 20 | const leftRegister = [ 21 | { 22 | type: AccessorType.Register, 23 | value: `${graph.id}_BinaryExpressionLeft`, 24 | }, 25 | ] 26 | 27 | const left = Compiler.compile(ast.left, environment, { 28 | ...context, 29 | outputRegister: leftRegister, 30 | }) 31 | addVertex(graph, left, { nodeData: getNodeData(ast) }) 32 | 33 | const leftValue = resolvePath(environment, leftRegister, null) as DataState 34 | 35 | let shortCircuit = false 36 | 37 | // Short circuit 38 | if (ast.operator == '&&' && leftValue.value == false) { 39 | shortCircuit = true 40 | } else if (ast.operator == '||' && leftValue.value == true) { 41 | shortCircuit = true 42 | } 43 | 44 | const rightRegister = [ 45 | { 46 | type: AccessorType.Register, 47 | value: `${graph.id}_BinaryExpressionRight`, 48 | }, 49 | ] 50 | 51 | if (!shortCircuit) { 52 | const right = Compiler.compile(ast.right, environment, { 53 | ...context, 54 | outputRegister: rightRegister, 55 | }) 56 | addVertex(graph, right, { nodeData: getNodeData(ast) }) 57 | } 58 | 59 | const evaluate = logicalExpressionEvaluate( 60 | leftRegister, 61 | rightRegister, 62 | shortCircuit, 63 | ast.operator, 64 | context.outputRegister 65 | ) 66 | 67 | // addVertex(graph, evaluate, { nodeData: getNodeData(ast) }) 68 | applyExecutionNode(evaluate, environment) 69 | 70 | cleanUpRegister(environment, leftRegister[0].value) 71 | cleanUpRegister(environment, rightRegister[0].value) 72 | 73 | graph.postcondition = clone(environment) 74 | return graph 75 | } 76 | -------------------------------------------------------------------------------- /src/scripts/transpiler/Expressions/BinaryOperations/MemberExpression.ts: -------------------------------------------------------------------------------- 1 | import * as ESTree from 'estree' 2 | import { convertIdentifierToLiteral } from '../../../environment/data' 3 | import { cleanUpRegister } from '../../../environment/environment' 4 | import { AccessorType, EnvironmentState } from '../../../environment/EnvironmentState' 5 | import { addVertex, applyExecutionNode } from '../../../execution/execution' 6 | import { createExecutionGraph, ExecutionGraph } from '../../../execution/graph/ExecutionGraph' 7 | import { findMember } from '../../../execution/primitive/Data/FindMember' 8 | import { getMember } from '../../../execution/primitive/Data/GetMember' 9 | import { ExecutionContext } from '../../../execution/primitive/ExecutionNode' 10 | import { clone } from '../../../utilities/objects' 11 | import { Compiler, getNodeData } from '../../Compiler' 12 | 13 | /** 14 | * 15 | * @param ast 16 | * @param view 17 | * @param context 18 | * @returns 19 | */ 20 | export function MemberExpression( 21 | ast: ESTree.MemberExpression, 22 | environment: EnvironmentState, 23 | context: ExecutionContext 24 | ) { 25 | const graph: ExecutionGraph = createExecutionGraph(getNodeData(ast)) 26 | graph.precondition = clone(environment) 27 | 28 | // Create a register which'll *point* to the location of object 29 | const objectRegister = [{ type: AccessorType.Register, value: `${graph.id}_Object` }] 30 | const object = Compiler.compile(ast.object, environment, { 31 | ...context, 32 | feed: false, 33 | outputRegister: objectRegister, 34 | }) 35 | addVertex(graph, object, { nodeData: getNodeData(ast.object, 'Object') }) 36 | 37 | // Create a register that'll point to the location of computed property 38 | const propertyRegister = [{ type: AccessorType.Register, value: `${graph.id}_Property` }] 39 | 40 | // Something like obj[i], or obj['x'] 41 | const property = Compiler.compile( 42 | ast.computed ? ast.property : convertIdentifierToLiteral(ast.property as ESTree.Identifier), 43 | environment, 44 | { 45 | ...context, 46 | outputRegister: propertyRegister, 47 | feed: false, 48 | } 49 | ) 50 | addVertex(graph, property, { 51 | nodeData: getNodeData(ast.property, 'Property'), 52 | }) 53 | 54 | // Compute the result 55 | if (context.feed) { 56 | const find = findMember(objectRegister, propertyRegister, context.outputRegister) 57 | addVertex(graph, find, { nodeData: getNodeData(ast, 'Find Member') }) 58 | applyExecutionNode(find, environment) 59 | } else { 60 | const member = getMember(objectRegister, propertyRegister, context.outputRegister) 61 | addVertex(graph, member, { nodeData: getNodeData(ast, 'Get Member') }) 62 | applyExecutionNode(member, environment) 63 | } 64 | 65 | cleanUpRegister(environment, objectRegister[0].value) 66 | cleanUpRegister(environment, propertyRegister[0].value) 67 | 68 | graph.postcondition = clone(environment) 69 | return graph 70 | } 71 | -------------------------------------------------------------------------------- /src/scripts/transpiler/Expressions/ObjectExpression.ts: -------------------------------------------------------------------------------- 1 | import { generate } from 'astring' 2 | import * as ESTree from 'estree' 3 | import { cleanUpRegister } from '../../environment/environment' 4 | import { AccessorType, EnvironmentState } from '../../environment/EnvironmentState' 5 | import { addVertex, applyExecutionNode } from '../../execution/execution' 6 | import { createExecutionGraph, ExecutionGraph } from '../../execution/graph/ExecutionGraph' 7 | import { objectStartAnimation } from '../../execution/primitive/Container/ObjectStartAnimation' 8 | import { moveAndPlaceAnimation } from '../../execution/primitive/Data/MoveAndPlaceAnimation' 9 | import { ExecutionContext } from '../../execution/primitive/ExecutionNode' 10 | import { clone } from '../../utilities/objects' 11 | import { Compiler, getNodeData } from '../Compiler' 12 | 13 | export function ObjectExpression( 14 | ast: ESTree.ObjectExpression, 15 | environment: EnvironmentState, 16 | context: ExecutionContext 17 | ) { 18 | const graph: ExecutionGraph = createExecutionGraph(getNodeData(ast)) 19 | graph.precondition = clone(environment) 20 | 21 | const start = objectStartAnimation(context.outputRegister) 22 | addVertex(graph, start, { nodeData: getNodeData(ast, 'start') }) 23 | applyExecutionNode(start, environment) 24 | 25 | for (let i = 0; i < ast.properties.length; i++) { 26 | const property = ast.properties[i] 27 | if (property.type === 'SpreadElement') { 28 | throw new Error("Spread elements aren't supported yet") 29 | } 30 | 31 | const register = [ 32 | { 33 | type: AccessorType.Register, 34 | value: `${graph.id}_ArrayExpression_${i}`, 35 | }, 36 | ] 37 | 38 | // Object element 39 | const animation = Compiler.compile(property.value, environment, { 40 | ...context, 41 | outputRegister: register, 42 | }) 43 | addVertex(graph, animation, { 44 | nodeData: getNodeData(property.value, `${property.key}`), 45 | }) 46 | 47 | const place = moveAndPlaceAnimation(register, [ 48 | ...context.outputRegister, 49 | { type: AccessorType.Index, value: generate(property.key) }, 50 | ]) 51 | addVertex(graph, place, { nodeData: getNodeData(property) }) 52 | applyExecutionNode(place, environment) 53 | 54 | cleanUpRegister(environment, register[0].value) 55 | } 56 | 57 | graph.postcondition = clone(environment) 58 | return graph 59 | } 60 | -------------------------------------------------------------------------------- /src/scripts/transpiler/Expressions/UnaryOperations/UnaryExpression.ts: -------------------------------------------------------------------------------- 1 | import * as ESTree from 'estree' 2 | import { cleanUpRegister } from '../../../environment/environment' 3 | import { AccessorType, EnvironmentState } from '../../../environment/EnvironmentState' 4 | import { addVertex, applyExecutionNode } from '../../../execution/execution' 5 | import { createExecutionGraph, ExecutionGraph } from '../../../execution/graph/ExecutionGraph' 6 | import { ExecutionContext } from '../../../execution/primitive/ExecutionNode' 7 | import { unaryExpressionEvaluate } from '../../../execution/primitive/Unary/UnaryExpressionEvaluate' 8 | import { clone } from '../../../utilities/objects' 9 | import { Compiler, getNodeData } from '../../Compiler' 10 | 11 | export function UnaryExpression( 12 | ast: ESTree.UnaryExpression, 13 | environment: EnvironmentState, 14 | context: ExecutionContext 15 | ) { 16 | const graph: ExecutionGraph = createExecutionGraph(getNodeData(ast)) 17 | graph.precondition = clone(environment) 18 | 19 | const argRegister = [{ type: AccessorType.Register, value: `${graph.id}_UpdateExpr` }] 20 | 21 | const argument = Compiler.compile(ast.argument, environment, { 22 | ...context, 23 | outputRegister: argRegister, 24 | }) 25 | addVertex(graph, argument, { nodeData: getNodeData(ast.argument) }) 26 | 27 | const evaluate = unaryExpressionEvaluate(argRegister, ast.operator, context.outputRegister) 28 | addVertex(graph, evaluate, { nodeData: getNodeData(ast, 'Evaluate') }) 29 | applyExecutionNode(evaluate, environment) 30 | 31 | cleanUpRegister(environment, argRegister[0].value) 32 | 33 | graph.postcondition = clone(environment) 34 | return graph 35 | } 36 | -------------------------------------------------------------------------------- /src/scripts/transpiler/Expressions/UnaryOperations/UpdateExpression.ts: -------------------------------------------------------------------------------- 1 | import * as ESTree from 'estree' 2 | import { cleanUpRegister } from '../../../environment/environment' 3 | import { AccessorType, EnvironmentState } from '../../../environment/EnvironmentState' 4 | import { addVertex, applyExecutionNode } from '../../../execution/execution' 5 | import { createExecutionGraph, ExecutionGraph } from '../../../execution/graph/ExecutionGraph' 6 | import { updateAnimation } from '../../../execution/primitive/Data/UpdateAnimation' 7 | import { ExecutionContext } from '../../../execution/primitive/ExecutionNode' 8 | import { clone } from '../../../utilities/objects' 9 | import { Compiler, getNodeData } from '../../Compiler' 10 | 11 | export function UpdateExpression( 12 | ast: ESTree.UpdateExpression, 13 | environment: EnvironmentState, 14 | context: ExecutionContext 15 | ) { 16 | const graph: ExecutionGraph = createExecutionGraph(getNodeData(ast)) 17 | graph.precondition = clone(environment) 18 | 19 | const argRegister = [{ type: AccessorType.Register, value: `${graph.id}_UpdateExpr` }] 20 | 21 | // Put the *location* of argument in a register 22 | const argument = Compiler.compile(ast.argument, environment, { 23 | ...context, 24 | outputRegister: argRegister, 25 | feed: true, 26 | }) 27 | addVertex(graph, argument, { nodeData: getNodeData(ast.argument) }) 28 | 29 | if (context.outputRegister != null) { 30 | // Return a copy of it before applying update 31 | const output = Compiler.compile(ast.argument, environment, { 32 | ...context, 33 | outputRegister: context.outputRegister, 34 | feed: false, 35 | }) 36 | addVertex(graph, output, { nodeData: getNodeData(ast.argument) }) 37 | } 38 | 39 | // Apply the operation 40 | const update = updateAnimation(argRegister, ast.operator) 41 | addVertex(graph, update, { nodeData: getNodeData(ast.argument) }) 42 | applyExecutionNode(update, environment) 43 | 44 | cleanUpRegister(environment, argRegister[0].value) 45 | 46 | graph.postcondition = clone(environment) 47 | return graph 48 | } 49 | -------------------------------------------------------------------------------- /src/scripts/transpiler/Functions/FunctionCall.ts: -------------------------------------------------------------------------------- 1 | import * as ESTree from 'estree' 2 | import { EnvironmentState } from '../../environment/EnvironmentState' 3 | import { addVertex, applyExecutionNode } from '../../execution/execution' 4 | import { createExecutionGraph, ExecutionGraph } from '../../execution/graph/ExecutionGraph' 5 | import { bindAnimation } from '../../execution/primitive/Binding/BindAnimation' 6 | import { ExecutionContext } from '../../execution/primitive/ExecutionNode' 7 | import { createScopeAnimation } from '../../execution/primitive/Scope/CreateScopeAnimation' 8 | import { popScopeAnimation } from '../../execution/primitive/Scope/PopScopeAnimation' 9 | import { clone } from '../../utilities/objects' 10 | import { getNodeData } from '../Compiler' 11 | import { BlockStatement, ScopeType } from '../Statements/BlockStatement' 12 | 13 | export function FunctionCall( 14 | ast: ESTree.FunctionDeclaration, 15 | environment: EnvironmentState, 16 | context: ExecutionContext 17 | ) { 18 | const graph: ExecutionGraph = createExecutionGraph(getNodeData(ast)) 19 | graph.precondition = clone(environment) 20 | 21 | // Create a scope @TODO: HARD SCOPE 22 | const createScope = createScopeAnimation(ScopeType.Hard) 23 | // addVertex(graph, createScope, { nodeData: getNodeData(ast) }) 24 | applyExecutionNode(createScope, environment) 25 | 26 | // Bind arguments 27 | const args: ExecutionGraph = createExecutionGraph(getNodeData(ast)) 28 | args.precondition = clone(environment) 29 | 30 | for (let i = 0; i < ast.params.length; i++) { 31 | const param = ast.params[i] as ESTree.Identifier 32 | const argRegister = context.args[i] 33 | 34 | const bind = bindAnimation(param.name, argRegister) 35 | addVertex(args, bind, { nodeData: getNodeData(ast.params[i]) }) 36 | applyExecutionNode(bind, environment) 37 | } 38 | args.postcondition = clone(environment) 39 | 40 | // addVertex(graph, args, { nodeData: getNodeData(ast.params, 'Arguments') }) 41 | 42 | // Call function 43 | const body = BlockStatement(ast.body, environment, { ...context, args: null }, ScopeType.None) 44 | addVertex(graph, body, { nodeData: getNodeData(ast.body, 'Body') }) 45 | 46 | // Pop scope 47 | const popScope = popScopeAnimation() 48 | // addVertex(body, popScope, { nodeData: getNodeData(ast) }) 49 | applyExecutionNode(popScope, environment) 50 | 51 | graph.postcondition = clone(environment) 52 | body.postcondition = clone(environment) 53 | return body 54 | } 55 | -------------------------------------------------------------------------------- /src/scripts/transpiler/Functions/FunctionDeclaration.ts: -------------------------------------------------------------------------------- 1 | import * as ESTree from 'estree' 2 | import { EnvironmentState } from '../../environment/EnvironmentState' 3 | import { applyExecutionNode } from '../../execution/execution' 4 | import { bindFunctionAnimation } from '../../execution/primitive/Binding/BindFunctionAnimation' 5 | import { ExecutionContext } from '../../execution/primitive/ExecutionNode' 6 | 7 | export function FunctionDeclaration( 8 | ast: ESTree.FunctionDeclaration, 9 | environment: EnvironmentState, 10 | context: ExecutionContext 11 | ) { 12 | // const graph: ExecutionGraph = createExecutionGraph(getNodeData(ast)) 13 | // graph.precondition = clone(environment) 14 | 15 | // Allocate a place for variable that *points* to the register @TODO: support other initializations that identifier 16 | const bind = bindFunctionAnimation((ast.id as ESTree.Identifier).name, ast) 17 | // addVertex(graph, bind, { nodeData: getNodeData(ast.id) }) 18 | applyExecutionNode(bind, environment) 19 | 20 | // const FunctionCallInstance = (ast: ESTree.FunctionDeclaration, environment: EnvironmentState, context: ExecutionContext) => { 21 | // const subScope = createScopeAnimation(); 22 | 23 | // for (let i = 0; i < params.length; i++) { 24 | // const param = params[i] as ESTree.Identifier; 25 | // 26 | // const bind = bindAnimation(param.name, context.args[i], subScope); 27 | // } 28 | // }; 29 | 30 | // graph.postcondition = clone(environment) 31 | return null 32 | } 33 | -------------------------------------------------------------------------------- /src/scripts/transpiler/Functions/ReturnStatement.ts: -------------------------------------------------------------------------------- 1 | import * as ESTree from 'estree' 2 | import { cleanUpRegister } from '../../environment/environment' 3 | import { Accessor, AccessorType, EnvironmentState } from '../../environment/EnvironmentState' 4 | import { addVertex, applyExecutionNode } from '../../execution/execution' 5 | import { createExecutionGraph, ExecutionGraph } from '../../execution/graph/ExecutionGraph' 6 | import { consumeDataAnimation } from '../../execution/primitive/Data/ConsumeDataAnimation' 7 | import { ControlOutput, ExecutionContext } from '../../execution/primitive/ExecutionNode' 8 | import { returnStatementAnimation } from '../../execution/primitive/Functions/ReturnStatementAnimation' 9 | import { clone } from '../../utilities/objects' 10 | import { Compiler, getNodeData } from '../Compiler' 11 | 12 | export function ReturnStatement(ast: ESTree.ReturnStatement, environment: EnvironmentState, context: ExecutionContext) { 13 | const graph: ExecutionGraph = createExecutionGraph(getNodeData(ast)) 14 | graph.precondition = clone(environment) 15 | 16 | // Evaluate the result of argument into register 17 | 18 | let usingTemp = false 19 | let returnRegister: Accessor[] = context.returnData.register 20 | 21 | if (context.returnData.register == null) { 22 | // Temp return register 23 | returnRegister = [ 24 | { 25 | type: AccessorType.Register, 26 | value: `${graph.id}_TempReturnRegister`, 27 | }, 28 | ] 29 | 30 | usingTemp = true 31 | } 32 | 33 | const argument = Compiler.compile(ast.argument, environment, { 34 | ...context, 35 | outputRegister: returnRegister, 36 | }) 37 | addVertex(graph, argument, { nodeData: getNodeData(ast.argument) }) 38 | 39 | // Make sure the memory this register is pointing at doesn't get destroyed 40 | // when popping scopes 41 | if (usingTemp) { 42 | context.returnData.register = returnRegister 43 | } 44 | const ret = returnStatementAnimation(context.returnData) 45 | addVertex(graph, ret, { nodeData: { ...getNodeData(ast), type: 'ReturnStatementAnimation' } }) 46 | applyExecutionNode(ret, environment) 47 | 48 | // Cleanup temp return 49 | if (usingTemp) { 50 | const consume = consumeDataAnimation(returnRegister) 51 | // addVertex(graph, consume, { nodeData: getNodeData(ast) }) 52 | applyExecutionNode(consume, environment) 53 | cleanUpRegister(environment, returnRegister[0].value) 54 | 55 | context.returnData.register = null 56 | } 57 | 58 | // const special = createExecutionNode({ 59 | // ...getNodeData(ast), 60 | // type: 'ReturnSpecial', 61 | // preLabel: 'ReturnSpecial', 62 | // }) 63 | // special._reads = [ 64 | // { 65 | // location: context.returnData.register, 66 | // value: 'return', 67 | // }, 68 | // ] 69 | // special._writes = [] 70 | // special.precondition = clone(environment) 71 | // special.postcondition = clone(environment) 72 | 73 | // addVertex(graph, special, { nodeData: getNodeData(ast) }) 74 | 75 | // TODO: Does this need a move and place? 76 | // const place = moveAndPlaceAnimation(register, context.returnData.register); 77 | // addVertex(graph, place, getNodeData(ast)); 78 | //applyExecution(place, view); 79 | 80 | context.controlOutput.output = ControlOutput.Return 81 | 82 | graph.postcondition = clone(environment) 83 | return graph 84 | } 85 | -------------------------------------------------------------------------------- /src/scripts/transpiler/Identifier.ts: -------------------------------------------------------------------------------- 1 | import * as ESTree from 'estree' 2 | import { resolvePath } from '../environment/environment' 3 | import { AccessorType, EnvironmentState } from '../environment/EnvironmentState' 4 | import { addVertex, applyExecutionNode } from '../execution/execution' 5 | import { createExecutionGraph } from '../execution/graph/ExecutionGraph' 6 | import { copyDataAnimation } from '../execution/primitive/Data/CopyDataAnimation' 7 | import { copyReferenceAnimation } from '../execution/primitive/Data/CopyReferenceAnimation' 8 | import { findVariableAnimation } from '../execution/primitive/Data/FindVariableAnimation' 9 | import { ExecutionContext } from '../execution/primitive/ExecutionNode' 10 | import { DataState, instanceOfObjectData } from '../renderer/View/Environment/data/DataState' 11 | import { clone } from '../utilities/objects' 12 | import { getNodeData } from './Compiler' 13 | 14 | export function Identifier( 15 | ast: ESTree.Identifier, 16 | environment: EnvironmentState, 17 | context: ExecutionContext 18 | ) { 19 | const graph = createExecutionGraph(getNodeData(ast)) 20 | graph.precondition = clone(environment) 21 | 22 | if (context.feed) { 23 | // Feeding 24 | const reference = findVariableAnimation(ast.name, context.outputRegister) 25 | addVertex(graph, reference, { nodeData: getNodeData(ast) }) 26 | applyExecutionNode(reference, environment) 27 | } else { 28 | const data = resolvePath( 29 | environment, 30 | [{ type: AccessorType.Symbol, value: ast.name }], 31 | null 32 | ) as DataState 33 | 34 | if (instanceOfObjectData(data)) { 35 | // Create a reference to it 36 | const copyReference = copyReferenceAnimation( 37 | [{ type: AccessorType.Symbol, value: ast.name }], 38 | context.outputRegister 39 | ) 40 | addVertex(graph, copyReference, { nodeData: getNodeData(ast) }) 41 | applyExecutionNode(copyReference, environment) 42 | } else { 43 | // Create a copy of it 44 | const copy = copyDataAnimation( 45 | [{ type: AccessorType.Symbol, value: ast.name }], 46 | context.outputRegister 47 | ) 48 | addVertex(graph, copy, { nodeData: getNodeData(ast) }) 49 | applyExecutionNode(copy, environment) 50 | } 51 | } 52 | 53 | graph.postcondition = clone(environment) 54 | return graph 55 | } 56 | -------------------------------------------------------------------------------- /src/scripts/transpiler/Literal.ts: -------------------------------------------------------------------------------- 1 | import * as ESTree from 'estree' 2 | import { EnvironmentState } from '../environment/EnvironmentState' 3 | import { addVertex, applyExecutionNode } from '../execution/execution' 4 | import { createExecutionGraph, ExecutionGraph } from '../execution/graph/ExecutionGraph' 5 | import { createLiteralAnimation } from '../execution/primitive/Data/CreateLiteralAnimation' 6 | import { ExecutionContext } from '../execution/primitive/ExecutionNode' 7 | import { clone } from '../utilities/objects' 8 | import { getNodeData } from './Compiler' 9 | 10 | export function Literal( 11 | ast: ESTree.Literal, 12 | environment: EnvironmentState, 13 | context: ExecutionContext 14 | ) { 15 | const graph: ExecutionGraph = createExecutionGraph(getNodeData(ast)) 16 | graph.precondition = clone(environment) 17 | 18 | const create = createLiteralAnimation(ast.value, context.outputRegister, context.locationHint) 19 | addVertex(graph, create, { nodeData: getNodeData(ast) }) 20 | applyExecutionNode(create, environment) 21 | 22 | graph.postcondition = clone(environment) 23 | return graph 24 | } 25 | -------------------------------------------------------------------------------- /src/scripts/transpiler/Statements/BlockStatement.ts: -------------------------------------------------------------------------------- 1 | import * as ESTree from 'estree' 2 | import { EnvironmentState } from '../../environment/EnvironmentState' 3 | import { addVertex, applyExecutionNode } from '../../execution/execution' 4 | import { createExecutionGraph, ExecutionGraph } from '../../execution/graph/ExecutionGraph' 5 | import { ControlOutput, ControlOutputData, ExecutionContext } from '../../execution/primitive/ExecutionNode' 6 | import { createScopeAnimation } from '../../execution/primitive/Scope/CreateScopeAnimation' 7 | import { popScopeAnimation } from '../../execution/primitive/Scope/PopScopeAnimation' 8 | import { clone } from '../../utilities/objects' 9 | import { Compiler, getNodeData } from '../Compiler' 10 | 11 | export enum ScopeType { 12 | None = 'None', 13 | Default = 'Default', 14 | Global = 'Global', 15 | Hard = 'Hard', 16 | } 17 | 18 | /** 19 | * A block contains a sequence of one or more statements. 20 | * @param ast Block Statement AST 21 | * @param view ViewState state leading up to block statement 22 | * @param context 23 | * @returns {ExecutionGraph} animation 24 | */ 25 | export function BlockStatement( 26 | ast: ESTree.BlockStatement, 27 | environment: EnvironmentState, 28 | context: ExecutionContext, 29 | scopeType: ScopeType = ScopeType.Default 30 | ): ExecutionGraph { 31 | const graph = createExecutionGraph(getNodeData(ast)) 32 | graph.precondition = clone(environment) 33 | 34 | context.locationHint = [] 35 | 36 | // Create a scope 37 | if (scopeType == ScopeType.Default) { 38 | const createScope = createScopeAnimation() 39 | // addVertex(graph, createScope, { nodeData: getNodeData(ast, 'Create Scope') }) 40 | applyExecutionNode(createScope, environment) 41 | } 42 | 43 | let currLine = -1 44 | 45 | // Add statements 46 | for (const statement of ast.body) { 47 | const controlOutput: ControlOutputData = { output: ControlOutput.None } 48 | 49 | const animation = Compiler.compile(statement, environment, { 50 | ...context, 51 | controlOutput, 52 | }) 53 | 54 | let line = animation.nodeData.location?.start.line as number 55 | let hasLineBreak = false 56 | if (line > currLine + 1 && currLine >= 0) { 57 | // Add a line break 58 | hasLineBreak = true 59 | } 60 | currLine = animation.nodeData.location?.end.line as number 61 | 62 | addVertex(graph, animation, { nodeData: getNodeData(statement, 'Statement', hasLineBreak) }) 63 | 64 | if (controlOutput.output == ControlOutput.Break) { 65 | // Keep propagating 'break' until reaching a ForStatement or WhileStatement 66 | context.controlOutput.output = ControlOutput.Break 67 | break 68 | } else if (controlOutput.output == ControlOutput.Continue) { 69 | // Keep propagating 'continue' until reaching a ForStatement or WhileStatement 70 | context.controlOutput.output = ControlOutput.Continue 71 | break 72 | } else if (controlOutput.output == ControlOutput.Return) { 73 | // Keep propagating 'return' until reaching a ForStatement or WhileStatement 74 | context.controlOutput.output = ControlOutput.Return 75 | break 76 | } 77 | } 78 | 79 | // Pop scope 80 | if (scopeType == ScopeType.Default) { 81 | const popScope = popScopeAnimation() 82 | // addVertex(graph, popScope, { nodeData: getNodeData(ast, 'Pop Scope') }) 83 | applyExecutionNode(popScope, environment) 84 | } 85 | 86 | graph.postcondition = clone(environment) 87 | return graph 88 | } 89 | -------------------------------------------------------------------------------- /src/scripts/transpiler/Statements/Choice/IfStatement.ts: -------------------------------------------------------------------------------- 1 | import * as ESTree from 'estree' 2 | import { cleanUpRegister, resolvePath } from '../../../environment/environment' 3 | import { AccessorType, EnvironmentState } from '../../../environment/EnvironmentState' 4 | import { addVertex, applyExecutionNode } from '../../../execution/execution' 5 | import { createExecutionGraph, ExecutionGraph } from '../../../execution/graph/ExecutionGraph' 6 | import { consumeDataAnimation } from '../../../execution/primitive/Data/ConsumeDataAnimation' 7 | import { 8 | ControlOutput, 9 | ControlOutputData, 10 | ExecutionContext, 11 | } from '../../../execution/primitive/ExecutionNode' 12 | import { DataState } from '../../../renderer/View/Environment/data/DataState' 13 | import { clone } from '../../../utilities/objects' 14 | import { Compiler, getNodeData } from '../../Compiler' 15 | 16 | export function IfStatement( 17 | ast: ESTree.IfStatement, 18 | environment: EnvironmentState, 19 | context: ExecutionContext 20 | ) { 21 | const graph: ExecutionGraph = createExecutionGraph(getNodeData(ast)) 22 | graph.precondition = clone(environment) 23 | 24 | // Points to the result of the test 25 | const testRegister = [{ type: AccessorType.Register, value: `${graph.id}_TestIf` }] 26 | 27 | const test = Compiler.compile(ast.test, environment, { 28 | ...context, 29 | outputRegister: testRegister, 30 | }) 31 | addVertex(graph, test, { nodeData: getNodeData(ast.test, 'Test') }) 32 | 33 | // @TODO: Add a probe test animation 34 | const testData = resolvePath(environment, testRegister, null) as DataState 35 | const testValue = testData.value as boolean 36 | 37 | // Consume testData 38 | const consume = consumeDataAnimation(testRegister) 39 | // addVertex(graph, consume, { nodeData: getNodeData(ast) }) 40 | applyExecutionNode(consume, environment) 41 | cleanUpRegister(environment, testRegister[0].value) 42 | 43 | const controlOutput: ControlOutputData = { output: ControlOutput.None } 44 | 45 | if (testValue) { 46 | // Execute the body 47 | const body = Compiler.compile(ast.consequent, environment, { 48 | ...context, 49 | controlOutput, 50 | }) 51 | addVertex(graph, body, { nodeData: getNodeData(ast.consequent, 'Consequent') }) 52 | } else if (ast.alternate != null) { 53 | // Execute the alternate (if any) 54 | const alternate = Compiler.compile(ast.alternate, environment, { 55 | ...context, 56 | controlOutput, 57 | }) 58 | addVertex(graph, alternate, { nodeData: getNodeData(ast.alternate, 'Alternate') }) 59 | } 60 | 61 | context.controlOutput.output = controlOutput.output 62 | 63 | graph.postcondition = clone(environment) 64 | return graph 65 | } 66 | -------------------------------------------------------------------------------- /src/scripts/transpiler/Statements/ExpressionStatement.ts: -------------------------------------------------------------------------------- 1 | import * as ESTree from 'estree' 2 | import { EnvironmentState } from '../../environment/EnvironmentState' 3 | import { ExecutionContext } from '../../execution/primitive/ExecutionNode' 4 | import { Compiler } from '../Compiler' 5 | 6 | export function ExpressionStatement( 7 | ast: ESTree.ExpressionStatement, 8 | environment: EnvironmentState, 9 | context: ExecutionContext 10 | ) { 11 | return Compiler.compile(ast.expression, environment, context) 12 | } 13 | -------------------------------------------------------------------------------- /src/scripts/transpiler/Statements/Loops/ForStatement.ts: -------------------------------------------------------------------------------- 1 | import * as ESTree from 'estree' 2 | import { cleanUpRegister, resolvePath } from '../../../environment/environment' 3 | import { AccessorType, EnvironmentState } from '../../../environment/EnvironmentState' 4 | import { addVertex, applyExecutionNode } from '../../../execution/execution' 5 | import { createExecutionGraph, ExecutionGraph } from '../../../execution/graph/ExecutionGraph' 6 | import { consumeDataAnimation } from '../../../execution/primitive/Data/ConsumeDataAnimation' 7 | import { ControlOutput, ControlOutputData, ExecutionContext } from '../../../execution/primitive/ExecutionNode' 8 | import { createScopeAnimation } from '../../../execution/primitive/Scope/CreateScopeAnimation' 9 | import { popScopeAnimation } from '../../../execution/primitive/Scope/PopScopeAnimation' 10 | import { DataState } from '../../../renderer/View/Environment/data/DataState' 11 | import { clone } from '../../../utilities/objects' 12 | import { Compiler, getNodeData } from '../../Compiler' 13 | 14 | export function ForStatement(ast: ESTree.ForStatement, environment: EnvironmentState, context: ExecutionContext) { 15 | const graph: ExecutionGraph = createExecutionGraph(getNodeData(ast)) 16 | graph.precondition = clone(environment) 17 | 18 | // Create a scope 19 | const createScope = createScopeAnimation() 20 | // addVertex(graph, createScope, { nodeData: getNodeData(ast, 'Create Scope') }) 21 | applyExecutionNode(createScope, environment) 22 | 23 | // let iteration = createExecutionGraph({ 24 | // ...getNodeData(ast), 25 | // type: 'ForStatementIteration', 26 | // }) 27 | // iteration.precondition = clone(environment) 28 | 29 | // Init 30 | const init = Compiler.compile(ast.init, environment, context) 31 | addVertex(graph, init, { nodeData: getNodeData(ast.init, 'Initial') }) 32 | 33 | // Points to the result of test 34 | 35 | let _i = 0 36 | // Loop 37 | while (true) { 38 | // Test 39 | const testRegister = [{ type: AccessorType.Register, value: `${graph.id}_TestIf${_i}` }] 40 | const test = Compiler.compile(ast.test, environment, { 41 | ...context, 42 | outputRegister: testRegister, 43 | }) 44 | addVertex(graph, test, { nodeData: getNodeData(ast.test, 'Test') }) 45 | 46 | const testData = resolvePath(environment, testRegister, null) as DataState 47 | const testValue = testData.value as boolean 48 | 49 | // Consume testData 50 | const consume = consumeDataAnimation(testRegister) 51 | applyExecutionNode(consume, environment) 52 | cleanUpRegister(environment, testRegister[0].value) 53 | 54 | if (!testValue) { 55 | graph.postcondition = clone(environment) 56 | break 57 | } 58 | 59 | // Body 60 | const controlOutput: ControlOutputData = { output: ControlOutput.None } 61 | const body = Compiler.compile(ast.body, environment, { 62 | ...context, 63 | controlOutput: controlOutput, 64 | }) 65 | 66 | addVertex(graph, body, { nodeData: getNodeData(ast.body, 'Body') }) 67 | 68 | if (controlOutput.output == ControlOutput.Break) { 69 | context.controlOutput.output = ControlOutput.None 70 | break 71 | } else if (controlOutput.output == ControlOutput.Continue) { 72 | context.controlOutput.output = ControlOutput.None 73 | } else if (controlOutput.output == ControlOutput.Return) { 74 | context.controlOutput.output = ControlOutput.Return 75 | break 76 | } 77 | 78 | // Update 79 | const update = Compiler.compile(ast.update, environment, { 80 | ...context, 81 | outputRegister: null, 82 | }) 83 | addVertex(graph, update, { nodeData: getNodeData(ast.update, 'Update') }) 84 | 85 | // Add iteration to the graph 86 | // iteration.postcondition = clone(environment) 87 | // addVertex(graph, iteration, { 88 | // nodeData: { 89 | // ...getNodeData(ast, `Iteration ${_i}`), 90 | // type: 'ForStatementIteration', 91 | // }, 92 | // }) 93 | 94 | // iteration = createExecutionGraph({ 95 | // ...getNodeData(ast), 96 | // type: 'ForStatementIteration', 97 | // }) 98 | // iteration.precondition = clone(environment) 99 | 100 | _i++ 101 | } 102 | 103 | if (context.controlOutput.output != ControlOutput.Return) { 104 | context.controlOutput.output = ControlOutput.None 105 | } 106 | 107 | // Pop scope 108 | const popScope = popScopeAnimation() 109 | // addVertex(graph, popScope, { nodeData: getNodeData(ast, 'Pop Scope') }) 110 | applyExecutionNode(popScope, environment) 111 | 112 | graph.postcondition = clone(environment) 113 | return graph 114 | } 115 | -------------------------------------------------------------------------------- /src/scripts/transpiler/Statements/Loops/WhileStatement.ts: -------------------------------------------------------------------------------- 1 | import * as ESTree from 'estree' 2 | import { cleanUpRegister, resolvePath } from '../../../environment/environment' 3 | import { AccessorType, EnvironmentState } from '../../../environment/EnvironmentState' 4 | import { addVertex, applyExecutionNode } from '../../../execution/execution' 5 | import { createExecutionGraph, ExecutionGraph } from '../../../execution/graph/ExecutionGraph' 6 | import { 7 | ControlOutput, 8 | ControlOutputData, 9 | ExecutionContext, 10 | } from '../../../execution/primitive/ExecutionNode' 11 | import { createScopeAnimation } from '../../../execution/primitive/Scope/CreateScopeAnimation' 12 | import { popScopeAnimation } from '../../../execution/primitive/Scope/PopScopeAnimation' 13 | import { DataState } from '../../../renderer/View/Environment/data/DataState' 14 | import { clone } from '../../../utilities/objects' 15 | import { Compiler, getNodeData } from '../../Compiler' 16 | 17 | export function WhileStatement( 18 | ast: ESTree.WhileStatement, 19 | environment: EnvironmentState, 20 | context: ExecutionContext 21 | ) { 22 | const graph: ExecutionGraph = createExecutionGraph(getNodeData(ast)) 23 | graph.precondition = clone(environment) 24 | 25 | // Create a scope 26 | const createScope = createScopeAnimation() 27 | addVertex(graph, createScope, { nodeData: getNodeData(ast) }) 28 | applyExecutionNode(createScope, environment) 29 | 30 | let _i = 0 31 | 32 | // Loop 33 | while (true) { 34 | // Test 35 | const testRegister = [{ type: AccessorType.Register, value: `${graph.id}_TestIf${_i}` }] 36 | const test = Compiler.compile(ast.test, environment, { 37 | ...context, 38 | outputRegister: testRegister, 39 | }) 40 | addVertex(graph, test, { nodeData: getNodeData(ast.test) }) 41 | const testData = resolvePath(environment, testRegister, null) as DataState // @TODO: Add a probe test animation 42 | const testValue = testData.value as boolean 43 | cleanUpRegister(environment, testRegister[0].value) 44 | if (!testValue) break 45 | 46 | // Body 47 | const controlOutput: ControlOutputData = { output: ControlOutput.None } 48 | const body = Compiler.compile(ast.body, environment, { 49 | ...context, 50 | controlOutput: controlOutput, 51 | }) 52 | addVertex(graph, body, { nodeData: getNodeData(ast.body) }) 53 | 54 | if (controlOutput.output == ControlOutput.Break) { 55 | context.controlOutput.output = ControlOutput.None 56 | break 57 | } else if (controlOutput.output == ControlOutput.Continue) { 58 | context.controlOutput.output = ControlOutput.None 59 | } else if (controlOutput.output == ControlOutput.Return) { 60 | context.controlOutput.output = ControlOutput.Return 61 | break 62 | } 63 | 64 | // Update 65 | _i++ 66 | } 67 | 68 | if (context.controlOutput.output != ControlOutput.Return) { 69 | context.controlOutput.output = ControlOutput.None 70 | } 71 | 72 | // Pop scope 73 | const popScope = popScopeAnimation() 74 | addVertex(graph, popScope, { nodeData: getNodeData(ast) }) 75 | applyExecutionNode(popScope, environment) 76 | 77 | graph.postcondition = clone(environment) 78 | return graph 79 | } 80 | -------------------------------------------------------------------------------- /src/scripts/transpiler/Statements/Program.ts: -------------------------------------------------------------------------------- 1 | import * as ESTree from 'estree' 2 | import { EnvironmentState } from '../../environment/EnvironmentState' 3 | import { addVertex } from '../../execution/execution' 4 | import { createExecutionGraph } from '../../execution/graph/ExecutionGraph' 5 | import { ControlOutput, ExecutionContext } from '../../execution/primitive/ExecutionNode' 6 | import { clone } from '../../utilities/objects' 7 | import { Compiler, getNodeData } from '../Compiler' 8 | 9 | export function Program(ast: ESTree.Program, environment: EnvironmentState, context: ExecutionContext) { 10 | const graph = createExecutionGraph(getNodeData(ast, 'Program')) 11 | graph.precondition = clone(environment) 12 | 13 | const controlOutput = { output: ControlOutput.None } 14 | 15 | let currLine = -1 16 | 17 | // Add blocks 18 | for (const statement of ast.body) { 19 | const animation = Compiler.compile(statement, environment, { 20 | ...context, 21 | controlOutput, 22 | }) 23 | 24 | if (animation != null) { 25 | let line = animation.nodeData.location?.start.line as number 26 | let hasLineBreak = false 27 | if (line > currLine + 1 && currLine >= 0) { 28 | // Add a line break 29 | hasLineBreak = true 30 | } 31 | currLine = animation.nodeData.location?.end.line as number 32 | 33 | addVertex(graph, animation, { nodeData: getNodeData(statement, 'Statement', hasLineBreak) }) 34 | } 35 | } 36 | 37 | graph.postcondition = clone(environment) 38 | return graph 39 | } 40 | -------------------------------------------------------------------------------- /src/scripts/transpiler/Statements/VariableDeclaration.ts: -------------------------------------------------------------------------------- 1 | import * as ESTree from 'estree' 2 | import { EnvironmentState } from '../../environment/EnvironmentState' 3 | import { addVertex } from '../../execution/execution' 4 | import { createExecutionGraph, ExecutionGraph } from '../../execution/graph/ExecutionGraph' 5 | import { ExecutionContext } from '../../execution/primitive/ExecutionNode' 6 | import { clone } from '../../utilities/objects' 7 | import { getNodeData } from '../Compiler' 8 | import { VariableDeclarator } from './VariableDeclarator' 9 | 10 | export function VariableDeclaration( 11 | ast: ESTree.VariableDeclaration, 12 | environment: EnvironmentState, 13 | context: ExecutionContext 14 | ) { 15 | if (ast.declarations.length == 1) { 16 | return VariableDeclarator(ast.declarations[0], environment, context) 17 | } 18 | 19 | const graph: ExecutionGraph = createExecutionGraph(getNodeData(ast)) 20 | graph.precondition = clone(environment) 21 | 22 | for (const declaration of ast.declarations) { 23 | const animation = VariableDeclarator(declaration, environment, context) 24 | addVertex(graph, animation, { nodeData: getNodeData(declaration, 'declaration') }) 25 | } 26 | 27 | graph.postcondition = clone(environment) 28 | return graph 29 | } 30 | -------------------------------------------------------------------------------- /src/scripts/transpiler/Statements/VariableDeclarator.ts: -------------------------------------------------------------------------------- 1 | import * as ESTree from 'estree' 2 | import { cleanUpRegister } from '../../environment/environment' 3 | import { AccessorType, EnvironmentState } from '../../environment/EnvironmentState' 4 | import { addVertex, applyExecutionNode } from '../../execution/execution' 5 | import { createExecutionGraph, ExecutionGraph } from '../../execution/graph/ExecutionGraph' 6 | import { bindAnimation } from '../../execution/primitive/Binding/BindAnimation' 7 | import { ExecutionContext } from '../../execution/primitive/ExecutionNode' 8 | import { clone } from '../../utilities/objects' 9 | import { Compiler, getNodeData } from '../Compiler' 10 | 11 | export function VariableDeclarator( 12 | ast: ESTree.VariableDeclarator, 13 | environment: EnvironmentState, 14 | context: ExecutionContext 15 | ) { 16 | const graph: ExecutionGraph = createExecutionGraph(getNodeData(ast)) 17 | graph.precondition = clone(environment) 18 | 19 | // Create a register that'll point to the RHS 20 | const register = [ 21 | { 22 | type: AccessorType.Register, 23 | value: `${graph.id}__VariableDeclaration`, 24 | }, 25 | ] 26 | 27 | // Copy / create and float up RHS 28 | const init = Compiler.compile(ast.init, environment, { 29 | ...context, 30 | outputRegister: register, 31 | }) 32 | addVertex(graph, init, { nodeData: getNodeData(ast.init, 'Value') }) 33 | 34 | // Allocate a place for variable that *points* to the RHS 35 | // @TODO: support initializations other than identifier 36 | const bind = bindAnimation((ast.id as ESTree.Identifier).name, register) 37 | const bindNodeData = getNodeData(ast.id, 'Name') 38 | bindNodeData.location.start.column -= 4 39 | 40 | addVertex(graph, bind, { nodeData: bindNodeData }) 41 | applyExecutionNode(bind, environment) 42 | cleanUpRegister(environment, register[0].value) 43 | 44 | graph.postcondition = clone(environment) 45 | return graph 46 | } 47 | -------------------------------------------------------------------------------- /src/scripts/utilities/Keyboard.ts: -------------------------------------------------------------------------------- 1 | export class Keyboard { 2 | // Singleton 3 | static instance: Keyboard 4 | 5 | // List of tick callbacks 6 | private pressed: { [key: string]: boolean } = {} 7 | 8 | // List of tick callbacks 9 | 10 | static _initialize() { 11 | if (Keyboard.instance) return 12 | Keyboard.instance = new Keyboard() 13 | } 14 | 15 | constructor() { 16 | const keyboard = this 17 | 18 | document.addEventListener('keydown', (e) => { 19 | keyboard.pressed[e.key] = true 20 | }) 21 | 22 | document.addEventListener('keyup', (e) => { 23 | keyboard.pressed[e.key] = false 24 | }) 25 | } 26 | 27 | isPressed(key: string) { 28 | return key in this.pressed && this.pressed[key] 29 | } 30 | } 31 | 32 | Keyboard._initialize() 33 | -------------------------------------------------------------------------------- /src/scripts/utilities/Mouse.ts: -------------------------------------------------------------------------------- 1 | export class Mouse { 2 | // Singleton 3 | static instance: Mouse 4 | 5 | // List of tick callbacks 6 | position: { x: number; y: number } = { x: 0, y: 0 } 7 | 8 | held: boolean = false 9 | 10 | element: HTMLElement 11 | 12 | static _initialize() { 13 | if (Mouse.instance) return 14 | Mouse.instance = new Mouse() 15 | } 16 | 17 | constructor() { 18 | document.addEventListener('mousemove', (e) => { 19 | this.position = { x: e.clientX, y: e.clientY } 20 | 21 | this.element.style.left = `${this.position.x}px` 22 | this.element.style.top = `${this.position.y}px` 23 | }) 24 | 25 | document.addEventListener('mousedown', (e) => { 26 | this.held = true 27 | }) 28 | 29 | document.addEventListener('mouseup', (e) => { 30 | this.held = false 31 | }) 32 | 33 | this.element = document.createElement('div') 34 | this.element.classList.add('mouse-indicator') 35 | document.body.appendChild(this.element) 36 | } 37 | } 38 | 39 | Mouse._initialize() 40 | -------------------------------------------------------------------------------- /src/scripts/utilities/Ticker.ts: -------------------------------------------------------------------------------- 1 | import { ApplicationState } from '../ApplicationState' 2 | 3 | export class Ticker { 4 | // Singleton 5 | static instance: Ticker 6 | 7 | // List of tick callbacks 8 | private callbacks: { [id: string]: (dt: number) => void } = {} 9 | private time = 0 10 | private currID = 0 11 | 12 | static _initialize() { 13 | if (Ticker.instance) return 14 | Ticker.instance = new Ticker() 15 | } 16 | 17 | constructor() { 18 | const timer = this 19 | 20 | function tick(time: number) { 21 | ApplicationState.visualization.fpsGraph.begin() 22 | const dt = time - timer.time 23 | Object.values(timer.callbacks).forEach((callback) => callback(dt)) 24 | timer.time = time 25 | ApplicationState.visualization.fpsGraph.end() 26 | requestAnimationFrame(tick) 27 | } 28 | 29 | requestAnimationFrame(tick) 30 | } 31 | 32 | registerTick(callback: (dt: number) => void) { 33 | this.currID++ 34 | return this.registerTickFrom(callback, `Ticker_${this.currID}`) 35 | } 36 | 37 | registerTickFrom(callback: (dt: number) => void, from: string) { 38 | this.callbacks[from] = callback 39 | return from 40 | } 41 | 42 | removeTickFrom(from: string) { 43 | delete this.callbacks[from] 44 | } 45 | } 46 | 47 | Ticker._initialize() 48 | -------------------------------------------------------------------------------- /src/scripts/utilities/executor.ts: -------------------------------------------------------------------------------- 1 | import * as acorn from 'acorn' 2 | import * as ESTree from 'estree' 3 | 4 | export function getAST(code: string): { 5 | ast: ESTree.Node | null 6 | errors: string[] 7 | } { 8 | // Compile AST 9 | let ast: ESTree.Node 10 | 11 | try { 12 | ast = acorn.parse(code, { 13 | locations: true, 14 | ecmaVersion: 2017, 15 | }) as ESTree.Node 16 | } catch (e: any) { 17 | return { ast: null, errors: [e.toString()] } 18 | } 19 | 20 | return { ast, errors: [] } 21 | // Check for runtime errors 22 | } 23 | -------------------------------------------------------------------------------- /src/scripts/utilities/generic.ts: -------------------------------------------------------------------------------- 1 | export function assert(condition: any, msg?: string): asserts condition { 2 | if (!condition) { 3 | throw new Error(msg) 4 | } 5 | } 6 | -------------------------------------------------------------------------------- /src/scripts/utilities/objects.ts: -------------------------------------------------------------------------------- 1 | import * as RFDC from './rfdc.js' 2 | 3 | export function clone(obj: T, d = 0): T { 4 | return RFDC.RFDCclone(obj) 5 | } 6 | -------------------------------------------------------------------------------- /src/scripts/utilities/rfdc.js: -------------------------------------------------------------------------------- 1 | function RFDCcopyBuffer(cur) { 2 | if (cur instanceof Buffer) { 3 | return Buffer.from(cur) 4 | } 5 | 6 | return new cur.constructor(cur.buffer.slice(), cur.byteOffset, cur.length) 7 | } 8 | 9 | export function RFDCcloneArray(a, fn) { 10 | var keys = Object.keys(a) 11 | var a2 = new Array(keys.length) 12 | for (var i = 0; i < keys.length; i++) { 13 | var k = keys[i] 14 | var cur = a[k] 15 | if (typeof cur !== 'object' || cur === null) { 16 | a2[k] = cur 17 | } else if (cur instanceof Date) { 18 | a2[k] = new Date(cur) 19 | } else if (ArrayBuffer.isView(cur)) { 20 | a2[k] = RFDCcopyBuffer(cur) 21 | } else { 22 | a2[k] = fn(cur) 23 | } 24 | } 25 | return a2 26 | } 27 | 28 | export function RFDCclone(o) { 29 | if (typeof o !== 'object' || o === null) return o 30 | if (o instanceof Date) return new Date(o) 31 | if (Array.isArray(o)) return RFDCcloneArray(o, RFDCclone) 32 | if (o instanceof Map) return new Map(RFDCcloneArray(Array.from(o), RFDCclone)) 33 | if (o instanceof Set) return new Set(RFDCcloneArray(Array.from(o), clone)) 34 | var o2 = {} 35 | for (var k in o) { 36 | if (Object.hasOwnProperty.call(o, k) === false) continue 37 | var cur = o[k] 38 | if (typeof cur !== 'object' || cur === null) { 39 | o2[k] = cur 40 | } else if (cur instanceof Date) { 41 | o2[k] = new Date(cur) 42 | } else if (cur instanceof Map) { 43 | o2[k] = new Map(RFDCcloneArray(Array.from(cur), RFDCclone)) 44 | } else if (cur instanceof Set) { 45 | o2[k] = new Set(RFDCcloneArray(Array.from(cur), RFDCclone)) 46 | } else if (ArrayBuffer.isView(cur)) { 47 | o2[k] = RFDCcopyBuffer(cur) 48 | } else { 49 | o2[k] = RFDCclone(cur) 50 | } 51 | } 52 | return o2 53 | } 54 | 55 | export function RFDCcloneProto(o) { 56 | if (typeof o !== 'object' || o === null) return o 57 | if (o instanceof Date) return new Date(o) 58 | if (Array.isArray(o)) return RFDCcloneArray(o, RFDCcloneProto) 59 | if (o instanceof Map) return new Map(RFDCcloneArray(Array.from(o), RFDCcloneProto)) 60 | if (o instanceof Set) return new Set(RFDCcloneArray(Array.from(o), RFDCcloneProto)) 61 | var o2 = {} 62 | for (var k in o) { 63 | var cur = o[k] 64 | if (typeof cur !== 'object' || cur === null) { 65 | o2[k] = cur 66 | } else if (cur instanceof Date) { 67 | o2[k] = new Date(cur) 68 | } else if (cur instanceof Map) { 69 | o2[k] = new Map(RFDCcloneArray(Array.from(cur), RFDCcloneProto)) 70 | } else if (cur instanceof Set) { 71 | o2[k] = new Set(RFDCcloneArray(Array.from(cur), RFDCcloneProto)) 72 | } else if (ArrayBuffer.isView(cur)) { 73 | o2[k] = RFDCcopyBuffer(cur) 74 | } else { 75 | o2[k] = RFDCcloneProto(cur) 76 | } 77 | } 78 | return o2 79 | } 80 | -------------------------------------------------------------------------------- /src/scripts/utilities/string.ts: -------------------------------------------------------------------------------- 1 | export function camelCaseToSentence(string: string) { 2 | const result = string.replace(/([A-Z])/g, ' $1') 3 | return result.charAt(0).toUpperCase() + result.slice(1) 4 | } 5 | 6 | export function stringHashCode(str: string) { 7 | let hash = 0, 8 | i: number, 9 | chr: number 10 | if (str.length === 0) return hash 11 | for (i = 0; i < str.length; i++) { 12 | chr = str.charCodeAt(i) 13 | hash = (hash << 5) - hash + chr 14 | hash |= 0 // Convert to 32bit integer 15 | } 16 | return Math.abs(hash) 17 | } 18 | -------------------------------------------------------------------------------- /src/scripts/visualization/Focus.ts: -------------------------------------------------------------------------------- 1 | import { Ticker } from '../utilities/Ticker' 2 | 3 | export type FocusState = { 4 | focusedActions: (string | string[])[] 5 | 6 | // Tick id 7 | tickID: string 8 | } 9 | 10 | export function createFocus(overrides: Partial = {}): FocusState { 11 | // const element = createElement('div', 'focus', document.body) 12 | 13 | const base: FocusState = { 14 | focusedActions: [], 15 | tickID: Ticker.instance.registerTick(() => { 16 | updateFocus() 17 | }), 18 | } 19 | 20 | return { ...base, ...overrides } 21 | } 22 | 23 | export function destroyFocus(focus: FocusState) { 24 | // focus.element.remove() 25 | } 26 | 27 | // export function clearExistingFocus() { 28 | // const focus = ApplicationState.visualization.focus 29 | // assert(focus != undefined, 'Focus is undefined') 30 | 31 | // if (Keyboard.instance.isPressed('Shift')) { 32 | // return 33 | // } 34 | 35 | // for (let i = focus.focusedActions.length - 1; i >= 0; i--) { 36 | // const otherId = focus.focusedActions[i] 37 | 38 | // if (Array.isArray(otherId)) { 39 | // const forLoopId = ApplicationState.actions[otherId[0]].parentID as string 40 | // const forLoop = ApplicationState.actions[forLoopId] 41 | 42 | // const iterationIndex = Math.floor(Math.max(0, forLoop.vertices.indexOf(otherId[0]) - 1) / 3) 43 | // const representation = forLoop.representation as ForStatementRepresentation 44 | 45 | // if (!representation.pinnedIterations.has(iterationIndex)) { 46 | // // Remove iteration from focus 47 | // focus.focusedActions.splice(focus.focusedActions.indexOf(otherId), 1) 48 | // representation.pinnedIterations.delete(iterationIndex) 49 | // representation.iterationElements[iterationIndex].classList.remove('is-focused') 50 | // } 51 | // } else { 52 | // const other = ApplicationState.actions[otherId] 53 | // if (!other.isPinned) { 54 | // other.proxy.container.classList.remove('is-focused') 55 | // other.proxy.element.classList.remove('is-focused') 56 | 57 | // focus.focusedActions.splice(i, 1) 58 | // } 59 | // } 60 | // } 61 | // } 62 | 63 | export function updateFocus() { 64 | // const focus = ApplicationState.visualization.focus 65 | // assert(focus != undefined, 'Focus is undefined.') 66 | // if (focus.focusedActions.length == 0) { 67 | // focus.element.classList.add('is-hidden') 68 | // } else { 69 | // focus.element.classList.remove('is-hidden') 70 | // const element = ApplicationState.actions[focus.focusedActions[0]].proxy.element 71 | // if (element == undefined) { 72 | // throw new Error('Focus element is undefined.') 73 | // } 74 | // // Set position 75 | // const bbox = element.getBoundingClientRect() 76 | // focus.element.style.left = `${bbox.left}px` 77 | // focus.element.style.top = `${bbox.top}px` 78 | // focus.element.style.width = `${bbox.width}px` 79 | // focus.element.style.height = `${bbox.height}px` 80 | // } 81 | // Scale down all elements that are not focused 82 | // const spatialActionProxies = Object.values(ApplicationState.actions).filter( 83 | // (action) => 84 | // action.isSpatial || action.execution.nodeData.type == 'Program' 85 | // ) 86 | // for (const action of spatialActionProxies) { 87 | // if ( 88 | // action.id == focus.currentFocus || 89 | // focus.currentFocus == undefined 90 | // ) { 91 | // if (action.proxy.element.classList.contains('out-of-focus')) { 92 | // action.proxy.element.classList.remove('out-of-focus') 93 | // } 94 | // } else { 95 | // if (!action.proxy.element.classList.contains('out-of-focus')) { 96 | // action.proxy.element.classList.add('out-of-focus') 97 | // } 98 | // } 99 | // } 100 | } 101 | -------------------------------------------------------------------------------- /src/styles/editor.scss: -------------------------------------------------------------------------------- 1 | /* ------------------------------------------------------ */ 2 | /* Defines styles for source code editor */ 3 | /* ------------------------------------------------------ */ 4 | 5 | /* ---------------------- Container --------------------- */ 6 | 7 | #editor { 8 | width: 100%; 9 | height: 100%; 10 | padding: 2rem; 11 | padding-top: 2rem; 12 | padding-left: 0px; 13 | } 14 | 15 | /* ----------------------- Monaco ----------------------- */ 16 | 17 | .monaco-editor, 18 | .monaco-editor-background, 19 | .monaco-editor .inputarea.ime-input { 20 | background-color: rgba(0, 0, 0, 0) !important; 21 | } 22 | 23 | .monaco-editor-background { 24 | /* background-color: var(--main-bg-color) !important; */ 25 | background-color: rgba(0, 0, 0, 0) !important; 26 | } 27 | 28 | .monaco-editor .margin { 29 | /* background-color: var(--main-bg-color) !important; */ 30 | background-color: rgba(0, 0, 0, 0) !important; 31 | } 32 | 33 | .monaco-editor .view-overlays .current-line { 34 | display: none; 35 | } 36 | 37 | // // .execution-decoration { 38 | // // left: 45px !important; 39 | 40 | // // width: 8px !important; 41 | // // height: 8px !important; 42 | 43 | // // border-radius: 50%; 44 | // // border: 1px solid #bfbebe; 45 | // // background-color: #00000008 !important; 46 | 47 | // // margin-top: 10%; 48 | 49 | // // display: none; 50 | // // } 51 | 52 | .monaco-editor .line-numbers { 53 | opacity: 0.4; 54 | font-size: 12px !important; 55 | margin-top: 2px !important; 56 | transition-property: opacity; 57 | color: black !important; 58 | } 59 | 60 | .monaco-editor .line-numbers:hover { 61 | opacity: 1 !important; 62 | } 63 | 64 | .line-selected { 65 | font-weight: 800 !important; 66 | opacity: 1 !important; 67 | } 68 | 69 | .core-guide { 70 | box-shadow: none !important; 71 | } 72 | 73 | .cigr { 74 | box-shadow: 0.5px 0 0 0 rgba(0, 0, 0, 0) inset !important; 75 | } 76 | 77 | .cigra { 78 | box-shadow: 0.5px 0 0 0 rgba(0, 0, 0, 0) inset !important; 79 | } 80 | 81 | .monaco-editor .margin-view-overlays .codicon-chevron-down { 82 | font-size: 80%; 83 | } 84 | 85 | .monaco-editor .margin-view-overlays .codicon-folding-collapsed, 86 | .monaco-editor .margin-view-overlays .codicon-folding-expanded { 87 | font-size: 80% !important; 88 | } 89 | 90 | .cdr.bracket-match { 91 | opacity: 0.5; 92 | border: none !important; 93 | outline: none !important; 94 | border-radius: 3px; 95 | padding: 2px; 96 | background: none !important; 97 | } 98 | 99 | .monaco-editor .cursors-layer .cursor { 100 | background-color: var(--color-6) !important; 101 | // transition-property: top, left; 102 | // transition: 0.1s; 103 | opacity: 0.8; 104 | height: 15px !important; 105 | margin-top: 4px !important; 106 | margin-left: 0px !important; 107 | } 108 | 109 | .monaco-editor .scroll-decoration { 110 | box-shadow: none !important; 111 | } 112 | 113 | .lines-content { 114 | contain: none !important; 115 | overflow: visible !important; 116 | } 117 | 118 | .monaco-scrollable-element { 119 | overflow: visible !important; 120 | } 121 | 122 | .monaco-editor .codelens-decoration { 123 | color: #999999; 124 | margin-left: -10px; 125 | margin-top: -2px; 126 | opacity: 1; 127 | } 128 | 129 | .monaco-editor .codelens-decoration > span, 130 | .monaco-editor .codelens-decoration > a { 131 | font-size: 14px; 132 | } 133 | 134 | .monaco-editor .editor-widget { 135 | display: none !important; 136 | visibility: hidden !important; 137 | } 138 | 139 | .cdr.folded-background { 140 | opacity: 0; 141 | } 142 | 143 | /* -------------------- Syntax colors ------------------- */ 144 | 145 | // Generic 146 | span.mtk1 { 147 | color: black !important; 148 | } 149 | 150 | // Literals 151 | span.mtk7 { 152 | color: var(--s-color-0) !important; 153 | } 154 | 155 | // Keywords 156 | span.mtk22 { 157 | color: var(--s-color-5) !important; 158 | font-weight: normal !important; 159 | } 160 | 161 | // In between keywords 162 | span.mtk14 { 163 | color: #535359 !important; 164 | } 165 | 166 | // Strings 167 | span.mtk32 { 168 | color: var(--s-color-2) !important; 169 | } 170 | 171 | // Comments 172 | span.mtk8 { 173 | color: var(--color-4); 174 | font-style: italic; 175 | } 176 | 177 | /* ------------------- Error feedback ------------------- */ 178 | 179 | #errors { 180 | font-family: var(--code-font-family); 181 | border-radius: 10px; 182 | padding: 10px; 183 | position: absolute; 184 | top: 50px; 185 | left: 177px; 186 | display: none; 187 | color: #d86f64; 188 | } 189 | 190 | #errors.active { 191 | display: inherit; 192 | } 193 | 194 | .monaco-hover { 195 | display: none !important; 196 | } 197 | 198 | /* ------------------------ Focus ----------------------- */ 199 | .view-line > span > span.is-focused { 200 | background-color: #161d23; 201 | } 202 | -------------------------------------------------------------------------------- /src/styles/generic/button.scss: -------------------------------------------------------------------------------- 1 | .button { 2 | color: var(--color-5); 3 | cursor: pointer; 4 | border-radius: 7px; 5 | display: flex; 6 | align-items: center; 7 | justify-content: center; 8 | height: fit-content; 9 | height: -moz-fit-content; 10 | font-weight: 400; 11 | font-size: 14px; 12 | padding: 5px; 13 | opacity: 0.8; 14 | 15 | box-shadow: -1px 1.5px 0px 0.5px var(--color-2); 16 | background: var(--color-1); 17 | transform: translate(0px, 0px); 18 | } 19 | 20 | .button > ion-icon { 21 | color: var(--color-5); 22 | } 23 | 24 | .button:hover { 25 | opacity: 1; 26 | box-shadow: -1.5px 2.5px 0px 1px var(--color-2); 27 | transform: translate(1px, -1px); 28 | } 29 | 30 | .button.active, 31 | .button:active { 32 | background-color: var(--s-color-0); 33 | opacity: 1; 34 | box-shadow: -0.5px 0.5px 0px 0.5px var(--s-color-1); 35 | transform: translate(-1px, 1px); 36 | } 37 | -------------------------------------------------------------------------------- /src/styles/generic/checkbox.scss: -------------------------------------------------------------------------------- 1 | * { 2 | box-sizing: border-box; 3 | } 4 | 5 | .cbx { 6 | -webkit-user-select: none; 7 | user-select: none; 8 | cursor: pointer; 9 | padding: 6px 8px; 10 | border-radius: 6px; 11 | overflow: hidden; 12 | } 13 | 14 | .cbx:not(:last-child) { 15 | margin-right: 6px; 16 | } 17 | 18 | .cbx:hover { 19 | background: rgba(139, 139, 139, 0.06); 20 | } 21 | 22 | .cbx span { 23 | float: left; 24 | vertical-align: middle; 25 | transform: translate3d(0, 0, 0); 26 | } 27 | 28 | .cbx span:first-child { 29 | position: relative; 30 | width: 18px; 31 | height: 18px; 32 | border-radius: 4px; 33 | transform: scale(1); 34 | border: 1px solid #cccfdb; 35 | box-shadow: 0 1px 1px rgba(56, 56, 56, 0.05); 36 | } 37 | 38 | .cbx span:first-child svg { 39 | position: absolute; 40 | top: 3px; 41 | left: 2px; 42 | fill: none; 43 | stroke: #fff; 44 | stroke-width: 2; 45 | stroke-linecap: round; 46 | stroke-linejoin: round; 47 | stroke-dasharray: 16px; 48 | stroke-dashoffset: 16px; 49 | transition-delay: 0.1s; 50 | transform: translate3d(0, 0, 0); 51 | } 52 | 53 | .cbx span:last-child { 54 | padding-left: 8px; 55 | line-height: 18px; 56 | } 57 | 58 | .cbx:hover span:first-child { 59 | border-color: rgb(73, 84, 86); 60 | } 61 | 62 | .inp-cbx { 63 | position: absolute; 64 | visibility: hidden; 65 | } 66 | 67 | .inp-cbx:checked + .cbx span:first-child { 68 | background: rgb(73, 84, 86); 69 | border-color: rgb(73, 84, 86); 70 | animation: wave 0.4s ease; 71 | } 72 | 73 | .inp-cbx:checked + .cbx span:first-child svg { 74 | stroke-dashoffset: 0; 75 | } 76 | 77 | .inline-svg { 78 | position: absolute; 79 | width: 0; 80 | height: 0; 81 | pointer-events: none; 82 | user-select: none; 83 | } 84 | 85 | @-moz-keyframes wave { 86 | 50% { 87 | transform: scale(0.9); 88 | } 89 | } 90 | 91 | @-webkit-keyframes wave { 92 | 50% { 93 | transform: scale(0.9); 94 | } 95 | } 96 | 97 | @-o-keyframes wave { 98 | 50% { 99 | transform: scale(0.9); 100 | } 101 | } 102 | 103 | @keyframes wave { 104 | 50% { 105 | transform: scale(0.9); 106 | } 107 | } 108 | -------------------------------------------------------------------------------- /src/styles/renderer/data/array.scss: -------------------------------------------------------------------------------- 1 | /** Array style used for computing layout **/ 2 | .data-array { 3 | display: flex; 4 | min-height: 20px; 5 | } 6 | 7 | // .data.data-literal.array-item:not(.last-array-item):after { 8 | // content: ','; 9 | // color: var(--color-4); 10 | // } 11 | 12 | .data-array-brace { 13 | color: var(--color-4); 14 | font-weight: 400; 15 | font-family: var(--code-font-family); 16 | font-size: 12px; 17 | } 18 | 19 | .data-array-opening-brace { 20 | } 21 | 22 | .data-array-closing-brace { 23 | } 24 | 25 | .data.array-item:not(.first-array-item):not(.last-array-item) { 26 | // border-radius: 0px; 27 | // border-left: none; 28 | } 29 | 30 | .data.array-item { 31 | // margin-left: -1px; 32 | } 33 | 34 | .data.first-array-item { 35 | // border-radius: 7px 0px 0px 7px; 36 | } 37 | 38 | .data.last-array-item { 39 | // border-radius: 0px 7px 7px 0px; 40 | // border-left: none; 41 | } 42 | 43 | .data-array-index { 44 | background: none; 45 | position: absolute; 46 | font-size: 12px; 47 | opacity: 0.5; 48 | margin-left: 8px; 49 | margin-top: 6px; 50 | } 51 | 52 | .data-array-comma { 53 | color: var(--color-4); 54 | font-weight: 400; 55 | font-family: var(--code-font-family); 56 | font-size: 12px; 57 | } 58 | 59 | // .data.data-array.environment-write > .data-array-brace { 60 | // color: var(--s-color-0-3); 61 | // } 62 | -------------------------------------------------------------------------------- /src/styles/renderer/data/data.scss: -------------------------------------------------------------------------------- 1 | .data { 2 | display: flex; 3 | justify-content: center; 4 | align-items: center; 5 | height: fit-content; 6 | position: relative; 7 | } 8 | 9 | .environment > .data:not(:first-child) { 10 | margin-left: 25px; 11 | } 12 | 13 | .data.hidden { 14 | opacity: 0 !important; 15 | } 16 | 17 | .data-row { 18 | position: relative; 19 | } 20 | 21 | /* ------------------------ Focus ----------------------- */ 22 | .data.secondary-focused { 23 | color: var(--s-color-3); 24 | } 25 | 26 | .data.is-free { 27 | position: absolute; 28 | } 29 | 30 | /* ---------------------- Residual ---------------------- */ 31 | .data.is-residual { 32 | position: absolute; 33 | filter: saturate(0); 34 | opacity: 0.2; 35 | top: 0px; 36 | left: 0px; 37 | } 38 | 39 | .hole { 40 | position: relative; 41 | } 42 | 43 | .data.temp-move { 44 | position: absolute; 45 | } 46 | 47 | .temp-return-animation-node { 48 | position: absolute !important; 49 | } 50 | 51 | .trail-return { 52 | opacity: 0 !important; 53 | } 54 | 55 | .temp-return-environment .data.data-literal { 56 | opacity: 1 !important; 57 | color: #000000a1 !important; 58 | } 59 | 60 | .temp-return-environment { 61 | opacity: 0.4; 62 | 63 | border-top: 1px solid #000; 64 | border-left: 1px solid #000; 65 | } 66 | 67 | .action-proxy.type-BinaryExpressionEvaluate { 68 | background: none !important; 69 | box-shadow: none !important; 70 | } 71 | -------------------------------------------------------------------------------- /src/styles/renderer/data/identifier.scss: -------------------------------------------------------------------------------- 1 | .identifier { 2 | font-size: 12px; 3 | border-radius: var(--border-radius); 4 | font-family: var(--code-font-family); 5 | color: black; 6 | padding: 1px 2px 1px 2px; 7 | height: fit-content; 8 | } 9 | 10 | .identifier.selected { 11 | color: var(--s-color-4); 12 | font-weight: 500; 13 | } 14 | 15 | .identifier.ret { 16 | color: var(--s-color-3); 17 | font-weight: 500; 18 | } 19 | 20 | .identifier.trail-create { 21 | color: var(--s-color-2); 22 | } 23 | 24 | .identifier.trail-move { 25 | color: var(--s-color-3); 26 | } 27 | 28 | /* ------------------------ Focus ----------------------- */ 29 | .identifier.secondary-focused { 30 | color: var(--s-color-3); 31 | opacity: 0.7; 32 | } 33 | 34 | .identifier.identifier-write { 35 | color: var(--s-color-0-3); 36 | } 37 | 38 | .identifier.identifier-read { 39 | color: #ff9431; 40 | } 41 | -------------------------------------------------------------------------------- /src/styles/renderer/data/literal.scss: -------------------------------------------------------------------------------- 1 | /** Literal style used for computing layout **/ 2 | 3 | .data-literal, 4 | .data-function { 5 | font-size: var(--code-font-size); 6 | font-family: var(--code-font-family); 7 | color: var(--color-4-1); 8 | padding: 0px 4px; 9 | min-height: 20px; 10 | font-weight: 400; 11 | position: relative; 12 | z-index: 10; 13 | width: fit-content; 14 | font-size: 14px; 15 | transition-property: color; 16 | transition-duration: 0s; // Flashes if given a transition 17 | } 18 | 19 | .data-array > .hole > .data-literal { 20 | // padding: 0px 0px 0px 6px; 21 | } 22 | 23 | .data-literal.environment-write { 24 | color: var(--s-color-0-3); 25 | } 26 | 27 | .data-literal.environment-read { 28 | color: #ff9431; 29 | } 30 | 31 | .data-array > .hole:nth-child(2) > .data-literal { 32 | // padding: 0px 0px 0px 0px; 33 | } 34 | 35 | .data-function { 36 | color: var(--s-color-2); 37 | } 38 | 39 | .data-literal-string { 40 | color: var(--s-color-2) !important; 41 | } 42 | 43 | .data-literal.trail-create { 44 | box-shadow: 0px 0px 0px 0.8px var(--s-color-2) inset; 45 | } 46 | 47 | .data-literal.trail-move { 48 | box-shadow: 0px 0px 0px 0.8px var(--s-color-3) inset; 49 | } 50 | 51 | .data-literal.data-literal-boolean.true { 52 | color: var(--s-color-2); 53 | } 54 | 55 | .data-literal.data-literal-boolean.false { 56 | color: var(--s-color-5); 57 | } 58 | -------------------------------------------------------------------------------- /src/styles/renderer/trail.scss: -------------------------------------------------------------------------------- 1 | /* ------------------------------------------------------ */ 2 | /* Defines styles for trails */ 3 | /* ------------------------------------------------------ */ 4 | 5 | /* --------------------- Animations --------------------- */ 6 | @keyframes showStroke { 7 | from { 8 | stroke-dashoffset: 200; 9 | } 10 | to { 11 | stroke-dashoffset: 0; 12 | } 13 | } 14 | 15 | /* --------------------- Trail path --------------------- */ 16 | .trail-path { 17 | fill: #0000; 18 | stroke: url(#grad1); 19 | stroke-width: 1px; 20 | z-index: -1; 21 | // animation: showStroke 10s; 22 | } 23 | 24 | .trail-move { 25 | stroke: url(#grad3) !important; 26 | stroke-dasharray: 6 2; 27 | stroke-dashoffset: 0; 28 | } 29 | 30 | .trail-move.is-expired { 31 | filter: saturate(0); 32 | } 33 | 34 | .trail-create { 35 | stroke: url(#grad2); 36 | stroke-width: 1px; 37 | } 38 | 39 | .trail-path.hidden { 40 | display: none; 41 | opacity: 0; 42 | } 43 | 44 | .reference-movement-path { 45 | fill: #0000; 46 | stroke: #0000; 47 | } 48 | -------------------------------------------------------------------------------- /src/styles/themes/coffee.scss: -------------------------------------------------------------------------------- 1 | /* ------------------------------------------------------ */ 2 | /* Coffee-like light brown theme */ 3 | /* ------------------------------------------------------ */ 4 | :root { 5 | --color-0: #f7f4f3; 6 | --color-1: #eae2de; 7 | --color-2: #d2c8c6; 8 | --color-3: #ae9a98; 9 | --color-4: #482528; 10 | --color-5: #2f2625; 11 | --color-6: #171212; 12 | 13 | --s-color-0: #cfe2ea; 14 | --s-color-1: #bdc3c5; 15 | --s-color-2: #a0c4d0; 16 | } 17 | -------------------------------------------------------------------------------- /src/vite-env.d.ts: -------------------------------------------------------------------------------- 1 | /// 2 | -------------------------------------------------------------------------------- /test.js: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/hayatpur/crosscode/8a73693812fc5afd02cfa41107128f149770ec7a/test.js -------------------------------------------------------------------------------- /tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "compilerOptions": { 3 | "target": "ESNext", 4 | "useDefineForClassFields": true, 5 | "module": "ESNext", 6 | "lib": ["ESNext", "DOM"], 7 | "moduleResolution": "Node", 8 | "strict": true, 9 | "sourceMap": true, 10 | "resolveJsonModule": true, 11 | "isolatedModules": true, 12 | "esModuleInterop": true, 13 | "noEmit": true, 14 | "noUnusedLocals": true, 15 | "noUnusedParameters": true, 16 | "noImplicitReturns": true, 17 | "skipLibCheck": true 18 | }, 19 | "include": ["src"] 20 | } 21 | -------------------------------------------------------------------------------- /tutorial/01_basic.js: -------------------------------------------------------------------------------- 1 | // Tutorial A 2 | // 3 | // Controls: 4 | // - Left key to move one step back 5 | // - Right key to move one step forward 6 | // - Up key to move animation backward 7 | // - Down key to move animation forward 8 | // - Grab control flow cursor to move 9 | // - Set animation speed 10 | // Concepts 11 | // - Control flow panel 12 | // - Data panel 13 | // - Source code panel 14 | // - Color encoding for read and writes 15 | // - Trace and trace information 16 | // - Movement and effect animations 17 | 18 | // Variable declaration 19 | let l = [1, 2, 3] 20 | 21 | // Trace, and partial trace 22 | let y = l[0] 23 | let z = l[2] + y 24 | 25 | // Residual 26 | y = z 27 | -------------------------------------------------------------------------------- /tutorial/02_if.js: -------------------------------------------------------------------------------- 1 | // Tutorial B 2 | // 3 | // Controls: 4 | // - Shift click to get break down a step 5 | // Concepts 6 | // - Multiple levels of operational detail 7 | // - Specialized representations for if statement 8 | 9 | let list = [1, 2, 3, 4, 5, 6, 7, 8, 9] 10 | let n = list.length 11 | 12 | if (list[0] < list[n - 1]) { 13 | let temp = list[0] 14 | list[0] = list[n - 1] 15 | list[n - 1] = temp 16 | } 17 | -------------------------------------------------------------------------------- /tutorial/03_for_func.js: -------------------------------------------------------------------------------- 1 | // Tutorial C 2 | // 3 | // Controls: 4 | // - Shift click into a function call 5 | // - For expanding / collapsing 6 | // - Minimize / maximize 7 | // - Progressive closure enable 8 | // - Expanding / collapsing abbreviations 9 | // Concepts 10 | // - Aggregation over breadth 11 | // - Abbreviations 12 | // - Specialized representations for For Statement 13 | // - Data state scope 14 | // - Detailed / less detailed representations 15 | // - Progressive closure 16 | 17 | function avg(list) { 18 | let sum = 0 19 | let n = list.length 20 | 21 | for (let i = 0; i < n; i = i + 1) { 22 | sum = sum + list[i] 23 | } 24 | 25 | return sum / n 26 | } 27 | 28 | let l = [1, 5, 6, 10, 2, 5, 1, 2] 29 | let k = avg(l) 30 | -------------------------------------------------------------------------------- /tutorial/03_rec.js: -------------------------------------------------------------------------------- 1 | // Tutorial D 2 | // 3 | // Controls: 4 | // - Progressive closure 5 | // - Progressive disclosure 6 | // - Selection from source code 7 | // Concepts 8 | // - Aggregation over depth 9 | 10 | function fact(x) { 11 | if (x > 1) { 12 | return x * fact(x - 1) 13 | } 14 | 15 | return 1 16 | } 17 | 18 | function fact2(x) { 19 | let r = 5 20 | let z = r + 1 21 | let y = z - r 22 | y = y - r 23 | r = y * y 24 | 25 | if (x > 1) { 26 | return x * fact(x - 1) 27 | } 28 | 29 | return 1 30 | } 31 | 32 | let n = 5 33 | let y = fact(n) 34 | -------------------------------------------------------------------------------- /vite.config.ts: -------------------------------------------------------------------------------- 1 | import { defineConfig } from 'vite' 2 | import monacoEditorPlugin from 'vite-plugin-monaco-editor' 3 | 4 | export default defineConfig({ 5 | plugins: [monacoEditorPlugin({})], 6 | }) 7 | --------------------------------------------------------------------------------