Files
modeling-app/src/lang/executor.test.ts

457 lines
13 KiB
TypeScript
Raw Normal View History

2022-11-26 08:34:23 +11:00
import fs from 'node:fs'
2022-11-14 14:04:23 +11:00
2022-11-26 08:34:23 +11:00
import { abstractSyntaxTree } from './abstractSyntaxTree'
import { lexer } from './tokeniser'
import { executor, ProgramMemory, Path, SketchGroup } from './executor'
import { initPromise } from './rust'
beforeAll(() => initPromise)
2022-11-14 14:04:23 +11:00
2022-11-26 08:34:23 +11:00
describe('test', () => {
it('test assigning two variables, the second summing with the first', () => {
2022-11-14 14:04:23 +11:00
const code = `const myVar = 5
2022-11-26 08:34:23 +11:00
const newVar = myVar + 1`
const { root } = exe(code)
expect(root.myVar.value).toBe(5)
expect(root.newVar.value).toBe(6)
2022-11-26 08:34:23 +11:00
})
it('test assigning a var with a string', () => {
const code = `const myVar = "a str"`
const { root } = exe(code)
expect(root.myVar.value).toBe('a str')
2022-11-26 08:34:23 +11:00
})
it('test assigning a var by cont concatenating two strings string execute', () => {
const code = fs.readFileSync(
2022-11-26 08:34:23 +11:00
'./src/lang/testExamples/variableDeclaration.cado',
'utf-8'
)
const { root } = exe(code)
expect(root.myVar.value).toBe('a str another str')
2022-11-26 08:34:23 +11:00
})
it('test with function call', () => {
2022-11-14 14:04:23 +11:00
const code = `
const myVar = "hello"
2022-11-26 08:34:23 +11:00
log(5, myVar)`
const programMemoryOverride: ProgramMemory['root'] = {
log: {
type: 'userVal',
value: jest.fn(),
__meta: [
{
sourceRange: [0, 0],
pathToNode: [],
},
],
},
2022-11-26 08:34:23 +11:00
}
const { root } = executor(abstractSyntaxTree(lexer(code)), {
root: programMemoryOverride,
_sketch: [],
2022-11-26 08:34:23 +11:00
})
expect(root.myVar.value).toBe('hello')
expect(programMemoryOverride.log.value).toHaveBeenCalledWith(5, 'hello')
2022-11-26 08:34:23 +11:00
})
it('fn funcN = () => {} execute', () => {
const { root } = exe(
[
2022-11-26 08:34:23 +11:00
'fn funcN = (a, b) => {',
' return a + b',
'}',
'const theVar = 60',
'const magicNum = funcN(9, theVar)',
].join('\n')
)
expect(root.theVar.value).toBe(60)
expect(root.magicNum.value).toBe(69)
2022-11-26 08:34:23 +11:00
})
it('sketch declaration', () => {
let code = `const mySketch = startSketchAt([0,0])
|> lineTo({to: [0,2], tag: "myPath"}, %)
|> lineTo([2,3], %)
|> lineTo({ to: [5,-1], tag: "rightPath" }, %)
// |> close(%)
2022-11-21 09:16:24 +11:00
show(mySketch)
2022-11-26 08:34:23 +11:00
`
const { root, return: _return } = exe(code)
// geo is three js buffer geometry and is very bloated to have in tests
const minusGeo = removeGeoFromPaths(root.mySketch.value)
expect(minusGeo).toEqual([
{
type: 'toPoint',
to: [0, 2],
from: [0, 0],
__geoMeta: {
sourceRange: [43, 80],
pathToNode: [],
geos: ['line', 'lineEnd'],
},
name: 'myPath',
},
{
type: 'toPoint',
to: [2, 3],
from: [0, 2],
__geoMeta: {
sourceRange: [86, 102],
pathToNode: [],
geos: ['line', 'lineEnd'],
},
},
{
type: 'toPoint',
to: [5, -1],
from: [2, 3],
__geoMeta: {
sourceRange: [108, 151],
pathToNode: [],
geos: ['line', 'lineEnd'],
},
name: 'rightPath',
},
2022-11-26 08:34:23 +11:00
])
2022-12-30 14:09:07 +11:00
// expect(root.mySketch.sketch[0]).toEqual(root.mySketch.sketch[4].firstPath)
2022-11-21 09:16:24 +11:00
expect(_return).toEqual([
{
2022-11-26 08:34:23 +11:00
type: 'Identifier',
start: 174,
end: 182,
2022-11-26 08:34:23 +11:00
name: 'mySketch',
2022-11-21 09:16:24 +11:00
},
2022-11-26 08:34:23 +11:00
])
})
it('pipe binary expression into call expression', () => {
const code = [
'fn myFn = (a) => { return a + 1 }',
'const myVar = 5 + 1 |> myFn(%)',
].join('\n')
const { root } = exe(code)
expect(root.myVar.value).toBe(7)
})
2022-12-04 08:16:04 +11:00
it('rotated sketch', () => {
const code = [
'const mySk1 = startSketchAt([0,0])',
' |> lineTo([1,1], %)',
' |> lineTo({to: [0, 1], tag: "myPath"}, %)',
' |> lineTo([1, 1], %)',
2022-12-04 08:16:04 +11:00
'const rotated = rx(90, mySk1)',
].join('\n')
const { root } = exe(code)
expect(root.mySk1.value).toHaveLength(3)
expect(root?.rotated?.type).toBe('sketchGroup')
if (
root?.mySk1?.type !== 'sketchGroup' ||
root?.rotated?.type !== 'sketchGroup'
)
throw new Error('not a sketch group')
expect(root.mySk1.rotation).toEqual([0, 0, 0, 1])
expect(root.rotated.rotation.map((a) => a.toFixed(4))).toEqual([
'0.7071',
'0.0000',
'0.0000',
'0.7071',
])
2022-12-04 08:16:04 +11:00
})
it('execute pipe sketch into call expression', () => {
const code = [
'const mySk1 = startSketchAt([0,0])',
' |> lineTo([1,1], %)',
' |> lineTo({to: [0, 1], tag: "myPath"}, %)',
' |> lineTo([1,1], %)',
' |> rx(90, %)',
2022-12-04 08:16:04 +11:00
].join('\n')
const { root } = exe(code)
const striptVersion = removeGeoFromSketch(root.mySk1 as SketchGroup)
2022-12-04 08:16:04 +11:00
expect(striptVersion).toEqual({
type: 'sketchGroup',
start: {
type: 'base',
to: [0, 0],
from: [0, 0],
__geoMeta: {
sourceRange: [14, 34],
pathToNode: [],
geos: ['sketchBase'],
},
},
value: [
2022-12-04 08:16:04 +11:00
{
type: 'toPoint',
to: [1, 1],
from: [0, 0],
__geoMeta: {
sourceRange: [40, 56],
pathToNode: [],
geos: ['line', 'lineEnd'],
},
2022-12-04 08:16:04 +11:00
},
{
type: 'toPoint',
to: [0, 1],
from: [1, 1],
__geoMeta: {
sourceRange: [62, 100],
pathToNode: [],
geos: ['line', 'lineEnd'],
},
2022-12-04 08:16:04 +11:00
name: 'myPath',
},
{
type: 'toPoint',
to: [1, 1],
from: [0, 1],
__geoMeta: {
sourceRange: [106, 122],
pathToNode: [],
geos: ['line', 'lineEnd'],
},
},
],
position: [0, 0, 0],
rotation: [0.7071067811865475, 0, 0, 0.7071067811865476],
__meta: [
{ sourceRange: [14, 34], pathToNode: [] },
{ sourceRange: [128, 137], pathToNode: [] },
2022-12-04 08:16:04 +11:00
],
})
})
2022-12-30 21:53:50 +11:00
it('execute array expression', () => {
const code = ['const three = 3', "const yo = [1, '2', three, 4 + 5]"].join(
'\n'
)
const { root } = exe(code)
// TODO path to node is probably wrong here, zero indexes are not correct
2022-12-30 21:53:50 +11:00
expect(root).toEqual({
three: {
type: 'userVal',
value: 3,
__meta: [
{
pathToNode: ['body', 0, 'declarations', 0, 'init'],
sourceRange: [14, 15],
},
],
},
yo: {
type: 'userVal',
value: [1, '2', 3, 9],
__meta: [
{
pathToNode: ['body', 1, 'declarations', 0, 'init'],
sourceRange: [27, 49],
},
{
pathToNode: ['body', 0, 'declarations', 0, 'init'],
sourceRange: [14, 15],
},
],
},
2022-12-30 21:53:50 +11:00
})
})
2023-01-01 21:48:30 +11:00
it('execute object expression', () => {
const code = [
'const three = 3',
"const yo = {aStr: 'str', anum: 2, identifier: three, binExp: 4 + 5}",
].join('\n')
const { root } = exe(code)
expect(root.yo).toEqual({
type: 'userVal',
value: {
2023-01-01 21:48:30 +11:00
aStr: 'str',
anum: 2,
identifier: 3,
binExp: 9,
},
__meta: [
{
pathToNode: ['body', 1, 'declarations', 0, 'init'],
sourceRange: [27, 83],
},
],
2023-01-01 21:48:30 +11:00
})
})
2023-01-03 19:41:27 +11:00
it('execute memberExpression', () => {
const code = ["const yo = {a: {b: '123'}}", "const myVar = yo.a['b']"].join(
'\n'
)
const { root } = exe(code)
expect(root.myVar).toEqual({
type: 'userVal',
value: '123',
__meta: [
{
pathToNode: ['body', 1, 'declarations', 0, 'init'],
sourceRange: [41, 50],
},
],
2023-01-03 19:41:27 +11:00
})
})
2022-11-26 08:34:23 +11:00
})
2022-11-14 14:04:23 +11:00
describe('testing math operators', () => {
it('it can sum', () => {
const code = ['const myVar = 1 + 2'].join('\n')
const { root } = exe(code)
expect(root.myVar.value).toBe(3)
})
it('it can subtract', () => {
const code = ['const myVar = 1 - 2'].join('\n')
const { root } = exe(code)
expect(root.myVar.value).toBe(-1)
})
it('it can multiply', () => {
const code = ['const myVar = 1 * 2'].join('\n')
const { root } = exe(code)
expect(root.myVar.value).toBe(2)
})
it('it can divide', () => {
const code = ['const myVar = 1 / 2'].join('\n')
const { root } = exe(code)
expect(root.myVar.value).toBe(0.5)
})
it('it can modulus', () => {
const code = ['const myVar = 5 % 2'].join('\n')
const { root } = exe(code)
expect(root.myVar.value).toBe(1)
})
it('it can do multiple operations', () => {
const code = ['const myVar = 1 + 2 * 3'].join('\n')
const { root } = exe(code)
expect(root.myVar.value).toBe(7)
})
it('big example with parans', () => {
const code = ['const myVar = 1 + 2 * (3 - 4) / -5 + 6'].join('\n')
const { root } = exe(code)
expect(root.myVar.value).toBe(7.4)
})
it('with identifier', () => {
const code = ['const yo = 6', 'const myVar = yo / 2'].join('\n')
const { root } = exe(code)
expect(root.myVar.value).toBe(3)
})
it('with identifier', () => {
const code = ['const myVar = 2 * ((2 + 3 ) / 4 + 5)'].join('\n')
const { root } = exe(code)
expect(root.myVar.value).toBe(12.5)
})
Add equal-length constraints & implement UnaryExpressions (#35) * add segLen help to lang std * adding helpers functions to sketchConstraints * update tokeniser tests because they were annoying me not being 100% * compare async lexer with sync lexer instead * add helper functions * remove unneeded nesting * update add ast modifier function for angledLine * initial equal ast modification It adds a tag to the primary line, and converts any secondary lines to angledLine, but doesn't reference the taged/primary line yet * Update fn call with refernce to previous line using segLen * add test for giveSketchFnCallTag * fix excutor bug, executing call expression in array expression * fix small issue in executor * add CallExpressions to BinaryExpressions * add unary Expressions * tweaks to unaryExpression logic * add recasting for unaryExpressions and CallExpressions in BinaryExpressions * ensure pipe substitution info is passed down to unary expressions and others * allow binary expressions in function argumentns * inital setup, new way of organising sketch fn transforms Starting with equal length * overhaul equalLength button * add equal length support for angledLine * line with one variable supports signed legLength * fix indentation when recasting long arrayExpressions in a pipeExpression * improve modifyAst consision * further modify ast tidy * equalLength transfroms far angledLineOfXLength * add transforms for line-yRelative * add equal constraint for angledLineOfYLength * quick test fix * add equal length constrain transforms for lineTo * add equal length constraints for angledLineToX * add equalLength constraints for angledLineToY * test tidy * setup new vertical-horizontal constraints * Add equal Length constraints for vertical/horizontal lines * migrate old tests, and refactor callback tag * tweaks and refactor horzVert component * fix leg len with small negative leg length
2023-03-02 21:19:11 +11:00
it('with callExpression at start', () => {
const code = 'const myVar = min(4, 100) + 2'
const { root } = exe(code)
expect(root.myVar.value).toBe(6)
})
it('with callExpression at end', () => {
const code = 'const myVar = 2 + min(4, 100)'
const { root } = exe(code)
expect(root.myVar.value).toBe(6)
})
it('with nested callExpression', () => {
const code = 'const myVar = 2 + min(100, legLen(5, 3))'
const { root } = exe(code)
expect(root.myVar.value).toBe(6)
})
it('with unaryExpression', () => {
const code = 'const myVar = -min(100, 3)'
const { root } = exe(code)
expect(root.myVar.value).toBe(-3)
})
it('with unaryExpression in callExpression', () => {
const code = 'const myVar = min(-legLen(5, 4), 5)'
const code2 = 'const myVar = min(5 , -legLen(5, 4))'
const { root } = exe(code)
const { root: root2 } = exe(code2)
expect(root.myVar.value).toBe(-3)
expect(root.myVar.value).toBe(root2.myVar.value)
})
it('with unaryExpression in ArrayExpression', () => {
const code = 'const myVar = [1,-legLen(5, 4)]'
const { root } = exe(code)
expect(root.myVar.value).toEqual([1, -3])
})
it('with unaryExpression in ArrayExpression in CallExpression, checking nothing funny happens when used in a sketch', () => {
const code = [
'const part001 = startSketchAt([0, 0])',
'|> line([-2.21, -legLen(5, min(3, 999))], %)',
].join('\n')
const { root } = exe(code)
const sketch = removeGeoFromSketch(root.part001 as SketchGroup)
// result of `-legLen(5, min(3, 999))` should be -4
const yVal = sketch.value?.[0]?.to?.[1]
expect(yVal).toBe(-4)
})
it('test that % substitution feeds down CallExp->ArrExp->UnaryExp->CallExp', () => {
const code = [
`const myVar = 3`,
`const part001 = startSketchAt([0, 0])`,
` |> line({ to: [3, 4], tag: 'seg01' }, %)`,
` |> line([`,
` min(segLen('seg01', %), myVar),`,
` -legLen(segLen('seg01', %), myVar)`,
`], %)`,
``,
`show(part001)`,
].join('\n')
const { root } = exe(code)
const sketch = removeGeoFromSketch(root.part001 as SketchGroup)
// expect -legLen(segLen('seg01', %), myVar) to equal -4 setting the y value back to 0
expect(sketch.value?.[1]?.from).toEqual([3, 4])
expect(sketch.value?.[1]?.to).toEqual([6, 0])
const removedUnaryExp = code.replace(
`-legLen(segLen('seg01', %), myVar)`,
`legLen(segLen('seg01', %), myVar)`
)
const { root: removedUnaryExpRoot } = exe(removedUnaryExp)
const removedUnaryExpRootSketch = removeGeoFromSketch(
removedUnaryExpRoot.part001 as SketchGroup
)
// without the minus sign, the y value should be 8
expect(removedUnaryExpRootSketch.value?.[1]?.to).toEqual([6, 8])
})
it('with nested callExpression and binaryExpression', () => {
const code = 'const myVar = 2 + min(100, -1 + legLen(5, 3))'
const { root } = exe(code)
expect(root.myVar.value).toBe(5)
})
})
2022-11-14 14:04:23 +11:00
// helpers
function exe(
code: string,
programMemory: ProgramMemory = { root: {}, _sketch: [] }
) {
2022-11-26 08:34:23 +11:00
const tokens = lexer(code)
const ast = abstractSyntaxTree(tokens)
return executor(ast, programMemory)
}
2022-12-04 08:16:04 +11:00
Add equal-length constraints & implement UnaryExpressions (#35) * add segLen help to lang std * adding helpers functions to sketchConstraints * update tokeniser tests because they were annoying me not being 100% * compare async lexer with sync lexer instead * add helper functions * remove unneeded nesting * update add ast modifier function for angledLine * initial equal ast modification It adds a tag to the primary line, and converts any secondary lines to angledLine, but doesn't reference the taged/primary line yet * Update fn call with refernce to previous line using segLen * add test for giveSketchFnCallTag * fix excutor bug, executing call expression in array expression * fix small issue in executor * add CallExpressions to BinaryExpressions * add unary Expressions * tweaks to unaryExpression logic * add recasting for unaryExpressions and CallExpressions in BinaryExpressions * ensure pipe substitution info is passed down to unary expressions and others * allow binary expressions in function argumentns * inital setup, new way of organising sketch fn transforms Starting with equal length * overhaul equalLength button * add equal length support for angledLine * line with one variable supports signed legLength * fix indentation when recasting long arrayExpressions in a pipeExpression * improve modifyAst consision * further modify ast tidy * equalLength transfroms far angledLineOfXLength * add transforms for line-yRelative * add equal constraint for angledLineOfYLength * quick test fix * add equal length constrain transforms for lineTo * add equal length constraints for angledLineToX * add equalLength constraints for angledLineToY * test tidy * setup new vertical-horizontal constraints * Add equal Length constraints for vertical/horizontal lines * migrate old tests, and refactor callback tag * tweaks and refactor horzVert component * fix leg len with small negative leg length
2023-03-02 21:19:11 +11:00
function removeGeoFromSketch(sketch: SketchGroup): SketchGroup {
return {
...sketch,
start: !sketch.start
? undefined
: {
...sketch.start,
__geoMeta: {
...sketch.start.__geoMeta,
geos: sketch.start.__geoMeta.geos.map((geo) => geo.type as any),
},
},
value: removeGeoFromPaths(sketch.value),
2022-12-04 08:16:04 +11:00
}
}
function removeGeoFromPaths(paths: Path[]): any[] {
return paths.map((path: Path) => {
const newGeos = path?.__geoMeta?.geos.map((geo) => geo.type)
2022-12-30 14:09:07 +11:00
return {
...path,
__geoMeta: {
...path.__geoMeta,
geos: newGeos,
},
2022-12-30 14:09:07 +11:00
}
})
2022-12-04 08:16:04 +11:00
}