Add lexical scope and redefining variables in functions (#3015)
* Fix to allow variable shadowing inside functions * Implement closures * Fix KCL test code to not reference future tag definition * Remove tag declarator from function parameters This is an example where the scoping change revealed a subtle issue with TagDeclarators. You cannot bind a new tag using a function parameter. The issue is that evaluating a TagDeclarator like $foo binds an identifier to its corresponding TagIdentifier, but returns the TagDeclarator. If you have a TagDeclarator passed in as a parameter to a function, you can never get its corresponding TagIdentifier. This seems like a case where TagDeclarator evaluation needs to be revisited, especially now that we have scoped tags. * Fix to query return, functions, and tag declarator AST nodes correctly
This commit is contained in:
@ -12,25 +12,25 @@ describe('test executor', () => {
|
||||
it('test assigning two variables, the second summing with the first', async () => {
|
||||
const code = `const myVar = 5
|
||||
const newVar = myVar + 1`
|
||||
const { root } = await exe(code)
|
||||
expect(root.myVar.value).toBe(5)
|
||||
expect(root.newVar.value).toBe(6)
|
||||
const mem = await exe(code)
|
||||
expect(mem.get('myVar')?.value).toBe(5)
|
||||
expect(mem.get('newVar')?.value).toBe(6)
|
||||
})
|
||||
it('test assigning a var with a string', async () => {
|
||||
const code = `const myVar = "a str"`
|
||||
const { root } = await exe(code)
|
||||
expect(root.myVar.value).toBe('a str')
|
||||
const mem = await exe(code)
|
||||
expect(mem.get('myVar')?.value).toBe('a str')
|
||||
})
|
||||
it('test assigning a var by cont concatenating two strings string execute', async () => {
|
||||
const code = fs.readFileSync(
|
||||
'./src/lang/testExamples/variableDeclaration.cado',
|
||||
'utf-8'
|
||||
)
|
||||
const { root } = await exe(code)
|
||||
expect(root.myVar.value).toBe('a str another str')
|
||||
const mem = await exe(code)
|
||||
expect(mem.get('myVar')?.value).toBe('a str another str')
|
||||
})
|
||||
it('fn funcN = () => {} execute', async () => {
|
||||
const { root } = await exe(
|
||||
const mem = await exe(
|
||||
[
|
||||
'fn funcN = (a, b) => {',
|
||||
' return a + b',
|
||||
@ -39,8 +39,8 @@ const newVar = myVar + 1`
|
||||
'const magicNum = funcN(9, theVar)',
|
||||
].join('\n')
|
||||
)
|
||||
expect(root.theVar.value).toBe(60)
|
||||
expect(root.magicNum.value).toBe(69)
|
||||
expect(mem.get('theVar')?.value).toBe(60)
|
||||
expect(mem.get('magicNum')?.value).toBe(69)
|
||||
})
|
||||
it('sketch declaration', async () => {
|
||||
let code = `const mySketch = startSketchOn('XY')
|
||||
@ -50,9 +50,9 @@ const newVar = myVar + 1`
|
||||
|> lineTo([5,-1], %, "rightPath")
|
||||
// |> close(%)
|
||||
`
|
||||
const { root } = await exe(code)
|
||||
const mem = await exe(code)
|
||||
// geo is three js buffer geometry and is very bloated to have in tests
|
||||
const minusGeo = root.mySketch.value
|
||||
const minusGeo = mem.get('mySketch')?.value
|
||||
expect(minusGeo).toEqual([
|
||||
{
|
||||
type: 'ToPoint',
|
||||
@ -104,8 +104,8 @@ const newVar = myVar + 1`
|
||||
'fn myFn = (a) => { return a + 1 }',
|
||||
'const myVar = 5 + 1 |> myFn(%)',
|
||||
].join('\n')
|
||||
const { root } = await exe(code)
|
||||
expect(root.myVar.value).toBe(7)
|
||||
const mem = await exe(code)
|
||||
expect(mem.get('myVar')?.value).toBe(7)
|
||||
})
|
||||
|
||||
// Enable rotations #152
|
||||
@ -117,16 +117,16 @@ const newVar = myVar + 1`
|
||||
// ' |> lineTo([1, 1], %)',
|
||||
// 'const rotated = rx(90, mySk1)',
|
||||
// ].join('\n')
|
||||
// const { root } = await exe(code)
|
||||
// expect(root.mySk1.value).toHaveLength(3)
|
||||
// expect(root?.rotated?.type).toBe('SketchGroup')
|
||||
// const mem = await exe(code)
|
||||
// expect(mem.get('mySk1')?.value).toHaveLength(3)
|
||||
// expect(mem.get('rotated')?.type).toBe('SketchGroup')
|
||||
// if (
|
||||
// root?.mySk1?.type !== 'SketchGroup' ||
|
||||
// root?.rotated?.type !== 'SketchGroup'
|
||||
// mem.get('mySk1')?.type !== 'SketchGroup' ||
|
||||
// mem.get('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([
|
||||
// expect(mem.get('mySk1')?.rotation).toEqual([0, 0, 0, 1])
|
||||
// expect(mem.get('rotated')?.rotation.map((a) => a.toFixed(4))).toEqual([
|
||||
// '0.7071',
|
||||
// '0.0000',
|
||||
// '0.0000',
|
||||
@ -144,8 +144,8 @@ const newVar = myVar + 1`
|
||||
' |> lineTo([1,1], %)',
|
||||
// ' |> rx(90, %)',
|
||||
].join('\n')
|
||||
const { root } = await exe(code)
|
||||
expect(root.mySk1).toEqual({
|
||||
const mem = await exe(code)
|
||||
expect(mem.get('mySk1')).toEqual({
|
||||
type: 'SketchGroup',
|
||||
on: expect.any(Object),
|
||||
start: {
|
||||
@ -214,36 +214,37 @@ const newVar = myVar + 1`
|
||||
const code = ['const three = 3', "const yo = [1, '2', three, 4 + 5]"].join(
|
||||
'\n'
|
||||
)
|
||||
const { root } = await exe(code)
|
||||
const mem = await exe(code)
|
||||
// TODO path to node is probably wrong here, zero indexes are not correct
|
||||
expect(root).toEqual({
|
||||
three: {
|
||||
type: 'UserVal',
|
||||
value: 3,
|
||||
__meta: [
|
||||
{
|
||||
sourceRange: [14, 15],
|
||||
},
|
||||
],
|
||||
},
|
||||
yo: {
|
||||
type: 'UserVal',
|
||||
value: [1, '2', 3, 9],
|
||||
__meta: [
|
||||
{
|
||||
sourceRange: [27, 49],
|
||||
},
|
||||
],
|
||||
},
|
||||
expect(mem.get('three')).toEqual({
|
||||
type: 'UserVal',
|
||||
value: 3,
|
||||
__meta: [
|
||||
{
|
||||
sourceRange: [14, 15],
|
||||
},
|
||||
],
|
||||
})
|
||||
expect(mem.get('yo')).toEqual({
|
||||
type: 'UserVal',
|
||||
value: [1, '2', 3, 9],
|
||||
__meta: [
|
||||
{
|
||||
sourceRange: [27, 49],
|
||||
},
|
||||
],
|
||||
})
|
||||
// Check that there are no other variables or environments.
|
||||
expect(mem.numEnvironments()).toBe(1)
|
||||
expect(mem.numVariables(0)).toBe(2)
|
||||
})
|
||||
it('execute object expression', async () => {
|
||||
const code = [
|
||||
'const three = 3',
|
||||
"const yo = {aStr: 'str', anum: 2, identifier: three, binExp: 4 + 5}",
|
||||
].join('\n')
|
||||
const { root } = await exe(code)
|
||||
expect(root.yo).toEqual({
|
||||
const mem = await exe(code)
|
||||
expect(mem.get('yo')).toEqual({
|
||||
type: 'UserVal',
|
||||
value: { aStr: 'str', anum: 2, identifier: 3, binExp: 9 },
|
||||
__meta: [
|
||||
@ -257,8 +258,8 @@ const newVar = myVar + 1`
|
||||
const code = ["const yo = {a: {b: '123'}}", "const myVar = yo.a['b']"].join(
|
||||
'\n'
|
||||
)
|
||||
const { root } = await exe(code)
|
||||
expect(root.myVar).toEqual({
|
||||
const mem = await exe(code)
|
||||
expect(mem.get('myVar')).toEqual({
|
||||
type: 'UserVal',
|
||||
value: '123',
|
||||
__meta: [
|
||||
@ -273,81 +274,81 @@ const newVar = myVar + 1`
|
||||
describe('testing math operators', () => {
|
||||
it('can sum', async () => {
|
||||
const code = ['const myVar = 1 + 2'].join('\n')
|
||||
const { root } = await exe(code)
|
||||
expect(root.myVar.value).toBe(3)
|
||||
const mem = await exe(code)
|
||||
expect(mem.get('myVar')?.value).toBe(3)
|
||||
})
|
||||
it('can subtract', async () => {
|
||||
const code = ['const myVar = 1 - 2'].join('\n')
|
||||
const { root } = await exe(code)
|
||||
expect(root.myVar.value).toBe(-1)
|
||||
const mem = await exe(code)
|
||||
expect(mem.get('myVar')?.value).toBe(-1)
|
||||
})
|
||||
it('can multiply', async () => {
|
||||
const code = ['const myVar = 1 * 2'].join('\n')
|
||||
const { root } = await exe(code)
|
||||
expect(root.myVar.value).toBe(2)
|
||||
const mem = await exe(code)
|
||||
expect(mem.get('myVar')?.value).toBe(2)
|
||||
})
|
||||
it('can divide', async () => {
|
||||
const code = ['const myVar = 1 / 2'].join('\n')
|
||||
const { root } = await exe(code)
|
||||
expect(root.myVar.value).toBe(0.5)
|
||||
const mem = await exe(code)
|
||||
expect(mem.get('myVar')?.value).toBe(0.5)
|
||||
})
|
||||
it('can modulus', async () => {
|
||||
const code = ['const myVar = 5 % 2'].join('\n')
|
||||
const { root } = await exe(code)
|
||||
expect(root.myVar.value).toBe(1)
|
||||
const mem = await exe(code)
|
||||
expect(mem.get('myVar')?.value).toBe(1)
|
||||
})
|
||||
it('can do multiple operations', async () => {
|
||||
const code = ['const myVar = 1 + 2 * 3'].join('\n')
|
||||
const { root } = await exe(code)
|
||||
expect(root.myVar.value).toBe(7)
|
||||
const mem = await exe(code)
|
||||
expect(mem.get('myVar')?.value).toBe(7)
|
||||
})
|
||||
it('big example with parans', async () => {
|
||||
const code = ['const myVar = 1 + 2 * (3 - 4) / -5 + 6'].join('\n')
|
||||
const { root } = await exe(code)
|
||||
expect(root.myVar.value).toBe(7.4)
|
||||
const mem = await exe(code)
|
||||
expect(mem.get('myVar')?.value).toBe(7.4)
|
||||
})
|
||||
it('with identifier', async () => {
|
||||
const code = ['const yo = 6', 'const myVar = yo / 2'].join('\n')
|
||||
const { root } = await exe(code)
|
||||
expect(root.myVar.value).toBe(3)
|
||||
const mem = await exe(code)
|
||||
expect(mem.get('myVar')?.value).toBe(3)
|
||||
})
|
||||
it('with lots of testing', async () => {
|
||||
const code = ['const myVar = 2 * ((2 + 3 ) / 4 + 5)'].join('\n')
|
||||
const { root } = await exe(code)
|
||||
expect(root.myVar.value).toBe(12.5)
|
||||
const mem = await exe(code)
|
||||
expect(mem.get('myVar')?.value).toBe(12.5)
|
||||
})
|
||||
it('with callExpression at start', async () => {
|
||||
const code = 'const myVar = min(4, 100) + 2'
|
||||
const { root } = await exe(code)
|
||||
expect(root.myVar.value).toBe(6)
|
||||
const mem = await exe(code)
|
||||
expect(mem.get('myVar')?.value).toBe(6)
|
||||
})
|
||||
it('with callExpression at end', async () => {
|
||||
const code = 'const myVar = 2 + min(4, 100)'
|
||||
const { root } = await exe(code)
|
||||
expect(root.myVar.value).toBe(6)
|
||||
const mem = await exe(code)
|
||||
expect(mem.get('myVar')?.value).toBe(6)
|
||||
})
|
||||
it('with nested callExpression', async () => {
|
||||
const code = 'const myVar = 2 + min(100, legLen(5, 3))'
|
||||
const { root } = await exe(code)
|
||||
expect(root.myVar.value).toBe(6)
|
||||
const mem = await exe(code)
|
||||
expect(mem.get('myVar')?.value).toBe(6)
|
||||
})
|
||||
it('with unaryExpression', async () => {
|
||||
const code = 'const myVar = -min(100, 3)'
|
||||
const { root } = await exe(code)
|
||||
expect(root.myVar.value).toBe(-3)
|
||||
const mem = await exe(code)
|
||||
expect(mem.get('myVar')?.value).toBe(-3)
|
||||
})
|
||||
it('with unaryExpression in callExpression', async () => {
|
||||
const code = 'const myVar = min(-legLen(5, 4), 5)'
|
||||
const code2 = 'const myVar = min(5 , -legLen(5, 4))'
|
||||
const { root } = await exe(code)
|
||||
const { root: root2 } = await exe(code2)
|
||||
expect(root.myVar.value).toBe(-3)
|
||||
expect(root.myVar.value).toBe(root2.myVar.value)
|
||||
const mem = await exe(code)
|
||||
const mem2 = await exe(code2)
|
||||
expect(mem.get('myVar')?.value).toBe(-3)
|
||||
expect(mem.get('myVar')?.value).toBe(mem2.get('myVar')?.value)
|
||||
})
|
||||
it('with unaryExpression in ArrayExpression', async () => {
|
||||
const code = 'const myVar = [1,-legLen(5, 4)]'
|
||||
const { root } = await exe(code)
|
||||
expect(root.myVar.value).toEqual([1, -3])
|
||||
const mem = await exe(code)
|
||||
expect(mem.get('myVar')?.value).toEqual([1, -3])
|
||||
})
|
||||
it('with unaryExpression in ArrayExpression in CallExpression, checking nothing funny happens when used in a sketch', async () => {
|
||||
const code = [
|
||||
@ -355,8 +356,8 @@ describe('testing math operators', () => {
|
||||
' |> startProfileAt([0, 0], %)',
|
||||
'|> line([-2.21, -legLen(5, min(3, 999))], %)',
|
||||
].join('\n')
|
||||
const { root } = await exe(code)
|
||||
const sketch = root.part001
|
||||
const mem = await exe(code)
|
||||
const sketch = mem.get('part001')
|
||||
// result of `-legLen(5, min(3, 999))` should be -4
|
||||
const yVal = (sketch as SketchGroup).value?.[0]?.to?.[1]
|
||||
expect(yVal).toBe(-4)
|
||||
@ -373,8 +374,8 @@ describe('testing math operators', () => {
|
||||
`], %)`,
|
||||
``,
|
||||
].join('\n')
|
||||
const { root } = await exe(code)
|
||||
const sketch = root.part001
|
||||
const mem = await exe(code)
|
||||
const sketch = mem.get('part001')
|
||||
// expect -legLen(segLen('seg01', %), myVar) to equal -4 setting the y value back to 0
|
||||
expect((sketch as SketchGroup).value?.[1]?.from).toEqual([3, 4])
|
||||
expect((sketch as SketchGroup).value?.[1]?.to).toEqual([6, 0])
|
||||
@ -382,18 +383,18 @@ describe('testing math operators', () => {
|
||||
`-legLen(segLen('seg01', %), myVar)`,
|
||||
`legLen(segLen('seg01', %), myVar)`
|
||||
)
|
||||
const { root: removedUnaryExpRoot } = await exe(removedUnaryExp)
|
||||
const removedUnaryExpRootSketch = removedUnaryExpRoot.part001
|
||||
const removedUnaryExpMem = await exe(removedUnaryExp)
|
||||
const removedUnaryExpMemSketch = removedUnaryExpMem.get('part001')
|
||||
|
||||
// without the minus sign, the y value should be 8
|
||||
expect((removedUnaryExpRootSketch as SketchGroup).value?.[1]?.to).toEqual([
|
||||
expect((removedUnaryExpMemSketch as SketchGroup).value?.[1]?.to).toEqual([
|
||||
6, 8,
|
||||
])
|
||||
})
|
||||
it('with nested callExpression and binaryExpression', async () => {
|
||||
const code = 'const myVar = 2 + min(100, -1 + legLen(5, 3))'
|
||||
const { root } = await exe(code)
|
||||
expect(root.myVar.value).toBe(5)
|
||||
const mem = await exe(code)
|
||||
expect(mem.get('myVar')?.value).toBe(5)
|
||||
})
|
||||
})
|
||||
|
||||
@ -421,7 +422,7 @@ const theExtrude = startSketchOn('XY')
|
||||
|
||||
async function exe(
|
||||
code: string,
|
||||
programMemory: ProgramMemory = { root: {}, return: null }
|
||||
programMemory: ProgramMemory = ProgramMemory.empty()
|
||||
) {
|
||||
const ast = parse(code)
|
||||
|
||||
|
Reference in New Issue
Block a user