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

1944 lines
53 KiB
TypeScript
Raw Normal View History

import {
abstractSyntaxTree,
findClosingBrace,
hasPipeOperator,
findEndOfBinaryExpression,
} from './abstractSyntaxTree'
2022-11-26 08:34:23 +11:00
import { lexer } from './tokeniser'
2022-11-13 11:14:30 +11:00
2022-11-26 08:34:23 +11:00
describe('findClosingBrace', () => {
test('finds the closing brace', () => {
const basic = '( hey )'
expect(findClosingBrace(lexer(basic), 0)).toBe(4)
2022-11-17 16:06:38 +11:00
const handlesNonZeroIndex =
2022-11-26 08:34:23 +11:00
'(indexForBracketToRightOfThisIsTwo(shouldBeFour)AndNotThisSix)'
expect(findClosingBrace(lexer(handlesNonZeroIndex), 2)).toBe(4)
expect(findClosingBrace(lexer(handlesNonZeroIndex), 0)).toBe(6)
const handlesNested =
2022-11-26 08:34:23 +11:00
'{a{b{c(}d]}eathou athoeu tah u} thatOneToTheLeftIsLast }'
expect(findClosingBrace(lexer(handlesNested), 0)).toBe(18)
2022-11-17 16:06:38 +11:00
// throws when not started on a brace
2022-11-26 08:34:23 +11:00
expect(() => findClosingBrace(lexer(handlesNested), 1)).toThrow()
})
})
2022-11-17 16:06:38 +11:00
2022-11-26 08:34:23 +11:00
describe('testing AST', () => {
test('test 5 + 6', () => {
const tokens = lexer('5 +6')
const result = abstractSyntaxTree(tokens)
2022-11-13 11:14:30 +11:00
expect(result).toEqual({
2022-11-26 08:34:23 +11:00
type: 'Program',
2022-11-13 11:14:30 +11:00
start: 0,
end: 4,
body: [
{
2022-11-26 08:34:23 +11:00
type: 'ExpressionStatement',
2022-11-13 11:14:30 +11:00
start: 0,
end: 4,
expression: {
2022-11-26 08:34:23 +11:00
type: 'BinaryExpression',
2022-11-13 11:14:30 +11:00
start: 0,
end: 4,
left: {
2022-11-26 08:34:23 +11:00
type: 'Literal',
2022-11-13 11:14:30 +11:00
start: 0,
end: 1,
value: 5,
2022-11-26 08:34:23 +11:00
raw: '5',
2022-11-13 11:14:30 +11:00
},
2022-11-26 08:34:23 +11:00
operator: '+',
2022-11-13 11:14:30 +11:00
right: {
2022-11-26 08:34:23 +11:00
type: 'Literal',
2022-11-13 11:14:30 +11:00
start: 3,
end: 4,
value: 6,
2022-11-26 08:34:23 +11:00
raw: '6',
2022-11-13 11:14:30 +11:00
},
},
},
],
2022-11-26 08:34:23 +11:00
})
})
test('test const myVar = 5', () => {
const tokens = lexer('const myVar = 5')
const { body } = abstractSyntaxTree(tokens)
2022-11-13 11:14:30 +11:00
expect(body).toEqual([
{
2022-11-26 08:34:23 +11:00
type: 'VariableDeclaration',
2022-11-13 11:14:30 +11:00
start: 0,
end: 15,
2022-11-26 08:34:23 +11:00
kind: 'const',
2022-11-13 11:14:30 +11:00
declarations: [
{
2022-11-26 08:34:23 +11:00
type: 'VariableDeclarator',
2022-11-13 11:14:30 +11:00
start: 6,
end: 15,
id: {
2022-11-26 08:34:23 +11:00
type: 'Identifier',
2022-11-13 11:14:30 +11:00
start: 6,
end: 11,
2022-11-26 08:34:23 +11:00
name: 'myVar',
2022-11-13 11:14:30 +11:00
},
init: {
2022-11-26 08:34:23 +11:00
type: 'Literal',
2022-11-13 11:14:30 +11:00
start: 14,
end: 15,
value: 5,
2022-11-26 08:34:23 +11:00
raw: '5',
2022-11-13 11:14:30 +11:00
},
},
],
},
2022-11-26 08:34:23 +11:00
])
})
test('test multi-line', () => {
2022-11-13 11:14:30 +11:00
const code = `const myVar = 5
const newVar = myVar + 1
2022-11-26 08:34:23 +11:00
`
const tokens = lexer(code)
const { body } = abstractSyntaxTree(tokens)
2022-11-13 11:14:30 +11:00
expect(body).toEqual([
{
2022-11-26 08:34:23 +11:00
type: 'VariableDeclaration',
2022-11-13 11:14:30 +11:00
start: 0,
end: 15,
2022-11-26 08:34:23 +11:00
kind: 'const',
2022-11-13 11:14:30 +11:00
declarations: [
{
2022-11-26 08:34:23 +11:00
type: 'VariableDeclarator',
2022-11-13 11:14:30 +11:00
start: 6,
end: 15,
id: {
2022-11-26 08:34:23 +11:00
type: 'Identifier',
2022-11-13 11:14:30 +11:00
start: 6,
end: 11,
2022-11-26 08:34:23 +11:00
name: 'myVar',
2022-11-13 11:14:30 +11:00
},
init: {
2022-11-26 08:34:23 +11:00
type: 'Literal',
2022-11-13 11:14:30 +11:00
start: 14,
end: 15,
value: 5,
2022-11-26 08:34:23 +11:00
raw: '5',
2022-11-13 11:14:30 +11:00
},
},
],
},
{
2022-11-26 08:34:23 +11:00
type: 'VariableDeclaration',
2022-11-13 11:14:30 +11:00
start: 16,
end: 40,
2022-11-26 08:34:23 +11:00
kind: 'const',
2022-11-13 11:14:30 +11:00
declarations: [
{
2022-11-26 08:34:23 +11:00
type: 'VariableDeclarator',
2022-11-13 11:14:30 +11:00
start: 22,
end: 40,
id: {
2022-11-26 08:34:23 +11:00
type: 'Identifier',
2022-11-13 11:14:30 +11:00
start: 22,
end: 28,
2022-11-26 08:34:23 +11:00
name: 'newVar',
2022-11-13 11:14:30 +11:00
},
init: {
2022-11-26 08:34:23 +11:00
type: 'BinaryExpression',
2022-11-13 11:14:30 +11:00
start: 31,
end: 40,
left: {
2022-11-26 08:34:23 +11:00
type: 'Identifier',
2022-11-13 11:14:30 +11:00
start: 31,
end: 36,
2022-11-26 08:34:23 +11:00
name: 'myVar',
2022-11-13 11:14:30 +11:00
},
2022-11-26 08:34:23 +11:00
operator: '+',
2022-11-13 11:14:30 +11:00
right: {
2022-11-26 08:34:23 +11:00
type: 'Literal',
2022-11-13 11:14:30 +11:00
start: 39,
end: 40,
value: 1,
2022-11-26 08:34:23 +11:00
raw: '1',
2022-11-13 11:14:30 +11:00
},
},
},
],
},
2022-11-26 08:34:23 +11:00
])
})
2022-11-14 13:28:16 +11:00
test('test using std function "log"', () => {
2022-11-26 08:34:23 +11:00
const code = `log(5, "hello", aIdentifier)`
const tokens = lexer(code)
const { body } = abstractSyntaxTree(tokens)
2022-11-14 13:28:16 +11:00
expect(body).toEqual([
{
2022-11-26 08:34:23 +11:00
type: 'ExpressionStatement',
start: 0,
end: 28,
expression: {
2022-11-26 08:34:23 +11:00
type: 'CallExpression',
start: 0,
end: 28,
callee: {
2022-11-26 08:34:23 +11:00
type: 'Identifier',
start: 0,
end: 3,
2022-11-26 08:34:23 +11:00
name: 'log',
2022-11-14 13:28:16 +11:00
},
arguments: [
2022-11-14 13:28:16 +11:00
{
2022-11-26 08:34:23 +11:00
type: 'Literal',
start: 4,
end: 5,
value: 5,
2022-11-26 08:34:23 +11:00
raw: '5',
2022-11-14 13:28:16 +11:00
},
{
2022-11-26 08:34:23 +11:00
type: 'Literal',
start: 7,
end: 14,
2022-11-26 08:34:23 +11:00
value: 'hello',
raw: '"hello"',
2022-11-14 13:28:16 +11:00
},
{
2022-11-26 08:34:23 +11:00
type: 'Identifier',
start: 16,
end: 27,
2022-11-26 08:34:23 +11:00
name: 'aIdentifier',
},
2022-11-14 13:28:16 +11:00
],
optional: false,
},
},
2022-11-26 08:34:23 +11:00
])
})
})
2022-11-17 20:17:00 +11:00
2022-11-26 08:34:23 +11:00
describe('testing function declaration', () => {
test('fn funcN = () => {}', () => {
const tokens = lexer('fn funcN = () => {}')
const { body } = abstractSyntaxTree(tokens)
2022-11-17 20:17:00 +11:00
expect(body).toEqual([
{
2022-11-26 08:34:23 +11:00
type: 'VariableDeclaration',
start: 0,
end: 19,
2022-11-26 08:34:23 +11:00
kind: 'fn',
declarations: [
2022-11-17 20:17:00 +11:00
{
2022-11-26 08:34:23 +11:00
type: 'VariableDeclarator',
start: 3,
end: 19,
id: {
2022-11-26 08:34:23 +11:00
type: 'Identifier',
start: 3,
end: 8,
2022-11-26 08:34:23 +11:00
name: 'funcN',
},
init: {
2022-11-26 08:34:23 +11:00
type: 'FunctionExpression',
start: 11,
end: 19,
id: null,
params: [],
body: {
2022-11-26 08:34:23 +11:00
type: 'BlockStatement',
start: 17,
end: 19,
body: [],
},
2022-11-17 20:17:00 +11:00
},
},
],
},
2022-11-26 08:34:23 +11:00
])
})
test('fn funcN = (a, b) => {return a + b}', () => {
const tokens = lexer(
2022-11-26 08:34:23 +11:00
['fn funcN = (a, b) => {', ' return a + b', '}'].join('\n')
)
const { body } = abstractSyntaxTree(tokens)
expect(body).toEqual([
{
2022-11-26 08:34:23 +11:00
type: 'VariableDeclaration',
start: 0,
end: 39,
2022-11-26 08:34:23 +11:00
kind: 'fn',
declarations: [
{
2022-11-26 08:34:23 +11:00
type: 'VariableDeclarator',
start: 3,
end: 39,
id: {
2022-11-26 08:34:23 +11:00
type: 'Identifier',
start: 3,
end: 8,
2022-11-26 08:34:23 +11:00
name: 'funcN',
},
init: {
2022-11-26 08:34:23 +11:00
type: 'FunctionExpression',
start: 11,
end: 39,
id: null,
params: [
{
2022-11-26 08:34:23 +11:00
type: 'Identifier',
start: 12,
end: 13,
2022-11-26 08:34:23 +11:00
name: 'a',
},
{
2022-11-26 08:34:23 +11:00
type: 'Identifier',
start: 15,
end: 16,
2022-11-26 08:34:23 +11:00
name: 'b',
},
],
body: {
2022-11-26 08:34:23 +11:00
type: 'BlockStatement',
start: 21,
end: 39,
body: [
{
2022-11-26 08:34:23 +11:00
type: 'ReturnStatement',
start: 25,
end: 37,
argument: {
2022-11-26 08:34:23 +11:00
type: 'BinaryExpression',
start: 32,
end: 37,
left: {
2022-11-26 08:34:23 +11:00
type: 'Identifier',
start: 32,
end: 33,
2022-11-26 08:34:23 +11:00
name: 'a',
},
2022-11-26 08:34:23 +11:00
operator: '+',
right: {
2022-11-26 08:34:23 +11:00
type: 'Identifier',
start: 36,
end: 37,
2022-11-26 08:34:23 +11:00
name: 'b',
},
},
},
],
},
},
},
],
},
2022-11-26 08:34:23 +11:00
])
})
test('call expression assignment', () => {
const tokens = lexer(
`fn funcN = (a, b) => { return a + b }
2022-11-20 17:43:21 +11:00
const myVar = funcN(1, 2)`
2022-11-26 08:34:23 +11:00
)
const { body } = abstractSyntaxTree(tokens)
expect(body).toEqual([
{
2022-11-26 08:34:23 +11:00
type: 'VariableDeclaration',
2022-11-20 17:43:21 +11:00
start: 0,
end: 37,
2022-11-26 08:34:23 +11:00
kind: 'fn',
2022-11-20 17:43:21 +11:00
declarations: [
{
2022-11-26 08:34:23 +11:00
type: 'VariableDeclarator',
2022-11-20 17:43:21 +11:00
start: 3,
end: 37,
id: {
2022-11-26 08:34:23 +11:00
type: 'Identifier',
2022-11-20 17:43:21 +11:00
start: 3,
end: 8,
2022-11-26 08:34:23 +11:00
name: 'funcN',
},
2022-11-20 17:43:21 +11:00
init: {
2022-11-26 08:34:23 +11:00
type: 'FunctionExpression',
2022-11-20 17:43:21 +11:00
start: 11,
end: 37,
id: null,
params: [
{
2022-11-26 08:34:23 +11:00
type: 'Identifier',
2022-11-20 17:43:21 +11:00
start: 12,
end: 13,
2022-11-26 08:34:23 +11:00
name: 'a',
},
{
2022-11-26 08:34:23 +11:00
type: 'Identifier',
2022-11-20 17:43:21 +11:00
start: 15,
end: 16,
2022-11-26 08:34:23 +11:00
name: 'b',
2022-11-20 17:43:21 +11:00
},
],
2022-11-20 17:43:21 +11:00
body: {
2022-11-26 08:34:23 +11:00
type: 'BlockStatement',
2022-11-20 17:43:21 +11:00
start: 21,
end: 37,
body: [
{
2022-11-26 08:34:23 +11:00
type: 'ReturnStatement',
2022-11-20 17:43:21 +11:00
start: 23,
end: 35,
argument: {
2022-11-26 08:34:23 +11:00
type: 'BinaryExpression',
2022-11-20 17:43:21 +11:00
start: 30,
end: 35,
left: {
2022-11-26 08:34:23 +11:00
type: 'Identifier',
2022-11-20 17:43:21 +11:00
start: 30,
end: 31,
2022-11-26 08:34:23 +11:00
name: 'a',
},
2022-11-26 08:34:23 +11:00
operator: '+',
2022-11-20 17:43:21 +11:00
right: {
2022-11-26 08:34:23 +11:00
type: 'Identifier',
2022-11-20 17:43:21 +11:00
start: 34,
end: 35,
2022-11-26 08:34:23 +11:00
name: 'b',
2022-11-20 17:43:21 +11:00
},
},
},
],
},
},
},
],
},
{
2022-11-26 08:34:23 +11:00
type: 'VariableDeclaration',
2022-11-20 17:43:21 +11:00
start: 38,
end: 63,
2022-11-26 08:34:23 +11:00
kind: 'const',
2022-11-20 17:43:21 +11:00
declarations: [
{
2022-11-26 08:34:23 +11:00
type: 'VariableDeclarator',
2022-11-20 17:43:21 +11:00
start: 44,
end: 63,
id: {
2022-11-26 08:34:23 +11:00
type: 'Identifier',
2022-11-20 17:43:21 +11:00
start: 44,
end: 49,
2022-11-26 08:34:23 +11:00
name: 'myVar',
},
2022-11-20 17:43:21 +11:00
init: {
2022-11-26 08:34:23 +11:00
type: 'CallExpression',
2022-11-20 17:43:21 +11:00
start: 52,
end: 63,
callee: {
2022-11-26 08:34:23 +11:00
type: 'Identifier',
2022-11-20 17:43:21 +11:00
start: 52,
end: 57,
2022-11-26 08:34:23 +11:00
name: 'funcN',
},
2022-11-20 17:43:21 +11:00
arguments: [
{
2022-11-26 08:34:23 +11:00
type: 'Literal',
2022-11-20 17:43:21 +11:00
start: 58,
end: 59,
value: 1,
2022-11-26 08:34:23 +11:00
raw: '1',
},
{
2022-11-26 08:34:23 +11:00
type: 'Literal',
2022-11-20 17:43:21 +11:00
start: 61,
end: 62,
value: 2,
2022-11-26 08:34:23 +11:00
raw: '2',
2022-11-20 17:43:21 +11:00
},
],
2022-11-20 17:43:21 +11:00
optional: false,
},
},
],
},
2022-11-26 08:34:23 +11:00
])
})
})
2022-11-20 17:43:21 +11:00
2022-11-26 08:34:23 +11:00
describe('structures specific to this lang', () => {
test('sketch', () => {
2022-11-20 17:43:21 +11:00
let code = `sketch mySketch {
path myPath = lineTo(0,1)
lineTo(1,1)
path rightPath = lineTo(1,0)
close()
}
2022-11-26 08:34:23 +11:00
`
const tokens = lexer(code)
const { body } = abstractSyntaxTree(tokens)
2022-11-20 17:43:21 +11:00
expect(body).toEqual([
{
2022-11-26 08:34:23 +11:00
type: 'VariableDeclaration',
2022-11-20 17:43:21 +11:00
start: 0,
end: 102,
2022-11-26 08:34:23 +11:00
kind: 'sketch',
2022-11-20 17:43:21 +11:00
declarations: [
{
2022-11-26 08:34:23 +11:00
type: 'VariableDeclarator',
2022-11-20 17:43:21 +11:00
start: 7,
end: 102,
id: {
2022-11-26 08:34:23 +11:00
type: 'Identifier',
2022-11-20 17:43:21 +11:00
start: 7,
end: 15,
2022-11-26 08:34:23 +11:00
name: 'mySketch',
2022-11-20 17:43:21 +11:00
},
init: {
2022-11-26 08:34:23 +11:00
type: 'SketchExpression',
2022-11-20 17:43:21 +11:00
start: 16,
end: 102,
body: {
2022-11-26 08:34:23 +11:00
type: 'BlockStatement',
2022-11-20 17:43:21 +11:00
start: 16,
end: 102,
body: [
{
2022-11-26 08:34:23 +11:00
type: 'VariableDeclaration',
2022-11-20 17:43:21 +11:00
start: 20,
end: 45,
2022-11-26 08:34:23 +11:00
kind: 'path',
2022-11-20 17:43:21 +11:00
declarations: [
{
2022-11-26 08:34:23 +11:00
type: 'VariableDeclarator',
2022-11-20 17:43:21 +11:00
start: 25,
end: 45,
id: {
2022-11-26 08:34:23 +11:00
type: 'Identifier',
2022-11-20 17:43:21 +11:00
start: 25,
end: 31,
2022-11-26 08:34:23 +11:00
name: 'myPath',
2022-11-20 17:43:21 +11:00
},
init: {
2022-11-26 08:34:23 +11:00
type: 'CallExpression',
2022-11-20 17:43:21 +11:00
start: 34,
end: 45,
callee: {
2022-11-26 08:34:23 +11:00
type: 'Identifier',
2022-11-20 17:43:21 +11:00
start: 34,
end: 40,
2022-11-26 08:34:23 +11:00
name: 'lineTo',
2022-11-20 17:43:21 +11:00
},
arguments: [
{
2022-11-26 08:34:23 +11:00
type: 'Literal',
2022-11-20 17:43:21 +11:00
start: 41,
end: 42,
value: 0,
2022-11-26 08:34:23 +11:00
raw: '0',
2022-11-20 17:43:21 +11:00
},
{
2022-11-26 08:34:23 +11:00
type: 'Literal',
2022-11-20 17:43:21 +11:00
start: 43,
end: 44,
value: 1,
2022-11-26 08:34:23 +11:00
raw: '1',
2022-11-20 17:43:21 +11:00
},
],
optional: false,
},
},
],
},
{
2022-11-26 08:34:23 +11:00
type: 'ExpressionStatement',
2022-11-20 17:43:21 +11:00
start: 48,
end: 59,
expression: {
2022-11-26 08:34:23 +11:00
type: 'CallExpression',
2022-11-20 17:43:21 +11:00
start: 48,
end: 59,
callee: {
2022-11-26 08:34:23 +11:00
type: 'Identifier',
2022-11-20 17:43:21 +11:00
start: 48,
end: 54,
2022-11-26 08:34:23 +11:00
name: 'lineTo',
2022-11-20 17:43:21 +11:00
},
arguments: [
{
2022-11-26 08:34:23 +11:00
type: 'Literal',
2022-11-20 17:43:21 +11:00
start: 55,
end: 56,
value: 1,
2022-11-26 08:34:23 +11:00
raw: '1',
2022-11-20 17:43:21 +11:00
},
{
2022-11-26 08:34:23 +11:00
type: 'Literal',
2022-11-20 17:43:21 +11:00
start: 57,
end: 58,
value: 1,
2022-11-26 08:34:23 +11:00
raw: '1',
2022-11-20 17:43:21 +11:00
},
],
optional: false,
},
},
{
2022-11-26 08:34:23 +11:00
type: 'VariableDeclaration',
2022-11-20 17:43:21 +11:00
start: 62,
end: 90,
2022-11-26 08:34:23 +11:00
kind: 'path',
2022-11-20 17:43:21 +11:00
declarations: [
{
2022-11-26 08:34:23 +11:00
type: 'VariableDeclarator',
2022-11-20 17:43:21 +11:00
start: 67,
end: 90,
id: {
2022-11-26 08:34:23 +11:00
type: 'Identifier',
2022-11-20 17:43:21 +11:00
start: 67,
end: 76,
2022-11-26 08:34:23 +11:00
name: 'rightPath',
2022-11-20 17:43:21 +11:00
},
init: {
2022-11-26 08:34:23 +11:00
type: 'CallExpression',
2022-11-20 17:43:21 +11:00
start: 79,
end: 90,
callee: {
2022-11-26 08:34:23 +11:00
type: 'Identifier',
2022-11-20 17:43:21 +11:00
start: 79,
end: 85,
2022-11-26 08:34:23 +11:00
name: 'lineTo',
2022-11-20 17:43:21 +11:00
},
arguments: [
{
2022-11-26 08:34:23 +11:00
type: 'Literal',
2022-11-20 17:43:21 +11:00
start: 86,
end: 87,
value: 1,
2022-11-26 08:34:23 +11:00
raw: '1',
2022-11-20 17:43:21 +11:00
},
{
2022-11-26 08:34:23 +11:00
type: 'Literal',
2022-11-20 17:43:21 +11:00
start: 88,
end: 89,
value: 0,
2022-11-26 08:34:23 +11:00
raw: '0',
2022-11-20 17:43:21 +11:00
},
],
optional: false,
},
},
],
},
{
2022-11-26 08:34:23 +11:00
type: 'ExpressionStatement',
2022-11-20 17:43:21 +11:00
start: 93,
end: 100,
expression: {
2022-11-26 08:34:23 +11:00
type: 'CallExpression',
2022-11-20 17:43:21 +11:00
start: 93,
end: 100,
callee: {
2022-11-26 08:34:23 +11:00
type: 'Identifier',
2022-11-20 17:43:21 +11:00
start: 93,
end: 98,
2022-11-26 08:34:23 +11:00
name: 'close',
2022-11-20 17:43:21 +11:00
},
arguments: [],
optional: false,
},
},
],
},
},
},
],
},
2022-11-26 08:34:23 +11:00
])
})
})
describe('testing hasPipeOperator', () => {
test('hasPipeOperator is true', () => {
let code = `sketch mySketch {
lineTo(2, 3)
} |> rx(45, %)
`
const tokens = lexer(code)
expect(hasPipeOperator(tokens, 0)).toEqual({
index: 16,
token: { end: 37, start: 35, type: 'operator', value: '|>' },
})
})
test('matches the first pipe', () => {
let code = `sketch mySketch {
lineTo(2, 3)
} |> rx(45, %) |> rx(45, %)
`
const tokens = lexer(code)
const result = hasPipeOperator(tokens, 0)
expect(result).toEqual({
index: 16,
token: { end: 37, start: 35, type: 'operator', value: '|>' },
})
if (!result) throw new Error('should not happen')
expect(code.slice(result.token.start, result.token.end)).toEqual('|>')
})
test('hasPipeOperator is false when the pipe operator is after a new variable declaration', () => {
let code = `sketch mySketch {
lineTo(2, 3)
}
const yo = myFunc(9()
|> rx(45, %)
`
const tokens = lexer(code)
expect(hasPipeOperator(tokens, 0)).toEqual(false)
})
test('hasPipeOperator with binary expression', () => {
let code = `const myVar2 = 5 + 1 |> myFn(%)`
const tokens = lexer(code)
const result = hasPipeOperator(tokens, 1)
expect(result).toEqual({
index: 12,
token: { end: 23, start: 21, type: 'operator', value: '|>' },
})
if (!result) throw new Error('should not happen')
expect(code.slice(result.token.start, result.token.end)).toEqual('|>')
})
test('hasPipeOperator of called mid sketchExpression on a callExpression, and called at the start of the sketchExpression at "{"', () => {
const code = [
'sketch mySk1 {',
' lineTo(1,1)',
' path myPath = lineTo(0, 1)',
' lineTo(1,1)',
'} |> rx(90, %)',
'show(mySk1)',
].join('\n')
const tokens = lexer(code)
const tokenWithMyPathIndex = tokens.findIndex(
({ value }) => value === 'myPath'
)
const tokenWithLineToIndexForVarDecIndex = tokens.findIndex(
({ value }, index) => value === 'lineTo' && index > tokenWithMyPathIndex
)
const result = hasPipeOperator(tokens, tokenWithLineToIndexForVarDecIndex)
expect(result).toBe(false)
const braceTokenIndex = tokens.findIndex(({ value }) => value === '{')
const result2 = hasPipeOperator(tokens, braceTokenIndex)
expect(result2).toEqual({
index: 36,
token: { end: 76, start: 74, type: 'operator', value: '|>' },
})
if (!result2) throw new Error('should not happen')
expect(code.slice(result2?.token?.start, result2?.token?.end)).toEqual('|>')
})
})
describe('testing pipe operator special', () => {
test('pipe operator with sketch', () => {
let code = `sketch mySketch {
lineTo(2, 3)
path myPath = lineTo(0, 1)
lineTo(1,1)
} |> rx(45, %)
`
const tokens = lexer(code)
const { body } = abstractSyntaxTree(tokens)
expect(body).toEqual([
{
type: 'VariableDeclaration',
start: 0,
end: 90,
kind: 'sketch',
declarations: [
{
type: 'VariableDeclarator',
start: 7,
end: 90,
id: {
type: 'Identifier',
start: 7,
end: 15,
name: 'mySketch',
},
init: {
type: 'PipeExpression',
start: 16,
end: 90,
body: [
{
type: 'SketchExpression',
start: 16,
end: 77,
body: {
type: 'BlockStatement',
start: 16,
end: 77,
body: [
{
type: 'ExpressionStatement',
start: 20,
end: 32,
expression: {
type: 'CallExpression',
start: 20,
end: 32,
callee: {
type: 'Identifier',
start: 20,
end: 26,
name: 'lineTo',
},
arguments: [
{
type: 'Literal',
start: 27,
end: 28,
value: 2,
raw: '2',
},
{
type: 'Literal',
start: 30,
end: 31,
value: 3,
raw: '3',
},
],
optional: false,
},
},
{
type: 'VariableDeclaration',
start: 35,
end: 61,
kind: 'path',
declarations: [
{
type: 'VariableDeclarator',
start: 40,
end: 61,
id: {
type: 'Identifier',
start: 40,
end: 46,
name: 'myPath',
},
init: {
type: 'CallExpression',
start: 49,
end: 61,
callee: {
type: 'Identifier',
start: 49,
end: 55,
name: 'lineTo',
},
arguments: [
{
type: 'Literal',
start: 56,
end: 57,
value: 0,
raw: '0',
},
{
type: 'Literal',
start: 59,
end: 60,
value: 1,
raw: '1',
},
],
optional: false,
},
},
],
},
{
type: 'ExpressionStatement',
start: 64,
end: 75,
expression: {
type: 'CallExpression',
start: 64,
end: 75,
callee: {
type: 'Identifier',
start: 64,
end: 70,
name: 'lineTo',
},
arguments: [
{
type: 'Literal',
start: 71,
end: 72,
value: 1,
raw: '1',
},
{
type: 'Literal',
start: 73,
end: 74,
value: 1,
raw: '1',
},
],
optional: false,
},
},
],
},
},
{
type: 'CallExpression',
start: 81,
end: 90,
callee: {
type: 'Identifier',
start: 81,
end: 83,
name: 'rx',
},
arguments: [
{
type: 'Literal',
start: 84,
end: 86,
value: 45,
raw: '45',
},
{
type: 'PipeSubstitution',
start: 88,
end: 89,
},
],
optional: false,
},
],
},
},
],
},
])
})
test('pipe operator with binary expression', () => {
let code = `const myVar = 5 + 6 |> myFunc(45, %)`
const tokens = lexer(code)
const { body } = abstractSyntaxTree(tokens)
expect(body).toEqual([
{
type: 'VariableDeclaration',
start: 0,
end: 36,
kind: 'const',
declarations: [
{
type: 'VariableDeclarator',
start: 6,
end: 36,
id: {
type: 'Identifier',
start: 6,
end: 11,
name: 'myVar',
},
init: {
type: 'PipeExpression',
start: 12,
end: 36,
body: [
{
type: 'BinaryExpression',
start: 14,
end: 19,
left: {
type: 'Literal',
start: 14,
end: 15,
value: 5,
raw: '5',
},
operator: '+',
right: {
type: 'Literal',
start: 18,
end: 19,
value: 6,
raw: '6',
},
},
{
type: 'CallExpression',
start: 23,
end: 36,
callee: {
type: 'Identifier',
start: 23,
end: 29,
name: 'myFunc',
},
arguments: [
{
type: 'Literal',
start: 30,
end: 32,
value: 45,
raw: '45',
},
{
type: 'PipeSubstitution',
start: 34,
end: 35,
},
],
optional: false,
},
],
},
},
],
},
])
})
2022-12-30 21:53:50 +11:00
test('array expression', () => {
let code = `const yo = [1, '2', three, 4 + 5]`
const tokens = lexer(code)
const { body } = abstractSyntaxTree(tokens)
expect(body).toEqual([
{
type: 'VariableDeclaration',
start: 0,
end: 33,
kind: 'const',
declarations: [
{
type: 'VariableDeclarator',
start: 6,
end: 33,
id: {
type: 'Identifier',
start: 6,
end: 8,
name: 'yo',
},
init: {
type: 'ArrayExpression',
start: 11,
end: 33,
elements: [
{
type: 'Literal',
start: 12,
end: 13,
value: 1,
raw: '1',
},
{
type: 'Literal',
start: 15,
end: 18,
value: '2',
raw: "'2'",
},
{
type: 'Identifier',
start: 20,
end: 25,
name: 'three',
},
{
type: 'BinaryExpression',
start: 27,
end: 32,
left: {
type: 'Literal',
start: 27,
end: 28,
value: 4,
raw: '4',
},
operator: '+',
right: {
type: 'Literal',
start: 31,
end: 32,
value: 5,
raw: '5',
},
},
],
},
},
],
},
])
})
2023-01-01 21:48:30 +11:00
test('object expression ast', () => {
const code = [
'const three = 3',
"const yo = {aStr: 'str', anum: 2, identifier: three, binExp: 4 + 5}",
].join('\n')
const tokens = lexer(code)
const { body } = abstractSyntaxTree(tokens)
expect(body).toEqual([
{
type: 'VariableDeclaration',
start: 0,
end: 15,
kind: 'const',
declarations: [
{
type: 'VariableDeclarator',
start: 6,
end: 15,
id: {
type: 'Identifier',
start: 6,
end: 11,
name: 'three',
},
init: {
type: 'Literal',
start: 14,
end: 15,
value: 3,
raw: '3',
},
},
],
},
{
type: 'VariableDeclaration',
start: 16,
end: 83,
kind: 'const',
declarations: [
{
type: 'VariableDeclarator',
start: 22,
end: 83,
id: {
type: 'Identifier',
start: 22,
end: 24,
name: 'yo',
},
init: {
type: 'ObjectExpression',
start: 27,
end: 83,
properties: [
{
type: 'ObjectProperty',
start: 28,
end: 39,
key: {
type: 'Identifier',
start: 28,
end: 32,
name: 'aStr',
},
value: {
type: 'Literal',
start: 34,
end: 39,
value: 'str',
raw: "'str'",
},
},
{
type: 'ObjectProperty',
start: 41,
end: 48,
key: {
type: 'Identifier',
start: 41,
end: 45,
name: 'anum',
},
value: {
type: 'Literal',
start: 47,
end: 48,
value: 2,
raw: '2',
},
},
{
type: 'ObjectProperty',
start: 50,
end: 67,
key: {
type: 'Identifier',
start: 50,
end: 60,
name: 'identifier',
},
value: {
type: 'Identifier',
start: 62,
end: 67,
name: 'three',
},
},
{
type: 'ObjectProperty',
start: 69,
end: 82,
key: {
type: 'Identifier',
start: 69,
end: 75,
name: 'binExp',
},
value: {
type: 'BinaryExpression',
start: 77,
end: 82,
left: {
type: 'Literal',
start: 77,
end: 78,
value: 4,
raw: '4',
},
operator: '+',
right: {
type: 'Literal',
start: 81,
end: 82,
value: 5,
raw: '5',
},
},
},
],
},
},
],
},
])
})
test('nested object expression ast', () => {
const code = `const yo = {key: {
key2: 'value'
}}`
const tokens = lexer(code)
const { body } = abstractSyntaxTree(tokens)
expect(body).toEqual([
{
type: 'VariableDeclaration',
start: 0,
end: 37,
kind: 'const',
declarations: [
{
type: 'VariableDeclarator',
start: 6,
end: 37,
id: {
type: 'Identifier',
start: 6,
end: 8,
name: 'yo',
},
init: {
type: 'ObjectExpression',
start: 11,
end: 37,
properties: [
{
type: 'ObjectProperty',
start: 12,
end: 36,
key: {
type: 'Identifier',
start: 12,
end: 15,
name: 'key',
},
value: {
type: 'ObjectExpression',
start: 17,
end: 36,
properties: [
{
type: 'ObjectProperty',
start: 21,
end: 34,
key: {
type: 'Identifier',
start: 21,
end: 25,
name: 'key2',
},
value: {
type: 'Literal',
start: 27,
end: 34,
value: 'value',
raw: "'value'",
},
},
],
},
},
],
},
},
],
},
])
})
test('object expression with array ast', () => {
const code = `const yo = {key: [1, '2']}`
const tokens = lexer(code)
const { body } = abstractSyntaxTree(tokens)
expect(body).toEqual([
{
type: 'VariableDeclaration',
start: 0,
end: 26,
kind: 'const',
declarations: [
{
type: 'VariableDeclarator',
start: 6,
end: 26,
id: {
type: 'Identifier',
start: 6,
end: 8,
name: 'yo',
},
init: {
type: 'ObjectExpression',
start: 11,
end: 26,
properties: [
{
type: 'ObjectProperty',
start: 12,
end: 25,
key: {
type: 'Identifier',
start: 12,
end: 15,
name: 'key',
},
value: {
type: 'ArrayExpression',
start: 17,
end: 25,
elements: [
{
type: 'Literal',
start: 18,
end: 19,
value: 1,
raw: '1',
},
{
type: 'Literal',
start: 21,
end: 24,
value: '2',
raw: "'2'",
},
],
},
},
],
},
},
],
},
])
})
2023-01-03 19:41:27 +11:00
test('object memberExpression simple', () => {
const code = `const prop = yo.one.two`
const tokens = lexer(code)
const { body } = abstractSyntaxTree(tokens)
expect(body).toEqual([
{
type: 'VariableDeclaration',
start: 0,
end: 23,
kind: 'const',
declarations: [
{
type: 'VariableDeclarator',
start: 6,
end: 23,
id: {
type: 'Identifier',
start: 6,
end: 10,
name: 'prop',
},
init: {
type: 'MemberExpression',
start: 13,
end: 23,
computed: false,
object: {
type: 'MemberExpression',
start: 13,
end: 19,
computed: false,
object: {
type: 'Identifier',
start: 13,
end: 15,
name: 'yo',
},
property: {
type: 'Identifier',
start: 16,
end: 19,
name: 'one',
},
},
property: {
type: 'Identifier',
start: 20,
end: 23,
name: 'two',
},
},
},
],
},
])
})
test('object memberExpression with square braces', () => {
const code = `const prop = yo.one["two"]`
const tokens = lexer(code)
const { body } = abstractSyntaxTree(tokens)
expect(body).toEqual([
{
type: 'VariableDeclaration',
start: 0,
end: 26,
kind: 'const',
declarations: [
{
type: 'VariableDeclarator',
start: 6,
end: 26,
id: {
type: 'Identifier',
start: 6,
end: 10,
name: 'prop',
},
init: {
type: 'MemberExpression',
start: 13,
end: 26,
computed: false,
object: {
type: 'MemberExpression',
start: 13,
end: 19,
computed: false,
object: {
type: 'Identifier',
start: 13,
end: 15,
name: 'yo',
},
property: {
type: 'Identifier',
start: 16,
end: 19,
name: 'one',
},
},
property: {
type: 'Literal',
start: 20,
end: 25,
value: 'two',
raw: '"two"',
},
},
},
],
},
])
})
test('object memberExpression with two square braces literal and identifier', () => {
const code = `const prop = yo["one"][two]`
const tokens = lexer(code)
const { body } = abstractSyntaxTree(tokens)
expect(body).toEqual([
{
type: 'VariableDeclaration',
start: 0,
end: 27,
kind: 'const',
declarations: [
{
type: 'VariableDeclarator',
start: 6,
end: 27,
id: {
type: 'Identifier',
start: 6,
end: 10,
name: 'prop',
},
init: {
type: 'MemberExpression',
start: 13,
end: 27,
computed: true,
object: {
type: 'MemberExpression',
start: 13,
end: 22,
computed: false,
object: {
type: 'Identifier',
start: 13,
end: 15,
name: 'yo',
},
property: {
type: 'Literal',
start: 16,
end: 21,
value: 'one',
raw: '"one"',
},
},
property: {
type: 'Identifier',
start: 23,
end: 26,
name: 'two',
},
},
},
],
},
])
})
})
describe('nests binary expressions correctly', () => {
it('it works with the simple case', () => {
const code = `const yo = 1 + 2`
const { body } = abstractSyntaxTree(lexer(code))
expect(body[0]).toEqual({
type: 'VariableDeclaration',
start: 0,
end: 16,
kind: 'const',
declarations: [
{
type: 'VariableDeclarator',
start: 6,
end: 16,
id: {
type: 'Identifier',
start: 6,
end: 8,
name: 'yo',
},
init: {
type: 'BinaryExpression',
start: 11,
end: 16,
left: {
type: 'Literal',
start: 11,
end: 12,
value: 1,
raw: '1',
},
operator: '+',
right: {
type: 'Literal',
start: 15,
end: 16,
value: 2,
raw: '2',
},
},
},
],
})
})
it('it should nest according to precedence with multiply first', () => {
// should be binExp { binExp { lit-1 * lit-2 } + lit}
const code = `const yo = 1 * 2 + 3`
const { body } = abstractSyntaxTree(lexer(code))
expect(body[0]).toEqual({
type: 'VariableDeclaration',
start: 0,
end: 20,
kind: 'const',
declarations: [
{
type: 'VariableDeclarator',
start: 6,
end: 20,
id: {
type: 'Identifier',
start: 6,
end: 8,
name: 'yo',
},
init: {
type: 'BinaryExpression',
start: 11,
end: 20,
left: {
type: 'BinaryExpression',
start: 11,
end: 16,
left: {
type: 'Literal',
start: 11,
end: 12,
value: 1,
raw: '1',
},
operator: '*',
right: {
type: 'Literal',
start: 15,
end: 16,
value: 2,
raw: '2',
},
},
operator: '+',
right: {
type: 'Literal',
start: 19,
end: 20,
value: 3,
raw: '3',
},
},
},
],
})
})
it('it should nest according to precedence with sum first', () => {
// should be binExp { lit-1 + binExp { lit-2 * lit-3 } }
const code = `const yo = 1 + 2 * 3`
const { body } = abstractSyntaxTree(lexer(code))
expect(body[0]).toEqual({
type: 'VariableDeclaration',
start: 0,
end: 20,
kind: 'const',
declarations: [
{
type: 'VariableDeclarator',
start: 6,
end: 20,
id: {
type: 'Identifier',
start: 6,
end: 8,
name: 'yo',
},
init: {
type: 'BinaryExpression',
start: 11,
end: 20,
left: {
type: 'Literal',
start: 11,
end: 12,
value: 1,
raw: '1',
},
operator: '+',
right: {
type: 'BinaryExpression',
start: 15,
end: 20,
left: {
type: 'Literal',
start: 15,
end: 16,
value: 2,
raw: '2',
},
operator: '*',
right: {
type: 'Literal',
start: 19,
end: 20,
value: 3,
raw: '3',
},
},
},
},
],
})
})
it('it should nest properly with two opperators of equal precedence', () => {
const code = `const yo = 1 + 2 - 3`
const { body } = abstractSyntaxTree(lexer(code))
expect((body[0] as any).declarations[0].init).toEqual({
type: 'BinaryExpression',
start: 11,
end: 20,
left: {
type: 'BinaryExpression',
start: 11,
end: 16,
left: {
type: 'Literal',
start: 11,
end: 12,
value: 1,
raw: '1',
},
operator: '+',
right: {
type: 'Literal',
start: 15,
end: 16,
value: 2,
raw: '2',
},
},
operator: '-',
right: {
type: 'Literal',
start: 19,
end: 20,
value: 3,
raw: '3',
},
})
})
it('it should nest properly with two opperators of equal (but higher) precedence', () => {
const code = `const yo = 1 * 2 / 3`
const { body } = abstractSyntaxTree(lexer(code))
expect((body[0] as any).declarations[0].init).toEqual({
type: 'BinaryExpression',
start: 11,
end: 20,
left: {
type: 'BinaryExpression',
start: 11,
end: 16,
left: {
type: 'Literal',
start: 11,
end: 12,
value: 1,
raw: '1',
},
operator: '*',
right: {
type: 'Literal',
start: 15,
end: 16,
value: 2,
raw: '2',
},
},
operator: '/',
right: {
type: 'Literal',
start: 19,
end: 20,
value: 3,
raw: '3',
},
})
})
it('it should nest properly with longer example', () => {
const code = `const yo = 1 + 2 * (3 - 4) / 5 + 6`
const { body } = abstractSyntaxTree(lexer(code))
const init = (body[0] as any).declarations[0].init
expect(init).toEqual({
type: 'BinaryExpression',
operator: '+',
start: 11,
end: 34,
left: {
type: 'BinaryExpression',
operator: '+',
start: 11,
end: 30,
left: { type: 'Literal', value: 1, raw: '1', start: 11, end: 12 },
right: {
type: 'BinaryExpression',
operator: '/',
start: 15,
end: 30,
left: {
type: 'BinaryExpression',
operator: '*',
start: 15,
end: 26,
left: { type: 'Literal', value: 2, raw: '2', start: 15, end: 16 },
right: {
type: 'BinaryExpression',
operator: '-',
start: 20,
end: 25,
left: { type: 'Literal', value: 3, raw: '3', start: 20, end: 21 },
right: {
type: 'Literal',
value: 4,
raw: '4',
start: 24,
end: 25,
},
},
},
right: { type: 'Literal', value: 5, raw: '5', start: 29, end: 30 },
},
},
right: { type: 'Literal', value: 6, raw: '6', start: 33, end: 34 },
})
})
})
describe('testing findEndofBinaryExpression', () => {
it('1 + 2 * 3', () => {
const code = `1 + 2 * 3\nconst yo = 5`
const tokens = lexer(code)
const end = findEndOfBinaryExpression(tokens, 0)
expect(end).toBe(8)
})
it('(1 + 2) / 5 - 3', () => {
const code = `(1 + 25) / 5 - 3\nconst yo = 5`
const tokens = lexer(code)
const end = findEndOfBinaryExpression(tokens, 0)
expect(end).toBe(14)
// expect to have the same end if started later in the string at a legitimate place
const indexOf5 = code.indexOf('5')
const endStartingAtThe5 = findEndOfBinaryExpression(tokens, indexOf5)
expect(endStartingAtThe5).toBe(end)
})
it('whole thing wraped: ((1 + 2) / 5 - 3)', () => {
const code = '((1 + 2) / 5 - 3)\nconst yo = 5'
const tokens = lexer(code)
const end = findEndOfBinaryExpression(tokens, 0)
expect(end).toBe(code.indexOf('3)') + 1)
})
it('whole thing wraped but given index after the first brace: ((1 + 2) / 5 - 3)', () => {
const code = '((1 + 2) / 5 - 3)\nconst yo = 5'
const tokens = lexer(code)
const end = findEndOfBinaryExpression(tokens, 1)
expect(end).toBe(code.indexOf('3'))
})
it('given the index of a small wrapped section i.e. `1 + 2` in ((1 + 2) / 5 - 3)', () => {
const code = '((1 + 2) / 5 - 3)\nconst yo = 5'
const tokens = lexer(code)
const end = findEndOfBinaryExpression(tokens, 2)
expect(end).toBe(code.indexOf('2'))
})
it('lots of silly nesting: (1 + 2) / (5 - (3))', () => {
const code = '(1 + 2) / (5 - (3))\nconst yo = 5'
const tokens = lexer(code)
const end = findEndOfBinaryExpression(tokens, 0)
expect(end).toBe(code.indexOf('))') + 1)
})
it('with pipe operator at the end', () => {
const code = '(1 + 2) / (5 - (3))\n |> fn(%)'
const tokens = lexer(code)
const end = findEndOfBinaryExpression(tokens, 0)
expect(end).toBe(code.indexOf('))') + 1)
})
})
Add the ability to recast comments and some whitespace (#10) * Add the ability to recast comments and some whitespace Currently because whitespace or anything that's not needed for execution is not stored in the AST, it's hard to respect things like user formatting when recasting. I think having a by-default-opinioned formatter is a good thing, but where this becomes problematic is when users wants to simply leave a blank space between some lines for a bit of breathing room, a code paragraph if you will, but maybe more importantly comments have not been implemented for the same reason, there wasn't a way with the current setup to insert them back in. In some ways the most straightforward way to do this is to put whitespace and comments into the AST. Even though they are not crucial for execution, code-gen/recasting needs to be a first-class citizen in this lang so that's probably the long-term solution. However I'm trying to draw inspiration from other languages, and since it's not the norm to put comments et-al into the AST I haven't done so. Because whitespace is tokenised already if not transformed into the AST, there is somewhat of a map of these things without going back to source code, so atm I'm experimenting with using this to insert extra linebreaks and comments back in between statements. I think this is a good compromise for the time being for what is a nice to have feature atm. Because it's only going to respect non-code parts in between statements this will mean that you can't format objects or function params how you like (but I think this is good to have an opinioned fmt out of the box) and comments like myFunctionCall('a', /* inline comment */ b) will not work either. * clean up
2023-01-23 14:50:58 +11:00
describe('testing code with comments', () => {
it('should ignore line comments', () => {
const comment = '// this is a comment'
const codeWithComment = `const yo = 5
${comment}
const yo2 = 6`
// filling with extra whitespace to make the source start end numbers match
const codeWithoutComment = `const yo = 5
${comment
.split('')
.map(() => ' ')
.join('')}
const yo2 = 6`
const { body } = abstractSyntaxTree(lexer(codeWithComment))
const { body: bodyWithoutComment } = abstractSyntaxTree(
lexer(codeWithoutComment)
)
expect(body).toEqual(bodyWithoutComment)
})
it('should ignore block comments', () => {
const comment = `/* this is a
multi line
comment */`
const codeWithComment = `const yo = 5${comment}
const yo2 = 6`
// filling with extra whitespace to make the source start end numbers match
const codeWithoutComment = `const yo = 5${comment
.split('')
.map(() => ' ')
.join('')}
const yo2 = 6`
const { body } = abstractSyntaxTree(lexer(codeWithComment))
const { body: bodyWithoutComment } = abstractSyntaxTree(
lexer(codeWithoutComment)
)
expect(body).toEqual(bodyWithoutComment)
})
it('comment in function declaration', () => {
const code = `const yo=(a)=>{
// this is a comment
return a
}`
const { body } = abstractSyntaxTree(lexer(code))
const yo = [
{
type: 'VariableDeclaration',
start: 0,
end: 51,
kind: 'const',
declarations: [
{
type: 'VariableDeclarator',
start: 6,
end: 51,
id: { type: 'Identifier', start: 6, end: 8, name: 'yo' },
init: {
type: 'FunctionExpression',
start: 9,
end: 51,
id: null,
params: [{ type: 'Identifier', start: 10, end: 11, name: 'a' }],
body: {
type: 'BlockStatement',
start: 14,
end: 51,
body: [
{
type: 'ReturnStatement',
start: 41,
end: 49,
argument: {
type: 'Identifier',
start: 48,
end: 49,
name: 'a',
},
},
],
},
},
},
],
},
]
expect(body).toEqual(yo)
})
})