diff --git a/src/lang/executor.test.ts b/src/lang/executor.test.ts index 9d1e84a15..3d00c4403 100644 --- a/src/lang/executor.test.ts +++ b/src/lang/executor.test.ts @@ -2,40 +2,28 @@ import fs from "node:fs"; import { abstractSyntaxTree } from "./abstractSyntaxTree"; import { lexer } from "./tokeniser"; -import { executor } from "./executor"; +import { executor, ProgramMemory } from "./executor"; describe("test", () => { it("test assigning two variables, the second summing with the first", () => { const code = `const myVar = 5 const newVar = myVar + 1`; - const programMemory = exe(code); - expect(withoutStdFns(programMemory)).toEqual({ - root: { - myVar: 5, - newVar: 6, - }, - }); + const { root } = exe(code); + expect(root.myVar).toBe(5); + expect(root.newVar).toBe(6); }); it("test assigning a var with a string", () => { const code = `const myVar = "a str"`; - const programMemory = exe(code); - expect(withoutStdFns(programMemory)).toEqual({ - root: { - myVar: "a str", - }, - }); + const { root } = exe(code); + expect(root.myVar).toBe("a str"); }); it("test assigning a var by cont concatenating two strings string", () => { const code = fs.readFileSync( "./src/lang/testExamples/variableDeclaration.cado", "utf-8" ); - const programMemory = exe(code); - expect(withoutStdFns(programMemory)).toEqual({ - root: { - myVar: "a str another str", - }, - }); + const { root } = exe(code); + expect(root.myVar).toBe("a str another str"); }); it("test with function call", () => { const code = ` @@ -44,16 +32,15 @@ log(5, myVar)`; const programMemoryOverride = { log: jest.fn(), }; - const programMemory = executor(abstractSyntaxTree(lexer(code)), { + const { root } = executor(abstractSyntaxTree(lexer(code)), { root: programMemoryOverride, + _sketch: [], }); - expect(withoutStdFns(programMemory)).toEqual({ - root: { myVar: "hello" }, - }); + expect(root.myVar).toBe("hello"); expect(programMemoryOverride.log).toHaveBeenCalledWith(5, "hello"); }); it("fn funcN = () => {}", () => { - const programMemory = exe( + const { root } = exe( [ "fn funcN = (a, b) => {", " return a + b", @@ -62,9 +49,28 @@ log(5, myVar)`; "const magicNum = funcN(9, theVar)", ].join("\n") ); - expect(withoutStdFns(programMemory, ["funcN"])).toEqual({ - root: { theVar: 60, magicNum: 69 }, - }); + expect(root.theVar).toBe(60); + expect(root.magicNum).toBe(69); + }); + it("sketch declaration", () => { + let code = `sketch mySketch { + path myPath = lineTo(0,1) + lineTo(1,1) + path rightPath = lineTo(1,0) + close() +} +`; + const { root } = exe(code); + expect(root.mySketch.map(({ previousPath, ...rest }: any) => rest)).toEqual( + [ + { type: "base", from: [0, 0] }, + { type: "toPoint", to: [0, 1], name: "myPath" }, + { type: "toPoint", to: [1, 1] }, + { type: "toPoint", to: [1, 0], name: "rightPath" }, + { type: "close", firstPath: { type: "base", from: [0, 0] } }, + ] + ); + expect(root.mySketch[0]).toEqual(root.mySketch[4].firstPath); }); }); @@ -72,19 +78,9 @@ log(5, myVar)`; function exe( code: string, - programMemory: { root: { [key: string]: any }; return?: any } = { root: {} } + programMemory: ProgramMemory = { root: {}, _sketch: [] } ) { const tokens = lexer(code); const ast = abstractSyntaxTree(tokens); return executor(ast, programMemory); } - -function withoutStdFns(obj: any, toDelete: string[] = []) { - const newRoot = { ...obj.root }; - const newObj = { ...obj, root: newRoot }; - delete newObj.root.log; - toDelete.forEach((key) => { - delete newObj.root[key]; - }); - return newObj; -} diff --git a/src/lang/executor.ts b/src/lang/executor.ts index 124127f43..657ed7c75 100644 --- a/src/lang/executor.ts +++ b/src/lang/executor.ts @@ -1,18 +1,22 @@ import { Program, BinaryPart, BinaryExpression } from "./abstractSyntaxTree"; +import {Path, sketchFns } from "./sketch"; -interface ProgramMemory { +export interface ProgramMemory { root: { [key: string]: any }; return?: any; + _sketch: Path[]; } export const executor = ( node: Program, - programMemory: ProgramMemory = { root: {} } + programMemory: ProgramMemory = { root: {}, _sketch: [] }, + options: { bodyType: "default" | "sketch" } = { bodyType: "default" } ): any => { const _programMemory: ProgramMemory = { root: { ...programMemory.root, }, + _sketch: [], return: programMemory.return, }; const { body } = node; @@ -23,7 +27,22 @@ export const executor = ( if (declaration.init.type === "Literal") { _programMemory.root[variableName] = declaration.init.value; } else if (declaration.init.type === "BinaryExpression") { - _programMemory.root[variableName] = getBinaryExpressionResult(declaration.init, _programMemory); + _programMemory.root[variableName] = getBinaryExpressionResult( + declaration.init, + _programMemory + ); + } else if (declaration.init.type === "SketchExpression") { + const sketchInit = declaration.init; + const fnMemory: ProgramMemory = { + root: { + ..._programMemory.root, + }, + _sketch: [], + }; + const { _sketch } = executor(sketchInit.body, fnMemory, { + bodyType: "sketch", + }); + _programMemory.root[variableName] = _sketch; } else if (declaration.init.type === "FunctionExpression") { const fnInit = declaration.init; @@ -32,6 +51,7 @@ export const executor = ( root: { ..._programMemory.root, }, + _sketch: [], }; if (args.length > fnInit.params.length) { throw new Error( @@ -56,9 +76,24 @@ export const executor = ( return _programMemory.root[arg.name]; } }); - _programMemory.root[variableName] = _programMemory.root[fnName]( - ...fnArgs - ); + if ("lineTo" === fnName || "close" === fnName) { + if (options.bodyType !== "sketch") { + throw new Error( + `Cannot call ${fnName} outside of a sketch declaration` + ); + } + const result = sketchFns[fnName]( + _programMemory, + variableName, + ...fnArgs + ); + _programMemory._sketch = result.programMemory._sketch; + _programMemory.root[variableName] = result.currentPath; + } else { + _programMemory.root[variableName] = _programMemory.root[fnName]( + ...fnArgs + ); + } } }); } else if (statement.type === "ExpressionStatement") { @@ -72,12 +107,24 @@ export const executor = ( return _programMemory.root[arg.name]; } }); - _programMemory.root[functionName](...args); + if ("lineTo" === functionName || "close" === functionName) { + if (options.bodyType !== "sketch") { + throw new Error( + `Cannot call ${functionName} outside of a sketch declaration` + ); + } + const result = sketchFns[functionName](_programMemory, "", ...args); + _programMemory._sketch = [...result.programMemory._sketch]; + } else { + _programMemory.root[functionName](...args); + } } } else if (statement.type === "ReturnStatement") { - if(statement.argument.type === "BinaryExpression") { - const returnValue = getBinaryExpressionResult(statement.argument, _programMemory); - _programMemory.return = returnValue; + if (statement.argument.type === "BinaryExpression") { + _programMemory.return = getBinaryExpressionResult( + statement.argument, + _programMemory + ); } } }); diff --git a/src/lang/sketch.ts b/src/lang/sketch.ts new file mode 100644 index 000000000..8b816dab6 --- /dev/null +++ b/src/lang/sketch.ts @@ -0,0 +1,111 @@ +import { ProgramMemory } from "./executor"; + +export type Path = + | { + type: "points"; + name?: string; + from: [number, number]; + to: [number, number]; + } + | { + type: "horizontalLineTo"; + name?: string; + x: number; + previousPath: Path; + } + | { + type: "verticalLineTo"; + name?: string; + y: number; + previousPath: Path; + } + | { + type: "toPoint"; + name?: string; + to: [number, number]; + previousPath: Path; + } + | { + type: "close"; + name?: string; + firstPath: Path; + previousPath: Path; + } + | { + type: "base"; + from: [number, number]; + }; + +function addBasePath(programMemory: ProgramMemory) { + const base: Path = { + type: "base", + from: [0, 0], + }; + if (programMemory._sketch?.length === 0) { + return { + ...programMemory, + _sketch: [base], + }; + } + return programMemory; +} + +interface PathReturn { + programMemory: ProgramMemory; + currentPath: Path; +} + +export const sketchFns = { + close: (programMemory: ProgramMemory, name: string = ""): PathReturn => { + const lastPath = programMemory?._sketch?.[ + programMemory?._sketch.length - 1 + ] as Path; + const firstPath = programMemory?._sketch?.[0] as Path; + if (lastPath?.type === "base") { + throw new Error("Cannot close a base path"); + } + const newPath: Path = { + type: "close", + firstPath, + previousPath: lastPath, + }; + if (name) { + newPath.name = name; + } + return { + programMemory: { + ...programMemory, + _sketch: [...(programMemory?._sketch || []), newPath], + }, + currentPath: newPath, + }; + }, + lineTo: ( + programMemory: ProgramMemory, + name: string = "", + ...args: any[] + ): PathReturn => { + const _programMemory = addBasePath(programMemory); + const [x, y] = args; + if (!_programMemory._sketch) { + throw new Error("No sketch to draw on"); + } + const lastPath: Path = + _programMemory._sketch[_programMemory._sketch.length - 1]; + const currentPath: Path = { + type: "toPoint", + to: [x, y], + previousPath: lastPath, + }; + if (name) { + currentPath.name = name; + } + return { + programMemory: { + ..._programMemory, + _sketch: [...(_programMemory._sketch || []), currentPath], + }, + currentPath, + }; + }, +};