diff --git a/package-lock.json b/package-lock.json index 53f1e5d..749fe4c 100644 --- a/package-lock.json +++ b/package-lock.json @@ -9,6 +9,7 @@ "version": "0.1.0", "dependencies": { "@lambdulus/core": "^0.0.7", + "@lambdulus/tiny-lisp-core": "git+ssh://git@github.com:lambdulus/tiny-lisp-core.git", "@testing-library/jest-dom": "^5.14.1", "@testing-library/react": "^12.1.2", "@testing-library/user-event": "^13.5.0", @@ -2366,6 +2367,11 @@ "resolved": "https://registry.npmjs.org/@lambdulus/core/-/core-0.0.7.tgz", "integrity": "sha512-62LrHJFjNlfdi01l/eBTEP4gcx2TGYCjI/GSoiACgsfyflL3HJJGKcKqjJ3v7i2YWZnVXwMYIv2op/AB4S+NGA==" }, + "node_modules/@lambdulus/tiny-lisp-core": { + "version": "0.0.1", + "resolved": "git+ssh://git@github.com/lambdulus/tiny-lisp-core.git", + "license": "MIT" + }, "node_modules/@nodelib/fs.scandir": { "version": "2.1.5", "resolved": "https://registry.npmjs.org/@nodelib/fs.scandir/-/fs.scandir-2.1.5.tgz", @@ -22712,6 +22718,10 @@ "resolved": "https://registry.npmjs.org/@lambdulus/core/-/core-0.0.7.tgz", "integrity": "sha512-62LrHJFjNlfdi01l/eBTEP4gcx2TGYCjI/GSoiACgsfyflL3HJJGKcKqjJ3v7i2YWZnVXwMYIv2op/AB4S+NGA==" }, + "@lambdulus/tiny-lisp-core": { + "version": "git+ssh://git@github.com/lambdulus/tiny-lisp-core.git", + "from": "@lambdulus/tiny-lisp-core@git+ssh://git@github.com:lambdulus/tiny-lisp-core.git" + }, "@nodelib/fs.scandir": { "version": "2.1.5", "resolved": "https://registry.npmjs.org/@nodelib/fs.scandir/-/fs.scandir-2.1.5.tgz", diff --git a/package.json b/package.json index 2d76bdb..9dcd4f1 100644 --- a/package.json +++ b/package.json @@ -4,6 +4,7 @@ "private": true, "dependencies": { "@lambdulus/core": "^0.0.7", + "@lambdulus/tiny-lisp-core": "git+ssh://git@github.com:lambdulus/tiny-lisp-core.git", "@testing-library/jest-dom": "^5.14.1", "@testing-library/react": "^12.1.2", "@testing-library/user-event": "^13.5.0", @@ -42,4 +43,4 @@ "last 1 safari version" ] } -} +} \ No newline at end of file diff --git a/src/Constants.ts b/src/Constants.ts index 6609de7..a96390b 100644 --- a/src/Constants.ts +++ b/src/Constants.ts @@ -10,7 +10,6 @@ export const CLEAR_WORKSPACE_CONFIRMATION : string = Are you sure?` - export function mapBoxTypeToStr (type : BoxType) : string { switch (type) { case BoxType.UNTYPED_LAMBDA: @@ -75,6 +74,7 @@ export function loadSettingsFromStorage () : GlobalSettings { export function loadAppStateFromStorage () : AppState { + return EmptyAppState/* const maybeState : string | null = localStorage.getItem('AppState') if (maybeState === null) { @@ -90,11 +90,12 @@ export function loadAppStateFromStorage () : AppState { return EmptyAppState } - } + }*/ } export function updateAppStateToStorage (state : AppState) : void { - localStorage.setItem('AppState', JSON.stringify(state)) + const {stringify} = require('flatted/cjs'); + localStorage.setItem('AppState', stringify(state)) } export function updateNotebookStateToStorage (notebook : NotebookState, index : number) { diff --git a/src/Types.ts b/src/Types.ts index e1176c1..0ac39bc 100644 --- a/src/Types.ts +++ b/src/Types.ts @@ -1,5 +1,6 @@ import { UntypedLambdaState, UntypedLambdaSettings } from "./untyped-lambda-integration/Types" import { NoteState } from "./markdown-integration/AppTypes" +import { TinyLispState } from "./tiny-lisp-integration/Types" export enum BoxType { UNTYPED_LAMBDA = 'UNTYPED_LAMBDA', @@ -26,15 +27,11 @@ export interface AbstractSettings { type : BoxType, } -export interface LispBox extends AbstractBoxState { - // TODO: delete this placeholder and implement it -} - export interface LispSettings extends AbstractSettings { // TODO: delete this placeholder and implement it } -export type BoxState = UntypedLambdaState | LispBox | NoteState // or other things in the future +export type BoxState = UntypedLambdaState | TinyLispState | NoteState // or other things in the future export type Settings = UntypedLambdaSettings | LispSettings // or other things in the future diff --git a/src/components/Box.tsx b/src/components/Box.tsx index 10f960d..ffbceb4 100644 --- a/src/components/Box.tsx +++ b/src/components/Box.tsx @@ -15,6 +15,8 @@ import { NoteState } from '../markdown-integration/AppTypes' import Note from '../markdown-integration/Note' import Empty from '../empty-integration' +import TinyLispBox from '../tiny-lisp-integration/TinyLispBox' +import { TinyLispState } from '../tiny-lisp-integration/Types' // import { BoxState } from '../AppTypes' @@ -59,6 +61,17 @@ export default function Box (props : BoxProperties) : JSX.Element { /> ) } + if (type === BoxType.LISP) { + return ( + + ) + } else { return ( diff --git a/src/components/BoxTitleBar.tsx b/src/components/BoxTitleBar.tsx index 8e1ddb1..66dfccb 100644 --- a/src/components/BoxTitleBar.tsx +++ b/src/components/BoxTitleBar.tsx @@ -219,7 +219,8 @@ export default class BoxTitleBar extends Component { break } case BoxType.MARKDOWN: { - updateBoxState({ ...state, isEditing : true }) + updateBoxState({ ...state, isEditing : true } as NoteState) + // updateBoxState(resetMarkdownBox(state as NoteState)) break } } diff --git a/src/components/PickBoxTypeModal.tsx b/src/components/PickBoxTypeModal.tsx index ab89fac..c83348d 100644 --- a/src/components/PickBoxTypeModal.tsx +++ b/src/components/PickBoxTypeModal.tsx @@ -2,10 +2,14 @@ import React from 'react' import { GlobalSettings, BoxState } from '../Types' import { createNewMarkdown } from '../markdown-integration/AppTypes' import { UntypedLambdaSettings, UntypedLambdaState } from '../untyped-lambda-integration/Types' -import { createNewUntypedLambdaExpression, ADD_BOX_LABEL, CODE_NAME as UNTYPED_CODE_NAME } from '../untyped-lambda-integration/AppTypes' + +import { createNewUntypedLambdaExpression, ADD_BOX_LABEL as UNTYPED_ADD_BOX_LABEL, CODE_NAME as UNTYPED_CODE_NAME } from '../untyped-lambda-integration/AppTypes' +import { CODE_NAME as TINY_LISP_CODE_NAME, ADD_BOX_LABEL as TINY_LISP_ADD_BOX_LABEL } from '../tiny-lisp-integration/Constants' import '../styles/PickBoxTypeModal.css' +import { createNewTinyLispExpression } from '../tiny-lisp-integration/Constants' +import { TinyLispSettings } from '../tiny-lisp-integration/Types' interface Props { @@ -18,6 +22,7 @@ export default function PickBoxTypeModal (props : Props) : JSX.Element { const { addNew, settings } : Props = props const untLSettings : UntypedLambdaSettings = settings[UNTYPED_CODE_NAME] as UntypedLambdaState + const tinyLSettings : TinyLispSettings = settings[TINY_LISP_CODE_NAME] as TinyLispSettings const addLambdaBox = (
-

λ

-

{ ADD_BOX_LABEL }

+
+

λ

+

{ UNTYPED_ADD_BOX_LABEL }

+
) - // const addLispBox = ( - //
{ - // e.stopPropagation() - // // this.setState({ opened : false }) - // addNew({__key : Date.now().toString()} as BoxState) } // NOTE: just for now - // } - // > - //
- //

()

- //

+ Lisp

- //
- //
- // ) + +const addLispBox = ( +
{ + e.stopPropagation() + // this.setState({ opened : false }) + addNew(createNewTinyLispExpression(tinyLSettings)) } // NOTE: just for now + } + > +
+

()

+

{ TINY_LISP_ADD_BOX_LABEL }

+
+
+ ) const addMDBox = (
{ addLambdaBox } - {/* { addLispBox } */} + { addLispBox } { addMDBox }
diff --git a/src/components/TopBar.tsx b/src/components/TopBar.tsx index 6f6e1db..dbb81a4 100644 --- a/src/components/TopBar.tsx +++ b/src/components/TopBar.tsx @@ -21,8 +21,8 @@ export default function TopBar (props : Props) : JSX.Element { const { name } : NotebookState = ntbk // const dehydrated : object = dehydrate(state) - - const serialized : string = JSON.stringify(ntbk) + const {stringify} = require('flatted/cjs'); + const serialized : string = stringify(ntbk) const link : string = createURL(serialized) return ( diff --git a/src/styles/PickBoxTypeModal.css b/src/styles/PickBoxTypeModal.css index 327d835..066712c 100644 --- a/src/styles/PickBoxTypeModal.css +++ b/src/styles/PickBoxTypeModal.css @@ -24,7 +24,7 @@ .add-box--group { display: inline-block; - width: 50%; + width: 33.3%; text-align: center; /* margin: 30px; */ margin-right: 0%; diff --git a/src/tiny-lisp-integration/Constants.ts b/src/tiny-lisp-integration/Constants.ts new file mode 100644 index 0000000..b5e02d2 --- /dev/null +++ b/src/tiny-lisp-integration/Constants.ts @@ -0,0 +1,33 @@ +import { InstructionShortcut } from "@lambdulus/tiny-lisp-core"; +import { BoxType } from "../Types"; +import { TinyLispSettings, TinyLispState, TinyLispType } from "./Types" + +export const ADD_BOX_LABEL = '+ Lisp Expression' + +export const CODE_NAME = 'TINY_LISP' + + +export function createNewTinyLispExpression (defaultSettings : TinyLispSettings) : TinyLispState { + return { + __key : Date.now().toString(), + type : BoxType.LISP, + title : 'New Tiny Lisp Expression', + minimized : false, + settingsOpen : false, + + subtype: TinyLispType.EMPTY, + expression : '', + mouseOver: null, + cleanNeeded: false, + interpreterState: null, + errorMsg: null, + + editor : { + placeholder : '', + content : '', + caretPosition : -1, + syntaxError : null, + }, + current : InstructionShortcut.DUMMY + }; +} diff --git a/src/tiny-lisp-integration/Painter.ts b/src/tiny-lisp-integration/Painter.ts new file mode 100644 index 0000000..a25a94c --- /dev/null +++ b/src/tiny-lisp-integration/Painter.ts @@ -0,0 +1,179 @@ +import {ColourType, IfNode, Instruction, InstructionShortcut, InterpreterUtils, + InterpreterState, SECDArray, SECDElement, SECDValue } from "@lambdulus/tiny-lisp-core" +import {BinaryExprNode, CompositeNode, ApplicationNode, InnerNode, QuoteNode, ReduceNode, UnaryExprNode } from "@lambdulus/tiny-lisp-core/dist/AST/AST" + + +export class Painter { + + constructor(private readonly state: InterpreterState) { + + } + + clean(){ + this.state.topNode.clean(); + this.state.code.clearPrinted() + this.state.code.clean(); + this.state.stack.clearPrinted() + this.state.stack.clean(); + this.state.dump.clearPrinted() + this.state.dump.clean(); + this.state.environment.clearPrinted() + this.state.environment.clean(); + } + + colourArray(instruction: Instruction) { + this.clean() + if(instruction.shortcut === InstructionShortcut.DUMMY){ + return + } + let instructionShortcut = instruction.shortcut + let element: SECDElement + this.state.code.get(this.state.code.length() - 1).colour = ColourType.Current + let node: InnerNode + //@ts-ignore + switch (instructionShortcut) { + case InstructionShortcut.LDC: + element = this.state.code.get(this.state.code.length() - 2); + element.node.setColour(ColourType.Coloured) + element.colour = ColourType.Coloured + break + case InstructionShortcut.LD: + this.state.code.get(this.state.code.length() - 1).node.setColour(ColourType.Current) + element = this.state.code.get(this.state.code.length() - 2); + let loaded: SECDElement + if (element instanceof SECDArray) { + element.colour = ColourType.Current + let index1 = element.get(1) + let index2 = element.get(0) + if (!(index1 instanceof SECDValue && index2 instanceof SECDValue)) + throw Error() + let val1 = index1.constant + let val2 = index2.constant + if ((typeof (val1) != "number") && (typeof (val2) != "number")) + throw Error() + loaded = InterpreterUtils.evaluateLoad(this.state.environment, val1 as unknown as number, val2 as unknown as number) + if (loaded.node.parent instanceof QuoteNode) {//If it is quoted list colour it all + loaded.node.parent.colour = ColourType.Coloured + loaded.node.parent.setColour(ColourType.Coloured) + } else { + loaded.colour = ColourType.Coloured; + loaded.node.setColour(ColourType.Coloured) + } + } else + throw Error() + break + case InstructionShortcut.SEL: + //colour current instruction, condition on top of the stack and 2 branches on the stack + //colour registers + this.state.stack.get(this.state.stack.length() - 1).colour = ColourType.Coloured + this.state.code.get(this.state.code.length() - 2).colour = ColourType.SecondColoured + this.state.code.get(this.state.code.length() - 3).colour = ColourType.ThirdColoured + //colour nodes + let ifNode = this.state.code.get(this.state.code.length() - 1).node as IfNode + ifNode.setColour(ColourType.Current) + ifNode.condition().setColour(ColourType.Coloured) + ifNode.left().setColour(ColourType.SecondColoured) + ifNode.right().setColour(ColourType.ThirdColoured) + + break + case InstructionShortcut.JOIN: + //colour the top of the dumb, where is new content of the code register + this.state.dump.get(this.state.dump.length() - 1).colour = ColourType.Coloured; + this.state.code.get(this.state.code.length() - 1).node.setColour(ColourType.Coloured) + break + case InstructionShortcut.NIL: + break + case InstructionShortcut.DUM: + break + case InstructionShortcut.POP: + //colour the top of the stack register that will be popped + this.state.stack.get(this.state.stack.length() - 1).colour = ColourType.Coloured + break + case InstructionShortcut.CONSP: + case InstructionShortcut.CAR: + case InstructionShortcut.CDR: + //colour nodes and registers of argument an operator + this.state.code.get(this.state.code.length() - 1).node.setColour(ColourType.Current); + (this.state.code.get(this.state.code.length() - 1).node.parent as UnaryExprNode).expr().setColour(ColourType.Coloured); + this.state.stack.get(this.state.stack.length() - 1).colour = ColourType.Coloured + break + case InstructionShortcut.ADD: + case InstructionShortcut.SUB: + case InstructionShortcut.MUL: + case InstructionShortcut.DIV: + case InstructionShortcut.EQ: + case InstructionShortcut.NE: + case InstructionShortcut.LT: + case InstructionShortcut.LE: + case InstructionShortcut.HT: + case InstructionShortcut.HE: + //colour nodes and registers of arguments an operator + this.state.code.get(this.state.code.length() - 1).node.setColour(ColourType.Current); + (this.state.code.get(this.state.code.length() - 1).node.parent as BinaryExprNode).left().setColour(ColourType.Coloured); + (this.state.code.get(this.state.code.length() - 1).node.parent as BinaryExprNode).right().setColour(ColourType.SecondColoured); + this.state.stack.get(this.state.stack.length() - 1).colour = ColourType.Coloured + this.state.stack.get(this.state.stack.length() - 2).colour = ColourType.SecondColoured + break + case InstructionShortcut.CONS: + //since Cons is sometimes used on empty arrays without nodes, it is coloured differently than other bin instructions + //just the arguments in registers are coloured, not the node and the operator node has green colour + this.state.code.get(this.state.code.length() - 1).node.setColour(ColourType.Coloured) + this.state.stack.get(this.state.stack.length() - 1).colour = ColourType.Coloured + this.state.stack.get(this.state.stack.length() - 2).colour = ColourType.SecondColoured; + break + case InstructionShortcut.LDF: + //colour the function + this.state.code.get(this.state.code.length() - 2).colour = ColourType.Coloured; + this.state.code.get(this.state.code.length() - 2).node.setColour(ColourType.Coloured) + break + case InstructionShortcut.AP: + element = this.state.stack.get(this.state.stack.length() - 1) + element.colour = ColourType.Current + node = element.node + node.setColour(ColourType.Current); + if (node.parent instanceof ApplicationNode) { + //Normal non-recursive lambdas + //Here argument will be highlighted even if it is variable in code + let args = (node.parent as ApplicationNode).args() + if (args instanceof ReduceNode) + (args.reduced() as CompositeNode).items().forEach(node => node.setColour(ColourType.Coloured)) + else + (args as CompositeNode).items().forEach(node => node.setColour(ColourType.Coloured)) + } else { + //For lambdas defined without argument immediately following them + //If argument is list colour it whole + if (this.state.stack.get(this.state.stack.length() - 2).node instanceof QuoteNode) + this.state.stack.get(this.state.stack.length() - 2).node.setColour(ColourType.Coloured) + //Because of recursive functions where argument is in code just once + else + (this.state.stack.get(this.state.stack.length() - 2) as SECDArray).forEach(element => element.node.setColour(ColourType.Coloured)); + } + (this.state.stack.get(this.state.stack.length() - 2) as SECDArray).forEach(element => element.colour = ColourType.Coloured); + break + case InstructionShortcut.RAP: + //colour top 2 elements in the stack (function closure and arguments) + this.state.stack.get(this.state.stack.length() - 1).colour = ColourType.Current + this.state.stack.get(this.state.stack.length() - 1).node.colour = ColourType.Current; + (this.state.stack.get(this.state.stack.length() - 2) as SECDArray).get(0).colour = ColourType.Coloured; + (this.state.stack.get(this.state.stack.length() - 2) as SECDArray).get(0).node.colour = ColourType.Coloured + break + case InstructionShortcut.RTN: + //colour the top of the stack that will be returned and top 3 elements in dumb that will move to other registers + this.state.stack.get(this.state.stack.length() - 1).colour = ColourType.Current; + this.state.code.get(this.state.code.length() - 1).node.setColour(ColourType.Current) + this.state.dump.get(this.state.dump.length() - 2).colour = ColourType.ThirdColoured + this.state.dump.get(this.state.dump.length() - 3).colour = ColourType.SecondColoured + this.state.dump.get(this.state.dump.length() - 4).colour = ColourType.Coloured + break + case InstructionShortcut.DEFUN: + //colour top of the stack register + this.state.code.get(this.state.code.length() - 1).node.setColour(ColourType.Current) + this.state.stack.get(this.state.stack.length() - 1).colour = ColourType.Current + break + case InstructionShortcut.STOP: + break + default: + throw Error() + } + } +} \ No newline at end of file diff --git a/src/tiny-lisp-integration/ReactSECDPrinter.tsx b/src/tiny-lisp-integration/ReactSECDPrinter.tsx new file mode 100644 index 0000000..87e83df --- /dev/null +++ b/src/tiny-lisp-integration/ReactSECDPrinter.tsx @@ -0,0 +1,174 @@ +import { + ColourType, + ApplicationNode, + InnerNode, + Instruction, + InstructionShortcut, + SECDArray, + SECDElement, + SECDValue, + PrintedState, + GeneralUtils, + ReduceNode, + SECDHidden +} from "@lambdulus/tiny-lisp-core" +import React from "react"; + +import './styles/Step.css' + + +export default class ReactSECDPrinter { + private rendered : JSX.Element | null = null + private enclosingArrayColoured: boolean = false + private placeholders: Array = [] + + constructor(arr: SECDArray, public hasMouseOver: () => boolean, public parentHasMouseOver: (innerNode: InnerNode, returnTrueIfColoured: boolean) => boolean, + readonly current: InstructionShortcut) { + this.visit(arr) + this.placeholders = [] + } + + + public print(): JSX.Element | null{ + return this.rendered + } + + visit(arr: SECDArray): void { + this.rendered = this.getElements(arr) + } + + /** + * Generate React Component for SECDElement + * @param element + * @private + */ + + private getElements(element: SECDElement): JSX.Element{ + if(element instanceof SECDArray) { + if(element.empty()) {//If empty than no need for recursion + return [] + } + if(element.printedState !== PrintedState.Not) {//If already printed and placeholder string can be used, use the placeholder + if (typeof (element.node) != "undefined") { + console.log("01245648684", element) + let str = GeneralUtils.getFunctionName(element.node) + if (str !== "") { + this.placeholders.push(str) + element.printedInc() + let colour: ColourType = this.enclosingArrayColoured ? ColourType.None : element.colour//If node is coloured, colour also placeholder + let name = this.parentHasMouseOver(element.node, colour !== ColourType.None) + && !this.enclosingArrayColoured ? this.highlight(colour) : this.underline(colour) + return + [{str}] + + } + } + } + element.printedInc()//Array is printed so update its printed state + let array : JSX.Element[] = [] + if(element.colour !== ColourType.None)//If this array is coloured, don't colour inner elements + this.enclosingArrayColoured = true + array = element.arr.map(value => this.getElements(value)).reverse();//call getElements on values and reverse so top of registers will be on left + if(element.colour !== ColourType.None) + this.enclosingArrayColoured = false + let name: string = this.getClassName(element)// get class name + // @ts-ignore + if(element.printedState === PrintedState.More && element.node){//If array is inside itself, declare a placeholder + let str = GeneralUtils.getFunctionName(element.node) + console.log("jhkjbhll", element) + if(str !== "") { + return + {str} + {': ['} + {array} + {']'} + + } + } + return + {'['} + {array} + {']'} + + + } + else if(element instanceof SECDValue){ + let name: string = this.getClassName(element) + if(element.constant instanceof Instruction){ + return + {' '} + {element.constant.toString()} + {' '} + + } + return + {' '} + {element.constant} + {' '} + + + } + else if(element instanceof SECDHidden){ + return + } + throw Error() + } + + /** + * Returns className picked based on elements colour. + * If elements node or its predecesor is coloured className start as highlighte, othrewise it starts as underline. + * @param val + * @protected + */ + + protected getClassName(val: SECDElement): string{ + if(this.enclosingArrayColoured) + return this.underline(val.colour) + let node = val.node//important + if(typeof(node) != "undefined"){ + let coloured = val.colour !== ColourType.None + if(node instanceof ApplicationNode && val.colour === ColourType.Current){//AP instruction is specificaly coloured + node = node.func() + if(node instanceof ReduceNode){ + node = node.reduced()//If func is recursive function then next is its name in code and reduced its lambda + } + } + if(this.parentHasMouseOver(node, coloured)){ + return this.highlight(val.colour) + } + } + return this.underline(val.colour) + } + + protected underline(colour: ColourType): string{ + switch (colour){ + case ColourType.Current: + return "underlineCurrent" + case ColourType.Coloured: + return "underlineFirstArgument" + case ColourType.SecondColoured: + return "underlineSecondArgument" + case ColourType.ThirdColoured: + return "underlineThirdArgument" + case ColourType.None: + default: + return "normalInstruction" + } + } + + protected highlight(colour: ColourType): string{ + switch (colour){ + case ColourType.Current: + return "highlightCurrent" + case ColourType.Coloured: + return "highlightFirstArgument" + case ColourType.SecondColoured: + return "highlightSecondArgument" + case ColourType.ThirdColoured: + return "highlightThirdArgument" + case ColourType.None: + default: + return "highlightOther" + } + } +} \ No newline at end of file diff --git a/src/tiny-lisp-integration/ReactTreePrinter.tsx b/src/tiny-lisp-integration/ReactTreePrinter.tsx new file mode 100644 index 0000000..62a9e47 --- /dev/null +++ b/src/tiny-lisp-integration/ReactTreePrinter.tsx @@ -0,0 +1,858 @@ +import { + BinaryExprNode, + CallNode, + ColourType, + CompositeNode, + DefineNode, + ReduceNode, + ApplicationNode, + IfNode, + InnerNode, + LambdaNode, + LetNode, + BindNode, + LispASTVisitor, + MainNode, + OperatorNode, + StringNode, + TopNode, + UnaryExprNode, + ValueNode, + VarNode, + Instruction, + BeginNode, + QuoteNode +} from "@lambdulus/tiny-lisp-core"; +import React, {MouseEvent} from "react"; + +import './styles/Step.css' + +/** + * Extends LispASTVisitor class + * + * For each node, a check is done to see if its predecessor is coloured + * If so it just renders the node without any additional CSS + * + * If not, it prepares the node to handle a mouse over it, and it looks at its colour + * + * If it has an expected colour, it underlines the node in source code with the colour + * + * Otherwise it + */ + +export default class ReactTreePrinter extends LispASTVisitor{ + private rendered : JSX.Element | null = null + private predecessorColoured: boolean = false + private quoted = false + private tabs: Array = [] + + constructor(public readonly node: TopNode, public mouseOver: (node: InnerNode) => void, public mouseLeft: () => void) { + super() + node.accept(this) + } + + public print(): JSX.Element | null{ + return this.rendered + } + + private handleMouseOver = (e: MouseEvent, node: InnerNode) => { + e.preventDefault() + this.mouseOver(node) + } + private handleMouseLeft = (e: MouseEvent) => { + e.preventDefault() + this.mouseLeft() + } + + private addTab(){ + this.tabs.push(      ) + } + + private removeTab(){ + this.tabs.pop() + } + + onBinaryExprNode(node: BinaryExprNode): void { + if(this.predecessorColoured){ + node.operator().accept(this) + let rend1 = this.rendered + node.left().accept(this) + let rend2 = this.rendered + node.right().accept(this) + let rend3 = this.rendered + this.rendered = + {'('} + {rend1} + {' '} + {rend2} + {' '} + {rend3} + {')'} + + } + else { + if(node.colour === ColourType.Current || node.colour === ColourType.Coloured) + this.predecessorColoured = true + node.operator().accept(this) + let rend1 = this.rendered + node.left().accept(this) + let rend2 = this.rendered + node.right().accept(this) + let rend3 = this.rendered + if(node.colour === ColourType.Current || node.colour === ColourType.Coloured) + this.predecessorColoured = false + if (node.colour === ColourType.Current) { + this.rendered = + this.handleMouseOver(e, node)} + onMouseLeave={e => this.handleMouseLeft(e)}> + {'('} + + this.handleMouseOver(e, node.operator())} + onMouseLeave={e => this.handleMouseLeft(e)}> + {rend1} + + {' '} + this.handleMouseOver(e, node.left())} + onMouseLeave={e => this.handleMouseLeft(e)}> + {rend2} + + {' '} + this.handleMouseOver(e, node.right())} + onMouseLeave={e => this.handleMouseLeft(e)}> + {rend3} + + this.handleMouseOver(e, node)} + onMouseLeave={e => this.handleMouseLeft(e)}> + {')'} + + + } + else if (node.colour === ColourType.Coloured) { + this.rendered = this.handleMouseOver(e, node)} + onMouseLeave={e => this.handleMouseLeft(e)}> + {'('} + {rend1} + {' '} + {rend2} + {' '} + {rend3} + {')'} + + } + else { + this.rendered = + this.handleMouseOver(e, node)} + onMouseLeave={e => this.handleMouseLeft(e)}> + {'('} + + + {rend1} + + {' '} + + {rend2} + + {' '} + + {rend3} + + this.handleMouseOver(e, node)} + onMouseLeave={e => this.handleMouseLeft(e)}> + {')'} + + + } + if(node.colour === ColourType.Current) + this.predecessorColoured = false + } + } + + onCompositeNode(node: CompositeNode): void { + let acc: Array = [] + node.items().forEach(item => { + item.accept(this) + if (this.rendered) { + acc.push(this.rendered) + acc.push( + {' '} + ) + } + else + throw Error() + }) + acc = acc.slice(0, -1)//remove last ' ' + if(this.quoted)//If quoted add parentesess + if(this.predecessorColoured) { + this.rendered = + this.handleMouseOver(e, node)} onMouseLeave={e => this.handleMouseLeft(e)}> + ( + + {acc} + this.handleMouseOver(e, node)} onMouseLeave={e => this.handleMouseLeft(e)}> + ) + + + } + else { + if(node.colour === ColourType.Coloured){ + this.predecessorColoured = true + this.rendered = this.handleMouseOver(e, node)} onMouseLeave={e => this.handleMouseLeft(e)}> + ({acc}) + + this.predecessorColoured = false + } + else { + this.rendered = + this.handleMouseOver(e, node)} onMouseLeave={e => this.handleMouseLeft(e)}> + ( + + {acc} + this.handleMouseOver(e, node)} onMouseLeave={e => this.handleMouseLeft(e)}> + ) + + + } + } + else + this.rendered = + {acc} + + } + + onReduceNode(node: ReduceNode): void { + node.original().accept(this) + let rend = this.rendered + this.rendered = + {rend} + + } + + onApplicationNode(node: ApplicationNode): void { + if(this.predecessorColoured){ + node.func().accept(this) + let rend1 = this.rendered + node.args().accept(this) + let rend2 = this.rendered + this.rendered = + {'('} + {rend1} + {' '} + {rend2} + {')'} + + } + else { + if(node.colour === ColourType.Current || node.colour === ColourType.Coloured) + this.predecessorColoured = true + node.func().accept(this) + let rend1 = this.rendered + node.args().accept(this) + let rend2 = this.rendered + if(node.colour === ColourType.Current || node.colour === ColourType.Coloured) + this.predecessorColoured = false + if (node.colour === ColourType.Current) { + this.rendered = this.handleMouseOver(e, node)} + onMouseLeave={e => this.handleMouseLeft(e)}> + {'('} + {rend1} + {' '} + {rend2} + {')'} + + } + else if (node.colour === ColourType.Coloured) { + this.rendered = this.handleMouseOver(e, node)} + onMouseLeave={e => this.handleMouseLeft(e)}> + {'('} + {rend1} + {' '} + {rend2} + {')'} + + } + else { + this.rendered = + this.handleMouseOver(e, node)} + onMouseLeave={e => this.handleMouseLeft(e)}> + {'('} + + {rend1} + {' '} + {rend2} + this.handleMouseOver(e, node)} + onMouseLeave={e => this.handleMouseLeft(e)}> + {')'} + + + } + } + this.removeTab() + this.removeTab() + } + + onIfNode(node: IfNode): void { + this.addTab() + let tab = this.tabs.map(tab => Object.assign({}, tab)) + if(this.predecessorColoured){ + node.condition().accept(this) + let rend1 = this.rendered + node.left().accept(this) + let rend2 = this.rendered + node.right().accept(this) + let rend3 = this.rendered + this.rendered = + {"(if "} + {rend1} +

+ {tab} + {rend2} +

+ {tab} + {rend3} + ) +
+ } + else { + if(node.colour === ColourType.Current || node.colour === ColourType.Coloured) + this.predecessorColoured = true + node.condition().accept(this) + let rend1 = this.rendered + node.left().accept(this) + let rend2 = this.rendered + node.right().accept(this) + let rend3 = this.rendered + if (node.colour === ColourType.Current) { + this.rendered = + this.handleMouseOver(e, node)} + onMouseLeave={e => this.handleMouseLeft(e)}> + {"(if "} + + this.handleMouseOver(e, node.condition())} + onMouseLeave={e => this.handleMouseLeft(e)}> + {rend1} + +

+ {tab} + this.handleMouseOver(e, node.left())} + onMouseLeave={e => this.handleMouseLeft(e)}> + {rend2} + +

+ {tab} + this.handleMouseOver(e, node.right())} + onMouseLeave={e => this.handleMouseLeft(e)}> + {rend3} + + ) +
+ } + else if (node.colour === ColourType.Coloured) { + this.rendered = + this.handleMouseOver(e, node)} onMouseLeave={e => this.handleMouseLeft(e)}> + {"(if "} + {rend1} +

+ {tab} + {rend2} +

+ {tab} + {rend3} + ) +
+
+ } + else { + this.rendered = + this.handleMouseOver(e, node)} onMouseLeave={e => this.handleMouseLeft(e)}> + {"(if "} + + {rend1} +

+ {tab} + {rend2} +

+ {tab} + {rend3} + this.handleMouseOver(e, node)} onMouseLeave={e => this.handleMouseLeft(e)}> + ) + + +
+ } + if(node.colour === ColourType.Current || node.colour === ColourType.Coloured) + this.predecessorColoured = false + } + this.removeTab() + } + + onLambdaNode(node: LambdaNode): void { + this.addTab() + let tab = this.tabs.map(tab => Object.assign({}, tab)) + if(this.predecessorColoured){ + node.vars().accept(this) + let rend1 = this.rendered + node.body().accept(this) + let rend2 = this.rendered + this.rendered = + + {'(lambda ('} + {rend1} + {')'} +

+ {tab} + {rend2} + {')'} +
+ } + else { + this.predecessorColoured = true + node.vars().accept(this) + let rend1 = this.rendered + this.predecessorColoured = false + if(node.colour === ColourType.Current || node.colour === ColourType.Coloured) + this.predecessorColoured = true + node.body().accept(this) + let rend2 = this.rendered + if(node.colour === ColourType.Current || node.colour === ColourType.Coloured) + this.predecessorColoured = false + if (node.colour === ColourType.Current) { + this.rendered = + this.handleMouseOver(e, node)} + onMouseLeave={e => this.handleMouseLeft(e)}> + {'(lambda ('} + {rend1} + {')'} +

+ {tab} + {rend2} + {')'} +
+ } else if (node.colour === ColourType.Coloured) { + this.rendered = + this.handleMouseOver(e, node)} + onMouseLeave={e => this.handleMouseLeft(e)}> + {'(lambda ('} + {rend1} + {')'} +

+ {tab} + {rend2} + {')'} +
+ } + else { + this.rendered = + this.handleMouseOver(e, node)} + onMouseLeave={e => this.handleMouseLeft(e)}> + {'(lambda ('} + {rend1} + {')'} + +

+ {tab} + {rend2} + this.handleMouseOver(e, node)} + onMouseLeave={e => this.handleMouseLeft(e)}> + {')'} + +
+ } + } + this.removeTab() + } + + onTopNode(node: TopNode): void { + let acc: Array = [] + node.topLevelExprs.forEach(func => { + func.accept(this) + if (this.rendered) { + acc.push(this.rendered) + acc.push(

) + } + }) + this.rendered = + {acc} + + } + + onMainNode(node: MainNode) { + node.node.accept(this) + } + + onDefineNode(node: DefineNode): void {//We do not need to check this node for a predecessor, since it always a top level statement + if (node.colour === ColourType.Current || node.colour === ColourType.Coloured) + this.predecessorColoured = true + node.vars().accept(this) + let rend1 = this.rendered + node.body().accept(this) + let rend2 = this.rendered + if (node.colour === ColourType.Current || node.colour === ColourType.Coloured) + this.predecessorColoured = false + let name = node.name + let defineStr = node.isMacro ? 'define-macro' : 'define' + if(node.colour === ColourType.Current){ + this.rendered = this.handleMouseOver(e, node)} onMouseLeave={e => this.handleMouseLeft(e)}> + {'('+ defineStr + ' ('} + {node.name} + {' '} + {rend1} + {')'} +
    + {rend2} + {'))'} +
+
+ } + else if(node.colour === ColourType.Coloured){ + this.rendered = this.handleMouseOver(e, node)} onMouseLeave={e => this.handleMouseLeft(e)}> + {'('+ defineStr + ' ('} + {node.name} + {' '} + {rend1} + {')'} +
    + {rend2} + {'))'} +
+
+ } + else { + this.rendered =
+ + this.handleMouseOver(e, node)} + onMouseLeave={e => this.handleMouseLeft(e)}> + {'('+ defineStr + ' ('} + {name} + + {' '} + {rend1} + ) +
    + {rend2} + ) +
+
+
+ } + } + + onLetNode(node: LetNode) { + this.addTab() + let tab = this.tabs.map(tab => Object.assign({}, tab)) + if(this.predecessorColoured){ + node.bindings().accept(this) + let rend1 = this.rendered + node.body().accept(this) + let rend2 = this.rendered + this.rendered = + {'(let' + (node.recursive ? 'rec' : '') + '('} + {rend1} + {')'} +

+ {tab} + {rend2} + {')'} +
+ } + else { + if(node.colour === ColourType.Current) + this.predecessorColoured = true + node.bindings().accept(this) + let rend1 = this.rendered + node.body().accept(this) + let rend2 = this.rendered + if (node.colour === ColourType.Current) { + this.rendered = this.handleMouseOver(e, node)} + onMouseLeave={e => this.handleMouseLeft(e)}> + + {'(let' + (node.recursive ? 'rec' : '') + '('} + {rend1} + + {')'} +

+ {tab} + {rend2} + + {')'} + +
+ } + else { + this.rendered = + this.handleMouseOver(e, node)} + onMouseLeave={e => this.handleMouseLeft(e)}> + {'(let' + (node.recursive ? 'rec' : '')} + + {'('} + {rend1} + {')'} +

+ {tab} + {rend2} + this.handleMouseOver(e, node)} + onMouseLeave={e => this.handleMouseLeft(e)}> + {')'} + +
+ } + if(node.colour === ColourType.Current) + this.predecessorColoured = false + } + } + + onCallNode(node: CallNode) { + if(this.predecessorColoured){ + node.body().accept(this) + let rend1 = this.rendered + this.rendered = + {rend1} + + } + else { + if(node.colour === ColourType.Current || node.colour === ColourType.Coloured) + this.predecessorColoured = true + + node.body().accept(this) + let rend1 = this.rendered + if(node.colour === ColourType.Current){ + this.rendered = this.handleMouseOver(e, node)} onMouseLeave={e => this.handleMouseLeft(e)}> + {rend1} + + } + else if(node.colour === ColourType.Coloured){ + this.rendered = this.handleMouseOver(e, node)} onMouseLeave={e => this.handleMouseLeft(e)}> + {rend1} + + } + else { + this.rendered = + {rend1} + + } + + if(node.colour === ColourType.Current || node.colour === ColourType.Coloured) + this.predecessorColoured = false + } + } + + onUnaryExprNode(node: UnaryExprNode): void { + let operatorNode: InnerNode = node.operator() + let shortcut = Instruction.toSourceCode(operatorNode instanceof ReduceNode + ? (operatorNode.original() as OperatorNode).operator + : (operatorNode as OperatorNode).operator) + if(this.predecessorColoured){ + node.expr().accept(this) + let rend2 = this.rendered + this.rendered = + {'('} + {shortcut} + {' '} + {rend2} + {')'} + + } + else { + if(node.colour === ColourType.Current) + this.predecessorColoured = true + node.expr().accept(this) + let rend2 = this.rendered + if (node.colour === ColourType.Current) { + this.predecessorColoured = true + this.rendered = + {'('} + this.handleMouseOver(e, node)} onMouseLeave={e => this.handleMouseLeft(e)}> + {shortcut} + + {' '} + this.handleMouseOver(e, node.expr())} + onMouseLeave={e => this.handleMouseLeft(e)}> + {rend2} + + {')'} + + this.predecessorColoured = false + } + else { + this.rendered = + this.handleMouseOver(e, node)} + onMouseLeave={e => this.handleMouseLeft(e)}> + {'('} + {shortcut} + + {' '} + {rend2} + this.handleMouseOver(e, node)} + onMouseLeave={e => this.handleMouseLeft(e)}> + {')'} + + + } + if(node.colour === ColourType.Current) + this.predecessorColoured = false + } + } + + onValueNode(node: ValueNode): void { + if(this.predecessorColoured) + this.rendered = + {node.value} + + else + this.rendered = this.handleMouseOver(e, node)} onMouseLeave={e => this.handleMouseLeft(e)}> + {node.value} + + + } + + onVarNode(node: VarNode): void { + if(this.predecessorColoured) + this.rendered = + {node.variable} + + else + this.rendered = this.handleMouseOver(e, node)} onMouseLeave={e => this.handleMouseLeft(e)}> + {node.variable} + + } + + onOperatorNode(node: OperatorNode) { + if(this.predecessorColoured) + this.rendered = + {Instruction.toSourceCode(node.operator)} + + else + this.rendered = this.handleMouseOver(e, node)} onMouseLeave={e => this.handleMouseLeft(e)}> + {Instruction.toSourceCode(node.operator)} + + } + + onStringNode(node: StringNode) { + if(this.predecessorColoured) + this.rendered = + {node.str} + + else + this.rendered = this.handleMouseOver(e, node)} + onMouseLeave={e => this.handleMouseLeft(e)}> + {node.str} + + } + + onBeginNode(node: BeginNode){ + node.items().accept(this) + let rend = this.rendered + if(this.predecessorColoured) + this.rendered = + {'(begin '} + {rend} + {')'} + + else { + this.rendered = + this.handleMouseOver(e, node)} + onMouseLeave={e => this.handleMouseLeft(e)}> + {'(begin '} + + {rend} + this.handleMouseOver(e, node)} + onMouseLeave={e => this.handleMouseLeft(e)}> + {')'} + + + } + } + + + onQuoteNode(node: QuoteNode){ + let changeQuoted = !this.quoted + this.quoted = true + if(this.predecessorColoured) { + node.node().accept(this) + let rend = this.rendered + this.rendered = + {node.isBack ? '`' : '\''} + {rend} + + } + else { + if(node.colour !== ColourType.None) + this.predecessorColoured = true + node.node().accept(this) + let rend = this.rendered + if(node.colour !== ColourType.None) + this.predecessorColoured = false + this.rendered = + this.handleMouseOver(e, node)} + onMouseLeave={e => this.handleMouseLeft(e)}> + {node.isBack ? '`' : '\''} + + {rend} + this.handleMouseOver(e, node)} + onMouseLeave={e => this.handleMouseLeft(e)}> + + + + } + if(changeQuoted) + this.quoted = false + } + + + onCommaNode(node: QuoteNode){ + if(this.predecessorColoured) { + node.node().accept(this) + let rend = this.rendered + this.rendered = + ,{rend} + + } + else { + if(node.colour !== ColourType.None) + this.predecessorColoured = true + node.node().accept(this) + let rend = this.rendered + if(node.colour !== ColourType.None) + this.predecessorColoured = false + this.rendered = + this.handleMouseOver(e, node)} + onMouseLeave={e => this.handleMouseLeft(e)}> + , + + {rend} + + } + } + + + onBindNode(node: BindNode){ + node.variable().accept(this) + let rend = this.rendered + node.binded().accept(this) + let rend2 = this.rendered + this.rendered = + {"("} + {rend} + {" "} + {rend2} + {")"} + + } + + private static getClassName(node: InnerNode): string{ + switch (node.colour){ + case ColourType.Current: + return "underlineCurrent" + case ColourType.Coloured: + return "underlineFirstArgument" + case ColourType.SecondColoured: + return "underlineSecondArgument" + case ColourType.ThirdColoured: + return "underlineThirdArgument" + case ColourType.None: + default: + return "normalInstruction" + } + } + +} \ No newline at end of file diff --git a/src/tiny-lisp-integration/TinyLispBox.tsx b/src/tiny-lisp-integration/TinyLispBox.tsx new file mode 100644 index 0000000..44f27fd --- /dev/null +++ b/src/tiny-lisp-integration/TinyLispBox.tsx @@ -0,0 +1,350 @@ +import React, {PureComponent} from 'react' +import { + ColourType, + DefineNode, + ReduceNode, + InnerNode, + Interpreter, + MainNode, + Parser, + TopNode, + VarNode, + LexerError, + InterpreterError, + ParserError, + SyntaxError, + InterpreterState +} from '@lambdulus/tiny-lisp-core' + +import {TinyLispState, TinyLispType} from './Types' +import Editor from "../components/Editor"; +import ReactSECDPrinter from "./ReactSECDPrinter"; +import ReactTreePrinter from "./ReactTreePrinter"; +import { Painter } from './Painter'; + + +interface Props { + state : TinyLispState + isActive : boolean + isFocused : boolean + + setBoxState (state : TinyLispState) : void +} + +export default class TinyLispBox extends PureComponent { + constructor (props : Props) { + super(props) + this.onStep = this.onStep.bind(this) + this.onContent = this.onContent.bind(this) + this.onDebug = this.onDebug.bind(this) + this.onRun = this.onRun.bind(this) + this.onMouseOver = this.onMouseOver.bind(this) + this.onMouseLeft = this.onMouseLeft.bind(this) + this.hasMouseOver = this.hasMouseOver.bind(this) + this.parentHasMouseOver = this.parentHasMouseOver.bind(this) + } + + render () { + const { state } : Props = this.props + const { interpreterState, subtype } : TinyLispState = state + const renderBoxContent = () => { + switch(subtype) { + case TinyLispType.EMPTY: + return
+ void 0 } // fn + onCtrlEnter={ this.onDebug } + onShiftEnter={ () => void 0 } + onExecute={ () => void 0 } // fn + /> + +
+ + case TinyLispType.ORDINARY: + if(interpreterState == null){ + throw Error + } + //print source code + const staticLisp = new ReactTreePrinter(interpreterState.topNode, this.onMouseOver, this.onMouseLeft).print() + //print code register + const c = new ReactSECDPrinter(interpreterState.code, this.hasMouseOver, this.parentHasMouseOver, state.current).print() + //print stack register + const s = new ReactSECDPrinter(interpreterState.stack, this.hasMouseOver, this.parentHasMouseOver, state.current).print() + //print environment register + const e = new ReactSECDPrinter(interpreterState.environment, this.hasMouseOver, this.parentHasMouseOver, state.current).print() + //print dump register + const d = new ReactSECDPrinter(interpreterState.dump, this.hasMouseOver, this.parentHasMouseOver, state.current).print() + //set printedState of all arrays in registers to None + interpreterState.code.clearPrinted() + interpreterState.stack.clearPrinted() + interpreterState.environment.clearPrinted() + interpreterState.dump.clearPrinted() + return ( +
+ LISP: +

+ { staticLisp } +

+

+ code: { c } +

+

+ stack: { s } +

+

+ environment: { e } +

+

+ dump: { d } +

+

+ + + +
+ ) + case TinyLispType.PARSER_ERROR: + return ( +
+ void 0 } // fn + onCtrlEnter={ this.onDebug } + onShiftEnter={ () => void 0 } + onExecute={ () => void 0 } // fn + /> + +

+

+ {this.props.state.errorMsg} +
+ ) + } + } + return renderBoxContent() + } + + onDebug(): void{ + try { + //Parse the source code and start an interpreter with parse code + let parser = new Parser() + let arr = parser.parse(this.props.state.editor.content) + let interpreterState = new InterpreterState(arr, parser.topNode as TopNode) + this.props.setBoxState({...this.props.state, subtype: TinyLispType.ORDINARY, interpreterState,}) + } + catch (exception) { + //If there is an error, render the error msg + let msg = "Error while parsing: " + if(exception instanceof ParserError || exception instanceof SyntaxError || exception instanceof LexerError){ + msg += exception.value + } + this.props.setBoxState({ + ...this.props.state, + errorMsg: msg, + subtype: TinyLispType.PARSER_ERROR, + editor : { + ...this.props.state.editor, + syntaxError : Error("Error when parsing"), + } + }) + } + } + + onContent(str: string): void{ + this.props.setBoxState({...this.props.state, editor: {...this.props.state.editor, content: str}}) + } + + onStep() : void { + const { state, setBoxState } : Props = this.props + const { interpreterState } : TinyLispState = state + if(interpreterState == null){ + throw Error + } + try { + let interpreter = new Interpreter(interpreterState) + interpreter.step()//Perform step of the interpreter + let painter = new Painter(interpreterState) + painter.colourArray(interpreter.lastInstruction)//Colour registers + let current = interpreter.lastInstruction.shortcut//Update last instruction + setBoxState({...state, interpreterState, current}) + } + catch (error){ + if(error instanceof InterpreterError){//If error caught, update the error message + setBoxState({...state, subtype: TinyLispType.PARSER_ERROR, errorMsg: error.value}) + } + } + } + + onRun() : void { + const { state, setBoxState } : Props = this.props + const { interpreterState } : TinyLispState = state + if(interpreterState == null){ + throw Error + } + try { + let interpreter = new Interpreter(interpreterState) + interpreter.run() + new Painter(interpreterState).clean() + setBoxState({...state, interpreterState}) + } + catch (error){ + if(error instanceof InterpreterError){ + setBoxState({...state, subtype: TinyLispType.PARSER_ERROR, errorMsg: error.value}) + } + } + } + + /** + * + * @param node + */ + + onMouseOver(node: InnerNode): void{ + if(this.props.state.cleanNeeded) + if(this.props.state.mouseOver) + this.props.state.mouseOver.colour = ColourType.None + let tmp = node + let i = 0 + let colour = node.colour + while (tmp.hasParent()){//Find predecessor ReduceNode so reduced values can be coloured + let tmp2 = tmp._parent + if(tmp2 instanceof TopNode || tmp2 instanceof MainNode) + break + tmp2 = tmp._parent as InnerNode + if(tmp2.colour !== ColourType.None) + break + if(tmp2 instanceof ReduceNode) + node = tmp2 + tmp = tmp2 as InnerNode + if(i ++ > 10) + break + } + let cleanNeaded = false + if(node.colour === ColourType.None) {//If predecessor node is chosen, it should have the same colour + node.colour = colour + cleanNeaded = true + } + this.props.setBoxState({...this.props.state, mouseOver: node, cleanNeeded: cleanNeaded}) + } + + /** + * Set the mouseOver to null, when leaving a node + */ + + onMouseLeft(): void{ + if(this.props.state.cleanNeeded) + if(this.props.state.mouseOver) + this.props.state.mouseOver.colour = ColourType.None + this.props.setBoxState({...this.props.state, mouseOver: null, cleanNeeded: false}) + } + + /** + * True if mouseOver is not null + */ + + hasMouseOver(): boolean{ + return this.props.state.mouseOver != null + } + + /** + * Checks if parent of the node is the mouseOver + * @param node + * @param returnTrueIfColoured If the mouseOver node is coloured, return true + */ + + parentHasMouseOver(node: InnerNode, returnTrueIfColoured: boolean): boolean { + //If mouseOver not defined + if(typeof(this.props.state.mouseOver) == "undefined" || !this.props.state.mouseOver) + return false + // colour not None and returnTrueIfColoured false + if(!returnTrueIfColoured && this.props.state.mouseOver.colour !== ColourType.None) + return false + return this.parentHasMouseOverCont(node) + } + + /** + * Checks if parent of the node is the mouseOver + * @param node + * @private + */ + + private parentHasMouseOverCont(node: InnerNode): boolean { + //If node is call of the function in mouseOver + if (this.props.state.mouseOver instanceof DefineNode && node instanceof VarNode){ + if(this.props.state.mouseOver.name === node.variable){ + return true + } + } + let mouseOverNode = this.props.state.mouseOver + if(node === mouseOverNode) {//If node is the mouseOver node + return true + } + if(node instanceof ReduceNode){//If original is the mouseOver node + if(node.original() === mouseOverNode){ + return true + } + } + let parent = node._parent + if(!(parent instanceof InnerNode)) {//If on top + return false + } + return this.parentHasMouseOverCont(parent)//Call on parent + } +} \ No newline at end of file diff --git a/src/tiny-lisp-integration/Types.ts b/src/tiny-lisp-integration/Types.ts new file mode 100644 index 0000000..2e332d8 --- /dev/null +++ b/src/tiny-lisp-integration/Types.ts @@ -0,0 +1,35 @@ + +import { AbstractBoxState, AbstractSettings, BoxType } from "../Types" +import {InnerNode, InstructionShortcut, InterpreterState} from "@lambdulus/tiny-lisp-core"; + + +export interface TinyLispState extends AbstractBoxState { + __key : string + type : BoxType + + expression : string // + mouseOver: InnerNode | null // node where the mouse currently hovers over + cleanNeeded: boolean + interpreterState : InterpreterState | null // interpreter + errorMsg: string | null + subtype: TinyLispType //Type of the box + + editor : { + placeholder : string + content : string + caretPosition : number + syntaxError : Error | null // Value of an error + } + current: InstructionShortcut // current instruction shortcut +} + + +export interface TinyLispSettings extends AbstractSettings { + +} + +export enum TinyLispType { + EMPTY = 'EMPTY', + ORDINARY = 'ORDINARY', + PARSER_ERROR = 'PARSER_ERROR' +} \ No newline at end of file diff --git a/src/tiny-lisp-integration/styles/Step.css b/src/tiny-lisp-integration/styles/Step.css new file mode 100644 index 0000000..8a8c59c --- /dev/null +++ b/src/tiny-lisp-integration/styles/Step.css @@ -0,0 +1,88 @@ + + +.underlineFirstArgument { + border-bottom: 2px solid #81c784; +} + +.highlightFirstArgument { + + + border-radius: 2px; + background-color: #81c784; +} + +.underlineSecondArgument { + border-bottom: 2px solid #818ac7; +} + +.highlightSecondArgument { + + + border-radius: 2px; + background-color: #818ac7; +} + +.underlineThirdArgument { + border-bottom: 2px solid #c7c181; +} + +.highlightThirdArgument { + + + border-radius: 2px; + background-color: #c7c181; +} + +.underlineCurrent { + border-bottom: 2px solid #d55374; +} + +.highlightCurrent { + + + border-radius: 2px; + background-color: #d55374; +} + +.underlineOther { + border-bottom: 2px solid #53d5b7; +} + +.highlightOther { + + + border-radius: 2px; + background-color: #53d5d5; +} + +.normalInstruction { + +} + +ul#horizontal-list {/* + min-width: 696px; + list-style: none; + padding-top: 20px; + display: inline;*/ +} + +ul#horizontal-list li { + display: inline; +} + + +tab { + display:inline-block; + margin-left: 20px; +} + + +.toRight { + display: flex; +} + + +/* + + +border-radius: 2px;*/ \ No newline at end of file diff --git a/src/untyped-lambda-integration/Step.tsx b/src/untyped-lambda-integration/Step.tsx index af796d5..6bf909e 100644 --- a/src/untyped-lambda-integration/Step.tsx +++ b/src/untyped-lambda-integration/Step.tsx @@ -74,14 +74,14 @@ function Step (props : StepProperties) : JSX.Element | null { // this means -- next reduction is gonna be normal stuff (Beta, Alpha, Expansion) // because of some decision to structure the findSimplifiedReduction the way it works // mainly := first clone the tree and then mutate it with each recursive call - // if it's the normal stuff --> then the tree I used to identify the redex is not the same tree as I am giving to the ReactPrinter + // if it's the normal stuff --> then the tree I used to identify the redex is not the same tree as I am giving to the ReactSECDPrinter // for this reason I have to use redex finder which does not mutate the tree under my hands at least until I rewrite // the findSimplifiedReduction const evaluator : Evaluator = new (strategyToEvaluator(strategy) as any)(tree) nextReduction = evaluator.nextReduction // TODO: read carefully // this definitely needs to be fixed - // I will most certainly need to do some dirty magic in ReactPrinter - because this design also makes it impossible + // I will most certainly need to do some dirty magic in ReactSECDPrinter - because this design also makes it impossible // to decide what is current redex in expressions like: // (λ x . + x x) ( + 1 2 ) // + [( + 1 2 )] [( + 1 2 )] diff --git a/tsconfig.json b/tsconfig.json index e18c413..a779784 100644 --- a/tsconfig.json +++ b/tsconfig.json @@ -23,4 +23,4 @@ "include": [ "src" ] -} +} \ No newline at end of file